在业务代码开发中,我们经常会遇到需要根据多个条件分支执行不同逻辑的场景,如果直接堆叠多层if-else或者嵌套switch语句,很容易让代码的认知复杂度急剧上升,后续接手的开发者需要花费大量时间梳理逻辑链路,也更容易引入新的bug。

什么是代码认知复杂度
认知复杂度是衡量代码可读性的重要指标,它主要评估开发者理解一段代码需要付出的心智成本。嵌套的条件判断、复杂的循环逻辑、过多的分支跳转都会提升认知复杂度,其中多层嵌套的条件判断是最常见的影响因素。
一般来说,当条件判断的嵌套层级超过3层时,普通开发者理解这段逻辑的时间会成倍增加,代码的维护成本也会随之上升。
常见的嵌套条件判断问题场景
我们先看一段典型的多层嵌套条件判断代码,这是一段用户下单的校验逻辑:
public boolean checkOrderValid(Order order) {
if (order != null) {
if (order.getUserId() != null) {
if (order.getAmount() > 0) {
if (order.getStatus() == OrderStatus.UNPAID) {
if (order.getProductList() != null && !order.getProductList().isEmpty()) {
return true;
}
}
}
}
}
return false;
}
这段代码嵌套了5层条件判断,要理解它的逻辑需要逐层梳理每个条件的判断顺序,一旦后续需要新增校验规则,很容易改错位置。
优化嵌套条件判断的实用方法
1. 提前返回减少嵌套
提前返回是最直接的优化方式,把不符合条件的场景先判断并返回,剩下的逻辑就是正常流程,不需要再嵌套。我们对上面的代码做优化:
public boolean checkOrderValid(Order order) {
// 订单为空直接返回无效
if (order == null) {
return false;
}
// 用户ID为空直接返回无效
if (order.getUserId() == null) {
return false;
}
// 订单金额不合法直接返回无效
if (order.getAmount() <= 0) {
return false;
}
// 订单状态不是未支付直接返回无效
if (order.getStatus() != OrderStatus.UNPAID) {
return false;
}
// 商品列表为空直接返回无效
if (order.getProductList() == null || order.getProductList().isEmpty()) {
return false;
}
// 所有校验通过返回有效
return true;
}
优化后代码没有嵌套,每个条件判断都是独立的,逻辑链路非常清晰,新增校验规则只需要在对应位置添加判断即可,不会影响到其他逻辑。
2. 合并同类条件
如果多个条件是并列关系,没有先后依赖,可以把它们合并成一个判断,减少判断的层级。比如上面的校验逻辑中,多个前置校验没有依赖关系,也可以合并为:
public boolean checkOrderValid(Order order) {
// 合并所有前置校验条件
if (order == null
|| order.getUserId() == null
|| order.getAmount() <= 0
|| order.getStatus() != OrderStatus.UNPAID
|| order.getProductList() == null
|| order.getProductList().isEmpty()) {
return false;
}
return true;
}
这种方式适合条件之间没有先后执行顺序的场景,能让代码更简洁,不过如果条件过多,建议换行书写,避免单行代码过长。
3. 使用策略模式拆分复杂分支
如果条件判断的分支逻辑非常复杂,每个分支都有大量的业务代码,可以使用策略模式把每个分支的逻辑封装成独立的策略类,通过映射关系调用对应的策略,避免在一个方法里堆砌大量嵌套逻辑。
比如我们有不同类型的用户,计算折扣的逻辑完全不同,原来的嵌套代码可能是:
public double calculateDiscount(User user, double amount) {
if (user != null) {
if (user.getType() == UserType.VIP) {
// VIP用户折扣逻辑,大量代码
return amount * 0.8;
} else if (user.getType() == UserType.SVIP) {
// SVIP用户折扣逻辑,大量代码
return amount * 0.7;
} else if (user.getType() == UserType.NORMAL) {
// 普通用户折扣逻辑,大量代码
return amount * 0.95;
}
}
return amount;
}
使用策略模式优化后:
// 折扣策略接口
public interface DiscountStrategy {
double calculate(double amount);
}
// VIP折扣策略
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double amount) {
// VIP用户折扣逻辑
return amount * 0.8;
}
}
// SVIP折扣策略
public class SvipDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double amount) {
// SVIP用户折扣逻辑
return amount * 0.7;
}
}
// 普通用户折扣策略
public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double amount) {
// 普通用户折扣逻辑
return amount * 0.95;
}
}
// 折扣策略上下文
public class DiscountContext {
private Map<UserType, DiscountStrategy> strategyMap = new HashMap<>();
public DiscountContext() {
strategyMap.put(UserType.VIP, new VipDiscountStrategy());
strategyMap.put(UserType.SVIP, new SvipDiscountStrategy());
strategyMap.put(UserType.NORMAL, new NormalDiscountStrategy());
}
public double calculateDiscount(User user, double amount) {
if (user == null) {
return amount;
}
DiscountStrategy strategy = strategyMap.get(user.getType());
if (strategy == null) {
return amount;
}
return strategy.calculate(amount);
}
}
优化后每个分支的逻辑都在独立的策略类里,主方法的逻辑非常简洁,新增用户类型只需要新增对应的策略类,不需要修改原有代码,符合开闭原则。
4. 使用卫语句处理异常场景
卫语句和提前返回类似,专门用来处理异常场景或者边界条件,把异常情况先拦截掉,剩下的就是核心正常逻辑。比如处理接口请求参数的场景:
def handle_request(params):
# 卫语句处理参数异常
if not params:
return {"code": 400, "msg": "参数不能为空"}
if "user_id" not in params:
return {"code": 400, "msg": "缺少用户ID参数"}
if "action" not in params:
return {"code": 400, "msg": "缺少操作类型参数"}
# 核心业务逻辑
user_id = params["user_id"]
action = params["action"]
# 执行对应操作
return {"code": 200, "msg": "操作成功", "data": result}
这种方式能让核心业务逻辑和异常处理逻辑分离,代码结构更清晰。
优化后的效果验证
我们可以通过认知复杂度的计算规则来验证优化效果,常见的认知复杂度计算规则中,每多一层嵌套加1分,每个if、else、switch分支加1分。优化前的订单校验代码嵌套5层,有5个if分支,认知复杂度是5+5=10分;优化后的提前返回版本没有嵌套,5个if分支,认知复杂度是5分,降低了一半。
在实际开发中,我们不需要追求极致的低复杂度,只要把复杂度控制在合理范围内,让代码逻辑清晰易懂即可。一般来说,单个方法的认知复杂度建议不超过10分,超过的话就需要考虑做拆分或者优化了。
注意事项
- 优化时不要为了降低复杂度而过度设计,比如简单的两层条件判断不需要强行用策略模式,反而会增加代码量
- 合并条件时要注意条件的逻辑关系,不要改变原有的判断逻辑,避免引入bug
- 优化后建议补充对应的单元测试,覆盖所有条件分支,确保逻辑和原来一致