本文最初版本发表于DZone。
在过去几个月里,我一直在研究一项可以帮助开发团队减少代码量的功能。我们读到的所有内容都围绕着新的框架、新的工具和新的技术,然而,我们许多人都忽略了一件事:只需简单地丢弃我们不再需要的东西,就能提高运转效率。如果您是一名资深开发人员,正在帮助团队的新成员。请思考一下,您要完成多少工作,才能让新成员顺利上手工作内容,了解这些代码和业务。每当做出修改时,他们都会滚动屏幕,跳过一些方法,他们的集成开发环境 (IDE) 可以帮助他们在文件和方法之间找到方向。但是,我们真的需要所有这些代码吗?当我和团队谈到我们积累了大量死代码的想法时,我听到了这样的评论:
如果 Java 开发人员能有一种更简单的方法来识别需要删除的死代码,让我们可以在冲刺期间优先进行代码清理,以减少技术债,同时又无需占用为满足业务需求而添加功能的时间,是不是很美妙?
删除代码是一项复杂的工作,并且与开发新功能相比,通常被视作是次要的。团队在重构过程中(例如,将注解注释掉、变更路径,或移动功能),随着时间的推移,越来越多的代码由于未被删除而变为无用的代码。大多数资深工程师都会在冲刺期间分配时间评估缺失的日志语句,或使用静态分析器审查代码,寻找需要删除的内容。从花费的时间看,这两种方式都存在问题,因此,许多团队选择将这些内容留在代码库中。虽然它们留下来了,但实际上已经被废弃,这将成为未来团队领导的问题,或者会被推迟到下一次大规模重写再进行处理。然而,JVM 拥有一项被忽视的能力:识别死代码,并简化这个优先级问题。JVM 能够重新利用字节码解释器,在每次执行中识别方法的首次调用时间。通过在一个中心位置进行追踪,这些日志将形成一张寻宝图,您可以按照这些线索减少死代码,从而减轻整体认知负担,并提高团队的运作效率。如果某个方法有一年未运行,您或许可以将其删除。团队领导可以找出未执行过的类和方法,将这些代码一次性删除,或在几个冲刺过程中删除。
为什么要删除死代码?对许多团队来说,更新库和主要的 Java 版本需要触及大量代码。从 Java 8 到 Java 17,XML 库被弃用和删除,在移植应用程序时,您真的还会使用所有那些 XML 处理功能吗?与其修改这些代码和所有相关的单元测试,不如直接丢弃它们,并删除测试,您觉得怎么样?如果代码不会运行,团队成员就不应该花费数小时修改它们,也不应该为了让测试可以通过而更新测试:删除死代码是更快的做法,也减轻了理解那些代码所带来的精神负担。 Spring、iText 等主要框架的更新也会出现类似情况。
代码杂乱无用的问题也会影响到正在分解单体架构或为云端重新设计架构的团队。如果不对仍在使用的代码进行全面评估,团队最终会分解出庞大的微服务。这些微服务由于包含了许多从单体架构中引入的多余部分,将变得难以管理。这些架构重构项目非但不会产生预期的精简微服务套件,反而将耗费更多时间和金钱成本,而且在完成之后,很快就会让人觉得需要再进行重写,因为团队试图摆脱的杂乱从未被消除。在团队能够减轻维护负担之前,这些困难会一直伴随着项目,而删除多余代码是减轻这种负担的快捷途径。
追踪死代码的益处
通过 JVM 追踪有效代码与死代码的显著优势在于,团队可以在不影响性能的前提下,从生产应用程序中收集数据。JVM 知道方法的首次调用时间,而记录该信息不会增加任何明显开销。这种方式使对测试环境稳健性存疑的团队有了可以依赖的结果。对于在生命周期中实施了不同程度测试驱动开发的项目,人们也有着相似的经历。即使只是改动一小段代码,就有可能导致必须花费数小时重构测试,才能使测试通过,并看到绿色的进度条。这些单元测试无法准确评估代码的重要性:如果经过全面测试的代码绝不会在生产环境中运行,那么花时间确保测试通过就是浪费时间。让这些单元测试通过只会带来虚假的成就感,简单地删除这些代码将对整个项目的健康度和团队满意度产生更积极的影响。
以被动方式追踪实际运行的代码是识别并删除死代码的最佳手段。与其手动查找或在冲刺期间耗费时间,不如调整 JVM,记录每个方法的首次调用。之后,再在冲刺或常规工作中运行脚本,将代码与列表进行比较,找出从未运行过的类和方法。团队可以在构建新功能和处理常规开发工作的同时,着手删除从未运行过的代码。执行标准测试,如果测试失败,也应考虑删除或更改测试,因为它只是在测试死代码。
通过逐步删除死代码,团队将能够减轻包袱,降低杂乱程度,减轻在处理代码时需要对其进行筛选的精神负担。如果您在一个项目中投入了大量时间,或者刚刚加入一个团队,而业务要求您加快进度,请考虑彻底放弃那些不需要的代码。