golangci-lint 通过显式启用 unused 和 ineffassign linter 检测死代码和无效赋值;二者默认关闭,需在 .golangci.yml 的 linters.enable 中声明,且配置文件必须与 go.mod 同级、命名准确(.golangci.yml),否则回退默认极简配置导致无输出。

golangci-lint 怎么检测死代码(unused)
死代码指定义但从未被调用的函数、方法、变量、类型或 import;unused linter 是专干这事的,它比 deadcode 更准、更轻量,且默认不启用——必须显式打开。
常见误判场景:导出但未在当前 module 调用的函数(如供其他 module 使用的 public API),unused 会误报;非导出函数被测试文件调用,但测试目录被 skip-dirs 忽略时也会漏检。
- 确保
.golangci.yml和go.mod在同一级目录,且配置中包含:unused在linters.enable列表里 - 若项目含多个 module(如 monorepo),每个 module 的根目录都得有自己的一份
.golangci.yml,不能靠继承 - CI 中运行时务必加
--config=.golangci.yml,否则可能 fallback 到默认极简配置(只开govet和errcheck) - 避免和
deadcode同时启用——两者功能重叠,deadcode已被社区普遍弃用
golangci-lint 怎么检测无效赋值(ineffassign)
ineffassign 检查的是「分配了值却没被后续使用」的变量,比如 x := 1; x = 2 中第一次赋值无意义,或 _ = foo() 类型忽略返回值但实际该值不该被丢弃。
它不报错于所有未使用变量(那是 unused 的事),而是聚焦「写入即失效」这类真实资源浪费。典型触发点包括:循环内重复声明同名变量、条件分支中冗余初始化、defer 前赋值后未读取。
立即学习“go语言免费学习笔记(深入)”;
-
ineffassign默认关闭,需手动加入linters.enable - 它对 generated 代码(如 protobuf)极度敏感,务必在
run.skip-dirs中加入- generated和- vendor - 与
staticcheck的 SA4006(死循环)、SA1019(弃用 API)不同,ineffassign不依赖控制流分析,纯语法层扫描,因此速度快、误报少 - 若某处确实需要“写但不读”(如原子计数器初始化),可用
//nolint:ineffassign单行忽略
为什么 run 无输出却不报死代码或无效赋值
最常见原因不是规则没写对,而是配置根本没加载成功——golangci-lint run 在找不到 .golangci.yml 时,会静默退回到默认配置集,而默认配置里 unused 和 ineffassign 都是关着的。
- 执行
golangci-lint help linters查看当前生效的启用列表,确认unused和ineffassign状态是否为enabled - 检查文件名是否拼错:
.golangci.yaml或golangci.yml(缺点)都不行,必须是.golangci.yml - VS Code 的 Go 插件可能缓存旧配置,改完配置后重启 IDE 或手动触发
Go: Restart Language Server - 在子目录下运行
golangci-lint run ./pkg/xxx时,工具仍会从当前工作目录向上找.golangci.yml,但若该路径下没有go.mod,则配置可能被跳过
别让 unused 和 ineffassign 在 CI 里突然失败
这两个 linter 对代码“健康度”敏感,但对“工程成熟度”要求高:一个刚起步的模块,大量未导出函数、临时调试变量、占位符结构体字段,全会被它们当靶子打。
上线前最易踩的坑,是把 unused 当成“清理一切”的开关,结果阻塞 PR;或者在没配 skip-dirs 的情况下,让 ineffassign 扫描到 vendor/ 下的第三方库生成代码,导致上千条误报。
真正稳的做法是:先在本地跑通 golangci-lint run --out-format=tab 看原始输出,确认问题真实存在;再决定是修复、忽略,还是阶段性禁用——而不是一上来就卡死 CI 流水线。


















