卫语句的核心是将前置校验提前返回,使主逻辑保持顶层缩进以降低认知负荷;需独立成行、不嵌套、按失败概率排序,避免else包裹,慎用optional作输入,controller中分层校验并记录明确日志。

卫语句不是为了少写if,而是为了让主逻辑浮上来
多层 if 嵌套真正让人头疼的,从来不是缩进本身,而是主业务逻辑被埋在层层守卫之下,读代码时得先“解压”三重条件才能看到真正干活的那几行。卫语句的核心作用,是把提前退出的判断拎到函数开头,让正常流程保持在顶层缩进——这不是炫技,是降低认知负荷。
常见错误现象:if (obj != null) { if (obj.isValid()) { if (obj.isReady()) { ... }}} 这种写法看似安全,但一旦新增校验(比如加个权限检查),就得再套一层,主逻辑继续下沉。
实操建议:
- 每个卫语句只做一件事:校验 + 提前返回(
return、throw或return Optional.empty()) - 拒绝用
else包裹主逻辑——卫语句之后直接写主干,不缩进 - 校验顺序按“失败概率从高到低”排,比如先判
null,再判状态,最后判业务规则
Java里怎么写才算真正的卫语句(不是if-else变形)
关键看两点:是否独立成行、是否不嵌套。很多同学把 if (x == null) return; 放在开头,但后面立刻跟个 if (y == null) { ... },这就又掉回坑里了。
立即学习“Java免费学习笔记(深入)”;
正确姿势是把所有前置拦截条件都拆成单独的卫语句,中间不夹杂业务逻辑:
public String processOrder(Order order) {
if (order == null) throw new IllegalArgumentException("order must not be null");
if (!order.hasItems()) return "empty";
if (order.getCustomer() == null) throw new IllegalStateException("customer missing");
if (order.getTotal() <= 0) return "invalid total";
// ✅ 主逻辑在这里,顶格,无缩进
return order.ship().confirm();
}
注意:throw 和 return 都算合法退出,但别混用风格;同一函数里统一用异常还是用空值/默认值返回,否则调用方很难处理。
Optional和卫语句一起用容易翻车的三个点
很多人想用 Optional 替代空值检查,结果写出 optional.map(...).orElseThrow() 这种嵌套,反而更难读。卫语句和 Optional 不是互斥关系,但搭配时有陷阱。
常见错误现象:Optional.ofNullable(user).filter(u -> u.isActive()).map(...) 写在方法开头,本意是校验,实际却把逻辑耦合进了构建链里。
实操建议:
- 卫语句阶段尽量用原始值判断,别过早包装成
Optional——user == null比Optional.ofNullable(user).isEmpty()直观多了 - 如果参数本身就是
Optional<t></t>,卫语句用isPresent()没问题,但别紧接着用get(),改用orElseThrow()或直接if (!opt.isPresent()) return; -
Optional更适合用在返回值上,而不是作为卫语句的输入载体
卫语句在Spring Controller里怎么避免重复校验
Controller 层经常要校验参数、登录态、权限,全堆在方法里会臃肿。但直接扔进 AOP 或自定义注解,又容易让校验逻辑散落各处,调试困难。
推荐做法是分层拦截:基础非空和格式校验走 @Valid + DTO,业务规则类卫语句留在 Controller 方法体内。
实操建议:
-
@RequestBody @Valid OrderRequest req处理字段级约束(@NotNull、@Min),这部分交给框架 - Controller 方法内只留“跨字段”或“依赖上下文”的判断,比如:
if (!userService.canModify(req.getUserId(), req.getOrderId())) throw new AccessDeniedException(...); - 别把数据库查询塞进卫语句(如
if (repo.findById(id).isEmpty()) return;),这属于业务逻辑,应归入主流程或 Service 层
最常被忽略的是日志:每个卫语句的 throw 最好带明确错误码和上下文,比如 throw new BadRequestException("ORDER_INVALID_STATUS:" + order.getStatus());,否则线上出问题时只能靠猜。










