Fowler 重构 22 反模式

共 22 题
📑 题目列表 22 题
#
★★★

1. Long Method 在 try/catch 嵌套、嵌套 if、循环内长体的拆分策略。

说明 Long Method(过长方法)在 try/catch 嵌套、嵌套 if、循环内长体的拆分策略?

  • Long Method 的识别
  • try/catch 嵌套的拆分
  • 嵌套 if 与循环长体的拆分

Long Method(过长方法)指方法体过长、包含过多职责,难以阅读与测试。针对不同结构有不同拆分策略:try/catch 嵌套——把每个 try 块内的业务逻辑提炼为独立方法,让 try/catch 只负责异常处理边界,主方法逻辑清晰;嵌套 if——把每个条件分支的判断与处理提炼为方法(如条件语句用"卫语句"或合并条件),或把深层嵌套拆成独立方法,减少缩进层级;循环内长体——把循环体提炼为独立方法(如"对每个元素做什么"),主循环只负责遍历,循环体方法可单独测试。拆分后每个方法单一职责、可命名、可复用、可测试。

Long Method 的根治是"提炼方法",让每个方法只做一件事。针对 try/catch、嵌套 if、循环的特定结构,把"异常处理""条件判断""循环遍历"与"核心逻辑"分离,可读性与可测试性显著提升。

#
★★★

2. Primitive Obsession 在用 int 描述订单状态、钱、范围的工程治理。

说明 Primitive Obsession(基本类型偏执)在用 int 描述订单状态、钱、范围的工程治理?

  • int 描述业务概念的危害
  • 值对象/枚举治理
  • 工程治理步骤

Primitive Obsession 指用 int 描述业务概念(订单状态、钱、范围),危害:无类型安全(任意 int 都能传入,无法区分状态与金额)、重复校验逻辑(到处判状态值、校验范围)、语义不清(状态对应的数字含义不直观)、难以承载行为。治理方式:用枚举(enum)替代 int 状态(如 OrderStatus 枚举,编译器保证合法值);用值对象(如 Money 封装金额与币种、Range 封装起止范围)替代裸 int,把校验与行为封装进对象;迁移时逐步替换 int 参数为类型化对象,配合编译期类型检查发现遗漏。治理后类型安全、语义清晰、行为内聚。

int 偏执的治理核心是"用类型系统表达领域语义"。状态用枚举、金额/范围用值对象,把"什么是合法值"交给编译器,减少运行时错误。工程上要渐进迁移,避免一次性大改。

#
★★★

3. Middle Man 在大量方法仅转发到被委托对象的中间人识别。

说明 Middle Man(中间人)反模式,如何识别大量方法仅转发到被委托对象的中间人,以及如何治理?

  • Middle Man 的识别
  • 大量转发方法
  • 治理(Remove Middle Man)

Middle Man(中间人)指一个对象的大量方法只是转发给被委托对象,自身不做任何业务,形成"纯转发"的冗余层。识别信号:类中大量方法体只是调用 delegate 的对应方法、方法本身没有逻辑、方法数量与委托对象方法高度重合。这种中间人没有提供额外价值(未封装、未改变行为),只是增加了间接层。治理用 Remove Middle Man(移除中间人):让调用方直接使用被委托对象,删除转发方法;若部分转发确有价值(如缓存、日志、权限),则保留并封装。治理后减少不必要的间接层,调用关系更清晰。

Middle Man 是"过度委托"的产物。识别看"方法是否只是转发、是否提供额外价值"。没有价值的转发用 Remove Middle Man 消除,有价值的保留。核心是"间接层要有存在的理由"。

#
★★

4. Comments 在用代码本身表达、删除坏注释的工程治理。

说明 Comments 反模式,用代码本身表达意图、删除坏注释的工程治理?

  • 坏注释的识别
  • 用代码表达意图
  • 删除坏注释

