10.1 & 10.2 分解条件表达式(Decompose Conditional)、合并条件表达式(Consolidate Conditional Expression)
1 | if (anEmployee.seniority < 2) return 0; |
分解条件表达式
本质是【提炼函数】的一个应用场景,为了解决:
- 避免大规模函数
- 减少条件逻辑带来的可读性降低
- 可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因
特别是多个条件嵌套的时候,使用函数+合理的命名,可以快速搞清楚这个分支的产生原因
合并条件表达式
场景:检查条件各不相同,最终行为却一致
是否合并取决于这些条件是否独立,如果不是独立应该只检查一次,只是检查条件有多项,更符合直观意识。
另一个好处是如果设计修改最终行为,不用修改多处,减少重复代码很关键。
10.3 以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses)
1 | function getPayAmount() { |
条件表达式风格:
- 两个条件分支都属于正常行为(用if…else…表达同等关系)
- 只有一个条件分支是正常行为,另一个分支则是异常的情况(异常应该及时退出清理)
什么是卫语句:
- 如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回
- 表达:这种情况不是本函数的核心逻辑所关心的,如果它真发生了,请做一些必要的整理工作,然后退出
10.4 以多态取代条件表达式(Replace Conditional with Polymorphism)
1 | switch (bird.type) { |
常见的使用多态拆分复杂条件逻辑场景:
- 基于类型代码的 switch 语句,每种类型有自己的行为
- 基础逻辑的变体(父类和子类),将非通用部分放到子类
书中提到都是分支的实现逻辑用多态,补充下条件本身也可以用多态,比如某个if语句写了一些只有某些类型才执行逻辑,比如xxx是宠物 或者 xxx是怪物,但如果需要判断的类型比较多的话,可以类型里面去实现【自己能否执行这段逻辑】的接口
但也不是所有条件逻辑都应该用多态取代,还是得看复杂度,多态只是降低复杂度的有力工具。
10.5 引入特例(Introduce Special Case)
1 | if (aCustomer === "unknown") customerName = "occupant"; |
什么情况需要引入:
- 一个数据结构的使用者都在检查某个特殊的值,并且当这个特殊值出现时所做的处理也都相同
- 比如上面示例有很多地方检查是否是未知对象,然后未知对象有对应的分支处理
- 造成的问题是所有对数据结构的使用接口都需要处理这个“异类”(且处理还类似),显然是重复代码的一种表现
引入特例解决了什么:
- 将特殊值创建特例元素,表达对这种特例的共用行为的处理
- 意思就是围绕特殊值的处理,使用接口通用的处理模式去处理,就不用每个接口去判断特殊值(比如获取对象的名字,为空则设为null,如果【空】是一个特例对象,本身空对象的名字就是null,就不需要特殊判断)
- Null 对象是特例的一种特例
怎么引入特例:
- 对象字面量:
- 特例不继承原对象,只是有一样的属性
- 比如原对象需要通过.name拿取名字,特例字面量对象也有.name属性去返回(比如返回"unknown")
- 比较适用于弱类型语言
- 特殊对象
- 继承原对象,实现一个特例对象
- 同理将特例值填充
- 可以支持特例需要有行为时,而不是简单的字面数值(比如unknown对象需要执行什么逻辑)
- 通过变换插入一个数据结构
- 在原有数据结构深拷贝后,插入接口关心的数据,并处理了特例部分的数据
- 这样在数据层面整理区分,接口层不需要去特殊判断
- 类似游戏收到协议数据,对数据二次整理,正常值使用协议的,异常值用预设的数据
核心都是函数不需要判断特殊值,而能通过一致的逻辑去处理
10.6 引入断言(Introduce Assertion)
1 | if (this.discountRate) |
一些算法有一些隐形假设,比如平方根只对正值进行处理,单位坐标不能是空值等等,而这些假设在代码中并没有体现(没事的,报错了就印象深刻了):)。使用断言可以去明确标明这些假设
虽然可以对每个接口断言判断,但像坐标为空值更应该在设值函数那里断言,在源头的地方抓起。
不应该滥用断言,否则会代码重复,应该在“必须为真”的地方加