mcpField
G
golang-design-patterns

golang-design-patterns

github.com/samber开发3.7k42.5k 次安装5 天前更新
代码仓库

关于 golang-design-patterns

面向 Go 开发者的设计技能,适用于设计 API、包和应用结构,也可用于代码审查。它强调简单明确的模式,并提供构造函数、资源管理、韧性和架构方面的指导。

主要功能

  1. 01设计与审查模式
  2. 02函数选项构造函数指导
  3. 03显式初始化与错误流程
  4. 04超时、重试与资源限制
  5. 05架构和可测试性原则

适用场景

  1. 01为 Go API 选择设计模式
  2. 02审查隐式状态或资源风险
  3. 03规划 Go 应用结构
  4. 04高效处理大型数据传输

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-design-patterns

安装后在 Claude Code 中输入/golang-design-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
architecture.md
clean-architecture.md
data-handling.md
ddd.md
hexagonal-architecture.md
resource-management.md

详细介绍

角色: 你是一名重视简单性和明确性的 Go 架构师。只有在模式能解决实际问题时才使用,而不是为了炫技;并且会反对过早抽象。

模式:

  • 设计模式 — 创建新的 API、包或应用结构时:先询问开发者偏好的架构,再提出模式;优先选择满足需求的最简模式。
  • 审查模式 — 审查现有代码的设计问题时:检查滥用 init()、无上限资源、缺失超时和隐式全局状态;先报告发现的问题,再建议重构。

社区默认规则。 明确取代 samber/cc-skills-golang@golang-design-patterns skill 的公司 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。

最佳实践摘要

  1. 构造函数应使用函数选项——随着 API 演进更易扩展(每个选项对应一个函数,不会造成破坏性变更)
  2. 如果验证可能失败,函数选项必须返回错误——在构造时捕获错误配置,而不是等到运行时
  3. 避免使用 init()——它会隐式运行、无法返回错误,还会让测试变得不可预测。应使用显式构造函数
  4. 枚举应从 1 开始(或将 Unknown 哨兵值设为 0)——Go 的零值会悄然成为第一个枚举成员
  5. 必须先处理错误情况并提前返回——让正常路径保持平坦
  6. panic 用于程序缺陷,而非预期错误——调用方可以处理返回的错误;panic 会导致进程崩溃
  7. 打开后立即 defer Close()——后续代码变更可能意外跳过清理
  8. 优先使用 runtime.AddCleanup,而非 runtime.SetFinalizer——终结器行为不可预测,还可能使对象复活
  9. 每个外部调用都应设置超时——上游响应缓慢会让 goroutine 无限期挂起
  10. 为所有资源设限(池大小、队列深度、缓冲区)——不受限制的资源会不断增长,直至导致崩溃
  11. 重试逻辑必须在每次尝试之间检查上下文是否已取消
  12. 在循环中拼接字符串时使用 strings.Builder → 参阅 samber/cc-skills-golang@golang-code-style
  13. string 与 []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. 为可测试性而设计——接收接口并注入依赖

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

函数选项(首选)
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
[]byteI/O写入 io.Writer、构建字符串、修改数据
[]runeUnicode 操作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 技能

评论

支持 Markdown