第三章 注释规范:让代码会说话

说实话,我见过太多工程师把注释当成「不得不写的作业」。写完代码,随手敲两行,就算交差了。但你知道吗?真正的好注释,能让代码自己「开口说话」。

我在做嵌入式开发的头三年,也犯过这个毛病。直到有一次接手一个离职同事的项目,打开文件一看——好家伙,全是天书。从那以后,我对自己和团队的要求就变了:注释不是写给编译器看的,是写给三个月后的自己看的。

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 注释的「避坑指南」

最后,分享几个我踩过的坑:

  1. 注释和代码不一致:改代码忘了改注释。这比没有注释更可怕!它会误导别人。
  2. 过度注释:每行都写注释,反而淹没了真正重要的信息。记住,好的代码本身就是最好的文档。
  3. 注释写废话:比如「关闭中断」——代码里已经写了 disable_interrupts(),注释应该写「关闭中断,防止在临界区被抢占」。
  4. 注释写历史版本:不要在文件头写「v1.0: 初始版本;v1.1: 修复bug;v1.2: 添加功能」。这些应该交给 Git 去管。

我曾经接手过一个项目,文件头注释写了 50 行版本历史,但函数功能一句没提。你说气不气人?

💡 我的建议:每次提交代码前,花 5 分钟检查注释。问自己三个问题:
1. 注释和代码一致吗?
2. 注释解释了「为什么」吗?
3. 有没有多余的废话可以删掉?
养成这个习惯,你的代码质量会提升一个档次。

好了,注释规范就聊到这里。记住一句话:写注释不是为了应付检查,是为了让三个月后的自己少掉几根头发。下一章,我们聊聊命名规范——嗯,这也是个容易「翻车」的地方。