《重构》学习笔记(07)-- 简化条件表达式

条件逻辑有可能十分复杂,复杂的条件逻辑可能让复杂度快速上升,并有可能导致代码难以理解。因此,需要一些手段,来简化它们。

Decompose Conditional(分解条件表达式)

带有复杂逻辑的函数中,代码会告诉你发生的事情。但是十分冗长的条件表达,往往让后来者摸不着头脑。这种情况下,可以将它分解为独立的函数,并且为新函数命名(命名可以起到自注释的作用)。在原函数中调用新建函数,从而更清楚地表达自己的意图。举个例子

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

可以重构为

if(notSummer(date))
    charge=winterCharge(quantity);
else
    charge=summerCharge(quantity);

上面的例子就是将date.before(SUMMER_START) || date.after(SUMMER_END) 替代为notSummer(date),这种情况就一目了然。概括下这种重构方法的步骤:

  • 将if段落提炼出来,构成一个独立的函数。
  • 将then段落和else段落都提炼出来,各自构成一个独立的函数。
  • 如果发现嵌套的条件逻辑,应该先看看是否可以使用以卫语句取代嵌套条件式。如果不行才开始分解其中的每个条件。

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

如果有一串条件检查,如果检查条件各不相同,但是最终导致的结果却一致。那么,就使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。举例:

 double disabilityAmount() {
        if (_seniority < 2) {
            return 0;
        }
        if (_monthsDisabled > 12) {
            return 0;
        }
        if (_isPartTime) {
            return 0;
        }
        // compute the disability amount
        // ...
    }

由于都是返回0,因此可以将条件表达式合并

    double disabilityAmount() {
        if (isNotEligibleForDisability()) {
            return 0;
        }
        // compute the disability amount
        // ...
    }

    boolean isNotEligibleForDisability() {
        return ((_seniority < 2) || (_monthsDisabled > 12) || (_isPartTime));
    }

需要注意的是,要分清返回0是系统的行为是否在逻辑上一致,还是只是同时返回相同数值,在未来条件变动时还会有所改动。总结这种重构的做法为:

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

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

你有时候会发现,条件语句不同分支执行了相同的片段。如果是这样,你就需要将这段代码搬移到条件表达式外面。举例子:

    if (isSpecialDeal()) {
        total = price * 0.95;
       send();
    } else {
        total = price * 0.98;
        send();
   }

重构后,可以将send()提取出来

    if (isSpecialDeal()) {
        total = price * 0.95;
    } else {
        total = price * 0.98;
    }
    send();

同样的,如果try catch中都有同样的代码,就可以放在final中去执行。总结这种重构的做法为:

  • 鉴别出”执行方式不随条件变化而变化”的代码。
  • 如果这些共通代码位于条件表达式起始处,就将它移到条件表达式之前。
  • 如果这些共通代码位于条件表达式尾端,就将它移到条件表达式之后。
  • 如果这些共通代码位于条件表达式中段,就需要观察共通代码之前或之后的代码是否改变了什么东西,如果的确有所改变,应该首先将共通代码向前或向后移动,移至条件表达式的起始处或尾端,再以前面所受的办法来处理。
  • 如果共通代码不止一条语句,应该先使用Extract Method(提炼函数)将共通代码提炼到一个独立函数中,再以前面所说的办法来处理。

Remove Control Flag(移除控制标记)

在一系列的布尔表达式中,某个变量带有“控制标记”的作用,那么用break或者return语句取代控制标记。这样的控制标记带来的麻烦通常超过带来的便利。结构化编程告诉我们一个原则:每个子程序都只能有一个入口和一个出口,但是“单一出口”原则通常让我们在代码中加入许多的控制标记,这样就大大降低了表达式的可读性。使用break或者return可以让程序条理更加清晰。举例:

function checkSecurity(peoples) {
  String found = '';
  for(let i = 0; i < peoples.length; i++) {
    if(!found) {
      if(peoples[i] === 'Don' || peoples[i] === 'John') {
        sendAlert();
        found = peoples[i];
      }
    }
  }
  someLaterCode(found);
}

重构为

function checkSecurity(peoples) {
  String found = foundMiscreant(peoples);
  someLaterCode(found);
}

function foundMiscreant(peoples) {
  for(let i = 0; i < peoples.length; i++) {
    if(peoples[i] === 'Don' || peoples[i] === 'John') {
      sendAlert();
      return peoples[i];
    }
  }
  return '';
}

这种重构的一般做法为:
使用extract method,整段逻辑提炼到一个独立函数中

  • 找出跳出这段逻辑的控制标记值
  • 找出对标记变量赋值的语句,代以恰当的return语句.
  • 替换完成后,编译并测试。

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

如果if else分支都属于正常行为,那么建议正常使用if else去表达。但是如果else分支中的情况极其罕见,那么建议采用卫语句进行表现。

function getPayAmount () {
  let result
  if (isDead) result = deadAmount()
  else {
    if (isSeparated) result = separatedAmount()
    else {
      if (isRetired) result = retiredAmount()
      else result = normalPayAmount()
    }
  }
  return result
}

可以重构为:

function getPayAmount () {
  if (isDead) return deadAmount()
  if (isSeparated) return separatedAmount()
  if (isRetired) return retiredAmount()
  return normalPayAmount()
}

这样代码条理更加清晰,可读性更高。这种重构方法的做法为:

  • 对于每个检查,放进一个卫语句(卫语句要不就从函数返回,要不就抛出一个异常)。
  • 编译测试。

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

“多态”是面向对象的精髓所在,多态可以避免为不同类型代入不同行为。举个例子:

var makeSound = function(animal) {
    if(animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
var Duck = function(){}
var Chiken = function() {};
makeSound(new Chicken());
makeSound(new Duck());

重构后

var makeSound = function(animal) {
    animal.sound();
}

var Duck = function(){}
Duck.prototype.sound = function() {
    console.log('嘎嘎嘎')
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
    console.log('咯咯咯')
}

makeSound(new Chicken());
makeSound(new Duck());

多态背后的思想是将”做什么“和”谁去做以及怎样去做分开“。通常的做法是:

  • 如果要处理的条件表达式是一个更大函数中的一部分。首先,对条件表达式进行分析,然后使用extract method将其提炼到一个独立函数去。
  • 如果由必要,使用move method、将条件表达式放置到继承结构的顶端。
  • 任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新建函数中,并对其进行适当调整。
    可能需要将超类中某些private字段声明为protected。
  • 超类中删掉条件表达式内被复制了的分支。
  • 将超类中容纳条件表达式的函数声明为抽象函数。

Introduce Null Object(引入Null对象)

这种重构方法在前端中不知如何使用,暂忽略。

Introduce Assertion(引入断言)

这种情况在程序中一般使用是非空判断,为了减少圈复杂度。我建议进行直接的空判断,而不是非空判断。
举例

if(response){
   //do something
}

可以重构为

if(!response){
    return;
}
//do something

本章重构方法比较简单,总的原则就是让代码清晰可懂,减少复杂度。这样的代码维护和交接都是一件非常愉悦的事情。

你可能感兴趣的:(《重构》学习笔记(07)-- 简化条件表达式)