Skip to main content

原始碼品質檢查清單 (Code Quality Checklist)

Move 語言及其生態系統的快速演進,已使許多較舊的實務做法過時。本指南作為開發者檢視其原始碼的檢查清單,以確保其符合目前 Move 開發的最佳實務。請仔細閱讀,並盡可能將更多建議套用至你的原始碼。

原始碼組織 (Code Organization)​

本指南提到的部分問題可以透過使用 Move Formatter 解決,可將其作為 CLI 工具使用、作為 CI 檢查,或作為 VSCode(Cursor)的外掛程式。

套件清單 (Package Manifest)​

使用正確版本 (Use Right Edition)​

本指南中的所有功能都需要 Move 2024 版本,且必須在套件清單中指定。

[package]
name = "my_package"
edition = "2024.beta" # 或(僅)使用 "2024"

隱含的框架依賴項 (Implicit Framework Dependency)​

從 Sui 1.45 開始,你不再需要在 Move.toml 中指定框架依賴項:

# 舊版,1.45 之前
[dependencies]
Sui = { ... }

# 現代作法:Sui、Bridge、MoveStdlib 和 SuiSystem 會隱含匯入!
[dependencies]

為具名地址加上前綴 (Prefix Named Addresses)​

如果你的套件名稱很通用(例如 token)——尤其是專案包含多個套件時——請務必為具名地址加上前綴:

# 不佳!無法表示任何意義,也可能造成衝突
[addresses]
math = "0x0"

# 良好!清楚表明專案,不太可能發生衝突
[addresses]
my_protocol_math = "0x0"

匯入、模組與常數 (Imports, Module and Constants)​

使用模組標籤 (Using Module Label)​

// 不佳:會增加縮排,舊版風格
module my_package::my_module {
public struct A {}
}

// 良好!
module my_package::my_module;

public struct A {}

use 陳述式中不單獨使用 Self (No Single Self in use Statements)​

// 正確:成員 + self 匯入
use my_package::other::{Self, OtherMember};

// 不佳!`{Self}` 是多餘的
use my_package::my_module::{Self};

// 良好!
use my_package::my_module;

使用 Self 分組 use 陳述式 (Group use Statements with Self)​

// 不佳!
use my_package::my_module;
use my_package::my_module::OtherMember;

// 良好!
use my_package::my_module::{Self, OtherMember};

錯誤常數採用 EPascalCase (Error Constants Are in EPascalCase)​

// 不佳!全大寫用於一般常數
const NOT_AUTHORIZED: u64 = 0;

// 良好!清楚表示這是錯誤常數
const ENotAuthorized: u64 = 0;

一般常數採用 ALL_CAPS (Regular Constants Are ALL_CAPS)​

// 不佳!PascalCase 與錯誤常數相關
const MyConstant: vector<u8> = "my const";

// 良好!清楚表示這是常數值
const MY_CONSTANT: vector<u8> = "my const";

結構 (Structs)​

功能憑證以 Cap 為後綴 (Capabilities are Suffixed with Cap)​

// 不佳!若是功能憑證,請加上 `Cap` 後綴
public struct Admin has key, store {
id: UID,
}

// 良好!審查者知道可從型別預期什麼
public struct AdminCap has key, store {
id: UID,
}

名稱中不要使用 Potato (No Potato in Names)​

// 不佳!它沒有能力,我們已經知道它是 Hot-Potato 型別
public struct PromisePotato {}

// 良好!
public struct Promise {}

事件應以過去式命名 (Events Should Be Named in Past Tense)​

// 不佳!不清楚這個結構的用途
public struct RegisterUser has copy, drop { user: address }

// 良好!很明確,這是一個事件
public struct UserRegistered has copy, drop { user: address }

動態欄位鍵使用位置結構與 Key 後綴 (Use Positional Structs for Dynamic Field Keys + Key Suffix)​

// 不算太差,但違反了標準風格
public struct DynamicField has copy, drop, store {}

// 良好!標準風格,使用 Key 後綴
public struct DynamicFieldKey() has copy, drop, store;

函式 (Functions)​

不使用 public entry,僅使用 public 或 entry (No public entry, Only public or entry)​

// 不佳!函式可在交易中呼叫,不需要 entry
public entry fun do_something() { /* ... */ }

// 良好!public 函式更具彈性,可以回傳值
public fun do_something_2(): T { /* ... */ }

為 PTB 撰寫可組合函式 (Write Composable Functions for PTBs)​

// 不佳!不可組合,較難測試!
public fun mint_and_transfer(ctx: &mut TxContext) {
/* ... */
transfer::transfer(nft, ctx.sender());
}

// 良好!可組合!
public fun mint(ctx: &mut TxContext): NFT { /* ... */ }

