关于 golang-design-patterns
面向 Go 开发者的设计技能,适用于设计 API、包和应用结构,也可用于代码审查。它强调简单明确的模式,并提供构造函数、资源管理、韧性和架构方面的指导。
主要功能
- 01设计与审查模式
- 02函数选项构造函数指导
- 03显式初始化与错误流程
- 04超时、重试与资源限制
- 05架构和可测试性原则
适用场景
- 01为 Go API 选择设计模式
- 02审查隐式状态或资源风险
- 03规划 Go 应用结构
- 04高效处理大型数据传输
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-design-patterns安装后在 Claude Code 中输入/golang-design-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/architecture.mdclean-architecture.mddata-handling.mdddd.mdhexagonal-architecture.mdresource-management.md详细介绍
角色: 你是一名重视简单性和明确性的 Go 架构师。只有在模式能解决实际问题时才使用,而不是为了炫技;并且会反对过早抽象。
模式:
- 设计模式 — 创建新的 API、包或应用结构时:先询问开发者偏好的架构,再提出模式;优先选择满足需求的最简模式。
- 审查模式 — 审查现有代码的设计问题时:检查滥用
init()、无上限资源、缺失超时和隐式全局状态;先报告发现的问题,再建议重构。
社区默认规则。 明确取代
samber/cc-skills-golang@golang-design-patternsskill 的公司 skill 优先。
Go 设计模式与惯用法
适用于生产环境代码的惯用 Go 模式。错误处理详情请参阅 samber/cc-skills-golang@golang-error-handling skill;上下文传递请参阅 samber/cc-skills-golang@golang-context skill;结构体与接口设计请参阅 samber/cc-skills-golang@golang-structs-interfaces skill。
最佳实践摘要
- 构造函数应使用函数选项——随着 API 演进更易扩展(每个选项对应一个函数,不会造成破坏性变更)
- 如果验证可能失败,函数选项必须返回错误——在构造时捕获错误配置,而不是等到运行时
- 避免使用
init()——它会隐式运行、无法返回错误,还会让测试变得不可预测。应使用显式构造函数 - 枚举应从 1 开始(或将 Unknown 哨兵值设为 0)——Go 的零值会悄然成为第一个枚举成员
- 必须先处理错误情况并提前返回——让正常路径保持平坦
- panic 用于程序缺陷,而非预期错误——调用方可以处理返回的错误;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) - 少量自编代码胜过大型依赖——每个依赖都会扩大攻击面并增加维护负担
- 为可测试性而设计——接收接口并注入依赖
构造函数模式:函数选项与构建器
函数选项(首选)
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),
)
构造函数应使用函数选项——它们更适应 API 的演进,所需代码也更少。只有在需要对配置步骤进行复杂验证时,才使用构建器模式。
构造函数与初始化
避免 init() 和可变全局变量
init() 会隐式运行,增加测试难度,并产生隐藏依赖:
- 多个
init()函数会按声明顺序运行;跨文件时按文件名的字母顺序执行——这种顺序很脆弱 - 无法返回错误——失败时必须 panic 或调用
log.Fatal - 在
main()和测试之前运行——副作用会让测试变得不可预测
// 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}
}
枚举:从 1 开始
零值应表示无效/未设置状态:
type Status int
const (
StatusUnknown Status = iota // 0 = invalid/unset
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)
仅编译一次正则表达式
// 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: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:本应不可能发生的位置出现空指针、违反不变量、在初始化时使用
Must*构造函数 .Close()/Flush()错误:只读清理通常可以使用defer f.Close(),但当持久性很重要时,必须报告写入/刷新资源的关闭或刷新错误
数据处理
string、[]byte 与 []rune
| 类型 | 默认用途 | 适用场景 |
|---|---|---|
string | 所有情况 | 不可变、安全、UTF-8 |
[]byte | I/O | 写入 io.Writer、构建字符串、修改数据 |
[]rune | Unicode 操作 | len() 必须表示字符数,而非字节数 |
避免反复转换——每次转换都会分配内存。尽可能保持使用同一种类型,直到确实需要另一种类型。
大型数据的迭代器与流式处理
使用迭代器(Go 1.23+)和流式处理模式,在不将所有数据载入内存的情况下处理大型数据集。对于服务之间的大型传输(例如,将数据库中的 1M 行传输到 HTTP),请使用流式处理以防止 OOM。
代码示例请参阅数据处理模式。
资源管理
打开资源后立即 defer Close()——不要等待,也不要忘记:
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()
有关优雅关闭、资源池和 runtime.AddCleanup,请参阅资源管理。
弹性与限制
为每个外部调用设置超时
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resp, err := httpClient.Do(req.WithContext(ctx))
重试与上下文检查
重试逻辑必须在每次尝试之间检查 ctx.Err(),并通过对 ctx.Done() 使用 select,采用指数或线性退避。长循环必须定期检查 ctx.Err()。→ 请参阅 samber/cc-skills-golang@golang-context 技能。
数据库模式
→ 有关 sqlx/pgx、事务、可空列、连接池、存储库接口和测试,请参阅 samber/cc-skills-golang@golang-database 技能。
架构
询问开发者偏好哪种架构:整洁架构、六边形架构、DDD 或扁平布局。不要在小型项目中强加复杂架构。
无论采用哪种架构,都应遵循以下核心原则:
- 保持领域纯粹——领域层不依赖框架
- 尽早失败——在边界处验证,信任内部代码
- 让非法状态无法表示——使用类型强制执行不变量
- 遵循 12-factor app 原则——→ 请参阅
samber/cc-skills-golang@golang-project-layout
详细指南
| 指南 | 范围 |
|---|---|
| 架构模式 | 高层原则,以及各类架构适用的场景 |
| 整洁架构 | 用例、依赖规则、分层适配器 |
| 六边形架构 | 端口与适配器、领域核心隔离 |
| 领域驱动设计 | 聚合、值对象、限界上下文 |
代码理念
- 避免重复代码——但不要过早抽象
- 尽量减少依赖——少量重写胜过引入大型依赖
- 面向可测试性进行设计——接受接口、注入依赖、保持函数纯粹
交叉引用
- → 有关数据结构选择、内部实现和 container/ 包,请参阅
samber/cc-skills-golang@golang-data-structures技能 - → 有关错误包装、哨兵错误和单次处理规则,请参阅
samber/cc-skills-golang@golang-error-handling技能 - → 有关接口设计与组合,请参阅
samber/cc-skills-golang@golang-structs-interfaces技能 - → 有关 goroutine 生命周期和优雅关闭,请参阅
samber/cc-skills-golang@golang-concurrency技能 - → 有关超时和取消模式,请参阅
samber/cc-skills-golang@golang-context技能 - → 有关架构和目录结构,请参阅
samber/cc-skills-golang@golang-project-layout技能 - → 有关如何在现有代码库中安全地分阶段迁移到这些模式之一(选项结构体、依赖注入、使用方接口),请参阅
samber/cc-skills-golang@golang-refactoring技能