遗留代码全解析:什么是遗留代码、如何处理和重构、有效处理遗留代码的静态分析工具等
什么是遗留代码?遗留代码是指已经存在且需要再次使用的源代码。它不一定是糟糕的代码,但通常需要一些努力才能集成到较新的系统中。这意味着您需要有效的方法来解决遗留代码的问题。
您处理遗留代码的几率有多大?鉴于 TIOBE流行度指数排名前10的编程语言大多已存在至少20年,因此,您遇到遗留代码的几率非常高。(Go语言是个例外,它首次出现于2009年。)
↑ 随时间变化的TIOBE流行度指数-C++
您不能总是依赖创建遵循现代编程实践的新代码。
本文旨在为您提供帮助,详细介绍什么是遗留代码、处理遗留代码的最佳实践、如何编写经得起时间考验的新代码,以及使处理遗留代码更为轻松的工具。
什么是遗留代码?
遗留代码是指从他人或旧版本软件中继承而来的源代码。它还可以是任何您不理解或不知道如何测试,且难以修改的代码。
这是一个简单定义。处理遗留代码可能会有许多潜在的问题需要解决。在《高效处理遗留代码》一书中,Michael C. Feathers给出进一步的定义,指出要了解一段遗留代码能做什么是很困难的:
“对我来说,遗留代码就是未经测试的代码。” —— Michael C. Feathers
没有测试,您就无法知道代码满足了哪些要求,以及如何将其应用到新的项目中。这意味着需要更多的努力来理解它、调整它,并编写新的测试来验证它是否仍然有效。
但是,这些定义忽略了这样一种情况:您有旧的测试代码,但它们都不符合现代要求或新标准。或者,这些代码仍然有价值且合规,但您不知道如何将其集成到新的系统中。
想象一下,您必须将一个旧的接口驱动程序移植到新的应用程序堆栈——您可能已经编写了驱动程序,但却不了解堆栈。
这就是为什么我们上述对遗留代码的定义更加全面且注重问题,我们将继续探索有效的方法来解决这些差距,并将您的遗留代码转化为新的、可用的功能。
为什么有效处理遗留代码是一项挑战?
在处理较旧或不熟悉的代码时,最大的挑战可能是你对它的假设。
您可能认为代码很糟糕,编写代码的人不知道自己在做什么,本可以做得更好。
但事实上,代码之所以如此,通常是有原因的。如果您不是编写者,可能并不知道其中的原因。
以下是处理遗留代码时的一些常见挑战:
维护难题 — 遗留代码通常会增加系统的整体技术债务,使其更难维护或修改。这可能是由于代码采用了过时的编程实践、使用了旧的语言版本、算法效率低下或缺乏文档。技术债务持续得越久,就越难维护。 集成问题 — 自遗留代码创建以来,系统或业务已经发生了变化。当今世界有不同类型的连接性、云平台、数据格式、功能安全标准、安全指南等,所有这些都需要仔细规划才能正确地集成。 性能和可扩展性问题 — 遗留代码可能无法针对当今的硬件进行优化,也无法利用较新的软件技术和平台选项。必须考虑并解决遗留代码与其环境之间的任何瓶颈问题。 安全风险 — 旧代码很可能是在未考虑现代安全实践或不了解当今威胁形势的情况下开发的。它也可能不支持安全更新和补丁,从而容易受到已知和潜在漏洞的攻击。 缺乏专业知识 — 开发人员离开组织的情况并不少见,这可能会导致他们的代码无人支持且缺乏文档。供应商可能会消失,围绕开源软件包的社区也可能会枯竭。在缺乏这些知识的情况下,尝试维护和升级遗留代码需要花费时间和精力。
在改进遗留代码库时必须小心谨慎。不能只针对一个区域快速修复,或运行现有的测试并希望它们通过。可能存在一些您不知道的依赖关系或无法预测的意外行为。
因此,了解何时维护或更改遗留代码至关重要。
处理和重构遗留代码的9个技巧
您不可能在一夜之间改进继承的代码。但是,可以采取循序渐进的措施来改进它。
首先,了解为什么重用代码,这会有所帮助。如果是由于财务或资源限制,除了继续下去,您别无他法。中断关键系统的风险可能会阻碍进展,但也可能有机会规划向新代码库的缓慢过渡。
通常,坚持使用遗留系统往往是出于对变革的抵制。团队安于现状,尤其是工作繁忙时,人们不愿意迁移到新的工作方式。在这种情况下,了解如何有效地处理遗留代码以消除误解或顾虑,就显得尤为重要。
无论您是刚刚起步,还是已经处理了一段时间,您都应该遵循以下九个技巧。
1. 测试遗留代码
了解代码的一种方法是创建特征测试和单元测试。您也可以使用代码质量工具(如静态代码分析器)来检查代码,以识别潜在问题。
这将帮助您了解代码的实际作用,还能揭示功能、性能、安全性和编码标准方面任何可能存在问题的区域。一旦了解了代码,您就可以更有信心地进行量化更改、记录异常和更新。
2. 审查文档
审查原始需求和功能文档有助于了解代码的来源。包括查看从需求管理工具到内联代码注释的所有内容。
这些文档可帮助您了解代码当前的工作方式,并识别与新预期功能之间的差距。当需要做出更改时,这些信息有助于防止意外更改引入不良行为,或危及更大的系统。
3. 仅在必要时重写代码
重写整个继承的代码库可能很“诱人”,但这通常是个错误。
如果不完全了解代码库的工作原理以及与外部世界的交互情况,重写代码可能会引入新的错误并导致依赖项问题。此外,重写所有内容也需要耗费大量的时间和程序员。
最好对代码重写采取审慎的态度,基于高度信心和用于验证行为的测试来选择性地修改组件。
4. 尝试重构遗留代码
比重写代码更有效的是重构代码,且最好是逐步进行。
重构是改变代码结构的过程,同时不改变其功能或外部行为。示例包括:
简化随时间变得过于复杂的条件表达式。
删除硬编码的字符串和值。
改进函数调用,使其更易于理解。
在函数之间移动功能,以消除代码重复或避免一个组件承担太多责任。
这些操作可以清理代码,使其更易于理解。它还能够消除潜在错误,减少项目的整体技术债务。
在重构遗留代码时,最好注意以下事项:
重构具有单元测试的代码,以便你知道自己拥有什么。
从代码的最深处开始,这样重构是最容易的。
重构后进行测试,以确保没有破坏任何东西。
有一个安全网(例如持续集成),以便可以恢复到之前的版本。
5. 在不同的审查周期中进行更改
不要一次性更改太多。在同一审查周期内重构遗留代码和功能性变更是个坏主意,因为它们会变得过于复杂,难以管理、测试和修复。
此外,限制更改范围会使代码审查更容易。对于审查员来说,孤立的更改要比一大堆更改要明显得多。
6. 与其他开发人员合作
对于一些代码库您可能不太了解,但您的同事可能很了解。向最了解代码库的人咨询要快得多。
因此,如果可能的话,请与比您更了解它的人合作。多一个人看代码,可以帮助你更好地理解代码。
7. 保持新代码的整洁
有一种方法可以避免代码出现更多问题——那就是确保新代码是整洁的。它应遵循最佳实践编写,并通过自动化测试以确保符合编码标准。
您无法控制继承代码的质量,但您可以确保所添加的代码是整洁的。
8. 使用AI
一种较新的技术是使用AI将遗留代码转换为新的语言,查找优化,检测未使用的代码等等,甚至可以要求生成式AI为您重构遗留代码。
一个大问题是,您能信任AI来编码吗?重构遗留代码是一项风险很高的操作,而AI在训练方式和理解内容方面引入了新的变数。
好消息是正在取得进展。DARPA的Translating All C to Rust(TRACTOR)项目正在研究如何使用大语言模型(LLM)和其他技术,来通过将C代码转换为Rust以减少其内存安全漏洞。
“你可以访问任何一个LLM网站,与其中一个AI聊天机器人聊天,你只需要说’这里有一些C代码,请把它翻译成安全的惯用Rust代码’,剪切、粘贴,然后就会有一些东西出来,而且通常很好,但并不总是如此。”
——Dan Wallach 博士,DARPA TRACTOR项目经理
9. 进一步研究
随着时间的推移,处理遗留的代码库会变得更容易。初级开发人员可能不理解为什么没有重构代码库(而且可能很想重构)。但是高级开发人员会知道什么时候该放手不管。
更多地了解代码库将有助于您改进代码库。
一个良好的起点是Michael C. Feathers的《高效处理遗留代码》,其中包含一些有关如何更改旧代码库的良好示例。
另一个良好来源是Martin Fowler所著的《重构:改进现有代码的设计》。这本书提供了许多有效重构遗留代码的技巧。
如何预防遗留代码问题
防患于未然总是比较好的,因此在创建新代码时,这里有一些小贴士可供参考:
采用编码标准 — 行业公认的编码标准提供了最佳实践,有助于避免出现更多问题。虽然这些标准通常被强制用于安全关键型应用,如 ISO 26262 和 MISRA,但它们对于在任何组织中培养“面向未来”的思维方式都非常重要。 记录和注释代码 — 有用的解释、提示和指南可使未来的开发团队更容易访问和维护代码。 使用版本控制 — 版本控制对于维护和跟踪代码库的更改至关重要。了解遗留代码的历史记录,有助于您更好地理解它,并更轻松地在团队之间协调重构。 运行自动化(持续)测试 — 在发布代码之前,进行全面的自动化测试至关重要。这可确保代码随着时间推移进行更改后,仍能保持功能性和可靠性。 遵循代码安全最佳实践 — 使用安全的编码技术、安全测试工具和静态分析工具,可以最大限度地降低代码泄露的风险,并在整个生命周期内保持代码安全。
有效处理遗留代码的工具
您总是会需要处理遗留代码——或者规避它。毕竟,代码的存在是有原因的。它可以工作。而且,其结果可能足够好,以至于你可以忽略已知的问题。
当然,也有充分的理由对代码进行修改。您可能正在添加功能、修复漏洞或改进设计。
理想情况下,您会持续重写那些旧的或不熟悉的代码,直到它们完全无误。但这很可能是不现实的。
因此,您需要做的是找出哪些可以更改,其他的就不要管了。
静态分析
例如,Helix QAC和Klocwork等工具就使这一操作变得非常简单。
Helix QAC 可以根据规则(通常是编码标准)检查您的代码库。对于违规的诊断结果,您可以根据严重程度确定它们的优先级。这意味着您可以集中精力,优先修复错误最多的部分。
您还可以将代码库设置为基线。也许代码本身没有问题,你想让它保持不变。设置基线意味着代码库不会被纳入诊断。相反,您可以专注于查找新代码中的问题,并确保代码是干净的。
↑ Helix QAC使设置基线变得轻而易举
在某些情况下,您可能会将源代码从一个项目重用到另一个项目。但有些源代码不是按照编码标准开发的。如果您需要实现合规性(例如 MISRA),这可能会产生问题。通过使用 Perforce静态代码分析器(例如适用于C/C++的Helix QAC,或适用于C、C++、C#、Java、JavaScript和Python的Klocwork),您可以轻松查看代码中的错误所在。
有效处理遗留代码需要花费时间和精力,但这是一项必要的任务。
与其将其视为 “我不得不使用的糟糕代码”,不如考虑遗留代码的潜在价值,并逐步对其进行重构、测试和重新运行。当已有轮子被证明有用时,就没有必要再重新发明轮子。