// 良好!刻意設計為不可組合
entry fun mint_and_keep(ctx: &mut TxContext) { /* ... */ }

物件優先(Clock 除外) (Objects Go First (Except for Clock))​

// 不佳!難以閱讀!
public fun call_app(
value: u8,
app: &mut App,
is_smth: bool,
cap: &AppCap,
clock: &Clock,
ctx: &mut TxContext,
) { /* ... */ }

// 良好!
public fun call_app(
app: &mut App,
cap: &AppCap,
value: u8,
is_smth: bool,
clock: &Clock,
ctx: &mut TxContext,
) { /* ... */ }

能力物件置於第二位 (Capabilities Go Second)​

// 不佳!破壞方法關聯性
public fun authorize_action(cap: &AdminCap, app: &mut App) { /* ... */ }

// 良好!讓 Cap 在函式簽章中保持可見,並維持 `.calls()`
public fun authorize_action(app: &mut App, cap: &AdminCap) { /* ... */ }

取值函式以欄位名稱加上 _mut 命名 (Getters Named After Field + _mut)​

// 不佳!不必要的 `get_`
public fun get_name(u: &User): String { /* ... */ }

// 良好!明確表示其存取 `name` 欄位
public fun name(u: &User): String { /* ... */ }

// 良好!可變參考使用 `_mut`
public fun details_mut(u: &mut User): &mut Details { /* ... */ }

函式主體:結構方法 (Function Body: Struct Methods)​

常見的 Coin 操作 (Common Coin Operations)​

// 不佳!舊版原始碼,難以閱讀!
let paid = coin::split(&mut payment, amount, ctx);
let balance = coin::into_balance(paid);

// 良好!結構方法讓操作更容易!
let balance = payment.split(amount, ctx).into_balance();

// 更佳(在此範例中,無須建立暫存 Coin)
let balance = payment.balance_mut().split(amount);

// 也可以這樣做!
let coin = balance.into_coin(ctx);

請勿匯入 std::string::utf8 (Do Not Import std::string::utf8)​

// 不佳!很遺憾,這非常常見!
use std::string::utf8;

let str = utf8(b"hello, world!");

// 良好!字面值會在編譯時期檢查
let str: String = "hello, world!";

// 也適用於 ASCII 字串
let ascii: ascii::String = "hello, world!";

vector<u8> 上的 .to_string() 與 .to_ascii_string() 方法仍有其用途—— 用於轉換在編譯時期未知的位元組。對於字面值,請優先使用字串字面值 語法。

UID 具有 delete (UID has delete)​

// 不佳!
object::delete(id);

// 良好!
id.delete();

ctx 具有 sender() (ctx has sender())​

// 不佳!
tx_context::sender(ctx);

// 良好!
ctx.sender()

Vector 具有字面值與關聯函式 (Vector Has a Literal. And Associated Functions)​

// 不佳!
let mut my_vec = vector::empty();
vector::push_back(&mut my_vec, 10);
let first_el = vector::borrow(&my_vec);
assert!(vector::length(&my_vec) == 1);

// 良好!
let mut my_vec = vector[10];
let first_el = my_vec[0];
assert!(my_vec.length() == 1);

集合支援索引語法 (Collections Support Index Syntax)​

let x: VecMap<u8, String> = /* ... */;

// 不佳!
x.get(&10);
x.get_mut(&10);

// 良好!
&x[&10];
&mut x[&10];

Option 到巨集 (Option -> Macros)​

解構並呼叫函式 (Destroy And Call Function)​

// 不佳!
if (opt.is_some()) {
let inner = opt.destroy_some();
call_function(inner);
};

// 良好!有對應的巨集!
opt.do!(|value| call_function(value));

使用預設值解構 Some (Destroy Some With Default)​

let opt = option::none();

// 不佳!
let value = if (opt.is_some()) {
opt.destroy_some()
} else {
abort EError
};

// 良好!有對應的巨集!
let value = opt.destroy_or!(default_value);

// 你甚至可以在 `none` 時中止
let value = opt.destroy_or!(abort ECannotBeEmpty);

迴圈 -> 巨集 (Loops -> Macros)​

執行操作 N 次 (Do Operation N Times)​

// 不佳!難以閱讀!
let mut i = 0;
while (i < 32) {
do_action();
i = i + 1;
};

// 良好!任何 uint 都有這個巨集!
32u8.do!(|_| do_action());

從迭代建立新向量 (New Vector From Iteration)​

// 較難閱讀!
let mut i = 0;
let mut elements = vector[];
while (i < 32) {
elements.push_back(i);
i = i + 1;
};

// 易於閱讀!
vector::tabulate!(32, |i| i);

