Skip to main content

執行檢查程式碼風格 (Running Lints)

Move 編譯器隨附一組 lints(靜態程式碼檢查工具),會在編譯時標記程式碼中可疑的模式。測試驗證程式碼是否做了它該做的事;lints 則抓出那些能通過編譯、甚至能通過測試,但卻不是經驗豐富的 Move 開發者會寫出來的程式碼:破壞可組合性的轉移(transfer)、看起來像做了某件事但實際上永遠不會如此運作的比較、或永遠無法被呼叫的 entry 函式。定期執行 lints——並讓套件保持沒有警告的狀態——是維持程式碼品質的低成本做法。

執行檢查程式碼風格 (Running Lints)

sui move lint 指令會編譯套件並執行完整的 linter 集合:

sui move lint

若要同時檢查 tests 目錄中的程式碼,加上 --test 旗標:

sui move lint --test

其他指令也可透過 --lint 旗標使用相同的檢查——例如,sui move test --lint 會一次執行測試以及完整的 lint 集合。

考慮一個模組,其中有個函式會將剛建立的物件轉移給交易發送者:

module book::mint;

public struct Item has key, store { id: UID }

public fun mint(ctx: &mut TxContext) {
let item = Item { id: object::new(ctx) };
transfer::transfer(item, ctx.sender());
}

執行 linter 會印出一則警告,附帶說明及指向確切運算式的指標:

warning[Lint W99001]: non-composable transfer to sender
┌─ ./sources/mint.move:7:5

5 │ public fun mint(ctx: &mut TxContext) {
│ ---- Returning an object from a function, allows a caller to use the object and enables composability via programmable transactions.
6 │ let item = Item { id: object::new(ctx) };
7 │ transfer::transfer(item, ctx.sender());
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ │ │
│ │ Transaction sender address coming from here
│ Transfer of an object to transaction sender address

= This warning can be suppressed with '#[allow(lint(self_transfer))]' applied to the 'module' or module member ('const', 'fun', or 'struct')

這個 lint 建議的修正方式是從函式中回傳 Item,而不是直接轉移它,讓呼叫端自行決定該如何處理該物件。

預設與額外檢查 (Default and Extra Lints)

Lints 分成兩個層級。default(預設)層級包含最重要的 Sui 專屬檢查,會在每次編譯時執行——一般的 sui move buildsui move test 也會回報這些警告。extra(額外)層級則新增了兩項 Sui 檢查以及一組程式碼風格 lints;當明確要求執行檢查時——透過 sui move lint--lint 旗標——才會執行。

抑制檢查 (Suppressing Lints)

Lints 是啟發式的(heuristic),有時被標記的程式碼是刻意寫成那樣的。可以用 #[allow(lint(<name>))] 屬性來抑制某個 lint,套用在模組或模組成員上,使用警告中印出的 lint 名稱:

public struct Account has key { id: UID }

/// 一個帳戶物件,刻意為 sender 建立並由其擁有。
#[allow(lint(self_transfer))]
public fun new_account(ctx: &mut TxContext) {
transfer::transfer(
Account { id: object::new(ctx) },
ctx.sender(),
);
}

單一屬性可以抑制多個 lints:#[allow(lint(share_owned, self_transfer))]。把抑制當成其他例外狀況一樣處理——盡量縮小範圍(優先選函式而非整個模組),並在註解或文件註解中說明原因。

CI 中的檢查程式碼風格 (Lints in CI)

若要強制執行無警告的程式碼庫,加上 --warnings-are-errors 旗標——這樣一來,只要有任何警告(包括 lints),指令就會以非零結束碼失敗:

sui move lint --test --warnings-are-errors

若工具需要以程式化方式解析輸出,--json-errors 可將診斷訊息切換為 JSON 格式。

檢查程式碼風格參考 (Lint Reference)

Linter 將其檢查項目分為兩組:每次編譯都會執行的 default lints,以及只在 --lint 旗標下才會執行的 extra lints。

預設檢查 (Default Lints)

這些會在每次編譯時執行:

Lint代碼標記內容
share_ownedW99000分享一個可能先前已被擁有的物件;應在建立物件的同一筆交易中分享物件
self_transferW99001將新物件轉移給發送者,而非直接回傳它;會損害可組合性
custom_state_changeW99002在具有 store 能力的型別上自訂轉移/分享/凍結策略;public_* 儲存函式 可以繞過它
coin_fieldW99003型別為 Coin<T> 的 struct 欄位;Balance<T> 成本較低,通常是更好的選擇
freeze_wrappedW99004凍結一個包裹著其他物件的物件
collection_equalityW99005使用 == 比較動態集合;只會比較 idsize,不會比較內容
public_randomW99006接受 Randompublic 函式;會將隨機性暴露給組合攻擊
missing_keyW99007具有 id: UID 欄位但缺少 key 能力的 struct
public_entryW99010public 函式上不必要的 entry 修飾詞
uncallable_functionW99011永遠無法在交易中被呼叫的函式,例如接受 &mut Clockentry 函式

額外檢查 (Extra Lints)

sui move lint--lint 旗標啟用:

Lint代碼標記內容
freezing_capabilityW99008凍結一個看起來像能力(capability)的型別
prefer_mut_tx_contextW99009接受 &TxContextpublic 函式;建議改用 &mut TxContext,以保持簽章面向未來的彈性

extra 層級還包含程式碼風格 lints(代碼 W04xxx):constant_namingwhile_trueunnecessary_mathunneeded_returnabort_without_constantloop_without_exitunnecessary_conditionalself_assignmentredundant_ref_derefunnecessary_unitalways_equal_operands,以及 combinable_comparisons。每一項都會標記一個小的可讀性或正確性問題,並提出更簡潔的等價寫法。

總結 (Summary)

指令說明
sui move lint編譯套件並執行完整的 lint 集合
sui move lint --test同時檢查 tests 目錄中的程式碼
sui move lint --warnings-are-errors任何警告都會導致失敗——適用於 CI
sui move build / sui move test執行 default lint 層級
sui move test --lint以完整的 lint 集合執行測試
--no-lint完全停用 linters

延伸閱讀 (Further Reading)