3. 用状态机替代复杂条件分支

任务目标

现有按键处理逻辑使用多层if-else嵌套判断按键事件与内部模式,导致分支复杂、难以扩展。本案例将其重构为有限状态机(FSM),使按键响应逻辑清晰、状态迁移可追踪。

重构前:复杂条件分支

// 重构前:模式+按键组合导致分支爆炸
void handle_key_legacy(int key, int *mode) {
    if (*mode == 0) { // IDLE
        if (key == 1) { *mode = 1; start_timer(); }
        else if (key == 2) { *mode = 2; }
    } else if (*mode == 1) { // RUN
        if (key == 1) { *mode = 0; stop_timer(); }
        else if (key == 2) { *mode = 3; }
        else if (key == 3) { if (timer_running()) pause_timer(); }
    } else if (*mode == 2) { // CFG
        if (key == 1) { *mode = 0; }
        else if (key == 2) { *mode = 1; }
        else if (key == 3) { save_config(); }
    } else if (*mode == 3) { // PAUSE
        if (key == 1) { *mode = 0; stop_timer(); }
        else if (key == 2) { *mode = 1; resume_timer(); }
    }
}

问题:模式与按键组合呈N×M增长,新增模式或按键需修改多处嵌套,易漏改。

重构后:状态机实现

#include <stdint.h>

// 状态与事件定义
typedef enum { ST_IDLE, ST_RUN, ST_CFG, ST_PAUSE } state_t;
typedef enum { EV_KEY1, EV_KEY2, EV_KEY3, EV_NONE } event_t;

// 外部依赖(由硬件层提供)
void timer_start(void);
void timer_stop(void);
void timer_pause(void);
void timer_resume(void);
uint8_t timer_is_running(void);
void config_save(void);

// 状态迁移表: [当前状态][事件] = 目标状态
static const state_t trans_table[4][3] = {
    /* ST_IDLE */ { ST_RUN,  ST_CFG,  ST_IDLE },
    /* ST_RUN  */ { ST_IDLE, ST_PAUSE, ST_RUN },
    /* ST_CFG  */ { ST_IDLE, ST_RUN,  ST_CFG },
    /* ST_PAUSE*/ { ST_IDLE, ST_RUN,  ST_PAUSE },
};

// 动作表: [当前状态][事件] = 动作函数指针
typedef void (*action_t)(void);
static void act_none(void) {}
static void act_start(void) { timer_start(); }
static void act_stop(void)  { timer_stop(); }
static void act_pause(void) { if (timer_is_running()) timer_pause(); }
static void act_resume(void){ timer_resume(); }
static void act_save(void)  { config_save(); }

static const action_t action_table[4][3] = {
    /* ST_IDLE */ { act_start, act_none, act_none },
    /* ST_RUN  */ { act_stop,  act_pause, act_none },
    /* ST_CFG  */ { act_none,  act_none, act_save },
    /* ST_PAUSE*/ { act_stop,  act_resume, act_none },
};

// 当前状态
static state_t current_state = ST_IDLE;

// 按键事件映射(硬件层调用)
event_t key_to_event(int key) {
    switch (key) {
        case 1: return EV_KEY1;
        case 2: return EV_KEY2;
        case 3: return EV_KEY3;
        default: return EV_NONE;
    }
}

// 状态机主处理函数
void handle_key_fsm(int key) {
    event_t ev = key_to_event(key);
    if (ev == EV_NONE) return;

    uint8_t s = (uint8_t)current_state;
    uint8_t e = (uint8_t)ev;

    // 执行动作
    action_table[s][e]();
    // 状态迁移
    current_state = trans_table[s][e];
}

关键设计说明

  • 状态与事件解耦:新增状态只需扩展枚举和表行,新增按键只需扩展枚举和表列,无需修改处理函数主体。
  • 动作与迁移分离:动作表处理副作用(定时器、存储),迁移表只负责状态跳转,逻辑互不干扰。
  • 非法事件处理:表中非法组合映射到自身状态并执行act_none,保证状态机始终有效。

运行与验证

  1. 编译(需提供上述外部函数的桩实现):
    gcc -c fsm_key.c -o fsm_key.o
  2. 最小验证代码(单独编译运行):
    // test_fsm.c —— 桩实现与验证
    #include <stdio.h>
    #include <assert.h>
    static int timer_running_flag = 0;
    void timer_start(void)   { timer_running_flag = 1; }
    void timer_stop(void)    { timer_running_flag = 0; }
    void timer_pause(void)   { timer_running_flag = 0; }
    void timer_resume(void)  { timer_running_flag = 1; }
    uint8_t timer_is_running(void) { return timer_running_flag; }
    void config_save(void)   { printf("config saved\n"); }
    
    // 声明被测函数
    void handle_key_fsm(int key);
    state_t get_state(void) { return current_state; }
    
    int main(void) {
        // 初始 IDLE,按 KEY1 → RUN,定时器启动
        handle_key_fsm(1);
        assert(get_state() == ST_RUN);
        assert(timer_running_flag == 1);
    
        // RUN 按 KEY3 → 暂停(定时器停止)
        handle_key_fsm(3);
        assert(get_state() == ST_PAUSE);
        assert(timer_running_flag == 0);
    
        // PAUSE 按 KEY2 → 恢复 RUN
        handle_key_fsm(2);
        assert(get_state() == ST_RUN);
        assert(timer_running_flag == 1);
    
        // RUN 按 KEY1 → IDLE,定时器停止
        handle_key_fsm(1);
        assert(get_state() == ST_IDLE);
        assert(timer_running_flag == 0);
    
        printf("All FSM tests passed.\n");
        return 0;
    }
  3. 编译并运行验证:
    gcc -o test_fsm test_fsm.c fsm_key.c
    ./test_fsm
  4. 通过条件:程序输出All FSM tests passed.且无断言失败,说明状态迁移与动作执行均符合预期。

对比与收益

  • 原if-else版本新增一个模式需修改所有相关分支;状态机版本只需增加一行表项和对应动作函数。
  • 状态迁移关系以表格形式集中呈现,代码审查时可直观检查所有合法路径。
  • 非法按键组合被表驱动逻辑自动吸收,不会出现遗漏分支导致的未定义行为。