重构心法修炼第三层:简化条件表达式
9.1 分解条件表达式
如果有一个非常复杂的条件(if--then--else),可以将 if then else 三个段落中分别提炼出独立的函数。
if 语句若有多个条件,按其意图抽取函数,并按意图命名,则代码可读性将大大提升。 then 与 else 段落的代码也一样可抽取。提炼出来的函数可读性也更高一些,它看上去就像一段注释那样清楚明白。
if( isSummer() ) return summerPrice() ;
else return winterPrice();
9.2 合并条件表达式
多个条件返回同样的检查结果,则可将这些条件语句合并为一个条件表达式。
用逻辑运算符进行连接,并可将此条件表达式抽取为一个方法。
一系列卫语句都得到相同的结果,可将这些卫语句合并为一个条件表达式,并将这个表达式提炼为一个函数。
if( a == 0 ) return 0 ;
if( b == 0 ) return 0 ;
if( c == 0) return 0 ;
doSomething();
改为
if( isZero() ) return 0 ;
doSomething();
boolean isZero() { return a == 0 || b == 0 || c == 0 }
逻辑与可以使用三目运算符来合并分支。
if( a > 0) {
if ( b > 0 )
return 1 ;
return 0.5 ;
可重构为
return a > 0 && b > 0 ? 1 : 0.5 ;
9.3 合并重复的条件片段
各个分支中若有相同功能的代码,则将其抽取并搬移到个分支的外面。
都有的代码说明不随分支变化而变化,就不应该出现在分支中。
重复代码在分支开始,将其搬到条件表达式之前。同理,重复代码在分支之后,可将其搬到条件表达式之后。
如果重复代码在条件表达式中间,那么可以尝试将其移动到条件表达式的起始处或者末尾处,再进行搬移出去。
if ( isA () ) {funA() ; funC();}
if (isB()) {funB() ; funC();}
可重构为
if ( isA () ){ funA() ;}
if (isB()) { funB() ; }
funC();
9.4 移除控制标记
尽量不要使用控制标记,在循环中使用break或者continue结束循环。
如果不在循环中,找到程序的结束点,比如我的目标是某个IF条件,那么就在这个IF条件成立时,使用return结束当前的程序。
9.5 使用卫语句
将所有非正常的逻辑提炼到条件表达式之前。这样的话,可以过滤一些罕见的情况,并突出显示我们的正常逻辑。
if( _isDead ) return deadAmount();
if( _isSeparted) return separatedAmount();
if( _isRetired) return retiredAmount();
return normalPayAmount();
其实就是把一些比较罕见的或者说不正常的情况提炼到函数起始处,对这些非正常的情况进行检查。
如果不对这些情况进行检查,大量采用 if else 语句的话,很可能会让人难以看清正常的执行路径。
使用卫语句的精髓就是,给某条分支特别的重视。如果使用 if - else 语句,就表明对 if 分支 与 else 分支的重视程度是相同的。如果两条分支都是正常的话,那么就使用 if - else 语句。如果某些情况十分罕见,就应该使用卫语句进行单独的检查,并在条件为真的时候,立刻从函数中返回。
还有一种重构手法是,将条件反转。
正常逻辑的执行条件是 A > 0 B>0 C>0 。那么可以如此重构。
if ( A < = 0 ) return ;
if ( B < = 0 ) return ;
if ( C < = 0 ) return ;
doSomething();
9.6 使用多态取代条件表达式
如果有一个条件表达式,它根据对象类型的不同而选择不同的行为。
将这个条件表达式的每个分支放进一个子类内的重写函数中,然后将原函数申明为抽象函数。
当前对象持有抽象状态类型引用,将不同的状态设计为子类。为当前对象设置好相应的状态后,可将当前对象的相关功能方法委托给状态子类对象去做。例如,员工的职位类型有非常多种,那么就创建一个员工职位类型的抽象对象,并让员工类持有这个抽象职位类型的引用,再为这个抽象职位类型创建设值方法。接着,为抽象职位类型创建子类,并实现相关计算薪资的办法。最后,在员工对象中,将计算薪资的方法委托给接收到的职位类型状态对象。
经典代码参考:重构——以多态取代条件表达式(状态模式)
public class Employ {
/**
* 核心:当前对象持有抽象状态类型对象
* 即员工对象持有一个抽象的员工状态类型对象引用
* 设计一个取值方法,即setEmployType方法获取 抽象员工状态 的子类对象
*/
private EmployType employType;
public int getBaseWage() {
return 2000;
}
public int getBonus() {
return 500;
}
private void setEmployType(EmployType employType) {
this.employType = employType;
}
/**
* 将员工计算工资的方法 委托给 员工状态类型
* 方法参数为 this
* 是为了让 员工状态类型的子类对象
* 可以使用当前员工对象的 getBaseWage()与 getBonus方法
*
* @return
*/
public int caculateMoney() {
return employType.caculateMoney(this);
}
}