Community程式設計與開發github.com

viccom/golang-design-patterns

慣用的 Go 設計模式 — 函數選項、建構器、錯誤流程與串聯、資源管理與生命週期、優雅關閉、韌性、架構、依賴注入、資料處理、串流等。在明確需要選擇架構模式、實作函數選項、設計建構器 API、設定優雅關閉、套用韌性模式,或詢問哪個慣用 Go 模式適合特定問題時套用。

golang-design-patterns 是什麼?

此技能為 Go 開發者與 AI 助手提供了一套完整的慣用設計模式與最佳實踐,涵蓋從建構器設計、錯誤處理、資源管理到高階架構的範疇。它強調「簡單明確」與「解決真實問題」,反對過早抽象,並透過明確的「設計模式」與「審查模式」引導 AI 在合適的時機提出建議。技能支援所有主流 Go 專案,並與多個 `samber/cc-skills-golang` 系列技能(如錯誤處理、Context、結構體與介面)交叉引用,形成完整的知識體系。核心原則包括:偏好函數選項而非建構器模式、避免 `init()`、從 1 開始定義列舉、提早處理錯誤、使用 `defer Close()` 確保資源釋放、為每個外部呼叫設定逾時、以及限制所有資源以避免崩潰。此外,技能也提供了關於字串與位元組選擇、迭代器與串流處理、正則表達式編譯、編譯時期介面檢查、依賴注入與測試友善設計等實用指引。對於需要架構指導的場景,它會先詢問開發者偏好(整潔架構、六角形架構、DDD 或扁平佈局),然後才提出具體模式,避免在小專案中強加複雜性。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/golang-design-patterns

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

角色: 你是一位重視簡潔與明確性的 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 技能;關於結構體/介面設計,請參閱 samber/cc-skills-golang@golang-structs-interfaces 技能。

最佳實踐摘要

  1. 建構器(Constructor)應該使用函數選項(functional options) — 它們在 API 演進時更具擴展性(每個選項一個函數,無破壞性變更)
  2. 函數選項在驗證可能失敗時必須回傳錯誤 — 在建構時捕獲錯誤配置,而非在執行時期
  3. 避免使用 init() — 它會隱含執行、無法回傳錯誤、使測試難以預測。應使用明確的建構器
  4. 列舉(Enum)應該從 1 開始(或將 Unknown 哨兵值設為 0)— Go 的零值會靜默地作為第一個列舉成員傳遞
  5. 錯誤情況必須優先處理並提早回傳 — 保持快樂路徑平坦
  6. Panic 是給錯誤(bug)用的,不是給預期的錯誤(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:對可變操作和 I/O 使用 []byte,對顯示和鍵(key)使用 string — 轉換會產生記憶體分配
  14. 迭代器(Iterator, 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. 為可測試性而設計 — 接受介面、注入依賴

建構器模式:函數選項 vs 建構器(Builder)

函數選項(建議使用)

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),
)

建構器應該使用函數選項 — 它們在 API 演進時更具擴展性且需要更少的程式碼。只有在不同配置步驟之間需要複雜驗證時,才使用建構器模式。

建構器與初始化

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

init() 會隱含執行、增加測試難度,並建立隱藏的依賴:

  • 多個 init() 函數按照宣告順序執行,跨檔案時則按照檔案名稱的字母順序 — 這非常脆弱
  • 無法回傳錯誤 — 失敗時必須 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

編譯時期的介面檢查

→ 有關 var _ Interface = (*Type)(nil) 模式,請參閱 samber/cc-skills-golang@golang-structs-interfaces。

錯誤流程模式

錯誤情況必須優先處理並提早回傳 — 將快樂路徑保持在最小的縮排層級。→ 有關完整模式與範例,請參閱 samber/cc-skills-golang@golang-code-style。

何時使用 Panic 與回傳錯誤

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

資料處理

string vs []byte vs []rune

類型預設用於使用時機
string所有情況不可變、安全、UTF-8
[]byteI/O寫入 io.Writer、建立字串、可變操作
[]runeUnicode 操作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(),並透過 select 搭配 ctx.Done() 使用指數/線性退避。長時間執行的迴圈必須定期檢查 ctx.Err()。→ 參閱 samber/cc-skills-golang@golang-context 技能。

資料庫模式

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

架構

詢問開發者他們偏好的架構:整潔架構(Clean Architecture)、六角形架構(Hexagonal)、領域驅動設計(DDD)或扁平佈局。不要在小專案上強加複雜的架構。

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

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

詳細指南

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

程式碼哲學

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

交叉引用

  • → 參閱 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 技能,了解架構與目錄結構
  • → 參閱 samber/cc-skills-golang@golang-refactoring 技能,了解如何在現有程式碼庫中安全地分階段遷移到這些模式之一(選項結構、DI、消費者端介面)

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相關技能