10、代码风格与规范:命名规范、缩进与排版、注释规范、代码审查清单
代码风格这事儿,说大不大,说小不小。我见过太多项目,功能跑得挺好,但代码一打开,简直像车祸现场。你想想看,一个团队里,有人用驼峰,有人用下划线,有人缩进2格,有人缩进8格……这代码还怎么维护?
说白了,代码风格不是审美问题,是工程问题。今天我就把我在嵌入式项目里踩过的坑、总结的经验,掰开了跟你聊聊。
核心观点:代码规范是写给“人”看的,不是写给编译器看的。编译器只关心语法对不对,而人需要一眼看懂你的意图。
10.1 命名规范:名字就是最好的注释
我刚开始写代码时,特别喜欢用 a、b、tmp 这种名字。后来有一次,一个 bug 查了整整两天,最后发现是 tmp 变量在三个地方被复用,值被覆盖了。从那以后,我再也不敢偷懒了。
10.1.1 变量命名
- 全局变量:加前缀
g_,比如g_systemTick - 静态变量:加前缀
s_,比如s_bufferIndex - 局部变量:简短但清晰,比如
i只用于循环计数,其他情况用count、index - 布尔变量:用
is、has、enable开头,比如isReady、hasError
我的习惯:变量名长度控制在 8-20 个字符。太短看不懂,太长写起来累。你想想看,一个叫 thisIsTheCounterForTheNumberOfRetryAttempts 的变量,谁看了不头疼?
10.1.2 函数命名
| 风格 | 示例 | 适用场景 |
|---|---|---|
| 动词+名词 | getTemperature() |
获取数据 |
| set+名词 | setBaudRate() |
设置参数 |
| is/has+形容词 | isBufferFull() |
状态判断 |
| 动词+宾语 | initUart() |
初始化操作 |
我曾经在一个项目里看到有人写 function1()、function2()……嗯,这种命名方式,我建议直接拉出去“枪毙”五分钟。
10.1.3 宏与枚举
- 宏定义:全大写 + 下划线,比如
#define BUFFER_SIZE 256 - 枚举常量:全大写,比如
STATE_IDLE、STATE_BUSY - 枚举类型:加
_e后缀,比如state_e
注意:宏定义不要跟变量名冲突。我见过有人定义了 #define MAX 100,然后又声明了 int max = 50;……结果预处理器一展开,代码直接崩了。
10.2 缩进与排版:让代码有呼吸感
代码排版就像房间的布局。东西摆得整整齐齐,你进去心情就好;乱七八糟,你连找把椅子都费劲。
10.2.1 缩进风格
我个人推荐 4空格缩进。为什么不是 Tab?因为不同编辑器对 Tab 的解释不一样。你在我电脑上看对齐的代码,到别人电脑上可能就歪了。4空格是业界共识,稳妥。
// 好的缩进风格
void processData(uint8_t *data, uint16_t len)
{
for (uint16_t i = 0; i < len; i++)
{
if (data[i] > 0x7F)
{
data[i] = data[i] & 0x7F;
}
}
}
10.2.2 大括号位置
嵌入式领域,我建议用 Allman风格(大括号另起一行)。为什么?因为嵌入式代码经常要加注释,大括号单独一行,注释位置更清晰。
// Allman风格(推荐)
if (condition)
{
// 这里可以加注释
doSomething();
}
else
{
doOther();
}
// K&R风格(不推荐嵌入式使用)
if (condition) {
doSomething();
} else {
doOther();
}
10.2.3 空行与分组
- 函数之间:空2行
- 逻辑块之间:空1行
- 变量声明与代码之间:空1行
- 不要连续空3行以上——那说明你的函数太长了,该拆了
避坑指南:我曾经在一个驱动文件里看到连续15个空行,原因是有人删了代码但没删空行。嗯,这种“幽灵空行”比没有空行更让人抓狂。
10.3 注释规范:写给人看的说明书
注释这件事,我经历过三个阶段:
- 新手期:不写注释,觉得代码就是注释
- 狂热期:每行都写注释,结果代码改了注释没改,反而误导人
- 成熟期:只写“为什么”,不写“是什么”
10.3.1 文件头注释
/**
* @file uart_driver.c
* @brief UART底层驱动,支持波特率9600-115200
* @author 张三
* @date 2024-01-15
* @note 修改波特率后需重新初始化FIFO
*/
10.3.2 函数注释
/**
* @brief 计算CRC16校验值
* @param data 待校验数据指针
* @param len 数据长度
* @return uint16_t CRC16值
* @note 使用Modbus RTU标准多项式0x8005
*/
uint16_t calcCRC16(uint8_t *data, uint16_t len)
{
// 实现代码...
}
10.3.3 行内注释
行内注释只用于解释“为什么这样做”,而不是“做了什么”。
// 好的注释:解释原因
delay_ms(10); // 等待电源稳定,否则ADC读数会跳变
// 坏的注释:废话
delay_ms(10); // 延时10毫秒
我的原则:如果代码本身已经足够清晰,就不要加注释。注释是有成本的——它需要维护。代码改了,注释没改,那就是误导。
10.4 代码审查清单:把关的最后一道防线
代码审查这事儿,我刚开始觉得是浪费时间。直到有一次,我提交的代码里有个 off-by-one 错误,审查的同事一眼就看出来了。要是没审查,这个 bug 可能要到现场测试才能发现——那时候代价就大了。
下面是我自己整理的审查清单,每次提交代码前我都会过一遍:
| 类别 | 检查项 | 说明 |
|---|---|---|
| 命名 | 变量名是否清晰? | 避免单字母变量(循环变量除外) |
| 命名 | 函数名是否体现功能? | 不要用 process()、handle() 这种模糊名字 |
| 命名 | 宏定义是否全大写? | 区分宏与变量 |
| 结构 | 函数是否超过50行? | 超过就考虑拆分 |
| 结构 | 是否有重复代码? | 抽取为函数或宏 |
| 安全 | 数组访问是否越界? | 检查循环边界条件 |
| 安全 | 指针是否判空? | 尤其是函数参数 |
| 安全 | 是否有未初始化变量? | 局部变量必须初始化 |
| 注释 | 注释是否与代码一致? | 过时的注释比没有注释更可怕 |
| 注释 | 是否有 TODO 未处理? | TODO 要么完成,要么提 Issue |
我曾经踩过的坑:有一次审查时没注意一个全局变量的修改,结果导致两个任务同时写同一个变量,系统随机死机。从那以后,我审查时特别关注全局变量的读写位置——全局变量就是全局锁,能不用就不用,用了就要盯死它。
10.5 本章知识体系
下面这张图,把代码风格与规范的几个维度串起来了。你可以把它贴在工位上,每次写代码前瞄一眼。
代码风格这事儿,说白了就是习惯成自然。刚开始你可能觉得麻烦,但坚持一个月,你就会发现:写规范的代码,比写乱七八糟的代码,其实更省时间——因为你不用反复回头去猜自己当初写了什么。
最后送你一句话:好的代码风格,是对未来同事的尊重,也是对自己过去的交代。你想想看,三个月后的你,其实就是另一个同事——别让他骂现在的你。
公众号:蓝海资料掘金营,微信deep3321