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

Go 代码审查不是格式检查,而是防止 nil panic、goroutine 泄漏、defer 失效和错误静默的最后防线。只要项目里还调用 http.Get、os.Open、json.Unmarshal 这类函数,忽略 err 就等于给生产环境埋雷。
为什么 resp, _ := http.Get(url) 必须被拦下来
下划线丢弃 error 后,网络超时、DNS失败、TLS握手异常全无痕迹——服务日志空白,监控无指标,问题只能靠用户投诉触发。这不是“写法不优雅”,是直接绕过 Go 错误处理契约。
- 所有含
error返回值的调用,必须显式出现在if err != nil分支中,或用fmt.Errorf("xxx: %w", err)包装后向上抛 - 测试文件外禁止用
_忽略error;CI 中必须启用golangci-lint的errcheck插件拦截 - 对已知不会出错的调用(如
bytes.NewReader([]byte{})),也建议保留err变量并加注释说明,避免后续变更引入风险
defer f.Close() 放错位置,等于没写
defer 不是“函数退出时才运行”,而是在包含它的作用域结束前执行。写在循环或条件块里,极易导致资源只关最后一次、或根本没注册。
-
for range files { f, _ := os.Open(name); defer f.Close() }:只有最后一次打开的文件被关闭,其余全泄漏 -
if cond { f, _ := os.Open(); defer f.Close() }:cond为 false 时defer不注册,为 true 时又可能因提前return而未执行 - 正确做法是把资源获取和
defer放在同一作用域顶层,确保它一定被执行:func processFile(name string) error { f, err := os.Open(name) if err != nil { return err } defer f.Close() // 这里才安全 return doSomething(f) }
context.WithTimeout 配对 cancel 是硬性要求,不是可选项
裸 go func() 没有 context 管理,就是内存与句柄泄漏源。审查重点不是“有没有 go”,而是“它怎么停”。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 后台轮询、心跳、监听器等长周期任务,必须接收
context.Context参数,并在select中监听ctx.Done() -
context.WithCancel/context.WithTimeout调用后,cancel()必须在defer中调用,且不能放在 goroutine 内部(会导致不执行) - 审查时优先用
grep -n "context.With" *.go定位关键节点,逐行确认是否配对、是否在正确作用域内调用
sync.Map 不是万能 Map,写多读少时性能反而更差
过度信任 sync.Map,把它当通用并发 Map 用,在写多读少场景下会触发频繁的 dirtymap 升级与加锁重构,性能比 sync.RWMutex + 原生 map 还差。
- 高频写入场景(如请求计数、状态缓存更新),优先考虑
sync.RWMutex+map或分片锁 -
sync.Map仅适合读远多于写的场景(如配置缓存、白名单表) - 若必须用,需配合 pprof 验证实际争用情况,而非凭直觉判断
最常被忽略的是 defer 参数求值时机和 error 链断裂——前者让 defer f.Close() 关掉错误的文件描述符,后者让 errors.Is(err, myErr) 永远返回 false。这些点不靠工具扫描,得靠人盯住函数签名和调用上下文。

















