学习《重构手册》

原文转载自 「逸思杂陈」 ( http://ponder.work/2020/10/15/学习《重构手册》/ ) By Jay.Run

预计阅读时间 0 分钟(共 0 个字, 0 张图片, 0 个链接)

类中的异味

可度量的异味

注释

症状

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

措施

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

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

过长的方法

症状

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

措施

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

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

过大的类

症状

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

措施

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

例外

过长的参数表

症状

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

措施

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

例外

命名

名字中包含类型

症状

原因

措施

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

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

表达力差的名字

症状

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

措施

收益
可以增强表述能力

例外

不一致的名字

症状

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

措施

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

例外

不必要的复杂性

死代码

症状

原因

措施

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

例外

过分一般性

症状

原因

措施

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

例外

重复

魔法数

症状

原因

措施

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

例外

重复代码

症状

原因

措施

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

例外

具有不同接口的相似类

症状

原因

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

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

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

例外

条件逻辑

Null检查

症状

原因

措施

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

例外

复杂的布尔表达式

症状

原因

措施

收益
可能增强表述能力

例外

特殊用例

症状

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

措施

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

例外

模拟继承(switch语句)

症状

原因
懒得引入类型

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

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

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

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

例外

类之间的异味

数据

基本类型困扰

症状

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

措施
对于缺失对象

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

对于模拟字段访问函数

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

例外

数据类

症状

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

措施

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

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

例外

数据泥团

症状

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

措施

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

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

例外

临时字段

症状

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

措施

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

例外

继承

拒收的遗赠

症状

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

措施

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

例外

不当的紧密性

症状

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

措施

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

例外

懒惰类

症状

原因

措施

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

例外

职责

依恋情节

症状

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

措施

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

例外

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

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

症状

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

措施

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

例外

消息链

症状

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

措施

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

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

中间人

症状

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

措施

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

例外

相关改变

发散式改变

症状

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

措施

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

例外

霰弹式修改

症状

原因

措施

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

例外

并行继承体系

症状

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

措施

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

例外

组合爆炸

症状

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

措施

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

例外

类库

不完备的类库

症状

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

措施

收益
可以减少重复

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

重构技法

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
more_vert