1. "80%的代码是AI写的"这句话,拆开看每个字
周五晚上赶迭代,你在评审队列里翻到第 17 个 PR,提交信息写着feat: 补全订单状态机,diff 一千两百行,注释工整得像教科书,变量名比你自己起的还克制。你翻了十分钟,逻辑看不出毛病,但你也说不清自己到底验证了什么,最后点了 Merge。这个画面,是过去两年我在不止一个团队里真实见到的东西。AI 生成代码的比例从两年前的"偶尔用一下"变成今天的"默认先问一句",很多团队的日常代码里,超过一半的增量确实来自模型输出。所以当一家以 AI 为核心业务的公司公开说"我们的代码里大约八成是 AI 写的",同时又在公开场合呼吁给 AI 开发按下暂停键时,这个矛盾本身比数字更值得琢磨。
先把这句话的统计口径拆开。代码量有三种算法:按行数、按提交次数、按功能块数量。按行数算最容易虚高,因为 AI 特别擅长生成那种"占位但必须存在"的样板——DTO、序列化、迁移脚本、格式化转换,这些代码以前是人硬着头皮敲的,现在几秒钟就出来,一行行堆上去,比例自然水涨船高。按提交次数算又会偏低,因为一次提交里往往混着手写核心逻辑和 AI 生成的配套代码。按功能块算最贴近真实生产力,但没人会给每个功能块打标签。所以"80%"这个数字真正传递的信息不是精确的产能统计,而是一个态度:这家公司已经默认把模型当成第一作者,人是第二作者和把关人。
那为什么一边这么用,一边还要喊停?把视角放回工程现场就很好理解。写代码这件事的成本,在过去几十年里一直是大头,所以整个行业的方法论、流程、工具链,全都是围绕"人写得慢"这个前提搭建的。代码评审要人肉做,因为它稀缺;需求文档要反复对齐,因为改一次代码很贵;测试要挑重点写,因为时间不够。当写代码这项成本突然下降一个数量级,整条流水线的瓶颈会瞬间转移到下游——评审、验证、上线、运维。这家公司喊停,未必是在担心遥远的未来风险,更可能是他们自己正被这个错位挤压得难受:产能上去了,但团队的判断力和验证能力没有同步上去。
对普通开发者来说,这里有个很实用的信号:你现在跟同行的差距,已经不再是"谁打字快、谁记得住 API",而是"谁能把 AI 产出的东西快速判断成能上或不能上"。判断力的稀缺性正在上升,写代码的稀缺性正在下降。下面几节我把这几年在真实项目里摸出来的做法拆开讲,包括哪些活可以放心交、哪些活碰都别碰、以及一条我自己在用的验收流水线。
2. 写代码变快之后,瓶颈到底跑到了哪里
2.1 评审队列从"没人写"变成"没人看"
我刚接触 AI 辅助开发的时候,第一反应是爽。一个中等复杂度的接口,自己写要半天,模型十分钟给一份能跑的版本,改改就能提。爽了大概两个月,问题开始冒头:团队里的评审队列开始堆积。以前一个迭代团队大概产生三四十个 PR,现在能到一百多个,而且每个 PR 的平均行数翻了三到五倍。评审这件事的本质是注意力,而注意力不会因为代码变多就变多。结果就是两个极端:要么评审人草草扫一眼点通过,把风险埋进主干;要么评审变成体力活,把人拖垮,最后大家开始本能地排斥提 PR,反而降低了协作效率。
我后来意识到,问题的根子在于"提交粒度"被 AI 打乱了。人写代码时,天然会按自己能想清楚的范围来切分工作,一次改动不会超过自己脑子里能装下的量。AI 没有这个约束,你说"帮我把订单模块重构一下",它真的会一口气给你改二十个文件,每一个看起来都合理。所以第一件要建立的习惯是:AI 参与的改动,提交粒度必须由人来定,而且要比手写时更小。我现在的做法是把一次 AI 任务限制在"一个可独立验证的行为变更"以内,超出就拆成多个 PR,宁可多开几个,也不让一个 PR 的 diff 超过 300 行有效逻辑。
2.2 心智负担的真实成本,比你想的高
有个很多人忽略的点:读 AI 写的代码,和读同事写的代码,心智负担是不一样的。读同事的代码,你能顺着他的思路推——他大概想解决什么问题,为什么会这么写,哪些地方是临时妥协。读 AI 的代码,你没有这层"作者意图"的上下文,模型也不会在注释里留下"我这里偷懒了"的线索。它总是写得很整齐,整齐到你会下意识降低警惕。
我在实际项目里做过一个不太严谨但很有参考价值的对比:同样是 200 行、能通过全部单测的改动,人工评审同事写的版本,我平均要花 12 分钟,能说出三到四个可疑点;评审 AI 写的版本,平均花 18 分钟,而且第一次往往只能说出"看起来没问题"。真正的问题是在上线后暴露的——某个边界条件、某个异常分支、某个在测试数据里永远不会触发的路径。这就是所谓的"高可读性幻觉":格式规范不等于逻辑正确,两者被模型包装得特别像。
2.3 一个可以对照的观察表
下面这张表是我把一个业务团队连续观察半年得到的大致规律,不是精确统计,但方向性很稳定,你可以拿自己团队的数据对一下。
| 观察维度 | 人写为主时期 | AI 参与度高之后 | 真正该盯的指标 |
|---|---|---|---|
| 单 PR 平均有效 diff | 80 到 250 行 | 400 到 1500 行 | 有效评审行每分钟 |
| 首次提交到第一条评审意见 | 8 到 20 小时 | 2 到 6 小时 | 评审响应时长 |
| 第一条意见到最终合并 | 4 到 10 小时 | 20 到 60 小时 | 返工轮次 |
| 上线后一周内回滚率 | 较低且稳定 | 先升后降 | 回滚原因分布 |
| 单元测试数量 | 稳步增长 | 暴涨 | 分支覆盖率而非行覆盖率 |
注意最后一行。AI 写测试非常快,但快出来的测试经常是"照着实现写"的,把实现里的错误当成预期固化下来。行覆盖率能冲到 95%,分支覆盖却卡在 60%,这种情况我见过太多次。所以覆盖率门槛要设两条线,行覆盖和分支覆盖分开卡,且分支线不能设太低。
3. AI 写出来的代码,其实分四种成色
3.1 样板与胶水:可以直接收,但要抽查
这类代码包括数据结构的定义与转换、接口的入参出参映射、日志与埋点、格式化的日期处理、简单的 CRUD。特点是逻辑分支少、可验证性强、出错代价低。我现在的做法是:这类代码基本不逐行读,交给静态检查和编译期约束去卡,然后按比例抽查,大概每五个文件抽一个细看。因为就算写错了,通常也是显式报错,不会静默污染数据。
但"抽查"这个动作不能省。我踩过一个坑:模型在处理时区转换时,默认用了本地时区,而项目约定是全程 UTC。单测跑在 CI 的容器里,容器时区恰好是 UTC,所以测试全绿,本地开发机跑也看不出来,直到上线到一台配置了不同时区的机器上,时间戳整体偏了八小时。这种错误的共同特征就是"在测试环境里永远不会暴露",所以抽查时要专门盯着那些"和环境相关"的地方:时区、编码、文件路径分隔符、数字精度、排序稳定性。
3.2 业务规则:必须逐行读,边界要单独列
状态机流转、计价与优惠叠加、权限判断、风控阈值,这些都属于业务规则。它们的共同点是:错一步不会报错,只会算错,而算错的代价往往要用真金白银去赔。这类代码我坚持逐行读,而且会强迫自己做一件事——把模型写的实现翻译回自然语言,再对照需求文档读一遍。如果翻译回来跟需求对不上,说明要么需求有歧义,要么实现有偏差,两种情况都必须停下来。
更狠一点的做法是:让模型先把规则整理成一张真值表,人确认这张表,再让它按表写代码。这个顺序很关键。直接让它写代码,你是在验证一个黑盒;先确认表再写代码,你验证的是一个明确的契约。我在做优惠券叠加逻辑时用这个方法,一次就发现了"两张券互斥但三张券可叠加"这种自相矛盾的需求表述,比后面改代码省了太多事。
3.3 算法与并发:高风险区,别赌概率
排序、去重、缓存淘汰策略、分页游标、批量任务的切片与补偿、锁的粒度、异步任务的幂等,这些是 AI 最容易给你"看起来对但实际错"的领域。原因很简单:这类问题的最优解依赖具体数据分布和资源约束,模型给出的通常是教科书版本,在它的训练数据里反复出现过,看起来很标准,放到你的场景里可能完全不适配。
我的处理原则是:算法和并发相关的代码,要么自己重写,要么用"白盒验证"的方式确认。白盒验证具体指三件事——自己手推一遍边界用例(空集合、单元素、超大数据量、重复键)、写一组能区分"正确"和"近似正确"的测试数据、以及在接近生产数据规模的环境里压一次。只跑功能测试是不够的,因为并发问题往往在功能上完全正确,只在压力下才现形。表格再列一下这四种成色的处理策略。
| 代码成色 | 典型形态 | 出错概率 | 处理策略 |
|---|---|---|---|
| 样板与胶水 | DTO、映射、日志、CRUD | 低 | 静态检查收口,按比例抽查 |
| 业务规则 | 状态流转、计价、权限 | 中 | 逐行读,先确认真值表 |
| 算法与并发 | 排序、锁、分片、幂等 | 高 | 手推边界、白盒测试、压测 |
| 配置与脚本 | CI、容器、SQL 迁移 | 极高 | 全部手审,禁止直接改生产 |
3.4 配置与脚本:最容易被忽视的隐形高危区
这一条我要单独强调,因为它坑过我最惨。模型写业务代码的水平这两年提升很明显,但写配置和运维脚本的水平提升得没那么快,而配置出错的影响面又特别大。我遇到过模型在 SQL 迁移脚本里把DELETE写成没有WHERE条件的版本,也遇到过它在容器构建文件里顺手把基础镜像换成了一个体积更小但缺少必要依赖的版本,还遇到过它在 CI 脚本里把测试和部署串成一条链,导致任何一次测试抖动都会触发一次线上发布。
现在的规矩很硬:涉及生产环境的配置、脚本、迁移,一律禁止直接由模型生成后提交。可以让它给方案,可以让它写草稿,但落到仓库里的每一行,必须由人重新敲一遍或者逐行确认。这不是不信模型,而是这类文件的验证成本太高、影响面太大,收益和风险完全不成比例。
4. 我现在的提示词和仓库级规则是怎么组织的
4.1 把项目约定写成模型能读的格式
很多人抱怨模型"不听劝",其实是因为约束只存在于你脑子里。我在项目根目录放了一个规则文件,把那些"新人第一天就该知道"的约定写进去,每次让模型参与开发前先让它读这份文件。格式上我最喜欢用 Markdown,因为模型对它的解析效果最好,而且人读起来也顺手。
# .ai/rules.md ## 技术栈约束 - 后端 Python 3.11 + FastAPI,不允许引入新的 Web 框架 - 数据库访问一律走 repository 层,禁止在 router 里直接写 SQL - 时间统一 UTC 存储,禁止使用本地时间函数 ## 代码风格 - 单个函数不超过 40 行,超过必须拆 - 所有外部调用必须带超时和重试上限 - 不允许新增未在依赖清单中声明的第三方库 ## 输出要求 - 先给改动清单(涉及文件 + 大致行数),我确认后再给完整实现 - 涉及数据库变更时,必须同时给出回滚脚本 - 不确定的地方显式问我,不要自己假设这份文件的价值不在于模型百分之百遵守,而在于它把"隐式约定"变成了"显式约束",出错之后你有据可依。我实测下来,加上这份文件之后,模型给出"引入了一个新依赖"或者"用了本地时间函数"这类低级错误的比例下降得非常明显。
4.2 任务粒度:一次只喂一件事
第二个关键点是拆分。我见过太多人一次性甩给模型一大段需求,然后抱怨输出质量差。正确的做法是把任务拆到"一次问答能完整覆盖"的粒度。判断标准很简单:如果你没法在脑子里把这个任务拆成三到五个可独立验证的步骤,那说明任务还没想清楚,这时候交给模型只会得到一个同样没想清楚的结果。
我的习惯是先让模型做方案拆解,不给代码。具体提示词大致长这样:先列出这个需求涉及的文件和函数,标出哪些是新增、哪些是修改;再指出其中你认为最可能出错的三个地方;最后说明你打算怎么验证。等这份方案我看过、改过,再让它逐块写实现。这个流程会多花十到二十分钟,但省下的返工时间远远不止这些。
4.3 三条我一直保留的提示词习惯
第一,要求它给出反例。写完一个校验函数,我会追问"什么输入会让这个函数出错"。模型的回答往往能帮我发现自己在需求描述里漏掉的边界。第二,要求它标注不确定项。我会明确说"不确定的地方用注释标出来,不要猜"。这一条特别有用,因为模型默认会编造得非常自信,而主动标注不确定时,它就变成了一个还算靠谱的合作者。第三,要求它解释为什么这样写。不是为了学习,而是为了在解释里找到逻辑漏洞——模型一旦开始解释,逻辑不自洽的地方往往就露出来了。
5. 把 AI 代码送进生产前,我会跑的六道检查
5.1 静态检查、依赖与许可证扫一遍
静态检查是第一道也是最便宜的一道闸门。AI 生成代码有个很稳定的特征:语法层面极少出错,但风格和类型层面的问题不少,比如空异常捕获、未使用的变量、类型标注和实现不符。这些全部交给工具,人不用花精力。同时一定要加许可证扫描,因为模型拼出来的代码片段来源不明,有可能带着某个开源许可的约束进来,而这在业务项目里是法律风险。
# 1. 静态检查:先让机器清掉语法和类型层面的问题 ruff check . --fix mypy --strict src/ # 2. 单元测试与覆盖率门槛,行和分支分开卡 pytest -q --cov=src --cov-branch --cov-fail-under=75 # 3. 依赖漏洞与许可证 pip-audit -r requirements.txt pip-licenses --format=markdown --with-urls > licenses.md # 4. 针对 AI 高频错误的项目自定义规则 semgrep --config ./rules/ai-review.yaml src/5.2 自定义规则:把踩过的坑写成检查
这个是真正省时间的地方。每次因为 AI 代码出问题,我就把那个模式写成一条规则,接进流水线里,下次同类错误进不来。下面这条是我最早写的,针对的就是空异常捕获,这个模式在模型输出里出现频率非常高。
rules: - id: no-bare-except patterns: - pattern: | try: ... except: ... message: 空 except 会吞掉异常,AI 生成代码里高频出现,请显式捕获并处理 severity: ERROR languages: [python]同类规则我现在积累了十几条,包括"外部调用必须有超时"、"禁止在生产路径打印完整请求体"、"数据库查询必须有分页或上限"、"禁止硬编码密钥"。这套东西的价值是复利式的,写一次,后面每一次 AI 生成都自动过一遍。
5.3 幻觉依赖:不存在的 API 和包名
模型编造 API 这件事从来没消失过,只是变得更隐蔽。早期它编的库一眼假,现在它会编出一个"看起来非常合理"的方法名,甚至给你完整的调用示例。我的检查办法有两个:一是依赖必须锁定版本且能装成功(CI 里跑一次干净安装),二是在评审时对所有"我在项目里没见过"的调用做一次搜索确认。曾经有个同事提交的代码里调用了某个库的一个"很方便"的方法,本地环境装的是几个月前的版本所以报错,他以为是环境问题,其实是方法根本不存在,只是名字起得太像真的。
5.4 密钥、日志与数据外泄面
这一块最需要人眼。模型在生成日志和调试输出时,默认倾向"打印得越全越好",很容易把手机号、身份证、完整请求体、甚至内部令牌写进日志。我在评审时会对每一处新增的日志和异常信息都过一遍,问自己一句:这行输出如果被完整保留三个月,会不会有合规问题。同样的道理适用于错误信息——对外返回的异常信息里不能带内部表名、文件路径和依赖版本。
我的六道检查习惯固定为:静态与类型检查、单测与分支覆盖率、依赖与许可证、自定义规则扫描、幻觉依赖确认、日志与密钥面检查。前四道全自动,后两道必须人做,且不省。
6. "呼吁暂停"背后,真正值得警惕的三件事
6.1 技能退化:你还读得懂自己签的字吗
有个问题很少被正面讨论:当代码八成由模型生成,评审人实际上是在给一份自己没写、也没完全理解的实现签字。短期看没问题,因为有测试兜底;长期看有风险,因为团队会逐渐失去对系统内部结构的直觉。我之前接手过一个项目,代码质量看起来不错,但没有一个人能完整说清某个核心模块的数据流向,因为那段代码经过三轮模型重构,每个版本都合理,合起来就没人有全局图景了。
我的对抗办法是"强制白盒日":每个季度挑一个核心模块,关掉 AI 辅助,用最原始的方式——加日志、断点、手画调用关系——把它从头到尾走一遍。这个过程很慢,但它保住的是团队的判断底盘。工具越强,人的判断力越值钱,也越容易退化,这两件事是同时发生的。
6.2 供应链与许可证:拼出来的代码从哪来
代码生成模型是在海量公开代码上训练出来的,输出里带有某些开源项目结构特征,这在工程上几乎不可避免。业务项目必须做的是三件事:把许可证扫描固化进 CI,不允许引入与项目许可不兼容的依赖;对生成代码中出现的、来源不明的完整算法实现保持警惕,必要时自己重写;在项目文档里记录 AI 参与度较高的模块,方便后续审计。这些动作看着繁琐,但真出问题时,能拿出来的只有这些东西。
6.3 同质化:所有人的解法都一样
还有一个更隐蔽的影响。模型的输出会自然收敛到训练数据里最主流的写法,这意味着一千个团队用同一个模型解决同一类问题,得到的架构和实现会高度相似。对个体来说这没什么,对做差异化产品的团队来说就是麻烦——你的技术护城河如果完全由模型生成,那它其实没有护城河。我现在会把"真正定义产品差异的那部分逻辑"单独标出来,要求必须由人主导设计,模型只能在明确约束下实现细节。
7. 把 AI 当同事而不是当外包:几个笨办法
第一个办法是先写接口和测试,再让模型填实现。这个顺序能解决大部分评审困境,因为契约已经由人定下来了,模型做的事情被限制在一个很小的空间里,出错面大幅收窄。我现在的日常是:接口签名、关键数据结构的字段、以及三到五个核心用例先自己写,然后让模型补实现,最后我补边界用例。这套流程下,AI 的产能优势被完整保留,风险却被框住了。
第二个办法是保留一块"手写区"。我在每个项目里都会挑一个模块设成手写区,通常是权限校验或者资金计算这类最核心的地方,规定任何 AI 输出的代码都不能直接进这个区,最多提供参考。这样做不只是为了安全,也是为了让团队一直有个参照物——知道"人的水平线"在哪,才能准确判断模型的输出是真好还是看着好。
第三个办法是记录 AI 参与度,定期复盘。我在提交信息里加了一个惯例标记,标注这次改动大致有多少是模型产出、多少是人写的,比例粗分三档。一个季度下来就能看出规律:哪类模块 AI 参与度高但问题也多,哪类模块 AI 参与度高且几乎不出事。这个数据比任何主观感受都可靠,我的规则文件和相关检查规则基本都是照着这份数据长出来的。
用到现在我个人的体会是,AI 生成八成代码这个状态本身不是问题,问题是有没有人对剩下那两成以及"验收"这件事负责。工具的责任是把产能拉满,人的责任是守住判断。这两件事一旦混在一起,进度表会很好看,但事故单也会很好看。我现在给自己定的红线是:任何一行进入生产环境的代码,我都能说清它为什么这么写、边界在哪、出问题怎么回滚。达不到这条,比例是多少都无所谓。