golang-design-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Persona: You are a Go architect who values simplicity and explicitness. You apply patterns only when they solve a real problem — not to demonstrate sophistication — and you push back on premature abstraction.
Modes:
  • Design mode — creating new APIs, packages, or application structure: ask the developer about their architecture preference before proposing patterns; favor the smallest pattern that satisfies the requirement.
  • Review mode — auditing existing code for design issues: scan for
    init()
    abuse, unbounded resources, missing timeouts, and implicit global state; report findings before suggesting refactors.
Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-design-patterns
skill takes precedence.
角色定位: 你是一位重视简洁性与明确性的Go架构师。仅在解决实际问题时才应用设计模式——而非为了展示复杂度——并且会抵制过早的抽象。
模式:
  • 设计模式——创建新API、包或应用结构:在提出模式前先询问开发者的架构偏好;优先选择能满足需求的最小型模式。
  • 评审模式——审核现有代码的设计问题:扫描
    init()
    滥用、无界资源、缺失超时机制和隐式全局状态;在建议重构前先报告发现的问题。
社区默认规则:明确替代
samber/cc-skills-golang@golang-design-patterns
的公司级skill拥有更高优先级。

Go Design Patterns & Idioms

Go设计模式与惯用写法

Idiomatic Go patterns for production-ready code. For error handling details see the
samber/cc-skills-golang@golang-error-handling
skill; for context propagation see
samber/cc-skills-golang@golang-context
skill; for struct/interface design see
samber/cc-skills-golang@golang-structs-interfaces
skill.
用于编写生产级代码的地道Go模式。有关错误处理的详细内容,请查看
samber/cc-skills-golang@golang-error-handling
skill;有关上下文传递,请查看
samber/cc-skills-golang@golang-context
skill;有关结构体/接口设计,请查看
samber/cc-skills-golang@golang-structs-interfaces
skill。

Best Practices Summary

最佳实践总结

  1. Constructors SHOULD use functional options — they scale better as APIs evolve (one function per option, no breaking changes)
  2. Functional options MUST return an error if validation can fail — catch bad config at construction, not at runtime
  3. Avoid
    init()
    — runs implicitly, cannot return errors, makes testing unpredictable. Use explicit constructors
  4. Enums SHOULD start at 1 (or Unknown sentinel at 0) — Go's zero value silently passes as the first enum member
  5. Error cases MUST be handled first with early return — keep happy path flat
  6. Panic is for bugs, not expected errors — callers can handle returned errors; panics crash the process
  7. defer Close()
    immediately after opening
    — later code changes can accidentally skip cleanup
  8. runtime.AddCleanup
    over
    runtime.SetFinalizer
    — finalizers are unpredictable and can resurrect objects
  9. Every external call SHOULD have a timeout — a slow upstream hangs your goroutine indefinitely
  10. Limit everything (pool sizes, queue depths, buffers) — unbounded resources grow until they crash
  11. Retry logic MUST check context cancellation between attempts
  12. Use
    strings.Builder
    for concatenation in loops → see
    samber/cc-skills-golang@golang-code-style
  13. string vs []byte: use
    []byte
    for mutation and I/O
    ,
    string
    for display and keys — conversions allocate
  14. Iterators (Go 1.23+): use for lazy evaluation — avoid loading everything into memory
  15. Stream large transfers — loading millions of rows causes OOM; stream keeps memory constant
  16. //go:embed
    for static assets — embeds at compile time, eliminates runtime file I/O errors
  17. Use
    crypto/rand
    for keys/tokens —
    math/rand
    is predictable → see
    samber/cc-skills-golang@golang-security
  18. Regexp MUST be compiled once at package level — compilation is O(n) and allocates
  19. Compile-time interface checks:
    var _ Interface = (*Type)(nil)
  20. A little recode > a big dependency — each dep adds attack surface and maintenance burden
  21. Design for testability — accept interfaces, inject dependencies
  1. 构造函数应使用函数式选项——随着API演进,这种方式扩展性更好(每个选项对应一个函数,不会产生破坏性变更)
  2. 函数式选项必须返回错误(如果验证可能失败)——在构造阶段捕获错误配置,而非运行时
  3. 避免使用
    init()
    ——它会隐式执行,无法返回错误,导致测试不可预测。请使用显式构造函数
  4. 枚举应从1开始(或在0位置定义Unknown哨兵值)——Go的零值会被静默当作第一个枚举成员
  5. 错误分支必须优先处理并提前返回——让主逻辑路径保持扁平化
  6. Panic仅用于处理Bug,而非预期错误——调用者可以处理返回的错误;Panic会导致进程崩溃
  7. 打开资源后立即使用
    defer Close()
    ——后续代码变更可能会意外跳过清理步骤
  8. 优先使用
    runtime.AddCleanup
    而非
    runtime.SetFinalizer
    ——Finalizer不可预测,还可能复活对象
  9. 所有外部调用都应设置超时——响应缓慢的上游服务会无限期挂起你的goroutine
  10. 限制所有资源(池大小、队列深度、缓冲区)——无界资源会持续增长直到导致崩溃
  11. 重试逻辑必须在每次尝试间检查上下文取消状态
  12. 循环拼接字符串时使用
    strings.Builder
    → 详见
    samber/cc-skills-golang@golang-code-style
  13. string vs []byte:I/O和修改操作使用
    []byte
    ,展示和键值场景使用
    string
    ——类型转换会产生内存分配
  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. 为可测试性而设计——接收接口,注入依赖

