golang-design-patterns

golang-design-patterns

熱門

符合 Go 語言慣例的設計模式 — functional options、建構子、錯誤流程與串接、資源管理與生命週期、優雅關機、韌性、架構、依賴注入、資料處理、串流等。適用於明確選擇架構模式、實作 functional options、設計建構子 API、設定優雅關機、套用韌性模式,或詢問哪個慣用 Go 模式適合特定問題時。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-design-patterns
description

符合 Go 語言慣例的設計模式 — functional options、建構子、錯誤流程與串接、資源管理與生命週期、優雅關機、韌性、架構、依賴注入、資料處理、串流等。適用於明確選擇架構模式、實作 functional options、設計建構子 API、設定優雅關機、套用韌性模式,或詢問哪個慣用 Go 模式適合特定問題時。

角色設定: 你是一位重視簡潔與明確的 Go 架構師。只有在模式能解決實際問題時才套用 — 不是為了展現技術深度 — 並且會對過早抽象化提出異議。

模式:

  • 設計模式 — 建立新的 API、套件或應用程式結構:在提出模式之前,先詢問開發者偏好的架構;選擇能滿足需求的最小模式。
  • 審查模式 — 審查現有程式碼的設計問題:掃描 init() 濫用、無限制資源、缺少逾時、隱含的全域狀態;在建議重構前先報告發現。

社群預設。 明確取代 samber/cc-skills-golang@golang-design-patterns 技能的團隊技能具有優先權。

Go 設計模式與慣例

適用於正式環境的慣用 Go 模式。錯誤處理細節請見 samber/cc-skills-golang@golang-error-handling 技能;context 傳播請見 samber/cc-skills-golang@golang-context 技能;struct/interface 設計請見 samber/cc-skills-golang@golang-structs-interfaces 技能。

最佳實踐摘要

  1. 建構子應使用 functional options — 隨著 API 演進,它們更具擴充性(每個選項一個函式,無破壞性變更)
  2. Functional options 若驗證可能失敗,必須回傳 error — 在建構時捕捉錯誤設定,而非執行時期
  3. 避免 init() — 隱含執行、無法回傳 error、使測試不可預測。請使用明確的建構子
  4. 列舉應從 1 開始(或將 Unknown 哨兵值設為 0)— Go 的零值會默默地通過成為第一個列舉成員
  5. 錯誤情況必須優先處理並及早回傳 — 保持快樂路徑平坦
  6. Panic 用於程式錯誤,非預期錯誤 — 呼叫端可以處理回傳的 error;panic 會使程序崩潰
  7. 開啟後立即 defer Close() — 後續的程式碼變更可能意外跳過清理
  8. runtime.AddCleanup 優於 runtime.SetFinalizer — finalizer 不可預測且可能復活物件
  9. 每個外部呼叫都應有逾時設定 — 上游服務慢會無限期掛住你的 goroutine
  10. 限制所有資源(池大小、佇列深度、緩衝區)— 無限制的資源會持續增長直到崩潰
  11. 重試邏輯必須在每次嘗試之間檢查 context 取消
  12. 在迴圈中使用 strings.Builder 進行字串串接 → 請見 samber/cc-skills-golang@golang-code-style
  13. string 與 []byte:使用 []byte 處理變動與 I/Ostring 用於顯示與鍵值 — 轉換會配置記憶體
  14. 迭代器(Go 1.23+):用於惰性求值 — 避免將所有資料載入記憶體
  15. 串流處理大量傳輸 — 載入數百萬筆資料會導致 OOM;串流可保持記憶體穩定
  16. //go:embed 用於靜態資源 — 在編譯時嵌入,消除執行時期的檔案 I/O 錯誤
  17. 使用 crypto/rand 產生金鑰/令牌 — math/rand 是可預測的 → 請見 samber/cc-skills-golang@golang-security
  18. 正規表達式必須在套件層級編譯一次 — 編譯是 O(n) 且會配置記憶體
  19. 編譯時期介面檢查:var _ Interface = (*Type)(nil)
  20. 少量重寫 > 大型依賴 — 每個依賴都增加攻擊面與維護負擔
  21. 設計為可測試 — 接受介面、注入依賴

建構子模式:Functional Options vs Builder

Functional Options(建議使用)

type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    maxConns     int
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) { s.readTimeout = d }
}

func WithWriteTimeout(d time.Duration) Option {
    return func(s *Server) { s.writeTimeout = d }
}

func WithMaxConns(n int) Option {
    return func(s *Server) { s.maxConns = n }
}

