5、函数重构(上):分解条件表达式、合并条件表达式、以卫语句取代嵌套条件
条件逻辑,是代码里最容易藏污纳垢的地方。我见过太多函数,一个 if-else 套一个 if-else,读起来像在解迷宫。今天咱们就聊聊怎么收拾这些烂摊子。
5.1 分解条件表达式:把大块逻辑拆开
先看一段代码。你感受一下:
double calculateCharge(Date start, Date end, int quantity) {
if (start != NULL && end != NULL && end > start) {
int days = (end - start) / (24 * 3600);
if (days <= 7) {
return quantity * 10.0;
} else if (days <= 30) {
return quantity * 8.5;
} else {
return quantity * 7.0;
}
} else {
return 0.0;
}
}
这段代码干了三件事:校验日期、计算天数、按阶梯定价。全揉在一起了。我早期写代码也这样,觉得「反正就几行,没必要拆」。后来维护过一次别人的代码,一个函数 200 行,全是这种嵌套——嗯,从那以后我再也不敢偷懒了。
分解后的样子:
double calculateCharge(Date start, Date end, int quantity) {
if (!isValidPeriod(start, end)) {
return 0.0;
}
int days = calculateDays(start, end);
return getRate(days) * quantity;
}
bool isValidPeriod(Date start, Date end) {
return start != NULL && end != NULL && end > start;
}
int calculateDays(Date start, Date end) {
return (end - start) / (24 * 3600);
}
double getRate(int days) {
if (days <= 7) return 10.0;
if (days <= 30) return 8.5;
return 7.0;
}
每个函数只做一件事。函数名就是注释。你想想看,以后改阶梯价格,只需要改 getRate,完全不影响其他逻辑。
5.2 合并条件表达式:别写重复的判断
有时候你会看到这样的代码:
double getDiscount(Order order) {
if (order.getTotal() > 1000) {
return 0.1;
}
if (order.getCustomer().isVIP()) {
return 0.1;
}
if (order.getItems().size() > 10) {
return 0.1;
}
return 0.0;
}
三个条件,返回同一个值。为什么要写三个 if?合并起来多清爽:
double getDiscount(Order order) {
if (isEligibleForDiscount(order)) {
return 0.1;
}
return 0.0;
}
bool isEligibleForDiscount(Order order) {
return order.getTotal() > 1000
|| order.getCustomer().isVIP()
|| order.getItems().size() > 10;
}
合并条件表达式的核心原则:相同结果的条件,合并成一个判断。这样逻辑更集中,以后要改折扣规则,只需要改一个地方。
5.3 以卫语句取代嵌套条件:提前返回,减少缩进
嵌套条件最大的问题是什么?是阅读负担。你读代码时,脑子里要维护一个「当前在第几层」的栈。层数一多,人就晕了。
看这个例子:
void processPayment(Payment payment) {
if (payment != NULL) {
if (payment.isValid()) {
if (payment.getAmount() > 0) {
// 真正的处理逻辑
charge(payment);
sendReceipt(payment);
} else {
logError("金额无效");
}
} else {
logError("支付无效");
}
} else {
logError("支付对象为空");
}
}
三层嵌套。你找找看,真正的业务逻辑在哪?被埋在最里面了。用卫语句改写:
void processPayment(Payment payment) {
if (payment == NULL) {
logError("支付对象为空");
return;
}
if (!payment.isValid()) {
logError("支付无效");
return;
}
if (payment.getAmount() <= 0) {
logError("金额无效");
return;
}
// 真正的处理逻辑
charge(payment);
sendReceipt(payment);
}
看到了吗?每个异常情况提前返回,主逻辑直接放在函数末尾。缩进只有一层,读起来像流水线一样顺畅。
5.4 三种技巧的对比与选择
| 技巧 | 适用场景 | 解决的问题 | 我常用的场景 |
|---|---|---|---|
| 分解条件表达式 | 条件分支内部逻辑复杂 | 函数过长、职责不单一 | 价格计算、规则引擎 |
| 合并条件表达式 | 多个条件导致相同结果 | 重复代码、逻辑分散 | 权限校验、折扣判断 |
| 以卫语句取代嵌套条件 | 多层嵌套、异常处理多 | 代码可读性差、缩进过深 | 参数校验、前置条件检查 |
这三种技巧不是互斥的。我在实际项目中经常组合使用:先用卫语句把异常情况提前返回,再把剩下的条件分支拆成独立函数,最后看看有没有可以合并的条件。一套组合拳下来,代码质量提升很明显。
5.5 本章知识体系
下面这张图总结了三种重构技巧的核心逻辑和适用场景:
这三种技巧,说白了就是让条件逻辑的意图更清晰。你想想看,一段代码写出来,三个月后你自己回来看,能不能一眼看懂?如果不行,那就该重构了。
我个人习惯是:写条件逻辑时,先问自己三个问题——这个条件能不能拆?这些条件能不能合并?能不能用卫语句提前返回?问完这三个问题,代码质量基本就有保障了。