第三章:编码规范总览——命名规范、文件结构、注释规范、代码布局

好,咱们直接进入正题。

状态机这东西,说白了就是逻辑的骨架。骨架歪了,肉长再多也没用。我见过太多项目,功能跑得通,但代码一打开,头皮发麻——变量名像乱码,文件堆成一团,注释全是废话。嗯,这种代码,三个月后连原作者自己都看不懂。

所以这一章,我把状态机编码的四大规范给你捋清楚。你照着做,至少能少踩一半的坑。

3.1 命名规范:让变量自己会说话

我个人习惯,命名是第一道防线。名字起好了,注释都可以少写一半。

3.1.1 状态枚举命名

状态枚举用 STATE_ 前缀,全大写,下划线分隔。为什么?因为一眼就能看出这是个状态,不是普通变量。

// 好的命名
typedef enum {
    STATE_IDLE,
    STATE_INIT,
    STATE_RUNNING,
    STATE_ERROR,
    STATE_SLEEP
} system_state_t;

// 糟糕的命名
typedef enum {
    idle,
    init,
    running,
    err,
    slp
} state_t;

我在项目中遇到过有人用 st1、st2 这种名字。调试的时候,你根本不知道 st1 代表什么。你想想看,这得多痛苦?

3.1.2 事件命名

事件用 EVENT_ 前缀,动词+名词结构。比如 EVENT_BUTTON_PRESS、EVENT_TIMEOUT。

小技巧: 事件名最好能直接反映触发条件。别用 EVENT_1、EVENT_2 这种,那是给自己挖坑。

3.1.3 函数命名

状态机相关的函数,统一加 fsm_ 前缀。比如 fsm_init()、fsm_process_event()、fsm_get_current_state()。

注意: 别把状态机函数和普通业务函数混在一起。我曾经在一个项目里看到 calculate_temperature() 和 state_machine_run() 放在同一个文件里,那叫一个乱。

3.2 文件结构:一个状态机一个家

文件怎么组织?我建议遵循「一个状态机,一对文件」的原则。

文件 内容 说明
fsm_led.h 状态枚举、事件枚举、函数声明 对外接口,别人只需要看这个
fsm_led.c 状态表、状态处理函数、事件分发逻辑 内部实现,对外不可见
fsm_led_cfg.h 宏定义、配置参数 可选,用于不同平台适配

为什么要这么分?说白了就是「接口与实现分离」。你改内部逻辑,别人不用动他的代码。我在做嵌入式项目时,经常需要给不同硬件平台适配状态机,这种结构让我省了不少事。

3.3 注释规范:写给人看的,不是写给编译器看的

注释这东西,多了是噪音,少了是灾难。我总结了三句话:

  • 解释「为什么」,而不是「是什么」——代码本身已经说明了「是什么」。
  • 状态转换必须注释——这是状态机的核心逻辑。
  • 复杂条件判断要注释——别让后人猜你的意图。
// 好的注释:解释为什么这么跳转
// 当收到超时事件,且当前处于等待状态,跳转到错误恢复状态
if (event == EVENT_TIMEOUT && current_state == STATE_WAITING) {
    next_state = STATE_ERROR_RECOVERY;
}

// 糟糕的注释:废话连篇
// 如果事件是超时,并且当前状态是等待,那么下一个状态是错误恢复
if (event == EVENT_TIMEOUT && current_state == STATE_WAITING) {
    next_state = STATE_ERROR_RECOVERY;
}

我的习惯: 每个状态处理函数的头部,用一两句话说明这个状态下能处理哪些事件。这样别人看代码时,不用从头读到尾。

3.4 代码布局:视觉上的秩序感

代码布局,说白了就是「排版」。好的排版让人一眼就能看出逻辑结构。

3.4.1 状态表布局

状态表建议用二维数组或结构体数组,每个状态一行,每个事件一列。这样状态转换关系一目了然。

// 状态转换表:行=当前状态,列=事件
static const state_transition_t state_table[STATE_MAX][EVENT_MAX] = {
    // STATE_IDLE
    [STATE_IDLE] = {
        [EVENT_START]   = { .next_state = STATE_RUNNING, .action = action_start },
        [EVENT_STOP]    = { .next_state = STATE_IDLE,    .action = NULL },
        [EVENT_ERROR]   = { .next_state = STATE_ERROR,   .action = action_error },
    },
    // STATE_RUNNING
    [STATE_RUNNING] = {
        [EVENT_START]   = { .next_state = STATE_RUNNING, .action = NULL },
        [EVENT_STOP]    = { .next_state = STATE_IDLE,    .action = action_stop },
        [EVENT_ERROR]   = { .next_state = STATE_ERROR,   .action = action_error },
    },
};

3.4.2 缩进与空行

  • 缩进统一用4个空格,别用Tab。不同编辑器Tab宽度不一样,会乱。
  • 函数之间空一行,逻辑块之间空一行。
  • 一行代码别超过80个字符。超过就换行,对齐。

我曾经踩过的坑: 有个同事用Tab缩进,我用空格。合并代码时,整个文件缩进全乱了。从那以后,我强制团队统一用空格。

3.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你照着这个框架去写代码,基本不会跑偏。

状态机编码规范 命名规范 文件结构 注释规范 代码布局 STATE_ / EVENT_ 前缀 全大写 + 下划线 函数加 fsm_ 前缀 .h 声明接口 .c 实现逻辑 _cfg.h 配置分离 解释「为什么」 状态转换必注释 复杂条件加说明 状态表二维布局 4空格缩进 每行不超过80字符 目标:可读、可维护、可复用

嗯,这张图基本把本章的骨架画出来了。你写代码时,就按这四个维度去检查。哪个维度没做好,就停下来改。别想着「后面再优化」——我吃过这个亏,后面根本不会回头改。

好了,这一章就到这里。记住:规范不是束缚,是帮你省时间的工具。

可可精品资料库