func NewServer(addr string, opts ...Option) *Server {
    // 預設選項
    s := &Server{
        addr:         addr,
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        maxConns:     100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// 使用方式
srv := NewServer(":8080",
    WithReadTimeout(30*time.Second),
    WithMaxConns(500),
)

建構子應使用 functional options — 它們隨著 API 演進更具擴充性,且需要較少程式碼。僅在需要在設定步驟之間進行複雜驗證時,才使用 builder 模式。

建構子與初始化

避免 init() 與可變的全域變數

init() 隱含執行、使測試更困難,並建立隱藏依賴:

  • 多個 init() 函式依宣告順序執行,跨檔案時依檔名字母順序 — 脆弱
  • 無法回傳 error — 失敗必須 panic 或 log.Fatal
  • main() 與測試之前執行 — 副作用使測試不可預測
// 不好 — 隱藏的全域狀態
var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        log.Fatal(err)
    }
}

// 好 — 明確初始化,可注入
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

列舉:從 1 開始

零值應代表無效/未設定狀態:

type Status int

const (
    StatusUnknown Status = iota // 0 = 無效/未設定
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)

正規表達式編譯一次

// 好 — 在套件層級編譯一次
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

func ValidateEmail(email string) bool {
    return emailRegex.MatchString(email)
}

使用 //go:embed 處理靜態資源

import "embed"

//go:embed templates/*
var templateFS embed.FS

//go:embed version.txt
var version string

編譯時期介面檢查

→ 請見 samber/cc-skills-golang@golang-structs-interfaces 了解 var _ Interface = (*Type)(nil) 模式。

錯誤流程模式

錯誤情況必須優先處理並及早回傳 — 保持快樂路徑的縮排最少。→ 請見 samber/cc-skills-golang@golang-code-style 了解完整模式與範例。

何時 Panic 與回傳 Error

  • 回傳 error:網路失敗、檔案找不到、無效輸入 — 任何呼叫端可以處理的情況
  • Panic:在不該發生的地方出現 nil 指標、違反不變量、在初始化時使用 Must* 建構子
  • .Close() / Flush() 錯誤:唯讀清理通常可以使用 defer f.Close(),但寫入/沖刷資源在需要持久性時必須回報關閉或沖刷錯誤

資料處理

string vs []byte vs []rune

類型 預設用於 使用時機
string 所有情況 不可變、安全、UTF-8
[]byte I/O 寫入 io.Writer、建立字串、變動
[]rune Unicode 操作 len() 必須表示字元數,而非位元組數

避免重複轉換 — 每次轉換都會配置記憶體。保持在一個類型中,直到需要另一個類型。

大量資料的迭代器與串流

使用迭代器(Go 1.23+)與串流模式處理大型資料集,避免將所有資料載入記憶體。對於服務之間的大型傳輸(例如從資料庫到 HTTP 的 100 萬筆資料),使用串流以防止 OOM。

程式碼範例請見 資料處理模式

資源管理

開啟後立即 defer Close() — 不要等待,不要忘記:

f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close() // 就在這裡,不是 50 行之後

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()

關於優雅關機、資源池與 runtime.AddCleanup,請見 資源管理

韌性與限制

每個外部呼叫都設定逾時

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

resp, err := httpClient.Do(req.WithContext(ctx))

重試與 Context 檢查

重試邏輯必須在每次嘗試之間檢查 ctx.Err(),並透過 selectctx.Done() 上使用指數/線性退避。長時間迴圈必須定期檢查 ctx.Err()。→ 請見 samber/cc-skills-golang@golang-context 技能。

資料庫模式

→ 請見 samber/cc-skills-golang@golang-database 技能了解 sqlx/pgx、交易、可為 null 的欄位、連線池、儲存庫介面、測試。

架構

詢問開發者偏好的架構:clean architecture、hexagonal、DDD 或 flat layout。不要對小型專案強加複雜的架構。

無論何種架構,核心原則如下:

  • 保持領域純淨 — 領域層沒有框架依賴
  • 快速失敗 — 在邊界驗證,信任內部程式碼
  • 讓非法狀態無法表示 — 使用類型強制執行不變量
  • 遵循 12-factor app 原則 — → 請見 samber/cc-skills-golang@golang-project-layout

詳細指南

指南 範圍
架構模式 高層原則,每種架構適用的時機
Clean Architecture 使用案例、依賴規則、分層適配器
Hexagonal Architecture 連接埠與適配器、領域核心隔離
領域驅動設計 聚合、值物件、限界上下文

程式碼哲學

  • 避免重複程式碼 — 但不要過早抽象化
  • 最小化依賴 — 少量重寫 > 大型依賴
  • 設計為可測試 — 接受介面、注入依賴、保持函式純淨

交叉參考

  • → 請見 samber/cc-skills-golang@golang-data-structures 技能了解資料結構選擇、內部實作與 container/ 套件
  • → 請見 samber/cc-skills-golang@golang-error-handling 技能了解錯誤包裝、哨兵錯誤與單一處理規則
  • → 請見 samber/cc-skills-golang@golang-structs-interfaces 技能了解介面設計與組合
  • → 請見 samber/cc-skills-golang@golang-concurrency 技能了解 goroutine 生命週期與優雅關機
  • → 請見 samber/cc-skills-golang@golang-context 技能了解逾時與取消模式
  • → 請見 samber/cc-skills-golang@golang-project-layout 技能了解架構與目錄結構