垃圾代码——浣熊的最爱——是一种维护负担,开发人员必须解决它才能在进行其他重大更改时继续通过测试。
译自 Trash Pandas Love Enterprise Java Code,作者 Erik Costlow。
如果浣熊是软件工程师,它们会在许多企业系统中感到宾至如归。这些系统通常充满了未使用的死代码,这些代码经过编写、全面测试,然后以一种阻止团队运行它们的方式进行更改。
这种垃圾代码——浣熊的最爱——是一种维护负担,开发人员必须解决它才能在进行其他重大更改时继续通过测试。这种代码通常不会最终进入垃圾箱,因为很难知道在不破坏应用程序的情况下可以安全地删除什么。但是,如果你了解如何查找它,你可以更好地识别不良代码。
识别死代码或未使用的代码 的典型方法是通过静态分析或日志审查。现代 IDE 可以通过可达性分析检测未使用的死代码,但对于不是 100% 私有的类和方法存在健全性问题。如果代码具有公共访问修饰符,那么它可以加载,因此你的工具永远不会报告它。
在其他时候,当代码被跟踪时,它会在单元测试中运行——从技术上讲,此代码不是死的。不幸的是,虽然测试驱动设计是一种好做法,但有一些代码部分只有在删除该代码时单元测试失败才会存活。所有这些测试所做的只是验证不必要的包袱是否满足其原始设计。
尽管删除未使用的死代码会使测试变红,但开发人员还可以删除此测试并通过不再运行它来加快整体构建管道。全面了解哪些代码可以被丢弃的最佳方法是分别监控测试和生产环境,看看会发生什么。
许多团队持“何必担心”的态度:实际上能有多少未使用的死代码,为什么它很重要?未使用的死代码是杂乱无章的,会减慢现在和未来。每次有人处理应用程序时,他们都必须解决该代码。当进行重大更改 时,例如从 Java 11 升级到 17 或 21,所有这些不必要的代码都需要维护。无论代码是否重要,构建管道都需要时间来确保代码正常工作。
当一家大型美国金融机构着手识别和删除未使用的代码时,它能够“将代码库的大小减少 67%”。降低的复杂性极大地改善了开发人员体验,以至于开发人员在一年内对应用程序发布了 250 多次更新,并最终升级 了阻碍他们的部分较旧的库。
IEEE 对工业软件系统进行的类似研究发现,高达 50% 的大型代码库未被使用或已死,总体平均值为 5% 至 10% 的代码未被使用或已死。应用程序越大、越旧,百分比就越高。
Java 工程师可以将未使用的死代码检测视为类似于垃圾回收:选择你的设置,让 JVM 完成剩下的工作。代码是否在生产中使用的问题取决于该方法是否被调用(或内联)。此决策的记录在三个地方之一进行:字节码解释器、AppCDS(应用程序类数据共享)或 Azul Platform Prime 的 ReadyNow 功能。如果再次使用该方法,则无需影响记录第一次调用的性能。该方法已在生产中使用,因此代码不是未使用的或死的。在此场景中,JVM 运行被标记为“应用程序环境”,以帮助记录哪个应用程序为给定环境调用了该方法。保持这些环境分离有助于识别代码和单元测试仅用于相互保持活动的情况。
监控代码以查看你可以丢弃什么需要增加时间维度。在你观察应用程序一段时间之前,很多代码还没有运行。在这里,团队通常将死代码分成两部分:他们认为可以摆脱但又不确定的东西,以及需要更长时间才能看到的东西——通常基于与业务周期相关的某种类型的周期性活动。
开发人员可能怀疑代码的某些部分未使用或已失效,但又没有足够的信心将其删除。一种方法是跟踪代码清单,在应用程序中执行一些操作,然后查看是否报告这些方法正在运行。如果正在运行,则代码是有效的;如果不是,则可能是未使用或已失效。其他功能(如年终报告)需要运行其过程才能进行检测。这就是为什么最好随着时间的推移监控代码的有效性。可以尽早赢得小胜利,然后稍后可以丢弃更多代码。
Azul 的代码清单不同于检测不可达代码的典型静态分析工具和 IDE。代码清单的监控从正在运行的 JVM 中进行,以识别存在但未运行的整个类或公共方法。它是一个混乱查找器——一种浣熊驱虫剂。它准确地识别未使用的和已失效的代码,以便通过精确地详细说明正在运行的自定义和第三方代码来删除它们。
失效代码检测提高了开发人员的生产力和幸福感。维护代码是一项繁重的任务,其唯一目的是满足确保不必要的代码工作的测试。通过检测未使用的和已失效的代码,开发人员可以安全地将它们扔进垃圾桶——这是我们浣熊朋友的美味佳肴。