0%

学习《重构手册》

类中的异味

可度量的异味

注释

症状

  • 代码中出现注释

原因
作者认为某些内容没有说清楚

  • 采用某种做法的原因
  • 某种复杂的算法的实现

措施

  • 对一个代码块的注释:抽取方法,并用注释为新方法取名
  • 对方法所做工作的注释:重命名方法,用注释为方法重命名
  • 注释描述的是前提条件或边界:引入断言

收益
可以增强表述能力,有可能暴露出重复性

例外
有些注释是必要的,比如需求文档的链接

过长的方法

症状

  • 代码块行数很多(个人感觉超过屏幕的高度为多)

原因
没有及时分解代码块,只是一味地在原来的位置增加代码

措施

  • 根据代码块的注释和空行和空格,分析前后语义,抽取方法
  • 找到一些重构技术,用于清理直线式代码( 即大量代码都放在一行上) 、条件式和变量

收益
可以增强表述能力,有可能暴露出重复性,通常有助于建立新的类和抽象。

例外
有些复杂的算法需求,天然需求很多行

过大的类

症状

  • 代码行数很多
  • 大量实例变量
  • 大量方法

原因
没有及时根据职责拆分类,只是一味地在原来的类上增加代码。

措施

  • 优先解决过长的方法
  • 一个新类能承担此类部分职责, 抽取类
  • 可以划分类和新子类之间的功能的话, 抽取子类
  • 可以确定客户所用的特性子集, 抽取接口

收益
可以增强表述能力,有可能暴露出重复性

例外

过长的参数表

症状

  • 方法有1个或2个以上的参数(个人觉得3个以上)

原因
可能是为了尽量减少对象之间的耦合。这样做不是由被调用对象来了解类之间的关系,而是让调用者来确定所有一切。
也有可能是程序员对例程进行了通用化

措施

  • 参数可由已知对象得到:参数替换为方法(调用方法获取该参数)
  • 参数来自一个对象:保持对象完整(传递完整对象作为参数)
  • 数据不是来自一个逻辑对象:引入参数对象,将其分组(将长参数表变为少量参数对象)

收益
可以增强表述能力,有可能暴露出重复性。通常可以缩小规模。

例外

  • 不希望两个类出现依赖
  • 参数不存在有意义的分组

命名

名字中包含类型

症状

  • 名字采用复合词,包含类型信息
  • 匈牙利记法,如iCount
  • 变量名反映的是类型,而不是其用途

原因

  • 没有ide的时代,命令包含类型增加了可读性

措施

收益
可以增强表述能力,有可能暴露出重复性

例外
有些场景命名中的类型是有帮助的,如sql的字段

表达力差的名字

症状

  • 单字符或双字符
  • 无元音的名字
  • 带编号的变量,如panel1,panel2
  • 奇怪的缩写
  • 容易误导的名称

原因
取名没有规范,过于随意

措施

收益
可以增强表述能力

例外

  • i,j,k等循环变量
  • 只在几行代码中起作用的单字符变量
  • 函数的多个版本实现

不一致的名字

症状

  • 同一个对象却有多个名字

原因
不同的人会在不同时刻创建类, 但作用是相同的, 导致名称不一样

措施

收益
可以增强表述能力, 有可能暴露出重复性

例外

不必要的复杂性

死代码

症状

  • 变量、参数、字段、代码段、方法或类未在任何地方使用(除了测试)

原因

  • 需求变更, 或采用了新方法, 未充分清理
  • 简化代码时未发现冗余的逻辑

措施

  • 删除相关代码并测试

收益
降低规模。可以增强表述能力,代码更简单。

例外

  • 为子类或者调用方提供的hook等方法

过分一般性

症状

  • 未使用的变量、参数、字段、代码段、方法或类; 没有实际作用的继承体系和调用链
  • 就当前需求来说, 代码实现过于复杂

原因

  • 预先设计, 但是实际需求并没有用到该设计, 或者不符合该设计

措施

收益
降低规模。可以增强表述能力,代码更简单。

例外

  • 框架代码
  • 为测试提供的接口

重复

魔法数

