状态机退出不清理标志,下一状态就会捡到上一状态的烂摊子

Babel36acl 工程复盘 无~ 26 次阅读 预计阅读时间: 7 分钟 发布于 4 天前 最后更新于 1 小时前 1581 字


摘要:当状态机没有统一的退出清理契约时,完成标志、守卫变量和阶段残留会沿着流程一路串台,最后演变成偶发又难复现的动作错误。

这篇短文聚焦一个在流程控制代码里反复出现的问题:状态切走了,但标志没切干净。文章把它上升成设计层面的缺陷,而不是停留在“再加一个守卫变量”这种补丁式修法上。

状态机退出不清理标志,下一状态就会捡到上一状态的烂摊子

状态机类 bug 里,最容易反复出现的一类,不是转移条件写错,而是“上一个状态留下来的痕迹还在”。

这类问题的表面现象很混乱:

  • 本状态的判断逻辑看起来没错
  • 执行器动作有时正常、有时直接跳过
  • 加一个守卫标志后暂时又好了

真正的问题,其实是状态之间缺少统一的清理契约。

为什么残留标志会这么麻烦

因为状态切换并不等于语义切换完成。

如果某个 done 标志、motion_seen 标志或阶段变量仍然保留着上一个状态的结果,那么下一个状态一读取,就可能把“旧完成”当成“新完成”。

于是你会看到一种非常典型的现象:

  • 新状态刚进来就直接通过判断
  • 实际动作根本没发生
  • 流程却已经进入下一步

机械系统里,这类问题尤其危险,因为逻辑上“已经完成”和物理上“还没归位”可能会直接撞在一起。

为什么补一个守卫变量治不了根

守卫变量当然有用,特别是在需要确认“动作真的启动过”时。

但如果每次出现残留都只是再加一个:

  • xxx_motion_seen
  • xxx_started
  • xxx_guard

那实际上是在用更多局部状态,去掩盖状态退出时没有统一收尾这件事。

时间一长,代码就会变成:

  • 每个状态都有自己的清理习惯
  • 每个流程都靠局部经验规避历史问题
  • 谁该负责重置哪些标志,没有统一答案

更像工程方案的做法

状态机如果已经复杂到依赖很多跨阶段标志,就应该尽量补齐明确的生命周期:

  • enter
  • run
  • exit

至少要让退出阶段负责清理那些不应该跨状态继承的局部执行痕迹。

这样做不是为了“面向对象好看”,而是为了给标志的生死边界一个固定归属。

一句话总结

只要状态退出没有统一清理,下一状态迟早会读到上一状态留下的旧世界。

此作者没有提供个人介绍。
最后更新于 2026-05-30