注释应与代码同行,但坏注释(Comments 反模式)指那些解释"怎么做的"、重复代码、过时失步、注释掉的代码等,它们会误导读者、增加噪音。治理方向是"用代码本身表达":把注释描述的意图转化为好的命名、方法提炼、清晰的逻辑,让代码自解释;对"为什么这么做"的注释保留(解释背景与权衡),对"怎么做"的冗余注释删除。删除坏注释:删除重复代码的注释、过时失步的注释、注释掉的代码(用版本控制保留历史),让代码与注释保持同步、无冗余。

好的注释是"解释为什么",坏的注释是"重复怎么做"。治理目标是让代码自解释,注释只保留"机器无法表达的意图"。删坏注释、重命名、提炼方法,让维护成本下降。

#
★★

5. Data Class 在只有 getter/setter、无行为的贫血模型。

说明 Data Class 反模式,只有 getter/setter、无行为的贫血模型如何治理?

  • Data Class 的识别
  • 贫血模型
  • 承载行为

Data Class(数据类)指只有 getter/setter、没有行为的类,是贫血模型(anemic model)——数据散落在外部逻辑中,行为被放在调用方,导致数据与行为分离。识别信号:类只有字段与 getter/setter,方法很少或没有,业务逻辑都在外部读取数据后处理。治理:把操作该类数据的逻辑用 Move Method 移入类中,让类承载行为(如把计算、校验、转换逻辑从外部移入类的方法),使数据与行为内聚;对暴露集合字段的,封装集合并暴露意图方法(addItem、total 等),防止外部随意修改。治理后类成为"有行为的领域对象",而非纯数据容器。

Data Class 是贫血模型,数据与行为分离导致逻辑分散。治理是"为数据加上行为",用 Move Method 把逻辑归位、封装集合、暴露意图方法,提升内聚与封装。

#
★★

6. Data Clumps 在参数列表常出现的字段组应当抽取为对象。

说明 Data Clumps(数据泥团)反模式,参数列表常出现的字段组应当抽取为对象?

  • Data Clumps 的识别
  • 抽取字段组为对象
  • 参数对象

Data Clumps(数据泥团)指同一组字段(如 startDate、endDate 或 name、phone、address)在多个方法的参数列表或类中反复成组出现,被一起处理。识别信号:多个方法签名都带着这一组参数、字段组总是一起出现、一起被操作。治理:用 Introduce Parameter Object / Extract Class 把这一组字段抽取为对象(如 DateRange、Contact、Address),把字段与相关行为(校验、运算)封装进类,多处签名从多个参数收敛为一个对象参数。抽取后减少重复、参数列表更短、语义清晰、行为可复用。注意只在字段组"成组出现且一起被操作"时才抽取,避免过度封装。

Data Clumps 的治理是"把重复的字段组提升为领域对象"。抽取参数对象让签名简洁、行为内聚。判断标准是"是否成组出现、一起被操作"。

#
★★

7. Divergent Change 在单一类因不同原因修改的拆分(SRP)。

说明 Divergent Change(发散式变化)反模式,单一类因不同原因修改时如何按 SRP 拆分?

  • Divergent Change 的识别
  • 单一职责原则
  • 拆分

Divergent Change(发散式变化)指一个类因为不同原因被修改——同一个类承担了多种职责,每种职责的变化都会触发修改。识别信号:加一个功能时反复改同一个类、类中逻辑与多个业务概念相关、改动原因多样。治理:按 SRP(单一职责原则)拆分,用 Extract Class 把属于不同职责的逻辑拆到不同类,让"每个类只因一个原因而变"。例如把"订单持久化"与"订单通知"拆成两个类,各自因各自原因修改。拆分后,单一职责的类改动局部化、更可测试、更内聚。注意拆分要基于职责边界,而非代码大小。

Divergent Change 是 SRP 被违反的典型信号。治理是"按职责拆分,让每个类只响应一个变化原因"。拆分依据是"变化原因"而非"代码量"。

#
★★

8. Duplicated Code 在 magic number、if-else 链、相同字段重复三个位置的工程识别。

说明 Duplicated Code(重复代码)在 magic number、if-else 链、相同字段重复三个位置的工程识别?

  • 重复代码的识别
  • magic number 重复
  • if-else 链与字段重复

