java如何写出简洁代码

该文章主要是描述如何使用重构方法来精简代码,使代码看起来更有灵性。

重构原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. 三次法则(事不过三,三则重构)
2. 何时重构:
1)添加功能时重构
2)修补错误时重构
3)复审代码时重构
3. 重构优点:
1)允许逻辑共享
2)分开解释意图和实现
3)隔离变化
4)封装条件逻辑
4. 重构难点:
1)数据库
2)修改接口
解决办法:不要过早发布接口,请修改你的代码所有权策略,
使重构更顺畅。
3)难以通过重构手法完成的设计改动
5. 重构与设计:
重构肩负一项特殊使命,它和设计彼此互补。

代码坏味道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Duplicated Code(重复代码)
Long Method(过长函数)
Large Class(过大的类)
Long Parameters List(过长参数列)
Divergent Change(发散式变化)-> 一个类受多种变化的影响
Shotgun Surgery(霰弹式修改)-> 一种变化引发多个类相应修改
Feature Envy(依恋情结)-> 将数据和数据的操作行为包装在一起的技术
Data Clumps(数据泥团)
Primitive Obsession(基本类型偏执)
Switch Statements(switch惊悚现身)
Parallel Inheritance Hierarchies(平行继承行为)
Lazy Class(冗余类)
Sepculative Generality
Temporary Field(令人迷惑的暂时字段)
Message Chain(过度耦合的消息链)
Middle Many(中间人)
Inappropriate Intimacy(狎昵关系)
Alternative Classes with Different Interfaces(异曲同工的类)
Incomplete Library Class(不完善的类库)
Data Class(纯稚的数据类)
Refused Bequest(被拒绝的遗赠)
Comments(过多的注释)-> 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释变得多余。

构筑测试体系

1
2
3
4
5
6
7
8
确保所有测试都完全自动化,让它们检查自己的测试结果。
一套测试就是一个强大的侦测器,能够大大缩短查询bug所需要的时间
频繁的运行测试,每次编译请把测试也考虑进去————每天至少执行每个测试一次
每当你收到bug报告,请先写一个单元测试来暴露bug.
编写未臻完善的测试并实际运行,好过对完善测试的无尽等待。
考虑可能出错的边界条件,把测试火力集中在那儿
当事情被认为应该出错时,别忘了检查是否抛出了预期的异常。
不要因为测试无法捕捉所有bug就不写测试,因为测试的确可以捕捉到大多数的bug。

重构列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 重构记录格式:
name、summary、motivation、mechanics、examples
1)summary:
一句话,介绍这个重构能够帮助解决的问题
一段简述,介绍你应该做的事
一幅速写图,简单展现重构前后示例
2)mechanics:
简短的笔记,为了让自己在一段时间不做某项重构之后还能记录怎么做。
3)examples
像是简单有趣的教科书
2. 寻找引用点
1)如果被删除的部分在继承体系中声明不上一次
2)编译器可能很慢
3)编译器无法找到通过反射机制而得到的引用点

重新组织函数

Extract Method(提炼函数)

1
2
3
4
5
6
7
8
motivation:喜欢简短而命名良好的函数,细粒度函数,被复用的机会更大,函数的覆写也更容易些
mechanics:
1)创造一个新函数
2)将提炼的代码从源函数复制到新建的目标函数中
3)仔细检查提炼出的代码,看看其中是否引用了“作用域源于源函数”的变量(包括局部变量和源函数参数)
4)检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将它们声明为临时变量
5)检查被提炼代码段,看看是否有任何局部变量的值被它改变。
6)将被提炼代码段中需要读取的局部变量,当作参数传给目标函数

