中文

一篇关于重构遗留代码的实用指南,内容涵盖识别、优先级排序、技术以及现代化和可维护性的最佳实践。

驯服猛兽:遗留代码重构策略

遗留代码。这个术语本身常常让人联想到庞大、无文档的系统、脆弱的依赖关系以及一种难以抗拒的恐惧感。全球许多开发人员都面临着维护和发展这些系统的挑战,而这些系统通常对业务运营至关重要。这份全面的指南提供了重构遗留代码的实用策略,将令人沮丧的源头转变为现代化和改进的机会。

什么是遗留代码?

在深入探讨重构技术之前,我们有必要先定义“遗留代码”的含义。虽然该术语可以简单地指代较旧的代码,但一个更细致的定义侧重于其可维护性。Michael Feathers 在其开创性著作《有效处理遗留代码》(Working Effectively with Legacy Code)中,将遗留代码定义为没有测试的代码。这种测试的缺乏使得在不引入回归问题的情况下安全地修改代码变得非常困难。然而,遗留代码也可能表现出其他特征:

值得注意的是,遗留代码本身并不一定是坏事。它通常代表了一项重大投资,并体现了宝贵的领域知识。重构的目标是在保留这些价值的同时,提高代码的可维护性、可靠性和性能。

为什么要重构遗留代码?

重构遗留代码可能是一项艰巨的任务,但其好处往往大于挑战。以下是投资重构的一些关键原因:

识别重构候选对象

并非所有遗留代码都需要重构。根据以下因素确定重构工作的优先级至关重要:

示例:假设一家全球物流公司有一个管理货运的遗留系统。负责计算运费的模块由于法规和燃油价格的变化而频繁更新。该模块就是重构的首要候选对象。

重构技术

有许多可用的重构技术,每种技术都旨在解决特定的代码异味或改进代码的特定方面。以下是一些常用的技术:

组合方法 (Composing Methods)

这些技术专注于将大型、复杂的方法分解为更小、更易于管理的方法。这可以提高可读性,减少重复,并使代码更易于测试。

在对象之间移动特性 (Moving Features Between Objects)

这些技术专注于通过将职责移动到它们所属的位置来改进类和对象的设计。

组织数据 (Organizing Data)

这些技术专注于改进数据的存储和访问方式,使其更易于理解和修改。

简化条件表达式 (Simplifying Conditional Expressions)

条件逻辑可能很快变得错综复杂。这些技术旨在澄清和简化它。

简化方法调用 (Simplifying Method Calls)

处理泛化 (Dealing with Generalization)

这些只是众多可用重构技术中的几个例子。选择使用哪种技术取决于具体的代码异味和期望的结果。

示例:一家全球性银行使用的 Java 应用程序中有一个庞大的方法用于计算利率。应用提炼方法 (Extract Method) 来创建更小、更集中的方法,可以提高可读性,并使其更容易更新利率计算逻辑,而不会影响方法的其他部分。

重构过程

重构应系统地进行,以最大限度地降低风险并提高成功率。以下是推荐的流程:

  1. 识别重构候选对象:使用前面提到的标准来识别最能从重构中受益的代码区域。
  2. 创建测试:在进行任何更改之前,编写自动化测试来验证代码的现有行为。这对于确保重构不引入回归至关重要。可以使用 JUnit (Java)、pytest (Python) 或 Jest (JavaScript) 等工具编写单元测试。
  3. 增量重构:进行小的、增量的更改,并在每次更改后运行测试。这使得更容易识别和修复任何引入的错误。
  4. 频繁提交:频繁地将您的更改提交到版本控制。如果出现问题,这使您可以轻松地恢复到以前的版本。
  5. 代码审查:让另一位开发人员审查您的代码。这有助于识别潜在问题并确保重构正确完成。
  6. 监控性能:重构后,监控系统的性能,以确保更改没有引入任何性能回归。

示例:一个团队在重构一个全球电子商务平台的 Python 模块时,使用 `pytest` 为现有功能创建单元测试。然后他们应用提炼类 (Extract Class) 重构来分离关注点并改善模块的结构。在每次小的更改后,他们都会运行测试以确保功能保持不变。

为遗留代码引入测试的策略

正如 Michael Feathers 所恰当指出的,遗留代码是没有测试的代码。向现有代码库引入测试感觉像是一项巨大的任务,但对于安全重构至关重要。以下是完成此任务的几种策略:

特征化测试 (Characterization Tests) (又名黄金标准测试)

当您处理难以理解的代码时,特征化测试可以帮助您在开始进行更改之前捕获其现有行为。其思想是编写测试,断言代码对于一组给定的输入所产生的当前输出。这些测试不一定验证正确性;它们只是记录代码*当前*的行为。

步骤:

  1. 识别您想要特征化的代码单元(例如,一个函数或方法)。
  2. 创建一组代表常见和边缘情况场景的输入值。
  3. 用这些输入运行代码并捕获结果输出。
  4. 编写测试,断言代码为这些输入产生了那些确切的输出。

注意:如果底层逻辑复杂或依赖于数据,特征化测试可能会很脆弱。如果您以后需要更改代码的行为,请准备好更新它们。

萌芽方法 (Sprout Method) 和萌芽类 (Sprout Class)

这些技术同样由 Michael Feathers 描述,旨在将新功能引入遗留系统,同时最大限度地降低破坏现有代码的风险。

萌芽方法 (Sprout Method):当您需要添加一个需要修改现有方法的新功能时,创建一个包含新逻辑的新方法。然后,从现有方法中调用这个新方法。这使您能够隔离新代码并独立测试它。

萌芽类 (Sprout Class):与萌芽方法类似,但用于类。创建一个实现新功能的新类,然后将其集成到现有系统中。

沙盒化 (Sandboxing)

沙盒化涉及将遗留代码与系统的其余部分隔离开来,让您可以在受控环境中对其进行测试。这可以通过为依赖项创建模拟(mock)或存根(stub)或在虚拟机中运行代码来完成。

Mikado 方法

Mikado 方法是一种用于处理复杂重构任务的可视化问题解决方法。它涉及创建一个图表,表示代码不同部分之间的依赖关系,然后以最小化对系统其他部分影响的方式重构代码。其核心原则是“尝试”更改,看看会破坏什么。如果它被破坏了,就恢复到最后一个工作状态并记录问题。然后在重新尝试原始更改之前解决该问题。

重构工具

有几种工具可以协助重构,自动化重复性任务并提供最佳实践指导。这些工具通常集成到集成开发环境 (IDE) 中:

示例:一个为全球保险公司开发 C# 应用程序的开发团队使用 Visual Studio 的内置重构工具来自动重命名变量和提取方法。他们还使用 SonarQube 来识别代码异味和潜在漏洞。

挑战与风险

重构遗留代码并非没有挑战和风险:

最佳实践

为了减轻与重构遗留代码相关的挑战和风险,请遵循以下最佳实践:

结论

重构遗留代码是一项具有挑战性但回报丰厚的工作。通过遵循本指南中概述的策略和最佳实践,您可以驯服这头猛兽,将您的遗留系统转变为可维护、可靠和高性能的资产。记住要系统地进行重构,频繁测试,并与您的团队有效沟通。通过周密的计划和执行,您可以释放遗留代码中隐藏的潜力,为未来的创新铺平道路。