重构读书笔记九——简化条件逻辑
概述
程序的大部分逻辑来自条件逻辑,但很不幸,程序的复杂度大多也来自于条件逻辑。通过重构手法可以让条件逻辑更容易理解。
9.1 分解条件表达式
描述
将复杂的条件表达式用函数提炼出来,让函数赋予条件表达式语义,方便理解。
代码
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
charge = quantity * plan.summerRate;
else
charge = quantity * plan.regularRate + plan.regularServiceCharge;
refactor
if (summer())
charge = summerCharge();
else
charge = regularCharge();
动机
程序之中,复杂的条件逻辑是经常导致复杂度上升的地点之一。我们应该将复杂的条件逻辑分解成独立的函数,从而更清楚地表达自己的意图。
9.2 合并条件表达式
描述
将最终行为一致的表达式用逻辑或,逻辑与合并到一个条件判断里面。
动机
因为不要重复,这种逻辑是典型的重复,然后就是为以后提炼函数做准备。
9.3 以卫语句取代嵌套表达式
描述
经常看到这样的代码:一个if嵌套另一个if,有多达五层的嵌套。
代码
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
else
result = normalPayAmount();
}
}
return result;
}
refactor:
function getPayAmount() {
if (isDead)
return deadAmount();
if (isSeparated)
return separatedAmount();
if (isRetired)
return retiredAmount();
return normalPayAmount();
}
动机
条件逻辑有两种情况,每个if-else-then都是正常的业务逻辑,这样的条件结构是合理的(但是也不应该过多,可以用多态和工厂方法取代)。另外一种是一个分支合理,另外一个不合理的异常情况。在这种情况下不合理的情况应该尽早处理并从业务中结束。在正常逻辑之前把异常情况都处理掉的语句书中称为卫语句(我一般叫做防御编程)。只保留正常的业务逻辑让业务代码更清晰,同时可以有效减少逻辑判断的层数。
9.4 用多态取代条件表达式
描述
通过工厂方法来隐藏条件逻辑,利用多态来复用共有逻辑,再利用类的封装特性来屏蔽不同的业务逻辑。
动机
[me]:用多态和工厂方法,如果具有良好的ddd实践和依赖注入的思想,可以提供耦合度很低的业务代码,并且对扩展非常方便。在做dsp平台的时候,要对接很多adx平台,对于dsp平台来说,对内部流程的抽象,可以抹平不同adx平台的差异。后续添加新的adx平台只需要关注接口的对接即可。代码复用率很高。
复杂的条件逻辑是编程中最难理解部分之一。作者发现可以将条件逻辑拆分到不同的场景(或者叫高阶用例),从而拆解复杂的条件逻辑。这种拆分逻辑有时用条件逻辑本身的结构就足以表达,但使用类和多态能把逻辑的拆分表达得更清晰。
9.5 引入特例
描述
作者这里是希望用一个特例来取代null对象,或者一个特殊的对象来代表一种特殊情况。用这个对象来取代所有的类似操作(判空,不处理等)。
[me]:现在编程语言提供了option类似的语法来让我们以一种更优雅的方式处理空对象。
动机
一种常见的重复代码是:一个数据结构的使用者都在检查某个特殊的值,并且当这个特殊值出现时,所做的处理也都是一样的。这种情况,我会把这个逻辑都收拢到一处。处理这种情况的一个好办法是使用特例模式:创建一个特例元素,用以表达对这个特例的公用行为的处理。
9.5 引入断言
描述
断言从c语言乃至更好就存在的语法,说实话用的不是很多。在spring和一些项目的源码里面见过使用,对于在公司的项目从来没有使用。
动机
如果你发现代码假设某个条件始终为真,就加入一个断言明确说明这种情况。
总结
无论学会了什么样的方法技巧,其实关键不是对技巧的掌握,而是深刻理解其中体现的思想。重构的思想,面向对象的思想,设计模式的思想,ddd的思想。回到本源是封装(语义化,不要和陌生人谈话...),抽象(高阶函数,算子,领域划分),设计模式六大规则,依赖注入(解耦的方式)等等,掌握好这些自然而然就能写出好的代码了。