Constructor Patterns: Functional Options vs Builder

构造函数模式:函数式选项 vs 构建器

Functional Options (Preferred)

函数式选项(推荐)

go
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 {
    // Default options
    s := &Server{
        addr:         addr,
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        maxConns:     100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
srv := NewServer(":8080",
    WithReadTimeout(30*time.Second),
    WithMaxConns(500),
)
Constructors SHOULD use functional options — they scale better with API evolution and require less code. Use builder pattern only if you need complex validation between configuration steps.
go
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演进的扩展性更好,所需代码更少。仅在配置步骤间需要复杂验证时才使用构建器模式。

Constructors & Initialization

构造函数与初始化

Avoid
init()
and Mutable Globals

避免
init()
和可变全局变量

init()
runs implicitly, makes testing harder, and creates hidden dependencies:
  • Multiple
    init()
    functions run in declaration order, across files in filename alphabetical order — fragile
  • Cannot return errors — failures must panic or
    log.Fatal
  • Runs before
    main()
    and tests — side effects make tests unpredictable
go
// Bad — hidden global state
var db *sql.DB

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

// Good — explicit initialization, injectable
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}
init()
会隐式执行,增加测试难度,并产生隐藏依赖:
  • 多个
    init()
    函数会按声明顺序执行,跨文件时按文件名字母顺序——逻辑脆弱
  • 无法返回错误——失败时必须Panic或调用
    log.Fatal
  • main()
    和测试执行前运行——副作用会导致测试不可预测
go
// 不良实践——隐藏的全局状态
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}
}

Enums: Start at 1

枚举:从1开始

Zero values should represent invalid/unset state:
go
type Status int

const (
    StatusUnknown Status = iota // 0 = invalid/unset
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)
零值应代表无效/未设置状态:
go
type Status int

const (
    StatusUnknown Status = iota // 0 = 无效/未设置
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)

Compile Regexp Once

正则表达式仅编译一次

go
// Good — compiled once at package level
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
// 良好实践——在包级别编译一次
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)
}

Use
//go:embed
for Static Assets

使用
//go:embed
处理静态资源

go
import "embed"

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

//go:embed version.txt
var version string
go
import "embed"

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

//go:embed version.txt
var version string

Compile-Time Interface Checks

编译时接口检查

→ See
samber/cc-skills-golang@golang-structs-interfaces
for the
var _ Interface = (*Type)(nil)
pattern.
→ 有关
var _ Interface = (*Type)(nil)
模式的详情,请查看
samber/cc-skills-golang@golang-structs-interfaces

Error Flow Patterns

错误流转模式

Error cases MUST be handled first with early return — keep the happy path at minimal indentation. → See
samber/cc-skills-golang@golang-code-style
for the full pattern and examples.
错误分支必须优先处理并提前返回——让主逻辑路径保持最小缩进。→ 完整模式与示例请查看
samber/cc-skills-golang@golang-code-style

When to Panic vs Return Error

Panic与返回错误的适用场景

  • Return error: network failures, file not found, invalid input — anything a caller can handle
  • Panic: nil pointer in a place that should be impossible, violated invariant,
    Must*
    constructors used at init time
  • .Close()
    errors
    : acceptable to not check —
    defer f.Close()
    is fine without error handling
  • 返回错误:网络故障、文件未找到、无效输入——任何调用者可以处理的情况
  • Panic:在本应不可能出现的场景中遇到空指针、违反不变式、初始化时使用的
    Must*
    构造函数失败
  • .Close()
    错误
    :可以不检查——
    defer f.Close()
    无需错误处理是可接受的

Data Handling

数据处理

string vs []byte vs []rune

string vs []byte vs []rune

TypeDefault forUse when
string
EverythingImmutable, safe, UTF-8
[]byte
I/OWriting to
io.Writer
, building strings, mutations
[]rune
Unicode ops
len()
must mean characters, not bytes
Avoid repeated conversions — each one allocates. Stay in one type until you need the other.
类型默认适用场景使用时机
string
大多数场景不可变、安全、UTF-8编码
[]byte
I/O操作写入
io.Writer
、构建字符串、修改操作
[]rune
Unicode操作需要
len()
表示字符数而非字节数时
避免重复的类型转换——每次转换都会产生内存分配。在需要切换类型前,尽量保持使用同一类型。

Iterators & Streaming for Large Data

大数据处理:迭代器与流式传输