Duplicated Code(重复代码)指同一逻辑/值在多个位置重复,是重构的经典信号。三个典型位置:magic number(魔法数字)——同一数字在多个位置重复出现且含义不明,应提取为命名常量或枚举;if-else 链——相同条件的 if-else 分支在多个方法重复,应提炼为方法或条件表达式;相同字段重复——同一组字段在多个类重复定义,应提取为共享类或父类。识别手段:静态分析(重复检测工具)、人工 review(复制粘贴痕迹)、同类逻辑的模式识别。治理:常量提取、条件提炼、字段抽取,消除重复,让修改只改一处。

重复代码的代价是"改一处漏一处、修改漂移"。识别 magic number、if-else 链、重复字段三个位置,用常量/方法/类抽取消除重复。核心是"单一事实来源"。

#
★★

9. Shotgun Surgery 在单一改动触多类的工程治理。

说明 Shotgun Surgery(霰弹式修改)反模式,单一改动触发多类的工程治理?

  • Shotgun Surgery 的识别
  • 单一改动触多类
  • 收敛治理

Shotgun Surgery(霰弹式修改)指一个需求改动需要修改多个分散的类/文件,说明相关逻辑被拆散到多处,违背内聚。识别信号:为一个功能要改多个类、改动列表常是一组固定文件、相关逻辑分散。治理:用 Move Method / Move Field 把散落的逻辑收敛到同一类(或新建类承载),形成内聚模块;用 Extract Class 把相关职责聚拢;必要时把对同一概念的处理集中。治理后,一次需求改动落在一处,减少遗漏与回归风险。它与 Divergent Change 是"对偶":前者是"一个变化改多处"需合并,后者是"一类因多原因改"需拆分。

Shotgun Surgery 的治理方向是"合并收敛",让同一变化点集中。收敛后改动局部化、易测试。识别信号是"改动列表固定、逻辑分散"。

#
★★

10. Speculative Generality 在为未来准备的抽象、参数、类的过度设计。

说明 Speculative Generality(猜测性通用)反模式,为未来准备的抽象、参数、类的过度设计如何识别与治理?

  • Speculative Generality 的识别
  • 为未来准备的抽象
  • 过度设计治理

Speculative Generality(猜测性通用)指为"将来可能用到"而做的过度设计——多余的抽象、参数、类、接口,当前没有真实使用者。识别信号:抽象只有一个实现、参数从不被传非默认值、接口/基类没有调用方、方法带有无用参数、类几乎不被使用。治理:删除这些过度抽象——先确认无使用者(静态分析调用图),再删除无用参数、合并多余接口/基类、删除死代码,每步保持可编译可测试;若确有明确未来需求,可保留但加注释说明依据。核心是遵循 YAGNI,不为假设的未来需求预建抽象。

Speculative Generality 是 YAGNI 的反面。识别看"是否只有一个实现、是否有使用者"。治理是"安全删除过度抽象",让代码只保留当前真实需求。判断"真需求"与"瞎猜"是关键。

#
★★

11. Temporary Field 在实例字段仅在特定算法期间使用的设计问题。

说明 Temporary Field(临时字段)反模式,实例字段仅在特定算法期间使用的设计问题如何治理?

  • Temporary Field 的识别
  • 仅在特定算法期间使用
  • 治理

Temporary Field(临时字段)指实例字段仅在特定算法/特定场景期间才被使用,其他时候为 null 或无效,导致字段占用状态、逻辑需判断"字段是否有效"。识别信号:字段只在某个方法/状态下被赋值与读取、其他时候为 null、逻辑中反复判断字段是否可用。设计问题:字段生命周期不清晰、给对象增加无效状态、增加分支判断。治理:把该字段从实例字段改为局部变量(在算法方法内声明),或把算法提炼为独立类/方法,让"临时数据"成为局部状态而非对象状态;若算法是独立场景,用 Extract Class 把算法状态与临时字段一起封装。治理后对象状态更干净,无需判断字段有效性。

