必须显式禁用typecheck,因其与Go Modules(尤其Go 1.21+)类型解析冲突,易引发panic、超时或“failed to load program”错误,且功能被go build冗余覆盖。

Go Modules 项目不配 .golangci.yml 就跑 golangci-lint run,大概率会卡住、panic 或报 failed to load program: typechecker error ——这不是你代码的问题,是工具在用已弃用的 linter 硬扛 Go 1.21+ 的模块解析逻辑。
为什么命令行工具项目必须显式禁用 typecheck
命令行工具(如用 github.com/spf13/cobra 或纯 flag 构建)通常依赖大量 interface、泛型推导和构建标签(//go:build),而 typecheck linter 会尝试完整加载类型系统,与 go build -mod=readonly 行为冲突。它不只冗余(go build 已覆盖),还会在 init() 链或未导出包路径上触发 AST 解析失败。
-
typecheck在 v1.54+ 默认启用,但官方文档明确标注“should be disabled in modules projects” - 命令行项目常含
main.go+ 多个子命令文件,跨包 import 频繁,typecheck易因循环引用或未 resolve 的_test包 panic - 实测:禁用后
golangci-lint run耗时从 >3min 降到
skip-dirs 必须排除 cmd/ 下的非主入口目录
很多 CLI 项目把多个可执行文件放在 cmd/ 下:cmd/app、cmd/cli、cmd/migrate。若只在根目录配 skip-dirs: ["vendor", "testdata"],golangci-lint 会逐个分析每个 cmd/* 目录,导致重复检查、误报未使用变量(因各 main 入口独立)、甚至 govet 报 printf 格式不匹配(不同 cmd 用不同日志库)。
- 正确做法:用
run.skip-dirs显式跳过非主入口,例如- cmd/migrate、- cmd/testutil - 更稳妥的是按需启用:只对
cmd/app和internal/运行,用golangci-lint run ./cmd/app ./internal/...替代全局扫描 - 若用
go.work管理多命令,还需加run.modules-download-mode: readonly,否则 lint 过程可能意外写go.sum
errcheck 和 staticcheck 的关键参数调优
命令行工具高频调用 os.Exit()、log.Fatal()、flag.Parse(),默认 errcheck 会误报“error not checked”,而 staticcheck 的 SA1019(弃用警告)在 CLI 场景下价值偏低(用户升级二进制即可,不需兼容旧 API)。
立即学习“go语言免费学习笔记(深入)”;
-
errcheck:设check-blank: false(避免_ = os.Remove()类写法被拦),但保留check-type-assertions: true(防止v, ok := x.(T)忘写ok判断) -
staticcheck:不用checks: ["all"],改用checks: ["SA1000", "SA1001", "SA1006", "SA1012"]—— 聚焦字符串格式、HTTP header、time.Parse 错误等真实高危项 - 禁用
golint:它已归档,且其命名规则(如var fooBar int)与 CLI 常见 flag 名(foo-bar)冲突,产生大量假阳性
最易被忽略的一点:命令行项目往往有自动生成的代码(如 cmd/gen 下的模板渲染文件、protobuf 生成的 cmd/api/*.pb.go),这些必须用 skip-dirs 或 issues.exclude 显式排除——errcheck 对 proto.Unmarshal 的返回值报错毫无意义,只会拖慢 CI。


















