代码生成与改写:小步提交,避免大爆炸式重写
"帮我重写整个 order 模块"是 AI 辅助开发里最贵的一句话。它一次产出上千行 diff,你既没法逐行审查,也没法在出错时定位是哪一步引入的;一旦行为偏移,回退的代价等于重新写一遍。
本章要解决的问题
- diff 太大,评审退化成"扫一眼 + 点通过";
- 一次改动涉及多个文件,测试红了不知道是谁的锅;
- 让 AI"优化一下",它顺手改了命名、日志和错误处理,噪音淹没真实改动;
- 出问题后
git bisect失去意义,因为一次提交里塞了十件事。
核心纪律一句话:一次只动一个函数或一个文件,每步编译加测试,逐块审查 diff。
小步改动纪律
| 纪律 | 具体做法 | 检查命令 |
|---|---|---|
| 一次一个函数 | 提示词里写死"只修改 Redeem 函数体" | git diff --stat 只应出现 1 个文件 |
| 一次一个文件 | 跨文件改动拆成多个提交,先改接口再改调用点 | git diff --stat 变动行数 < 200 |
| 每步编译加测试 | 每次生成后立刻跑,红了先修再继续 | go build ./... && go test ./... |
| 逐块审查 diff | 按 hunk 读,不按文件读 | git diff -U5 -- internal/coupon/service.go |
| 提交信息写明范围 | refactor(coupon): 抽出核销校验函数 | git log --oneline -1 |
"每次生成后立刻跑"是最容易被跳过、也最不能跳过的一条:测试通过的每一步都是安全回退点,没跑测试的一步则是债务。
提示词:把范围钉死
目标:让 internal/coupon/service.go 的 Redeem 支持"订单金额低于门槛时返回 ErrBelowThreshold"。
严格约束:
1. 只允许修改 Redeem 的函数体,不改签名、不改其他文件;
2. 不改动现有注释、日志格式与错误文案;
3. 不要顺手重构、不要重命名变量、不要调整 import 顺序;
4. 输出 unified diff(git diff 格式),不要输出整个文件;
5. 如果发现必须改其他文件,先停下来说明原因,不要擅自改。
第 4 条尤其重要:要 diff,不要要文件。让 AI 输出整文件,你就得在几百行里找它改了哪 12 行;要 diff,审查面积直接等于改动面积。
一次改动应该有多大
| 规模 | 行数参考 | 能否一次交给 AI |
|---|---|---|
| 单函数改逻辑 | 5~40 行 | 可以,最理想 |
| 单文件多函数 | 40~150 行 | 可以,但要逐 hunk 审 |
| 跨 2~3 文件改接口 | 150~300 行 | 拆两步:先改接口,再改调用点 |
| 整个模块重写 | 500 行以上 | 不要,见下一节 |
为什么"重写整个模块"危险
- 不可审查:1000 行 diff 里夹带的 3 行行为改变,人眼几乎必然漏掉;
- 行为漂移:旧代码里为绕过某个线上问题写的奇怪判断(带注释的特殊
if、被调过的重试次数)会在重写中消失,而这些恰恰没有测试覆盖; - 责任链断裂:
git blame与 issue 的关联全部失效,日后排查失去线索; - 无法二分:一次提交包含十处改动,
git bisect指到的是"一大坨",不是根因; - 回退即重做:出错时不能只撤销一个函数,只能整体回滚,前面的时间全部白费;
- AI 不知道你没说的约束:重写时它按通用最佳实践来写,你项目里的历史包袱它看不见。
正确的做法是把"重写"翻译成有序的小步:先补测试固化现有行为 → 再逐个函数替换实现 → 每步测试全绿才提交。
# 重写前的安全网:先给旧实现补一组行为测试并确认全绿
go test ./internal/order/... -run TestOrderFlow -count=1 -v
# 每替换一个函数后
gofmt -l . && go build ./... && go test ./internal/order/... -count=1
常见坑
- "顺便优化一下":这类模糊指令必然带来大面积无关改动,提示词里要显式禁止重构;
- 让 AI 直接改文件而不看 diff:工具越自动,越要先用
git diff决定是否保留; - 只在最后跑一次测试:中途的编译错误会被 AI 用"删掉调用"的方式掩盖,所以每步都要跑;
- 相信 AI 的自我总结:它说"只改了一处"不算数,
git diff --stat才算数; - 格式改动与逻辑改动混在一步:gofmt 或 import 排序会污染 diff,应先单独提交格式变更。
小结:小步提交的本质是让每一步都可编译、可测试、可回退、可审查。提示词里钉死"改哪个函数、不许改什么、输出 diff 而不是文件",再配合 git diff --stat 与 go build && go test 两道闸门,AI 的产出就被压在可控范围内。"重写整个模块"不是效率,是把风险一次性堆积到最后一刻。