最近半年,我明显感觉到团队合并请求里AI生成的代码占比越来越高。前两天做代码走查,我盯着一个AI生成的异步处理模块看了整整二十分钟——代码能跑,测试能过,但就是说不清它为什么要这样设计。这种感觉很微妙,它不像人写的代码那样能看出决策脉络,更像是一个很会写答案、但完全不懂题目的人。我突然意识到一件事:AI代码最大的问题不是它写得差,而是它会在你毫无察觉的时候,把今天省下的时间,变成明天需要加倍偿还的技术债。
这不是危言耸听。我自己从Copilot、ChatGPT这类工具刚普及就一直在重度使用,帮团队写过不少AI代码规范,也踩过数不清的坑。今天这篇东西,就是想把这些经验完整地梳理一遍:AI代码为什么容易产生技术债、代码该怎么review、改代码要注意什么、怎么用AI扫描隐患、怎么让AI自动写测试用例,最后聊一个更根本的问题——AI时代,工程师的位置到底在哪里。
1. AI代码的技术债从哪里来
1.1 AI生成代码的本质是“概率拼图”
要理解AI代码为什么容易变成技术债,得先搞清楚它的生成机制。
传统开发是“设计驱动”:先有需求分析,再有方案设计,然后编码实现,最后测试验证。每一步都是人类基于对业务的理解做出的决策。AI生成代码则完全不同,它的本质是“概率拼图”——根据上文预测下一个最可能的token,逐字逐句把代码拼出来。它不是在“实现需求”,而是在“模仿人类写代码的模式”。
这一点解释了AI代码的两个显著特点。第一,它看起来非常“像样”,语法正确、结构完整、命名也算合理,这恰恰是它的危险之处——表面越正常,越容易让人放松警惕。第二,它没有真正的上下文记忆。你告诉它“做一个订单系统”,它会生成一个订单系统,但它并不知道你的业务规则是什么、你对性能的底线在哪里、你未来的扩展方向是什么。它只是在输出一份“看起来和订单系统相关”的代码。
我把这个理解总结成一个判断:AI代码本质上是“语法正确、语义存疑”的代码。它最大的风险不是写错,而是它经常“正确地完成了错误的事情”。
提示:不要用“代码能不能跑”来评判AI生成结果的好坏。“能跑”只是底线,“是否符合业务语义”才是关键。
1.2 四种典型的技术债形态
基于大量AI代码的走查经验,我把AI代码带来的技术债归纳成四种形态,每种都有非常清晰的识别特征。
结构债。AI特别喜欢生成超长函数和重复度极高的代码块。你让它写一个数据处理的逻辑,它能给你同一个if-else结构复制粘贴五遍,每遍只改一个变量名。原因很简单:AI是在“拼概率”,它在训练数据里见过大量重复代码,所以它觉得重复是正常写法。这种代码短期跑得通,但一旦业务逻辑要改,你得同时改五个地方,漏一处就是线上事故。
依赖债。AI训练数据包含大量开源项目和第三方库的历史版本,它经常会引用一些你已经不用的、或者已经停止维护的库,甚至有可能是过时的API。我踩过最典型的一个坑是,AI在生成一个文件上传功能时,引入了一个三年前流行的base64处理库,这个库有两个未修复的安全漏洞。代码运行没问题,安全扫描的时候直接亮了红灯。
语义债。这个问题最隐蔽。AI能正确理解你表层的指令,但经常理解不了隐含的业务约束。比如你让它写“获取用户订单”的函数,它可能直接按主键查数据库然后把所有字段返回,但你没有告诉它“这里的用户是当前登录用户,只能查自己的订单,不能越权”。这种代码在功能上是通的,在安全上是漏的,在业务上是有大坑的。
认知债。AI生成的代码往往命名混乱、逻辑绕,或者过度设计。比如一个简单的配置读取功能,它给你抽象出三个类加两个接口,美其名曰“可扩展性”。问题是人看代码是线性阅读的,过多的抽象层级会大幅提高团队的理解成本。等原开发同事离职后,接手的人需要花两三倍的时间去搞懂这堆代码到底在干什么。
2. AI代码的Review不能走老路
2.1 传统Review的信任模型已经不适用了
传统的代码评审,本质上建立在一个信任模型之上:代码作者是理解业务需求的,评审者是在“帮他把技术实现做得更好”,所以默认假设是“大方向没问题,重点看细节”。
AI代码打破了这套信任模型。AI不理解业务,它只是在概率层面生成了“看起来合理的代码”。所以Review AI代码时的默认假设应该反过来:默认AI写的代码是有根本性问题的,需要在语义层面进行完整验证,而不只是看看命名和边界条件。
我知道很多团队的Review习惯还停留在“哪里看不懂就问问作者”的状态,这套流程对AI代码完全不适用。因为AI代码有许多部分连提交代码的人都看不懂——他可能只看了开头和结尾,中间的逻辑来自AI的“自由发挥”。你问他“这里为什么这么写”,他只能回你“这是AI写的,我没细看”。
2.2 分级Review策略
面对AI代码,我推荐的是一套分级Review策略,简单说就是:越核心的代码,用越高级别的审查标准。
L1级别:格式与规范审查。这个级别可以用工具自动处理。格式化、命名规范、简单的静态检查,交给ESLint、Pylint、GolangCI-Lint这类工具就行。AI生成的代码在语法层面通常没什么大问题,这一级的通过率很高。
L2级别:逻辑与边界审查。这是人工Review的主战场。重点看三件事:一是分支逻辑是否覆盖了所有业务场景,尤其是异常路径和边界值;二是数据流是否安全,用户输入是否做了校验,敏感操作是否有权限控制;三是错误处理是否完整,AI经常漏掉失败场景,比如网络超时、数据库连接断开、消息重复消费。
L3级别:架构与业务语义审查。这个级别需要最资深的人来把关。它关注的是:这段代码放在这个位置是否合适?它的抽象边界是否合理?它是否和现有的架构风格一致?它在业务语义上是否符合真实需求?这三个问题,AI一个都答不了,必须靠人判断。
2.3 一个可以直接用的Review清单
下面是这半年我一直在用的一份清单,每一条都是真实踩坑后的总结。直接复印到你们团队的Review模板里,能省很多事。
| 检查维度 | 核心问题 | 典型AI翻车点 |
|---|---|---|
| 业务语义 | 这段代码真的在实现需求描述的功能吗? | 表面功能正确,实际业务逻辑曲线救国 |
| 数据安全 | 有没有未经验证的输入直接进入SQL、命令或渲染流程? | 忽略了用户输入的恶意构造可能 |
| 权限控制 | 这个操作是否校验了当前用户的身份与权限? | 默认所有调用者都是合法用户 |
| 异常处理 | 失败分支有没有兜底?资源有没有释放? | 只见成功路径,不见失败路径 |
| 重复代码 | 有没有复制粘贴式的重复逻辑?能不能抽象复用? | 同一逻辑复制五份,微改一个变量 |
| 依赖管理 | 引入的新依赖是否必要?版本是否安全?是否在维护? | 引入不可维护的冷门库 |
| 抽象层级 | 抽象是否适度?还是为了设计感而设计? | 一个简单功能套三层抽象 |
| 性能底线 | 有没有明显性能隐患?比如N+1查询、死循环、大对象常驻内存? | 循环里塞数据库查询 |
| 可测试性 | 这段代码容易写单测吗?依赖注入是否可行? | 核心逻辑和基础设施强耦合 |
| 与现有代码一致性 | 风格和模式是否和项目现有代码保持一致? | 同类代码用了完全不同的两种方案 |
3. 用AI改代码:最容易埋雷的操作
3.1 需求没说清,AI就动手
大量使用AI改代码的团队,最容易踩的第一个坑就是:需求描述得太模糊,AI直接按它自己的理解乱改。
有一次我的一个同事想优化一个文件处理流程的性能,他跟AI说“这个函数太慢了,帮我优化一下”。结果AI把这个同步函数改成了异步,加入了消息队列,还顺手改了上游调用方的返回逻辑。所有人都没注意到这个改动——直到线上出现了数据不一致的问题。
后来我总结了一个很朴素的道理:用AI改代码,最核心的不是AI的能力,而是你给它的约束。你要明确告诉它“不能改什么”,而不只是“要优化什么”。比如同样一个需求,正确的描述方式应该是:
“这个函数是文件解析核心路径,不能被其他模块改动。当前瓶颈是第12行到20行的循环做了重复的数据库查询,请把查询提取到循环外部,保持函数签名和返回结构不变,不允许添加新的依赖。”
一顿操作下来,AI不仅没有推倒重来,还老老实实只改了该改的地方。约束越清晰,AI的输出越可控。
3.2 给AI改代码定三条铁律
在用AI改代码这件事上,我自己摸索出三条铁律,基本上每次都不跑偏。
第一条,一次只改一件事。让AI同时完成“优化性能”和“重构代码结构”和“增加日志”三件事,结果往往是三件事都做了一半,而且是混在一起改的,Review的时候你根本看不出哪个改动对应哪个目的。一次只给AI一个目标,让它集中干一件清晰的事。
第二条,先有测试,再让AI动手。任何用AI做的修改,都必须先有对应的测试用例如防护网。我把代码提交给AI之前,一定是先写一个能捕获“目标缺陷”的测试用例,让它在现有代码上跑一遍是红的状态,然后让AI去实现让测试变绿。这个流程保证了AI的改动是朝着正确方向走的。
第三条,diff要最小化。我习惯让AI先告诉我它准备怎么改,然后明确要求“尽量用最少的改动完成需求”,改完之后我还会检查diff。如果AI的改动量远超预期,比如一个简单的性能优化动了十几个文件,那八成是它做了计划外的重构,果断回退重来。
3.3 别把AI的重构建议当圣旨
很多人用AI做Code Review的时候,喜欢把AI给的意见全部照单全收。这是个很危险的习惯。
AI提的“重构建议”,很多时候只是它基于概率判断的“风格偏好”,而不是真正的改进建议。比如我遇到过AI建议我把一个正常工作的for循环改成Stream流,理由是“更现代”。但这种改动没有解决任何实际问题,反而增加了阅读门槛和性能负担。
真正正确的做法是:把AI的建议当作候选方案,而不是行动命令。只有当你确认这个改动能够解决具体问题、且不会引入新风险的时候,才动手改。对于那种“可改可不改”的风格问题,最理性的回复就是我经常和AI说的那句话——“这个建议很好,下次不要提了”。
4. 让AI当体检医生:扫描既有代码的Bug与设计缺陷
4.1 AI扫描能做什么,不能做什么
用AI扫描代码里的Bug和设计问题,是我觉得目前AI编程场景里性价比最高、风险最低的应用方式。它不像让AI直接写代码那么不可控,其实更像是把代码交给一个特别细心的体检医生,帮你查体。
AI扫描真正擅长的领域有几块:静态缺陷(比如空指针、越界访问)、简单的安全问题(比如硬编码密钥、SQL注入风险)、代码风格问题、明显的重复代码、以及部分逻辑矛盾。这些问题的共同点是:它们有规律可循,且不依赖复杂的业务上下文。AI在训练数据里见过成千上万次同类Bug,识别起来准确率不低。
但AI扫描有一个非常明确的边界:它不懂业务。它能告诉你“这段代码可能越权”,但判断不了“这个接口是否真的不需要登录”;它能告诉你“这个函数复杂度太高”,但判断不了“这个复杂度是否是这个业务本身需要的”。所以AI扫描的报告只能当“疑点线索”,不能当“最终结论”。
注意:AI扫描报告里出现“代码复杂度高”“函数过长”这类话,基本都是空话。真正有价值的线索是“第X行存在空指针风险”“用户输入未经过滤直接进入SQL”这类具体的、可验证的描述。
4.2 把AI扫描结果变成有效行动
拿到AI扫描报告之后,我的处理习惯是三步走。
第一步,做危害分级。我把问题按“外部可触发性”分成三档:能从外部触发、可能导致数据泄露或服务不可用的,属于P0级,立刻处理;在特定输入下才出现的逻辑错误,属于P1级,安排到最近迭代修复;纯代码风格和可读性问题,属于P2级,攒到重构窗口统一处理。
第二步,交叉验证。每个AI报出来的问题,我都会用静态检查工具或者测试用例做二次确认。原因很简单:AI扫描的误报率不低。如果我用脚本或者测试可以复现AI描述的问题,那就确认是真Bug;如果复现不了,宁可信其无也不能花时间瞎折腾。
第三步,沉淀成回归用例。每个确认的Bug,我都会要求团队把它对应的测试用例补上,防止AI将来生成的新代码再犯同样的错误。这一步很关键——AI扫描本质上是一次性的,但回归测试能形成持续性保护。
4.3 一个实测场景:扫描结果怎么“过滤噪声”
我印象最深的一次扫描,是我让AI去检查一个处理订单导出的服务。AI报告了整整47条“问题”,我一条一条过完之后,真正有价值的是三条:一是导出的Excel文件名拼接用户输入,可能被构造为恶意路径;二是循环里对每个订单都查询了一次用户表,性能堪忧;三是订单金额的精度处理,用了float而不是decimal。
剩下的44条,大部分是“建议使用函数式编程风格”“这里可以用Optional替代if判断”“函数命名不够语义化”这类正确的废话。如果不懂筛选,团队很可能把大量时间浪费在这些改写成本高、收益为零的建议上,反而把真正要紧的那三条隐患淹没了。
5. 用AI自动写测试用例,其实是性价比最高的入口
5.1 先写测试再写代码,AI就不敢乱来了
谈到“AI自动写测试用例做自动测试”,我想先提出一个观点:让AI直接写生产代码是高风险操作,但让AI先写测试用例,是极高杠杆的安全操作。原因很简单,测试用例是对行为的约束,而约束正是控制AI的唯一手段。
我在实际项目里用得很顺的一种模式是“测试先行”:先把验收标准写成测试用例,再让AI在这些测试的约束下实现功能。比如做一个“用户注册”接口,我不会先让AI写注册逻辑,而是先给它一套测试用例:
- 正常注册时返回成功,数据库中新增记录
- 注册邮箱格式非法时返回参数错误
- 重复邮箱注册时返回“已存在”
- 密码长度低于8位时返回参数错误
- 注册成功后发送欢迎邮件(可用mock验证)
AI拿到这些用例之后再写代码,它的自由发挥空间就被压缩了。它不能跳过“重复邮箱”这个分支,因为测试告诉它必须存在;它也不能把邮箱格式校验放在前端而不在后端,因为后端测试过不去。
这就是测试用例的魔法:它们把AI从“概率生成器”变成了“约束求解器”。你用测试定义边界,AI就在边界内工作。
5.2 AI写测试用例的三个常见坑
但我必须诚实地说,AI写测试用例也不是万无一失的,它有三个很典型的坑。
第一个坑是“照着实现抄断言”。AI会本能地生成“跟着生产代码走的测试”——生产代码返回true,测试就断言true;生产代码抛异常,测试就断言抛出异常。这种测试什么都保护不了,它只是把实现逻辑复述了一遍。识别方法很简单:把生产代码挪走,看看这个测试还能不能正常写出来。如果测试的每一个断言都和生产代码一一对应,基本就是废的。
第二个坑是“断言太弱”。AI生成的测试经常出现大量“函数被调用成功”这种断言,而不是验证返回值、状态变化和副作用。比如测一个“扣减库存”的函数,AI可能会写“调用接口返回成功”,但它不会断言“库存数量从100变成了99”。弱的断言会给你一种“测试全过了”的虚假安全感。
第三个坑是“边界值缺失”。AI写测试用例趋向于“正常路径通畅”,对于空值、超长字符串、超大数字、并发请求、超时这类边界场景,AI生成的测试往往会遗漏。解决的办法是在让AI生成测试时,明确要求包含“happy path、边界值、异常输入、外部依赖失败”四类用例,并且人工Review时重点盯边界部分。
5.3 推荐流水线:从测试计划开始
现在我团队里让AI写测试的流水线是这样跑的,每一步都有明确产出。
第一步,把需求文档或用户故事喂给AI,让它生成一份“测试计划”,也就是列出所有需要覆盖的场景清单,分优先级。注意,这一步AI输出的是场景列表,不是代码。我在这一步只需要确认“它有没有漏场景”,不会浪费时间去写实现。
第二步,让AI根据测试计划生成测试代码。我会明确要求:不要mock掉生产代码的内部逻辑,只mock外部依赖;断言必须具体;至少包含一条边界值用例和一条异常路径用例。生成完以后,我会把测试代码跑一遍,确认是红的(即没有生产代码时测试必须失败)。
第三步,再让AI根据测试用例去实现生产代码。这样整个开发过程就变成了“按测试验收标准填空”,AI的每一次改动都处于测试的监控之下,技术债从源头就被压住了。
这套流程跑顺之后,我甚至觉得AI写测试用例和做自动测试,比AI写业务代码更值得推广。因为它是目前唯一既能让AI提效、又不牺牲代码质量的工程化手段。
6. AI时代,工程师的位置在哪里
6.1 从“写代码的人”变成“审代码的人”
有一个热搜词我一直很关注:AI抢走写代码工作,谁来培养工程师?这个问题我看得很简单:如果你的核心竞争力只是“能写出让机器运行的代码”,那AI确实会把你卷走,因为这一点它做得比你快得多。但如果你把自己的定位从“写代码的人”调整成“审代码的人”,你会发现你的价值不是被削弱了,反而是放大了。
代码写得好不好,AI已经能胜任;但代码该不该这样设计、这段逻辑和业务是否匹配、这个方案和系统长期演进方向是否一致——这些判断只有人能做。当AI把编码的执行成本压到接近零,真正稀缺的不再是“生产能力”,而是“判断能力”。
我特别认同一个说法:AI时代的工程师,更像“带着AI的架构师”。你要有足够的功底去判断AI给出的方案是优是劣,你要有足够的能力去纠偏,你要有足够的审美去守住代码的可维护底线。这些能力不会因为AI的出现而贬值,反而会更加值钱。
6.2 未来工程师最该练的三项核心能力
基于我对团队成员的观察,AI时代最值得练的核心能力聚焦在三个方面。
需求拆解能力。把模糊的业务需求拆成明确的、可验证的验收标准,这是AI编程时代最重要的上游能力。AI之所以经常生成跑偏的代码,很大程度是因为你给的“需求”太模糊、太粗略。谁能把需求拆得越细,谁就能让AI发挥出越大的价值。
方案评审能力。这个能力直接决定AI代码的质量下限。你要能在Review的几分钟内快速识别AI生成代码的风险点,判断哪些地方可以放心接收,哪些地方必须打回重写。光这一项能力,就能决定一个人是“被AI带飞”还是“被AI拖下水”。
质量兜底能力。当AI生成坏代码的时候,你要有本事兜住。这包括会写覆盖关键路径的测试用例、会用工具扫描隐患、能在故障发生后迅速定位和修复。AI可以把代码的正确率从60%提升到90%,但剩下的10%只有人才能兜住。
7. 实操总结:按这条路线走,AI代码是资产而不是债
写到这里,我基本把AI代码从产生到Review再到维护的完整链路都过了一遍。最后把整套思路浓缩成一条可执行的流水线,算是给团队立规矩用的一份内部“作业指导书”。
第一步,写清楚需求约束。每次让AI动手之前,先明确“要实现什么、不能改什么、边界在哪里”。这段准备时间最多占整个任务的20%,但它决定了剩下80%的成败。
第二步,先写测试再写代码。至少把验收标准列出来,最好能让AI先把测试用例写出来。测试不是可选项,它是防止AI跑偏的唯一防线。
第三步,让AI按测试约束实现代码。这一步可以放心大胆地用AI,因为它的自由发挥空间已经被测试用例牢牢锁住了。
第四步,分级做Review。L1格式交给工具,L2逻辑边界靠主力开发,L3架构和业务语义让最懂系统的人把关。务必用我前面那张Review清单做逐项检查。
第五步,用AI扫描补漏。把合入前的代码交给AI做一轮全量体检,重点关注安全、性能和异常分支,人工确认后把真Bug沉淀成回归用例。
第六步,定期还债。技术债不是不产生,而是不能失控。每个月留出固定时间,把AI代码产生的重复、坏味道、老旧依赖做一轮彻底清理。债只要不断被清,就不会滚成压垮系统的雪球。
按这条路线走下来,AI代码就不再是“明天的技术债”,而是真正的生产力资产。最后再说一句我的切身体会:工具本身没有好坏,管理方式却有好坏。AI时代不是拼谁写的代码多,而是拼谁能让代码保持健康、可维护、可演进。把写代码这件事交给AI,把自己留给判断、评审和设计,这就是我们这代工程师最大的机会所在。