其他
在 Go 项目中选择惯用设计模式与架构时(函数式选项…
当你需要为 Go 代码挑选惯用设计模式、设计构造器 API、实现优雅关闭与弹性策略,或决定采用哪种架构风格时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 47/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 47/100 ×0.15
适用场景
在 Go 项目中选择惯用设计模式与架构时通用
解决思路
- 构造器优先使用函数式选项(functional options):每个选项一个函数,API 演进时不会产生破坏性变更。
- 函数式选项在可能校验失败时必须返回 error,把错误配置在构造阶段就暴露,而不是留到运行时。
- 避免使用 init():它隐式执行、无法返回错误、让测试不可预测;改用显式构造器。
- 枚举从 1 开始(或把 0 作为 Unknown 哨兵值),因为 Go 的零值会静默地等于第一个枚举成员。
- 错误分支必须优先处理并使用提前返回(early return),保持 happy path 处于最小缩进。
- panic 只用于 bug,不用于可预期的错误:调用方能处理返回的 error,而 panic 会直接崩溃进程。
- 打开资源后立即 defer Close(),不要等到几十行之后再写,以免后续改动意外跳过清理。
- 优先使用 runtime.AddCleanup 而不是 runtime.SetFinalizer,因为 finalizer 行为不可预测且可能让对象复活。
- 每个外部调用都应设置超时(如 context.WithTimeout),否则慢上游会无限期挂住你的 goroutine。
- 限制一切资源(连接池大小、队列深度、缓冲区),无界资源会持续增长直到崩溃。
- 重试逻辑必须在每次尝试之间检查 context 取消(ctx.Err()),并使用指数或线性退避配合 select 监听 ctx.Done()。
- 循环中拼接字符串使用 strings.Builder。
- string 与 []byte 的选择:需要修改和 I/O 时用 []byte,用于展示和键时用 string,避免反复转换造成分配。
- 迭代器(Go 1.23+)用于惰性求值,避免一次性把所有数据加载进内存。
- 大流量传输要流式处理:加载数百万行会导致 OOM,流式处理可保持内存恒定。
- 静态资源使用 //go:embed,在编译期嵌入,消除运行时文件 I/O 错误。
- 密钥和令牌使用 crypto/rand,math/rand 是可预测的。
- 正则表达式必须在包级别编译一次,因为编译是 O(n) 且会分配内存。
- 使用编译期接口检查:var _ Interface = (*Type)(nil)。
- 少量重写代码优于引入大依赖,每个依赖都会增加攻击面和维护负担。
- 为可测试性而设计:接受接口、注入依赖、保持函数纯粹。
- 仅在需要配置步骤间复杂校验时才使用 builder 模式,否则优先函数式选项。
- 只读清理可以 defer f.Close(),但当持久性重要时,写入/刷新资源必须上报 close 或 flush 错误。
- 长循环必须周期性检查 ctx.Err()。
- 架构选择前先询问开发者偏好(clean architecture、hexagonal、DDD 或扁平布局),不要强加复杂架构给小型项目。
- 无论采用哪种架构,都保持领域层纯粹(不依赖框架)、在边界处快速失败、用类型让非法状态不可表示、遵循 12-factor app 原则。
适用边界 · 注意事项
- 不要用本技能来接线 DI 容器或比较 DI 库(应参考 golang-dependency-injection 技能)。
- 不要用本技能处理错误包装、errors.Is/As 或日志机制(应参考 golang-error-handling 技能)。
- 不要为了展示技巧而套用模式,也不要过早抽象;只在模式能解决真实问题时才使用。