第二章 嵌入式C语言编码规范(上):命名规范、注释规范、排版与缩进规范
各位工程师,大家好。今天我们聊聊编码规范。说实话,我见过太多因为命名混乱、注释缺失导致的“血案”了。一个项目做了一年半载,新同事接手时,看着满屏的 a1、temp、data,那表情,简直比看天书还痛苦。所以,这一章,咱们就把这些基本功打扎实。
2.1 命名规范:让代码自己会说话
命名,说白了就是给变量、函数、宏起个名字。名字起得好,代码读起来就像读文章;起得不好,那就是在猜谜语。我个人习惯,命名要遵循三个原则:见名知意、风格统一、避免歧义。
2.1.1 变量命名
变量名,我建议用小写字母+下划线。比如 battery_voltage、cell_temperature。你想想看,如果写成 BatteryVoltage,虽然也能看懂,但跟C语言标准库的风格就不太搭了。C标准库都是小写加下划线,比如 strcpy、printf,咱们跟着走,准没错。
这里有个避坑指南:不要用单个字母命名。我曾经在一个项目里看到有人用 i、j、k 表示所有循环变量,结果一个函数里嵌套了五层循环,最后连他自己都分不清哪个 i 是哪个了。嗯,后来我们规定,循环变量也要有意义,比如 cell_index、sensor_id。
推荐命名示例:
uint16_t battery_voltage_mv; // 电池电压,单位mV
int8_t cell_temperature_c; // 电芯温度,单位℃
uint8_t soc_percent; // 荷电状态,百分比
bool is_charging; // 是否在充电
2.1.2 函数命名
函数名,我建议用动词+名词的结构。比如 get_battery_voltage()、set_charging_current()。这样一看就知道这个函数是干什么的。如果是内部函数,可以加个下划线前缀,比如 _calculate_soc(),表示这个函数只在模块内部使用。
我记得有一次,一个同事写了个函数叫 process(),我问他处理什么?他说“处理数据”。我又问处理什么数据?他说“所有数据”。好家伙,一个函数处理所有数据,这代码根本没法维护。后来我们规定,函数名必须明确描述其功能,不能含糊。
函数命名小技巧:
- 获取数据:
get_xxx() - 设置数据:
set_xxx() - 初始化:
init_xxx() - 检查状态:
check_xxx() - 处理事件:
handle_xxx_event()
2.1.3 宏命名
宏,我建议全部用大写字母+下划线。比如 BATTERY_MAX_VOLTAGE_MV、CELL_OVERTEMP_THRESHOLD_C。这样一眼就能看出这是个宏,不是变量。而且宏名要尽量长一点,把含义说清楚。
我曾经在项目里看到有人定义 #define MAX 100,结果代码里到处都是 MAX,根本不知道这个最大值是干什么的。后来我们规定,宏名必须包含其用途,比如 BATTERY_CELL_COUNT_MAX。
宏命名注意事项:
- 不要用
#define定义函数,用内联函数或普通函数代替 - 宏名要包含模块前缀,比如
BMS_、ADC_ - 避免使用
#define定义复杂的表达式,容易出错
2.1.4 文件命名
文件名,我建议用小写字母+下划线,并且要体现模块功能。比如 bms_core.c、bms_core.h、adc_driver.c、can_comm.c。头文件和源文件要成对出现,名字保持一致。
嗯,这里有个小细节:文件名不要用数字开头,也不要用特殊字符。有些编译器对文件名很敏感,搞不好就编译不过去。
2.2 注释规范:写给人看的代码
注释,说白了就是写给人看的。代码是给机器执行的,注释是给同事(包括未来的自己)看的。我见过太多“为了注释而注释”的代码了,比如 i++; // i加1,这种注释纯粹是浪费生命。
2.2.1 文件头注释
每个源文件和头文件,开头都要有一个文件头注释。这个注释要说明文件的功能、作者、修改历史等信息。我个人习惯用这样的格式:
/**
* @file bms_core.c
* @brief BMS核心逻辑实现
* @author 张三
* @date 2024-01-15
* @version 1.0
*
* @details 本文件实现了电池管理系统的主要逻辑,
* 包括电压采集、温度采集、SOC计算等功能。
*
* @note 修改历史:
* 2024-01-15 张三 创建文件
* 2024-02-20 李四 添加过温保护逻辑
*/
为什么要写文件头?因为项目大了以后,你根本记不住每个文件是干什么的。有了文件头,一看就知道这个文件是干嘛的,谁写的,什么时候改过。我曾经在一个项目里,看到一个文件没有文件头,结果花了半天时间才搞清楚它的功能。
2.2.2 函数头注释
每个函数,尤其是对外接口函数,都要有函数头注释。这个注释要说明函数的功能、参数、返回值、注意事项等。我建议用这样的格式:
/**
* @brief 获取电池总电压
* @param channel ADC通道号,范围0-7
* @param voltage 输出电压值,单位mV
* @return int8_t 0:成功 -1:参数错误 -2:采集失败
* @note 调用前需要确保ADC已经初始化
* 返回值非0时,voltage的值无效
*/
int8_t get_battery_voltage(uint8_t channel, uint16_t *voltage);
你想想看,如果没有这个注释,别人调用这个函数时,还得去翻代码看参数是什么意思,返回值代表什么。有了注释,一目了然。
函数头注释要点:
- @brief:一句话描述函数功能
- @param:每个参数的含义、范围
- @return:返回值及其含义
- @note:注意事项、前置条件
2.2.3 代码行注释
代码行注释,要解释“为什么这么做”,而不是“做了什么”。比如:
// 错误:注释解释做了什么
voltage = adc_read(channel); // 读取ADC值
// 正确:注释解释为什么这么做
voltage = adc_read(channel); // 使用平均值滤波,减少噪声影响
我见过最离谱的注释是 // 这里加1,然后后面跟着 count++;。这种注释,删掉反而更清爽。注释要写的是代码背后的逻辑、设计决策、边界条件等。
代码行注释建议:
- 解释复杂算法或逻辑
- 说明特殊处理的原因
- 标注TODO或FIXME
- 不要写显而易见的注释
2.3 排版与缩进规范:让代码赏心悦目
排版和缩进,说白了就是代码的“颜值”。代码写得漂亮,读起来心情就好;写得乱七八糟,看着就头疼。我个人习惯用4个空格作为缩进,不用Tab。因为不同编辑器对Tab的解释不一样,有的显示2个空格,有的显示8个空格,容易乱。
2.3.1 缩进规则
缩进要统一,不能一会儿用2个空格,一会儿用4个空格。我建议整个项目统一用4个空格。大括号的写法也要统一,我推荐用K&R风格:
if (condition) {
// 代码块
do_something();
} else {
// 另一个代码块
do_other_thing();
}
for (int i = 0; i < count; i++) {
// 循环体
process_item(i);
}
为什么要统一?因为代码是团队协作的产物。你写一种风格,我写另一种风格,合并起来就像打补丁一样难看。我曾经在一个项目里,看到同一个文件里有三种不同的缩进风格,那叫一个酸爽。
2.3.2 空行与空格
空行和空格,是代码的“呼吸”。适当的空行可以让代码逻辑更清晰。我建议:
- 函数之间用两个空行分隔
- 逻辑块之间用一个空行分隔
- 操作符前后加一个空格,比如
a + b而不是a+b - 逗号后面加一个空格,比如
func(a, b, c)
排版示例:
// 好的排版
int32_t result = a + b * c;
if (result > threshold) {
handle_overflow();
}
// 差的排版
int32_t result=a+b*c;
if(result>threshold){
handle_overflow();
}
2.3.3 行长度与换行
每行代码不要太长,我建议不超过80个字符。如果一行太长,要适当换行。换行时,要在操作符或逗号处换行,并且缩进对齐:
// 好的换行
int32_t result = calculate_something(param1, param2,
param3, param4);
// 差的换行
int32_t result = calculate_something(param1, param2, param3, param4);
你想想看,如果一行代码有200个字符,在屏幕上根本看不全,还得左右滚动,多麻烦。而且代码评审时,一行太长也不方便对比。
排版注意事项:
- 不要用Tab缩进,用空格
- 不要在一行写多个语句,比如
a=1; b=2; c=3; - 不要用
//注释写在一行代码的末尾,除非很短 - 大括号要成对出现,不要省略
2.4 总结
好了,这一章的内容就到这里。命名规范、注释规范、排版规范,看似简单,但真正做到位并不容易。我见过太多项目,因为编码规范不统一,导致后期维护成本急剧上升。说白了,编码规范不是为了约束你,而是为了让你和你的同事都能高效地工作。
下一章,我们会继续聊嵌入式C语言编码规范的下半部分:变量定义、函数设计、预处理等。到时候,我会分享一些我在实际项目中踩过的坑,希望能帮大家少走弯路。
记住:好的代码,是写给人看的,顺便给机器执行。