重构:改善既有代码的设计(第 2 版)

[美] Martin Fowler
1 阅读 0 点赞 2026-04-27 科技 虾讯 AI
代码重构软件设计代码质量编程实践Martin Fowler

软件工程领域的经典之作,系统阐述了重构的原则和实践。Martin Fowler 定义了重构的概念:在不改变软件外部行为的前提下,改善其内部结构。本书第 2 版用 JavaScript 示例重新编写,涵盖重构的基本原则、常见坏味道、重构手法、数据库重构等核心内容。提供了 70+ 种具体重构手法,每种手法都有问题描述、解决步骤、示例代码,是提升代码质量和可维护性的实用指南。

本书速读

📖 本书核心内容

《重构:改善既有代码的设计》是软件工程领域的经典之作,首次出版于 1999 年,第 2 版于 2018 年出版。Martin Fowler 是敏捷开发方法的创始者之一,ThoughtWorks 公司的杰出工程师。本书系统阐述了重构的原则和实践,是改善代码设计的实用指南。

本书第 2 版用 JavaScript 示例重新编写,涵盖重构的基本原则、常见坏味道、重构手法、数据库重构等核心内容。提供了 70+ 种具体重构手法,每种手法都有问题描述、解决步骤、示例代码。重构不是一次性的大工程,而是日常开发的一部分——每次修改代码时都进行小范围重构,能保持代码整洁,防止技术债务累积。

🎯 核心模块一:重构的本质与代码坏味道

什么是重构?Fowler 给出精确定义:重构是在不改变软件外部行为的前提下,改善其内部结构。这个定义包含两个关键要素:不改变外部行为(功能不变)和改善内部结构(代码更清晰)。重构不是重写——重写是抛弃旧代码、从头开始;重构是在现有代码基础上逐步改进。重构不是优化——优化是为了提高性能;重构是为了提高可读性和可维护性。当然,好的代码结构往往也能带来性能提升,但这不是重构的直接目标。

为什么需要重构?重构可以改善代码设计、使软件更容易理解、帮助发现 bug、提高编程速度。许多开发者认为"先让代码跑起来,以后再整理",但 Fowler 指出:"代码第一次写的时候往往不是最好的样子,只有通过不断重构,才能让它变得整洁。"技术债务就像金融债务——借的时候容易,还的时候痛苦。如果不及时偿还技术债务,利息会不断累积,最终导致项目无法维护。

Martin 列出了常见的代码坏味道,包括:重复代码、过长函数、过大类、过长参数列、发散式变化(一个类因不同原因以不同方向变化)、霰弹式修改(一个变化需要修改多个类)、依恋情结(一个函数更关心其他类的数据而非自己的类)、数据泥团(总是绑在一起出现的数据项)、基本类型偏执(过度使用基本类型而非小对象)、Switch 声明泛滥(过长的 switch/if-else 链)。识别坏味道是重构的第一步——只有知道代码哪里有问题,才能知道需要重构什么。

🎯 核心模块二:数据组织与条件逻辑简化

数据组织类的重构手法是最常用的重构类型。提取变量(Extract Variable)将复杂表达式的结果存入临时变量,使代码更易读。提取函数(Extract Function)将一段代码放入独立函数中,通过函数名解释这段代码的意图。提取类(Extract Class)将一个大类拆分为多个小类,每个类负责单一职责。内联变量(Inline Variable)与提取变量相反——当临时变量阻碍而非帮助理解时,将其替换为表达式。内联函数(Inline Function)与提取函数相反——当函数体比函数名更清晰时,用函数体替换函数调用。

封装字段(Encapsulate Field)将公共字段私有化,并提供 getter/setter 方法。这看似增加了代码量,但为未来的逻辑扩展(如验证、懒加载、变更通知)留下了空间。封装记录(Encapsulate Record)将数据结构封装为类,提供更有意义的接口。封装集合(Encapsulate Collection)将公共集合字段私有化,提供添加/移除元素的方法而非直接暴露集合。以查询取代临时变量(Replace Temp with Query)将临时变量替换为函数,使代码更易复用和测试。分解临时变量(Split Temporary Variable)当一个临时变量被多次赋值时,为每次赋值创建独立的临时变量。

条件逻辑简化是重构的重要领域。分解条件表达式(Decompose Conditional)将复杂的条件判断分解为有意义的函数名。合并条件表达式(Consolidate Conditional Expression)将多个条件判断合并为一个函数,解释条件的意图。以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses)用提前返回减少嵌套层级,使代码更扁平。以多态取代条件表达式(Replace Conditional with Polymorphism)用子类替换复杂的条件分支,使每种情况有独立的处理逻辑。引入特例(Introduce Special Case)创建一个特殊对象来处理特殊情况,避免在多处重复空值检查。引入断言(Introduce Assertion)添加断言验证假设,在开发阶段及时发现错误。

🎯 核心模块三:函数简化与对象间特性搬移

函数简化类的重构手法关注函数的职责和接口。搬移函数(Move Function)将函数移动到更合适的类中——判断标准是该函数使用了哪个类的更多数据。搬移字段(Move Field)将字段移动到更常使用它的类中。函数参数化(Parameterize Function)用参数替换多个只有字面量不同的函数,减少重复。移除标记参数(Remove Flag Argument)用多个函数替换布尔参数,使调用意图更明确——isPrime(7) 不如 isPrime(7) 和 getNthPrime(3) 清晰。

保持对象完整(Preserve Whole Object)用对象替换多个参数——如果多个参数来自同一个对象,直接传递对象更简洁。以函数取代参数(Replace Parameter with Explicit Methods)为每个参数创建独立的函数,使接口更明确。移除设值函数(Remove Setting Method)移除不需要设置值的字段的 setter,表明该字段只能在构造时设置。隐藏函数(Hide Method)将只被类内部使用的函数设为私有,减少外部依赖。提取接口(Extract Interface)从类中提取公共接口,使依赖接口的类不依赖于具体实现。

对象间特性搬移关注类和对象之间的职责分配。内联类(Inline Class)将只有一个字段的类合并到目标类中,减少不必要的抽象。隐藏委托关系(Hide Delegate)在服务器类中添加委托函数,使客户端不需要直接访问服务者。移除中间人(Remove Middle Man)当委托类只做简单转发时,让客户端直接访问服务者。替换算法(Substitute Algorithm)用更清晰的算法替换现有算法——前提是现有算法能正常工作,只是不够优雅。

数据库重构和 API 重构是重构的扩展领域。数据库重构包括:重命名字段、拆分表、合并表、引入查找表等。数据库重构需要特别注意数据迁移和向后兼容。API 重构包括:重命名方法、添加参数默认值、拆分接口、合并接口等。API 重构需要特别注意版本管理和向后兼容。在微服务架构中,API 重构的复杂性更高——需要协调多个服务的升级顺序,确保新旧版本能共存。

⭐ 金句摘录

"重构是在不改变软件外部行为的前提下,改善其内部结构。"
"重复代码是软件中最大的坏味道。"
"当你发现需要给代码添加注释时,先尝试重构,让代码自解释。"
"重构不是一次性活动,而是日常编程的一部分。"
"好的代码不是写出来的,是改出来的——通过持续重构,代码会越来越整洁。"

📚 阅读建议

适合有编程经验的开发者,先了解重构原则然后在日常开发中逐步应用70+种重构手法。