别让AI把你的代码注释成废话

2026-10-19 15:25:49 8阅读

有些注释看起来很多,实际一点用都没有。比如这种:

cnt++;  // 计数加一
if (voltage < threshold) {  // 如果电压小于阈值
    power_off();
}

它没有告诉你为什么要加一,也没有告诉你阈值从哪里来,更没有告诉你断电前有没有保存状态、有没有通知上位机、有没有可能误触发。这种注释不是文档,是噪声。

嵌入式项目里,真正值得注释的地方,往往不是语法表面,而是代码背后的现场约束。为什么这个 GPIO 初始化顺序不能改?为什么 RS485 方向脚翻转后要等 20 微秒?为什么 ADC 采样要避开 PWM 翻转边沿?为什么这个状态机不能直接从运行态跳回空闲态?为什么这个错误码不能删除?这些信息通常不在代码本身里。老工程师知道,新人不知道;当年调过板子的人知道,两年后接手的人不知道;AI 更不知道。

所以给嵌入式代码自动加注释这件事,不能简单理解成"让 AI 扫一遍代码,把每个函数解释一下",那样很容易把项目注释成一堆正确但无用的废话。真正有价值的做法,是设计一个团队可复用的 Skill,让 AI 按统一规则给用户自己写的代码补关键注释,同时避开开源库、芯片库和自动生成文件。注释要少而准,能解释设计意图、状态机、边界条件和硬件约束。

一、嵌入式代码最缺的不是注释数量

很多项目并不是没有注释,而是注释没有打在关键点上。我见过不少代码,函数头写得很长,真正危险的逻辑却没有一句说明。

/**
 * @brief 处理电机状态
 */
void Motor_Process(void)
{
    switch (motor_state) {
    case MOTOR_STATE_RUN:
        if (fault_flags & MOTOR_FAULT_OVERCURRENT) {
            motor_state = MOTOR_STATE_STOPPING;
        }
        break;
    }
}

这个 @brief 没错,但也没什么价值。真正需要说明的是:过流后为什么不是直接进入停止态?STOPPING 状态里是否要保留减速过程?这个状态切换是否和功率驱动保护时序有关?中断里会不会同时改 fault_flags?

嵌入式注释的价值,不是把代码翻译成中文,而是把代码看不出来的工程事实补上。如果团队要批量使用 AI 自动注释,第一条规则就应该写清楚:不要解释语法,要解释意图、边界和风险。

二、自动注释最容易翻车的三个地方

第一个翻车点,是注释范围失控。AI 如果不加限制,很可能连 CMSIS、HAL、FreeRTOS、第三方协议栈、开源算法库都一起注释,结果仓库里多出一堆无意义改动,代码评审根本看不完。

第二个翻车点,是注释内容太虚。比如"该函数用于处理数据""该变量表示状态""这里进行错误判断",这些话看似没错,但读代码的人并不需要它。

第三个翻车点,是注释破坏工程节奏。嵌入式项目经常对行宽、编码、编译宏、静态分析、MISRA、Doxygen 格式有要求,批量注释如果不统一,后面维护起来会很痛苦。

所以这个 Skill 不能只写一句"给代码加注释",它至少要管住五件事。

要管住什么具体要求不管会怎样
注释范围只处理用户自研代码,排除开源库和生成文件引入大量无关变更
注释位置只注释关键变量、函数接口、状态机、边界逻辑注释膨胀,代码变吵
注释内容解释意图、约束、风险、单位、并发关系变成语法翻译
注释格式函数头、变量、局部逻辑统一模板团队风格混乱
验证方式编译、静态检查、差异评审注释影响代码或掩盖风险

如果这五件事没定义清楚,AI 自动注释很容易从效率工具变成仓库污染源。

三、先分清哪些文件不能碰

嵌入式项目里有很多文件不应该被 AI 批量注释,比如:芯片厂商库、RTOS 内核、USB/TCP/IP/文件系统等开源组件、自动生成的初始化代码、第三方算法库、已经受版本控制的协议头文件、只读导入的客户 SDK。这些文件不是不能读,而是不要随便改。

Skill 里必须先定义排除规则,建议直接写清楚排除目录和文件特征。

