🎯 什麼情境該想到我

分工:這張是「動手寫註解前的五秒判準」。四種註解的完整分類、Go 必寫的五類註解、最該刪的三種 → 註釋;Go doc comment 的格式慣例 → 工具-Go命名慣例。

當你正想加一段註解來「解釋」一段程式碼時,先停一下。

⚙️ 怎麼用

  1. 先試著用程式碼表達:把那段抽成一個命名清楚的函式,往往就不需要註解了。
  2. 註解是表達的失敗:能用命名/結構說清楚就別靠註解。
  3. 好註解:法律聲明、意圖說明、警告、TODO、對公開 API 的說明。
  4. 壞註解:多餘(複述程式碼)、誤導、被註解掉的死程式碼(直接刪,交給版本控制)、跟程式不同步的過時註解。

★ 寫註解前問四個問題(依序,前一題能解就不用往下)

#問題能解 → 做這個,不寫註解
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。

🔗 相關工具