mcpField
R
refactoring-patterns

refactoring-patterns

github.com/wondelai开发2.5k6.4k 次安装7 周前更新
代码仓库

关于 refactoring-patterns

refactoring-patterns 是用于改进现有代码结构、同时保持可观察行为不变的开发技能。它整理代码异味及对应的重构手法,并以测试验证的小步流程指导安全重构。

主要功能

  1. 01代码异味目录及对应处理
  2. 02方法组合模式
  3. 03在对象间移动职责
  4. 04数据与条件逻辑转换
  5. 05测试验证的重构流程

适用场景

  1. 01拆分过长的方法
  2. 02将行为移至持有相关数据的类
  3. 03简化嵌套条件逻辑
  4. 04添加功能前调整代码结构

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wondelai/skills --skill refactoring-patterns

安装后在 Claude Code 中输入/refactoring-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
composing-methods.md
moving-features.md
organizing-data.md
refactoring-workflow.md
simplifying-conditionals.md
smell-catalog.md

详细介绍

重构模式框架

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

核心原则

重构不是重写,而是一系列保持行为不变的小幅变换,且每一步都有测试作为保障。 你绝不改变代码的行为,只改变其组织方式。大规模重写会失败,因为它将结构变更与行为变更混在一起,让人无法判断究竟是什么导致了问题。

基础认知: 代码质量差是时间压力下交付的自然结果,并非性格缺陷。代码异味是结构退化的客观信号;异味目录告诉你该看哪里,重构目录则告诉你该怎么做。

评分

目标:10/10。 根据八项快速诊断检查中通过的项数评估结构质量——score = round(passed / 8 × 10);如果某一种异味特别严重,则相应下调分数。评分区间:

  • 9-10:没有明显的异味;每个函数只做一件事;名称能体现意图;重复代码已消除;适用时,条件逻辑使用多态;测试覆盖了重构后的路径。
  • 5-6:还存在少量异味(一个过长方法、一些重复代码),但整体结构基本合理。
  • ≤3:异味普遍存在——条件逻辑纠缠不清、上帝类、到处都是重复代码——或者没有可用于安全重构的测试。

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

重构模式框架

系统性改进代码结构的六个重点领域:

1. 以代码异味为触发信号

核心概念: 代码异味是深层结构问题的表面迹象——它们不是 bug,而是设计使代码更难理解、扩展或维护的信号。每种异味都对应一组有名称的重构,可以用来修复它。

有效原因: 明确命名的异味为团队提供了客观标准,避免停留在主观的“我不喜欢这样”——“这是特性嫉妒(Feature Envy)”能直接指明修复方向。

关键要点:

  • 异味分为五类:臃肿者、面向对象滥用者、变更阻碍者、冗余者、耦合者
  • 过长方法是最常见的异味;重复代码代价最高
  • 如果一个方法需要注释来解释它做什么,那就是一种异味——应提取这段代码块并为其命名
  • 霰弹式修改(一项变更涉及多个类)和发散式修改(一个类因多种原因而需要变更)是职责放置不当的相反信号
  • 基本类型偏执——使用原始字符串/整数,而非小型领域对象——会导致错误和重复代码扩散

代码应用:

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

当你需要为异味命名并找到对应的修复方式时,请参阅 references/smell-catalog.md——其中涵盖了全部五类异味(臃肿者、面向对象滥用者、变更阻碍者、冗余者、耦合者),并提供检测启发式方法及每种异味对应的重构。

2. 组合方法

核心概念: 大多数重构都从这里开始:将冗长的方法拆分成更小且命名清晰的部分,让代码读起来像散文——高层步骤委托给名称明确的辅助方法。

为什么有效: 简短且能体现意图的方法可以消除注释,让错误一目了然,并支持复用;只要方法名说明了一切,阅读方法调用就毫不费力。

关键见解:

  • 提取方法是最重要的重构,先掌握它
  • 想写注释?把代码块提取出来,并用注释作为方法名
  • 如果方法体和名称一样清晰,就内联方法——没有价值的间接调用只会增加噪声
  • 对多个位置使用的计算值,用查询替换临时变量;当一个临时变量承担两种用途时,拆分临时变量
  • 当局部变量纠缠得难以提取时,用方法对象替换方法——将局部变量变为字段

代码应用:

场景模式示例
带注释的代码块提取方法// check eligibility → isEligible()
只使用一次的临时变量内联变量删除 const price = order.getPrice()
简单的委托方法内联方法如果只使用一次,则内联 return deliveries > 5
局部变量纠缠的复杂方法用方法对象替换方法将局部变量变为新类中的字段

应用任何方法级转换时,请参阅 references/composing-methods.md——其中提供提取/内联方法、提取/内联变量、用查询替换临时变量、拆分临时变量,以及用方法对象替换方法的分步操作说明和转换前后代码。

3. 在对象之间移动功能

核心概念: OO 设计的关键决策是职责归属。当 Feature Envy、过度耦合或类大小失衡表明某个方法或字段放错了类时,就把它移到合适的位置。

为什么有效: 如果方法远离其使用的数据,就会产生隐形的跨类依赖,导致一次逻辑变更波及多个文件——这就是 Shotgun Surgery。让方法和数据归于同一处,可以将变更限制在一个类中。

关键见解:

  • 当某个方法使用另一个类的功能多于自身类时,移动方法;字段同理
  • 当一个类承担两种职责时,提取类——沿着变更轴拆分;当一个类职责过少时,内联类
  • 隐藏委托可以落实 Demeter 法则;当转发调用成为类的全部职责时,移除中间人可以撤销这种做法
  • 根据具体情况处理这种权衡:委托链不稳定时隐藏委托;纯粹转发时移除中间人

代码应用:

场景模式示例
方法觊觎另一个类的功能移动方法将 calculateShipping() 从 Order 移到 ShippingPolicy
超过 500 行的上帝类提取类将 Address 字段/方法提取到独立的类中
客户端调用 a.getB().getC()隐藏委托添加 a.getCThroughB()
类只负责转发调用移除中间人让客户端直接调用委托对象

在决定某项职责应该归属何处时,请参阅 references/moving-features.md——其中介绍移动方法/字段、提取/内联类、隐藏委托和移除中间人的操作方法。

4. 组织数据

核心概念: 原始数据——魔法数字、公开字段、整数类型代码——会引发隐蔽错误,并使领域知识散落各处。用封装行为并强制执行不变量的对象替代基本类型。

为何有效: int 金额没有舍入规则或货币代码;Money 对象将它们全部封装起来,让业务规则集中在一处,并由类型系统在编译时捕获错误。

关键要点:

  • 用符号常量替换魔法数字——最简单的数据重构;它能明确表达意图
  • 用对象替换数据值可以解决基本类型偏执(EmailAddress、Money、Temperature)
  • 封装字段和封装集合——绝不暴露原始字段或可变的内部列表
  • 当类型代码会影响行为时,用子类替换类型代码;如果不适合使用子类,则使用策略
  • 当需要身份语义时,将值改为引用(共享同一个 Customer,而不是创建副本)

代码应用:

上下文模式示例
if (status == 2)用符号常量替换魔法数字if (status == ORDER_SHIPPED)
到处传递 String email用对象替换数据值带验证功能的 EmailAddress 类
Getter 返回可变列表封装集合返回 Collections.unmodifiableList(items)
带有 switch 的 int typeCode用子类替换类型代码Employee → Engineer、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 中自动重构工具的基础。

评论

支持 Markdown