2741 字
约 9 分钟
17
S24-A19 · AI 与注释:写得更漂亮,还是更容易过期

一次批量重构后,助手给 40 个方法补了注释,风格统一、措辞得体。而其中 6 个方法的注释与实现不符——因为它读的是重构前的语义,而重构改变了行为。三个月后,一位同事按注释理解代码,改错了一个分支。

注释的价值与代码相反:代码越新越可信,注释越旧越可疑。这个错位的机制很清楚:模型能在几秒内生成 40 条"看起来很完整"的注释,而它读的语义是"生成那一刻的代码"——之后代码再改,注释不会跟着变。
而"让模型生成注释"这件事,恰好会批量生产"看起来很新、实际可能过期"的注释。


1. 三种注释的寿命

类型 寿命
说明"做什么" 短(代码一改就错)
说明"为什么" 长(原因通常不变)
说明"约束/前提" 长(前提变了才会变)

第一项是"最容易过期"的一类:它复述代码,而代码改一行它就可能错。而模型最擅长的恰恰是写这一类。

第二项与第三项才是"有长期价值"的:**"为什么用 8 秒超时"、"这里假设上游不返回 null"**第二项的"原因通常不变"有一个例外:当"约束变了",原因就过期了。比如"因为上游超时是 5 秒,所以这里设 8 秒"——上游改成 20 秒后,这条注释从"正确"变成"误导"。
—— 这些信息在代码里看不出来,而它们不随实现细节变化。

2. 让模型写注释的风险分布

注释类型 生成质量 过期风险
接口的用途说明 高 低
参数/返回值说明 高 中
"为什么这样写" 低(它不知道原因) —
复杂算法的步骤 中 中

第二项(参数说明)的生成质量高,是因为信息全在签名里。而它也有"细节漂移":参数含义被收窄(原来允许 null,现在不允许)时,描述不会自动更新。
第三项是硬约束:模型不知道"为什么",只能从代码倒推,而倒推出来的"原因"往往是错的(且写得很像真的)。

第四项在"它读懂了算法"时质量不错,但**它可能把"实现方式"当成"目的"**第四项还有个"过度解释":把三行代码解释成一段文字,看起来详尽,实际让读者读得更慢——因为"读注释"与"读代码"是两件事,都要花时间。
—— 比如把"用二分查找"写成"为了性能",而真实原因可能是"数据有序且需要早停"。

<!-- 反例:模型倒推的"为什么"(看起来很合理,实际是猜的) -->
// 使用二分查找以提高性能
<!-- 真实原因(只有作者知道) -->
// 上游按时间升序推送,且我们只需要最近一条,所以从尾部二分

3. 什么样的注释值得让模型写

场景 是否适合
公开 API 的用途与参数 适合
数据结构的字段含义 适合
复杂表达式的拆解 适合
业务规则的理由 不适合
历史决策与替代方案 不适合

第四项(业务规则)还有一个"边界"问题:规则本身可能有例外("满 100 减 15,但生鲜不参加"),而模型只会写出它能看到的规则,写不出例外——除非代码里恰好有。
第一项最适合:"这个方法做什么、参数是什么、返回什么"——信息全在签名与实现里,模型能准确提取。

第四项必须人工:"满 100 减 15"这条规则为什么是 15 而不是 20,只有业务方知道。

4. 手工补"为什么"的三个模板

模板 例子
因为 X,所以 Y 因为上游可能返回空数组,所以这里判空
若改成 Z 会怎样 若改成非阻塞,会在高并发下丢事件
前提是 W 前提是 id 全局唯一(由 DB 序列保证)

第一项最实用:它把"代码里看不到的原因"写下来,而这正是最有价值的注释。

第二项是"防误改":"若改成 X 会崩"比"这里必须这样写"更有说服力第三项(前提)最容易被漏:前提是隐式的("id 全局唯一"由 DB 序列保证),而它一旦不成立,代码就错了。这类注释的价值在于"让前提可被检查"。
,因为它给出了因果。

// 好注释(三行,含原因与前提)
// 因为上游在超时时会返回空数组而不是抛异常,所以这里用 isEmpty 判断而不是 catch。
// 若改成"依赖异常",超时会被当成"没有数据",导致静默少算。
// 前提:上游版本 >= 2.3(旧版本超时会抛)。

