1. “一开始太顺了”的陷阱:AI代码项目是怎么悄悄烂掉的
我用AI写代码也有两三年了,一开始的感觉谁用谁知道:真爽。一个需求丢给模型,几分钟给你生成一个能跑的模块,再改吧改吧就能用。有一阵子我甚至觉得,只要有AI,一个人顶一个小组不是梦。
直到我接手了一个已经用AI迭代了三个多月的项目,那种感觉才彻底被浇醒。项目能跑,功能也都在,但整个代码库像一座用乐高搭起来的违章建筑——每个模块都稳定地立在那里,但你不知道哪个是承重墙,哪个碰一下就塌。同一个概念被五六种方式实现,日志打得到处都是,还有一堆AI“礼貌性”保留的残缺代码。最可怕的是,没有任何一段代码能说清楚它是为了解决哪个问题而存在的。
后来我在自己的项目里也经历了从“AI辅助”到“AI失控”的完整过程,才慢慢想明白一件事:AI编程项目不是边写代码边烂的,而是从你打开编辑器、把第一个需求丢给AI、却没有任何验收机制的那一刻起就开始烂了。这篇文章不是教你提词,而是讲那条真正能救项目一命的规则:给AI生成的所有代码,建立一条不妥协的审查与验收闭环。
1.1 AI的“局部聪明”和全局失明
先说清楚了,AI写代码的能力没有问题,问题出在它的“视野”上。模型生成代码时,依据的是你给它的上下文窗口、对话记录和它自己训练时的“常识”。它能看到你刚才聊的那个文件,但它看不到你整个仓库里已有的相似函数、既定模式和架构约定。
我用个不太恰当的比喻:这就像你给一个新来的实习生挨个安排任务,每条任务都交代得清清楚楚,他干得也漂亮利落,但他没有向你提问“这个功能之前是不是已经有人做过了”“这个工具类是不是已经有了”,于是每个任务他都会按照自己最顺手的方式来一套。结果三个月后,你发现项目里出现了六个功能相近的时间格式化函数,三个不同的本地缓存实现,还有两套并存的HTTP请求封装。
这个场景在传统开发里也存在,但传统开发有一个防火墙:人。同事会主动翻代码库、会问你、会参考已有实现。AI不会,或者说它在每次会话里都只对当前那几段代码负责,对全局的一致性完全不敏感。那一开始你觉得“每段都写得不错”,恰恰是灾难的温床。
1.2 上下文窗口结束,一致性就开始崩
另一个关键问题是记忆。一块模型记忆窗口再大,也就那么大,项目一旦长了,你根本不可能把整个代码库都丢给它。于是实际操作中,你往往是“每次开新会话、丢一段新需求进去”。AI呢,也配合得很好,每次都根据你给的内容“重新理解一遍项目”。
问题就在这个“重新理解”上。每次重新理解,它都会生成一套新的、自洽但不一定和既有代码对齐的实现。今天你让它优化登录流程,它在一个新的文件里重新写了一套会话管理;下次你让它修另一个bug,它又基于自己新写的这套会话管理做调整——旧的那套就成了没人知道还活着的暗礁。
命名混乱、分层混乱、职责重叠这些“代码坏味道”,在AI项目里出现的频率比传统项目高一个数量级,根因就是这个。你永远不会看到一个AI说:“等等,这段逻辑在另一个模块里已经有了,我们复用它吧。”它的默认行为是给你再写一个。
1.3 隐性债务比显性bug更致命
传统项目里,最让人头疼的是线上bug,因为bug会当场爆炸、当场报警,逼着你去查。但AI项目里更普遍的问题是一类“隐性债务”:代码能跑、测试能过、功能也正常,但没人说得清它为什么要这么写,也没人敢动它。
AI生成的代码有个通病——几乎没有“设计意图”。人写代码时会留下认知痕迹:这个函数为什么这样分割、那个分支为什么优先处理、这段注释里的业务背景是什么。AI生成的代码,是把“大概率能通过测试”的token串在一起,它没有意图。于是当我要改一个两三个月前AI写的函数时,我往往需要先花大量时间去“考古”,猜它当时是看了什么需求写出这个逻辑的。
更有意思的是死代码。你在让AI改一个功能时,它会很“礼貌”地把旧逻辑保留着,加一个新分支绕过去。你以为它删了,其实没有。等到项目越来越大,这些AI留下的“历史遗迹”叠加起来,重复率报表暴涨,构建时间变长,排查问题时又总被这些“幽灵代码”带偏方向。
1.4 烂掉的本质:缺少一道全局校验机制
把上面这些现象归结起来,你会发现AI项目的病灶不是某一个bug,也不是某一个人写不出来,而是整个演进过程里没有任何一道机制在指望AI的时候替人类守住全局。
传统开发里,如果一个人老写烂代码,至少还有架构评审、代码评审、静态检查这些流程兜底。但很多人用AI时,把它们全部省掉了——因为AI产出看起来太像“能用的代码”,这个“能用”的假象,让人失去了警惕心。于是项目就在这种“局部可用、全局失守”的状态下一天天烂下去。
要救这个局面,靠“更聪明的提词”或者“换更强的模型”都没用,你得从生产方式上改一刀。
2. 真正该做的那件事:给AI也立一套“代码审查与验收闭环”
说了这么多问题,你肯定想问:那到底要做什么?答案是四个字:立一套对AI产物有效的代码审查与验收闭环。
说得更直白一点:AI永远只能产出“候选代码”,它绝对不能直接进入主线分支。所有AI生成的代码,必须像一个新人提交的合并请求一样,经过约束、门禁、人工评审三个环节,才允许进入项目。这条规则听上去简单,但绝大多数AI编程项目从来没有设置过,因为人人都在追求“一次生成就直接用”。
2.1 核心动作:让AI永远只产出“候选代码”
我做项目时有一条铁律:AI生成的代码绝不能直接进入主干。无论模型给我的结果看起来多正确、多完整,都要在代码托管平台上开一个独立的合并请求。合并请求的意义不只是留痕,它实际上是逼着你给每一段AI产出建立一个“审查节点”。
为什么这么强调?因为AI代码最危险的一点就是“看起来很对”。它语法上无懈可击、结构上四平八稳,但可能里面对你项目的核心假设理解错了,或者绕过了一个你辛苦设计的限制。如果没有人在这道关主动看一眼,那么“看起来很对”就会变成“上线之后才被发现很错”。
这其实也是对我自己提出的纪律:每次使用AI前,我都默认它会写错,默认它一定会遗漏边界,默认它会引入重复。有了这个默认,你才会老老实实走审查流程,而不是直接把代码拖进项目里。
2.2 闭环四件套:约束、生成、门禁、评审
我通常在项目里搭一条这样的流水线,对应四个环节:
第一,约束。在项目里放一份清晰到“傻瓜级”的开发约定文件。这份文件里写清楚:项目架构是什么,目录怎么划分,核心模块有哪些,什么代码绝不允许AI直接改(比如基础库、安全校验、支付相关),什么样的命名风格被允许。它不是写给程序员看的,而是每次让AI动手前喂给它的“项目世界观”。
第二,生成。把AI当成一个高效的编码助手,而不是架构师。只安排它做“小步快跑”的独立任务,每一次只解决一个问题,每过一小段就提交一次候选代码。我从不让AI一口气完成一个大功能再交给我,因为越大的提交,越容易藏雷。
第三,门禁。在代码进入人工评审之前,先让它过自动化检查。这个后面会展开讲,简单说就是静态扫描、单测、构建、覆盖率这套基础设施必须立起来,而且任何AI提交都一视同仁。
第四,评审。人工评审是整套闭环的终点,也是AI项目里最不可替代的一环。评审要回答的不是“这段代码语法对不对”,而是“这段代码该不该以这种方式存在”。
2.3 为什么人工评审不能省略
你可能会问:静态检查和测试都过了,还非得人工看吗?我觉得在AI项目里,人工评审的权重反而应该更高。因为自动化检查只能验证“代码对不对”,而AI项目中最贵的问题是“代码该不该存在”。
举个真实的例子:我见过一个AI生成的日志模块,功能非常完整,轮转、压缩、级别过滤都有,测试也全过了。但它在异常分支里把完整的数据对象直接打进了日志里,里面包含用户身份信息。如果只靠自动检查,这件事根本不会被发现,因为它不会报错。只有一个人带着“这行日志会在生产环境里打印什么”的视角,才能把它拦下来。
另一个例子是架构侵蚀。AI经常会把本该放在数据访问层的SQL拼接逻辑写到控制器里,这对测试完全没影响,但会让项目越来越难维护,最后变成一锅粥。自动化工具很难判断“这一行代码放错了抽象层”,只有人才有这个判断力。
2.4 和传统Code Review的本质差异
我知道有些人听到“人工评审”会眉头一皱,觉得这不就是老一套吗?不完全是。传统代码评审默认同事是有全局视野、有责任感的,只是需要互相查漏补缺。但AI评审的前提是:评审者默认AI一定会犯“结构性错误”,而且AI不可能为自己的代码负责。
所以针对AI代码的评审清单,比传统评审更偏重“一致性”和“边界意识”。我会反复去查的不是那个函数写得漂不漂亮,而是“这个新代码是不是重复造了轮子”“它是不是动了不该动的地方”“它有没有引入偏离项目既有风格的第三种实现”。
这套闭环到底有多大用处?我拿自己的一个项目复盘来说清楚。
3. 一个真实项目的“救火”复盘:从AI失控到AI可控
我在去年做了一个订单查询服务,算是个不大不小的内部工具。那段时间正好AI编码效率达到巅峰,我于是比较激进——从数据库设计建议、接口代码、分页逻辑、缓存策略,全都靠AI生成,生成完我就测试,没太仔细看实现细节就直接提交了。刚开始效率确实高,大概两周就把核心流程跑通了,那会儿我还跟人夸AI好用。
3.1 初次失控的现场:三个月后重构毫无下手之处
等到了三个月后,我准备在上面加一个新需求,打开项目一看,彻底傻眼。项目里出现了三个不同的缓存实现:一个基于进程内存,一个基于外部缓存组件的Java客户端封装,还有一个在接口层自己用并发Map硬撸的简易缓存。分页逻辑写了两套,一套是手写的limit/offset,一套是引用的分页插件,两者响应结构不一致,前端调用的时候需要分别处理。
更让我后背发凉的是,有五个模块各自实现了一个时间格式化函数,输入参数、输出格式、时区处理各不相同。我当时的第一个冲动是“全部重构”,但转念一想,重构需要先弄懂它们为什么存在、被谁调用了,而这个过程在那个项目里变得极其痛苦,因为没有任何注释、没有任何设计说明,唯一能确认的是它们都“能跑”。
这就是AI项目的典型死法:每个模块活着的时候都好好的,但当你试图把它们拼成一套系统时,就会发现自己面对的根本不是代码,而是一堆彼此不认识、但住在同一个仓库里的陌生人的临时涂鸦。
3.2 一次典型事故:一段“合理却致命”的代码
真正让我下决心改变的是那一次线上事故。当时用户在高峰期反馈查询很慢,我排查半天,发现CPU和内存都异常,最后定位到一个AI生成的列表查询接口,里面的循环里带着一个N+1查询,而且后端分页逻辑被那个接口完全绕过了——它一口气把所有数据都查出来,然后又用Java在内存里做了过滤和分页。
这段代码逻辑上其实写得不丑,异常也处理了,注释也有,但它在数据量小时完全没问题,数据一涨就变成灾难。而这种事,在传统开发里顶多发生在初级程序员身上,但初级程序员至少会迎来一轮认真的code review把它拦住。AI代码没有这道关,它就直接上线了。
我当时把它修好的处理方式很直接,但这样的事不能每次都靠“事后救火”。
3.3 建立闭环的具体顺序:先立规矩,再补门禁,最后养习惯
那次事故之后,我花了几天时间,给项目搭了一套此前本该从第一天就存在的流程。核心步骤我按顺序排出来,你可以直接参考:
第一步,把所有AI产出的提交方式统一改掉。任何模型生成的内容都必须先在本地跑起来,然后在代码托管平台上开合并请求,不允许直接推到主干。这个改动一开始会让人很不适应,因为多了一道工序,它很别扭。但它是整套机制的基石。
第二步,补齐自动化门禁。我给项目配了一个流水线,每次有新的合并请求进来,自动跑三件事:静态扫描、核心路径单测、重复代码检测。其中重复代码检测是我觉得AI项目里价值最高的一项检查,因为它能直接揪出“AI又写了一遍已经存在”的实现。
第三步,写了一份针对AI提交的评审清单,贴在自己屏幕边上。之后每次评审AI代码,我都按清单逐项走:这个改动解决了几个问题?是否只解决了一个?有没有复用既有的工具?错误处理完整吗?有没有动到核心依赖?改动的抽象层级对不对?
整套流程上线一个多月,效果是肉眼可见的:合并代码的返修率降下来了,线上出问题的次数明显减少。更重要的是,项目终于回到了我能够“谈论它”的状态,谁改了什么、为什么改,至少能在记录里找到踪迹,而不是靠考古。
3.4 为什么这套机制能救项目
复盘下来,这套机制的价值不是帮你“多检查几遍”,而是它强制地把AI生成的代码从“直接从机器里流出来”转变成了“经过人类认可才成为项目的一部分”。传统开发里你的控制点在人,AI项目里控制点必须在流程,否则项目一定会在某个时刻脱离你的认知范围。
我知道这个流程会让“用AI编码的效率”打个折扣——本来一次生成的代码,现在要经过那么多检查。但它其实是在“一次性爽完”和“长期能维护”之间做一个选择。对于写着玩玩的小脚本,流程无所谓;但对于任何打算活半年以上的项目,这个折扣必须付。
4. 可落地的四层防线:门禁、清单与约束文件怎么配
说了这么多理念和复盘,下面给一份可照抄的落地配置。我把救活项目的关键点拆成四层防线,这四层不需要一次到位,但最好按顺序补。
4.1 第一道墙:一份“给AI看的项目说明书”
很多人用AI辅助开发,第一件事就是打开编辑器直接开聊,这是最容易踩坑的。你让一个没有项目背景的人写代码,他会靠猜;你让AI在没有上下文的情况下写代码,它也会靠猜,只是猜得更流利而已。所以第一道墙是给项目写一份说明书,我通常放在根目录下,命名为PROJECT-GUIDE.md。
里面至少要有这么几块内容:
- 项目是干什么的,核心业务概念有哪些;
- 目录结构是什么样的,每层应该放什么、禁止放什么;
- 有哪些关键模块是“雷区”,AI绝对不允许直接重构,比如登录鉴权、配置加载;
- 命名规范和编码风格,简洁扼要地写清楚;
- 常见工具类的说明,列出有哪些已有实现可以复用。
每次让AI动大需求前,先把这份文件丢给它,再开始聊。它能大幅减少“AI凭空造轮子”的概率。当然,这份文件本身也需要维护,项目发展了就同步更新,别让它过期。文件过期比没有文件还危险,会让AI照着错误信息乱闯。
4.2 第二道墙:静态扫描和重复代码检测
第二道墙是自动化检查,能让AI代码在进入人眼之前先被机器过一遍。实用的检查项不用贪多,筛出几个最关键的:
- 重复代码检查:AI项目里最值钱的检查。能直接暴露“这个实现项目里已经有了”“这个方法在三处出现过”。看到重复率上升,就说明AI在“搞平行宇宙”。
- 复杂度检查:AI喜欢写超长方法和巨深的嵌套。把复杂度阈值设低一点,AI的提交就容易被挡回来,逼它拆解成小函数。
- 未使用代码检查:配合测试一起揪出AI“礼貌性”留下的死代码。
- 基本的坏味道规则:比如空catch、硬编码的敏感信息、可疑的相等判断。
这些检查可以在CI流水线里配置,也可以在本地通过钩子触发。建议把它们和合并请求绑定起来,检查不通过、不允许合并,这样才能真正挡住那些“看起来能跑”的垃圾代码。
4.3 第三道墙:测试门禁不能只走过场
测试门禁是很多AI项目最容易断掉的一环,因为它见效慢,前期总会觉得“需求都做不过来还写什么测试”。但我觉得AI项目恰恰比传统项目更需要测试,因为AI代码里最容易被漏掉的,是那些“看起来不用测”的边界分支。
我自己的做法是,AI生成的每个新函数,至少要为“正常路径”和“一个典型异常路径”各写一个测试,别追求覆盖率百分百,但核心业务逻辑必须覆盖。测试的意义不只是防回归,更重要的是,它是在人工评审之前的一道“逻辑翻译”:测试会强迫你把AI代码的假设从隐性的变成显性的。你写测试时如果发现“我不太确定这个函数这时应该怎么返回”,恭喜你,发现了一个AI糊弄过去的风险点。
4.4 第四道墙:一份针对AI代码的评审清单
人工评审是最后一道墙,也是你作为主理人真正行使决策权的地方。下面这份清单来自我多次“被AI坑过之后”总结出来的,你可以印出来贴显示器边上:
| 检查项 | 具体问题 | 说明 |
|---|---|---|
| 单一职责 | 这次改动是否只解决了一个问题 | AI喜欢顺手改一堆无关内容 |
| 复用性 | 有没有重复造轮子 | 查查已有工具类/函数里有没有现成实现 |
| 抽象层级 | 代码放在该放的层了吗 | 控制器里有没有堆SQL/业务逻辑 |
| 错误处理 | 异常路径和边界条件是否处理完整 | 不要只测开心路径 |
| 安全边界 | 有没有输出敏感数据/绕过鉴权 | 日志、接口、返回结构都要检查 |
| 兼容性 | 有没有破坏既有调用方 | 接口变更有没有同步改调用方 |
| 代码清理 | 有没有遗留无用代码或实验性分支 | AI经常把不要的代码留在原地 |
这套清单的重点,不是用来看语法,而是逼你自己回答“为何这段代码应该存在”以及“它是否和项目既有结构相容”。人一旦变成流程的一部分,AI项目就不会再朝着失控的方向无边际地膨胀。
5. 从个人到团队:让AI编程在长期迭代中保持健康的组织纪律
如果你是一个人维护自己的项目,上面四层防线已经足够让你避开绝大多数坑。但如果你所在的小团队也在重度使用AI写业务代码,还有最后几条组织纪律值得提前立住。
5.1 让代码永远有“人责”
AI可以写代码,但AI不会为自己的代码负责。所以任何一段成为正式的代码,都必须有一个明确的“负责人”——这段代码出问题时,谁能说清它的意图、谁能拍板怎么改。落实到操作层面,就是每个合并请求都必须绑定一个具体的人来评审和批准,哪怕整个项目都是AI写的,也不能出现“这段不是我写的我不懂”的情况。
我见过有些团队演进成“AI写代码、测试过了就算事、出bug就怪模型”的状态,这是最危险的工作方式。代码库如果没有人对每一行负责,那它就不是资产,而是地雷。
5.2 把“AI记忆”换成“项目记录”
AI的项目记忆不可靠,但人类团队的项目记忆可以转为显性记录。每当完成一个比较复杂的模块,我会留一份简短的设计说明,写下“当时为什么选用这个方案”“有哪些备选方案被否掉了”。这份文档不一定很正式,甚至可以写在代码仓库的说明里,但它能成为AI下一次生成时的一个锚点。
这个习惯还有一个额外好处:当你要让AI改这块代码时,你可以把这句决策记录喂给它,它会收敛很多,不再凭空发挥。
5.3 知道什么时候不该让AI出手
AI不是所有场景都适合。我给自己立了几条禁用线:涉及数据库迁移和批量数据修复时,不让AI直接生成改动;涉及支付、权限、敏感数据这类核心安全边界时,不让AI在没有二次审查的情况下触碰;项目正在进行故障复盘时,不让AI顺手重构片段。理由很简单:这些场景里,“试错”的成本极高,而且出问题时AI也给不了你解释的线索。
不是AI写不好这些场景的代码,而是这些场景里的代码一旦出问题,代价就就不是省下来的那点时间能覆盖的。
5.4 几条铁律,写给所有重度AI用户
最后,把决定AI项目生死的关键点汇总成几条铁律,你可以对照一下自己的项目有没有做到:
- 所有AI代码都必须走合并请求流程,绝不直接上主干;
- 自动化门禁(静态扫描、测试、重复率检查)是底线而非可选项;
- 人工评审的重点不是语法,而是“为什么存在”和“是否与全局一致”;
- 核心模块和敏感边界处,AI只出草案,人来拍板;
- 每次AI大改动,都同步更新项目记录,防止项目变成不可解释的黑箱。
这几条铁律并不是要限制AI的发挥,恰恰相反,它们是让AI能力能持续发挥的前提。没有边界的工具是灾难,有边界的工具才是杠杆。
我自己经历过从头到尾不设防的AI项目,也经历过救火后建立闭环的过程。如果现在让我回到第一次用AI写项目的那一天,我不会去研究更花的提词技巧,也不会纠结选哪个模型,我会做的第一件事,是把上面这套验收规则写进仓库的根目录,然后再开始写第一行代码。AI编程项目健康与否,从来不取决于模型有多强,取决于你愿意为它的产出设多少道关。