断言是开发阶段的逻辑探针,专用于拦截本不该出现的根本性错误,不处理可预期异常;只校验“不可能”状态,如未初始化即调用、循环后不变量破坏、枚举遗漏、私有方法内部状态非法等。

在复杂流程中,条件断言(assert)不是用来兜底运行时异常的,而是作为开发阶段的“逻辑探针”,专用于拦截那些**本不该出现、一旦出现就说明代码存在根本性设计或实现错误**的状态。它不处理用户输入错误、网络超时或文件缺失这类可预期异常,而是守住程序员自己写的契约底线。
明确断言的适用边界:只守“不可能”,不管“有可能”
复杂流程常包含多阶段状态、嵌套分支和异步协同,容易因逻辑疏漏导致非法跃迁——比如订单对象尚未初始化就调用发货方法,或任务已关闭却再次触发重试。这类问题不是“会出错”,而是“绝不能发生”。断言正适合在此类节点设防:
- 方法入口处校验前置条件:如
assert isInitialized() : "order must be initialized before processing" - 关键分支出口前验证不变量:如循环结束后
assert items.size() == processedCount : "processed count mismatch" - switch 枚举处理中补全 default 断言:确保所有已知状态都被显式覆盖,避免遗漏新增枚举值
- 私有工具方法内部状态自检:如缓冲区操作前
assert buffer != null && buffer.position()
配合状态机与契约式设计,让断言真正起效
单纯加几条 assert 很难在复杂流程中持续发挥作用。必须把它嵌入更严谨的设计结构里:
- 为流程定义清晰的状态枚举(如
CREATED、PROCESSING、COMPLETED、FAILED),每个公开方法在执行前用 assert 检查当前状态是否满足该方法的契约(例如retry()要求状态为FAILED) - 提供配套的
canXxx()查询方法(如canRetry()),让调用方能主动判断而非依赖 try-catch 捕获 AssertionError - 在多线程场景下,状态检查与变更需原子化——用
AtomicBoolean.compareAndSet或同步块包裹,避免两个线程同时通过 assert 却并发修改状态
确保断言在开发阶段真实生效的关键操作
断言默认关闭,写得再准也形同虚设。必须让团队统一开启并纳入日常开发习惯:
- 运行时务必添加 JVM 参数
-ea(或-enableassertions),IDE 启动配置、单元测试命令、集成测试脚本均需显式设置 - 按包粒度精细控制:如
-ea:com.example.workflow -da:com.example.workflow.test,避免测试工具类干扰主流程断言 - 断言表达式严禁含副作用:不能在其中调用修改状态的方法、发 HTTP 请求或写日志,否则关闭断言后行为会突变
- 错误信息要具体:不要只写
"invalid state",而应说明期望值、实际值和上下文,例如"expected state RUNNING, but got STOPPED at step 'validatePayment' "
区分断言与业务校验,避免混用踩坑
复杂流程中常有人把参数非空检查、范围校验等直接写成 assert,这是典型误用:
-
错误做法:
assert orderId > 0 : "order ID must be positive"—— 这是外部传入的业务参数,可能因前端 bug 或恶意请求而非法,应走if (orderId -
正确做法:
assert result != null : "payment gateway callback returned null unexpectedly"—— 这是内部组件本应保证不为空的返回值,为空即代表底层逻辑缺陷 - 记住:assert 是对“自己代码”的信任投票;业务异常是对“外部世界”的防御工事











