重构改善既有代码的设计-学习(四):简化条件逻辑

1、分解条件表达式(Decompose Conditional)

可以将大块代码分解为多个独立的函数,根据每个小块代码的用途,为分解而得的新函数命名。对于条件逻辑,将每个分支条件分解成新函数还可以带来更多好处:可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因。

例如:

javascript 复制代码
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
    charge = quantity * plan.summerRate;
else
    charge = quantity * plan.regularRate + plan.regularServiceCharge;

改为:

javascript 复制代码
if (summer())
    charge = summerCharge();
else
    charge = regularCharge();

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

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

将检查条件提炼成一个独立的函数对于厘清代码意义非常有用,因为它把描述"做什么"的语句换成了"为什么这样做"。

如果这些检查的确彼此独立,的确不应该被视为同一次检查,就不能使用本项重构。

重构例子:

javascript 复制代码
if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;

改为:

javascript 复制代码
if (isNotEligibleForDisability()) return 0;
function isNotEligibleForDisability() {
    return ((anEmployee.seniority < 2)
        || (anEmployee.monthsDisabled > 12)
        || (anEmployee.isPartTime));
}

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

如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。

这样的单独检查常常被称为"卫语句"(guard clauses)。

至于"单一出口"规则,其实不是那么有用。保持代码清晰才是最关键的

例子:

javascript 复制代码
function getPayAmount() {
    let result;
    if (isDead)
        result = deadAmount();
    else {
        if (isSeparated)
            result = separatedAmount();
        else {
            if (isRetired)
                result = retiredAmount();
            else
                result = normalPayAmount();
        }
    }
    return result;
}

改为:

javascript 复制代码
function getPayAmount() {
    if (isDead) return deadAmount();
    if (isSeparated) return separatedAmount();
    if (isRetired) return retiredAmount();
    return normalPayAmount();
}

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

复杂的条件逻辑是编程中最难理解的东西之一,因此我一直在寻求给条件逻辑添加结构。很多时候,我发现可以将条件逻辑拆分到不同的场景(或者叫高阶用例),从而拆解复杂的条件逻辑。这种拆分有时用条件逻辑本身的结构就足以表达,但使用类和多态能把逻辑的拆分表述得更清晰。

一个常见的场景是:我可以构造一组类型,每个类型处理各自的一种条件逻辑。例如,我会注意到,图书、音乐、食品的处理方式不同,这是因为它们分属不同类型的商品。最明显的征兆就是有好几个函数都有基于类型代码的switch语句。若果真如此,我就可以针对switch语句中的每种分支逻辑创建一个类,用多态来承载各个类型特有的行为,从而去除重复的分支逻辑。

另一种情况是:有一个基础逻辑,在其上又有一些变体。基础逻辑可能是最常用的,也可能是最简单的。我可以把基础逻辑放进超类,这样我可以首先理解这部分逻辑,暂时不管各种变体,然后我可以把每种变体逻辑单独放进一个子类,其中的代码着重强调与基础逻辑的差异。

多态是面向对象编程的关键特性之一。跟其他一切有用的特性一样,它也很容易被滥用。我曾经遇到有人争论说所有条件逻辑都应该用多态取代。我不赞同这种观点。我的大部分条件逻辑只用到了基本的条件语句------if/else和switch/case,并不需要劳师动众地引入多态。但如果发现如前所述的复杂条件逻辑,多态是改善这种情况的有力工具。

例子:

javascript 复制代码
switch (bird.type) {
    case 'EuropeanSwallow':
        return "average";
    case 'AfricanSwallow':
        return (bird.numberOfCoconuts > 2) ? "tired" : "average";
    case 'NorwegianBlueParrot':
        return (bird.voltage > 100) ? "scorched" : "beautiful";
    default:
        return "unknown";
}

改为:

javascript 复制代码
class EuropeanSwallow {
    get plumage() {
        return "average";
    }
}
class AfricanSwallow {
    get plumage() {
        return (this.numberOfCoconuts > 2) ? "tired" : "average";
    }
}
class NorwegianBlueParrot {
    get plumage() {
        return (this.voltage > 100) ? "scorched" : "beautiful";
    }
}

5、引入特例(Introduce Special Case)

一个数据结构的使用者都在检查某个特殊的值,并且当这个特殊值出现时所做的处理也都相同。如果我发现代码库中有多处以同样方式应对同一个特殊值,我就会想要把这个处理逻辑收拢到一处。

处理这种情况的一个好办法是使用"特例"(Special Case)模式:创建一个特例元素,用以表达对这种特例的共用行为的处理。这样我就可以用一个函数调用取代大部分特例检查逻辑。

例子:

javascript 复制代码
if (aCustomer === "unknown") customerName = "occupant";

改为:

javascript 复制代码
class UnknownCustomer {
    get name() {return "occupant";}
}

6、引入断言(Introduce Assertion)

常常会有这样一段代码:只有当某个条件为真时,该段代码才能正常运行。

这样的假设通常并没有在代码中明确表现出来,你必须阅读整个算法才能看出。有时程序员会以注释写出这样的假设,而我要介绍的是一种更好的技术------使用断言

断言是一个条件表达式,应该总是为真。如果它失败,表示程序员犯了错误。断言的失败不应该被系统任何地方捕捉。整个程序的行为在有没有断言出现的时候都应该完全一样。实际上,有些编程语言中的断言可以在编译期用一个开关完全禁用掉。

断言是一种很有价值的交流形式------它们告诉阅读者,程序在执行到这一点时,对当前状态做了何种假设。另外断言对调试也很有帮助。而且,因为它们在交流上很有价值,即使解决了当下正在追踪的错误,我还是倾向于把断言留着。

例如:

javascript 复制代码
if (this.discountRate)
    base = base - (this.discountRate * base);

改为:

javascript 复制代码
assert(this.discountRate>= 0);
if (this.discountRate)
    base = base - (this.discountRate * base);
相关推荐
Bald Baby7 分钟前
JWT的使用
java·笔记·学习·servlet
心怀梦想的咸鱼44 分钟前
UE5 第一人称射击项目学习(四)
学习·ue5
AI完全体1 小时前
【AI日记】24.11.22 学习谷歌数据分析初级课程-第2/3课
学习·数据分析
Mephisto.java1 小时前
【大数据学习 | Spark-Core】Spark提交及运行流程
大数据·学习·spark
PandaCave2 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
yuwinter2 小时前
鸿蒙HarmonyOS学习笔记(2)
笔记·学习·harmonyos
red_redemption2 小时前
自由学习记录(23)
学习·unity·lua·ab包
幽兰的天空3 小时前
默语博主的推荐:探索技术世界的旅程
学习·程序人生·生活·美食·交友·美女·帅哥
沐泽Mu3 小时前
嵌入式学习-C嘎嘎-Day05
开发语言·c++·学习
你可以叫我仔哥呀4 小时前
ElasticSearch学习笔记三:基础操作(一)
笔记·学习·elasticsearch