防御式编程与快速失败
防御式编程检查输入与状态,快速失败则在错误发生点立即终止:构建期能拦的不留到运行期,能抛异常的不静默返回。它和契约式设计互补——进入系统时严格校验,内部调用之间信任契约,避免层层重复防御。
基本信息
| 边界策略 | 外部输入严格校验,失败立即暴露 |
|---|---|
| 内部策略 | 信任内部契约,避免重复防御 |
| 常用手段 | 前置条件检查、参数断言、显式异常 |
| 反模式 | 静默兜底、把异常当控制流 |
| 收益 | 错误在源头暴露,栈信息指向真正位置 |
典型使用场景
- 处理用户输入与外部接口返回时校验字段
- 构造函数禁止非法状态进入对象
- 解析配置时对缺失的必填项直接失败
- 状态机里拒绝非法状态转移
- 内部纯函数之间不做重复判空
失败方式对照
失败方式对照
// 静默兜底:错误数据进入下游,问题延后暴露
if (qty == null) qty = 1;
// 快速失败:就地拦截,栈信息指向源头
Objects.requireNonNull(qty, "qty required");
if (qty <= 0) throw new IllegalArgumentException("qty must be positive");在哪一层校验
| 位置 | 做法 | 说明 |
|---|---|---|
| 边界入口 | 严格校验,快速失败 | 外部数据不可信 |
| 领域内部 | 校验不变式与状态 | 保证对象始终合法 |
| 内部调用之间 | 信任契约,不重复校验 | 减少臃肿与重复代码 |
快速失败的价值在定位:错误在离源头最近处抛出,日志与堆栈直接指向问题;静默兜底会把问题推到几层之后,排查成本成倍上升。
不要用异常处理可预期的分支:这类控制流会让代码难读、性能也受影响;可预期的情况用返回值或结果对象表达更清楚。
校验边界通常只有几处:外部接口、用户输入、配置文件与消息体。把这些入口做实,内部代码就能按契约书写,重复判空会明显减少。