症状

  • 代码里出现了数值常量或者字符串常量

原因

  • 第一次添加常量时, 错误地认为该常量只会在该处使用

措施

收益
减少重复。可以增强表述能力,代码更简单。

例外

  • 为了提高测试代码的可读性

重复代码

症状

  • 简单形式: 代码实现几乎相同
  • 复杂形式: 代码作用几乎相同

原因

  • 不同的程序员独立开发产生
  • 懒惰, 常见于不愿意理解原代码的逻辑, 直接复制原代码少量修改

措施

收益
减少重复, 降低规模,代码更简单。

例外

  • 重复代码可读性更好
  • 只是恰好实现相同, 代码的作用并没有什么关系

具有不同接口的相似类

症状

  • 两个类的作用似乎相同, 但是使用了不同的方法名

原因

  • 开发代码时, 没有注意到已有类似代码

措施
协调各个类, 使他们一致, 从而去除其中一个

  1. 采用重命名方法使方法名类似。
  2. 使用搬移方法添加参数和令方法参数化来使协议(即方法签名和实现途径)类似。
  3. 如果两个类只是相似而并非相同,在对它们进行合理协调后,可抽取超类
  4. 尽量删除多余的类

收益
减少重复, 降低规模,可能增强表述能力。

例外

  • 有时这些类无法修改, 比如在不同的库中

条件逻辑

Null检查

症状

  • 反复出现Null检查代码

原因

  • 没有正确初始化对象和设置默认值

措施

收益
减少重复, 减少逻辑错误。

例外

  • 只出现一次的Null检查
  • Null对象的方法必须实现安全且符合逻辑的行为
  • Null有多种含义

复杂的布尔表达式

症状

  • 复杂的and, or, not表达式

原因

  • 复杂的业务逻辑
  • 逻辑多次修改叠加

措施

收益
可能增强表述能力

例外

  • 某些本质上复杂的逻辑, 改善不大

特殊用例

症状

  • 复杂的if语句
  • 在工作前某些特定值的检查

原因
没有对要判断的对象进行很好的分析和抽象

措施

  • 条件式替换为多态
  • 如果过个if子句的内容类似, 修改语句使之适用于多种情况(用变量控制不同的部分)

收益
可以增强表述能力, 可能暴露重复性问题

例外

  • 递归代码的退出判断
  • 有时if子句反而是最简单的

模拟继承(switch语句)

症状

  • switch语句
  • 多条if, elif
  • instanceof类型判断

原因
懒得引入类型

措施
相同条件的switch语句多处出现

  1. 抽取方法。抽出每个分支的代码
  2. 搬移方法。将相关代码搬移至适当的类
  3. 以子类取代类型码以状态/策略取代类型码。建立继承体系结构
  4. 以多态取代条件表达式, 去除条件式

如果条件式出现在一个单独的类中,可以通过将参数替换为显式方法或引入Null对象来取代条件逻辑。

收益
可以增强表述能力, 可能暴露重复性问题

例外

  • 仅出现一次的switch
  • 工厂方法的switch

类之间的异味

数据

基本类型困扰

症状

  • 使用了基本类型或接近基本类型的类型(int,float,String,等等)
  • 存在表示小整数的常量和枚举
  • 存在表示字段名的字符串常量

原因
当ArrayList(或其他一些通用结构)被滥用时,会出现这类紧密相关问题。

  • 没有使用类
  • 模拟类型
  • 模拟字段访问函数

措施
对于缺失对象

  • 参见的”数据泥团”,解决数据泥团问题后通常可以封装基本类型。
  • 通过以对象取代数据值,建立首类(first-class)数据值。

对于模拟类型,一个整型类型码对应一个类

对于模拟字段访问函数

收益
可以增强表述能力, 可能暴露重复性问题, 通常能表明还需要使用其他重构技术。

例外

  • 对象替换基本类型, 会增加开销

数据类

症状

  • 类只包含数据成员

原因
类还未重复开发, 还没有抽象出行为

