🎯 什麼情境該想到我

當你「兩種寫法都對、但不知道該選哪個」的時候——尤其是 review 時兩個人各有道理、吵不出結論的時候。

⚙️ 怎麼用(步驟 / 公式)

1. 用這個排序當裁決器

可讀性不是一個模糊的整體,是五個有優先序的屬性:

序屬性判準
1Clarity 清楚讀者看得懂它在做什麼、以及為什麼
2Simplicity 簡單用最簡單的方式達成目標(行為與效能上都是)
3Concision 精簡訊噪比高,重點不被雜訊蓋掉
4Maintainability 可維護下一個人容易正確地改
5Consistency 一致跟更大範圍的 codebase 一致

吵架時就問:「這兩個寫法,在第幾條上分出勝負?」 排名靠前的那條贏。

2. ⭐ 一致性是打平時的 tie-breaker,不是理由

原文寫得很精準:

一致性的考量不凌駕上面任何一條原則;但如果必須打破平手,往一致性那邊倒通常是有益的。

所以「旁邊都這樣寫」不足以壓過「這樣寫更清楚」。但如果兩個寫法在清楚/簡單/精簡/可維護上都分不出高下,那就跟旁邊一致。

一致性的優先範圍:同 package 內的一致性最要緊(同一個問題在一個 package 裡有三種解法、同一個概念在一個檔案裡有三個名字,都很刺眼),但仍不能凌駕已文件化的風格原則。

3. Clarity 要分兩個問題問

  • 「這段在做什麼?」 → 用更描述性的變數名、切成獨立函式、用空行與註解分段。
  • 「為什麼這樣做?」 → 這個只能靠註解。特別是當程式碼帶有讀者不熟的眉角時:語言層的眉角(閉包捕捉了很遠之外的迴圈變數)、業務層的眉角(權限檢查要分辨「本人」與「模擬本人的管理員」)、為了效能而寫得難懂的地方。

註解解釋 why,不要重述 what。 重述 what 的註解會變成雜訊、跟程式碼矛盾、還多一份要維護的東西。

4. ⭐ 最少機制(least mechanism):抽象的升級階梯

同一件事有多種表達方式時,用最標準的工具,按這個順序往上爬,能停就停:

  1. 核心語言構件 — channel、slice、map、loop、struct
  2. 標準函式庫 — HTTP client、template engine…
  3. 專案/公司既有的核心庫
  4. 才考慮新依賴,或自己造一個

理由:要加複雜度隨時可以加,但發現複雜度沒必要之後要移除就難得多。

實例:

// 需要集合成員檢查 → map 就夠了,不要為此引入 set 套件
seen := map[string]bool{}
 
// 測試要覆蓋 flag 綁定的值 → 直接改變數,不要用 flag.Set
// (除非你測的就是 CLI 本身)
pollInterval = 5 * time.Second

5. 刻意加的複雜度要留下說明

這條原則不是「不准寫複雜的 code」。它要的是:codebase 裡沒有無謂的複雜度,於是複雜度出現時本身就是一個訊號——告訴讀者「這裡需要小心」。

所以複雜的地方要附上:為什麼需要(通常是效能)、要注意什麼、以及示範正確用法的測試或 example。典型情境是預先配置一塊 buffer 在整個 goroutine 生命週期重複使用——維護者看到就知道這段是效能敏感的。

6. 區域一致性(local consistency)的邊界

風格指南沒規定的地方,照你喜歡的寫;但如果附近的程式碼已經有一致立場,就跟它一致。

  • ✅ 算有效的在地風格:錯誤格式化用 %s 還是 %v;用 buffered channel 取代 mutex。
  • ❌ 不算的:自訂行長限制;使用 assertion 風格的測試函式庫。

止血條款(很重要):如果這次改動會讓既有的偏差惡化、擴散到更多檔案、露出到更多 API 表面,或引入真正的 bug,「跟旁邊一致」就不再是正當理由。 這時該在同一個 CL 順手清乾淨、或先做一次前置重構。

🧪 我實際套用的紀錄

  • 2026-09-20:(待填)

⚠️ 注意 / 什麼時候不適用

  • 這五條是排序,不是評分表。 不要拿來幫每個寫法打分數加總;它的用途是在爭執時指出「你們其實是在不同層級上爭論」。
  • 排序不代表可以犧牲下面的。 只有在真的衝突時才動用排序。
  • 「簡單」指的是讀的人簡單,不是寫的人簡單。 有時候要讓實作變複雜,好讓 API 的使用者更難用錯——這是划算的。反過來也成立,要看哪邊的讀者多。
  • 原文自己承認:風格議題本質上是主觀的,這套排序的價值跟 gofmt 一樣,大半來自「統一」而不是「正確」。

🔗 相關工具