简化条件逻辑(上):分解条件表达式、合并条件表达式、以卫语句取代嵌套条件表达式

条件逻辑,说白了就是代码里的 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;
}

你看,每个条件现在都有一个名字。读代码的时候,你不需要去理解那个布尔表达式到底在算什么,直接看方法名就够了。

我的习惯:只要条件表达式超过 3 个逻辑运算符,我就考虑拆成独立方法。这样调试的时候也方便——你可以单独测试每个条件。

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;
}

你看,现在逻辑是平铺的。每个异常情况提前返回,剩下的就是正常流程。读起来就像在念一个清单:先检查这个,再检查那个,都没问题就执行。

核心思想:卫语句强调的是「保护性检查」。把异常情况、边界条件提前处理掉,让主流程保持清晰。我一般把卫语句放在方法开头,一眼就能看到所有「提前退出」的条件。

知识体系总览

这三种手法其实解决的是同一个问题:让条件逻辑更容易理解。它们各自适用的场景不同,我画了张图帮你理清思路:

简化条件逻辑三大手法 条件逻辑重构 分解条件表达式 复杂条件 → 独立方法 每个条件有明确命名 适用:条件表达式过长 合并条件表达式 相同结果 → 合并判断 用 || 连接多个条件 适用:多个分支结果相同 卫语句取代嵌套 异常提前返回 主流程平铺展开 适用:嵌套层级过深 核心原则:让条件逻辑「自解释」,减少阅读者的认知负担

实际应用中的选择

这三种手法不是互斥的。在实际项目中,我经常把它们组合使用。比如:

  1. 先看嵌套深度:如果超过两层,优先用卫语句拆平
  2. 再看条件复杂度:如果单个条件很长,用分解手法
  3. 最后看重复性:如果有多个条件返回相同结果,用合并手法

我曾经在一个支付网关项目里,把一个 80 行的条件判断方法,用这三种手法重构成了 5 个小方法。每个方法不超过 10 行。后来新同事接手,说「这代码读起来像说明书」。

避坑指南:不要为了重构而重构。如果一段条件逻辑只有两三层,而且业务含义很清晰,那就没必要动它。重构的目的是让代码更容易理解,不是炫技。

嗯,这三种手法就先聊到这儿。下次遇到复杂的 if-else,不妨试试看。你会发现,条件逻辑其实没那么可怕。


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