【前端】重构,有品位的代码 07 ── 简化条件逻辑

769 阅读6分钟

写在前面

条件逻辑越多越庞大,程序的复杂度也将提升,重构条件逻辑会变得更易理解。在对条件逻辑进行拆分和合并,可以使得条件逻辑得到简化,代码逻辑也就变得更加清晰易懂。

前情回顾:

简化条件逻辑

常见的简化条件逻辑的方式有:

  • 分解条件表达式
  • 合并条件表达式
  • 以卫语句取代嵌套条件表达式
  • 以多态取代条件表达式
  • 引入特例
  • 引入断言

1. 分解条件表达式

在程序编写中,复杂的条件逻辑会导致算法复杂度上升,因为会根据不同的条件分支做出不同的事情,这样便得到复杂冗长的函数。正如你所知道的,函数越大越长,代码的可读性就越低,在理解和阅读就愈发困难。

在前面几篇文章中,对于大块头函数可以根据功能意图分解成几个小型的函数,因此可以根据逻辑分支的意图将条件逻辑函数分解。

原始代码:

if(!have.before(plan.summerStart) && !plan.after(plan.summerEnd)){
  charge = quantity * plan.summerRate;
}else{
  charge = quantity * plan.regularRate + plan.regularServiceCharge;
}

重构代码:

charge = summer() ? summerCharge() : regularCharge();

function summer(){
  return !have.before(plan.summerStart()) && !have.aftet(plan.summerEnd());
}

function summerCharge(){
  return quantity * plan.summerRate;
}

function regularCharge(){
  return quantity * plan.regularRate + plan.regularServiceCharge;
}

2. 合并条件表达式

在进行代码的条件检查,检查条件虽有不同,但是处理事件的行为却是一致的。与其使用条件逻辑处理事件,不如使用逻辑『或』和『与』将其合并成条件表达式,分解冗长的函数代码。使用条件表达式可以让代码阅读更清晰,提炼函数可以将条件函数的代码逻辑提炼成独立函数,可理清代码意义。

具体的,进行合并条件表达式之时,必先确定其是否具有副作用,将查询函数和修改函数分离处理。使用适当的逻辑运算符,将两个逻辑条件表达式合并成一个。

原始代码:

if(employee.seniority < 2) return 0;
if(employee.months > 12) return 0;
if(employee.time) return 0;

重构代码:

function func(){
  return (employee.seniority < 2) || (employee.months > 12) || employee.time)
}

if(func()) return 0;

3. 以卫语句取代嵌套条件表达式

在条件表达式中通常有两个风格:两个条件分支都属于正常行为,可以使用if...else...;只有一个条件分支时正常行为,另一个分支则是异常行为,即出现某个罕见条件应该单独检查(此为『卫语句』),条件为真时立刻从函数中返回。

所谓『卫语句』取代嵌套条件表达式,就是给其中的某分支给予特别的重视。其实就是对条件语句不是一视同仁,而是倚重其中最重要的分支语句,让读者阅读代码时能够一眼便能看透逻辑。

原始代码:

function payMount(){
  let result;
  if(isDead){
    result = amountFunc();
  }else{
    if(isSeparated){
      result = separatedAmout();
    }else{
      if(isRetired){
        result = retiredAmount()
      }else{
        result = normalAmount();
      }
    }
  }
  return result;
}

重构代码:

function payMount(){
  if(isDead) result = amountFunc();
  if(isSeparated) result = separatedAmout();
  if(isRetired) result = retiredAmount();
  return normalAmount();
}

4. 以多态取代条件表达式

在编程中复杂的条件逻辑是相当难理解的代码,与其寻求给条件逻辑添加结构,不如拆分分支条件到不同的场景,即高阶用例。在拆解复杂的条件逻辑时,有时发现条件逻辑本身结构足以表达,但使用类和多态能把逻辑拆分表达更清晰。

如果现有的类尚不具备多态行为,可创建工厂函数返回恰当的对象实例,在调用方法时即可获得对象实例。可以将带有条件的函数移到超类中,如果条件逻辑还未提炼到独立的函数。任选子类在其中创建函数,将其进行覆写超类中容纳条件表达式的函数,将子类相关条件表达式分支复制到新函数进行调整。

原始代码:

switch(user.type){
  case "UserName":
    return "yichuan";
  case "UserAge":
    return this.age > 18 ? "成年" : "未成年";
  case "UserUniversity":
    return this.score > 600 ? "985高校" : "非985高校";
  default:
    return "unknown";
}

重构代码:

class UserName{
  get detail(){
    return "yichuan";
  }
}

class UserAge{
  get detail(){
    return this.age > 18 ? "成年" : "未成年";
  }
}

class UserUniversity{
  get detail(){
    return this.score > 600 ? "985高校" : "非985高校";
  }
}

5. 引入特例

常见的重复代码是:一个数据结构的使用者都在检查某个特殊的值,且当这个特殊值出现时多做的处理也同样。如果发现代码中在多处以相同方式应对同个特殊值,就可以将处理逻辑整合在一块。

处理重复代码最好的方法时使用『特例』模式,创建一个特例元素,用来处理特例的共用行为,可用一个函数调用取代部分特例检查逻辑。

通常的,特例有几种表现形式,我们可以对常见方式进行处理。如:

  • 只从对象中读取数据,可以提供一个预先填充值的字面量对象
  • 除获取简单数值外还需更多行为,则可以创建保函所有共有行为所对应的函数
  • 特例对象可以封装成类进行返回,亦可通过变换插入数据结构
//原始代码
if(flag === "unkown") name = "yichuan";

//重构代码
class User{
  get name(){
    return "yichuan";
  }
}

6. 引入断言

在进行程序开发时,只有当某个条件为真时代码才能正常运行,常规做法是使用注释进行解释。而使用断言可以明确标明假设,因为其时一个条件表达式,应当总是恒等于真,断言的失败不应该被系统任何地方捕获。

断言在交流上具有很高的价值,可以解决了当下正在追踪的错误,但是自测代码降低了断言在调试过程中的价值,因为逐步逼近的单元测试通常有助于调试。

原始代码:

func(num){
  return (discountRate) ? num - (discountRate * num) : num;
}

重构代码:

set discountRate(num){
  assert(null === num || num >= 0);
  discountRate= num;  
}

小结

在本文中,主要介绍了如何简化条件逻辑,可以对条件逻辑进行操作,而代码也变得更加清晰易懂。

参考文章

《重构──改善既有代码的设计(第2版)》

写在最后

我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。

更多最新文章敬请关注笔者掘金账号一川萤火和公众号前端万有引力