措施

  1. 使用封装字段防止直接访问字段
  2. 对方法尽可能采用移除设值方法
  3. 使用封装集合防止直接访问任何集合类型的字段。
  4. 査看对象的各个客户。可以对客户使用抽取方法,取出与类相关的代码,然后釆用搬移方法将其置于类中。
  5. 在完成上述工作后,你可能会发现类中还有多个相似的方法。使用重命名方法抽取方法添加参数移除参数等重构技术来协调签名并消除重复。
  6. 对字段的大多数访问都不再需要了,因为搬移的方法涵盖了实际使用。此时便可以使用隐藏方法来消除对获取方法和设置方法的访问

收益
可以增强表述能力,可能会暴露重复性问题

例外

数据泥团

症状

  • 两到三个同样的项频繁地一同出现在类和参数表中。
  • 在类中同时存在多组字段和方法
  • 各组字段名以类似的子字符串开头或结尾

原因
这些字段和方法, 往往应该属于另一个类, 但是没有人发现类缺失

措施

  1. 是类的字段: 抽取字段
  2. 在方法签名中: [引入参数对象](Introduce #Parameter-Object:引入参数对象-, 保持参数对象完整
  3. 查看调用, 利用搬移方法等重构方法

收益
可以增强表述能力,可能会暴露重复性问题, 通常会降低规模

例外

  • 传递对象会带来额外的依赖性

临时字段

症状

  • 字段仅在某些时候进行设置,而在其余时间为Null(或不使用)

原因
通过字段而不是参数来传递信息

措施

收益
增强了表述能力并提高了清晰性。可能会减少重复,特别是在其他位置可以使用新类时。

例外

  • 有时新对象不是一个有用的对象

继承

拒收的遗赠

症状

  • 坦率的拒绝: 一个类继承自其父类,但是抛出了一个异常而不是支持一个方法
  • 隐式的拒绝: 某个继承的方法不能正常工作
  • 客户试图通过子类的句柄而不是父类的句柄来访问类。
  • 继承没有什么实际意义,子类并不是父类的一个例子。

原因
某个类之所以继承自另一个类,可能只是为了实现方便,而不是真的想用这个类来取代其父类。

措施

  • 如果不会导致混淆,可以不去管它。
  • 如果找不到共享某个类关系的理由,就以委托取代继承
  • 如果父子关系确实有意义,则可以通过抽取子类、下移字段和下移方法来创建一个新的子类让这个类拥有非拒绝行为,并将父类的客户修改为这个新类的客户

收益
可增强表述能力,能改善可测试性

例外

  • 有时拒收的遗赠可用于避免新类型的爆炸(通过拒绝特定的方法,就不需要为各种拒绝组合创建一种类型继承体系了)

不当的紧密性

症状

  • 一个类访问了它父类的内部(本应是私有的)部分

原因
子类过分依赖了父类的一些信息, 过度耦合

措施

  • 如果子类以一种非受控方式访问父类的字段,则使用自封装字段
  • 如果父类可以定义一个子类能插入其中的通用算法,则使用塑造模板函数
  • 如果父类和子类需要进一步解耦合,则采用以委托取代继承

收益
可以减少重复。通常能够增强表述能力,还可能会降低规模。

例外

懒惰类

症状

  • 类没有做什么工作,似乎是它的父类、子类或者调用者完成了所有相关工作

原因

  • 过度设计
  • 重构过程中,类的所有职责都移到了其他位置

措施

  • 如果一个类的父类或子类更适合用来完成该类的行为,则通过折叠继承关系将该类与其父类或子类合并。
  • 否则,通过内联类将其行为合并到它的调用者中。

收益
可降低规模。能增强表述能力,代码更简单

例外

  • 有时使用懒惰类是为了传达意图

职责

依恋情节

症状

  • 一个方法似乎过于强调处理其他类的数据,而不是它自己的数据

原因
常见的现象, 一般是因为代码的迭代, 类的职责发生了便宜

措施

收益
可减少重复。通常会增强表述能力,暴露需重构的问题

例外

  • 策略模式或者访问者模式

说明
区分依恋情结和不当的紧密性有时并不简单。
依恋情结是指,一个类自身所做甚少,需要借助大量其他类才能完成自己的工作。
不当的紧密性则是指,一个类为了访问某些本不该访问的内容,过于深入到其他类中。

不当的紧密性(一般形式)

症状

  • 一个类访问了另一个类的内部部分(而这一部分本应是私有的)

原因
两个类之间有时可能会稍有关联。等你意识到存在问题时,这两个类已经过于耦合了

措施

  • 如果两个独立的类彼此“纠缠”,则使用搬移方法和搬移字段将适当的部分放在适当的类中。
  • 如果纠缠的部分看上去是一个被遗漏的类,则使用抽取类和隐藏委托引入这个新类。
  • 如果类相互指向对方,则使用将将双向关联改为单向
  • 如果子类与父类过于耦合, 参考“不当的紧密性(子类形式)”

收益
可以减少重复。通常能够增强表述能力,还可能会降低规模。

例外

消息链

症状

  • a.b().c().d()形式的调用

原因
一个对象必须与其他对象协作才能完成工作,这是自然,问题在于这不仅会使对象相互耦合,还会使获得这些对象的路径存在耦合。
方法不应与“陌生人”说话,也就是说,它应当只与其自身、其参数、它自己的字段或者它创建的对象有信息传递。

措施

  • 如果处理实际针对的是目标对象(即消息链最末端的对象),则使用抽取方法搬移方法将处理置于该对象中。
  • 使用隐藏委托使方法仅依赖于一个对象(因此应将d()方法置于a对象中。这可能还需要为b()和c()对象增加一个d()方法)。

收益
可以减少重复或者暴露重复性问题

例外
如果过分应用隐藏委托,那么对象都去忙着委托,就会没有一个真正做实事的

中间人

症状

  • 类的大多数方法都是在调用另一个对象的同一个(或类似的)方法
  • 如果一个类的任务主要是将工作委托出去,这样的类就称为中间人

原因
可能是因为应用了隐藏委托来解决消息链引起的。
可能在此之后其他一些特性已经被移除了,剩下的主要就是委托方法了。

措施

  • 一般来说,可以通过让客户直接调用委托来移除中间人
  • 如果委托被中间人所有,或者是不可变的,而且中间人还有增加的行为,那么此中间人可以看做委托的一个示例,此时可以以继承取代委托

收益
可降低规模,还可能增强表述能力

例外

  • 有些模式[如代理模式(Proxy)和装饰器模式(Decorator)]会有意地创建委托
  • 要在中间人和消息链之间权衡。

相关改变

发散式改变

症状

  • 尽管每次原因各异,但发现自己所修改的是同一个类

原因
类在发展过程中会承担越来越多的职责,但没有人注意到这会涉及两种截然不同的决策

措施

  • 如果类既要找到对象,又要对其做一些处理,则让调用者査找对象并将该对象传入,或者让类返回调用者使用的值。
  • 釆用抽取类为不同的决策抽取不同的类。
  • 如果几个类共享相同类型的决策,则可以考虑合并这些新类(例如,通过抽取超类抽取子类)

收益
可增强表述能力(更好地传达意图),还可以提高健壮性以备将来修改。

例外

霰弹式修改

症状

  • 仅做一个简单的修改就要更改多个类。

原因

  • 将多个类分离是代码的一大职责。你可能缺失一个通晓全部职责的类(而大量修改本应通过这个类来完成)。
  • 也有可能是因为你过度去除发散式改变而导致了这个异味。

措施

  • 找出一个负责这些修改的类。这可能是一个现有类,也可能需要你通过应用抽取类来创建一个新类。
  • 使用搬移字段和搬移方法将功能置于所选类中。让未选中的类足够简单后,使用内联类将该类去除。

收益
可以减少重复,增强表达能力,并能改进可维护性(将来的修改将更为本地化)。

例外

并行继承体系

症状

  • 在一个继承体系中建立了一个新子类后,却发现还需要在另一个继承体系中创建一个相关的子类。
  • 可能发现两个继承体系中的子类有着相同的前缀(命名可以反映出协调继承体系的需求)。
  • 这是霰弹式修改的一个特例

原因
这样两个类并不是无关的, 而是体现了同一决策的不同方面(维度)。

措施

  • 使用搬移字段和搬移方法来重新分配特性,以便能去除某个继承体系。

收益
可以减少重复。可能会增强表述能力,也可能会降低规模

例外

组合爆炸

症状

  • 你本来希望只引入一个单一的新类,但是最后却发现必须在继承体系的不同位置引入多个类。
  • 你发现继承体系的各个层使用了一组常见的词(例如,一层增加了样式信息,下一层增加了可变性)

原因
这与并行继承体系相关,但是所有内容都折叠到了一个继承体系中 。
原本应当是独立的决策却通过一个继承体系实现了。

措施

  • 如果问题不严重,可以釆用以委托取代继承, 通过为各种变化提供同样的接口,可以创建一个装饰器设计模式示例。
  • 如果情况已经变得相当复杂了,则有必要考虑进行大型重构了,可能还需要梳理并分解继承体系

收益
可以减少重复, 降低规模

例外

类库

不完备的类库

症状

  • 你正在使用一个库类,希望该类具有某种功能,但它没有。

原因
库类的作者未能满足你的要求

措施

  • 弄清类或库的所有者是否会考虑增加你所需要的支持。
  • 如果仅仅是一两个方法,可以对库类的客户应用引入外加函数
  • 如果有多个方法需要增加,则应引入本地扩展
  • 可能需要引入一个层来覆盖这个库

收益
可以减少重复

例外
如果有多个项目,每个项目都使用不兼容的方式来扩展一个类,那么在改变库时就会导致额外的工作

重构技法

Composing Methods:优化函数的构成

Extract Method:抽取方法

before

1
2
3
4
5
6
7
void printOwing() {
printBanner();

// Print details.
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}

after

1
2
3
4
5
6
7
8
9
void printOwing() {
printBanner();
printDetails(getOutstanding());
}

void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}

