我们是否已走到遗留代码的尽头?
技术债的解药已经存在了几十年。我们只是从来没有一个足够自律的人去实践它。直到现在。
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo
编程世界里最大的担忧之一就是技术债,而它最极端的形式就是遗留代码。我们知道,随着代码的演进,如果我们什么都不做,技术债会不断积累,直到最终变成遗留代码。但是……如果我们已经到了技术债的尽头呢?
1、技术债
软件演进并不简单。每一次变更都意味着将新代码与现有代码集成,而这并不总是容易的。现有代码通常没有预见到未来的变更,即使预见到了,也不是那些必须做出的确切变更。所以你必须不断地在沿途进行更新和清理。
但通常情况下,这些更新和清理从未发生。日常的压力、对未来的缺乏远见,使得添加新代码变得越来越困难。摩擦力不断增加,交付时间也越来越长。
这种情况一直持续到某个时刻——触碰现有代码开始变得令人恐惧。 不仅更新现有代码变得复杂,而且有很大的概率会破坏当前正在工作的功能。技术债开始转化为遗留代码。
这就形成了一个负螺旋。 为了避免修改代码时可能无意中破坏某个功能,反而添加了更多的技术债。同时,这在代码各部分之间造成了更脆弱的联系,更容易被破坏。需要做更多的修改,反过来又添加了更多的技术债。
最终往往导致一个典型的请求:"我们需要重写代码"。

2、代码维护
审视这个负螺旋,我们清楚看到它源于一个事实:缺乏维护,或者缺乏代码清理。
很多时候,这种清理的缺失来自于快速交付的压力(或冲动),而且很多清理被视为不必要的,因为代码已经能正常工作了。其他时候,代码看起来已经够好了,不是因为匆忙,而是因为做更多修改似乎没有意义。而在少数情况下,还有一个更大的问题:代码的几个部分之间形成了新的关系,需要重新思考和重构代码,以使未来的变更更容易,或者更好地理解各部分之间的关系。
问题是,能否以某种方式避免这种情况?
多年来行业的建议可以总结为 Boy Scout Rule(童子军法则):
"始终保持代码比你发现时更好。"
这很简单,它类比了露营旅行中的要求——离开森林时要比到达时更干净,清理你可能发现的别人留下的东西,我们对代码做同样的事情。遵循这个法则,不仅能防止技术债的增长,还能随着时间推移逐步减少它。
但问题很明显。让开发者为每一个功能都遵循这个法则很难。这是一个纪律和保持警觉的问题,而且这并不自然,环境本身也不帮忙,它最终会被搁置一边。因为如果我们把它当作一次性的事情,在整理的时候,很容易遵循这个法则。但在日常工作中,当每次交付都需要应用这个法则时,它最终会被抛到脑后而遗忘。
3、遗留代码
当代码开始严重恶化时,我们进入了下一个阶段——遗留代码阶段。这是开发者开始害怕触碰代码的时刻。一个变更可能在一个意想不到的地方触发功能故障。

不仅如此。在这个阶段,软件也变得足够大和足够老,以至于开发者甚至业务人员都开始忘记随时间实现的所有功能。即使有所有添加功能的记录,也没有人能够(或有时间)阅读它们,知道哪些仍然需要正常工作,哪些不需要,对软件应该做的所有事情有一个清晰的了解。
为了应对这一切,行业有一个常见做法:QA 测试。通常会创建整个质量保证团队来手动运行应用程序以检查是否失败。在许多情况下,他们甚至会创建自动化测试来验证某些功能。这个想法很简单:在用户发现一个变更引起的 bug 之前,最好让 QA 先以用户使用产品的方式发现它。
但是,即使如此,当这一切在事后进行时,还有一个根本问题:
什么是功能,什么不是?
因为产品在一点一点地增长,添加功能,并对代码所决定的一切做出让步。部分行为存在是因为那是想要的结果。另一部分行为只是未定义的情况,由代码行之间的交互所填补。还有一些只是开发者的个人意见。

