关于 go-design-patterns
面向 Go 开发的编码技能,指导开发者采用惯用的构造、行为、弹性与类型设计模式,并提供按需查阅的详细参考资料。
主要功能
- 01按设计需求选择模式
- 02函数选项与构造函数
- 03工厂与中间件组合
- 04重试、熔断器与隔离舱
- 05用类型表达有效状态
适用场景
- 01为 Go API 选择配置模式
- 02为依赖调用增加弹性
- 03组织中间件与装饰器
- 04表示经过验证的值和生命周期
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-design-patterns安装后在 Claude Code 中输入/go-design-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/behavioral-patterns.mdcreation-patterns.mdresilience-and-types.md详细介绍
Go 设计模式
Go 倾向于组合而非继承,倾向于简单而非抽象。 这些模式符合 Go 惯用法,不是移植到 Go 的 Java 模式。
社区默认规则。如果项目技能或 Agent 配置规则明确取代了
go-design-patterns,则以该规则为准。
详细参考资料,按需加载:
references/creation-patterns.md— 函数选项(完整示例)、 选项与配置结构体的比较、构造函数、工厂。references/behavioral-patterns.md— 策略、中间件/装饰器、 结果类型、defer 清理、哨兵值与零值的比较。references/resilience-and-types.md— 带抖动的重试、熔断器、 隔离舱、解析而非校验类型、状态类型。
仅当下方摘要不足时才阅读参考文件。
模式选择
| 需求 | 模式 | 参考资料 |
|---|---|---|
| 带有许多可选设置的构造函数 | 函数选项 | creation-patterns.md |
| 从文件/环境变量加载配置,且字段大多必需 | 配置结构体 | creation-patterns.md |
| 在创建时强制执行不变量 | 返回错误的构造函数 | creation-patterns.md |
| 根据运行时配置选择实现 | 返回接口的工厂 | creation-patterns.md |
| 在运行时切换简单行为 | 通过函数类型实现策略 | behavioral-patterns.md |
| 在运行时切换复杂行为 | 通过接口实现策略 | behavioral-patterns.md |
| 包装横切关注点(日志、缓存、指标) | 中间件/装饰器 | behavioral-patterns.md |
| 并发流水线中的值或错误 | Result[T] 结构体 | behavioral-patterns.md |
| 依赖项发生暂时性故障 | 带退避和抖动的重试 | resilience-and-types.md |
| 依赖项不可用 | 熔断器、隔离舱 | resilience-and-types.md |
| 只校验一次,之后即可信任 | 解析为字段未导出的类型 | resilience-and-types.md |
| 用多个布尔值描述同一生命周期 | 显式定义状态转换的状态类型 | resilience-and-types.md |
1. 函数选项(要点)
type Option func(*Server)
func WithAddr(addr string) Option {
return func(s *Server) { s.addr = addr }
}
func NewServer(opts ...Option) *Server {
s := &Server{
addr: ":8080", // sensible defaults first
readTimeout: 5 * time.Second,
logger: slog.Default(),
}
for _, opt := range opts {
opt(s)
}
return s
}
srv := NewServer(WithAddr(":9090"))
适用场景:有许多带合理默认值的可选参数,API 会随时间演进(新增选项不会破坏调用方),选项需要校验。 如果大多数字段都是必需的,或者配置从文件/环境变量反序列化而来,则应改用普通配置结构体。
2. 构造函数规则
- 每个带有不变量的导出类型都需要构造函数。
- 校验必需的依赖项;返回错误,不要触发 panic:
// ✅ Good — constructor enforces invariants
func NewUserService(repo UserRepository, logger *slog.Logger) (*UserService, error) {
if repo == nil {
return nil, errors.New("user service: nil repository")
}
return &UserService{repo: repo, logger: logger}, nil
}
// ❌ Bad — struct literal with no validation
svc := &UserService{} // nil dependencies → panic at runtime
3. 工厂
返回接口,而非具体类型。工厂是唯一知道具体实现的地方:
func NewStore(cfg Config) (Store, error) {
switch cfg.StoreType {
case "redis":
return newRedisStore(cfg.RedisAddr)
case "memory":
return newMemoryStore(), nil
default:
return nil, fmt.Errorf("unknown store type: %s", cfg.StoreType)
}
}
4. 中间件链
标准 HTTP 组合模式:
type Middleware func(http.Handler) http.Handler
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
handler := Chain(appHandler, Recoverer, RequestID, Logger, Auth)
同样的结构适用于任何接口:将装饰器按如下顺序堆叠:
cache → logging → metrics → actual repo
(参见 references/behavioral-patterns.md)。
5. 优先考虑零值
优先选择零值有用的类型(sync.Mutex、bytes.Buffer、
nil 切片)。只有当零值作为输入含义不明确时,才考虑使用哨兵包装类型或指针(nil *float64 =“未配置”)。
6. 弹性
- 每个出站调用都应有来自
ctx的截止时间(go-context 负责管理预算)。 - 仅重试可安全重复的操作,并使用指数退避和全抖动、设置上限,且只在一层进行——嵌套重试会产生倍增效应。
- 依赖项不可用时,熔断器会快速失败;使用维护良好的实现,并在熔断器打开时采用降级方案(缓存值、部分响应)。
- 隔离舱(每个依赖项一个信号量)可防止单个响应缓慢的依赖项耗尽所有 goroutine 和连接。
7. 让非法状态无法表示
将输入解析一次,得到字段未导出的类型(ParseEmail →
Email),这样接收该类型的每个函数都可以信任它。用一种状态类型和用于检查状态转换的方法,替换成组的布尔值(Paid、Shipped、Cancelled)。
第 6–7 节的示例:references/resilience-and-types.md。
应避免的反模式
// ❌ God interface — too many methods
type Service interface {
GetUser(ctx context.Context, id string) (*User, error)
CreateUser(ctx context.Context, u *User) error
DeleteUser(ctx context.Context, id string) error
ListOrders(ctx context.Context, userID string) ([]Order, error)
// 20 more methods...
}
// → Split into focused interfaces: UserReader, UserWriter, OrderLister
// ❌ Premature abstraction — interface for one implementation
type UserCache interface {
Get(key string) (*User, bool)
Set(key string, user *User)
}
// If there's only ever one implementation, use the concrete type.
// Extract an interface when a second consumer or implementation appears.
// ❌ Java-style inheritance simulation
type BaseService struct{ /* ... */ }
type UserService struct{ BaseService } // embedding is NOT inheritance
// → Use composition: UserService has a dependency, not a parent.
验证清单
- 对可选配置的类型使用函数选项
- 构造函数验证必需的依赖项并返回错误
- 工厂函数返回接口,而不是具体类型
- 不使用巨型接口——每个接口有 1–3 个方法
- 中间件遵循
func(http.Handler) http.Handler签名 - 装饰器包装接口,而不是具体类型
- 使用
defer清理所有资源(文件、连接、锁) - 零值有意义——无需不必要的初始化
- 不进行过早抽象——仅在需要时提取接口
- 使用组合而不是嵌入来复用代码
- 出站调用设有截止时间;重试带随机抖动、有上限且仅在一层进行
- 将经过验证的值和生命周期编码到类型中,而不是重复检查或通过布尔值分散表示