第3章 状态图绘制:从元素到实战

状态图这东西,说白了就是状态机的「设计图纸」。你想想看,盖房子得有施工图,写代码得有流程图,那设计状态机呢?状态图就是我们的蓝图。我个人习惯在写任何状态机代码之前,先把状态图画出来——这习惯帮我省了至少一半的调试时间。

3.1 状态图的基本元素

状态图其实没那么玄乎,核心就四个东西:状态、转移、事件、动作。嗯,就这么简单。

3.1.1 状态(State)

状态就是系统在某个时刻的「定格画面」。比如一个交通灯,它要么是红灯,要么是绿灯,要么是黄灯——这就是三个状态。

在状态图中,状态通常用一个圆角矩形表示。我见过不少新手把状态画成普通矩形,其实也没大问题,但行业规范就是圆角的,咱们还是按规矩来。

状态的标准画法:

  • 圆角矩形框
  • 内部写上状态名称(如 IDLE、RUNNING)
  • 初始状态用实心圆点表示
  • 结束状态用同心圆表示(外圈空心、内圈实心)

3.1.2 转移(Transition)

转移就是状态之间的「箭头」。从一个状态到另一个状态,画一条带箭头的线,线上标注触发条件。

我曾经在项目里见过一个同事,画了二十多个状态,箭头密密麻麻交叉在一起,根本看不清谁是谁。后来我帮他重新梳理,发现有一半的转移是冗余的。所以这里有个经验:转移线尽量少交叉,能用直线别用曲线。

3.1.3 事件(Event)与动作(Action)

事件是触发转移的「导火索」,动作是转移发生时执行的「操作」。

举个例子:按下按钮(事件)→ 从IDLE状态转移到WORK状态 → 同时启动定时器(动作)。

元素 符号 说明
状态 圆角矩形 表示系统的一个稳定状态
初始状态 实心圆点 系统的起点,每个状态图只有一个
结束状态 同心圆 系统的终点,可以有多个或没有
转移 带箭头实线 线上标注 事件[条件]/动作
选择点 菱形 用于条件分支判断

3.2 绘制规范——这些坑我都踩过

规范这东西,没人教的时候觉得无所谓,等出了问题才后悔。我刚开始画状态图时也吃过亏,下面几条是我用真金白银换来的教训。

3.2.1 命名规范

状态名称用大写字母,单词之间用下划线。比如:IDLE、DATA_READY、ERROR_HANDLING。

事件名称用小写字母,比如:button_press、timeout、data_received。

避坑指南:我曾经在一个项目里用了中文命名状态——「等待中」「处理中」「完成」。结果后来要跟国外团队对接,全部重画了一遍。所以,状态图尽量用英文命名,这是行业惯例。

3.2.2 布局规范

  • 从左到右或从上到下:状态图的流向应该清晰,别画成蜘蛛网
  • 状态之间保持间距:太挤了看不清转移线上的文字
  • 避免交叉线:如果不得不交叉,用半圆弧跳线表示
  • 自转移画在状态上方或下方:表示状态内部的事件处理

3.2.3 转移标注格式

标准的标注格式是:事件 [条件] / 动作

比如:button_press [count < 5] / increment_counter

如果只有事件没有条件,可以省略中括号:timeout / reset_system

小技巧:条件用方括号括起来,动作用斜杠开头。这样别人一看就知道哪个是条件、哪个是动作。我带的团队里,我强制要求所有人按这个格式写,代码评审时一眼就能看出问题。

3.3 使用draw.io绘制状态图

工具嘛,我用过Visio、Lucidchart、PlantUML,但最常用的还是draw.io。免费、轻量、支持离线,而且画状态图足够用了。

3.3.1 准备工作

打开draw.io,新建一个空白图。在左侧形状面板里,找到「UML」分类——没错,状态图属于UML的一种。

你会看到这些形状:

  • 初始状态(实心圆)
  • 结束状态(同心圆)
  • 状态(圆角矩形)
  • 转移(箭头线)
  • 选择点(菱形)

3.3.2 实战:画一个简单的状态机

咱们画一个最简单的例子:一个开关灯的状态机。

步骤:

  1. 拖一个「初始状态」到画布
  2. 拖一个「状态」到画布,命名为 OFF
  3. 从初始状态画箭头到 OFF 状态
  4. 再拖一个「状态」,命名为 ON
  5. 从 OFF 画箭头到 ON,标注 switch_press / turn_on
  6. 从 ON 画箭头回到 OFF,标注 switch_press / turn_off

你看,就这么简单。一个两状态的状态机就画好了。

核心要点:画状态图不是画画,是梳理逻辑。每画一个箭头,你都要问自己:这个转移真的需要吗?有没有遗漏的情况?

3.3.3 进阶技巧

画复杂状态图时,我习惯用分层的方式。比如一个通信协议的状态机,我会先画一个总览图,把主要状态画出来,然后再针对每个状态画子状态图。

draw.io支持「容器」功能,可以把一组状态框在一个大框里,表示这是某个父状态的子状态。这个功能在画复杂系统时特别好用。

注意:别试图在一张图里画完所有细节。我见过有人把五十多个状态画在一张A4纸上,结果打印出来根本看不清字。该分页就分页,该分层就分层。

3.4 本章知识体系

下面这张图总结了状态图绘制的核心知识结构,你可以把它当作一个快速参考。

状态图绘制知识体系 基本元素 ● 状态(圆角矩形) ● 初始/结束状态(圆点) ● 转移(带箭头线) ● 事件/条件/动作 ● 选择点(菱形) 绘制规范 ● 命名:大写状态,小写事件 ● 布局:从左到右,避免交叉 ● 标注:事件[条件]/动作 ● 分层:复杂系统分页绘制 ● 自转移:画在状态上方 工具使用(draw.io) ● UML分类中的状态图元素 ● 拖拽式操作,简单直观 ● 容器功能支持子状态 ● 支持导出SVG/PNG/PDF ● 免费离线,跨平台 实战要点 ● 先画图,再写代码 ● 每个转移都要问:需要吗? ● 检查是否有遗漏的状态 ● 避免过度复杂化 ● 团队统一规范

这张图把状态图绘制的四个核心模块串在了一起。你画状态图时,就对照着这四个方面检查:元素全不全?规范对不对?工具会不会用?实战有没有踩坑?

我的习惯:每次画完状态图,我都会打印出来贴在工位上,盯着看十分钟。不是为了好看,而是为了找漏洞。很多时候,盯着纸面比盯着屏幕更容易发现问题。

好了,状态图绘制的基本功就这些。记住,画图不是目的,把逻辑理清楚才是。下一章我们会用实际案例来练手,到时候你就知道这些规范有多重要了。

可可精品资料库