Go中查找死代码的工具


定期运行deadcode,尤其是在 重构工作,以帮助识别程序中不再需要的代码。

$ go install golang.org/x/tools/cmd/deadcode@latest

在Go项目下运行:

$ deadcode .
greet.go:23: unreachable func: goodbye
greet.go:20: unreachable func: Goodbyer.Greet

它发现 goodbye 函数和 Goodbyer.Greet 方法无法访问。

该工具还可以解释hello功能为何有效。它响应 从 main 开始,函数调用链到达 hello:

$ deadcode -whylive=example.com/greet.hello .
                  example.com/greet.main
dynamic@L0008 --> example.com/greet.Helloer.Greet
 static@L0019 --> example.com/greet.hello

如果它报告某个库软件包中的某个函数已死,这说明你的测试覆盖率还有待提高。
例如,下面命令列出了 encoding/json 中所有测试都未触及的函数:

$ deadcode -test -filter=encoding/json encoding/json
encoding/json/decode.go:150:31: unreachable func: UnmarshalFieldError.Error
encoding/json/encode.go:225:28: unreachable func: InvalidUTF8Error.Error


原理:
deadcode 命令会加载、解析和类型检查指定的软件包,然后将其转换为类似于典型编译器的中间表示形式。

然后,它使用一种名为快速类型分析(RTA)的算法来建立可访问的函数集,最初只是每个主软件包的入口点:主函数和软件包初始化函数,后者分配全局变量并调用名为 init 的函数。

RTA 会查看每个可访问函数正文中的语句,以收集三种信息:直接调用的函数集;通过接口方法进行的动态调用集;转换为接口的类型集。

直接调用函数很简单:我们只需将被调用者添加到可访问函数集合中,如果这是我们第一次遇到被调用者,我们就会像检查 main 函数一样检查其函数体。

通过接口方法进行动态调用比较棘手,因为我们不知道实现接口的类型集。我们不能假定程序中所有类型匹配的方法都是可能的调用目标,因为其中有些类型可能只是通过死代码实例化的!这就是我们收集转换为接口的类型集的原因:转换使这些类型中的每一个都可以从 main 访问,因此其方法现在都是动态调用的可能目标。

这就出现了 "先有鸡还是先有蛋 "的情况。当我们遇到每个新的可达函数时,我们会发现更多的接口方法调用和更多的具体类型到接口类型的转换。但随着这两个集合(接口方法调用 × 具体类型)的乘积越来越大,我们又会发现新的可达函数。

这类问题被称为 "动态编程",其解决方法是(在概念上)在一个大型二维表格中打钩,边打钩边增加行和列,直到没有打钩的地方为止。最后表格中的复选标记告诉我们哪些是可达到的,空白单元格则是死代码。

对(非方法)函数的动态调用与单个方法的接口类似。而使用反射进行的调用则被认为是对接口转换中使用的任何类型的任何方法的调用,或者是对使用反射包从接口中派生出来的任何类型的调用。但所有情况下的原则都是一样的。

其他: