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 行,或者条件表达式本身超过 3 个逻辑运算符,我就考虑拆成独立函数。

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 三种技巧的对比与选择

技巧 适用场景 解决的问题 我常用的场景
分解条件表达式 条件分支内部逻辑复杂 函数过长、职责不单一 价格计算、规则引擎
合并条件表达式 多个条件导致相同结果 重复代码、逻辑分散 权限校验、折扣判断
以卫语句取代嵌套条件 多层嵌套、异常处理多 代码可读性差、缩进过深 参数校验、前置条件检查

这三种技巧不是互斥的。我在实际项目中经常组合使用:先用卫语句把异常情况提前返回,再把剩下的条件分支拆成独立函数,最后看看有没有可以合并的条件。一套组合拳下来,代码质量提升很明显。

避坑指南: 我曾经在一个项目中过度使用卫语句,导致一个函数里有七八个 return。后来发现,卫语句太多其实也影响阅读。我的经验是:卫语句控制在 3-4 个以内,如果超过这个数,说明函数本身需要重新设计。

5.5 本章知识体系

下面这张图总结了三种重构技巧的核心逻辑和适用场景:

条件表达式重构三种技巧 条件逻辑重构 分解条件表达式 将复杂条件拆成独立函数 每个函数只做一件事 函数名即注释 合并条件表达式 相同结果的条件合并 减少重复判断 逻辑更集中 以卫语句取代嵌套 异常情况提前返回 减少缩进层级 主逻辑放在最后 核心原则:让条件逻辑的意图更清晰,减少阅读负担 组合使用效果更佳:卫语句 → 分解 → 合并

这三种技巧,说白了就是让条件逻辑的意图更清晰。你想想看,一段代码写出来,三个月后你自己回来看,能不能一眼看懂?如果不行,那就该重构了。

我个人习惯是:写条件逻辑时,先问自己三个问题——这个条件能不能拆?这些条件能不能合并?能不能用卫语句提前返回?问完这三个问题,代码质量基本就有保障了。


公众号:蓝海资料掘金营,微信deep3321