而且也不罕见地会遇到QA 将过去请求的功能作为 bug 报告。仅仅因为它们看起来很奇怪,而且很难找到它们被记录在哪里。
4、能否减缓?
几个月来,我们开始看到 GitHub Copilot 等工具对开发者的 pull request 进行自动化审查,而且准确度相当高。这些审查不仅能看到代码的细节并检测其中的错误,还能检测代码的真正意图。所以这个工具不仅会指出它认为写得不好或不够符合质量标准的地方,还能理解功能构建的上下文,并建议可能没有被考虑到的改进或用例。
好吧,Boy Scout Rule 的主要问题之一是,在日常软件开发中它迟早会被搁置一边。开发者专注于每次交付,而暂停一下,即使是很小的暂停,也往往会引入摩擦。所以问题是,我们能否改变环境来鼓励这种做法?或者更好的是:
我们能否将这种做法委托给一个不会忘记我们要求它做什么、并且会遵循法则的工具?
这就是问题所在。
技术允许这样做,我们不知道的是它能做得有多好。
这个想法似乎很简单:如果在 GitHub Copilot 审查这样的流程之上添加一个代码清理器,这个技术债减少阶段就会自动完成。就像现在每个 PR 都会出现一系列变更建议,甚至还有 AI 修复的选项,你可以简单地自动化它,让它直接应用。审批时不仅需要审查开发者的代码,还需要审查执行清理的 AI 的代码。
现在很容易通过修改审查者的指令来近似实现这一步,应该要求它按照 clean code 标准来审视所有技术债并提出改进建议。一旦检测到,让 AI 自己修复就够了。
甚至在夜间也可以,因为清理可以留给晚上做。这样,第二天早上在做任何事情之前,你就可以审查并合并这些变更。这些变更不会局限于单个 PR,而是会查看多个部分之间的结构关系,进行更深层次的清理。这还有一个额外的好处:开发者会有额外的动力去做持续交付——任何没有每天交付的工作都有第二天被冲突影响的风险。
5、能否逆转?
以上所说都是在沿途进行清理,但是……一旦代码变得如此复杂,任何变更都可能引发不可预测的缺陷,会发生什么?
在这种情况下,清理可以被看作一个风险点,因为每个变更都需要深度验证。所以你不能信任它的工作方式。
但还有另一条路。
在这里你需要利用一些不同的 AI 能力:阅读文档的能力、创建自动化测试的能力,甚至"手动"测试应用程序的能力。
因为在这种场景下,需要的是加固代码。要找到一种方法来确定变更是否会破坏什么。为此,测试,以及最重要的一点——知道要测试什么——至关重要。因为对于已经增长、已经失控的软件,甚至很难知道什么曾经是功能、什么不是。
所以这条路很长,在某种程度上很复杂。但可能是可行的。
AI 应该从阅读代码开始,阅读整个 commit 历史、开发工单历史、缺陷历史。然后开始形成假设。它的工作是找到软件的预期行为。对于每一个行为,检查它是否真的指代一个预期行为,还是某个缺陷的解决方案,并检查它是否在后来被修改过。确认之后,创建软件测试来验证它表现出该行为,并验证测试确实在检查该行为(通常是通过找到一种方法,甚至修改代码,来强制它因为该原因而失败)。一点一点地继续创建加固代码所需的所有测试。

一旦我们有了所有测试,就可以开始修改代码了。这样,如果任何时候测试失败了,它会告诉我们哪个行为被破坏了,我们能够验证它是否是预期的,我们能够决定是继续前进还是寻找替代方案。所有这些都在到达 QA 之前,最重要的是,在到达最终用户之前。
需要强调的是,让它寻找和测试的是软件行为,而不是测试代码(QA 定义的单元测试),因为测试代码会创建无法适应的僵化代码。通过测试代码,任何对代码的修改都会导致测试失败,这无助于我们持续改进内部设计。
6、极限在哪里
如果我们仔细看,所有对抗遗留代码的防线都有同一个盲点。
Boy Scout Rule 依赖于某个人记得在每次交付时清理代码。QA 依赖于某个人记得什么是功能、什么只是代码行的意外产物。从头重写依赖于某个人记得代码为什么做它所做的事情。始终,在中间,有一个人类必须不能忘记。
这就是问题所在。不是缺乏工具,不是缺乏知识。解药已经被知道了几十年。缺少的是一个能够一次又一次交付地应用它、不知疲倦、不被日常压力冲走、永远不会忘记被要求做什么或为什么的人。这不是人类能做好的事情。从来都不是。
所以,极限不是技术上的。是我们自己,试图保持干净一个增长速度超过我们记忆速度的东西。而"不忘记"第一次可以不依赖我们了。
也许我们还没有走到遗留代码的尽头。但我们已经走到了总是到达那里的原因的尽头。

原文链接: Have We Reached the End of Legacy Code?
汇智网翻译整理,转载请标明出处