🎯 什麼情境該想到我
分工:這張是「動手寫註解前的五秒判準」。四種註解的完整分類、Go 必寫的五類註解、最該刪的三種 → 註釋;Go doc comment 的格式慣例 → 工具-Go命名慣例。
當你正想加一段註解來「解釋」一段程式碼時,先停一下。
⚙️ 怎麼用
- 先試著用程式碼表達:把那段抽成一個命名清楚的函式,往往就不需要註解了。
- 註解是表達的失敗:能用命名/結構說清楚就別靠註解。
- 好註解:法律聲明、意圖說明、警告、TODO、對公開 API 的說明。
- 壞註解:多餘(複述程式碼)、誤導、被註解掉的死程式碼(直接刪,交給版本控制)、跟程式不同步的過時註解。
★ 寫註解前問四個問題(依序,前一題能解就不用往下)
| # | 問題 | 能解 → 做這個,不寫註解 |
|---|---|---|
| 1 | 能不能換個名字說清楚? | 改名:d → elapsedDays;check() → isEligibleForRefund() |
| 2 | 能不能抽成函式,讓函式名說清楚? | 提煉:// 計算含稅價 下面那 6 行 → priceWithTax(order) |
| 3 | 能不能用型別說清楚? | timeout int // 毫秒 → timeout time.Duration,單位跟著型別走,註解自然消失 |
| 4 | 能不能用測試或範例說清楚? | 寫一個 test case 或 Go 的 Example 函式(見下) |
四題都答「不能」——剩下的就是程式碼本身表達不了的東西,這時註解不是失敗,是必要:
- 為什麼這樣寫(Why):「這裡刻意不用
sync.Pool:物件平均存活 > 1 個 GC 週期,pool 反而增加壓力(benchmark 見 #1234)」 - 為什麼不那樣寫(Why not):擋住下一個人「好心」改回看起來比較直覺、但你已經踩過雷的寫法
- 外部約束:「上游 API 對同一 key 每秒只准 1 次,所以這裡要 sleep」——這是程式碼外的事實
- 不變式/前置條件:「呼叫前必須持有
mu」「items必須已排序」
判準一句話:註解應該說程式碼說不出的事(why),而不是重述程式碼已經說了的事(what)。
★ 讓註解不會過時的唯一可靠方法:讓它被執行
「過時註解比沒有更糟」的根因是:編譯器和測試都不檢查註解。所以真正有效的做法是把「要說明的行為」搬到會被執行的地方:
// 放在 _test.go 裡;go test 會實際執行它,並比對 // Output: 之後的內容
func ExampleParseDuration() {
d, _ := ParseDuration("1h30m")
fmt.Println(d.Minutes())
// Output: 90
}- 這段會出現在 godoc 當範例,又是測試——行為一改、輸出對不上,
go test就紅。註解做不到這件事。 - 輸出順序不固定(例如走訪 map)時用
// Unordered output:。 - 沒有
// Output:的 Example 只編譯不執行——還是比註解強,至少 API 改名時會編譯失敗。 - TS/JS 對應物:把用法寫成測試案例(
it('parses 1h30m as 90 minutes', …)),測試名本身就是規格。
★ 註解的「維護成本帳」
每一行註解都是一份沒有編譯器幫你檢查的副本。程式碼改了 N 次,註解要被人記得同步 N 次;只要漏一次,它就從「說明」變成「誤導」。所以:
- what 註解(重述程式碼):價值 ≈ 0,維護成本 > 0 → 負資產,刪。
- why 註解:價值高,而且 why 很少跟著實作細節變 → 正資產,留。
- TODO:只在附上 issue 編號時留(
// TODO(#482): 改成批次查詢);沒編號的 TODO 通常永遠不會被做,golangci-lint 的godox可以把它們全列出來盤點。
🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意
-
註解不會隨程式碼自動更新 → 過時註解比沒有更糟。
-
別把《程式碼整潔之道》的「註解是失敗」讀成「不准寫註解」:它針對的是 what 註解。公開 API 的文件註解、why 註解、法律聲明、警告都是它自己列為「好註解」的類別。
-
被註解掉的程式碼一律刪。它不會被編譯、不會被測試,半年後沒人敢刪也沒人敢用;要找回來有
git log -S。
🔗 相關工具
- 工具-有意義的命名 —— 第一替代方案,多數註解可以被一個好名字取代
- 工具-函式短小且單一職責 —— 第二替代方案,需要註解分段時通常代表該拆函式了