有限自动机 = 业务状态的形式化
解决什么问题:业务状态的形式化建模—— 用数学语言描述"状态 + 转移条件",避免业务逻辑的混乱和 bug。
业务问题
真实场景
场景 1:订单状态混乱
业务需求:
"订单有以下状态:待支付、已支付、已发货、已收货、已取消、已退款"
实现(没有状态机):
if (order.status == "待支付") {
if (user.cancel) {
order.status = "已取消";
} else if (user.pay) {
order.status = "已支付";
}
// ...
} else if (order.status == "已支付") {
if (merchant.ship) {
order.status = "已发货";
} else if (user.refund) {
order.status = "已退款";
}
// 忘记处理:用户取消(已支付后能否取消?)
}
问题:
- 状态判断散落在代码各处
- 容易遗漏状态转移
- 难以测试
- 业务规则变更时要改多个地方场景 2:状态转移的边界判断
业务问题:
- 已完成的订单能否退款?
- 已发货的订单能否取消?
- 已退款的订单能否再发货?
没有状态机的代码:
- 散落在各种 if-else
- 每个新场景都要加判断
- 容易遗漏边界场景 3:业务流程的可视化
需求:业务方想看订单的生命周期
实现:
- 文档过时(代码已经改了)
- 业务方看到的图和实际行为不一致失败的典型做法
做法 1:状态散落在代码里
✗ 状态判断散落在各个 if-else
✗ 没有统一的状态机
后果:
- 状态转移遗漏
- 难以维护
- 业务方无法理解做法 2:状态机过于复杂
✗ 一个状态机处理所有业务
✗ 几百个状态
后果:
- 难以理解
- 难以测试
- 维护成本高做法 3:状态机与代码脱节
✗ 文档中有状态机
✗ 代码中没有
后果:
- 文档过时
- 误导新人控制论视角
数学原型
钱学森《工程控制论》Ch 17 §17 引言:
"有限自动机是离散控制系统的一种特殊形式——系统的状态在有限集合中转移,每个转移由输入触发。这种数学模型在控制系统、计算机科学、通信等领域都有广泛应用。"
钱学森 §17:
"有限自动机可用五元组表示:(Q, Σ, δ, q₀, F)
- Q:状态的有限集合
- Σ:输入字母表
- δ:转移函数 Q × Σ → Q
- q₀:初始状态
- F:接受状态集合"
控制论解法
有限自动机的五元组:
Q = {q₁, q₂, ..., qₙ} ← 状态集合(有限)
Σ = {σ₁, σ₂, ..., σₘ} ← 输入字母表
δ = Q × Σ → Q ← 转移函数
q₀ ∈ Q ← 初始状态
F ⊆ Q ← 接受状态(终态)
软件工程对位:
- Q = 业务状态集合(待支付、已支付、已发货...)
- Σ = 业务事件 / 命令(创建、支付、发货...)
- δ = 状态转移规则
- q₀ = 初始状态(如"待支付")
- F = 终态(如"已完成"、"已取消")与业务的对应
| 控制论概念 | 软件工程 |
|---|---|
| 状态集合 Q | 业务状态集合 |
| 输入字母表 Σ | 业务事件 / 命令 |
| 转移函数 δ | 状态转移规则 |
| 初始状态 q₀ | 业务初始状态 |
| 接受状态 F | 业务终态 |
| 状态转移 | 业务状态变化 |
| 不可达状态 | 不可能的状态(应当移除) |
核心概念
定义
有限自动机:一个数学模型,描述系统的有限状态集合、输入字母表、转移函数、初始状态和接受状态。
软件工程对位:业务状态机的形式化描述。
有限自动机的关键组成
1. 状态集合 Q:
业务的所有可能状态
2. 输入集合 Σ:
触发状态转移的事件 / 命令
3. 转移函数 δ:
哪些输入可以从哪些状态转移到哪些状态
4. 初始状态 q₀:
业务开始的状态
5. 接受状态 F:
业务结束的状态(终态)关键属性
| 属性 | 含义 |
|---|---|
| 有限 | 状态数量有限(可枚举) |
| 确定性 | 同样的输入 + 同样的状态 → 同样的输出(DFA) |
| 可分析 | 可验证是否可达 / 是否会死锁 |
| 可执行 | 可生成代码 / 工作流 |
| 可测试 | 状态转移可枚举测试 |
有限自动机的两种类型
DFA(确定性有限自动机):
- 同样的输入 + 同样的状态 → 同样的输出
- 软件业务建模的默认选择
NFA(非确定性有限自动机):
- 同样的输入 + 同样的状态 → 可能多个输出
- 适合:复杂业务逻辑(但实现复杂)工程实践
步骤 1:识别业务状态
问业务方:
- 业务的所有状态是什么?
- 哪些状态是"终态"(业务结束)?
- 哪些状态是"初始状态"(业务开始)?
例(订单):
- 初始状态:待支付
- 状态:待支付、已支付、已发货、已收货、已取消、已退款、退款中
- 终态:已收货、已取消、已退款步骤 2:识别状态转移
问业务方:
- 哪些事件可以触发状态变化?
- 触发后状态如何变化?
- 转移是否有前置条件?
例(订单):
- 待支付 → (用户支付) → 已支付
- 待支付 → (用户取消) → 已取消
- 已支付 → (商家发货) → 已发货
- 已支付 → (用户退款) → 退款中
- 已发货 → (用户收货) → 已收货
- 退款中 → (退款完成) → 已退款步骤 3:识别边界(禁止的转移)
问业务方:
- 哪些转移是不允许的?
- 例:已完成的订单能否退款?
- 例:已取消的订单能否支付?
设计:
- 显式列出禁止的转移
- 在代码中强制检查步骤 4:实现状态机
实现方式 1:switch-case
- 简单直接
- 适合:状态少、转移少
实现方式 2:状态模式
- 每个状态一个类
- 适合:复杂业务
实现方式 3:状态机库
- Spring State Machine
- AWS Step Functions
- 适合:跨服务状态机
实现方式 4:数据库约束
- CHECK 约束
- 触发器
- 适合:强一致性步骤 5:测试状态机
测试场景:
1. 每个状态都有哪些合法转移?(覆盖率 100%)
2. 每个转移的前置条件?(条件验证)
3. 禁止的转移是否被拒绝?(边界测试)
4. 终态的不可逆性?(终态测试)
工具:
- 状态机测试库(如 given-when-then)
- 表驱动测试常见反模式
反模式 1:状态机缺失
症状:
- 状态判断散落在 if-else
- 没有统一的状态机
后果:状态混乱 / 边界遗漏
正解:所有有状态的业务都用状态机反模式 2:状态机过于复杂
症状:
- 一个状态机处理所有业务
- 几百个状态
后果:难以理解 / 难以测试
正解:拆分状态机(按聚合 / 按业务能力)反模式 3:状态机与代码脱节
症状:
- 文档中有状态机
- 代码中没有
后果:文档过时 / 误导新人
正解:状态机代码化 / 用工具生成代码反模式 4:忽略不可达状态
症状:
- 状态机中有不可达状态
- 代码中无法到达,但占用空间
后果:复杂度增加 / 理解困难
正解:移除不可达状态工程实例
实例 1:电商订单状态机
状态集合 Q:
- 待支付
- 已支付
- 已发货
- 已收货
- 已取消
- 退款中
- 已退款
输入集合 Σ:
- 支付
- 取消
- 发货
- 收货
- 申请退款
- 退款完成
转移函数 δ:
- 待支付 × 支付 → 已支付
- 待支付 × 取消 → 已取消
- 已支付 × 发货 → 已发货
- 已支付 × 申请退款 → 退款中
- 已发货 × 收货 → 已收货
- 退款中 × 退款完成 → 已退款
初始状态 q₀:待支付
接受状态 F:已收货 / 已取消 / 已退款
禁止的转移:
- 已收货 × 支付(不允许)
- 已取消 × 支付(不允许)
- 已退款 × 发货(不允许)
实现:
- 状态机库(Spring State Machine)
- 数据库 CHECK 约束
- API 层的状态转换前置检查实例 2:工作流引擎
业务:内容审核流程
状态集合 Q:
- 待审核
- 审核中
- 已通过
- 已拒绝
- 申诉中
转移函数 δ:
- 待审核 × 审核员领取 → 审核中
- 审核中 × 通过 → 已通过
- 审核中 × 拒绝 → 已拒绝
- 已拒绝 × 申诉 → 申诉中
- 申诉中 × 申诉通过 → 已通过
- 申诉中 × 申诉拒绝 → 已拒绝
实现:
- AWS Step Functions
- Temporal
- 自研工作流引擎实例 3:协议状态机
业务:客户端连接的生命周期
状态集合 Q:
- 连接中
- 已连接
- 关闭中
- 已关闭
转移函数 δ:
- 连接中 × 连接成功 → 已连接
- 连接中 × 连接失败 → 已关闭
- 已连接 × 心跳超时 → 关闭中
- 关闭中 × 关闭完成 → 已关闭
实现:
- TCP 状态机
- WebSocket 状态机
- 分布式连接管理性能指标
状态机健康指标
| 指标 | 设计目标 | 衡量方法 |
|---|---|---|
| 状态覆盖率 | 100% 业务状态都在状态机中 | 状态机中的状态 / 业务状态 |
| 转移覆盖率 | 100% 合法转移都在状态机中 | 状态机中的转移 / 合法转移 |
| 非法转移拦截率 | 100% | 拦截的非法转移 / 总尝试 |
| 状态机测试覆盖率 | > 90% | 已测试的状态 / 总状态 |
| 不可达状态数 | 0 | 状态机分析 |
监控设计
关键监控:
- 状态分布(每个状态有多少订单 / 任务)
- 状态转移频率
- 非法转移尝试
- 状态机执行延迟
告警分级:
- P2:状态分布异常
- P1:非法转移尝试 > 阈值
- P0:状态机死锁 / 循环边界判断
何时不用
场景 1:状态极少
- 只有 2-3 个状态
- 没有复杂转移
→ 不需要正式状态机,简单 if-else 足够场景 2:业务完全确定
- 所有路径都已建模
- 没有动态变化
→ 不需要状态机场景 3:业务完全不可控
- 状态由外部决定
- 系统无法控制状态变化
→ 不需要状态机,只需要状态观测与其他方法论的边界
| 相邻概念 | 区别 |
|---|---|
| 离散控制 | 离散控制关注"采样";本方法论关注"状态转移" |
| 状态机库 | 状态机库是实现;本方法论是设计模式 |
| DDD 聚合根 | 聚合根关注"一致性边界";本方法论关注"状态形式化" |
何时升级(更精细的状态机)
当以下情况出现时,考虑升级:
1. 业务状态 > 10 个 → 拆分状态机
2. 转移规则复杂 → 状态机库
3. 跨服务状态机 → 分布式状态机(Saga)
4. 状态机测试困难 → 自动化测试关键洞察
钱学森 Ch 17:有限自动机是离散控制系统的数学基础。
软件工程的业务状态机 = 控制论的有限自动机 + 业务语言。
业务状态机的价值:
- 可视化 —— 业务方能看懂
- 可验证 —— 数学上可证明
- 可执行 —— 直接生成代码
- 可测试 —— 状态 + 转移可枚举
没有状态机的业务 = 没有控制论支撑的业务 = 混乱的业务。
相关链接
IDDD 对位
- 状态机 = 实体生命周期 → 聚合 4 规则(Ch 10):聚合根管理状态
- 状态转移 = 领域事件 → 事件分发与一致性(Ch 8)
- 状态保护 = 不变量 → 应用服务 vs 领域服务
核心:状态机 = 实体的"生命体征"——IDDD 聚合根的状态转移 = cybernetics 有限状态机。