重构是一个IT俚语术语,指在不改变代码外部行为的情况下修改其内部结构。重构的目标是使代码更清晰、更易于理解和维护。根据Martin Fowler在《重构:改善既有代码的设计》一书中的观点,重构是维护代码库健康的必要实践,定期应用可将项目的总拥有成本降低20-30%。
要点
重构—是在不改变可观察行为的情况下,以提高其质量特征为目的,修改程序代码内部结构的过程。该术语由Martin Fowler于1999年引入广泛使用,该实践本身已成为敏捷开发和极限编程的基础之一。
重构的关键特征—保留功能性。重构后,程序必须执行与更改前完全相同的操作并返回相同的结果。其保证是在每个重构微步骤后运行的自动化测试。如果测试通过(绿色)—行为得以保留。如果测试失败(红色)—重构执行不正确或改变了行为,这意味着这不再是重构,而是功能修改。
行业中有一个普遍误解:任何代码修复都称为重构。实际上,改变行为的代码重写是"rewrite"或"rework",而不是重构。区别是根本性的:重构是一个受控、安全的过程,而改变逻辑的重写则是一个全新的开发,伴随着所有相关风险。
关于重构的知识在中文环境中的资本化与其他IT术语通过相同的机制进行:借用英语refactor并添加中文后缀。软件工程教育项目和书籍翻译已在专业词汇中巩固了该术语。
区分重构与完全重写代码(rewrite)很重要。重构—是一系列小的、安全的转换,每个转换都保留行为。重写—从头创建新实现,通常伴随着架构、技术和行为的改变。Standish Group(2023)的研究表明,选择完全重写的项目在40%的情况下失败,而定期进行重构的项目技术债务水平低25%。
重构解决几个关键任务,每个任务都直接影响开发速度和成本。理解这些目标有助于团队正确确定优先级,并在利益相关者面前证明花费在重构上的时间是合理的。
代码写一次,但读几十次甚至几百次。如果开发人员花30分钟理解一个函数的作用—那就是直接的生产力损失。可读性强的代码可降低认知负荷并加速新团队成员的上手。Rename Method、Extract Variable和Introduce Explaining Variable等技术正是旨在提高代码的可理解性。根据Developer Productivity(Microsoft Research,2023)的研究,开发人员花费高达60%的时间阅读代码,而不是编写代码,这使得可读性成为生产力的关键因素之一。
DRY(Don’t Repeat Yourself)原则—编程中的基本原则之一。代码重复导致同一更改必须在多个地方进行,这增加了错误和遗漏修改的风险。使用Extract Method和Pull Up Method技术进行重构可以消除重复并集中逻辑。
圈复杂度和嵌套深度的度量与代码中的缺陷数量直接相关。如果一个函数的圈复杂度高于10-15,就很难测试且容易出错。使用Replace Conditional with Polymorphism、Decompose Conditional和Extract Method进行重构可以将复杂性降低到可控水平。NIST(2024)的研究表明,高复杂度模块每千行代码的缺陷数高出2-3倍。
重构的主要原因之一—需要添加新功能。如果当前的代码结构不允许在不破坏现有行为的情况下进行更改,重构有助于铺平道路。"露营规则"(让代码比你发现时更干净)—是Martin Fowler的建议之一,它将重构从偶然的活动转变为持续的实践。
对GitHub上500个开源项目的分析数据(IEEE Transactions on Software Engineering,2024)表明,定期进行重构的项目比偶尔进行重构的项目少30%的"代码坏味"(code smells),技术债务指标低15%。
Martin Fowler在他的书中编录了70多种重构技术。在实践中,大多数团队定期使用其中的10-15种。让我们看看每个开发人员都应该掌握的关键技术。
最常用的技术。如果一段代码在语义上可以提取到单独的函数中—就应该这样做。Extract Method提高了可读性,允许为操作命名并简化测试。规则:如果您看到解释代码块功能的注释—该块可以提取到单独的方法中。
// 重构之前
double total = amount * price;
double discounted = total * (1 - discountRate);
double tax = discounted * taxRate;
// 重构之后
double total = calculateTotal(amount, price);
double finalPrice = applyDiscountAndTax(total);
名称应反映本质。如果变量或方法的名称不能回答"这里存储/做什么"的问题—就应该重命名。现代IDE使这个操作变得简单。清晰的名称—是改善代码的最便宜、最有效的方式。
当条件逻辑过于庞大和混乱时,多态性提供了更清晰的替代方案。不再使用基于类型的switch-case—创建具有重写方法的类层次结构。多态性使代码可扩展:添加新类型不需要更改现有条件,只需创建新的子类。
// 重构之前(条件语句)
if (type.equals("email")) {
sendEmail(message);
} else if (type.equals("sms")) {
sendSms(message);
}
// 重构之后(多态性)
Notifier notifier = new EmailNotifier();
notifier.send(message);
当一个函数接收过多参数(超过3-4个)时,很难阅读和传递。将相关参数分组到参数对象中可缩短签名,提高可读性并简化后续更改。
| 技术 | 用途 | 何时应用 |
|---|---|---|
| Extract Method | 将逻辑提取到单独函数 | 代码块可用一句话描述 |
| Rename Variable | 明确变量/方法的名称 | 名称不反映本质 |
| Replace Conditional | 用多态性替换switch-case | 根据对象类型的条件 |
| Extract Interface | 从类中提取契约 | 需要松耦合 |
重构的决定—不是技术性的,而是管理性的。需要在当前生产力与代码库的长期健康之间取得平衡。让我们看看重构合理的情况以及何时最好避免。
第一种情况—您不理解需要更改的代码。如果理解现有代码比实现新功能花费更多时间—这是需要先进行重构的信号。第二种情况—您发现了重复,它减慢了开发速度并增加了错误风险。第三种—添加新功能不可能不破坏现有结构。
此外,当代码库包含"代码坏味"(code smells)时也值得重构:长方法、大类、过多注释、调用链、并行继承层次结构。Fowler书中的code smells目录包含20多种典型问题指示器,每种都有对应的重构技术。
如果代码稳定运行且没有计划更改,则不需要重构。"如果没坏,就别修"的原则特别适用于很少更改的代码。为重构而重构—是工程师完美主义的一种形式,弊大于利。
同样,不要重构将在近期完全替换的代码。如果团队计划用另一种语言或架构重写模块,重构当前版本是浪费时间。最后,没有测试的重构—是一场冒险,特别是当代码库庞大而复杂时。例外情况—使用IDE进行的简单转换,这些转换可以撤销。
安全的重构—是纪律。有几个原则,遵守它们可以最小化风险并使过程可预测。第一个也是最重要的—重构只在测试保护下进行。如果您没有覆盖要修改代码的测试—首先编写它们。
第二个原则—小步骤。每个重构操作应该是最小化的:重命名一个变量、提取一个方法、隔离一个类。每一步之后—编译并运行测试。分解为微步骤可以立即发现错误并撤销最后的更改。根据Martin Fowler的说法,微步骤使重构比大更改安全3-4倍。
第三个原则—使用工具。现代IDE(IntelliJ IDEA、VS Code、Eclipse)提供自动化重构:rename、extract method、extract variable、move class等数十种。工具重构保证了转换的正确性,不需要手动搜索所有需要更改代码的地方。
第四个原则—不要将重构与功能更改混在一起。如果同时进行重构和添加新逻辑,就无法确定哪个更改导致了错误。提交分离为"重构"和"功能"—是行业标准,简化了代码审查和更改回滚。推荐结构:首先提交重构(仅结构更改,行为保留),然后提交新功能。
重构的Git-flow:创建单独的分支,执行重构,实现测试通过,提交,然后在同一分支中添加新功能。如果出现问题—重构更改始终可以通过git revert回滚。
# Git中的重构微步骤
git checkout -b refactor/extract-payment
# 步骤1:提取计算方法
# ...更改... → 编译 → 测试
git commit -m "refactor: extract calculatePayment method"
# 步骤2:重命名变量
# ...更改... → 编译 → 测试
git commit -m "refactor: rename amount to grossAmount"
常见问题
不,它们是不同的过程。重构—改进现有代码而不改变其行为。重写(rewrite)—从头创建新实现,通常伴随着架构和技术的变更。重构更安全、更便宜、更可预测。
推荐的规则—将冲刺时间的20%用于技术改进和重构。这可以在不延迟业务功能交付的情况下,将技术债务保持在可接受的水平。
可以,但有风险。对于通过IDE进行的简单转换(重命名、提取常量),测试不是必须的。对于复杂更改—测试是必须的。如果没有测试—首先编写特征测试来记录当前行为。
通过变更成本来论证。如果添加一个简单功能因为代码混乱需要一周时间—说明重构将缩短未来更改的时间。使用度量指标:代码审查时间、Bug数量、圈复杂度。
回滚最后的更改。如果使用Git—git revert最后一个提交。如果微步骤足够小,丢失的更改量将是最小的。因此,大型重构总是分解为一系列微步骤。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。