直接把函数体复制给 DeepSeek-Coder 让它补注释,结果和真实行为对不上,通常不是模型不会写,而是提问时缺了上下文:函数被谁调用、调用方传进来的实参是什么、返回值在调用点被怎么用。补注释这件事先把签名和调用点对齐,往往比换模型更有效。可以先在一两个函数上跑通流程,确认核对和回填方式顺手,再扩大范围。
给旧代码补注释时,先固定注释对象:函数定义、参数类型与返回值、用检索命令找到的全部调用点,再把这些信息一起交给 DeepSeek-Coder。生成结果逐条核对参数含义、返回条件、副作用、异常与空值处理,只接受能和调用点对上的描述。回填时只改注释,用 git diff 确认改动范围,再用编译或单测确认没有顺手改逻辑。这适用于能被编译或测试覆盖的代码,反射、字符串注册这类隐式调用路径需要人工补判断。
在项目里挑一个函数做样本,列出签名和全部调用点
注释要覆盖的是调用方感知到的行为,不只是函数体里写的那几行。选样本时建议挑调用点在 3 到 10 之间的函数:太少看不出参数在真实场景里怎么用,太多会让核对成本超过收益。开始收集前先用 git status 确认工作区干净,再整理下面四类信息。
- 函数定义:文件路径、行号、函数名,方法还要写清所属类或接收者。
- 参数类型:类型声明里的可空性、指针或引用、默认值、泛型约束,以及类型别名背后的真实类型。
- 返回值:返回类型、错误形式(error、异常、错误码、Result 类型)、可能的哨兵值和零值。
- 调用点:跨文件检索,不要只看当前目录。
# 通用检索,先把 FuncName 替换成真实函数名
rg -n 'FuncName\s*\(' `--glob` '*.go' `--glob` '*.py' `--glob` '*.ts' `--glob` '*.java' .
# 没有 rg 时用 grep
grep -rn `--include`='*.go' `--include`='*.py' -E '\bFuncName[[:space:]]*\(' .
# 只定位函数定义(Go 为例)
rg -n '^func .*FuncName\(' `--glob` '*.go' .
检索结果要人工过一遍:接口实现、反射调用、通过字符串注册的回调通常搜不出来,需要到框架注册点或配置文件里确认。把调用点的上下文(传进来的实参表达式、返回值被怎么用)一并记下来,第三步核对时要用到。同名方法在不同类型上的情况,用类型名加方法名再搜一次。
写提示模板:代码块、注释风格、禁止改动逻辑三段
固定提问结构是为了让不同函数的产出可比、可复用,也方便出问题时定位是哪一段缺了信息。
【代码块】
函数定义(文件:行号):
{{FUNCTION_DEFINITION}}
调用点(最多 {{N}} 处,每处保留前后几行上下文):
{{CALL_SITES}}
类型与接口说明(来自类型定义、接口声明、表结构或文档注释):
{{TYPE_NOTES}} 没有就写“无”,不要留空
【注释风格】
- 注释语法:{{COMMENT_SYNTAX}},例如 Go 的 //、Python 的三引号、Java 的 /** */
- 位置:函数声明上方,方法要标明接收者或所属类
- 必写字段:{{DETAIL_FIELDS}},例如参数含义、返回条件、副作用
- 用词沿用项目已有的 {{DOMAIN_TERMS}},不要引入新概念
- 长度:每段一句话,不解释显而易见的循环和赋值
【禁止改动逻辑】
- 只输出注释块,不改函数签名、参数名和控制流
- 不改调用点,不顺手重命名变量
- 信息不足的位置写 {{TODO}} 并说明缺什么,不要猜
- 输出结构:先给注释块,再给“需要人工确认”列表
三段里最容易被省掉的是调用点。只贴函数体时,模型只能按变量名和局部代码推测参数含义;把实参和返回值用法一起贴过去,才有可能写出和调用方一致的说法。函数很长时可以只截取签名和分支条件,但要写明截断位置,否则模型会把缺失的分支当成不存在。
生成后逐条核对注释与真实行为
生成结果不要直接粘回文件,先按下面几项逐条核对。核对时以类型声明和调用点为准,模型的解释只能当作待验证的说法。
- 参数含义:单位、取值范围、可空性、由调用方还是被调用方校验。对照调用点传进来的值,看注释描述是否成立。
- 返回条件:每个 return 分支对应什么情况;错误是通过返回值、异常还是错误码传出;调用点有没有检查。注释写了返回错误而调用点从不检查,说明实现和注释里至少有一处不可靠。
- 副作用:写文件或数据库、发网络请求、改全局或单例状态、加锁、修改入参(尤其是指针、引用、切片和 map)。这类描述写错的代价最大,要逐个对照函数体内的写操作。
- 异常与空值:nil、null、空字符串、空集合、零值分别走哪条分支,抛不抛异常,是不是静默返回默认值。
- 调用点视角:注释里承诺的行为至少要在调用点对上一次;对不上就标 TODO 交给熟悉业务的人,不要把注释改成与实现不符的另一套说法。
核对时建议把结论直接写进代码评审描述,标明哪些注释是模型生成、哪些是人工修正,后来人接手时能据此判断可信度。同一函数大量出现 TODO,通常说明调用点信息给得不够,先补上调用点再重新生成,比反复追问模型更省事。
回填注释并做一次编译或测试验证
回填时只替换注释块,不动逻辑行。改完先看版本对比:
git status
git diff `--stat`
git diff -w -- path/to/file.go # -w 忽略空白差异,便于确认是否只有注释变化
git diff `--name-only` # 确认没有顺带改动其他文件
diff 里出现非注释行,说明模型输出了代码,或者粘贴时误操作,需要按 hunk 还原那一段再改。确认改动范围只在注释后,跑一次项目已有的编译或测试命令,命令按实际构建方式替换:
# Go
go build ./... && go vet ./...
# Python
python -m compileall src/ && pytest -q tests/
# Java(Maven)
mvn -q -DskipTests compile && mvn -q test -Dtest=目标测试类
# C/C++
cmake `--build` build -j4 或 make -j4
项目里如果有代码生成环节(代码生成器、注解处理器、go generate 之类),注释回填后要重新生成一次并检查生成物差异,避免注释或源码位置参与了生成逻辑。只跑编译不跑测试的情况,建议至少覆盖与该函数相关的那个测试文件。构建或测试环境暂时跑不起来时,也要用上面的 diff 命令确认改动范围,并把没验证到的部分在提交信息里写清楚,不要默认注释改动一定安全。