
golang-code-style
熱門Golang 程式碼風格慣例 — 行長度與斷行、變數宣告、控制流程清晰度、何時該加註解。用於撰寫或審查 Go 程式碼、詢問風格或清晰度問題、或建立專案編碼標準。不適用於命名慣例(→ 請參閱 `samber/cc-skills-golang@golang-naming` 技能)、linter 配置(→ 請參閱 `samber/cc-skills-golang@golang-lint` 技能)或文件註解(→ 請參閱 `samber/cc-skills-golang@golang-documentation` 技能)。
Golang 程式碼風格慣例 — 行長度與斷行、變數宣告、控制流程清晰度、何時該加註解。用於撰寫或審查 Go 程式碼、詢問風格或清晰度問題、或建立專案編碼標準。不適用於命名慣例(→ 請參閱 `samber/cc-skills-golang@golang-naming` 技能)、linter 配置(→ 請參閱 `samber/cc-skills-golang@golang-lint` 技能)或文件註解(→ 請參閱 `samber/cc-skills-golang@golang-documentation` 技能)。
社群預設。 若公司技能明確取代
samber/cc-skills-golang@golang-code-style技能,則以公司技能為準。
Go 程式碼風格
需要人為判斷的風格規則 — linter 處理格式化,此技能處理清晰度。命名請參閱 samber/cc-skills-golang@golang-naming 技能;設計模式請參閱 samber/cc-skills-golang@golang-design-patterns 技能;結構/介面設計請參閱 samber/cc-skills-golang@golang-structs-interfaces 技能。
「清晰勝於巧妙。」— Go 諺語
若忽略某條規則,請在程式碼中加入註解。
行長度與斷行
沒有嚴格的行數限制,但超過約 120 個字元的行必須斷行。應在語意邊界處斷行,而非任意欄位數。函式呼叫有 4 個以上引數時,必須每個引數一行 — 即使提示要求單行程式碼:
// 好 — 每個引數獨立一行,右括號分開
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
handleUsers(
w,
r,
serviceName,
cfg,
logger,
authMiddleware,
)
})
當函式簽名過長時,真正的解決方案通常是減少參數(使用選項結構),而非更好的換行。對於多行簽名,每個參數應獨立一行。
變數宣告
非零值應使用 :=,零值初始化應使用 var。此形式傳達意圖:var 表示「從零開始」。
var count int // 零值,稍後設定
name := "default" // 非零值,適合用 :=
var buf bytes.Buffer // 零值即可直接使用
切片與映射初始化
切片和映射必須明確初始化,絕不能為 nil。Nil 映射在寫入時會 panic;nil 切片在 JSON 中序列化為 null(相對於空切片的 []),會讓 API 使用者感到意外。
users := []User{} // 總是初始化
m := map[string]int{} // 總是初始化
users := make([]User, 0, len(ids)) // 已知容量時預先分配
m := make(map[string]int, len(items)) // 已知大小時預先分配
不要投機性地預先分配 — 當常見情況只有 10 個項目時,make([]T, 0, 1000) 會浪費記憶體。
複合字面值
複合字面值必須使用欄位名稱 — 位置欄位在型別新增或重新排序欄位時會出錯:
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
控制流程
減少巢狀結構
錯誤和邊界情況必須優先處理(及早 return)。保持快樂路徑在最小縮排:
func process(data []byte) (*Result, error) {
if len(data) == 0 {
return nil, errors.New("empty data")
}
parsed, err := parse(data)
if err != nil {
return nil, fmt.Errorf("parsing: %w", err)
}
return transform(parsed), nil
}
消除不必要的 else
當 if 主體以 return/break/continue 結束時,必須移除 else。對於簡單的賦值,使用預設值再覆寫 — 先賦予預設值,再用獨立條件或 switch 覆寫:
// 好 — 預設值再以 switch 覆寫(互斥覆寫時最乾淨)
level := slog.LevelInfo
switch {
case debug:
level = slog.LevelDebug
case verbose:
level = slog.LevelWarn
}
// 差 — else-if 鏈隱藏了有預設值的事實
if debug {
level = slog.LevelDebug
} else if verbose {
level = slog.LevelWarn
} else {
level = slog.LevelInfo
}
複雜條件與初始化範圍
當 if 條件有 3 個以上運算元時,必須提取為具名布林值 — 一堆 || 難以閱讀且隱藏了商業邏輯。將昂貴的檢查保留在行內以獲得短路效益。詳細資訊
// 好 — 具名布林值讓意圖清晰
isAdmin := user.Role == RoleAdmin
isOwner := resource.OwnerID == user.ID
isPublicVerified := resource.IsPublic && user.IsVerified
if isAdmin || isOwner || isPublicVerified || permissions.Contains(PermOverride) {
allow()
}
將變數範圍限定在 if 區塊內,僅在檢查時需要:
if err := validate(input); err != nil {
return err
}
使用 Switch 取代 If-Else 鏈
當多次比較同一個變數時,優先使用 switch:
switch status {
case StatusActive:
activate()
case StatusInactive:
deactivate()
default:
panic(fmt.Sprintf("unexpected status: %d", status))
}
函式設計
- 函式應簡短且專注 — 一個函式只做一件事。
- 函式應有**≤4 個參數**。超過時,使用選項結構(請參閱
samber/cc-skills-golang@golang-design-patterns技能)。 - 參數順序:
context.Context優先,接著是輸入,最後是輸出目標。 - 裸 return 在非常短的函式(1-3 行)中有幫助,因為回傳值顯而易見,但當讀者需要滾動才能找到回傳值時會造成混淆 — 在較長的函式中明確命名回傳值。
func FetchUser(ctx context.Context, id string) (*User, error)
func SendEmail(ctx context.Context, msg EmailMessage) error // 分組到結構中
優先使用 range 進行迭代
應使用 range 而非基於索引的迴圈。使用 range n(Go 1.22+)進行簡單計數。
for _, user := range users {
process(user)
}
值與指標引數
小型型別(string、int、bool、time.Time)以值傳遞。在需要修改、大型結構(約 128+ 位元組)或 nil 有意義時使用指標。詳細資訊
檔案內程式碼組織
- 將相關宣告分組:型別、建構子、方法放在一起
- 順序:套件文件、匯入、常數、型別、建構子、方法、輔助函式
- 每個檔案一個主要型別,當該型別有重要方法時
- 空白匯入(
_ "pkg")註冊副作用(init 函式)。將其限制在main和測試套件中,讓副作用在應用程式根層級可見,而非隱藏在函式庫程式碼中 - 點匯入會污染命名空間,且無法判斷名稱來自何處 — 絕不在函式庫程式碼中使用
- 積極不匯出 — 你隨時可以匯出;取消匯出是破壞性變更
字串處理
使用 strconv 進行簡單轉換(較快),使用 fmt.Sprintf 進行複雜格式化。在錯誤訊息中使用 %q 讓字串邊界可見。在迴圈中使用 strings.Builder,簡單串接使用 +。
型別轉換
優先使用明確、狹義的轉換。當具體型別可行時,使用泛型而非 any:
func Contains[T comparable](slice []T, target T) bool // 不是 []any
哲學
- 「一點複製勝過一點依賴」
- 使用
slices和maps標準套件;對於 filter/group-by/chunk,使用github.com/samber/lo - 「反射永遠不清晰」 — 除非必要,否則避免使用
reflect - 不要過早抽象化 — 在模式穩定後再提取
- 最小化公開表面 — 每個匯出的名稱都是一項承諾
並行化程式碼風格審查
在大型程式碼庫中審查程式碼風格時,可使用最多 5 個並行子代理(透過 Agent 工具),每個子代理針對一個獨立的風格關注點(例如控制流程、函式設計、變數宣告、字串處理、程式碼組織)。
使用 Linter 強制執行
許多規則會自動強制執行:gofmt、gofumpt、goimports、gocritic、revive、wsl_v5。→ 請參閱 samber/cc-skills-golang@golang-lint 技能。
交叉參考
- → 請參閱
samber/cc-skills-golang@golang-naming技能了解識別碼命名慣例 - → 請參閱
samber/cc-skills-golang@golang-structs-interfaces技能了解指標與值接收器、介面設計 - → 請參閱
samber/cc-skills-golang@golang-design-patterns技能了解函式選項、建構器、建構子 - → 請參閱
samber/cc-skills-golang@golang-lint技能了解自動格式化強制執行 - → 請參閱
samber/cc-skills-golang@golang-continuous-integration技能了解在 CI 中使用這些指南進行自動化 AI 驅動的程式碼審查





