第三章 注释规范:让代码会说话
说实话,我见过太多工程师把注释当成「不得不写的作业」。写完代码,随手敲两行,就算交差了。但你知道吗?真正的好注释,能让代码自己「开口说话」。
我在做嵌入式开发的头三年,也犯过这个毛病。直到有一次接手一个离职同事的项目,打开文件一看——好家伙,全是天书。从那以后,我对自己和团队的要求就变了:注释不是写给编译器看的,是写给三个月后的自己看的。
3.1 文件头注释:给每个文件贴个「身份证」
每个源文件、头文件,都应该有一个文件头注释。它就像人的身份证,告诉别人这个文件是干什么的、谁写的、什么时候改过。
我个人习惯用这样的模板:
/**
* @file uart_driver.c
* @brief STM32F4 UART 驱动层实现
* @author 张三 (zhangsan@company.com)
* @date 2024-01-15
* @version v2.1.0
*
* @note 依赖: stm32f4xx_hal.h, ring_buffer.h
* 硬件平台: STM32F407VGT6
*
* @copyright Copyright (c) 2024 CompanyName. All rights reserved.
*/
这里面有几个关键点:
- @file:文件名,别写错。我见过有人复制粘贴,文件名和内容对不上,尴尬不?
- @brief:一句话说清楚功能。别写「UART驱动」,太笼统。要写「STM32F4 UART 驱动层实现」。
- @author:写真实姓名和邮箱。出了问题,大家知道找谁。
- @date:创建日期。注意是创建日期,不是最后修改日期。
- @version:语义化版本号。我习惯用 v主版本.次版本.修订号。
- @note:依赖关系和硬件平台。这个特别重要!嵌入式项目经常换芯片,没有这个信息,移植起来想死的心都有。
⚠️ 注意:文件头注释不要写「最后修改日期」。为什么?因为每次改代码都要改它,很容易忘记。最后修改日期应该交给版本控制系统(Git)去管。
3.2 函数头注释:给每个函数画个「说明书」
函数头注释,说白了就是函数的「使用说明书」。别人调用你的函数,不用看实现,光看注释就知道怎么用。
我常用的模板:
/**
* @brief 通过UART发送一帧数据
* @param huart UART句柄指针
* @param pData 待发送数据缓冲区
* @param len 数据长度(字节)
* @return HAL_StatusTypeDef
* - HAL_OK: 发送成功
* - HAL_ERROR: 发送失败
* - HAL_BUSY: 硬件忙
* @note 该函数为阻塞模式,超时时间由 huart->Timeout 决定
* 调用前请确保 UART 已初始化
* @see uart_receive_frame()
*/
HAL_StatusTypeDef uart_send_frame(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t len);
这里有几个坑,我踩过:
- @param:每个参数都要写清楚含义、范围、方向(输入/输出)。别写「pData: 数据缓冲区」,要写「pData: 待发送数据缓冲区,长度至少为len字节」。
- @return:列出所有可能的返回值,并解释每种返回值的含义。我曾经见过一个函数返回int,注释只写「成功返回0,失败返回-1」,结果调用方根本不知道-1代表什么错误。
- @note:写清楚前置条件、副作用、注意事项。比如「该函数为阻塞模式」,调用方就知道不能在中断里调用它。
- @see:关联函数。方便别人找到相关功能。
💡 小技巧:函数头注释不要写实现细节。比如「先关中断,再写寄存器,最后开中断」——这些应该写在函数体内部的行内注释里。函数头注释只关心「怎么用」,不关心「怎么实现」。
3.3 行内注释:解释「为什么」,而不是「是什么」
行内注释是最容易被滥用的。很多新手喜欢写:
i = i + 1; // i加1
这不是废话吗?代码本身已经告诉你了「i加1」,注释应该解释「为什么加1」。
好的行内注释长这样:
// 跳过CRC校验字节,因为硬件已经自动处理了
pData += 2;
// 这里用do-while而不是for,是为了保证至少执行一次
do {
// ...
} while (condition);
我个人习惯:
- 行内注释放在代码上方,而不是右侧。右侧注释容易超出屏幕宽度,看着难受。
- 注释和代码之间空一格,保持整洁。
- 复杂逻辑一定要写注释。比如状态机跳转、位运算、临界区保护。
🔑 核心原则:注释解释「为什么这样做」,而不是「做了什么」。代码本身已经说明了「做了什么」。
3.4 TODO 与 FIXME 标记:给未来的自己留个便签
TODO 和 FIXME 是嵌入式开发中特别实用的标记。它们就像给未来的自己留的便签,提醒你还有事情没做完。
我常用的格式:
// TODO: [优先级] [负责人] [日期] 描述
// FIXME: [优先级] [负责人] [日期] 描述
举个例子:
// TODO: [高] [张三] [2024-03-01] 需要添加超时重传机制
// 当前版本没有处理丢包情况,会导致通信卡死
// FIXME: [紧急] [李四] [2024-02-15] 这里存在潜在的死锁风险
// 当 rx_buffer 满时,中断和主循环同时访问它
关于标记,我有几条铁律:
- TODO:表示将来要做的功能或改进。不是 bug,是 enhancement。
- FIXME:表示已知的 bug 或潜在问题。必须尽快修复。
- XXX:表示危险代码,需要特别小心。我一般不用,因为太模糊。
- HACK:表示临时解决方案,不够优雅。用这个标记时,记得写清楚为什么 hack。
⚠️ 警告:TODO 和 FIXME 不能成为「永久欠债」。我见过有些项目,TODO 标记三年都没动过。建议每次代码审查时,专门检查 TODO/FIXME 的数量。超过 10 个?说明技术债已经很高了。
3.5 注释的「避坑指南」
最后,分享几个我踩过的坑:
- 注释和代码不一致:改代码忘了改注释。这比没有注释更可怕!它会误导别人。
- 过度注释:每行都写注释,反而淹没了真正重要的信息。记住,好的代码本身就是最好的文档。
- 注释写废话:比如「关闭中断」——代码里已经写了 disable_interrupts(),注释应该写「关闭中断,防止在临界区被抢占」。
- 注释写历史版本:不要在文件头写「v1.0: 初始版本;v1.1: 修复bug;v1.2: 添加功能」。这些应该交给 Git 去管。
我曾经接手过一个项目,文件头注释写了 50 行版本历史,但函数功能一句没提。你说气不气人?
💡 我的建议:每次提交代码前,花 5 分钟检查注释。问自己三个问题:
1. 注释和代码一致吗?
2. 注释解释了「为什么」吗?
3. 有没有多余的废话可以删掉?
养成这个习惯,你的代码质量会提升一个档次。
1. 注释和代码一致吗?
2. 注释解释了「为什么」吗?
3. 有没有多余的废话可以删掉?
养成这个习惯,你的代码质量会提升一个档次。
好了,注释规范就聊到这里。记住一句话:写注释不是为了应付检查,是为了让三个月后的自己少掉几根头发。下一章,我们聊聊命名规范——嗯,这也是个容易「翻车」的地方。