refactoring-patterns

refactoring-patterns

热门

应用命名的重构转换来改善代码结构,同时不改变其行为。当用户提到“重构这个”、“代码坏味”、“提取方法”、“替换条件”、“技术债务”、“移动方法”、“内联变量”、“分解条件”或“清理这段混乱的代码”时使用。同样适用于清理遗留代码、通过重构为新增功能做准备,或识别适合特定代码坏味的转换。涵盖坏味驱动的重构、安全转换序列和测试保护。关于代码质量基础,请参见 clean-code。关于管理复杂性,请参见 software-design-philosophy。

1698Star
171Fork
更新于 2026/7/16
SKILL.md
readonly只读
name
refactoring-patterns
description

应用命名的重构转换来改善代码结构,同时不改变其行为。当用户提到“重构这个”、“代码坏味”、“提取方法”、“替换条件”、“技术债务”、“移动方法”、“内联变量”、“分解条件”或“清理这段混乱的代码”时使用。同样适用于清理遗留代码、通过重构为新增功能做准备,或识别适合特定代码坏味的转换。涵盖坏味驱动的重构、安全转换序列和测试保护。关于代码质量基础,请参见 clean-code。关于管理复杂性,请参见 software-design-philosophy。

重构模式框架

一种有纪律的方法,用于改善现有代码的内部结构,而不改变其可观察的行为。每次重构都遵循相同的循环:验证测试通过,应用一个小的结构变化,验证测试仍然通过。

核心原则

重构不是重写。它是一系列小的、行为保持的转换,每个转换都有测试支持。 你从不改变代码的功能——只改变其组织方式。大爆炸式重写之所以失败,是因为它们将结构变化和行为变化混在一起,使得无法知道是什么破坏了功能。

基础: 糟糕的代码是在时间压力下交付的自然结果,而不是性格缺陷。代码坏味是结构退化的客观信号;坏味目录告诉你在哪里查找,重构目录告诉你做什么

评分

目标:10/10。 根据八个快速诊断行中有多少通过来评分结构质量——score = round(passed / 8 × 10),当单个坏味严重时向下调整。等级:

  • 9-10:没有明显的坏味,每个函数只做一件事,名称揭示意图,消除重复,条件语句在适当的地方使用多态,测试覆盖重构后的路径。
  • 5-6:存在一些坏味(长方法、一些重复),但结构基本合理。
  • ≤3:普遍存在坏味——混乱的条件语句、上帝类、到处重复——或者没有测试可以安全重构。

始终说明当前分数,指出导致分数下降的坏味,并列出达到10/10所需的具体重构。

重构模式框架

系统改善代码结构的六个关注领域:

1. 代码坏味作为触发器

核心概念: 代码坏味是更深层结构问题的表面指标——不是错误,而是设计使代码更难理解、扩展或维护的信号。每个坏味都对应着修复它的命名重构。

为什么有效: 命名的坏味为团队提供了客观标准,而不是主观的“我不喜欢这个”——“这是依恋情结”直接指向修复方法。

关键见解:

  • 坏味分为五个家族:臃肿者、面向对象滥用者、变更阻碍者、可有可无者、耦合者
  • 长方法是最常见的坏味;重复代码是最昂贵的坏味
  • 需要注释来解释做什么的方法是坏味——提取并命名该代码块
  • 霰弹式修改(一个更改,多个类)和发散式变化(一个类,多个更改原因)是职责错位的相反信号
  • 基本类型偏执——使用原始字符串/整数而不是小型领域对象——传播错误和重复

代码应用:

上下文 模式 示例
方法超过10行 提取方法 将循环体提取到 calculateLineTotal()
一个更改触及多个类(霰弹式修改) 移动方法/字段 将分散的行为收集到一个类中
多个方法中相同的参数 引入参数对象 startDate, endDateDateRange
复制粘贴的逻辑 提取方法 + 上移方法 通过公共方法或基类共享

当需要命名坏味及其修复时,请参见 references/smell-catalog.md——所有五个家族(臃肿者、OO滥用者、变更阻碍者、可有可无者、耦合者)及其检测启发式和对应的重构。

2. 组合方法

核心概念: 大多数重构从这里开始:将长方法分解成更小的、命名良好的片段,读起来像散文——高层步骤委托给清晰命名的辅助方法。

为什么有效: 短方法加上揭示意图的名称消除了注释,使错误一目了然,并支持重用;当名称说明一切时,方法调用几乎没有阅读成本。