Inline Method:内联方法

before

1
2
3
4
5
6
7
8
9
class PizzaDelivery {
// ...
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
}

after

1
2
3
4
5
6
class PizzaDelivery {
// ...
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
}

Extract Variable:提炼变量

before

1
2
3
4
5
6
7
8
void renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
}

after

1
2
3
4
5
6
7
8
9
void renderBanner() {
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;

if (isMacOs && isIE && wasInitialized() && wasResized) {
// do something
}
}

Inline Temp:内联临时变量

before

1
2
3
4
boolean hasDiscount(Order order) {
double basePrice = order.basePrice();
return basePrice > 1000;
}

after

1
2
3
boolean hasDiscount(Order order) {
return order.basePrice() > 1000;
}

Replace Temp with Query:以查询取代临时变量

before

1
2
3
4
5
6
7
8
9
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}

after

1
2
3
4
5
6
7
8
9
10
11
double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
}

Split Temporary Variable:拆分临时变量

before

1
2
3
4
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);

after

1
2
3
4
final double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area);

Remove Assignments to Parameters:移除参数赋值

before

1
2
3
4
5
6
int discount(int inputVal, int quantity) {
if (inputVal > 50) {
inputVal -= 2;
}
// ...
}

after

1
2
3
4
5
6
7
int discount(int inputVal, int quantity) {
int result = inputVal;
if (inputVal > 50) {
result -= 2;
}
// ...
}