Temporary Field 是"字段放错位置"——临时数据应作为局部变量或算法类的一部分,而非对象实例字段。治理让对象状态精简、消除无效判断。

#

12. Message Chains 在 a.getB().getC().getD() 的链式调用的封装。

说明 Message Chains(消息链)反模式,a.getB().getC().getD() 的链式调用如何封装治理?

  • Message Chains 的识别
  • 链式调用问题
  • 封装治理

Message Chains(消息链)指通过 a.getB().getC().getD() 这种多层链式调用访问对象,问题:调用方与真实对象(D)耦合被隐藏、真实依赖不清晰、链中任何一环变化都牵连调用方、过度暴露内部结构。治理用 Hide Delegate(隐藏委托):在链的中间对象上暴露意图方法,让调用方只需要调用一个方法,不需要知道链结构。例如在 A 上提供 a.getD() 或 a.getDest(),内部负责沿链获取。若链涉及的中间对象太多,可考虑 Extract Method 或重构中间对象。治理后调用方与真实依赖解耦,链结构变化不影响调用方。

Message Chains 是"过度暴露内部结构与依赖"。Hide Delegate 把链封装进意图方法,让调用方不感知链。判断标准是"调用方是否需要知道链结构",不需要则封装。

#

13. Parallel Inheritance Hierarchies 在父类新增时子类必须同步新增的反模式。

说明 Parallel Inheritance Hierarchies(平行继承层次)反模式,父类新增时子类必须同步新增的设计问题?

  • Parallel Inheritance Hierarchies 的识别
  • 必须同步新增子类
  • 治理

Parallel Inheritance Hierarchies(平行继承层次)指两个继承层次总是同步变化——为父类新增一个子类时,另一层次也必须新增对应子类(如"司机"类新增子类,则"车辆"类也必须新增对应子类)。问题:两个层次强耦合,改动需同步维护多处,违背单一职责,维护成本高。识别信号:两个继承层次对应关系严格一一对应、新增一个类必须同时新增另一个。治理:用"让一个层次引用另一个层次"减少平行——如用一个层次持有另一个层次的对象,把平行继承改为组合/委托;或把两个层次合并为一个层次,消除强制同步。治理后打破"必须同步新增"的耦合。

Parallel Inheritance Hierarchies 是"平行继承的耦合"。治理方向是"消除平行依赖",用组合/委托替代一个层次,或合并。让新增类不再需要同步改多处。

#

14. Refused Bequest 在子类拒绝父类方法/字段的继承问题。

说明 Refused Bequest(拒绝馈赠)反模式,子类拒绝父类方法/字段的继承问题如何治理?

  • Refused Bequest 的识别
  • 子类拒绝继承
  • 治理(委托取代继承)

Refused Bequest(拒绝馈赠)指子类只使用父类的一部分方法/字段,其余被拒绝(可能抛异常或空实现),说明继承关系不成立——子类并不"是"父类的完整特化。识别信号:子类重写父类方法抛异常、子类空实现父类方法、子类只依赖父类部分成员。治理:用"以委托取代继承"(Replace Inheritance with Delegation),让子类持有父类对象,通过委托复用需要的部分,而非继承暴露全部,避免"接口污染"(子类被迫拥有不适用的方法)、打破封装;若子类确实需要大部分父类行为,则保留继承。治理后子类只保留所需能力,继承关系更合理。

继承应表达"is-a",子类拒绝部分父类说明关系不成立。委托取代继承让类保留所需能力、避免暴露不适用的接口,符合"组合优于继承"。

#

15. Switch Statements 在多态替代的工程实现。

说明 Switch Statements 反模式,用多态替代的工程实现?

  • Switch Statements 的识别
  • 多态替代
  • 工程实现

