第三章:编码规范总览——命名规范、文件结构、注释规范、代码布局
好,咱们直接进入正题。
状态机这东西,说白了就是逻辑的骨架。骨架歪了,肉长再多也没用。我见过太多项目,功能跑得通,但代码一打开,头皮发麻——变量名像乱码,文件堆成一团,注释全是废话。嗯,这种代码,三个月后连原作者自己都看不懂。
所以这一章,我把状态机编码的四大规范给你捋清楚。你照着做,至少能少踩一半的坑。
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 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你照着这个框架去写代码,基本不会跑偏。
嗯,这张图基本把本章的骨架画出来了。你写代码时,就按这四个维度去检查。哪个维度没做好,就停下来改。别想着「后面再优化」——我吃过这个亏,后面根本不会回头改。
好了,这一章就到这里。记住:规范不是束缚,是帮你省时间的工具。