Inline Method(内联函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
*(1)查检函数,确定它不具备多态性
*(2)找出这个函数的所有被调用点
*(3)将这个函数的所有被调用点都替换为函数本体
*(4)编译,测试
*(5)删除该函数的定义
*/
int getRating() {
return moreThanFiveDeliveries() ? 2 : 1;
}
boolean moreThanFiveDelivers() {
return _numberOfLateDeliveries > 5;
}
->
int getRating() {
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}

Inline Temp(内联临时变量)

1
2
3
4
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
->
return (anOrder.basePrice() > 1000);

Replace Temp With Query(以查询取代临时变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
->
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
double basePrice() {
return _quantity * _itemPrice;
}

Introduce Explaing Variable(引入解释性变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if ((platform.toUpperCase().indexOf("MAC") > -1) && 
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0) {
// do something
}
->
final boolen isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasResized) {
// do something
}
double price() {
return _quantity * _itemPrice -
Math.max(0, _quantity - 500) * _itemPrice * 0.05 +
Math.min(_quantity * _itemPrice * 0.1, 100.0);
}
->
double price() {
final double basePrice = _quantity * _itemPrice;
final double quantityDiscount = Math.max(0, _quantity - 500) * _itemPrice * 0.05;
final double shpping = Math.min(basePrice * 0.1, 100.0);
return basePrice - quantityDiscount + shpping;
}
->
double price() {
return basePrice() * quantityDiscount() * shpping();
}
double basePrice() {
return _quantity * _itemPrice;
}
double quantityDiscount() {
return Math.max(0, _quantity - 500) * _itemPrice * 0.05;
}
double shpping() {
return Math.min(basePrice * 0.1, 100.0);
}

Split Temporary Variable(分解临时变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
double temp = 2 * (_heigth + _width);
sysout(temp);
temp = _heigth * _width;
sysout(temp);
->
final double perimeter = 2 * (_heigth + _width);
sysout(perimeter);
final double area = _heigth * _width;
sysout(area);
->
double getDistanceTravelled(int time) {
double result;
finall double primaryAcc = _primaryForce / _mass;
int primaryTime = Math.min(time, _delay);
return = 0.5 * primaryAcc * primaryTime * primaryTime;
int secondaryTime = time - _delay;
if (secondaryTime > 0) {
double primaryVel = primaryAcc * _delay;
final double secondaryAcc = (_primaryForce + _secondaryForce) / _mass;
result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime;
}
return result;
}
...

Remove Assignments to Parameters(移除对参数的赋值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Account {
int gamma (int inputVal, int quantity, int yearToDate) {
int importantValue1 = (inputVal * quantity) + delta();
int importantValue2 = (inputVal * quantity) + 100;
if (yearToDate - importantValue1 > 100)
importantValue2 -= 20;
int importantValue3 = importantValue2 * 7;
// do something
return importantValue3 - 2 * importantValue1;
}
}
->
Gamma {
private final Account _account;
private int inputVal;
private int quantity;
private int yearToDate;
private int importantValue1;
private int importantValue2;
private int importantValue3;
Gamma(source, inputVal, quantity, yearToDate) {
_account = source;
inputVal = inputVal;
...
}
int compute() {
importantValue1 = (inputVal * quantity) + _account.delta();
importantValue2 = (inputVal * quantity) + 100;
if (yearToDate - importantValue1 > 100)
importantValue2 -= 20;
importantValue3 = importantValue2 * 7;
// do something
return importantValue3 - 2 * importantValue1;
}
->
importThing() {
if (yearToDate - importantValue1 > 100)
importantValue2 -= 20;
}
}
->
int gamma (int inputVal, int quantity, int yearToDate) {
return new Gamma(this, inputVal, quantity, yearToDate).compute();
}

Substitude Algorithm(替换算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 "";
}
}
->
String foundPerson(String[] people) {
List<String> candidates = Arrays.asList("Don","John","Kent");
for(int i=0; i < people.length; i++)
if (candidates.contains(people[i]))
return people[i];
return "";
}

在对象之间搬移特性

Move Method(搬移函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 动机:搬移函数是重构理论的支柱,解耦。
* 做法:
*(1)检查源类中被源函数所使用的一切特性(包括字段和函数),考虑它们是否也该被搬移。
*(2)检查源类的子类和超类,看看是否有该函数的其它声明。
*(3)在目标类中声明这个函数。
*(4)将源函数的代码复制到目标函数中,并在源函数调用后者。
*(5)编译目标类
*(6)决定如何从源函数正确引用目标对象
*(7)修改源函数,使之成为一个纯委托函数
*(8)编译,测试
*(9)决定是否删除源函数,或将它当作一个委托函数保留下来。
*(10)如果要移除源函数,请将源类中对源函数的所有调用,替换为目标函数的调用。
*/
class Account {
double overdraftCharge() {
/**f (_type.isPreminum()) {
double result = 10;
if (_dayOverdrawn > 7) result += (_dayOverdrawn - 7) * 0.85;
return result;
}
else return _dayOverdrawn * 1.75;*/
return _type.overdraftCharge(_dayOverdrawn);
}
double bankCharge() {
double result = 4.5;
// if (_dayOverdrawn > 0) result += overdraftCharge();
if (_dayOverdrawn > 0)
result += _type.overdraftCharge(_dayOverdrawn);
return result;
}
}
->
class AccountType {
double overdraftCharge(int dayOverdrawn) {
if (isPreminum()) {
double result = 10;
if (dayOverdrawn > 7) result += (dayOverdrawn - 7) * 0.85;
return result;
}
else return dayOverdrawn * 1.75;
}
}

Move Field 搬移字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 动机:在类之间移动状态与行为,是重构过程中必不可少的措施。
* 做法:
* (1)如果字段的访问级是pulic,使用Encaspsulate Field将它封装起来
* (2)编译,测试
* (3)在目标类中建立与源字段相同的字段,并同时建立相应的设值/取值函数。
* (4)编译目标类。
* (5)决定如何在源对象中引用目标对象
* (6)删除源字段。
* (7)编译,测试
*/
class Account {
private AccountType _type;
// private double _interestRate;
double interestForAmount_days(double amount, int days) {
return getInterestRate() * amount * days / 365;
}
private void setInterestRate(double arg) {
_type.setInterestRate(arg);
}
private double getInterestRate() {
return _type.getInterestRate();
}
}
class AccountType {
private double _interestRate;
void setInterestRate(double arg) {
_interestRate = arg;
}
double getInterestRate() {
return _interestRate;
}
}

Extract Class(提炼类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 动机:单类做一件事情
* 做法:
* (1)决定如何分解类所负的责任
* (2)建立一个新类,用以表现从旧类中分离出来的责任
* (3)建立“从旧类访问新类”的连接关系
* (4)对于你想搬移的每一个字段,运用Move Field搬移
* (5)每次搬移后,重新编译
* (6)将必要函数搬移至新类,从低层函数(被其他函数调用次数>调用其他函数)到高层函数
* (7)每次搬移后,重新编译
* (8)检查,精简每个类的接口。
* (9)决定是否公开新类。
*/
class Person {
String _name;
TelephoneNumber _officeTelephone;
String _officeAreaCode;
String _officeNumber;
// setter、getter
String getTelephoneNumber() {
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}
}
->
class TelephoneNumber {
String _areaCode;
String _number;
// setter、getter
String getTelephoneNumber() {
return ("(" + _areaCode + ")" + _number);
}
}

Inline Class(将类内联化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
String _name;
TelephoneNumber _officeTelephone;
String _officeAreaCode;
String _officeNumber;
// setter、getter
String getTelephoneNumber() {
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}
class TelephoneNumber {
String _areaCode;
String _number;
// setter、getter
String getTelephoneNumber() {
return ("(" + _areaCode + ")" + _number);
}
}
}

Hide Delegate(隐藏“委托关系”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 动机:"封装"即使不是对象的最关键特征,也是最关键特征之一。
* 做法:
* (1)对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数。
* (2)调整客户
* (3)每次调整后,编译并测试
* (4)如果将来不再有任何客户需要取用Delegate,即可移除服务类
* (5)编译、测试
*/
class Person {
Department _department;
// setter、getter
}
class Department {
String _chargeCode;
Person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
...
}
-> manager = john.getDepartment().getManager();
-> manager = john.getManager();

Remove Middle Man(移除中间人)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
Department _department;
// setter、getter
}
class Department {
String _chargeCode;
Person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
...
}

Indroduce Foreign Method(引入外加函数)

1
2
3
4
5
6
Date newStart = new Date(previous.getYear(), 
previousEnd.getMonth(), previousEnd.getDate() + 1);
->
private static Date nextDay(Date arg) {
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1)
}

Indroduce Local Extension(引入本地扩展)

1
2
3
4
5
6
7
8
9
/**
* 动机:很遗憾,类的作者无法预知未来,他们常常没能为你预先准备一些有用的函数。
* 做法:
*(1)建立一个扩展类,将它作为原始类的子类或包装类。
*(2)在扩展中加入转型构造函数。
*(3)在扩展类中加入新特性
*(4)根据需要,将原对象替换为扩展对象
*(5)将针对原始类定义的所有外加函数搬移到扩展中
*/