Predicate必须是纯函数,只返回布尔值且无副作用;错误做法是掺杂状态变更,正确做法是命名规范、静态检查和逻辑分层。

因为 predicate 的职责必须严格限定在「判断」,一旦掺杂执行、副作用或状态变更,状态机的可预测性就立刻崩塌。
Predicate 是状态跃迁的“守门人”,不是“操盘手”
在状态机设计中,predicate 只负责回答一个问题:“当前条件下,是否允许这次跃迁?” 它必须是纯函数:输入确定 → 输出确定 → 无任何外部影响。
- ✅ 正确用法:判断订单是否满足发货条件(isOrderPayedAndInventoryAvailable()),只返回 true/false
- ❌ 危险写法:在判断过程中调用库存扣减接口、更新数据库字段、触发日志埋点——这已越界为 process 或 eventHandler 的职责
- 混淆后果:同一 predicate 多次调用结果不一致(如库存被并发扣减)、单元测试无法稳定复现、状态跃迁逻辑与业务副作用耦合,导致调试成本指数上升
越过这条红线,状态机就退化成“带 if 的面条代码”
现代高阶框架(如 Zustand + Zustand-middleware、Rematch、Spring StateMachine)依赖 predicate 构建声明式跃迁规则。若 predicate 不守界,框架的可观测性、回滚能力、事件审计都会失效。
- 例如:Zustand 中用 when: (state) => state.status === 'ready' 控制 action 执行时机;若此处混入 fetchData(),则该 action 就不再幂等,重试机制失效
- 再如:Spring StateMachine 的 .when(()-> order.isShippable()) 若内部修改了 order.shippedAt 字段,下一次状态检查就可能跳过合法跃迁路径
- 根本问题:框架靠 predicate 做静态校验和路径预判,而副作用让判断失去“静态性”
如何守住这条红线?三个落地动作
不需要抽象理论,只需在日常编码中坚持三件事:
- 命名即契约:所有 predicate 函数名必须以 isXxx、canXxx、shouldXxx 开头(如 canTransitionToShipped),禁止出现 do、update、send 等动词
- 禁止副作用检查:在 CI 阶段加入 ESLint 规则(如 no-side-effects-in-predicate)或静态扫描脚本,拦截 predicate 内部对 store.dispatch、API 调用、DOM 操作、全局变量赋值等行为
- 跃迁逻辑分层显式标注:用注释或类型系统明确区分三类函数——predicate(仅布尔返回)、process(执行动作)、eventHandler(生命周期钩子),避免语义模糊











