程序大部分功能来自条件逻辑,复杂度也大多来自条件逻辑。我们有时候需要借助重构把条件逻辑变得更容易理解。比如:
- 分解条件表达式:处理复杂的条件表达式
- 合并条件表达式:厘清逻辑组合
- 以卫语句取代嵌套条件表达式:在主要处理逻辑之前先做检查
- switch逻辑:以多态取代条件表达式
分解条件表达式(Decompose Conditional)
本重构手法其实只是提炼函数的一个应用场景。我们可以对条件判断和每个条件分支分别运用提炼函数
// 假设计算购买某样商品的总价(总价=数量x单价),商品在冬季和夏季的单价不同:
if (!aData.isBefore(plan.summerStart) && !aData.isAfter(plan.summerEnd)) {
charge = quantity * plan.summerRate;
} else {
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
// 提炼完后:
// if (summer()) {
// charge = summerCharge();
// } else {
// charge = regularCharge();
// }
charge = summer() ? summerCharge() : regularCharge();
function summer() {
return !aData.isBefore(plan.summerStart) && !aData.isAfter(plan.summerEnd);
}
function summerCharge() {
return quantity * plan.summerRate;
}
function regularCharge() {
return quantity * plan.regularRate + plan.regularServiceCharge;
}
合并条件表达式(Consolidate Conditional Expression)
做法:
- 确定这些条件表达式都没有副作用
- 使用适当的逻辑运算符,将两个相关条件表达式合并为一个
- 重复前面的合并过程,直到所有相关的条件表达式都合并到一起
function disabilityAmount(anEmployee) {
if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;
// ...
}
// 合并后提炼函数
function disabilityAmount(anEmployee) {
if (isNotEligableForDisability()) return 0;
// ...
}
function isNotEligableForDisability() {
return ((anEmployee.seniority < 2)
|| (anEmployee.monthsDisabled > 12)
|| (anEmployee.isPartTime));
}
以卫语句取代嵌套条件表达式(Replace Nested Conditional With Guard Clauses)
条件表达式通常有两种风格:两个条件分支都属于正常行为;只有一个条件分支是正常行为,另一个分支则是异常的情况。对于第二种情况,如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查通常被称为“卫语句”。
// 范例一
function payAmount(employee) {
let result;
if (employee.isSeparated) {
result = {amount: 0, reasonCode: 'SEP'};
} else {
if (employee.isRetired) {
result = {amount: 0, reasonCode: 'RET'};
} else {
// logic to compute amount
result = someFinalComputation();
}
}
return result;
}
// 修改完后:
function payAmount(employee) {
if (employee.isSeparated) return {amount: 0, reasonCode: 'SEP'};
if (employee.isRetired) return {amount: 0, reasonCode: 'RET'};
// logic to compute amount
return someFinalComputation();
}
// 范例二:条件反转
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital > 0) {
if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}
}
return result;
}
// 修改后:
function adjustedCapital(anInstrument) {
if ( anInstrument.capital <= 0
|| anInstrument.interestRate <= 0
|| anInstrument.duration <= 0) {
return 0;
}
return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;;
}
以多态取代条件表达式(Replace Conditional With Polymorphism)
复杂的条件逻辑是编程中最难理解的东西之一,有时候我们可以将条件逻辑拆分到不同的场景,从而拆解复杂的条件逻辑。这种拆分有时候用条件逻辑本身的结构就足以表达,但使用类和多态能把逻辑的拆分表达得更清晰。多态是面向对象编程的关键特性之一,它也很容易被滥用,并不是所有条件逻辑都应该用多态取代。
// 示例中:有一群鸟,鸟的特征为速度有多快和羽毛是什么样
function plumages(birds) {
return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
function plumage(bird) {
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';
}
}
function airSpeedVelocity(bird) {
switch (bird.type) {
case 'EuropeanSwallow':
return 35;
case 'AfricanSwallow':
return 40 - 2 * bird.numberOfCoconuts;
case 'NorwegianBlueParrot':
return (bird.isNailed) ? 0 : bird.voltage / 10;
default:
return null;
}
}
// 重构后...
function plumages(birds) {
return new Map(birds
.map(b => createBird(b))
.map(bird => [bird.name, bird.plumage]));
}
function speeds(birds) {
return new Map(birds
.map(b => createBird(b))
.map(bird => [bird.name, bird.airSpeedVelocity]));
}
function createBird(bird) {
switch (bird.type) {
case 'EuropeanSwallow':
return new EuropeanSwallow(bird);
case 'AfricanSwallow':
return new AfricanSwallow(bird);
case 'NorwegianBlueParrot':
return new NorwegianBlueParrot(bird);
default:
return new Bird(bird);
}
}
class Bird {
constructor(birdObject) {
Object.assign(this, birdObject);
}
get plumage() {
return 'unknown';
}
get airSpeedVelocity() {
return null;
}
}
class EuropeanSwallow extends Bird {
get plumage() {
return 'average';
}
get airSpeedVelocity() {
return 35;
}
}
class AfricanSwallow extends Bird {
get plumage() {
return (bird.numberOfCoconuts > 2) ? 'tired' : 'average';
}
get airSpeedVelocity() {
return 40 - 2 * bird.numberOfCoconuts;
}
}
class NorwegianBlueParrot extends Bird {
get plumage() {
return (bird.voltage > 100) ? 'scorched' : 'beautiful';
}
get airSpeedVelocity() {
return (bird.isNailed) ? 0 : bird.voltage / 10;
}
}