摘要:当状态机没有统一的退出清理契约时,完成标志、守卫变量和阶段残留会沿着流程一路串台,最后演变成偶发又难复现的动作错误。
这篇短文聚焦一个在流程控制代码里反复出现的问题:状态切走了,但标志没切干净。文章把它上升成设计层面的缺陷,而不是停留在“再加一个守卫变量”这种补丁式修法上。
状态机退出不清理标志,下一状态就会捡到上一状态的烂摊子
状态机类 bug 里,最容易反复出现的一类,不是转移条件写错,而是“上一个状态留下来的痕迹还在”。
这类问题的表面现象很混乱:
- 本状态的判断逻辑看起来没错
- 执行器动作有时正常、有时直接跳过
- 加一个守卫标志后暂时又好了
真正的问题,其实是状态之间缺少统一的清理契约。
为什么残留标志会这么麻烦
因为状态切换并不等于语义切换完成。
如果某个 done 标志、motion_seen 标志或阶段变量仍然保留着上一个状态的结果,那么下一个状态一读取,就可能把“旧完成”当成“新完成”。
于是你会看到一种非常典型的现象:
- 新状态刚进来就直接通过判断
- 实际动作根本没发生
- 流程却已经进入下一步
机械系统里,这类问题尤其危险,因为逻辑上“已经完成”和物理上“还没归位”可能会直接撞在一起。
为什么补一个守卫变量治不了根
守卫变量当然有用,特别是在需要确认“动作真的启动过”时。
但如果每次出现残留都只是再加一个:
xxx_motion_seenxxx_startedxxx_guard
那实际上是在用更多局部状态,去掩盖状态退出时没有统一收尾这件事。
时间一长,代码就会变成:
- 每个状态都有自己的清理习惯
- 每个流程都靠局部经验规避历史问题
- 谁该负责重置哪些标志,没有统一答案
更像工程方案的做法
状态机如果已经复杂到依赖很多跨阶段标志,就应该尽量补齐明确的生命周期:
enterrunexit
至少要让退出阶段负责清理那些不应该跨状态继承的局部执行痕迹。
这样做不是为了“面向对象好看”,而是为了给标志的生死边界一个固定归属。
一句话总结
只要状态退出没有统一清理,下一状态迟早会读到上一状态留下的旧世界。
Comments NOTHING