多年前,我参与了一个至今仍被广泛使用的打车移动应用的开发。我不知道现在他们运行的代码是什么情况,但如果我没记错的话,早期关于司机分配这部分的代码,很大程度上类似于下面这个极度简化的示例:
async function assignDriver(rider, availableDrivers) {
const driverDistances = await calculateDistances(rider.location, availableDrivers);
let assignedDriver = null;
for (let driver of availableDrivers) {
if (driverDistances[driver.id] 5) {
if (!rider.preferredVehicle || rider.preferredVehicle === driver.vehicle) {
if (driver.rating >= 4.5) {
if (rider.preferences.includes('Premium Driver')) {
if (driver.isPremiumDriver) {
assignedDriver = driver;
break;
} else {
continue;
}
} else {
assignedDriver = driver;
break;
}
} else if (driver.rating >= 4.0) {
assignedDriver = driver;
break;
}
}
}
}
return assignedDriver;
}
在这段不到 30 行的代码中,有五层嵌套的 if 语句。也许有人会说,这看起来还不算太糟,但不难想象,如果再加上诸如高峰期加价、忠诚度计划等更多检查条件,这些代码会变得多么复杂。
幸运的是,我们有办法将代码扁平化处理,而最终的结果可能会让你大吃一惊:当我们完成重构后,就不会再有 if 语句了。
利用保护子句
让我们先从简单的开始。第一个 if 语句用于检查司机和乘客之间允许的最大距离,由于它适用于所有情况,因此可以将其转换为保护子句,从而移除一层嵌套。同样,我们还可以为首选车辆检查添加另一个保护子句,以去除另一层嵌套。由此修改后的代码如下所示:
async function assignDriver(rider, availableDrivers) {
const driverDistances = await calculateDistances(rider.location, availableDrivers);
let assignedDriver = null;
for (let driver of availableDrivers) {
if (driverDistances[driver.id] > 5) {
continue;
}
if (rider.preferredVehicle && rider.preferredVehicle !== driver.vehicle) {
continue;
}
if (driver.rating >= 4.5) {
if (rider.preferences.includes('Premium Driver')) {
if (driver.isPremiumDriver) {
assignedDriver = driver;
break;
} else {
continue;
}
} else {
assignedDriver = driver;
break;
}
} else if (driver.rating >= 4.0) {
assignedDriver = driver;
break;
}
}
return assignedDriver;
}
决策表
与其在函数内部硬编码逻辑,我们可以将每个 if-else 模块放在它自己的函数中,并将这些函数放入一个数组,形成一个决策表。然后,我们遍历决策表中的每个函数,直到得到一个肯定的响应。当然了,表中的条目必须从最具体的到最不具体的顺序排列,这样才能在我们的案例中正常运行。
const conditions = [
(rider, driver) => driver.rating >= 4.5 && rider.preferences.includes('Premium Driver') && driver.isPremiumDriver,
(rider, driver) => driver.rating >= 4.5 && !rider.preferences.includes('Premium Driver'),
(rider, driver) => driver.rating >= 4.0 && driver.rating 4.5,
];
async function assignDriver(rider, availableDrivers, conditions) {
const driverDistances = await calculateDistances(rider.location, availableDrivers);
let assignedDriver = null;
for (let driver of availableDrivers) {
if (driverDistances[driver.id] > 5) {
continue;
}
if (rider.preferredVehicle && rider.preferredVehicle !== driver.vehicle) {
continue;
}
if (conditions.find((condition) => condition(rider, driver))) {
assignedDriver = driver;
break;
}
}
return assignedDriver;
}
有了决策表,我们就彻底消除了嵌套的 if 语句,还有一个额外的好处:现在只需简单编辑条件数组,就可以更改驱动程序的赋值逻辑。
函数组合
让我们把 for 循环转换为 Array.find(),并为循环中的每个 if 语句创建单独的函数,从而消除剩余的 if 语句:
const conditions = [
(rider, driver) => driver.rating >= 4.5 && rider.preferences.includes('Premium Driver') && driver.isPremiumDriver,
(rider, driver) => driver.rating >= 4.5 && !rider.preferences.includes('Premium Driver'),
(rider, driver) => driver.rating >= 4.0 && driver.rating 4.5,
];
async function assignDriver(rider, availableDrivers, conditions) {
const driverDistances = await calculateDistances(rider.location, availableDrivers);
const isDriverClose = (driver) => driverDistances[driver.id] 5;
const isVehicleOK = (rider, driver) => !rider.preferredVehicle || rider.preferredVehicle === driver.vehicle;
const isAConditionSatisfied = (rider, driver) => conditions.find((condition) => condition(rider, driver));
const assignedDriver = availableDrivers.find(
(driver) => isDriverClose(driver) && isVehicleOK(rider, driver) && isAConditionSatisfied(rider, driver)
);
return assignedDriver || null;
}
总结
通过利用基本的函数式编程原则,我们已经:
● 移除了所有的嵌套 if 语句,
● 将大部分逻辑解耦,使其易于修改,
● 通过将各个 if-else 块放入其自己的函数中,使代码更易于理解,
● 最后还有个额外的好处,即程序大小减少了三分之一。
本文摘自网络。
原创文章,作者:北大青鸟,如若转载,请注明出处:http://news.yy-accp.com/archives/18614