1. Decompose Conditional (分解条件式)
解释:
"复杂的条件逻辑" 是导致复杂性上升最常见的地方, "条件表达式中堆积的计算过程", "条件式表达得不简洁"等等都是造成复杂的原因. Decompose Conditional 用于将这些复杂的元素从条件表达式中分离出去, 仅在条件表达式中调用简洁的函数.
这样做带来的直接好处是减少重复, 而且代码的可读性提高了.
冲动前:
0 |
if (date.After(SUMMER_START) && date.Before(SUMMER_END)) |
1 |
charge = days * _price + _summerServiceTip; |
2 |
else |
3 |
charge = days * _price; |
冲动后:
0 |
if (date.IsSummer()) |
1 |
charge = SummerCharge(days); |
2 |
else |
3 |
charge = WinterCharge(days); |
2. Consolidate Conditional Expression (合并条件式)
解释:
如果代码中有一连串的 if 检查语句, 检查语句中的条件不相同, 但最终的行为都是一样的. 对于这样的情况, 应该使用 "逻辑与" 和 "逻辑或" 将它们合并成一个条件表达式, 如果嫌这个合并条件后的表达式太罗嗦, 你还可以将这个表达式提取成一个函数.
冲动前:
0 |
if (computer.CPU != "T6670" ) |
1 |
return false ; |
2 |
if (computer.RAM != "1.00GB" ) |
3 |
return false ; |
4 |
if (computer.SytemType != "32-bit Operating System" ) |
5 |
return false ; |
6 |
//other compution |
冲动后:
0 |
if ((computer.CPU != "T6670" ) || (computer.RAM != "1.00GB" ) || (computer.SytemType != "32-bit Operating System" )) |
1 |
return false ; |
2 |
//other compution |
你还可以将 if 里长长的条件表达式提取成一个方法, 如 bool IsStandard(Computer computer), 这样在原来的 if 语句中只需要调用这个方法即可
3. Consolidate Duplicate Conditional Fragments (合并重复的条件片段)
解释:
如果条件式的每个分支上都有同样一段代码, 如果这段代码对条件分支在执行这段代码后执行后面的代码没有影响, 请将这段代码移到条件式的外面.
冲动前:
00 |
if (date.IsSummer()) |
01 |
{ |
02 |
charge = days * _price + _summerServiceTip; |
03 |
PrintDetail(); |
04 |
} |
05 |
else |
06 |
{ |
07 |
charge = days * _price; |
08 |
PrintDetail(); |
09 |
} |
10 |
//other compution |
冲动后:
0 |
charge = days * _price; |
1 |
2 |
if (date.IsSummer()) |
3 |
charge += _summerServiceTip; |
4 |
5 |
PrintDetail(); |
6 |
//other compution |
4. Remove Control Flag (移除控制标志)
解释:
很多代码里执行一个 for 或者 while 循环用于寻找一个数组里特点的元素, 很多时候在循环开头就执行控制标志的检查, 满足检查条件就继续执行循环查找元素. 如果这一次查找到了想要的元素, 就更改控制标志的值, 让它下次被检查出不符合条件, 从而循环结束.
这并不是一个很好的做法, 使用诸如 break, continue, return 语句会让你的代码意图更加直接, 更加明显.
冲动前:
00 |
for ( int i = 0; i < suspects.Length; i++) |
01 |
{ |
02 |
if (!found) |
03 |
{ |
04 |
if (suspects[i].Name == guessName) |
05 |
{ |
06 |
sendAlert(); |
07 |
found = true ; |
08 |
} |
09 |
} |
10 |
11 |
} |
冲动后:
0 |
for ( int i = 0; i < suspects.Length; i++) |
1 |
{ |
2 |
if (suspects[i].Name == guessName) |
3 |
{ |
4 |
sendAlert(); |
5 |
break ; |
6 |
} |
7 |
} |
5. Replace Nested Conditional with Guard Clauses (以卫语句取代嵌套条件式)
解释:
许多程序员觉得函数应该只有一个出口 (return), 结果导致函数中的条件逻辑 (Conditional Logic) 本来完全可以终止下面的代码继续执行 (因为没有必要), 结果却只在函数最后 return, 使人难以看清程序的执行路径.
Replace Nested Conditional with Guard Clauses 用来解决这个问题, 它能带给代码可读性的提高, 还有性能上一点点的优化.
冲动前:
00 |
double charge; |
01 |
02 |
if (IsSummer(date)) |
03 |
{ |
04 |
//... |
05 |
SummerCharge(charge); |
06 |
} |
07 |
else |
08 |
{ |
09 |
//... |
10 |
WinterCharge(charge); |
11 |
} |
12 |
13 |
return charge; |
冲动后:
00 |
double charge; |
01 |
02 |
if (IsSummer(date)) |
03 |
{ |
04 |
//... |
05 |
SummerCharge(charge); |
06 |
return charge; |
07 |
} |
08 |
else |
09 |
{ |
10 |
//... |
11 |
WinterCharge(charge); |
12 |
return charge; |
13 |
} |
6. Replace Conditional with Polymorphism (以多态取代条件)
解释:
这条重构手法常常用于消除函数中长长的 switch-case 语句. 虽然写一个个的子类比较繁琐, 但随着项目的进行, 好处会体现出来的.
冲动前:
00 |
public double Salary(Employee employee) |
01 |
{ |
02 |
switch (employee.Type): |
03 |
{ |
04 |
case Employee.Engineer |
05 |
{ |
06 |
//... |
07 |
} |
08 |
case Employee.Salesman: |
09 |
{ |
10 |
//... |
11 |
} |
12 |
//... |
13 |
default : |
14 |
{ |
15 |
//... |
16 |
} |
17 |
} |
18 |
} |
冲动后:
00 |
public abstract double Salary(Employee employee); |
01 |
02 |
class Engineer : Employee |
03 |
{ |
04 |
public override double Salary(Employee employee) |
05 |
{ |
06 |
//... |
07 |
} |
08 |
} |
09 |
class Salesman : Employee |
10 |
{ |
11 |
public override double Salary(Employee employee) |
12 |
{ |
13 |
//... |
14 |
} |
15 |
} |
7. Introduce Null Object (引入 Null 对象)
解释:
如果代码中出现很多判断某值是不是为 null , 诸如 if (XXX != null) {//...} else {//...} 这样的情况, 可以考虑使用 Introduce Null Object 重构手段. 这个手段其实并不难以理解, 可以简单理解成为某一个物件在为空状态下设定默认的值域和行为, 可以建立一个子类, 继承父类中需要对 "为空" 情况下做出响应的虚函数或者值域. 它是 Null Object 设计模式里的最基础最常见的手段.
8. Introduce Assertion (引入断言)
解释:
严格上说, 引入断言并不是为了简化条件表达式, 它主要是为了代替条件表达式上面的注释, 通常这样的注释用来解释下面的条件表达式是基于什么样的假设之上的. 通常经过一系列的测试, 发现所写的断言在任何情况下都是正确的, 在系统发布的时候可以把它们全部删除掉.
在 C# 中引入断言使用 Debug.Assert() 方法, 如果一切假设都是正确的, 则代码会顺利的进行.
冲动前:
0 |
//index should between 0 to 10 |
1 |
return (customers[index] == "James" ) ? true : false ; |
冲动后:
0 |
Debug.Assert((index>=0)&&(index <= 10), "Error" , "index should between 0 to 10" ); |
1 |
return (customers[index] == "James" ) ? true : false ; |
如果断言错误, 在运行的时候会有一个消息框给予错误信息的提示.