条件逻辑有可能十分复杂,因此这一章提供的重构手法专门用来简化它们。其中一项核心重构就是分解条件表达式,可以将一个复杂的条件逻辑分解成若干个小块,使得“分支逻辑”和“操作细节”分离。这一章的其他重构手法处理另一些重要的问题,比如代码中的多处测试有相同的结果应该实施“合并条件表达式”,如果代码中有任何重复可以运用“合并重复的条件片段”将重复部分去掉在面向对象的程序中如果出现switch语句,应该运用“以多态取代条件表达式将它替换”,多态还有一种鲜为人知的用途就是通过“引入null对象”去除对null值的检验。
9.1、分解条件表达式
什么时候用?
有一个复杂的条件(if-then-else)语句
怎么用?
从If、then、else三个段落中分别提炼出独立函数
具体步骤:
1、将if段落提炼出来,构成一个独立的函数
2、将then段落和else段落都提炼出来,各自构成一个独立的函数(如果发现嵌套的条件逻辑,先观察是否可以使用“以卫语句取代嵌套条件表达式”(此手法是下面的9.5),如果不行才开始分解其中的每个条件)
9.2、合并条件条件表达式
什么时候用?
有一列条件测试,都得到相同的结果
怎么用?
将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立的函数
具体步骤:
1、确定这些条件语句都没有副作用(即最终行为一致)
2、使用适当的逻辑操作符,将一系列相关条件表达式合并为一个。然后编译测试
3、对合并后的条件表达式实施提炼方法
9.3、合并重复的条件片段
什么时候用?
在条件表达式的每一个分支上有着相同的一段代码
怎么用?
将这些代码搬至到条件表达式之外
具体步骤:
1、鉴别出“执行方式不随条件变化而变化”的代码
2、如果这些共通代码位于条件表达式起始处,就将它移到条件表达式之前
3、如果这些共通代码位于条件表达式尾端,就将它移到条件表达式之后
4、如果这些共通代码位于条件表达式中段,就需要观察共通代码之前或之后的代码是否改变了什么东西。如果有改变应该将共通代码向前或向后移动,移至条件表达式的起始处或者尾端,再以前面的步骤1、2、3处理。
5、如果共通代码不止一条语句,应该首先使用“提炼函数”将共通提炼到一个独立的函数中,再用前面的4、1、2、3步骤处理
9.4、移除控制标志
什么时候用?
在一系列布尔表达式中,某个变量带有“控制标记”的作用
怎么用?
以break语句或return语句取代控制标记
具体步骤:
对控制标记的处理,最显而易见的办法就是使用Java提供的break语句或continue语句。
1、找出跳出这段逻辑的控制标记值
2、找出对标记变量赋值的语句,代以恰当的break语句或continue语句
3、每次替换后,编译并测试
在未能提供break和continue语句的编程语言中,可以使用下述办法(即使在支持break和continue语句的编程语言中,通常也优先考虑下面的方案)
4、运用提炼函数,将整段逻辑提炼到一个独立函数中
5、找出跳出这段逻辑的控制标记值
6、找出对标记变量赋值的语句,代以恰当的return语句
7、每次替换后,编译并测试
9.5、以卫语句取代嵌套条件表达式
什么时候用?
函数中的条件逻辑使人难以看清正常的执行路径
怎么用?
使用卫语句表现所有特殊情况
具体步骤:
卫语句:如果两条分支都是正常行为,就应该使用形如if…else…的条件表达式,如果某个条件极其罕见就应该单独检查条件,并在该条件为真时立刻从函数中返回。
1、对于每个检查,放进一个卫语句(卫语句要不就从函数中返回,要不就抛出一个异常)
2、每次将条件检查替换成卫语句后,编译并测试(如果所有卫语句都导致相同的结果,使用合并条件表达式)
9.6、以多态取代条件表达式
什么时候用?
有个条件表达式,根据对象类型的不同而选择不同的行为。
怎么用?
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
具体步骤:
1、如果要处理的条件表达式是一个更大函数中的一部分,首先对条件表达式进行分析,然后使用提炼函数将它提炼到一个独立函数中
2、如果有必要,使用搬移函数将条件表达式放着到继承结构的顶端
3、任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将于该子类相关的条件表达式分支复制到新建函数中,并对它进行适当调整。(为了顺利进行这一步骤,可能需要将超类中的某些private字段声明为protected),编译并进行测试
4、在超类中删掉条件表达式内被复制了的分支然后编译并测试
5、针对条件表达式的每个分支,重复上述过程,直到所有分支都被移到子类内的函数为止
6、将超类之中容纳条件表达式的函数声明为抽象函数
9.7、引入Null对象
什么时候用?
需要再三检查某对象是否为null
怎么用?
将null值转换为null对象
具体步骤:
1、为源类建立一个子类,使其行为就像是源类的null版本。在源类和null子类中都加上isNull()函数,前者的isNull()应该返回false,后者的isNull()应该返回true。然后编译
2、找出所有“索求源对象却获得一个null”的地方,修改这些地方,使他们改而获取一个空对象。
3、找出所有“将源对象与null比较”的地方。修改这些地方,使它们调用isNull()函数。编译并测试。
4、找出这样的程序点:如果对象不是null,做A动作,否则做B动作
5、对于每一个上述点:在null类中覆写A动作,使其行为和B动作相同
6、使用上述被覆写的动作,然后删除“对象是否等于null”的条件测试。编译并测试。
9.8、引入断言
什么时候用?
某一段代码需要对程序状态做出某种假设
怎么用?
以断言明确表现这种假设
具体步骤:
如果程序员不犯错,断言就应该不会对系统运行造成任何影响,所以加入断言永远不会影响程序的行为。
如果发现代码假设某个条件始终为真,就加入一个断言明确说明这种情况(可以新建一个assert类,用于处理各种情况下的断言)