Go代码审查是防止nil panic、goroutine泄漏、defer失效和错误静默的最后防线,必须卡住忽略error的写法,检查defer作用域、goroutine生命周期及命名与接口是否符合Go惯例。

Go 团队代码审查不是“挑错流程”,而是防止 nil panic、goroutine 泄漏、defer 失效和错误被静默吞掉的最后防线。只要团队还在用 http.Get、os.Open、json.Unmarshal 这类会返回 error 的函数,审查就必须卡住忽略 err 的写法。
检查 error 是否被显式处理
Go 中绝大多数 I/O、网络、序列化操作都返回 error,但新人常写成 resp, _ := http.Get(url) 或 data, _ := json.Marshal(v) —— 下划线丢弃错误后,程序在生产环境静默失败,日志里只有一片空白。
实操建议:
- 所有含
error返回值的调用,必须出现在if err != nil判断分支中,或明确包装后向上抛(如return fmt.Errorf("failed to parse: %w", err)) - 禁止在测试文件外使用
_忽略error;CI 流程中应启用golangci-lint的errcheck插件强制拦截 - 对已知不会出错的调用(如
bytes.NewReader([]byte{})),也建议保留err变量并加注释说明,避免后续变更引入风险
确认 defer 是否在正确作用域内执行
defer 不是“函数退出时才运行”,而是在包含它的函数体结束前执行——但若写在循环或条件块内,可能根本不会触发,或多次重复注册同一资源释放逻辑。
立即学习“go语言免费学习笔记(深入)”;
常见错误现象:
-
for range files { f, _ := os.Open(name); defer f.Close() }:只有最后一次打开的文件被关闭,其余全泄漏 -
if cond { f, _ := os.Open(...); defer f.Close() }:cond 为 false 时defer不注册,cond 为 true 时又可能因提前 return 而未执行
正确做法是把资源获取和 defer 放在同一作用域,且确保它一定被执行:
func processFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // 这里才安全
return doSomething(f)
}
验证 goroutine 生命周期是否受控
无 context 管理的裸 go func() 是 Go 项目中最隐蔽的内存与句柄泄漏源。审查时重点不是“有没有 go”,而是“它怎么停”。
使用场景与判断点:
- 后台轮询、心跳、监听器等长周期任务,必须接收
context.Context参数,并在select中监听ctx.Done() - 启动 goroutine 前,确认其内部没有无限
for{}且无退出信号;否则即使父函数返回,goroutine 仍在后台跑 - 避免在循环中直接起 goroutine 且不控制并发数,应使用带缓冲 channel 或
semaphore限流
典型反模式:go func() { for { work() } }() —— 没有退出路径,无法测试、无法观测、无法回收。
命名与接口是否符合 Go 惯例
Go 不强制匈牙利命名或动词前缀,但团队一旦接受 GetUser,就会在 3 个包里看到 GetUserByID、GetUserList、GetUserProfile,最终谁都分不清哪个是缓存读、哪个走 DB、哪个带权限校验。
关键差异点:
- 导出函数名用
MixedCaps,但语义优先:用Owner()而非GetOwner(),用Bytes()而非ToBytes() - 单方法接口名用
-er后缀(io.Reader,http.Handler),两个及以上方法就用名词(sql.Rows,sync.Pool) - 包名必须小写、短、无下划线(
sql,yaml,cli),禁止与标准库冲突(如自建http包)
这不是风格偏好问题——当别人 grep Reader 找接口实现,或用 IDE 跳转 WriteTo 查调用链时,不遵守惯例会让整个团队多花两倍时间理解代码。
真正难的不是记住这些条目,而是在每次 review 时,盯着一行 json.Unmarshal 想三秒:它返回的 error 去哪了?那个 defer 真的关掉了刚打开的文件,还是只是给编译器看了眼?那个 go 启动的 goroutine,上线后会不会变成永远杀不死的僵尸?


















