Community编程与开发github.com

viccom/golang-design-patterns

地道的 Go 语言设计模式——函数选项、构造函数、错误流与级联、资源管理与生命周期、优雅关闭、弹性、架构、依赖注入、数据处理、流式处理等。当需要在架构模式之间做明确选择、实现函数选项、设计构造函数 API、设置优雅关闭、应用弹性模式或询问特定问题适合哪种地道 Go 模式时使用。

golang-design-patterns 是什么?

该技能提供了一套完整的 Go 语言设计模式指南,专注于生产级代码的地道实践。它涵盖了从构造函数模式(推荐函数选项而非构建器模式)、错误处理流程、资源管理(如立即 defer Close)、弹性设计(超时、重试与上下文检查)到架构决策(整洁架构、六边形架构、DDD 或扁平布局)的各个方面。技能强调简洁性和显式性,反对过早抽象,并包含 21 条最佳实践总结,如避免 `init()`、枚举从 1 开始、编译时接口检查等。它适用于 Claude Code、Cursor、Codex 等 AI 编码助手,通过与多个关联技能(如错误处理、上下文传播、结构体/接口设计)协同工作,为 Go 开发者提供全面的模式参考。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/golang-design-patterns

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

角色: 你是一名重视简单性和显式性的 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 的演进更好地扩展(每个选项一个函数,无需破坏性变更)
  2. 如果验证可能失败,函数选项必须 返回错误——在构造时捕获错误配置,而不是在运行时
  3. 避免 init() ——它会隐式执行,无法返回错误,并使测试不可预测。使用显式构造函数
  4. 枚举应该 从 1 开始(或者用 Unknown 哨兵值表示 0)——Go 的零值会静默地作为第一个枚举成员传递
  5. 错误情况必须 首先处理并尽早返回——保持主路径平坦
  6. Panic 用于 bug,而非预期错误——调用者可以处理返回的错误;panic 会崩溃进程
  7. 在打开后立即 defer Close() ——后续的代码更改可能会意外跳过清理
  8. runtime.AddCleanup 优于 runtime.SetFinalizer ——终结器不可预测且可能复活对象
  9. 每个外部调用应该 设置超时——上游缓慢会导致你的协程无限期挂起
  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. 为可测试性而设计——接受接口,注入依赖

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

函数选项(推荐)

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() 和测试之前执行——副作用使测试不可预测
// 错误——隐藏的全局状态
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:在不应出现的代码位置出现空指针、违反不变量、在初始化时使用 Must* 构造函数
  • .Close() / Flush() 错误:只读清理通常可以使用 defer f.Close(),但当持久性重要时,写入/刷新资源必须报告关闭或刷新错误

数据处理

string vs []byte vs []rune

类型默认用于使用场景
string所有情况不可变、安全、UTF-8
[]byteI/O写入 io.Writer、构建字符串、可变操作
[]runeUnicode 操作len() 必须表示字符数,而非字节数

避免重复转换——每次转换都会分配内存。保持一种类型,直到你需要另一种。

大数据处理的迭代器与流式处理

使用迭代器(Go 1.23+)和流式处理模式来处理大数据集,无需将所有内容加载到内存。对于服务间的大规模数据传输(例如,从 DB 到 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、事务、可空列、连接池、仓库接口、测试。

架构

询问开发者他们更喜欢哪种架构:整洁架构、六边形架构、领域驱动设计或扁平布局。不要对小项目强加复杂的架构。

无论哪种架构,核心原则:

  • 保持领域纯净——领域层中没有框架依赖
  • 快速失败——在边界处验证,信任内部代码
  • 使非法状态不可表示——使用类型来强制不变量
  • 遵循 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 技能了解协程生命周期和优雅关闭
  • → 参阅 samber/cc-skills-golang@golang-context 技能了解超时和取消模式
  • → 参阅 samber/cc-skills-golang@golang-project-layout 技能了解架构和目录结构
  • → 参阅 samber/cc-skills-golang@golang-refactoring 技能了解如何在现有代码库中安全地逐步迁移到这些模式之一(选项结构体、DI、消费者端接口)

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能