引言:为什么 Go 中的代码审查不仅仅是一个勾选框
代码审查不是官僚程序,而是开发文化的关键要素。在 Go 中,凭借其简洁的语法和严格的规则,审查常常成为早期过滤问题的第一道防线。许多转向 Golang 的团队发现,旧习惯(例如来自 Python 或 PHP 的习惯)不再适用:Go 对类型相关错误的容忍度更低,但要求关注细节。
在实践中,Go 中的代码审查解决三个任务:团队学习(新手更快融入代码风格),提高质量(发现错误和反模式)和代码一致性(确保项目不会变成“拼凑物”)。让我们具体分析一下要看什么以及如何表述意见。
1. 首先检查什么:从语法到错误
任何审查都从基础开始。Go 中有几个“热点”值得自动或手动检查。
1.1. 错误处理
Go 不使用异常——错误作为值返回。常见问题:开发者使用 _ 忽略返回的错误。这仅在极少数情况下允许(例如 fmt.Println)。
// 不好:错误被忽略resp, _ := http.Get("https://api.example.com/data")
// 好:错误被处理resp, err := http.Get("https://api.example.com/data")if err != nil { log.Printf("无法获取数据:%v", err) return err}defer resp.Body.Close()检查什么:
- 所有错误是否都已处理(不仅仅是
_)? - 是否使用
defer关闭资源(文件、连接)? - goroutine 中的错误是否被“吞掉”?
1.2. 命名和风格(Go 惯用法)
Go 要求简洁:私有使用 camelCase,导出使用 PascalCase。避免“匈牙利命名法”和长名称。
// 不好func GetUserDataFromDatabase(userID int) (*UserData, error)
// 好func GetUser(id int) (*User, error)危险信号:
- 变量名如
temp、data、val(过于通用)。 - 函数超过 30-40 行(需要拆分)。
- 导出函数缺少注释(这是
golint的要求)。
1.3. Goroutine 和同步
这是最复杂的部分。并发代码中的错误是生产环境中常见的错误来源。
// 不好:数据竞争var counter intfor i := 0; i < 100; i++ { go func() { counter++ // 不安全 }()}
// 好:使用互斥锁var mu sync.Mutexvar counter intfor i := 0; i < 100; i++ { go func() { mu.Lock() counter++ mu.Unlock() }()}检查什么:
- 共享数据是否有
sync.Mutex或atomic? - 是否忘记关闭通道(
close(ch))? - goroutine 是否泄漏(没有带超时的
select)?
2. 架构检查:从接口到包
在基础语法之后,查看代码结构。Go 鼓励组合而非继承。
2.1. 接口:小而消费者侧
在 Go 中,接口是隐式实现的。好的接口包含 1-3 个方法。检查作者是否试图创建“包罗万象”的接口。
// 不好:巨大的接口type Storage interface { Save(data []byte) error Load(id string) ([]byte, error) Delete(id string) error List() ([]string, error) Connect() error\