deadcode工具默认仅检测包内未被调用的非导出函数和方法,对导出函数(首字母大写)保持沉默;需加-all参数才能强制检查所有函数,包括导出项,并注意module根目录、vendor处理及反射、接口空声明等误判干扰因素。

deadcode 工具能直接报告不可达函数,但默认不扫描导出函数
deadcode 默认只检测包内未被任何路径调用的非导出函数(首字母小写)和方法,对 func Exported() 保持沉默——哪怕它在项目里从未被 import 或调用。这是最常被忽略的盲区。
原因很实际:Go 编译器认为导出函数可能被其他包引用,即使当前模块没用到,也不能贸然标记为死代码。所以 deadcode 的默认行为是保守的。
- 加
-all参数才能强制检查所有函数,包括导出项:deadcode -all ./... - 若项目使用 Go modules,确保当前目录在 module 根下,否则
deadcode可能漏掉跨 module 的调用链 - 注意 vendor 目录:默认会跳过,如需包含,加
-vendor;但多数情况应先清理 vendor 再扫描,避免干扰
接口实现体容易被误判,得看是否真被实例化
像 type Foo struct{} 实现了某个接口,但整个项目从没 new 过 Foo、也没把它赋值给接口变量,那它的方法就是死代码——deadcode 能识别这点,但前提是你没用 var _ Interface = Foo{} 这类“空声明”提前注册。
这种空声明会让工具误以为该类型被显式消费,从而放过其方法。你得人工确认:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 搜索
var _.*=.*Foo模式,删掉无实际用途的接口绑定 - 检查是否有反射调用(如
reflect.ValueOf(&Foo{}).MethodByName("Do")),这类调用deadcode完全无法感知 - 如果用了第三方框架(如 Gin 的中间件注册),确认 handler 是否真被路由挂载,而不是仅定义未注册
pprof /block 不是死代码检测工具,但能暴露“伪死代码”现场
/debug/pprof/block 显示的是阻塞在同步原语上的 goroutine,比如卡在 mu.Lock() 或 上超过阈值的调用栈。它不告诉你哪段代码没被调用,但能揪出“看似活、实则废”的逻辑分支。
典型场景:
- 一个函数启动 goroutine 发送数据到 channel,但忘了
close(ch)→ 接收方永远阻塞,该接收逻辑虽被编译进去,却永不执行 - 某配置开关为 false,导致整个 if 分支 never taken,但编译器无法证明其不可达 →
deadcode不报,/block却可能显示该分支里某把锁长期无人释放 - HTTP handler 中有
select { case ,default 分支永远不进,但代码仍在二进制里 —— 这不是死代码,是低效代码,<code>deadcode无法识别
CI 中跑 deadcode 要配白名单,否则易误伤
Go 生态里大量依赖“声明即注册”模式:比如 database/sql 驱动靠 import _ "github.com/go-sql-driver/mysql" 触发 init,驱动函数本身不会被显式调用。这类函数会被 deadcode 报告,但绝不能删。
处理方式只有两个:
- 用
-exclude列出已知安全的函数签名:deadcode -exclude="^github\.com/.*\.init$" ./... - 生成 baseline 文件(
deadcode -f=json > baseline.json),后续只比对新增项,避免历史噪音干扰 - 切记:不要用
//nolint:deadcode注释屏蔽——这会让问题沉淀,下次扫描时仍出现,失去持续清理意义
go tool trace + 手动覆盖率分析,而不是单靠静态扫描。

















