go-idiomslisted
Install: claude install-skill Wade-DevCode/awesome-coding-skills-cn
# Go 惯用法
## 何时用
- 写新的 Go 函数、包或服务时。
- 处理错误链路、goroutine 生命周期或接口设计时。
- Review Go 代码,发现有被忽略的 `err`、无法退出的 goroutine 或过度抽象的接口时。
- 用 `defer` 管理资源或发现循环变量捕获问题时。
- 整理 Go 项目结构或推敲包的公开 API 时。
## 核心规则
### 1. 错误显式处理:`if err != nil` 不忽略;用 `%w` 包装保留链路
**规则:** 每个返回 `error` 的调用结果必须检查;向上传递时用 `fmt.Errorf("操作说明: %w", err)` 包装,保留原始错误供 `errors.Is`/`errors.As` 使用;禁止用 `_` 丢弃 `error`。
**为什么:** AI 在快速生成代码时极易写出 `result, _ := db.Query(...)`——把错误扔掉,程序继续用一个零值 `result` 往下跑,最终在几十行后 nil pointer panic,且调用栈完全看不出根因。Go 的设计哲学就是让错误无处可藏,用 `_` 丢弃等于主动绕过这层保护。
**怎么做:**
- 每次调用后立即 `if err != nil { return ..., fmt.Errorf("xxx: %w", err) }`。
- 最终边界(main、HTTP handler)负责记录日志;中间层只包装不打印,避免重复日志。
- 需要判断错误类型 → `errors.Is(err, ErrNotFound)` 或 `errors.As(err, &target)`;不要字符串匹配。
---
### 2. 并发用 channel/sync 正确同步;`go` 启动的 goroutine 要能退出
**规则:** 每个 `go func()` 启动的 goroutine 都必须有明确的退出路径(通过 `context.Done()`、关闭 channel 或 `WaitGroup`);共享内存的并发访问必须用 `sync.Mutex`/`sync.RWMutex` 或原子操作保护;不用裸 goroutine 泄漏。
**为什么:** AI 常见并发 bug:`go func() { for { process() } }()` 启动后无法停止,服务关闭时 goroutine 还在运行,造成资源泄漏或数据竞争。另一个高频错误是在循环中直接捕获循环变量:`go func() { fmt.Println(v) }()` —— 所有 goroutine 最终打印同一个 `v`(Go 1.22 前的经典陷阱,升级版本不等于旧代码变安全)。
**怎么做:**
- 长期运行的 goroutine 必须接受 `ctx context.Context`,监听 `ctx.Done()`。
- 等待一组 goroutine → `sync.WaitGroup`;传结果 → buffered channel,大小与 goroutine 数匹配。
- 循环内启动 goroutine → 把循环变量显式传入:`go func(v T) { ... }(v)`(或升级到 Go 1.22+)。
- 用 `go test -race` 在 CI 中检测数据竞争。
---
### 3. 接口小而专,在使用方定义;不过度抽象
**规则:** 接口只包含调用方实际需要的方法(通常 1–3 个);接口定义放在