别再写 if-else 了:状态机才是复杂业务逻辑的正确答案

2026-06-18 10 0

别再写 if-else 了:状态机才是复杂业务逻辑的正确答案

你有没有这种感觉:代码写着写着,if-else 越来越多,多到自己都不想看,多到改一个小功能要小心翼翼,多到 Bug 永远修不完?

恭喜你,你已经陷入了「条件地狱」。


一个真实的噩梦

先说个真实故事。我接手过一个订单系统,原来的开发者在订单状态处理上写了将近 2000 行 switch-case,每增加一个新状态就要改三个地方,测试要回归 40 多个场景。那代码看起来像是被卡车碾过的意面。

你以为加注释就能解决?不,注释只会让代码看起来更加混乱。

我当时的第一反应是:重构。但仔细一看,2000 行代码全是一个模式——根据当前状态 + 事件,决定下一步该干什么。这不就是一个状态机吗?


状态机是什么?

状态机,全称有限状态机(Finite State Machine),本质上就是描述「在什么状态下,响应什么事件,会转移到什么新状态,同时执行什么动作」。

听起来很简单对吧?但就是这么简单的东西,能救你一命。

用一个最常见的例子——订单状态的流转:

待支付 → 已支付 → 已发货 → 已收货 → 已完成
  ↓         ↓         ↓        ↓
[超时取消] [退款申请] [退货申请] [申诉中]

如果用 if-else 写,这段逻辑会写成什么样?

function handleOrderEvent(order, event) {
    if (order.status === "PENDING" && event === "PAY") {
        order.status = "PAID";
        sendNotification();
    } else if (order.status === "PENDING" && event === "TIMEOUT") {
        order.status = "CANCELLED";
        refund();
    } else if (order.status === "PAID" && event === "SHIP") {
        order.status = "SHIPPED";
        updateLogistics();
    } else if (order.status === "PAID" && event === "REFUND_REQUEST") {
        order.status = "REFUND_PENDING";
        notifyMerchant();
    }
    // ... 你知道会变成什么样
}

然后某天产品说:「已发货状态下也要能申请退款」。你开始加 if-else,然后发现要改 7 个地方。

然后某天运营说:「部分退款也要考虑」。你看着那 2000 行代码陷入了沉默。


状态机的优雅实现

状态机的核心是把「状态」和「转移规则」分离,让规则变成数据而不是代码。

const transitions = {
    PENDING: {
        PAY: { nextState: "PAID", action: "sendNotification" },
        TIMEOUT: { nextState: "CANCELLED", action: "refund" },
        CANCEL: { nextState: "CANCELLED", action: null }
    },
    PAID: {
        SHIP: { nextState: "SHIPPED", action: "updateLogistics" },
        REFUND_REQUEST: { nextState: "REFUND_PENDING", action: "notifyMerchant" }
    },
    SHIPPED: {
        RECEIVE: { nextState: "COMPLETED", action: "confirmDelivery" },
        REFUND_REQUEST: { nextState: "REFUND_PENDING", action: "notifyMerchant" }
    },
    REFUND_PENDING: {
        APPROVE: { nextState: "REFUNDED", action: "processRefund" },
        REJECT: { nextState: "SHIPPED", action: "notifyCustomer" }
    }
};

function handleOrderEvent(order, event) {
    const currentTransitions = transitions[order.status];
    if (!currentTransitions) {
        throw new Error(`未知状态: ${order.status}`);
    }
    
    const transition = currentTransitions[event];
    if (!transition) {
        throw new Error(`状态 ${order.status} 下不支持事件: ${event}`);
    }
    
    order.status = transition.nextState;
    if (transition.action) {
        executeAction(transition.action, order);
    }
}

现在,加一个新状态?加一个新的状态节点定义就行了。加一个新事件?加一条转移规则就行了。再也不用改那个巨大的 if-else 链了。


状态机的高级玩法

1. 守卫条件(Guard Conditions)

有时候同一个状态 + 同一个事件,不同条件下要转移到不同状态。比如:

PAID: {
    REFUND_REQUEST: [
        { 
            condition: (order) => order.amount > 1000,
            nextState: "MANUAL_REVIEW",
            action: "assignReviewer"
        },
        { 
            condition: (order) => order.amount <= 1000,
            nextState: "REFUND_PENDING",
            action: "autoApprove"
        }
    ]
}

大额退款需要人工审核,小额自动通过——这就是守卫条件。

2. 动作执行时机

很多人在状态机里纠结动作该在哪里执行。我的经验是:

  • 进入动作(Entry Action):每次进入状态时执行,适合初始化、通知等
  • 退出动作(Exit Action):每次离开状态时执行,适合资源清理等
  • 转移动作(Transition Action):转移过程中执行,适合记录日志、发送事件等

建议把动作执行和状态转移解耦,用事件总线来处理副作用。状态机只负责状态流转,副作用通过发布事件来触发。这样测试也好写,调试也清晰。

3. 状态机可视化

用 JSON 定义状态机之后,可以直接生成状态图。推荐用 xstate 的可视化工具,或者自己写个简单的 DOT 语言导出。好处是:产品、设计、测试都能看懂,大家对业务逻辑的理解能对齐。


什么时候不该用状态机?

状态机不是银弹。我见过有人用状态机处理用户输入验证,那纯属杀鸡用牛刀。

简单判断标准:

  • 状态种类 ≤ 3 种,事件类型 ≤ 5 种?用 if-else 就够了,别折腾
  • 状态组合会指数增长?状态机会爆炸,考虑用决策树或规则引擎
  • 需要并行状态?普通状态机hold不住,考虑层级状态机(Hierarchical State Machine)

状态机最适合的场景是:状态种类明确、事件类型明确、状态转移规则是业务核心的场景。电商订单、工作流审批、游戏角色状态、设备状态管理——这些用状态机就是降维打击。


吐槽时间

我发现很多程序员有个毛病:喜欢用复杂的技巧来展示自己有多厉害,而不是用最简单清晰的方案来解决问题。if-else 写 2000 行,看着是「业务能力强」,实际上是不动脑子。

另一个极端是「过度设计」,一个简单的订单状态流转非要上 xstate + 类型安全的状态机定义,项目复杂度直接起飞。

我的建议是:在写代码之前,先想清楚业务的本质模型。状态机不是炫技,是把混乱的业务逻辑用一种清晰的模型来表达。模型选对了,代码自然就简单了。

下次当你发现自己在写第六层嵌套 if-else 的时候,停一下,问问自己:这个东西的本质是什么?也许你需要的不是更多条件,而是一个状态机。


最后送大家一句话:代码是写给人看的,顺手给机器执行。if-else 写多了,机器看得懂,人看不懂,这样的代码迟早要还的。

相关文章

你以为HTTP连接很简单?踩完这些坑你才知道什么叫网络编程
写了5年代码,我总结了这些让人想骂街的API设计血泪教训
当AI开始整活:我和OpenClaw的日常
当AI开始整活:我和OpenClaw的日常
被一只小龙虾支配的日常:我用 OpenClaw 这几个月的真实体验
被一只小龙虾支配的日常:我用 OpenClaw 这几个月的真实体验

发布评论