"删除"其实是最有效的注释维护动作,而没人愿意做。可以定一条规则:改动一处代码时,若它的注释"做什么"与新的实现不符,就删掉那条注释(而不是改它)——因为"改注释"往往只是把它改成新的重复。

// 三种处置(按优先级)
1 删:只是复述代码的注释(改了代码就删,别改它)
2 留:说明"为什么"的注释(改代码时检查原因是否还成立)
3 改:说明"前提/约束"的注释(前提变了必须改,并标注日期)

5. 注释的"过期检测"

手段 效果
把注释当断言(示例代码可编译) 强
文档测试(doc test) 中(限 Python 等)
定期人工抽查 弱但必要
只保留"为什么"类注释 从源头减少

第一项的代价是"维护成本":示例里的 API 改名要同步改,否则编译失败。而它恰好是目的——用编译失败来提醒"注释需要更新"。
第一项最有效:注释里的示例代码如果可编译/可运行,它就不会过期——因为过期的示例编译不过。

第四项是"结构性"解法:如果只写"为什么",那"代码改了注释要不要改"这个问题就很少出现(因为原因通常不变)。

/// 示例(可被 doclint 或测试校验)
/// <pre>{@code
/// var n = Parser.count("a,b,c");   // 3
/// }</pre>
// 若方法签名变了,这段示例编译失败 → 注释被迫更新

"注释与代码的一致性"还有一层"风格"问题:同一个项目里有的注释用中文、有的用英文,而模型会照它"看到的那一片"的风格生成——于是同一批生成里出现两种语言。约定应当写进提示,或写进仓库的 lint 规则。

// 生成前的两条约定(写进提示)
- 注释语言:中文(标识符与 API 名保留英文)
- 不写"做了什么"类注释;只写"为什么/前提/坑"
// 若无约定,模型会混合风格,评审时又不好统一要求

6. 排错:五种"注释反而误导"

现象 成因 处理方向
按注释改错代码 注释过期 删掉"做什么"类注释
注释与实现不一致 批量生成未复核 只生成可核查的部分
注释解释"为什么"但错了 模型倒推 人工补原因
注释太多没人读 复述代码 精简到"为什么"
新人信注释不信代码 注释太"完整" 标注"以代码为准"

第五项最危险:一份"看起来完整"的注释比"没有注释"更糟第二项(批量生成未复核)的可操作防线是"只让模型生成"可核查"的部分":接口用途、参数、字段含义——这些能对照签名验证;而"为什么"一律人工。
,因为它让人放弃阅读代码。

最后一个"成本"视角:注释不是免费的,它会随代码改动产生维护负担(即使只是"读一遍确认还对不对")。所以"多写注释"不等于"好维护"——有用的注释是少数几条"为什么",其余应当是"命名"与"结构"的责任。

// 三个替代注释的手段(优先于写注释)
1 改名字:把"为什么"编码进方法名(如 retryOnTimeout)
2 拆函数:把前提写成前置检查(if (id == null) throw ...)
3 加断言:把约束变成运行时检查(require(unique))
// 三者都比注释更强,因为它们会被执行

7. 边界:什么情况不必写注释

场景 为什么 做法
自解释代码(命名清晰) 注释是重复 不写
一次性脚本 不长期 不写
测试代码 用例名即说明 少写
已在文档/ADR 里说明 有单一来源 只留链接

最后一条与"团队"有关的边界:注释的读者是"三个月后的自己"。所以判断标准可以更具体:"三个月后我会不会因为不知道这条信息而踩坑"——会,就写;不会,写了也没人读。
判断口径一句话:这条信息"在代码里能不能看出来"。能(做什么、怎么做),注释是多余的;不能(为什么、前提、若改动会怎样),注释才有价值。


8. 自测

  1. 三种注释的寿命为何不同?为什么"说明做什么"最容易过期?
  2. 模型写"为什么"类注释为什么不可靠?它会把什么当成目的?
  3. 哪三类注释适合让模型写?哪两类必须人工?
  4. "因为 X 所以 Y"与"若改成 Z 会怎样"分别解决什么问题?为什么后者更有说服力?
  5. 为什么"示例代码可编译"是有效的过期检测?
  6. 判断"是否需要注释"的那个问题是什么?
S24-A19 · AI 与注释:写得更漂亮,还是更容易过期
https://www.clxhxhhr.top/posts/6547/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。