一、代码规范与风格:命名规范、缩进排版、注释规范与代码块组织
各位同学,咱们今天聊点实在的。
代码规范这事儿,说大不大,说小不小。我见过太多项目,功能跑得飞起,但代码一打开,简直像进了迷宫。你想想看,一个变量叫 a,另一个叫 tmp,第三个叫 data2…… 三个月后你自己都看不懂写了啥。
所以,规范不是束缚,是解放。它让你和未来的自己,以及你的队友,都能少掉几根头发。
1.1 命名规范:名字起得好,bug 找得少
命名是代码的第一张脸。我个人习惯,看到名字就能猜出八九分用途,这才算及格。
1.1.1 变量命名
- 小驼峰或下划线风格:嵌入式 C 里,我推荐用下划线风格。比如
adc_value、timer_count。清晰,一眼能看出含义。 - 不要用单字母:除了循环里的
i、j,其他情况尽量别用。我在项目中遇到过有人用p表示指针,结果全局搜索出来 200 多个p…… 那场面,简直灾难。 - 布尔变量用 is/has/can 开头:比如
is_ready、has_error。读起来像自然语言,舒服。
避坑指南:我曾经接手过一个老项目,变量名全是拼音缩写,比如 csq 代表“传输器启动”。你猜我花了多久才搞明白?整整两天。所以,宁可名字长一点,也别用缩写。
1.1.2 函数命名
- 动词 + 名词:比如
read_sensor()、set_pwm_duty()。一看就知道这个函数要干嘛。 - 返回值类型暗示:如果函数返回布尔值,用
is_、check_开头。比如is_buffer_full()。 - 模块前缀:在大型项目中,我习惯加模块前缀。比如
uart_send_byte()、timer_start()。这样即使函数名重复,也不会冲突。
1.1.3 宏命名
- 全大写 + 下划线:这是铁律。比如
#define BUFFER_SIZE 256、#define GPIO_PIN_13 13。 - 避免魔法数字:别在代码里直接写
if (timeout > 1000)。用宏定义#define TIMEOUT_MS 1000。为什么?因为半年后你看到 1000,根本不知道它代表什么。
小技巧:宏定义里如果涉及表达式,记得加括号。比如 #define SQUARE(x) ((x)*(x))。否则 SQUARE(1+2) 会变成 1+2*1+2,结果等于 5 而不是 9。我当年就踩过这个坑。
1.1.4 文件命名
- 模块名 + 功能:比如
uart_driver.c、timer_config.h。不要用test1.c、final_v2.c这种。 - 头文件与源文件同名:
uart_driver.c对应uart_driver.h。这是基本素养。
1.2 缩进与排版:让代码呼吸起来
缩进不是风格问题,是逻辑问题。我见过有人用 Tab,有人用空格,还有人混用。结果在不同编辑器里,代码像被狗啃过一样。
1.2.1 缩进风格
- 推荐 4 空格:我个人习惯用 4 个空格。Tab 在不同环境下显示宽度不同,容易乱。
- 大括号位置:我推荐 Allman 风格,大括号单独一行。比如:
if (condition)
{
do_something();
}
else
{
do_other();
}
为什么?因为大括号对齐,一眼就能看出代码块的起止。尤其是嵌套多层时,这种风格救过我好几次。
1.2.2 空行与空格
- 函数之间空一行:不同函数之间用空行隔开,视觉上更清晰。
- 操作符前后加空格:比如
a = b + c;而不是a=b+c;。读起来不费劲。 - 逗号后面加空格:
func(a, b, c);而不是func(a,b,c);。
注意:不要为了省几行代码,把逻辑挤在一起。代码是给人读的,不是给机器读的。机器不嫌累,人嫌。
1.3 注释规范:写注释是给自己留后路
说实话,我最怕看到两种注释:一种是完全没有,另一种是废话连篇。比如 i++; // i 加 1。这种注释写了等于没写。
1.3.1 文件头注释
每个源文件和头文件,都应该有一个文件头。我习惯的格式是:
/*
* File: uart_driver.c
* Author: 你的名字
* Date: 2024-01-15
* Description: UART 驱动模块,提供初始化、发送、接收功能
* History:
* 2024-01-15: 初始版本
* 2024-03-20: 增加 DMA 支持
*/
这个注释有什么用?当你一年后回头看代码,或者新人接手时,能快速知道这个文件是干嘛的。
1.3.2 函数头注释
每个函数都应该有注释,说明它的功能、参数、返回值。比如:
/**
* @brief 初始化 UART 外设
* @param baudrate: 波特率,如 115200
* @param data_bits: 数据位,可选 7/8/9
* @return 0 成功,-1 失败
*/
int uart_init(uint32_t baudrate, uint8_t data_bits);
我在项目中遇到过,有人写函数不写注释,结果调用者传参顺序搞反了,调试了整整一个下午。嗯,从那以后,我要求团队每个函数都必须有头注释。
1.3.3 关键逻辑注释
- 复杂算法:比如 FFT、PID 控制,一定要写清楚思路。
- 边界条件:比如
if (count == MAX_COUNT - 1)这种,注释说明为什么是 -1。 - 临时方案:如果代码里有 workaround,一定要标注
TODO或FIXME。否则后来者会以为这是正确写法。
避坑指南:我曾经在一个驱动里看到一段诡异的延时代码,没有任何注释。后来才知道,那是为了等硬件稳定。但因为没有注释,被下一个工程师优化掉了,结果整个系统崩溃。所以,任何非直观的代码,都必须加注释。
1.4 代码块组织原则:把代码当文章写
代码块组织,说白了就是怎么把代码排布得清晰易读。我把它总结为三个原则:
1.4.1 单一职责原则
一个函数只做一件事。比如 read_sensor() 只负责读传感器,不要在里面同时做滤波和显示。如果函数超过 50 行,就该考虑拆分了。
1.4.2 相关代码放一起
变量声明、初始化、使用,尽量靠近。比如:
int fd = open_device();
if (fd < 0)
{
return -1;
}
// 使用 fd 进行读写操作
read_data(fd, buffer, size);
而不是把 fd 声明在文件开头,然后隔了 200 行才用。那样你翻回去找声明,累不累?
1.4.3 减少嵌套深度
嵌套超过 3 层,代码就很难读了。我常用的方法是提前返回:
// 不好的写法
if (ptr != NULL)
{
if (ptr->data != NULL)
{
process(ptr->data);
}
}
// 好的写法
if (ptr == NULL) return;
if (ptr->data == NULL) return;
process(ptr->data);
你看,这样是不是清爽多了?
1.5 知识体系总览
下面这张图,是我自己总结的代码规范知识体系。你可以把它当作一个检查清单。
这张图把四个核心模块串起来了。你写代码时,可以对照着检查:命名规范吗?缩进整齐吗?注释到位吗?代码块组织合理吗?
最后说一句:规范不是一天养成的。刚开始可能会觉得麻烦,但坚持一个月,你会发现写出来的代码自己都爱看。嗯,这就是进步。