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






