14、简化条件逻辑(上):分解条件表达式、合并条件表达式、以卫语句取代嵌套条件表达式
条件逻辑,说白了就是代码里的「if-else 大法」。每个程序员每天都在写。但你知道吗?条件逻辑往往是代码腐化的重灾区。我见过太多项目,一个方法里嵌套七八层 if,读起来像在解迷宫。
今天咱们聊聊三个最实用的重构手法:分解条件表达式、合并条件表达式、以卫语句取代嵌套条件表达式。这三招用好了,你的代码会清爽很多。
为什么条件逻辑需要简化?
先问个问题:你见过最长的条件判断有多长?我在一个老项目中见过一个 if 条件,横跨了屏幕的三行,里面混着七八个 && 和 ||。当时我盯着它看了五分钟,愣是没搞明白它到底在判断什么。
条件逻辑复杂带来的问题很明显:
- 可读性差:别人看不懂,两周后的自己也看不懂
- 容易藏 bug:条件越复杂,漏掉边界情况的概率越大
- 难以测试:分支太多,测试用例写到手软
- 修改风险高:改一个条件,可能牵一发动全身
嗯,说白了,简化条件逻辑不是为了炫技,而是为了让你和你的同事少掉几根头发。
手法一:分解条件表达式
这个手法最简单,也最容易被忽略。它的核心思想就一句话:把复杂的条件判断拆成多个小函数。
来看个例子。假设我们有一个订单折扣计算逻辑:
// 重构前
function calculateDiscount(order) {
if (order.total > 1000 && order.customer.isVIP && !order.isHoliday) {
return order.total * 0.8;
} else if (order.total > 500 && order.customer.isVIP) {
return order.total * 0.9;
} else {
return order.total;
}
}
这段代码的问题在哪?条件太长了。你一眼看过去,很难快速理解「什么情况下打八折」。我个人习惯的做法是:把每个条件判断提取成独立的方法。
// 重构后
function calculateDiscount(order) {
if (isEligibleForSuperDiscount(order)) {
return order.total * 0.8;
}
if (isEligibleForNormalDiscount(order)) {
return order.total * 0.9;
}
return order.total;
}
function isEligibleForSuperDiscount(order) {
return order.total > 1000
&& order.customer.isVIP
&& !order.isHoliday;
}
function isEligibleForNormalDiscount(order) {
return order.total > 500
&& order.customer.isVIP;
}
核心原则:每个条件判断都应该能回答一个「是/否」问题。如果条件表达式超过一行,就该考虑提取了。
我在项目中遇到过这样的场景:一个支付系统的风控判断,原本有 15 行条件。拆成 5 个小函数后,不仅代码好读了,测试也容易写了——每个小函数单独测,爽得很。
手法二:合并条件表达式
这个手法和分解正好相反。有时候你会发现,多个条件分支执行的是相同的逻辑。这时候就应该把它们合并起来。
举个例子:
// 重构前
function getWorkDuration(employee) {
if (employee.isPartTime) {
return 0;
}
if (employee.isIntern) {
return 0;
}
if (employee.isOnLeave) {
return 0;
}
return employee.workHours;
}
三个条件都返回 0,却写了三个 if。你想想看,这多浪费阅读者的精力。合并一下:
// 重构后
function getWorkDuration(employee) {
if (isNotWorking(employee)) {
return 0;
}
return employee.workHours;
}
function isNotWorking(employee) {
return employee.isPartTime
|| employee.isIntern
|| employee.isOnLeave;
}
小技巧:合并条件时,用「或」还是「与」?记住一点:如果多个条件都导向同一个结果,用「或」;如果多个条件必须同时满足才导向某个结果,用「与」。
我曾经接手过一个报表系统,里面有个方法判断「是否应该发送通知」,写了 7 个 if 分支,每个分支都调用了同一个 sendNotification 方法。合并之后,代码从 40 行缩到了 10 行。嗯,这种重构做起来最有成就感。
手法三:以卫语句取代嵌套条件表达式
这个手法是我个人最常用的。它的核心思想是:先处理异常情况,再处理正常流程。
来看一个典型的嵌套地狱:
// 重构前
function processOrder(order) {
if (order.isValid) {
if (order.isPaid) {
if (order.isInStock) {
// 正常发货逻辑
shipOrder(order);
} else {
// 缺货处理
handleOutOfStock(order);
}
} else {
// 未支付处理
handleUnpaid(order);
}
} else {
// 无效订单处理
handleInvalidOrder(order);
}
}
这种代码我见得太多了。三层嵌套还算好的,五层六层的都有。读这种代码,你得在脑子里维护一个「当前在第几层」的计数器,累不累啊?
用卫语句改写一下:
// 重构后
function processOrder(order) {
if (!order.isValid) {
handleInvalidOrder(order);
return;
}
if (!order.isPaid) {
handleUnpaid(order);
return;
}
if (!order.isInStock) {
handleOutOfStock(order);
return;
}
// 正常发货逻辑
shipOrder(order);
}
注意:卫语句的关键是「提前返回」。但要注意,不是所有场景都适合提前返回。如果一个方法里有多个资源需要释放(比如文件句柄、数据库连接),提前返回可能导致资源泄漏。这时候可以考虑 try-finally 或者 using 语句。
我曾经在一个支付网关项目里,把一个 8 层嵌套的验证逻辑改成了卫语句风格。改完之后,同事问我:「这代码谁写的?好清晰啊。」我说:「还是原来那个方法,只是把嵌套展开了。」他一脸不信。
三种手法的选择时机
你可能会问:这三种手法什么时候用哪个?我总结了一个简单的判断标准:
| 场景 | 推荐手法 | 原因 |
|---|---|---|
| 条件表达式太长(超过一行) | 分解条件表达式 | 提高可读性,每个条件有名字 |
| 多个条件执行相同逻辑 | 合并条件表达式 | 消除重复,减少分支 |
| 嵌套层级过深(超过两层) | 以卫语句取代嵌套 | 扁平化结构,突出正常流程 |
当然,实际项目中往往是混合使用的。比如先分解条件,再配合卫语句。没有固定套路,灵活运用就好。
知识体系总览
下面这张图帮你梳理了本章的核心知识点:
避坑指南
最后分享几个我踩过的坑:
- 不要过度分解:如果一个条件就一行,而且语义很清晰(比如
if (user.isAdmin)),没必要硬拆成函数。过度分解反而让代码碎片化。 - 卫语句不是万能药:如果一个方法里有多个 return 点,确实会让流程更清晰。但如果方法本身就很长(超过 20 行),卫语句也救不了你——这时候应该先拆分方法。
- 合并条件时注意副作用:如果每个条件分支里除了返回值还有别的操作(比如日志、状态更新),合并前要确认这些操作是否也需要合并。
我的习惯:每次写完条件逻辑,我都会问自己一个问题——「如果让一个刚入职的同事来看这段代码,他需要多久才能理解?」如果超过 10 秒,就该重构了。
好了,今天的内容就到这里。这三种手法看似简单,但用好了能让你的代码质量提升一个档次。下次写 if 的时候,不妨多想想:能不能拆?能不能合?能不能用卫语句?
公众号:蓝海资料掘金营,微信deep3321