首页/开发/代码质量与评审速查/不变量与前后置条件(Design by Contract 视角)
设计原则与模式62 条

不变量与前后置条件(Design by Contract 视角)

把约束写成可检查的三元组:前置条件(调用方义务)、后置条件(实现方承诺)、不变量(对象恒真约束);写进测试与断言后,重构与扩展时错误更早暴露。

不变量与前后置条件是什么

把约束写成可检查的三元组:前置条件是调用方必须满足的义务,后置条件是实现方给出的承诺,不变量是对象在整个生命周期内恒真的约束。这是 Design by Contract 的基本视角(Bertrand Meyer 在 Eiffel 中系统化)。把三者写进测试与断言后,重构与扩展时错误会更早暴露,而不是等到数据出问题才发现。

基本信息

前置条件调用方必须满足,否则行为未定义
后置条件返回时必须成立的保证
不变量对象生命周期内始终成立
落地形式断言、测试用例、构造函数校验、数据库约束

常见的可写约束

  • 转账金额必须为正,且转出与转入账户不同
  • 转账前后两边余额之和保持不变(守恒量)
  • 账户余额任何时刻不得为负
  • 购物车条目数不超上限,金额合计等于各项之和
  • 状态机只允许沿合法边迁移(草稿 → 已提交 → 已支付)
  • 时间区间必须满足开始早于结束

约束示例

约束示例
transfer(from, to, amount):
  前置:from != to
        amount > 0
        from.balance >= amount
  后置:from.balance + to.balance 不变
        from.balance >= 0 且 to.balance >= 0
  不变量:任一账户 balance >= 0

# 落地:前置作为参数校验,后置与不变量写进断言与测试
assertThat(from.balance().add(to.balance()))
    .isEqualTo(balanceBefore);

三者对照

要素责任方检查时机
前置条件调用方入口处校验,失败即拒绝
后置条件实现方返回前断言,测试中固定验证
不变量对象自身每次状态变更后保持
守恒量流程整体跨操作的组合测试
只写文档不写测试的约束会逐渐失真:代码演进时没人核对文档,约束悄悄被违反。把关键不变量的检查写进测试,违约时会直接红灯,比任何文档都可靠。
把不变量当作运行时全量校验会让热点路径变慢:每次状态变更都遍历全部元素并不划算。常用折中是核心路径常开、冷路径按开关启用,或改抽样校验加离线核对任务。

易错点

  • 只写文档不写测试:约束无人执行,逐渐失真且无人察觉
  • 把不变量当校验点全量运行时检查:热点路径成本高,应改抽样与断言
  • 前置条件写成模糊描述:调用方不知道边界在哪,错误只能在下游暴露
  • 用异常吞掉违约:约束被破坏却不报警,数据问题延后暴露更难排查

数据来源与参考

  • Bertrand Meyer · Design by Contract(Eiffel 语言首次系统化)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