Switch Statements(switch 语句)反模式指代码中大量基于类型/枚举的 switch/if-else 分支,且分支随类型增长而频繁修改,违背开闭原则。治理用多态替代(Replace Conditional with Polymorphism):定义基类/接口,声明行为方法;为每种类型建子类实现;把 switch 分支逻辑移到子类方法中,用多态分发替代分支判断。工程实现步骤:新建接口与子类、迁移分支逻辑、替换调用点、删除 switch。注意:当 switch 基于"值的不同"而非"类型不同"时,用查表/策略而非多态;若分支少且稳定,不必强行替代。治理后新增类型只需新增子类,无需改 switch。

Switch Statements 是"类型分支"的坏味道。多态替代让分支逻辑归属到各类型,符合开闭原则。判断是否替代:看"分支是否随类型频繁变化"。稳定的简单分支不必强改。

#

16. Alternative Classes with Different Interfaces 在同名不同方法的抽象统一。

说明 Alternative Classes with Different Interfaces(异类接口)反模式,同名不同方法的抽象统一?

  • Alternative Classes with Different Interfaces 的识别
  • 同名不同方法
  • 抽象统一

Alternative Classes with Different Interfaces(异类接口)指两个类做相同的事,但方法名/接口不同(如一个类叫 getPrice(),另一个叫 fetchPrice()),调用方需分别为它们写不同代码。识别信号:多个类功能相似但接口不统一、调用方根据类类型写不同分支、方法语义相同但命名不同。治理:用统一的接口/抽象来统一——定义公共接口,声明统一的方法名,让这些类实现同一接口,调用方针对接口编程;或让其中一个类的方法改名对齐另一个。治理后,同一语义有统一接口,调用方代码统一、可替换、可扩展。

异类接口是"接口命名不统一"导致的重复。治理是"抽象统一接口",让做相同事情的类实现同一接口,消除调用方因接口差异而写的分支。

#

17. Feature Envy 在方法多访问他类字段而非本类字段的归属。

说明 Feature Envy(依恋情结)反模式,方法多访问他类字段而非本类字段时如何治理归属?

  • Feature Envy 的识别
  • 方法访问他类字段
  • 归属治理

Feature Envy(依恋情结)指一个方法大量访问其他类(他类)的字段而非本类字段,说明方法放错了归属对象。识别信号:方法的主要操作都依赖别的对象的 getter/字段、方法体几乎都在操作"他人"的数据。治理:用 Move Method 把方法搬到它真正依赖数据的类中,让"数据与操作它的行为"内聚;必要时 Move Field 一并迁移。例如方法 priceOf(Order o) 大量读取 o 的字段,应搬到 Order 类作为 order.price()。治理后方法靠近其数据,封装增强、Feature Envy 减少,调用方改为调用归属类的意图方法。

Feature Envy 本质是"行为与数据错位"。治理是"让方法靠近它操作的数据",用 Move Method 归位。判断看"方法依赖谁的数据",依赖谁就搬向谁。

#

18. Inappropriate Intimacy 在两个类互相访问私有成员的耦合。

说明 Inappropriate Intimacy(过度亲密)反模式,两个类互相访问私有成员的耦合如何治理?

  • Inappropriate Intimacy 的识别
  • 互相访问私有成员
  • 治理

Inappropriate Intimacy(过度亲密)指两个类互相访问对方的私有成员/内部细节,耦合过深,破坏了封装。识别信号:类 A 直接访问 B 的私有字段、两个类互相调用对方的内部方法、逻辑纠缠不清。治理:用 Move Method / Move Field 把"访问对方私有成员"的逻辑移到被访问的类中,让数据与行为归位;或把共同的部分提取为公共的类/方法,减少互相访问;必要时用接口隔离,明确公开边界。治理后类之间的依赖通过公开接口而非私有细节,降低耦合、提升封装。若两个类确实紧密相关,考虑合并为一个类。

过度亲密是"封装被破坏"的信号。治理是"让私有成员保持私有",通过搬移逻辑、提取公共部分、接口隔离来降低耦合。必要时合并真正紧密的类。

#

19. Incomplete Library Class 在无法修改的库的扩展模式。

说明 Incomplete Library Class(不完善的库类)反模式,在无法修改的库上如何扩展模式?

  • Incomplete Library Class 的识别
  • 无法修改的库
  • 扩展模式

