软件系统本质上运行在一个不完全可控的环境中,其核心风险来源于:
防御式编程的本质:
对系统中"不可控不确定性"的识别、隔离与控制。
失败必然性:
系统设计必须接受:
因此:
要素关系:
| 前置要素 | 后继要素 | 关系 |
|---|---|---|
| 边界定义 | 输入校验 | 边界定义决定校验范围 |
| 输入校验 | 状态守护 | 校验阻止污染数据破坏不变量 |
| 状态守护 | 传播阻断 | 不变量被破坏时启动阻断 |
| 故障应对 | 传播阻断 | 应对策略决定阻断行为 |
为什么需要: 外部世界不可控,其语义不被内部系统信任。
原理: 全局校验成本过高,边界是唯一的校验点。边界内默认可信,大幅降低内部调用成本。
为什么需要: 外部数据携带语义污染,一旦进入可信域,会以不可预测的方式破坏系统状态。
原理: 校验的本质是"翻译"——将不可信外部语义转换为可信内部语义。边界处拒绝的故障定位成本远低于内部调试。
为什么需要: 正确性来源于不变量,不变量被破坏意味着系统进入未定义状态。
原理: 延迟检测意味着更大的修复成本。断言处理"不该发生",检查处理"需要应对"。
为什么需要: 错误不可避免,必须预设响应策略。健壮性与正确性存在根本矛盾。
原理: 不存在普适最优解,不同场景需要不同的权衡取舍。
为什么需要: 局部错误可能演变为系统性故障,传播控制的关键在于隔离。
原理: 容错优于崩溃。部分功能丧失好过整体瘫痪。
防御式编程不应是线性分层,而应是:
时间维度 × 系统层级 的二维模型
错误的发生是时空交错的——错误可能在数据层产生,在控制层检测,在系统层传播。防御机制必须能在这个二维空间中定位自己,才能避免遗漏或重叠。
事前(Prevention) → 事中(Detection & Handling) → 事后(Recovery & Learning)
数据层 → 领域层 → 控制层 → 系统层
| 事前(预防) | 事中(处理) | 事后(恢复/治理) | |
|---|---|---|---|
| 数据层 | 输入验证、Schema约束 | 数据清洗、兜底值 | 数据修复、补偿 |
| 领域层 | 不变量设计、类型系统 | 断言、状态校验 | 状态回滚 |
| 控制层 | 接口契约、幂等设计 | 异常处理、重试 | 流程补偿 |
| 系统层 | 隔离设计、限流 | 熔断、降级 | 自动恢复、监控 |
| 类型 | 本质 | 示例 |
|---|---|---|
| 输入错误 | 非法或污染数据 | 参数非法 |
| 状态错误 | 不变量被破坏 | 数据不一致 |
| 时序错误 | 顺序/并发问题 | 并发覆盖 |
| 资源错误 | 资源耗尽 | OOM、超时 |
| 依赖错误 | 外部系统失败 | RPC失败 |
| 维度 | 分类 |
|---|---|
| 可恢复性 | 可恢复 / 不可恢复 |
| 确定性 | 确定性 / 随机性 |
| 影响范围 | 局部 / 全局 |
失败类型 → 防御策略 → 恢复机制
例如:
本质:信任边界控制
外部语义(不可信)→ 翻译层 → 内部语义(可信)
防止:语义污染(Semantic Corruption)
本质:不变量守护
不变量是系统在任何时刻都必须保持为真的陈述,一旦为假则系统进入未定义状态
本质:失败传播控制
| 场景 | 策略 |
|---|---|
| 可恢复错误 | 重试 |
| 临时错误 | 延迟 |
| 不可恢复 | Fail-fast |
| 高风险依赖 | 熔断 |
| 非核心功能 | 降级 |
本质:阻断错误传播
防止局部错误演变为系统性故障
本质:系统自愈能力
防错设计(Design-time)
↓
防御式编程(Runtime Protection)
↓
容错系统(Fault Tolerance)
↓
可观测性(Observability)
↓
系统演进(Evolution)
转化为:
驱动:
| 概念 | 阶段 | 本质 |
|---|---|---|
| 防错设计 | 设计期 | 避免错误产生 |
| 防御式编程 | 运行期 | 控制错误传播 |
| 容错系统 | 系统级 | 保证系统存活 |
三者关系:
预防 → 控制 → 生存
防御式编程的核心权衡在于健壮性与正确性的取舍:
| 概念 | 定义 | 核心行为 |
|---|---|---|
| 健壮性 (Robustness) | 系统在异常输入或环境下仍能运行,哪怕输出不准确或不完整 | 包容错误,继续运行 |
| 正确性 (Correctness) | 永不返回错误结果,一旦出现异常则不返回结果或终止程序 | 拒绝错误,精确失败 |
核心矛盾:
| 场景 | 策略 |
|---|---|
| 核心链路 | 强校验 + Fail-fast |
| 非核心链路 | 降级优先 |
| 高风险输入 | 强验证 |
| 内部可信调用 | 弱防御 |
核心思想:
防御不是越多越好,而是"在正确的位置防御选择正确的防御方式"。
减少运行时防御成本
输入防御前移