简化条件逻辑(上):分解条件表达式、合并条件表达式、以卫语句取代嵌套条件表达式
条件逻辑,说白了就是代码里的 if-else。这东西写起来容易,但读起来往往让人头疼。我见过不少项目,一个方法里嵌套了五六层 if,看到最后连自己写的逻辑都绕晕了。
今天咱们聊三个最实用的重构手法。它们能帮你把混乱的条件逻辑理清楚。我个人习惯是:先分解,再合并,最后用卫语句收尾。
1. 分解条件表达式
什么叫分解?就是把一个又长又复杂的条件判断,拆成多个小方法。
来看个例子。这是我以前在一个支付系统里遇到的代码:
function calculatePrice(order) {
if (order.isVIP && order.totalAmount > 1000 && !order.isHoliday) {
return order.totalAmount * 0.8;
} else if (order.isVIP && order.totalAmount > 500) {
return order.totalAmount * 0.9;
} else if (order.totalAmount > 200) {
return order.totalAmount * 0.95;
} else {
return order.totalAmount;
}
}
这段代码功能没问题。但你看那个条件:order.isVIP && order.totalAmount > 1000 && !order.isHoliday。读一遍还行,读两遍就开始犯嘀咕了——到底什么情况下打八折?
重构之后是这样的:
function calculatePrice(order) {
if (isEligibleForSuperDiscount(order)) {
return order.totalAmount * 0.8;
}
if (isEligibleForStandardDiscount(order)) {
return order.totalAmount * 0.9;
}
if (isEligibleForLightDiscount(order)) {
return order.totalAmount * 0.95;
}
return order.totalAmount;
}
function isEligibleForSuperDiscount(order) {
return order.isVIP && order.totalAmount > 1000 && !order.isHoliday;
}
function isEligibleForStandardDiscount(order) {
return order.isVIP && order.totalAmount > 500;
}
function isEligibleForLightDiscount(order) {
return order.totalAmount > 200;
}
你看,每个条件现在都有一个名字。读代码的时候,你不需要去理解那个布尔表达式到底在算什么,直接看方法名就够了。
2. 合并条件表达式
有时候情况正好相反。多个条件分支执行的是相同的逻辑,那就该合并。
我曾经在一个订单处理模块里看到这样的代码:
function getDiscountRate(customer) {
if (customer.age > 65) {
return 0.1;
}
if (customer.isStudent) {
return 0.1;
}
if (customer.isVeteran) {
return 0.1;
}
return 0;
}
三个条件,返回的都是 0.1。你想想看,这有什么必要分开写?合并之后清晰多了:
function getDiscountRate(customer) {
if (isEligibleForDiscount(customer)) {
return 0.1;
}
return 0;
}
function isEligibleForDiscount(customer) {
return customer.age > 65 || customer.isStudent || customer.isVeteran;
}
合并的好处很明显:逻辑集中了。以后要改折扣规则,只需要改一个地方。而且 isEligibleForDiscount 这个命名,直接表达了业务意图。
3. 以卫语句取代嵌套条件表达式
这是我最喜欢的手法之一。嵌套条件,说白了就是 if 里面套 if,再套 if。读这种代码就像剥洋葱,剥一层还有一层。
来看一个我重构过的真实案例。这是一个用户注册的校验逻辑:
function registerUser(user) {
if (user) {
if (user.email) {
if (isValidEmail(user.email)) {
if (!userExists(user.email)) {
// 真正的注册逻辑
saveUser(user);
sendWelcomeEmail(user);
return true;
} else {
throw new Error('用户已存在');
}
} else {
throw new Error('邮箱格式不正确');
}
} else {
throw new Error('邮箱不能为空');
}
} else {
throw new Error('用户信息不能为空');
}
}
这段代码,说实话,我当年第一次看到的时候,数了半天括号才搞清楚逻辑。嵌套太深了,而且每个 else 都在抛异常。
用卫语句重构之后:
function registerUser(user) {
if (!user) {
throw new Error('用户信息不能为空');
}
if (!user.email) {
throw new Error('邮箱不能为空');
}
if (!isValidEmail(user.email)) {
throw new Error('邮箱格式不正确');
}
if (userExists(user.email)) {
throw new Error('用户已存在');
}
// 真正的注册逻辑
saveUser(user);
sendWelcomeEmail(user);
return true;
}
你看,现在逻辑是平铺的。每个异常情况提前返回,剩下的就是正常流程。读起来就像在念一个清单:先检查这个,再检查那个,都没问题就执行。
知识体系总览
这三种手法其实解决的是同一个问题:让条件逻辑更容易理解。它们各自适用的场景不同,我画了张图帮你理清思路:
实际应用中的选择
这三种手法不是互斥的。在实际项目中,我经常把它们组合使用。比如:
- 先看嵌套深度:如果超过两层,优先用卫语句拆平
- 再看条件复杂度:如果单个条件很长,用分解手法
- 最后看重复性:如果有多个条件返回相同结果,用合并手法
我曾经在一个支付网关项目里,把一个 80 行的条件判断方法,用这三种手法重构成了 5 个小方法。每个方法不超过 10 行。后来新同事接手,说「这代码读起来像说明书」。
嗯,这三种手法就先聊到这儿。下次遇到复杂的 if-else,不妨试试看。你会发现,条件逻辑其实没那么可怕。