Replace Method with Method Object:以方法对象替代方法

before

1
2
3
4
5
6
7
8
9
class Order {
// ...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// Perform long computation.
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Order {
// ...
public double price() {
return new PriceCalculator(this).compute();
}
}

class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;

public PriceCalculator(Order order) {
// Copy relevant information from the
// order object.
}

public double compute() {
// Perform long computation.
}
}

Substitute Algorithm:替换算法

before

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")){
return "Don";
}
if (people[i].equals("John")){
return "John";
}
if (people[i].equals("Kent")){
return "Kent";
}
}
return "";
}

after

1
2
3
4
5
6
7
8
9
10
String foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
}

Moving Features between Objects:在对象之间搬移特性

Move Method:搬移方法

before after

Move Field:搬移字段

before after

Extract Class:抽取类

beforeafter

Inline Class:内联类

before after

Hide Delegate:隐藏委托

before after

Remove Middle Man:移除中间人

before after

Introduce Foreign Method:引入外加函数

before

1
2
3
4
5
6
7
8
class Report {
// ...
void sendReport() {
Date nextDay = new Date(previousEnd.getYear(),
previousEnd.getMonth(), previousEnd.getDate() + 1);
// ...
}
}

after

1
2
3
4
5
6
7
8
9
10
class Report {
// ...
void sendReport() {
Date newStart = nextDay(previousEnd);
// ...
}
private static Date nextDay(Date arg) {
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
}

Introduce Local Extension:引入本地扩展

before after

Organizing Data:重新组织数据

Change Value to Reference:将值对象改为引用对象

before after

Change Reference to Value:将引用对象改为值对象

before after

Duplicate Observed Data:复制被监视数据

before after

Self Encapsulate Field:自封装字段

before

1
2
3
4
5
6
class Range {
private int low, high;
boolean includes(int arg) {
return arg >= low && arg <= high;
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
class Range {
private int low, high;
boolean includes(int arg) {
return arg >= getLow() && arg <= getHigh();
}
int getLow() {
return low;
}
int getHigh() {
return high;
}
}

Replace Data Value with Object:以对象取代数据值

before after

Replace Array with Object:以对象取代数组

before

1
2
3
String[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";

after

1
2
3
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");

Change Unidirectional Association to Bidirectional:将单向关联改为双向

before after

Change Bidirectional Association to Unidirectional:将双向关联改为单向

before after

Encapsulate Field:封装字段

before

1
2
3
class Person {
public String name;
}

after

1
2
3
4
5
6
7
8
9
10
class Person {
private String name;

public String getName() {
return name;
}
public void setName(String arg) {
name = arg;
}
}

Encapsulate Collection:封装集合

before after

Replace Magic Number with Symbolic Constant:以符号常量取代魔法数

before

1
2
3
double potentialEnergy(double mass, double height) {
return mass * height * 9.81;
}

after

1
2
3
4
5
static final double GRAVITATIONAL_CONSTANT = 9.81;

double potentialEnergy(double mass, double height) {
return mass * height * GRAVITATIONAL_CONSTANT;
}

Replace Type Code with Class:以类取代类型码

before after

Replace Type Code with Subclasses:以子类取代类型码

before after

Replace Type Code with State/Strategy:以状态/策略取代类型码

before after

Replace Subclass with Fields:以字段取代子类

before after

Simplifying Conditional Expressions:简化条件表达式

Consolidate Conditional Expression:合并条件表达式

before

1
2
3
4
5
6
7
8
9
10
11
12
13
double disabilityAmount() {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// Compute the disability amount.
// ...
}

after

1
2
3
4
5
6
7
double disabilityAmount() {
if (isNotEligibleForDisability()) {
return 0;
}
// Compute the disability amount.
// ...
}

Consolidate Duplicate Conditional Fragments:合并重复条件片断

before

1
2
3
4
5
6
7
8
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}

after

1
2
3
4
5
6
7
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();

Decompose Conditional:分解条件表达式

before

1
2
3
4
5
6
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}

after

1
2
3
4
5
6
if (isSummer(date)) {
charge = summerCharge(quantity);
}
else {
charge = winterCharge(quantity);
}

Replace Conditional with Polymorphism:以多态取代条件表达式

before

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Bird {
// ...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Bird {
// ...
abstract double getSpeed();
}

class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}

// Somewhere in client code
speed = bird.getSpeed();

Remove Control Flag:移除控制标记

before

1
2
3
4
5
6
7
8
9
10
11
12
String foundMiscreant(String[] people){
String found = "";
for (int i = 0; i < people.length; i++) {
if (found.equals("")) {
if (people[i].equals ("John")){
sendAlert();
found = "John";
}
}
}
return found;
}

after

1
2
3
4
5
6
7
8
9
10
11
String foundMiscreant(String[] people){
for (int i = 0; i < people.length; i++) {
if (found.equals("")) {
if (people[i].equals ("John")){
sendAlert();
return "John";
}
}
}
return "";
}

Replace Nested Conditional with Guard Clauses:以卫语句取代嵌套条件式

before

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public double getPayAmount() {
double result;
if (isDead){
result = deadAmount();
}
else {
if (isSeparated){
result = separatedAmount();
}
else {
if (isRetired){
result = retiredAmount();
}
else{
result = normalPayAmount();
}
}
}
return result;
}

after

1
2
3
4
5
6
7
8
9
10
11
12
public double getPayAmount() {
if (isDead){
return deadAmount();
}
if (isSeparated){
return separatedAmount();
}
if (isRetired){
return retiredAmount();
}
return normalPayAmount();
}

Introduce Null Object:引入Null对象

before

1
2
3
4
5
6
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class NullCustomer extends Customer {
boolean isNull() {
return true;
}
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}

// Replace null values with Null-object.
customer = (order.customer != null) ?
order.customer : new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.getPlan();

Introduce Assertion:引入断言

before

1
2
3
4
5
6
7
double getExpenseLimit() {
// Should have either expense limit or
// a primary project.
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit :
primaryProject.getMemberExpenseLimit();
}

after

1
2
3
4
5
6
7
double getExpenseLimit() {
Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}

Simplifying Method Calls:简化方法调用

Add Parameter:添加参数

before after

Remove Parameter:移除参数

before after

Rename Method:重命名方法

before after

Separate Query from Modifier:将查询方法和修改方法分离

before after

Parameterize Method:让方法携带参数

before after

Introduce Parameter Object:引入参数对象

before after

Preserve Whole Object:保持对象完整

before

1
2
3
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

after

1
boolean withinPlan = plan.withinRange(daysTempRange);

Remove Setting Method:移除设值方法

before after

Replace Parameter with Explicit Methods:以明确函数取代参数

before

1
2
3
4
5
6
7
8
9
10
11
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}

