ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI生成代码正在重构代码审查,团队如何守住质量底线

AI生成代码正在重构代码审查,团队如何守住质量底线 过去一年里你在 code review 时应该已经明显感觉到一种撕裂感AI 生成的代码越来越流畅函数命名整齐、注释齐全、结构分层合理但评审者却越来越不敢点 Approve。团队里开始有人抱怨“既然 AI 都写出来了为什么还要人审”也有人因为连续在 AI 的漂亮代码里挑出隐蔽 bug而变得对每一行都过度警觉。代码审查这件事并没有因为 AI 的加入而变得轻松反而出现了一种反直觉的现象代码越好看了越不敢合并了。“AI Broke Code Review and It‘s Breaking Your Team”这个标题在英文技术社区引起了很多人的共鸣。它想表达的不是“AI 工具不稳定”或“模型写不出好代码”而是一个更结构性的问题代码评审原本建立在一套默认假设上——人的错误是零散的、个体化的、可以通过经验和清单来发现。但 AI 生成代码的错误不是这样的它们是统计学上自然的、流畅的、甚至看起来很有道理的臆测。当错误从“偶然手滑”变成“分布在代码库每个角落的合理幻觉”团队的评审机制如果还在原地踏步那就不是在 review而是在给 AI 的错误批量盖章。这篇文章要讲清楚三件事第一AI 究竟改变了代码审查的哪个底层环节第二为什么 AI 生成的“合理错误”那么难被发现第三团队应该如何调整评审流程、提示词方式和验收标准把质量底线重新握回人手里。文章后半部分会给出可复制到团队里的提示词模板、diff 检查命令以及一套双轨评审流程。1. 代码审查没有被 AI 加速而是被 AI 改写成了另一种风险先做一个最直接的对比。在 AI 辅助编码普及之前团队里坏代码通常来自哪里理解偏差、手误、状态漏处理、命名混乱以及极少数刻意绕过的逻辑。这些错误有个共同特征写错的人通常知道“这里有风险”只是没注意到。评审者要做的是在这些自然稀疏的错误中用经验和清单把遗漏找出来。AI 时代完全变了。以常见的大语言模型辅助编程为例模型输出的每一行都经过概率平滑处理它会主动避开那些“看起来别扭”的写法而是生成它在训练数据里见过大量次的常规写法。这带来一个隐蔽后果AI 代码的错误不再是“某个局部写错了”而是“整体都非常合理但某一个假设错了”。比如它默认某个字段永远非空默认某个接口不会抛异常默认时间戳来自同一时区默认用户输入不会超过某长度。这些假设不会被 lint 发现也不会让 happy path 上的单元测试失败。团队感受上的变化更明显。以前一个 PR 的缺陷密度是可预估的评审者能按节奏慢慢看。现在 AI 辅助提交的 PR 体积更大、速度更快、风格几乎无瑕疵想要从中挑出“看起来没错但语义上错误”的逻辑需要的信息量远超 diff 本身。很多团队因此把合入速度提上来了但发现问题的概率反而降下去了。这就是我认为“AI 不是修复了 code review而是把 code review 从一个查错工作变成了一个判断模型假设工作”的原因。如果只看这些描述可能会觉得是危言耸听。但这些现象背后有认知科学和机器学习研究的影子。下面两节把这个问题拆开讲。2. 代码评审到底在审什么代码评审表面上是在审 diff实际上是在审两层内容。第一层是“表达层”命名是否清晰、结构是否合理、有没有重复代码、是否遵守团队规范。第二层是“契约层”这段代码是不是真的实现了需求要求的行为边界条件是否覆盖出现异常时会不会导致数据不一致线上会不会因为调用方的不同用法而翻车。过去表达层占了评审者大量精力。因为人写代码会出现命名随意、结构凌乱、风格不一的情况所以 Git 提交里充满了手动调整的痕迹。而现在的 AI 生成代码把表达层绝大多数问题都抹平了。命名工整、函数够短、类型看起来也对注释还能对上。理论上评审者可以腾出精力专注于契约层这本来是好事。但问题在于表达层变好之后人类评审者会产生“代码质量高出错概率低”的直觉反而降低了在契约层上的投入。这就是心理学里的“认知流畅性效应”——看起来流畅的内容更容易被我们采信。用一个表格概括这三个阶段的差异阶段错误主要来源评审者要做的事常见失败方式纯人工编写手误、理解偏差、风格不一对照需求和常识逐行检查漏看、没时间静态工具 人工逻辑错误、边界遗漏工具过滤常规问题后聚焦逻辑过度依赖工具忽略语义AI 生成代码模型凭空产生的合理假设识别“看起来很对”但缺少依据的假设认知流畅性导致过度信任这也就解释了为什么标题会说 “breaking your team”评审者不是不努力而是在处理一类新型问题——模型假设。这类问题无法靠 Git diff 页面里的红绿行看到必须把需求文档、调用链、异常路径、甚至模型在训练数据里的常见偏见一起带进来才有机会发现。3. 为什么 AI 生成的“合理错误”这么多深入看机器学习原理大语言模型做的事情本质是“预测下一个词”。它在训练时被优化的是文本连贯性不是业务正确性。所以它更倾向于输出“更常见、更像人话、和前文更一致”的代码而不是“更符合某个私有仓库业务规则”的代码。你给它上游函数、类名、变量名它会调用训练数据里相似模式的记忆来补全。如果仓库里的业务逻辑和公开代码非常相似补全结果通常可用一旦你的业务有独特约束比如“金额必须四舍五入而不是截断”“告警时间按客户时区计算”“缓存过期时间必须是业务参数而不是固定值”模型就很容易凭常规经验补一个假设上去。这里要重点理解一个概念分布偏置。训练语料里开源项目、技术博客和教学代码占了大头。这些代码面向的通常是教育场景或演示场景对生产级约束的考虑并不充分。教学代码里datetime.now()满天飞但生产系统要考虑时区、夏令时、时间戳精度教学代码里split(, 1)很常见但生产配置要处理注释、转义和编码。AI 学到的不是“你的代码库事实”而是“整个互联网代码的平均形态”。因此那些“在平均值里对、在你的场景里错”的代码会以非常高的比例出现。另一个因素与训练目标有关。生成式模型天然带有不确定性但代码评审场景里的风险不在于那句明显错误的 API 调用而在于那些“旁边没有明显报错信号”的代码。假如你让 AI 写一个配置文件解析函数训练数据中的常见版本几乎都长这样跳过空行、忽略注释、按切分。它不会基于你的配置里有# 注释就主动规避它只会生成一个“看起来没问题但读到注释行就崩”的版本。如果你在 review 时只跑一条正常配置的冒烟用例这个 bug 会悄悄进入主分支。最后还有一层认知因素。人类对流畅感的敏感度极高看到函数里注释完整、命名规范、结构清晰大脑会自动把它归类为“可信内容”分配给它的注意力随之下降。这不是哪个人不认真而是大脑在做资源分配时的自然倾向。要对抗这一点只能把流程设计成“默认不信任”而不是“默认信任然后找茬”。4. 三张最典型的“AI 假完美”现场理论说完看几个具体场景。这三个场景是真实团队里反复出现过的模式代码经过简化。4.1 场景一边界条件看着都处理了细看缺少关键判断需求很简单在证件到期前 30 天内系统需要给用户发送续期提醒已经过期的也要提醒。from datetime import datetime def should_remind(expiry_date): if not expiry_date: return False remaining_days (expiry_date - datetime.now()).days return remaining_days 30这段代码第一眼很自然有非空判断有剩余天数计算有阈值比较。但它隐藏了好几个假设expiry_date和datetime.now()是不是同一时区一个没有时区信息的datetime直接相减会不会抛异常remaining_days是精确到秒的概念还是日期概念更微妙的是它把“剩余不足 30 天”和“已过期很多年”合在同一个分支里。如果需求希望“过期超过一年只提示一次”这段代码就不符合要求。这类边界问题在人工代码里也会出现但 AI 版本的特点是函数命名和注释都很好足以让评审者看完后产生“挺完善”的感觉。如果评审者不把需求拆成“已过期”“剩余 0-30 天”“剩余大于 30 天”三条路径逐一对照很容易漏掉。4.2 场景二重构优雅但行为契约悄悄变了团队有一段历史代码功能是发送通知。原代码有一个重要约定用户没填邮箱时什么都不做。public void sendNotification(User user) { if (user.getEmail() null || user.getEmail().isBlank()) { return; } emailService.send(user.getEmail()); }AI 参与重构后可能被改成public void sendNotification(User user) { try { emailService.send(user.getEmail()); } catch (IllegalArgumentException e) { log.warn(send email failed: {}, e.getMessage()); } }新代码看起来更健壮了加了异常捕获不会因为空邮箱直接抛错。但行为契约变了旧代码是“空邮箱直接不发”新代码是“空邮箱会走到emailService.send(null)由底层抛出异常后吞掉”。如果emailService.send在传入null时不抛IllegalArgumentException而是把空对象写入数据库或放进 MQ 消息队列那么错误会在这个函数之外更远的地方爆发。这种重构在 AI 辅助开发里非常常见因为模型从训练数据中学到“try-catch 是好的健壮性实践”但它不理解这里的业务约定“空邮箱必须直接返回”。评审者如果只看到新代码里的 catch 和 log很容易直接给出 LGTM。4.3 场景三模型凭记忆调用 API忽略版本差异第三种场景更偏“幻觉”。当模型需要调用某个不常见的 SDK 时它会倾向于生成一个“最像真的”调用方式而不是查询当前项目的实际依赖版本。例如def request_refund(order_id, reason): client RefundClient() result client.create( order_idorder_id, reasonreason, auto_approveTrue, ) return result.refund_id问题可能出现在任何一层RefundClient不存在、create方法签名不一致、auto_approve参数名错误或者在新版本中已被移除。更隐蔽的是即使类名和参数名都对模型也可能遗漏调用方要求的幂等参数request_no。这类代码若在编译期暴露还好真正危险的是在测试环境被 mock 掩盖直到订单系统在线上出现重复回调才发现。这说明一个原则AI 写的 API 调用代码必须一律当作“可能基于过时或错误的 API 记忆”来处理。评审时的第一步不是看逻辑而是去核对当前项目里的依赖版本、类定义和接口文档。5. 把提示词从“帮我写代码”改成“帮我理清假设”既然问题核心是模型在替你做假设应对方式就是把假设权收回来。最有效的改变发生在提示词层不要求 AI 直接写完整实现而是要求它先列出实现需要满足的约束、边界和不确定项再由你确认。一个适合评审和开发场景的提示词模板如下你是一名严谨的 Python 工程师。请根据下面的需求描述先输出“需求理解”和“实现假设”再输出代码。 需求描述 1. 根据证件到期日判断是否需要在未来 30 天内提醒用户续期。 2. 已经过期的证件也需要提醒。 3. 提醒事件每天只能触发一次。 输出格式 - 需求拆解列出每条需求的实现要点。 - 实现假设列出你认为正确、但需求里没有明确写的假设尤其是字段时区、空值、并发、幂等。 - 代码只输出核心函数。 - 注意没有把握的假设用“需要人工确认”标注。这样的提示会逼着模型把“时区怎么处理”“每天一次怎么实现”等问题摆到台面上。即使模型仍然会列出一堆假设评审者也有了可以直接对照核验的需求清单。经验是当模型主动列出假设后评审者发现问题的速度会显著快于直接读代码。除了提示词还可以把团队约定写进项目根目录的AGENTS.md或者代码评审模板。例如## 评审时需要确认的 AI 假设清单 - 这个函数是否隐含了“输入非空”的假设 - 是否隐含了“时间都是 UTC”的假设 - 是否隐含了“并发只会有一个调用方”的假设 - 对第三方 API 的调用是否核对了当前依赖版本的实际签名 - 是否在异常处理中把不该吞掉的错误吞掉了清单不复杂但能显著提高评审者对模型假设的敏感度。6. 新评审流程双轨审查把代码评审流程拆成两条轨道是现阶段比较务实的做法。轨道 A 是机器快速检查。凡是规则能判定的东西不要让 AI 或人逐行去看。比如运行git diff --check检查空白错误和 conflict markers跑一遍 lint跑单测跑依赖漏洞扫描再执行秘密信息扫描。这些规则可以写进 CI保证每个 PR 合入前自动执行。git diff main...feature --check git diff main...feature --stat git diff main...feature -U20 | head -300-U20的目的是让审查时看到更多上下文。默认的三行上下文往往不够还原模型被打断的语义尤其是在大模型生成的长函数里上下文比行数重要得多。轨道 B 是人工语义检查。这一步不接受“代码能编译”“单测过了”作为通过标准而是要求评审者回答三个问题这段代码是否兑现了需求里的每一条行为它有哪些新增假设这些假设是否经过确认如果这个函数在线上异常退出日志、数据和资金流会发生什么建议采用“小步阅读”的方式一个 PR 只审 200 行以内的实质性语义变更。超出部分要求提交者拆分因为多模型生成的 800 行代码里人类能维持有效注意力的区间非常有限。在实践中轨道 A 的自动化率越高评审者越能保留体力给轨道 B。AI 在这里可以做一件事把 diff 里“只改命名”“只改格式”的低风险代码筛选出来让评审者重点看“逻辑实质变化”的部分。但要注意AI 只能做摘要和风险标注不能做最终裁判。7. 完整示例用双轨审查流程处理一个 AI 生成的 PR假设团队收到一个 AI 辅助生成的 PR功能是计算订单实付金额。需求如下满 100 减 20新人首单再打 9 折两种优惠不叠加。实付金额保留两位小数四舍五入。AI 生成的代码可能是这样的def calculate_payable(amount, is_new_user, has_coupon): if amount 100 and has_coupon: amount - 20 if is_new_user: amount * 0.9 return round(amount, 2)第一轮轨道 A 检查格式没问题Lint 能过。单元测试如果只写了“输入 150、新人、有券 → 输出 117.0”这种 happy path也能通过。但轨道 B 的语义检查会立刻发现几个问题“满 100 减 20”的触发条件里代码写成了has_coupon而需求并没有说必须有优惠券。这里多了一个假设。“折扣不叠加”没有体现。当前代码会先减 20再打 9 折相当于先减后折属于叠加计算。如果需求是“两种优惠只能选一种”这里就是确定的逻辑错误。新人首单的“首单”没有判断。传入is_new_userTrue就会打 9 折但用户可能是老用户复购。金额单位没有说明。如果接口传入的是“分”round(amount, 2)会把单位差异掩盖掉。评审者可以在 PR 里这样写评论1. 需要确认满减是否要求必须有优惠券需求写的是“满 100 减 20”没有提到券。 2. 逻辑问题需求和“折扣不叠加”矛盾当前代码是减 20 后再打 9 折。 3. 首单未校验is_new_user 不等于 first_order需要增加订单表查询。 4. 金额单位请确认传入参数是元还是分否则 round 会掩盖单位错误。这个例子说明真正需要人的地方不是“跑得通”而是“需求契约与代码实现之间的一致性”。AI 很擅长生成一个“看起来完整真实”的实现但验收标准从来不是代码像样而是行为符合业务。8. 团队层面的制度调整前面几节是评审者个人能做的调整。但问题如果已经发展到“breaking your team”就需要在制度层面补规则。第一是明确 AI 生成代码的申报规则。团队可以不用禁止 AI但可以在 PR 描述里声明哪些片段由 AI 生成哪些经过人工仔细审阅。申报不是为了追责而是帮评审者分配注意力AI 生成的代码默认按“高风险”处理人工写的常规业务代码按正常节奏审。第二是设置单个 PR 的语义变更上限。当 PR 里实质性逻辑变更超过 300 行时应要求拆分。原因不是行数本身而是评审者的有效注意力有限。AI 让单人单日产出上千行代码变得很容易但评审者能承受的语义审查总量并没有同比例增长。如果只追求生成速度、不控制合并速度团队会在“表面上很成功”的情况下积累大量未被真正审查的代码。第三是重新定义“测试通过”的含义。对 AI 辅助生成的功能建议把测试从“覆盖主要路径”升级为“契约测试 反例测试”。比如金额计算除了验证金额减少还要验证“不满足门槛时不减”“优惠不叠加时的分支”和“金额为负数时如何处理”。反例用例不需要很多但对捕捉模型假设极有帮助。第四是生产环境变更必须走完整安全流程。任何涉及线上行为的变更都要能在测试环境验证要有开关、监控和回滚方案。这笔原则并非 AI 时代才有只是在 AI 生成代码更多、逻辑审查压力更大的时候更加重要。CI 里可以增加一个强制检查要求 PR 描述包含“影响范围”和“回滚方案”两栏否则不允许合入。9. 常见问题与排查思路问题现象可能原因排查方式解决方案代码风格统一但功能经常出错AI 按训练集平均风格生成忽略业务私有约束检查是否缺少需求拆解和假设确认环节用“需求拆解 假设清单”提示词重新生成或修复单测全过上线后出现边界类故障测试只覆盖 happy path行为契约未被验证查看测试用例是否包含反例、空值、并发场景增加契约测试和反例测试用例评审者表示“没什么可看的”AI 代码过于流畅触发认知流畅性效应在 PR 模板里强制要求填写需求要点和假设清单把“审假设”作为评审必读项大 PR 合并后问题集中爆发单 PR 语义变更过大评审注意力不够统计逻辑变更行数与缺陷密度的关系拆分 PR控制单次语义变更规模第三方 API 调用在真实环境抛异常模型使用了过时或错误的 API 签名核对依赖版本、接口文档和调用参数在评审入口增加“API 签名需核对”清单10. 建议放在手边的行动清单如果只想带走几条可落地的建议我建议是这四条。第一下次拿到 AI 生成的代码不要先看“写得对不对”而是先问“它做了哪些需求里没有的假设”。把假设逐条列出来和需求文档对照你会发现比直接读代码更快地发现问题。第二把代码评审从“逐行通读”改成“小步阅读 契约核对”。一次最多审 200 行实质性逻辑变更超出就要求拆分 PR。CI 里配置好git diff --check、Lint、单测和秘密扫描机器能做的先做完人工只保留真正需要语义判断的部分。第三写提示词时主动索要“需求拆解”和“实现假设”。让模型先把不确定的事项暴露出来再让它在确认后的规则上写代码。这个习惯的价值往往比挑选哪个模型更大。第四团队立一条简单规矩AI 生成代码默认高风险合入前必须有人明确验证行为契约。这条“默认不信任”的规则不会拖慢团队反而会让 AI 带来的速度真正转化为可持续效率。代码评审的本质从来不是“看别人有没有犯错”而是“我们如何以团队为单位对一段将在生产环境长期运行的行为达成共识”。AI 把写代码的成本降低了却没有降低判断代码的难度。越早接受这个现实越早把评审流程改成“验证模型假设”的模式团队就越能在 AI 编程的浪潮里站稳。
返回列表