第三章 状态图绘制:从草稿到规范
状态图这东西,说白了就是状态机的「设计蓝图」。我见过太多工程师,代码写得飞起,但一问他状态图长什么样,立马支支吾吾。嗯,这其实是个大坑——没有状态图,你连自己设计的是什么都说不清楚。
今天我们就聊聊状态图怎么画。不是那种教科书式的枯燥讲解,而是我这些年摸爬滚打总结出来的实战经验。
3.1 状态图的基本元素
先说说状态图里都有什么。你想想看,状态机无非就是「状态」和「跳转」这两件事。所以状态图的基本元素,其实就四个:
- 状态(State):用圆角矩形表示。里面写上状态的名字,比如 IDLE、READ、WRITE。
- 初始状态(Initial State):一个实心圆点。表示状态机从哪儿开始跑。
- 转移(Transition):带箭头的线。线上标注触发条件和输出动作。
- 终止状态(Final State):一个实心圆点外面再套个圆圈。表示状态机结束。
我在项目中遇到过一件事:有个同事画状态图,把所有状态都画成圆圈,结果大家分不清哪个是初始状态。后来我规定团队里必须用实心圆点表示初始状态,这才统一了规范。
核心要点:状态图不是艺术创作,是工程文档。每个元素都要有明确的含义,别搞花里胡哨的。
3.2 绘制规范
规范这东西,很多人觉得是束缚。但我告诉你,没有规范的状态图,比没有状态图更可怕。
3.2.1 状态命名规范
我个人习惯用大写字母加下划线,比如:
IDLE // 空闲状态
READ_DATA // 读数据状态
WRITE_DATA // 写数据状态
ERROR_HAND // 错误处理状态
为什么这么搞?因为代码里也这么写,状态图和代码一一对应,调试的时候一眼就能对上。
3.2.2 转移标注规范
转移线上的标注,格式一般是:
触发条件 [保护条件] / 输出动作
举个例子:
rx_valid == 1 [data_ready] / data_out <= rx_data
意思是:当 rx_valid 为 1 时,如果 data_ready 也为真,就跳转,同时把 rx_data 赋值给 data_out。
小技巧:如果转移条件比较复杂,我建议单独写一个条件表,状态图上只写条件编号。这样图不会太乱。
3.2.3 布局规范
- 状态尽量从左到右、从上到下排列
- 避免交叉线,实在避免不了就用跳线符号
- 同一层级的状态放在同一水平线上
- 状态之间的距离保持一致
我曾经见过一张状态图,线绕得跟蜘蛛网似的,三个人看了三遍都没看懂。后来我重新画了一遍,把状态按功能分组,线也重新布线,十分钟就讲清楚了。
3.3 使用工具绘制状态图
工具这块,我推荐 draw.io(现在叫 diagrams.net)。免费、开源、支持导出 SVG 和 PNG,团队协作也方便。
3.3.1 draw.io 绘制步骤
- 打开 draw.io,选择「空白图表」
- 从左侧形状库拖出「圆角矩形」作为状态
- 拖出「圆形」作为初始状态(记得调小一点)
- 用「箭头」连接各个状态
- 双击箭头,输入转移条件
- 调整布局,导出为 SVG 或 PNG
注意:draw.io 默认的箭头样式可能不太适合状态图。我习惯把箭头改成「实心箭头」,线宽设为 2px,这样打印出来也清晰。
3.4 实战案例:一个简单的 UART 接收状态机
光说不练假把式。我们画一个 UART 接收状态机的状态图,看看实际怎么操作。
这个状态机有四个状态:
- IDLE:等待起始位
- START:检测到起始位
- DATA:接收 8 位数据
- STOP:接收停止位
下面是我用 SVG 手绘的状态图:
这张图里,我用了不同颜色区分状态:绿色是空闲,橙色是起始,蓝色是数据,红色是停止。这样一眼就能看出状态机的运行阶段。
我的习惯:每个状态旁边加一个注释框,写上该状态下要处理的信号。比如 DATA 状态旁边写「采样 rx_data,bit_cnt++」。这样看状态图的人不用翻代码也能理解。
3.5 避坑指南
画了这么多年状态图,我踩过的坑不少。挑几个典型的说说:
- 状态太多:我曾经画过一个 20 多个状态的状态图,结果自己都看晕了。后来拆成三个子状态机,清爽多了。
- 漏掉默认转移:每个状态都要有一个默认的 else 转移,否则综合时会出锁存器。
- 转移条件冲突:两个转移条件同时满足怎么办?一定要用优先级或者互斥条件。
- 状态图与代码不一致:这是最要命的。我要求团队每次修改代码必须同步更新状态图,否则 code review 不通过。
血的教训:有一次我偷懒没更新状态图,结果新同事照着旧图写代码,功能完全不对。排查了两天才发现是状态图没同步。从那以后,我再也不敢偷这个懒了。
3.6 状态图的高级技巧
如果你已经掌握了基础,可以试试这些进阶玩法:
| 技巧 | 说明 | 适用场景 |
|---|---|---|
| 层次化状态图 | 把复杂状态拆成子状态图 | 状态超过 10 个时 |
| 并发状态区 | 用虚线框表示并行运行的状态 | 多通道处理、流水线控制 |
| 历史状态 | 用 H 符号记住上次离开时的子状态 | 中断恢复、模式切换 |
| 时间约束标注 | 在转移线上标注最大/最小时间 | 时序要求严格的场景 |
我个人最喜欢层次化状态图。你想想看,一个 30 个状态的状态机,平铺开来谁看得懂?但如果你把它分成 3 层,每层 10 个状态,再加上清晰的接口说明,那就好理解多了。
嗯,状态图绘制就聊到这儿。记住一句话:状态图是写给未来看的,包括你自己。三个月后你再看自己画的状态图,如果还能一眼看懂,那说明你画对了。
可可精品资料库