条件逻辑有可能十分复杂,因此本章提供一些重构手法,专门用来简化它下 们。其中一项核心重构就是Decompose Conditinal(238),可将一个复杂的条件逻辑分成若干小块。这项重构很重要,因为它使得"分支逻辑"和"操作细节"分离。


  本章的其余重构手法可用以处理另一些重要问题∶如果你发现代码中的多处测试有相同结果,应该实施Consolidate Conditional Expression(240);如果条件代码中有任何重复,可以运用Consolidate Duplicate Conditional Fragments(243)将重复成分去掉。


  如果程序开发者坚持"单一出口"原则,那么为让条件表达式也遵循这一原则,他往往会在其中加入控制标记。我并不特别在意"一个函数一个出口"的教条,所以我使用Replace Nested Conditional with Cuard Clauses(250)标示出那些特殊情况,并使用Remove Control Flag(245)去除那些讨厌的控制标记。


  较之于过程化程序而言,面向对象程序的条件表达式通常比较少,这是因为很多条件行为都被多态机制处理掉了。多态之所以更好,是因为调用者无需了解条件行为的细节,因此条件的扩展更为容易。所以面向对象程序中很少出现switc语句。一旦出现,就应该考虑运用Replace Conditional wih Polymorphism(255)将它替换为多态。


  多态还有一种十分有用但鲜为人知的用途∶通过nrouce NullObject(260)去除对于null值的检验。


9.1 Decompose Conditional(分解条件表达式)

你有一个复杂的条件(if-then-else)语句
从if.then.else三个段落中分别提炼出独立函数

if(date.before(SUMMER_START) || date.after(SUMMER_END))
	charge =quantity * _winterRate + _winterServiceCharge;
else charge =quantity * _summerRate;
	|
	V
if(notSummer(date))
	charge = winterCharge(quantity);
else charge = summerCharge(quantity);

动机

  程序之中,复杂的条件逻辑是最常导致复杂度上升的地点之一。你必须编写代码来检查不同的条件分支、根据不同的分支做不同的事,然后,你很快就会得到一个相当长的函数。大型函数自身就会使代码的可读性下降,而条件逻辑则会使代码更难阅读。在带有复杂条件逻辑的函数中,代码(包括检查条件分支的代码和真正实现功能的代码)会告诉你发生的事,但常常让你弄不清楚为什么会发生这样的事,这就说明代码的可读性的确大大降低了。


  和任何大块头代码一样,你可以将它分解为多个独立函数,根据每个小块代码的用途,为分解而得的新函数命名,并将原函数中对应的代码改为调用新建函数,从而更清楚地表达自己的意图。对于条件逻辑,将每个分支条件分解成新函数还可以给你带来更多好处∶可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因。

做法

  1. 将 if 段提炼出来,构成一个独立函数
  2. 将 then 段落和else段落都提炼出来,各自构成一个独立函数

9.2 Consolidate Conditional Expression(合并表达式)

你有一系列条件测试,都得到相同结果
将这些测试合并为一个条件表达式,并将这个条件表达式提炼为一个独立函数

double disabilityAmount()({
if (Leeniority<2) return 0;
if(monthsDisabled >12)return 0;
if(isPar:Time) return 0;
// compute the disability amount

	|
	V
double disabilityAmount(){
if(isNotE1igableForDisability())return 0;
// compute the disability amount

动机

  有时你会发现这样一串条件检查∶检查条件各不相同,最终行为却一致。如果发现这种情况,就应该使用"逻辑或"和"逻辑与"将它们合并为一个条件表达式。


  之所以要合并条件代码,有两个重要原因。首先,合并后的条件代码会告诉你"实际上只有一次条件检查,只不过有多个并列条件需要检查而已",从而使这一次检查的用意更清晰。当然,合并前和合并后的代码有着相同的效果,但原先代码传达出的信息却是"这里有一些各自独立的条件测试,它们只是恰好同时发生"。其次,这项重构往往可以为你使用Extract Method(110))做好准备。将检查条件提炼成一个独立函数对于厘清代码意义非常有用,因为它把描述"做什么"的语句换成了"为什么这样做"。


  条件语句的合并理由也同时指出了不要合并的理由∶如果你认为这些检查的确彼此独立,的确不应该被视为同一次检查,那么就不要使用本项重构。因为在这种情况下,你的代码已经清楚表达出自己的意义。

做法

  1. 确定这些条件语句都没有副作用
  2. 使用适当的逻辑操作符,将一些列相关条件表达式合并为一个
  3. 编译,测试
  4. 对合并后的条件表达式实施Extract Method(110).

使用逻辑与

使用逻辑或

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

在条件表达式上的每个分支上有着相同的一段代码.
将这段重复代码搬移到条件表达式之外.

9.4 Remove Control Flag(移除控制标记)

在一些列布尔表达式中,某个变量带有'控制标记'(control flag)的作用
以break 语句或 return 语句取代控制标记

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

函数中的条件逻辑使人难以看清正常的执行路径
使用位于距表现所有特殊情况

动机

  根据我的经验,条件表达式通常有两种表现形式。第一种形式是∶所有分支都属于正常行为。第二种形式则是∶条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。

将条件反转

9.6 Replace Conditional with Polymorphism(以多态取代条件表达式)

你手上有个条件表达式,它根据对象类型的不同而选择不同的行为.
将这个条件表达式的每个分支放进一个子类的覆写函数中,然后将原始函数声明为抽象函数.

动机

  在面向对象术语中,听上去最高贵的词非"多态"莫属。多态最根本的好处就是∶如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写明显的条件表达式。


  正因为有了多态,所以你会发现∶"类型码的switch语句"以及"基于类型名称的if-then-else语句"在面向对象程序中很少出现。


做法

  1. 如果要处理的条件表达式是一个更大函数中的一部分,首先对条件表达式进行分析,然后使用Extract Method(110)将他提炼到一个独立函数去.
  2. 如果有必要,使用 Move Method 将条件表达式放置到继承结构的顶端
  3. 任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数,将与该子类相关的条件表达式分支复制到新建函数中,并对它进行适当调整
  4. 编译,测试.
  5. 在超类中删掉条件表达式内被复制了的分支
  6. 编译,测试
  7. 针对条件表达式的每个分支,重复上述过程,知道所有分支都被移到子类内的函数为止
  8. 将超类之中容纳条件表达式的函数声明为抽象函数

9.7 Introduce Null Object(引入 Null 对象)

你需要再三检查某对象是否为null
将null值替换为null对象.

做法

  1. 为源类建立一个子类,使其行为就像是源类的null版本.在源类和Null子类中都加上isNull函数,前者的isNull应该返回false,后者的,isNull应该返回true.
  2. 编译
  3. 找出所有 所求源对象却获得一个null的地方 修改这些地方,使他们改而获得一个空对象.
  4. 找出虽有 '将源对象与null做比较' 的地方.修改这些地方,是他们调用isNull函数.
  5. 编译,测试
  6. 找出这样的程序点:如果对象不是null,做A动作,否则做B动作.
  7. 对于每一个上述地点,在null类中覆写A动作,使其行为和B动作相同.
  8. 使用上述被覆写的动作,然后删除'对象是否等于null'的条件测试,编译并测试

9.8 Introduce Assertion(引入断言)

某一段代码需要对程序状态做出某种假设
以断言明确表现这种假设

做法

如果你发现代码假设某个条件始终为真,就加入一个断言明确说明这种情况