對向量的每個元素執行操作 (Do Operation on Every Element of a Vector)​

// 不佳!
let mut i = 0;
while (i < vec.length()) {
call_function(&vec[i]);
i = i + 1;
};

// 良好!
vec.do_ref!(|e| call_function(e));

銷毀向量並對每個元素呼叫函式 (Destroy a Vector and Call a Function on Each Element)​

// 不佳!
while (!vec.is_empty()) {
call(vec.pop_back());
};

// 良好!
vec.destroy!(|e| call(e));

將向量摺疊為單一值 (Fold Vector Into a Single Value)​

// 不佳!
let mut aggregate = 0;
let mut i = 0;

while (i < source.length()) {
aggregate = aggregate + source[i];
i = i + 1;
};

// 良好!
let aggregate = source.fold!(0, |acc, v| {
acc + v
});

篩選向量的元素 (Filter Elements of the Vector)​

注意:source 向量中的 T: drop

// 不佳!
let mut filtered = [];
let mut i = 0;
while (i < source.length()) {
if (source[i] > 10) {
filtered.push_back(source[i]);
};
i = i + 1;
};

// 良好!
let filtered = source.filter!(|e| e > 10);

其他 (Other)​

解構中的忽略值可完全省略 (Ignored Values In Unpack Can Be Ignored Altogether)​

// 不好!非常稀疏!
let MyStruct { id, field_1: _, field_2: _, field_3: _ } = value;
id.delete();

// 好!2024 語法
let MyStruct { id, .. } = value;
id.delete();

測試 (Testing)​

合併 #[test] 與 #[expected_failure(...)] (Merge #[test] and #[expected_failure(...)])​

// 不佳!
#[test]
#[expected_failure]
fun value_passes_check() {
abort
}

// 良好!
#[test, expected_failure]
fun value_passes_check() {
abort
}

請勿清理 expected_failure 測試 (Do Not Clean Up expected_failure Tests)​

// 不佳!無須清理
#[test, expected_failure(abort_code = my_app::EIncorrectValue)]
fun try_take_missing_object_fail() {
let mut test = test_scenario::begin(@0);
my_app::call_function(test.ctx());
test.end();
}

// 良好!容易看出預期在哪裡失敗
#[test, expected_failure(abort_code = my_app::EIncorrectValue)]
fun try_take_missing_object_fail() {
let mut test = test_scenario::begin(@0);
my_app::call_function(test.ctx());

abort // 將與 EIncorrectValue 不同
}

測試模組中的測試函式請勿以 test_ 為前綴 (Do Not Prefix Tests With test_ in Testing Modules)​

// 不佳!模組已命名為 _tests
module my_package::my_module_tests;

#[test]
fun test_this_feature() { /* ... */ }

// 良好!因此可使用更好的函式名稱
#[test]
fun this_feature_works() { /* ... */ }

非必要時請勿使用 TestScenario (Do Not Use TestScenario Where Not Necessary)​

// 不佳!不需要,僅使用 ctx
let mut test = test_scenario::begin(@0);
let nft = app::mint(test.ctx());
app::destroy(nft);
test.end();

// 良好!簡單情況有虛擬 context 可用
let ctx = &mut tx_context::dummy();
app::mint(ctx).destroy();

測試中的 assert! 請勿使用中止碼 (Do Not Use Abort Codes in assert! in Tests)​

// 不佳!可能意外符合應用程式錯誤碼
assert!(is_success, 0);

// 良好!
assert!(is_success);

盡可能使用 assert_eq! (Use assert_eq! Whenever Possible)​

// 不佳!舊式原始碼
assert!(result == "expected_value", 0);

// 良好!若失敗將印出兩個值
use std::unit_test::assert_eq;

assert_eq!(result, expected_value);

使用「黑洞」destroy 函式 (Use "Black Hole" destroy Function)​

// 不佳!
nft.destroy_for_testing();
app.destroy_for_testing();

// 良好!無須為清理定義特殊函式
use std::unit_test::destroy;

destroy(nft);
destroy(app);

註解 (Comments)​

文件註解以 /// 開頭 (Doc Comments Start With ///)​

// 不佳!工具不支援 JavaDoc 樣式的註解
/**
* 很酷的方法
* @param ...
*/
public fun do_something() { /* ... */ }

// 良好!將在 docgen 與 IDE 中顯示為文件註解
/// 很酷的方法!
public fun do_something() { /* ... */ }

複雜邏輯?留下註解 // (Complex Logic? Leave a Comment //)​

保持友善,並協助檢閱者理解程式碼!

// 良好!
// 注意:若值小於 10,可能發生下溢位。
// TODO:在此加入 `assert!`
let value = external_call(value, ctx);