after

1
2
3
4
5
6
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}

Replace Parameter with Method Call:以函数调用取代参数

before

1
2
3
4
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);

after

1
2
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);

Hide Method:隐藏方法

before after

Replace Constructor with Factory Method:以工厂方法取代构造函数

before

1
2
3
4
5
6
class Employee {
Employee(int type) {
this.type = type;
}
// ...
}

after

1
2
3
4
5
6
7
8
class Employee {
static Employee create(int type) {
employee = new Employee(type);
// do some heavy lifting.
return employee;
}
// ...
}

Replace Error Code with Exception:以异常取代错误码

before

1
2
3
4
5
6
7
8
9
int withdraw(int amount) {
if (amount > _balance) {
return -1;
}
else {
balance -= amount;
return 0;
}
}

after

1
2
3
4
5
6
void withdraw(int amount) throws BalanceException {
if (amount > _balance) {
throw new BalanceException();
}
balance -= amount;
}

Replace Exception with Test:以测试取代异常

before

1
2
3
4
5
6
7
double getValueForPeriod(int periodNumber) {
try {
return values[periodNumber];
} catch (ArrayIndexOutOfBoundsException e) {
return 0;
}
}

after

1
2
3
4
5
6
double getValueForPeriod(int periodNumber) {
if (periodNumber >= values.length) {
return 0;
}
return values[periodNumber];
}

Pull Up Field:上移字段

before after

Pull Up Method:上移方法

before after

Pull Up Constructor Body:上移构造函数本体

before

1
2
3
4
5
6
7
8
class Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
// ...
}

after

1
2
3
4
5
6
7
class Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
// ...
}

Dealing with Generalization:处理概括关系

Push Down Field:下移字段

before after

Push Down Method:下移方法

before after

Extract Subclass:抽取子类

before after

Extract Superclass:抽取超类

before after

Extract Interface:抽取接口

before after

Collapse Hierarchy:折叠继承关系

before after

Form Template Method:塑造模板函数

before after

Replace Inheritance with Delegation:以委托取代继承

before after

Replace Delegation with Inheritance:以继承取代委托

before after

参考

  1. 重构手册
  2. https://refactoring.guru/refactoring/techniques