花拾录
← 返回知识库

用状态机重写满是 if-else 的订单流程:从状态爆炸到可测试的迁移表

后端开发AI2026/09/271 阅读0 评论

问题:订单流程里的 if-else 泥潭

几乎每个电商系统都有这样一段代码:

def handle_order(order, action):
    if order.status == "created":
        if action == "pay":
            order.status = "paid"
        elif action == "cancel":
            order.status = "cancelled"
        else:
            raise InvalidAction()
    elif order.status == "paid":
        if action == "ship":
            order.status = "shipped"
        elif action == "refund":
            order.status = "refunding"
        else:
            raise InvalidAction()
    # ... 更多分支

每加一个状态或动作,就要在所有分支里补判断。测试时也只能一个个场景去构造,漏掉一条路径线上就出问题。这种写法的问题不是“不优雅”,而是状态和迁移规则散落在代码里,无法被单独验证。

状态机视角:把“规则”和“执行”分开

有限状态机(FSM)的核心只有三样东西:

  • 状态(State):订单当前处于哪一步
  • 事件(Event):外部触发的动作,如支付、发货、取消
  • 迁移(Transition):在某个状态下收到某个事件,应该进入哪个新状态

关键转变是:把迁移规则写成一个数据表,而不是嵌套的条件分支。

TRANSITIONS = {
    ("created", "pay"):     "paid",
    ("created", "cancel"):  "cancelled",
    ("paid", "ship"):       "shipped",
    ("paid", "refund"):     "refunding",
    ("shipped", "confirm"): "completed",
    ("refunding", "approve"): "refunded",
}

这段表本身就是文档:看一眼就知道哪些操作合法、会走到哪里。

迁移表驱动的执行器

有了表,执行逻辑可以写得非常薄:

def apply(order, event):
    key = (order.status, event)
    if key not in TRANSITIONS:
        raise InvalidTransition(order.status, event)
    order.status = TRANSITIONS[key]
    return order.status

如果还需要副作用(发消息、写日志),可以给迁移表加值:

TRANSITIONS = {
    ("created", "pay"): {"to": "paid", "on_enter": notify_paid},
    # ...
}

执行器统一调用 on_enter,业务代码不再关心“从哪个状态来的”。

为什么这样更好测试

原来的 if-else 要写很多集成测试才能覆盖分支,现在可以分两层测:

  1. 规则层:直接断言迁移表。例如 assert TRANSITIONS[("paid", "ship")]["to"] == "shipped",以及非法组合确实不在表中。
  2. 执行层:用参数化测试遍历所有合法迁移,检查状态正确、副作用被调用;再遍历非法组合,检查抛出预期异常。
@pytest.mark.parametrize("state,event,expected", [
    ("created", "pay", "paid"),
    ("paid", "ship", "shipped"),
])
def test_legal_transitions(state, event, expected):
    order = Order(status=state)
    assert apply(order, event) == expected

新增状态时,只需在表里加一行,测试自动覆盖,不会漏掉某个分支。

落地时的几个注意点

  • 状态和事件用常量或枚举,避免字符串拼写错误。
  • 迁移表集中在一处,不要分散到多个文件,否则又回到“规则散落”的老问题。
  • 副作用保持幂等:状态机只负责状态流转,重试或并发下要保证通知类操作不会重复产生业务影响。
  • 并发控制:状态迁移通常要配合乐观锁(版本号)或数据库条件更新,避免两个请求同时从 paid 迁到不同状态。
  • 不必追求复杂框架:多数业务用一张字典加一个执行函数就够了,等规则复杂到需要分层状态机时再引入库。

小结

把订单流程从 if-else 改成迁移表,本质是把“隐式的控制流”变成“显式的数据”。规则可读、可枚举、可参数化测试,新增状态只需加一行,而不是在所有分支里补条件。对后端开发者来说,这是用很小的重构成本换取长期可维护性的典型做法。

评论(0)

  • 还没有评论,来抢沙发~

相关文章