Incomplete Library Class(不完善的库类)指使用的第三方库类缺少所需的方法,而库本身无法修改。识别信号:需要给库类加方法但无法改库源码、经常在外部写辅助函数操作库类。治理扩展模式:用"引入外部函数"(Introduce Foreign Method)——在客户端写一个辅助方法,把库类作为参数,实现所需行为;用"引入本地扩展"(Introduce Local Extension)——定义库类的子类或包装类,增加所需方法,客户端用扩展类替代库类。若用包装类,可保持库类不修改,通过委托扩展。治理后不修改库源码也能获得所需能力。

无法修改的库需要"外部扩展"而非"改库"。Introduce Foreign Method 适合单个方法,Introduce Local Extension(子类/包装)适合扩展多个方法。避免在库类源码上打补丁。

#

20. Large Class 在上帝类、聚合多种职责的拆分边界。

说明 Large Class(上帝类)反模式,聚合多种职责的类如何拆分边界?

  • Large Class 的识别
  • 聚合多种职责
  • 拆分边界

Large Class(上帝类)指一个类过大、聚合了多种职责,字段与方法众多、LCOM 低、被大量代码依赖。识别信号:类方法/字段数量大、LCOM 高、改动频繁牵涉多方面、职责混乱。拆分边界:按 SRP 划分职责,分析哪些字段与方法属于同一职责,用 Extract Class 把每组职责拆成独立类,让同类内字段方法与职责高度内聚;拆分的边界是"每个新类只承担一个清晰职责"。避免拆出新的上帝类:拆分前明确每个新类的单一职责,避免"大拆成多个中类";拆出的类各自职责清晰、配合依赖注入与接口。治理后类职责单一、内聚、易测试。

Large Class 的治理是"按职责拆分"。拆分边界以"单一职责"为准,而非按代码大小。关键是先聚类再拆分,避免拆出新上帝类,用测试验证行为不变。

#

21. Lazy Class 在没什么职责的类的删除。

说明 Lazy Class(懒散类)反模式,没什么职责的类如何识别并删除?

  • Lazy Class 的识别
  • 没什么职责的类
  • 删除

Lazy Class(懒散类)指几乎不做事的类——方法很少、字段很少、没有实际职责,常是重构遗留或计划未落实的产物。识别信号:类方法/字段数量极少、没有调用方或调用方很少、方法体几乎为空、类的存在价值存疑。治理:若类有少量相关内容,用 Collapse Class(合并到相邻类)并入合理归属的类;若类已无职责与调用方,直接删除(用版本控制保留历史)。删除前先确认无引用(静态分析调用图),避免误删真实依赖。治理后去除无价值类,降低认知负担,结构更清晰。

Lazy Class 是低价值类,增加认知负担。治理是"合并或删除",判断标准是"是否有不可替代的职责"。删除前用调用图确认无引用,配合版本控制保障安全。

#

22. Long Parameter List 在引入 Parameter Object 后的可读性与可演进性。

说明 Long Parameter List(过长参数列表)在引入 Parameter Object 后的可读性与可演进性?

  • Long Parameter List 的识别
  • 引入 Parameter Object
  • 可读性与可演进性

Long Parameter List(过长参数列表)指方法参数过多、难以阅读与调用。治理常用 Introduce Parameter Object:把逻辑上相关的参数组合成一个对象,减少参数个数。引入后可读性提升:方法签名从罗列多个参数变为一个语义清晰的对象,调用方易于理解;参数对象可承载默认值、校验与相关行为,语义更明确。可演进性提升:参数对象封装了一组相关数据,未来增删字段只需改对象,无需改方法签名(调用方无需逐层改动);参数对象可复用、可扩展,也便于测试构造。注意只在参数"成组相关"时引入,避免把无关参数强行拼装。

Parameter Object 的价值在于"可读性与可演进性"。它把一组相关参数收敛为一个对象,让签名简洁、演进局部化、行为可承载。判断标准是"参数是否成组相关"。