在开发业务代码时,最常出现的一类问题往往不是复杂的算法,而是重复的判断逻辑。初期,我们可能会觉得多次判断更严谨、更安全。但随着项目迭代,你会逐渐发现:
- 相同的判断散落在各个角落。
- 修改一个通用规则,需要翻查大量文件。
- 某些判断的初衷,连编写者自己都难以记清。
问题的根源往往不在于业务本身有多复杂,而在于许多本应统一的判断逻辑,被错误地分散在了具体的业务代码中。
一、典型场景:无处不在的“参数判空”
几乎每个项目里都能看到类似的代码:
if (id == null) {
throw new RuntimeException("id 不能为空");
}
if (name == null || name.isBlank()) {
throw new RuntimeException("name 不能为空");
}
单独看,每个方法里写一两行似乎无伤大雅。但当这类校验出现在每一个新增、修改、查询方法中时,代码的“噪音”就会急剧增大,严重干扰对核心业务逻辑的理解。实际上,这类校验完全可以被前置并统一处理,而不应侵入业务逻辑本身。
二、可统一处理的第一类:参数合法性校验
参数合法性(如是否为空、长度、数值范围)本质上是输入验证问题,而非核心业务问题。
- 它们通常与具体业务场景无关。
- 校验规则相对稳定。
- 目标应是保证传递给业务逻辑的参数是可信的。
将这些校验从业务方法中剥离,是保持后端与架构代码整洁的第一步。
三、第二类:数据存在性校验
另一种常见模式是,业务方法一开始就查询并判断数据是否存在:
Pet pet = petMapper.selectById(id);
if (pet == null) {
throw new RuntimeException("数据不存在");
}
这看似合理,却导致了多个问题:重复的数据库查询、散落各处的相同判断逻辑、不统一的异常信息。更糟糕的是,可能会遗漏校验或出现不一致的校验逻辑。“数据是否存在”应被视为一个跨业务的通用前置条件,而非单个业务的专属逻辑。
四、第三类:状态合法性校验
这是业务代码中最易膨胀的一类判断。起初可能只是一个简单的状态检查:
if (status != Status.INIT) {
throw new RuntimeException("当前状态不允许操作");
}
随后,A操作需要判断,B操作也需要判断,且条件可能略有不同。如果将这些状态流转规则完全散落在各处,后期维护和保证一致性将变得异常困难。状态管理本身也是一种算法与数据结构问题,需要清晰的定义和集中的控制。
五、哪些判断不应统一处理?
必须明确,并非所有判断都适合统一处理。以下情况应保留在业务代码中:
六、统一处理后的业务代码
将通用判断抽离后,一个典型的业务方法会变得更清晰:
public void update(UpdatePetDTO dto) {
Pet pet = loadAndCheck(dto.getId()); // 统一的数据加载与存在性检查
checkUpdatable(pet); // 统一的状态可更新性检查
doUpdate(pet, dto); // 纯净的核心业务逻辑
}
代码的变化不在于行数减少,而在于每一行都具有明确的业务含义。没有了重复的防御性代码,逻辑主干清晰,问题定位也更迅速。
七、一个实用的判断标准
如果你在编写一个判断时,内心产生了“这个判断在其他地方好像也写过”的想法,那么它很可能就应该被统一。
这里有一个更有效的思考框架:这个判断,是“所有相关操作都必须遵守的规则”,还是“仅当前这个功能需要的规则”?
- 所有人都要遵守 → 适合统一(如“ID不能为空”)。
- 只有这个功能需要 → 留在业务里(如“首次收养且用户未成年”)。
这个简单的原则在实践中具有很高的指导价值。
八、为什么统一处理反而更安全?
有人担心统一处理会丧失灵活性。事实上恰恰相反。当判断逻辑被集中、明确定义并拥有固定入口后,你能够更清晰地掌控:
- 哪些规则是全局性的。
- 规则改动会影响到哪些范围。
- 避免了因“各自为政”导致的规则遗漏或矛盾。在涉及并发和状态管理的复杂场景中,集中控制是保障网络与系统一致性的关键。
总结
业务代码变得混乱,往往并非因为业务本身复杂,而是因为过多本应统一的、通用的判断逻辑侵入了业务核心。将这些“守卫逻辑”从业务代码中剥离,留下的才是真正需要你专注思考和实现的业务本质。