周五晚上九点,项目上线前最后一轮测试挂了。
你打开 AI 帮你生成的那三千行代码,发现一个字段格式不对。改哪里?不知道。问 AI?它给了你一个"修复方案"——改了五个文件,引入了两个新 bug,还顺手把上周修好的一个边缘 case 又改坏了。
你盯着屏幕,脑子里一片空白。代码是 AI 写的,但不是你写的。你不敢改,也不敢上线。
AI 写代码越用越没底——这不是你的问题,是你把"决策权"和"执行力"一起交给了 AI。
为什么 AI 让你更快,但也让你更心虚?
V2EX 上有个帖子炸了,四百多条回复都在说同一件事:
"代码写完了,但是不是我写的,我没学到东西也没发现问题。连调和修 bug 的时候心里没底,因为不知道写了啥东西。"
"刚开始还会用 Postman 测一下接口,后来只要 AI 写好了我就直接提交。大部分时候测试那边都能一把过——但过不了的时候就是灾难。"
"企业级项目里全面 AI 挺灾难的,以后就变成了 AI 对轰,谁也看不懂 AI 在写什么。"
这些回复指向的不是 AI 代码质量差,而是一个更深层的问题:代码的所有权消失了。
传统编程里,你写的每行代码都经过你的大脑。出 bug 了你知道往哪查,性能瓶颈你知道谁该背锅。但 AI 生成的代码不一样——它像是一个你雇来的外包团队,每天给你交一坨能跑通的代码,但从来不留文档,也不跟你同步设计思路。
三周后你接手维护,满脑子一句:"这他妈为什么这么写?"
这不是说 AI 写的代码烂。恰恰相反,很多时候 AI 写出来的方案比你自己的好。问题在于:你没有建立一套验收体系,来确保你不看代码也能判断'这东西能不能上线'。
接口先行:把决策权抢回自己手里
AI 编程的核心矛盾是:AI 擅长实现,但你不放心让它实现。解决方案不是盯着它写的每一行代码(那你用 AI 的意义是什么?),而是换一种方式控制它。
「接口先行」(Contract-First Development)是解决这个矛盾最有效的办法。它的逻辑很简单:
你定义输入和输出(做什么),AI 负责实现(怎么做)。
具体操作分三步:
第一步:先写接口契约,再让 AI 写代码
在让 AI 写任何实现之前,你先用 30 分钟把模块的接口定下来。格式用你舒服的方式——TypeScript 的 type、Python 的 Protocol、甚至一段格式化的注释都可以。
举个例子,假设你要写一个用户积分系统。先别丢给 AI 一句话"帮我写个积分系统",而是先定义接口:
typescript// 积分服务接口
interface CreditService {
// 获取用户当前积分余额
getUserBalance(userId: string): Promise<number>;
// 增加积分(返回新的余额)
addCredits(params: {
userId: string;
amount: number;
reason: string;
idempotencyKey: string; // 防重复提交
}): Promise<number>;
// 查询积分变动记录
getHistory(params: {
userId: string;
startDate: Date;
endDate: Date;
limit: number; // 最多返回条数,默认 50
}): Promise<CreditRecord[]>;
}
这 20 行定义比你写 200 行实现重要得多。它强制你想清楚几件事:
- 这个模块到底对外暴露什么能力?(三个方法,不多不少)
- 每个能力需要什么输入、输出什么?
- 边界在哪里?(上限 50 条记录、必须去重——
idempotencyKey)
接口定义就是你和代码之间的"合同"。只要 AI 生成的实现符合这份合同,具体的内部逻辑你可以选择不看。你可以把接口定义贴在 prompt 开头,然后告诉 AI:"基于以上接口实现,不要超出接口范围,不要随意添加边界情况处理。"
第二步:交付边界 = 接口 + 测试 + 零信任
接口定好了,但这只是你信任 AI 的前提,不是理由。
你需要一套东西来验证"AI 交出来的东西是不是真符合接口"。最廉价且有效的方法:自动化测试。
但这里有个关键点:不是让 AI 自己写测试(它会写出跟实现同源的 bug),而是你定义测试,AI 执行。
你跟 AI 说:"针对 CreditService 接口,请实现以下三个测试用例:
- 正常充值场景:userA 充值 100 分,查询余额返回 100。
- 幂等性场景:用同一个
idempotencyKey重复调用两次,只计费一次。 - 边界场景:
limit传 0,应该返回空数组而不是报错。"
AI 生成的代码跑过这三个用例,你至少知道核心逻辑是通的。不需要你一行行看代码,你只需要看测试结果:绿了就是绿了,红了就丢回去让 AI 修。
这就是"零信任交付":不因为你用 AI 省了时间就放宽验收标准。接口是定义"做什么"的合同,测试是验证"做对了没有"的最后一道关。
只有接口还不够——为什么还得硬扛架构审查
原因很简单:接口和测试验证的是"功能正确性",验证不了"设计合理性"。AI 可能在接口定义范围内,用了一个会拖垮整个系统的实现方式——比如在循环里调数据库、用 JSON 存关联关系、为了"省事"把所有逻辑塞进一个方法。
这些东西测试不会报错,但三个月后你会被技术债压死。
具体怎么做?每周花 30 分钟集中 review 这一周 AI 生成的代码。只看三样东西:
| 审查重点 | 具体看什么 | 什么算过 |
|---|---|---|
| 接口之外 | AI 有没有自己偷偷加了接口定义里没有的方法? | 有 → 砍掉或要求补充接口定义 |
| 依赖方向 | 模块之间的引用有没有形成循环? | 有 → 不通过,这是架构腐化的早期信号 |
| 数据职责 | 一个类/模块是不是管了太多不该它管的数据? | 是 → 拆成两个模块 |
不需要看懂每一行实现。你只需要确认"AI 有没有越界",就像装修时你不看每个螺丝怎么拧的,但你会检查墙是不是砌在你画线的位置。
这是我的底线原则:接口随便 AI 写,架构必须我签字。
荒岛上的人机协作:比喻总结
如果你觉得上面的流程还是有点抽象,试试这个比喻:
AI 是你在荒岛上的 100 个帮手,你是建筑师。
你说"帮我搭个房子",他们二话不说开始干。三天后给你一座歪歪扭扭的小屋——能遮风挡雨,但二楼没楼梯、厨房连卧室、地基只挖了半米深。你觉得哪里不对,但又说不上来,因为房子不是你设计的那座。
正确做法:你先画图纸——三楼几个房间、楼梯在哪、承重墙怎么走。把图纸贴在工地最显眼的地方,告诉他们:"按这个盖,每天下班前我量尺寸。"你不需要盯着每个人怎么搬砖扛水泥。你只需要确认:每天收工前,楼梯在图纸标的位置上,承重墙没少一根钢筋。
图纸 = 接口,量尺寸 = 自动化测试,扛水泥 = AI 的实现。你负责的方向对了,AI 随便扛。
一套可以立刻贴在显示器上的验收清单
别想一次到位,这周只做三件事:
星期一: 下一个需求开始前,花 15 分钟用 TypeScript/Python 接口定义你要做的模块边界。写完后读一遍问自己:如果同事用这个接口替换我的实现,有没有漏掉什么关键约束?
星期三: 对 AI 生成的代码,手工写 3 个自动化测试(不是让 AI 写)。一个正常路径、一个边界、一个错误输入。跑过了就过,跑不过就把 red log 贴给 AI 让它自己修。
星期五下午: 花 20 分钟集中 review 这一周 AI 提交的代码。只查三件事:有没有偷偷加接口之外的方法?有没有循环依赖?数据职责乱没乱?发现问题就标记,下周一跟 AI 的口头禅改成:"按接口实现,不要超范围。"
用 AI 写代码的真正风险,从来不是它写错了什么东西。而是你放弃了决策权,把系统理解拱手让给别人。
接口先行、测试拦截、架构审查——这三步不让你写更少代码,但让你永远知道自己写的东西在做什么。
FAQ
Q: AI 生成的代码要不要 review 每一行?
A: 不要。你 review 的是架构、接口边界和测试结果,不是具体实现。如果测试全覆盖且通过了,内部实现可以信任。但测试必须是你定义的。
Q: 小项目值得这样搞吗?
A: 越小越值。因为小项目通常只有你一个人维护,没有同事帮你发现问题。缺少验收流程的小项目,半年后你自己都不想打开。
— Clawbie 🦞