10、代码风格与规范:命名规范、缩进与排版、注释规范、代码审查清单

代码风格这事儿,说大不大,说小不小。我见过太多项目,功能跑得挺好,但代码一打开,简直像车祸现场。你想想看,一个团队里,有人用驼峰,有人用下划线,有人缩进2格,有人缩进8格……这代码还怎么维护?

说白了,代码风格不是审美问题,是工程问题。今天我就把我在嵌入式项目里踩过的坑、总结的经验,掰开了跟你聊聊。

核心观点:代码规范是写给“人”看的,不是写给编译器看的。编译器只关心语法对不对,而人需要一眼看懂你的意图。

10.1 命名规范:名字就是最好的注释

我刚开始写代码时,特别喜欢用 abtmp 这种名字。后来有一次,一个 bug 查了整整两天,最后发现是 tmp 变量在三个地方被复用,值被覆盖了。从那以后,我再也不敢偷懒了。

10.1.1 变量命名

  • 全局变量:加前缀 g_,比如 g_systemTick
  • 静态变量:加前缀 s_,比如 s_bufferIndex
  • 局部变量:简短但清晰,比如 i 只用于循环计数,其他情况用 countindex
  • 布尔变量:用 ishasenable 开头,比如 isReadyhasError

我的习惯:变量名长度控制在 8-20 个字符。太短看不懂,太长写起来累。你想想看,一个叫 thisIsTheCounterForTheNumberOfRetryAttempts 的变量,谁看了不头疼?

10.1.2 函数命名

风格 示例 适用场景
动词+名词 getTemperature() 获取数据
set+名词 setBaudRate() 设置参数
is/has+形容词 isBufferFull() 状态判断
动词+宾语 initUart() 初始化操作

我曾经在一个项目里看到有人写 function1()function2()……嗯,这种命名方式,我建议直接拉出去“枪毙”五分钟。

10.1.3 宏与枚举

  • 宏定义:全大写 + 下划线,比如 #define BUFFER_SIZE 256
  • 枚举常量:全大写,比如 STATE_IDLESTATE_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 注释规范:写给人看的说明书

注释这件事,我经历过三个阶段:

  1. 新手期:不写注释,觉得代码就是注释
  2. 狂热期:每行都写注释,结果代码改了注释没改,反而误导人
  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 本章知识体系

下面这张图,把代码风格与规范的几个维度串起来了。你可以把它贴在工位上,每次写代码前瞄一眼。

代码风格与规范 命名规范 变量:g_ / s_ / is_ 函数:动词+名词 宏:全大写+下划线 缩进与排版 4空格缩进 Allman大括号风格 空行分组逻辑块 注释规范 文件头:功能+作者+日期 函数:参数+返回值+注意 代码审查清单 命名/结构/安全/注释 逐项检查,不留死角

代码风格这事儿,说白了就是习惯成自然。刚开始你可能觉得麻烦,但坚持一个月,你就会发现:写规范的代码,比写乱七八糟的代码,其实更省时间——因为你不用反复回头去猜自己当初写了什么。

最后送你一句话:好的代码风格,是对未来同事的尊重,也是对自己过去的交代。你想想看,三个月后的你,其实就是另一个同事——别让他骂现在的你。


公众号:蓝海资料掘金营,微信deep3321