地道的 Golang 设计模式——函数选项、构造函数、错误流与级联、资源管理与生命周期、优雅关闭、弹性、架构、依赖注入、数据处理、流式处理等。在明确选择架构模式、实现函数选项、设计构造函数 API、设置优雅关闭、应用弹性模式或询问哪种地道的 Go 模式适合特定问题时应用。
角色: 你是一位重视简洁和明确的 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 技能。
最佳实践总结
- 构造函数应使用函数选项——它们随着 API 的演变而更好地扩展(每个选项一个函数,无破坏性变更)
- 函数选项如果验证可能失败,则必须返回错误——在构造时捕获错误配置,而不是在运行时
- 避免
init()——隐式运行,无法返回错误,使测试不可预测。使用显式构造函数 - 枚举应从 1 开始(或 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 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 演变而更好地扩展,且需要更少的代码。仅当需要在配置步骤之间进行复杂验证时,才使用 builder 模式。
构造函数与初始化
避免 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
编译时接口检查
→ 参见 samber/cc-skills-golang@golang-structs-interfaces 了解 var _ Interface = (*Type)(nil) 模式。
错误流模式
错误情况必须首先处理并提前返回——保持快乐路径缩进最小。→ 参见 samber/cc-skills-golang@golang-code-style 了解完整模式和示例。
何时 Panic 与返回错误
- 返回错误:网络故障、文件未找到、无效输入——任何调用者可以处理的情况
- 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))
重试与上下文检查
重试逻辑必须在尝试之间检查 ctx.Err(),并通过 select 在 ctx.Done() 上使用指数/线性退避。长循环必须定期检查 ctx.Err()。→ 参见 samber/cc-skills-golang@golang-context 技能。
数据库模式
→ 参见 samber/cc-skills-golang@golang-database 技能了解 sqlx/pgx、事务、可空列、连接池、仓库接口、测试。
架构
询问开发者他们偏好的架构:整洁架构、六边形架构、DDD 或扁平布局。不要在小项目上强加复杂的架构。
无论架构如何,核心原则:
- 保持领域纯净——领域层没有框架依赖
- 快速失败——在边界验证,信任内部代码
- 使非法状态不可表示——使用类型强制不变性
- 遵循 12-factor 应用原则——→ 参见
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技能了解架构和目录结构






