本文转载自微信公众号「前端万有引力」,作者一川。转载本文请联系前端万有引力公众号。
简化条件逻辑
常见的简化条件逻辑方法有:
- 分解条件表达式
- 合并条件表达式
- 以卫语句取代嵌套条件表达式
- 以多态取代条件表达式
- 引入特例
- 引入断言
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版)》