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 Design Patterns & Idioms
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.
Best Practices Summary
- 1. Constructors SHOULD use functional options — they scale better as APIs evolve (one function per option, no breaking changes)
- Functional options MUST return an error if validation can fail — catch bad config at construction, not at runtime
- Avoid
init() — runs implicitly, cannot return errors, makes testing unpredictable. Use explicit constructors - Enums SHOULD start at 1 (or Unknown sentinel at 0) — Go's zero value silently passes as the first enum member
- Error cases MUST be handled first with early return — keep happy path flat
- Panic is for bugs, not expected errors — callers can handle returned errors; panics crash the process
defer Close() immediately after opening — later code changes can accidentally skip cleanupruntime.AddCleanup over runtime.SetFinalizer — finalizers are unpredictable and can resurrect objects- Every external call SHOULD have a timeout — a slow upstream hangs your goroutine indefinitely
- Limit everything (pool sizes, queue depths, buffers) — unbounded resources grow until they crash
- Retry logic MUST check context cancellation between attempts
- Use
strings.Builder for concatenation in loops → see INLINECODE10 - string vs []byte: use
[]byte for mutation and I/O, string for display and keys — conversions allocate - Iterators (Go 1.23+): use for lazy evaluation — avoid loading everything into memory
- Stream large transfers — loading millions of rows causes OOM; stream keeps memory constant
- INLINECODE13 for static assets — embeds at compile time, eliminates runtime file I/O errors
- Use
crypto/rand for keys/tokens — math/rand is predictable → see INLINECODE16 - Regexp MUST be compiled once at package level — compilation is O(n) and allocates
- Compile-time interface checks:
var _ Interface = (*Type)(nil) - A little recode > a big dependency — each dep adds attack surface and maintenance burden
- Design for testability — accept interfaces, inject dependencies
Constructor Patterns: Functional Options vs Builder
Functional Options (Preferred)
CODEBLOCK0
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.
Constructors & Initialization
Avoid init() and Mutable Globals
INLINECODE19 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 INLINECODE21
- Runs before
main() and tests — side effects make tests unpredictable
CODEBLOCK1
Enums: Start at 1
Zero values should represent invalid/unset state:
CODEBLOCK2
Compile Regexp Once
CODEBLOCK3
Use //go:embed for Static Assets
CODEBLOCK4
Compile-Time Interface Checks
→ See samber/cc-skills-golang@golang-structs-interfaces for the var _ Interface = (*Type)(nil) pattern.
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.
When to Panic vs Return Error
- - 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
Data Handling
string vs []byte vs []rune
| Type | Default for | Use when |
|---|
| INLINECODE30 | Everything | Immutable, safe, UTF-8 |
| INLINECODE31 |
I/O | Writing 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.
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.
Resource Management
INLINECODE35 immediately after opening — don't wait, don't forget:
CODEBLOCK5
For graceful shutdown, resource pools, and runtime.AddCleanup, see Resource Management.
Resilience & Limits
Timeout Every External Call
CODEBLOCK6
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.
Database Patterns
→ See samber/cc-skills-golang@golang-database skill for sqlx/pgx, transactions, nullable columns, connection pools, repository interfaces, testing.
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 INLINECODE43
Detailed Guides
Use cases, dependency rule, layered adapters |
|
Hexagonal Architecture | Ports and adapters, domain core isolation |
|
Domain-Driven Design | Aggregates, 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
角色定位: 你是一位重视简洁与明确的 Go 架构师。你仅在模式能解决实际问题时才应用它——而非为了展示技巧——并且你会抵制过早的抽象。
模式:
- - 设计模式 — 创建新的 API、包或应用结构:在提出模式之前,先询问开发者的架构偏好;优先选择能满足需求的最小模式。
- 审查模式 — 审计现有代码的设计问题:扫描 init() 的滥用、无界资源、缺失超时以及隐式全局状态;在建议重构之前先报告发现的问题。
社区默认规则。 明确取代 samber/cc-skills-golang@golang-design-patterns 技能的公司技能具有优先权。
Go 设计模式与惯用法
面向生产级代码的惯用 Go 模式。关于错误处理的详细信息,请参阅 samber/cc-skills-golang@golang-error-handling 技能;关于上下文传播,请参阅 samber/cc-skills-golang@golang-context 技能;关于结构体/接口设计,请参阅 samber/cc-skills-golang@golang-structs-interfaces 技能。
最佳实践总结
- 1. 构造函数应使用函数选项模式——它们在 API 演进时扩展性更好(每个选项一个函数,无破坏性变更)
- 如果验证可能失败,函数选项必须返回一个错误——在构造时捕获错误配置,而非运行时
- 避免使用 init()——它隐式运行,无法返回错误,使测试不可预测。应使用显式构造函数
- 枚举应从 1 开始(或将 Unknown 哨兵值设为 0)——Go 的零值会静默地作为第一个枚举成员传递
- 错误情况必须优先处理并提前返回——保持正常路径平坦
- Panic 用于处理 bug,而非预期错误——调用者可以处理返回的错误;panic 会导致进程崩溃
- 在打开资源后立即 defer Close()——后续的代码变更可能意外跳过清理
- 使用 runtime.AddCleanup 而非 runtime.SetFinalizer——终结器不可预测且可能复活对象
- 每个外部调用都应设置超时——上游服务缓慢会无限期挂起你的 goroutine
- 限制一切(池大小、队列深度、缓冲区)——无界资源会不断增长直至崩溃
- 重试逻辑必须在尝试之间检查上下文取消
- 在循环中使用 strings.Builder 进行字符串拼接 → 参见 samber/cc-skills-golang@golang-code-style
- string 与 []byte:可变和 I/O 操作使用 []byte,显示和键使用 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)
- 少量重写代码 > 大型依赖——每个依赖项都会增加攻击面和维护负担
- 为可测试性设计——接受接口,注入依赖
构造函数模式:函数选项 vs 构建器
函数选项(首选)
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 演进时扩展性更好且代码量更少。仅当需要在配置步骤之间进行复杂验证时才使用构建器模式。
构造函数与初始化
避免使用 init() 和可变全局变量
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}
}
枚举:从 1 开始
零值应表示无效/未设置状态:
go
type Status int
const (
StatusUnknown Status = iota // 0 = 无效/未设置
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)
编译一次正则表达式
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)
}
使用 //go:embed 处理静态资源
go
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() 错误:可以不检查——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()——不要等待,不要忘记:
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()
关于优雅关闭、资源