助手补的注释多半只是复述代码,真正缺的是那条约束:数据从哪来、失败谁处理。
要补文档注释,直接抄文档注释模板里那一份。
删掉这条注释,读的人会不会少知道一件事?
答「不会」,就删掉它——它只是把代码又念了一遍。答「会」,却说不出少知道的是哪一件事,说明注释本身没说清,要重写,不是留着。
工具只检查文档注释在不在,检查不了写得对不对;公开接口缺文档,它能一次全找出来。
| 拦什么 | 规则 | 要不要开 |
|---|---|---|
| 公开的函数缺文档字符串 | Python D103(函数)、D101(类)、D100(模块) | 要开。加之前先跑一次 ruff check --select D . 数现有函数有多少,一次改不完就先只开 D103 |
| 公开成员缺 XML 注释 | C# CS1591,需要 <GenerateDocumentationFile>true</GenerateDocumentationFile> | 要开。打开之后,缺注释会变成警告 |
| 公开项缺文档注释 | Rust #![warn(missing_docs)] 写在 crate 根上 | 要开。配合 -D warnings 让它变阻断 |
| 导出标识符缺注释 | Go revive 的 exported 规则 | 要开,把严重度设成 warning,不要设 error,否则会挡住正在写的代码 |
| 注释周围有空行、格式不对 | 排版工具不检查,Go 的 exported 会看是否紧贴声明 | 不要为它单开工具 |
先看清现状再决定开哪几条,不要一次性把 D 全开——D 里还有一批管标点、管空行、管首字母大写的规则,它们和内容无关。
ruff check --select D103 . golangci-lint run cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
工具查不到的边界:测试函数、getter、私有辅助函数不要求文档注释,它们的名字已经说明了做什么。TS 与 Java 在现有配置下都不检查「注释在不在」,这一项只能靠评审看。
判据是这条注释说没说出代码本身说不出来的东西。做了什么、按什么顺序、用了什么类型,代码自己就能说。代码说不出来的是三样:为什么这样、约束是什么、失败时谁负责。
| 反例 | 为什么无效 | 改成什么 |
|---|---|---|
// 打开文件 | 函数名和参数已经说了 | // 路径来自服务端配置。请求体里的路径不采用,避免读到其他目录。 |
// 增加计数 | 纯复述 | 删掉。计数有上限或并发要求,把那一条写下来 |
// 这里是特殊情况 | 没说出特殊在哪,读者仍要读完代码 | // 首次写入时表还不存在,这里建表再插入。 |
// 参数 scope_id 是所属范围 id | 名字和类型已经说了 | // scope_id 从会话读取,不从请求参数读取。 |
// 这段实现很精妙,不要动 | 无法验证,后来的改动没有判据 | 写出它避开了哪个具体错误,以及改动后哪个测试会失败 |
// TODO 以后改成向量检索 | 没有负责人和日期,等于不打算做 | // TODO(组员名, 2026-11-30): 换成向量检索。见 docs/decisions/0031-naming.md |
// 下面是旧实现 后接十几行被注释掉的代码 | 读的人要逐行判断它还能不能用 | 整段删掉。历史在 git log 里,要用时 git log -p 找回来 |
scope_id、用户 id 从哪来);不变量(这个字段在本函数返回后一定非空);单位与时区;失败时谁处理;为什么不用另一种更直观的写法。
工具查不到的边界:「不变量」这一条在算法与数学代码里放宽。快排的分区函数只写一句它保持什么性质,不必逐行解释交换逻辑。
模板只解决一件事:在写之前先想清楚「约束」和「失败时」这两项。写不出约束就写「无」,空着说明没想过。
第一行写成完整的一句话,以句号结尾。约束与失败各占一段。
def visible(notes, scope_id):
"""返回属于这个所属范围的笔记。
约束:scope_id 来自会话,不从请求参数读取。
失败时:notes 为 None 时抛 TypeError,不做静默兜底。
"""
return [n for n in notes if n["scope_id"] == scope_id]
中间不空行。第一行以函数名开头、以句号结尾,这样 go doc 输出才成句。
// Open 打开 path 指向的笔记文件。
//
// 约束:path 来自服务端配置,请求体里的路径不采用。
// 失败时:返回包装过的 error,调用方用 errors.Is 判断。
// 调用方负责关闭返回的文件。
func Open(path string) (*os.File, error) {
return nil, nil
}
约束多于一句时才展开成多行 JSDoc。eslint:recommended 不检查这段文字在不在。
/** 返回笔记标题,供列表显示。title 为空时返回空串。 */
export function label(note: { title: string }): string {
return note.title;
}
工具查不到的边界:三行以上的模板在只有一种参数、一种返回的简单函数上会变成形式主义,写一行就够。但「约束」这一项只要存在就必须写——多数项目里出问题的恰好是这一项。
评审不问「有没有注释」——助手会给每一行都配上,数量从来不是问题。改问「删掉它会不会少知道一件事」。
只扫新增行。-U0 去掉上下文,避免把没改的老注释卷进来。
git diff main...HEAD -U0 \ | grep -nE '^\+[[:space:]]*(//|/\*|\*|#|""")'
逐条确认每条 TODO 带没带姓名与日期。
git diff main...HEAD -U0 \ | grep -nE '^\+.*(TODO|FIXME|XXX|HACK)'
| 可识别现象 | 说明什么 | 评审动作 |
|---|---|---|
| 新增注释数量远多于新增代码行数的一半 | 多半是逐行复述 | 要求删到只剩有信息量的那几条 |
| 注释与紧邻的代码对不上(注释说返回值非空,代码会返回 nil) | 注释过期了 | 按代码对、注释错处理,要求同一次改动里改掉;不接受「下次一起改」 |
代码里出现 scope_id、路径、口令但没有来源说明 | 信任边界没写下来 | 要求补一句来源,并与数据边界的判定对照 |
工具查不到的边界:注释与代码对不上时,如果是代码本身错了,改代码,不动注释。这条不算例外,只是提醒不要在错代码上把注释改得「一致」。
// 遍历笔记列表,把可见的追加到结果
for _, n := range notes {
if n.Visible {
result = append(result, n)
}
}
// GetNote 获取笔记
// 参数:id 笔记 ID
// 返回:笔记与错误
func GetNote(id int64) (*Note, error)
认它的办法:删掉这条注释,读的人会不会少知道一件事。上面两条都答「不会」——第一条念的是循环体,第二条念的是签名,删掉它们,代码一行信息都没少。真正缺的那句恰恰没写:这个 id 要不要先经所属范围过滤、查不到时算错误还是算空结果。
scope_id、文件路径、用户 id、口令、令牌出现的位置,紧邻处要写一句它从哪来。判据:跑 grep -rn 'scope_id' --include='*.go' .,每一处附近都有来源说明。违反后果:下一个人或下一次助手会照着请求参数写,数据边界立刻被绕开。git diff main...HEAD -U0 | grep -nE '^\+[[:space:]]*(//|#)[[:space:]]*(if|for|func|def|return|import)',输出应为空。违反后果:读的人要逐行判断它还能不能用,而 git log -p 本来就能找回来。TODO(姓名, YYYY-MM-DD):。判据:上面那条 grep 的输出里每一条都符合格式。违反后果:没人认领,到期也不会有人发现。工具查不到的边界:第三条在有生成代码的仓库里放宽——生成器产出的文件本来就不该手改,把那类文件整目录排除在评审范围之外,而不是靠注释说明。生成文件的路径写进 .gitattributes 并标注 linguist-generated。