关键见解:

  • 提取方法是最重要的单一重构——首先掌握它
  • 想写注释?提取代码块并用注释作为方法名
  • 当方法体与名称一样清晰时,内联方法——没有价值的间接是噪音
  • 用查询替换临时变量用于在多个地方使用的计算值;拆分临时变量当一个临时变量服务于两个目的时
  • 用方法对象替换方法当局部变量过于混乱无法提取时——它们变成字段

代码应用:

上下文 模式 示例
带有注释的代码块 提取方法 // check eligibilityisEligible()
只使用一次的临时变量 内联变量 删除 const price = order.getPrice()
简单的委托方法 内联方法 如果只使用一次,内联 return deliveries > 5
包含许多混乱局部变量的方法 用方法对象替换方法 局部变量变成新类中的字段

当应用任何方法级转换时,请参见 references/composing-methods.md——提取/内联方法、提取/内联变量、用查询替换临时变量、拆分临时变量和用方法对象替换方法的逐步机制和前后代码示例。

3. 在对象之间移动特性

核心概念: 面向对象设计的关键决策是职责放在哪里。当依恋情结、过度耦合或类大小不平衡表明方法或字段在错误的类中时,将其移动到它所属的地方。

为什么有效: 方法远离其使用的数据会创建不可见的跨类依赖,导致一个逻辑更改波及多个文件——霰弹式修改。将方法和数据放在一起将更改限制在一个类中。

关键见解:

  • 当方法使用另一个类的特性多于自己的特性时,移动方法;移动字段同理
  • 当一个类做两件事时,提取类——沿着变化轴拆分;当一个类做得太少时,内联类
  • 隐藏委托强制遵循迪米特法则;移除中间人当转发成为类的全部时撤销它
  • 根据具体情况解决这种张力:当委托链不稳定时隐藏委托,当中间人纯粹是转发时移除它

代码应用:

上下文 模式 示例
方法羡慕另一个类 移动方法 calculateShipping()Order 移动到 ShippingPolicy
上帝类超过500行 提取类 Address 字段/方法提取到自己的类中
客户端调用 a.getB().getC() 隐藏委托 添加 a.getCThroughB()
类只转发调用 移除中间人 让客户端直接调用委托

当决定职责归属时,请参见 references/moving-features.md——移动方法/字段、提取/内联类、隐藏委托和移除中间人的机制。

4. 组织数据

核心概念: 原始数据——魔法数字、暴露的字段、整数类型代码——会引发微妙的错误并分散领域知识。用封装行为并强制不变量的对象替换基本类型。

为什么有效: 一个 int 金额没有舍入规则或货币代码;一个 Money 对象封装了所有这些,因此业务规则位于一个地方,类型系统在编译时捕获错误。

