errcheck 是 golangci-lint 中专用于检测显式丢弃 error 值(如 someFunc() 或 , = someFunc())的核心插件,不分析变量后续使用或错误是否被包装,仅检查是否未接收 error 返回值。

为什么 errcheck 是检查未包裹错误的核心插件
golangci-lint 本身不直接检测“未包裹的错误返回”,它依赖内置的 errcheck linter。这个工具专门识别形如 someFunc()(忽略返回的 error)或 _, _ = someFunc()(忽略 error 但保留其他返回值)的调用,但它默认**不会**报错那些已赋值给变量但后续未检查的错误——比如 err := someFunc(); return err 这种是安全的,而 someFunc(); return nil 才会被捕获。
关键点在于:errcheck 只关心“是否显式丢弃了 error 值”,不分析控制流或变量生命周期。
- 默认启用:golangci-lint v1.50+ 默认包含
errcheck,但可能被配置禁用,需确认 - 它不检查
log.Fatal(err)或panic(err)等终止流程的用法——这些算作“已处理” - 对方法链如
io.Copy(dst, src)同样生效,只要返回 error 且被完全忽略
如何启用并微调 errcheck 检查规则
在 .golangci.yml 中明确启用并排除误报项最稳妥。默认配置有时会漏掉某些标准库函数(如 fmt.Printf),需要手动白名单。
linters-settings:
errcheck:
# 忽略常见无副作用、可安全忽略 error 的函数
exclude-functions:
- "fmt.Print*"
- "os.Exit"
- "log.*"
- "testing.T.*"
- "http.Error"
- 必须用通配符
*匹配函数族(fmt.Print*覆盖Print/Printf/Println) - 不要写
fmt.Println单独一行——它会被fmt.Print*自动覆盖 - 若项目用了
github.com/pkg/errors或fmt.Errorf构造错误,它们不返回 error,不在errcheck检查范围内
errcheck 报错但实际需要忽略?用注释跳过
有些场景确实要忽略 error,比如写监控打点、日志上报失败可容忍。这时不能删代码,得用 //nolint:errcheck 注释跳过单行检查。
立即学习“go语言免费学习笔记(深入)”;
注意:必须紧贴出问题的那行代码上方或右侧,且中间不能有空行:
_, _ = os.Stat("/tmp/optional") //nolint:errcheck
// 或
//nolint:errcheck
_ = os.Remove("/tmp/stale.lock")
- 不能写成
//nolint(缺少 linter 名)或//nolint:goerr113(那是另一个 linter) - 避免批量加在函数开头——它只作用于紧邻的下一行
- 如果整个文件都需忽略(极少见),在文件顶部加
//nolint:errcheck
容易被误认为“未包裹”但其实合法的写法
很多开发者把“没用 errors.Wrap 或 fmt.Errorf 包裹原始 error”当成 errcheck 问题,其实这是两类事:errcheck 不管你是否包装,只管你是否丢弃。
-
if err := doThing(); err != nil { return err }✅ 安全,已检查 -
err := doThing(); if err != nil { log.Println(err); return nil }✅ 已使用,未丢弃 -
doThing()❌ 丢弃 error,触发 errcheck -
_, err := doThing2(); return err✅ 即使没包装,也属于“返回原始 error”,不违反 errcheck
真正需要包装错误的场景(比如加上下文、堆栈)由 goerr113 或 errorlint 等其他 linter 负责,不是 errcheck 的职责。
混淆这两类检查是最常见的配置失误——开了 errcheck 却以为它能发现“没包装”,结果线上错误日志缺乏上下文,却一直没意识到该开的是 errorlint。


















