《重构-改善既有代码的设计》系列读书笔记(五、重构条件表达式)

简化条件表达式

Decompose Conditional(分解条件表达式)

将复杂的条件判断分解为一组简单的函数

动机
  • 复杂的条件逻辑是最常导致代码复杂度上升的地点之一。
做法
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
// before
if (date.before(SUMMER_START) || date.after(SUMMER_END)){
charge = quantity * _winterRate + _winterServiceCharge;
}
else {
charge = quantity * _summerRate;
}
// after
if (notSummer(date)) {
charge = winterCharge(quantity);
}
else {
charge = summerCharge(quantity);
}
private boolean notSummer(Date date) {
return date.before(SUMMER_START) || date.after(SUMMER_END);
}
private double summerCharge(int quantity) {
return quantity * _summerRate;
}
private double winterCharge(int quantity) {
return quantity * _winterRate + _winterServiceCharge;
}

Consolidate Conditional Expression(合并条件表达式)

一系列条件测试都得到相同的结果

动机
  • 如果有一串条件各不相同的判断,最终结果却一致,则应该将这些条件合并为一个表达式。
  • 将检查条件提炼为一个独立函数对于厘清代码意义非常有用,因为它将“做什么”的语句换成“为什么这么做”。
做法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// before
double disabilityAmount () {
if (_seniority < 2) return 0;
if (_monthsDisabled > 12) return 0;
if (_isPartTime) return 0;
...
}
//after
double disabilityAmount () {
if (isNotEligibleForDisability()) return 0;
...
}
boolean isNotEligibleForDisability() {
return ((_seniority < 2) || (_monthsDisabled > 12) || (_isPartTime));
}

Consolidate Duplicate Conditional Fragments(合并重复的条件片段)

在条件表达式的每个分支上有着相同的一段代码

动机
  • 消除重复代码
做法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// before
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
// after
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();

Remove Control Flag (移除控制标记)

在一系列布尔表达式中,某个变量带有控制标记的作用

动机
  • 控制标记的频繁使用大大降低了表达式的可读性
  • 单一入口原则没有问题,但是单一出口原则可能需要引用讨厌的控制标记
  • 使用break,continue和return替换控制标记,会使条件语句清晰很多
做法

Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)

函数中的条件逻辑使人难以看清正常的执行路径

动机
  • 如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立即从函数中返回。这种单独检查常常被称为”卫语句“
  • 卫语句的精髓就是:给某一条分支以特别的重视。
做法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// before
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if(_isSeparated) result = separatedAmount();
else {
if(_isRetried) result = retriedAmount();
else result = normalPayAmount();
}
}
return result;
}
// after
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetried) return retriedAmount();
return normalPayAmount();
}

以多态取代条件表达式

你手上有个条件表达式,它根据对象类型的不同而选择不同的行为

动机
  • 在面向对象术语中,听上去最高贵的词非”多态“莫属了。
  • 多态最根本的好处是,如果你需要根据对象的不同而采取不同的行为,多态使你不必编写明显的条件表达式。
做法
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
45
46
47
48
49
50
51
// before
class Employee...
int payAmount() {
switch (getType()) {
case EmployeeType.ENGINEER:
return _monthlySalary;
case EmployeeType.SALESMAN:
return _monthlySalary + _commission;
case EmployeeType.MANAGER:
return _monthlySalary + _bonus;
default:
throw new RuntimeExcepetion("Incorrect Employee");
}
}
int getType() {
return _type.getTypeCode();
}
private EmployeeType _type;
abstract class EmployeeType...
abstract int getTypeCode();
class Engineer extends EmployeeType...
int getTypeCode() {
return Employee.ENGINEER;
}
// after
class Employee...
int payAmount() {
return _type.payAmount(this);
}
class EmployeeType...
abstract int payAmount(Employee emp);
class Engineer extends EmployeeType...
int payAmount(Employee emp) {
return emp.getMonthSalary();
}
class Salesman extends EmployeeType...
int payAmount(Employee emp) {
return emp.getMonthSalary() + emp.getCommission();
}
class Manager extends EmployeeType...
int payAmount(Employee emp) {
return emp.getMonthSalary() + emp.bouns();
}

Introduce Null Object(引入Null对象)

你需要再三检查某对象是否为null

动机
  • 大量的判空逻辑会引入大量的重复代码,空对象的运用,可以摆脱大量过程化的重复代码
  • 空对象不会破坏原有系统,空对象一定是常量,可以使用单例创建空对象。
做法
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// before
class Site...
Customer getCustomer() {
return _customer;
}
Customer _customer;
class Customer...
public String getName() {...}
public BillingPlan getPlan() {...}
public PaymentHistory getHistory() {...}
class PaymentHistory...
int getWeeksDelinquentInlastYear()
///
Customer customer = site.getCustomer();
BillingPlan plan;
if (customer == null) plan = BillingPlan.basic()
else plan = customer.getPlan();
String customerName;
if (customer == null) customerName = "occupant"
else customerName = customer.getName();
int weeksDelinquent;
if (customer == null) weeksDelinquent = 0
else weeksDelinquent = customer.getHistory().getWeeksDelinquentInlastYear();
//step 1 引入空对象
class Site...
Customer getCustomer() {
return (_customer == null) ? Customer.newNull() : _customer;
}
Customer _customer;
class NullCustomer extends Customer {
public boolean isNull() {
return true;
}
}
class Customer...
public boolean isNull() {
return false;
}
static Customer newNull() {
return new NullCustomer();
}
///
Customer customer = site.getCustomer();
BillingPlan plan;
if (customer.isNull()) plan = BillingPlan.basic()
else plan = customer.getPlan();
String customerName;
if (customer.isNull()) customerName = "occupant"
else customerName = customer.getName();
int weeksDelinquent;
if (customer.isNull()) weeksDelinquent = 0
else weeksDelinquent = customer.getHistory().getWeeksDelinquentInlastYear();
//step 2 引入空字段
class NullPaymentHistory extends PaymentHistory {
static PaymentHistory newNull() {
return new NullPaymentHistory();
}
public getWeeksDelinquentInlastYear() {
return 0;
}
}
class NullCustomer extends Customer {
public String getName(){
return "occupant"
}
public BillingPlan getPlan() {
return BillingPlan.basic();
}
public PaymentHistory getHistory() {
return NullPaymentHistory.newNull();
}
}
Customer customer = site.getCustomer();
BillingPlan plan = customer.getPlan();
String customerName = customer.getName();
int weeksDelinquent = customer.getHistory().getWeeksDelinquentInlastYear();

Introduce Assertion(引入断言)

某一段代码需要对程序状态做出某种假设

动机
  • 代码依赖某个条件为真才能运行,如果不为真,则说明程序出现了错误。
  • 断言可以作为交流与协调的辅助,帮助理解程序所做的假设和定位bug。