类型常见路径或特征处理方式
芯片厂商库Drivers/CMSIS/、Drivers/STM32*_HAL_Driver/、SDK/不修改
RTOS 内核FreeRTOS/、rt-thread/、ThreadX/不修改
开源组件lwip/、fatfs/、mbedtls/、tinyusb/不修改
自动生成文件包含 Generated by、DO NOT EDIT、.ioc 生成痕迹不修改
第三方协议库third_party/、external/、components/vendor/不修改
用户自研代码App/、BSP/、User/、Src/ 中非生成部分可分析

这里要注意:BSP/ 不一定都能改。有些板级文件是用户写的,可以注释;有些是工具生成的,只能跳过。Skill 不能死板,要先识别文件来源。

四、不是每一行都值得注释

自动注释最难的地方,是克制。嵌入式代码里,应该优先注释这些地方:

注释对象注释重点示例
全局变量单位、更新者、并发访问、生命周期g_adc_mv 的单位是毫伏,由 ADC 任务更新
状态变量状态含义、合法迁移、异常回退电机状态机不能从运行态直接回空闲态
函数参数输入范围、单位、是否允许为空timeout_ms 为毫秒,0 表示不等待
返回值错误码含义、调用方处理方式返回负数表示硬件错误,需要重新初始化
中断逻辑ISR 内只做什么、不做什么只置位和计数,不解析协议
DMA 缓冲区读写者、边界、覆盖策略DMA 写入,任务读取,满时丢弃新帧
状态机分支分支触发条件和硬件原因等待驱动电源稳定后再进入运行态
协议兼容旧版本保留原因兼容现场旧上位机,不可删除
安全保护阈值来源、动作顺序先关 PWM,再断使能,避免桥臂误导通

不建议注释这些地方:单纯的赋值、一眼能看懂的条件判断、标准库调用、普通循环计数、与业务无关的临时变量、已经很清楚的枚举名称。注释应该增加信息密度,而不是增加文字密度。

五、团队要先统一注释格式

自动注释要能批量用,格式必须统一。我比较推荐嵌入式 C 项目使用接近 Doxygen 的函数头,同时保持局部注释克制。函数注释模板可以这样定:

/**
 * @brief 处理 RS485 接收事件并推进协议解析状态机。
 *
 * @param port 通信端口索引,取值范围为 0 到 COMM_PORT_MAX - 1。
 * @param event 接收事件,必须来自串口空闲中断或 DMA 完成回调投递。
 *
 * @return 0 表示处理成功;负数表示端口无效、缓冲区异常或协议状态机错误。
 *
 * @note 本函数运行在通信任务上下文,不能在中断中直接调用。
 * @note 只消费环形缓冲区中的完整帧,不负责清空 DMA 原始缓冲区。
 */
int Comm_ProcessRxEvent(uint8_t port, const CommRxEvent_t *event);

变量注释可以这样定:

/* DMA 写入位置,由串口空闲中断更新;通信任务只读取快照,避免直接改写。 */
static volatile uint16_t s_uart3_dma_write_pos;

/* 电机保护状态机当前状态,只允许 Motor_Task 在任务上下文推进。 */
static MotorProtectState_t s_protect_state;

局部逻辑注释可以这样定:

/* RS485 方向脚释放后需要等待收发器进入接收态,V2.1 板卡实测最小稳定时间约 15us。 */
Delay_Us(20);

/* 零长度接收通常来自空闲中断重复进入,不能推进写指针,否则会破坏环形缓冲区边界。 */
if (rx_len == 0U) {
    s_uart3_zero_len_cnt++;
    return;
}

状态机注释可以这样定:

switch (s_motor_state) {
case MOTOR_STATE_STOPPING:
    /*
     * 先等待 PWM 输出降为 0,再关闭驱动使能。
     * 直接跳到 IDLE 会绕过制动释放流程,可能导致功率桥残余电流未泄放。
     */
    Motor_UpdateStopRamp();
    break;
}

这样的注释有几个特点:不重复代码表面含义、说明上下文和约束、能被后来的人验证、不把一句话写成论文。

六、一个团队可用的自动注释 Skill 应该长这样

下面是一个完整的中文 Skill 示例,可以作为团队内部版本的雏形。