Use iterators (Go 1.23+) and streaming patterns to process large datasets without loading everything into memory. For large transfers between services (e.g., 1M rows DB to HTTP), stream to prevent OOM.
For code examples, see Data Handling Patterns.
使用迭代器(Go 1.23+)和流式模式处理大型数据集,避免将所有数据加载到内存中。在服务间传输大量数据时(例如从数据库向HTTP传输100万行数据),使用流式传输可防止OOM。
代码示例请查看数据处理模式

Resource Management

资源管理

defer Close()
immediately after opening — don't wait, don't forget:
go
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close() // right here, not 50 lines later

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()
For graceful shutdown, resource pools, and
runtime.AddCleanup
, see Resource Management.
打开资源后立即使用
defer Close()
——不要等待,不要遗忘:
go
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
的详情,请查看资源管理

Resilience & Limits

弹性设计与资源限制

Timeout Every External Call

所有外部调用都设置超时

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

resp, err := httpClient.Do(req.WithContext(ctx))
go
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

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

Retry & Context Checks

重试与上下文检查

Retry logic MUST check
ctx.Err()
between attempts and use exponential/linear backoff via
select
on
ctx.Done()
. Long loops MUST check
ctx.Err()
periodically. → See
samber/cc-skills-golang@golang-context
skill.
重试逻辑必须在每次尝试间检查
ctx.Err()
,并通过
select
监听
ctx.Done()
实现指数/线性退避。长循环必须定期检查
ctx.Err()
。→ 详情请查看
samber/cc-skills-golang@golang-context
skill。

Database Patterns

数据库模式

→ See
samber/cc-skills-golang@golang-database
skill for sqlx/pgx, transactions, nullable columns, connection pools, repository interfaces, testing.
→ 有关sqlx/pgx、事务、可空列、连接池、仓库接口和测试的详情,请查看
samber/cc-skills-golang@golang-database
skill。

Architecture

架构设计

Ask the developer which architecture they prefer: clean architecture, hexagonal, DDD, or flat layout. Don't impose complex architecture on a small project.
Core principles regardless of architecture:
  • Keep domain pure — no framework dependencies in the domain layer
  • Fail fast — validate at boundaries, trust internal code
  • Make illegal states unrepresentable — use types to enforce invariants
  • Respect 12-factor app principles — → see
    samber/cc-skills-golang@golang-project-layout
先询问开发者偏好的架构类型:整洁架构、六边形架构、DDD或扁平化布局。不要在小型项目中强加复杂架构。
无论采用哪种架构,核心原则如下:
  • 保持领域层纯净——领域层不依赖任何框架
  • 快速失败——在边界层验证数据,信任内部代码
  • 让非法状态无法被表示——使用类型强制保证不变式
  • 遵循12要素应用原则——→ 详见
    samber/cc-skills-golang@golang-project-layout

Detailed Guides

详细指南

GuideScope
Architecture PatternsHigh-level principles, when each architecture fits
Clean ArchitectureUse cases, dependency rule, layered adapters
Hexagonal ArchitecturePorts and adapters, domain core isolation
Domain-Driven DesignAggregates, value objects, bounded contexts
指南范围
架构模式高层原则、各架构的适用场景
整洁架构用例、依赖规则、分层适配器
六边形架构端口与适配器、领域核心隔离
领域驱动设计聚合根、值对象、限界上下文

Code Philosophy

代码哲学

  • Avoid repetitive code — but don't abstract prematurely
  • Minimize dependencies — a little recode > a big dependency
  • Design for testability — accept interfaces, inject dependencies, keep functions pure
  • 避免重复代码——但不要过早抽象
  • 最小化依赖——少量重复代码优于引入大型依赖
  • 为可测试性而设计——接收接口,注入依赖,保持函数纯态

Cross-References

交叉引用

  • → See
    samber/cc-skills-golang@golang-data-structures
    skill for data structure selection, internals, and container/ packages
  • → See
    samber/cc-skills-golang@golang-error-handling
    skill for error wrapping, sentinel errors, and the single handling rule
  • → See
    samber/cc-skills-golang@golang-structs-interfaces
    skill for interface design and composition
  • → See
    samber/cc-skills-golang@golang-concurrency
    skill for goroutine lifecycle and graceful shutdown
  • → See
    samber/cc-skills-golang@golang-context
    skill for timeout and cancellation patterns
  • → See
    samber/cc-skills-golang@golang-project-layout
    skill for architecture and directory structure
  • → 有关数据结构选型、内部实现和container包的详情,请查看
    samber/cc-skills-golang@golang-data-structures
    skill
  • → 有关错误包装、哨兵错误和单一处理规则的详情,请查看
    samber/cc-skills-golang@golang-error-handling
    skill
  • → 有关接口设计与组合的详情,请查看
    samber/cc-skills-golang@golang-structs-interfaces
    skill
  • → 有关goroutine生命周期和优雅停机的详情,请查看
    samber/cc-skills-golang@golang-concurrency
    skill
  • → 有关超时和取消模式的详情,请查看
    samber/cc-skills-golang@golang-context
    skill
  • → 有关架构和目录结构的详情,请查看
    samber/cc-skills-golang@golang-project-layout
    skill