关键见解:

  • 用符号常量替换魔法数字——最简单的数据重构;它命名了意图
  • 用对象替换数据值治愈基本类型偏执(EmailAddressMoneyTemperature
  • 封装字段和封装集合——永远不要暴露原始字段或可变的内部列表
  • 当类型代码影响行为时,用子类替换类型代码;当子类化不切实际时,用策略替换
  • 当需要同一性语义时,将值对象改为引用对象(一个共享的 Customer,而不是副本)

代码应用:

上下文 模式 示例
if (status == 2) 替换魔法数字 if (status == ORDER_SHIPPED)
String email 到处传递 用对象替换数据值 带有验证的 EmailAddress
Getter 返回可变列表 封装集合 返回 Collections.unmodifiableList(items)
int typeCode 带有 switch 用子类替换类型代码 EmployeeEngineer, Manager

当用对象替换基本类型时,请参见 references/organizing-data.md——用对象替换数据值、将值对象改为引用对象、替换魔法数字、封装字段/集合以及替换类型代码变体的机制。

5. 简化条件逻辑

核心概念: 深度嵌套的 if/else 树、长 switch 和分散的空检查是最难阅读且最容易出错的代码。命名的重构分解、合并和用更清晰的结构替换条件语句。

为什么有效: 一个六分支的条件语句迫使读者在脑海中模拟每条路径;命名良好的提取分支是自文档化的,多态消除了整个类别的“忘记这种情况”的错误。

关键见解:

  • 分解条件:将条件、then 分支和 else 分支提取为命名方法
  • 合并条件表达式:将具有相同结果的条件合并为一个命名检查
  • 用卫语句替换嵌套条件:提前处理边缘情况并返回,保持主路径不缩进
  • 用多态替换条件是类型条件语句的黄金标准
  • 引入特例(空对象)消除了分散的 if (x == null) 检查;引入断言使假设快速失败

代码应用:

上下文 模式 示例
带有复杂条件的长 if 分解条件 提取 isSummer(date)summerCharge()
深度嵌套的 if/else 用卫语句替换 边缘情况优先,提前返回,扁平主路径
基于对象类型的 switch 用多态替换条件 每个类型实现自己的 calculatePay()
到处是 if (customer == null) 引入特例 带有安全默认行为的 NullCustomer

当理清分支时,请参见 references/simplifying-conditionals.md——分解/合并条件、卫语句、用多态替换条件、特例和断言的前后示例。

6. 安全重构工作流

核心概念: 只有在测试的保护下,重构才是安全的。工作流是机械的:运行测试(绿色),应用一个小的转换,运行测试(绿色),提交。如果测试变红,回滚——不要调试失败的重构。

为什么有效: 小步骤使失败显而易见(是你做的最后一件事),回滚只需几秒钟;调试失败的大爆炸式重写需要几天。

关键见解:

  • 三次法则:容忍一次重复,注意两次重复,在第三次出现时重构
  • 预备性重构:在添加功能之前重构以使功能易于添加;理解和捡垃圾重构在阅读和接触代码时持续改进代码
  • 何时不重构:重写更容易、没有测试且添加测试不可行、或者代码很快会被删除
  • 先为清晰重构,然后分析并优化测量到的瓶颈——清晰的代码更容易调优
  • 通过抽象分支和并行变更可以在生产环境中进行大型重构,而无需长期存在的分支

代码应用:

上下文 模式 示例
即将添加功能 预备性重构 先清理插入点
第三次出现相同逻辑 三次法则 现在提取共享逻辑
生产环境中大型 API 变更 通过抽象分支 添加抽象层,迁移调用者,移除旧路径
重命名广泛使用的方法 并行变更 添加新方法,弃用旧方法,迁移,移除

在进行大型或有风险的重构之前,请参见 references/refactoring-workflow.md——完整的绿色到绿色周期、何时(不)重构、性能、通过抽象分支和并行变更。

常见错误

错误 为什么失败 修复
没有测试的重构 没有安全网来检测行为变化 先编写表征测试
大爆炸式重写 混合结构变化和行为变化;无法调试 尽可能小的步骤,每一步后测试
在添加功能的同时重构 同时戴两顶帽子——两种变化都无法验证 先重构(提交),然后添加功能(提交)
重命名而不更新调用者 构建失败或死代码 使用 IDE 重命名;搜索所有引用
提取过多过小的方法 当名称不佳时,间接性没有清晰度 每个名称必须消除阅读方法体的需要
忽略坏味目录 重新发明修复方法而不是应用经过验证的配方 学习命名的坏味;每个都对应重构
重构注定要废弃的代码 在注定要废弃的代码上打磨是浪费 检查代码的生命周期是否值得投资
在重构的同时优化 混淆清晰度和性能 先清晰,然后分析,然后优化热点路径

快速诊断

问题 如果否 行动
开始前测试是否通过? 没有安全网 先编写或修复测试——永远不要在红色状态下重构
你能说出你正在修复的坏味名称吗? 凭直觉重构,而不是目录 识别坏味,应用其规定的重构
每个方法是否在约10行以下? 可能存在长方法 提取方法为命名步骤
每个类是否只有一个更改原因? 发散式变化或大类 提取类以分离职责
是否存在重复的代码块? 最昂贵的坏味 将共享逻辑提取到公共方法/基类
条件语句在适当的地方是否使用多态? 存在 switch 语句 用多态替换条件
你是否在每一步后提交? 有丢失工作、混合更改的风险 在每次绿色到绿色转换后提交
更改后代码是否更易读? 重构增加了复杂性 回滚并尝试不同的方法

进一步阅读

改善现有代码的权威指南:

关于作者

Martin Fowler 是 Thoughtworks 的首席科学家,敏捷宣言的签署人,以及《重构:改善既有代码的设计》(1999年;第2版2018年)的作者,该书将基于目录的命名重构引入主流开发。他的目录支撑着每个主要 IDE 中的自动化重构工具。