Go 错误处理分析器
对 Go 代码库中的错误处理模式进行深入分析。检测吞没的错误、缺失的包装上下文、不正确的哨兵使用、不安全的类型断言和非惯用模式。生成与 Go 谚语和标准库约定一致的优先分析结果。
使用时:审查 Go 代码以确定其是否适合生产、审计错误处理或建立团队约定。
分析步骤
cat go.mod 2>/dev/null | head -10
find . -name "
.go" -not -path '/vendor/
' | wc -l
find . -type d -name "errors" -o -name "errs" -not -path '/vendor/
' 2>/dev/null
grep "pkg/errors\|go.uber.org/multierr\|cockroachdb/errors" go.mod 2>/dev/null
确定:Go 版本(影响 errors.Is/As 的可用性)、错误库的使用情况、自定义错误基础设施。最关键的检查。吞没的错误是被忽略的 err 返回值。
# 显式下划线忽略错误
grep -rn '\b_\s=.
(' --include=".go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -25
# err 被赋值但从未检查
grep -rn 'err\s
=' --include=".go" . 2>/dev/null | grep -v 'if.
err\|return.err\|vendor/' | head -20
# defer 忽略错误(defer f.Close())
grep -rn 'defer.
Close()\|defer.Flush()\|defer.
Rollback()' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -15
# http.Error 无返回(处理程序在错误响应后继续)
grep -A2 'http\.Error(' --include="
.go" . 2>/dev/null | grep -v 'return\|vendor/' | head -15
严重性指南:
严重:来自 DB、文件 I/O、网络调用的被忽略的错误
高:_ = someFunc() 对于可能以有意义的方式失败的函数
中:defer f.Close() 没有错误处理 —— 使用命名返回 + defer 闭包
低:忽略 fmt.Fprintf 到 stdout(在 CLI 中可接受)# 使用 %w(Go 1.13+)正确包装
grep -rn 'fmt\.Errorf.%w' --include="
.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# 使用 %v/%s 代替 %w(丢失错误链)
grep -rn 'fmt\.Errorf.%[vs].
err\b' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -15
# 返回错误而不包装(丢失上下文)
grep -rn 'return.
err$' --include=".go" . 2>/dev/null | grep -v 'nil\|vendor/\|_test.go' | head -20
# 错误消息约定(不应以大写字母开头或以标点符号结尾)
grep -rn 'fmt\.Errorf("[ ]
[A-Z]' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -10
grep -rn 'errors\.New(".
\.")' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -10
标志:%v 代替 %w:fmt.Errorf("failed: %v", err) 会破坏 errors.Is()/errors.As() —— 使用 %w
裸错误返回:跨包边界返回 err 会丢失调用站点上下文 —— 使用 fmt.Errorf("operation: %w", err) 包装
大写/标点错误消息:Go 约定为小写,无尾随句号(错误消息可组合)
# 哨兵错误
grep -rn 'var\s\+Err[A-Z].
=\serrors\.New' --include="
.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# 使用字符串比较代替 errors.Is(不稳定)
grep -rn 'err\.Error()\s==' --include="
.go" . 2>/dev/null | grep -v 'vendor/' | head -10
# 直接相等(包装会破坏)
grep -rn 'err\s==\s
Err\|err\s!=\s
Err' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -10
# errors.Is / errors.As 使用情况(正确模式)
grep -rn 'errors\.Is(\|errors\.As(' --include="
.go" . 2>/dev/null | grep -v 'vendor/' | head -15
# 自定义错误类型
grep -rn 'func.Error()\s
string' --include=".go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -15
# 对错误的类型断言(包装会破坏)
grep -rn 'err\.(\
' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -10
标志:字符串比较:err.Error() == "not found" —— 使用 errors.Is(err, ErrNotFound)
直接相等:err == ErrNotFound 如果包装会失败 —— 使用 errors.Is()
缺失 Unwrap:包装其他错误的自定义错误类型必须实现 Unwrap() 错误
重复字符串错误:errors.New("not found") 在多个地方应该是一个哨兵
# 内部错误泄露到客户端(安全风险)
grep -rn 'http\.Error(w,.
err\.Error()' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -10
# 非测试代码中的 panic
grep -rn 'panic(' --include="
.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -10
# 没有错误传播的 Goroutine
grep -B2 -A10 'go func()' --include=".go" . 2>/dev/null | grep -v 'vendor/' | head -30
# errgroup 使用情况
grep -rn 'errgroup\|g\.Go\|group\.Go' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
标志:内部错误泄露:http.Error(w, err.Error(), 500) 向客户端发送原始错误 —— 在内部记录,返回通用消息
http.Error 后缺失返回:处理程序继续,可能写入多个响应
Goroutine 无错误通道/errgroup:错误被默默丢失
长期运行的 Goroutine 中缺失 recover:panic 会崩溃进程