---
name: embedded-code-commenter
description: 当用户要求为嵌入式 C 或 C++ 项目自动补充代码注释、统一注释格式、批量生成函数说明、变量说明、状态机说明、参数返回值说明,并且需要排除开源库、芯片库、自动生成文件和第三方代码时,使用本技能。
---

# 嵌入式代码自动注释

## 目标
为用户自研的嵌入式代码补充少量、准确、统一、可维护的注释。注释重点是设计意图、硬件约束、状态机规则、并发关系、参数单位、返回值含义和边界条件。不要把代码逐行翻译成中文。

## 处理范围
优先处理:App/、User/、BSP/ 中用户手写部分、Src/ 中非自动生成部分、Inc/ 中用户定义的接口头文件、项目中特别标记为业务代码的文件。
必须跳过:芯片厂商库、RTOS 内核、开源组件、第三方 SDK、自动生成文件、已经明确禁止修改的协议文件、用户没有授权修改的目录。

## 排除规则
遇到 CMSIS、HAL_Driver、FreeRTOS、rt-thread、lwip、fatfs、mbedtls、tinyusb、third_party、external、vendor、DO NOT EDIT、Generated by 时,不修改文件。如果文件来源不确定,先只读分析,不直接修改。

## 注释原则
1. 只给关键代码加注释。
2. 不解释一眼能看懂的语句。
3. 不写空泛评价,例如"提高稳定性""保证安全运行",除非说明具体机制。
4. 不改变代码逻辑、变量名、函数名、宏名和文件结构。
5. 不引入新的头文件,不扩大编译产物。
6. 保持原有编码、缩进和行宽风格。
7. 注释必须能被代码或工程事实支撑。
8. 如果无法判断设计意图,写成待确认注释或不写。

## 必须优先注释的对象
- 对外接口函数、带硬件时序要求的函数、中断服务函数和回调函数。
- DMA、环形缓冲区、双缓冲相关逻辑。
- 状态机状态和关键迁移条件。
- 安全保护、降额、故障恢复逻辑。
- 协议兼容分支、全局变量、静态变量、跨中断和任务共享的变量。
- 带单位的参数和返回值。
- 魔法数、阈值、延时值、重试次数。

## 函数注释格式
使用 Doxygen 风格:@brief 用一句话说明函数职责,避免重复函数名;@param 说明参数含义、单位、范围、是否允许为空;@return 说明返回值含义,特别是错误码和调用方应如何处理;@note 说明调用上下文、硬件约束、并发限制或版本兼容。没有参数时不写 @param,没有返回值时不写 @return,没有特殊约束时不强行写 @note。

这个 Skill 的关键,不是写得长,而是把自由度控制住。它告诉 AI:哪些能动,哪些不能动;哪些必须注释,哪些不要注释;注释应该写什么,不能写什么;最后还要检查什么。这才像一个团队能批量使用的工具。

七、批量使用时,先小范围试跑

我不建议第一次就让 AI 扫完整个仓库。正确做法是先选一个模块试跑,比如 App/Protocol/ 或 App/Motor/,建议第一轮只处理 5 到 10 个文件。

评审时重点看:有没有注释废话、有没有把不确定的现场原因写死、有没有漏掉关键参数单位、有没有误改第三方代码、有没有让代码更难读。通过之后,再扩大到整个用户自研目录。

好注释不是给编译器看的,也不是给领导看的。它是给半年后的自己、刚接手项目的同事、半夜处理现场问题的人看的。嵌入式代码里,很多真正重要的信息都藏在代码外面:板卡版本、现场波形、硬件余量、客户兼容、故障复现条件、当年踩过的坑。AI 可以帮我们把这些信息补到代码边上,但前提是我们要给它规则。这才是 AI Coding 在嵌入式团队里该做的事。


作者:不脱发的程序猿
链接:https://www.aiddithome.com/p/9341335a27e29.html
本作遵循 [CC BY 4.0 署名] 许可,版权归作者所有。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
3条评论

最烦的就是AI注释成语法翻译,确实不如不注

装过才知道
2楼 · 2天前

先把排除目录定好再批量注释,不然真会改乱

Lumen微光
3楼 · 2天前

团队注释Skill就该像这样把边界定清楚