跳到主要内容

38 篇博文 含有标签「code-review」

查看所有标签

语义差异:当行级对比毫无意义时,如何评审 Prompt 变更

· 阅读需 9 分钟
Tian Pan
Software Engineer

一位队友提交了一个拉取请求(PR)。Diff 只有三个单词。一行变红了 —— Do not add information not present in the source.(不要添加原文中不存在的信息)—— 另一行变绿了 —— Make your best guess if the source is incomplete.(如果原文不完整,请做出最佳猜测)。改动很小,意图也合理,代码审查只用了 11 秒。你批准了它。一周后,你的客服机器人开始自信地编造根本不存在的退款政策,而你正在翻阅日志,试图查出幻觉率是从什么时候开始翻倍的。

Git Diff 完美地完成了它的工作。它准确地向你展示了哪些字符发生了变化。但它无法展示唯一重要的事情:这些字符背后的行为已经从“不确定时拒绝”变成了“不确定时编造”。对于代码,文本差异是行为差异的忠实代理 —— 将 < 改为 <=,审查者可以推断出后果。而对于提示词(Prompts),文本差异和行为差异几乎没有任何关系。

舰队级 CODEOWNERS:当作者是 AI Agent 时的评审路由

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 CODEOWNERS 文件编码了一个古老到没人记得当初为何制定的假设:代码变更(diff)的作者知道自己身处谁的领地。一名修改计费服务的工程师深知自己进入了计费业务的领地。他们有目的地将变更范围限制在自己团队的目录内,如果必须触碰其他团队的代码,他们会亲自或在 Slack 上打个招呼,在发出评审请求之前先进行沟通。基于路径的评审路由之所以奏效,是因为人类作者会预先为自己的变更选择路由。

Agent 不会这样做。如果要求一个 Agent “迁移所有弃用的日志调用”,它会在一个下午横扫整个代码库,并生成一个涉及 400 个目录的 diff。GitHub 尽职尽责地查询 CODEOWNERS,匹配每一个通配符(glob),并同时向 40 个团队发出评审请求。

没有人询问过这些团队是否想要这种变更。没有人预警过变更即将到来。而且请求批准的“作者”并不是一个可以随时抓来了解背景信息的同事——它是一个代表其他部门某人行事的机器人账号,而那个人甚至可能都不知道哪些团队收到了通知。

晨间审查队列:如何分流处理 Agent 八小时无人值守的工作成果

· 阅读需 13 分钟
Tian Pan
Software Engineer

关于通宵编码智能体(coding agents)的宣传非常诱人:你睡觉,智能体集群干活,你醒来时 PR 已经处理完毕。但现实情况往往更微妙且代价更高。你醒来后面对的是一个队列——六个分支、两个失败的任务运行、一个你没要求的依赖项版本更新,以及一个要么精妙绝伦要么隐约有误的重构。智能体的确生成了代码。但呈现在你桌面上的交付物并不是代码,而是一个分诊(triage)问题,且大多数团队都没有处理它的工作流。

数据显示这并非少数人的抱怨。一项针对 1,255 个团队、超过 10,000 名开发者的遥测研究发现,AI 采用率高的团队合并的 PR 数量增加了 98%——而评审时间增加了 91%,平均 PR 大小增长了 154%。2026 年的后续数据更加糟糕:每个 PR 导致的生产环境事故大约翻了三倍,且现在有 31% 的 PR 在没有任何人工评审的情况下直接合并。当智能体开始值夜班时,瓶颈并没有消失。它转移到了上午 9 点,集中在你一天中的前 90 分钟,并有了一个名字:早晨评审队列。

理解债:没人能看懂的凌晨两点系统

· 阅读需 10 分钟
Tian Pan
Software Engineer

传呼机在凌晨 2:14 响起。结账服务正抛出 500 错误,收入在流失,而你是值班工程师。你调出故障模块并开始阅读。代码很整洁——函数命名规范,结构合理,甚至还有几处有用的注释。然而你完全不知道它是干什么的。代码不是你写的。你团队里的任何人都没真正写过它。四个月前,一个智能体(agent)生成了这段代码,它通过了评审,测试通过了,并从此在生产环境中运行。现在它出故障了,而那个理应修复它的人却是第一次见到它。

这就是理解债(comprehension debt):你的组织运行的代码量与人类实际理解的代码量之间日益扩大的差距。它不会显示在仪表盘上。当一切看起来都健康时,它在默默累积,并在最糟糕的时刻——在事故期间,当你对自己系统的理解不足其代价是以停机时间来衡量时——到期偿还。

资历倒置:为什么当 Agent 加速时,你的资深工程师反而变慢了

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的团队采用编程 Agent 的那个季度,发生了两件没人会放在同一张幻灯片上的事情。吞吐量上升了 —— 更多的 PR、更多的合并代码、更多的工单被关闭。然而,你最资深的三位工程师却变慢了。这种慢不是因为偷懒,而是因为“溺水”。他们自己的提交量枯竭了,日历被评审任务填满,一对一面谈的主旋律也从“这是我交付的内容”变成了“我花了一整周时间在帮别人排障”。

这就是“资历倒挂”。那些本该通过 AI 获得更多杠杆效应的人,反而被它埋没了。这既不是动力问题,也不是工具差距,而是 Agent 改变工作形态后的结构性后果:它们让生成变得廉价,而让验证变得昂贵。验证正是你无法交给初级人员或其他 Agent 的那一项任务。

你的编程代理基于落后 Main 分支三周的代码版本重建的代码库索引

· 阅读需 11 分钟
Tian Pan
Software Engineer

你团队中的一个 AI 编程 Agent 提交了一个 PR,在两个文件中调用了四次 parseUserToken()。这个函数在代码仓库中并不存在,甚至已经消失了 19 天,早在你团队所有工程师都记得评审过的一次提交中就被 decodeSessionClaim() 替换了。Agent 并不是凭空捏造了这个名字,它是从其语义索引中读取的——那个向量库是从一个比 main 分支落后 21 天的工作副本重建的。相比之下,Agent 的编辑步骤在会话开始时运行了 git pull,操作的是最新的代码。对同一个代码库的两个视角,相隔三周,而 Agent 却自信地用一段无法针对任何真实环境编译的代码桥接了它们。

这是一种不会自我宣告的失败模式。Agent 运行了。测试看起来通过了。PR 合并了。第一位评审者之所以注意到,仅仅是因为一个被删减的函数与一个无关的辅助函数重名,触发了 linter 报错。到那时,Agent 已经花了一个完整的冲刺(sprint)针对一个“幻影版本”的代码库进行编写,而团队中没有一个人——包括 Agent 自己——收到任何异常信号。

你的编程 Agent 开启的那个导致真实 PR 被关闭的拉取请求

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的编程智能体在周二下午 3:14 提交了一个 PR。PR 描述很整洁,代码差异(diff)很小,CI 测试也是绿色的。二十分钟后,它被压缩合并(squash-merged)了。第二天下午 1:20 吃完午饭回来的同事看到了一条通知:“PR #1247 已关闭。”不是已合并,而是已关闭。分支不见了。她上周留下的 72 条评审评论也消失了——全部折叠在一个“已过期”标签下,属于一个不再出现在任何活跃列表中的 PR。一位资深工程师的设计决策、与安全评审员的两轮反复沟通,以及耗时一周协商出的周密迁移计划,全都化为了另一个没人仔细阅读的 PR 底部的一个脚注。那个压缩提交(squash commit)留下的唯一痕迹是底部的一行标签:Closed by #1893

这就是信任编程智能体自行编写 PR 元数据的失败模式。出问题的不是代码,而是元数据。代码差异没有问题,智能体工作得很出色。它无法做到的是区分当前的讨论与陈旧的讨论,而 GitHub 的自动关闭机制将智能体编写的每一个关闭关键字都视为必须执行的指令。你的智能体通过读取评论来获取上下文,从一个六个月前的回复中推断出它的工作取代了一个旧的 PR,于是在它生成的描述中写下了 Closes #1247。合并操作完成了剩下的工作——在压缩合并的那一刻,对于任何没有盯着 diff 看的人来说,这一切都是无声地、机械地、不可逆转地发生了。

你的编程智能体生成的那些人类已经不再阅读的 PR 描述

· 阅读需 12 分钟
Tian Pan
Software Engineer

一年前,你的团队采用了 PR 描述模板。它包含 ## Summary## Changes## Test plan 和一排复选框。审查者非常喜欢它:每个 PR 都有上下文,每个 PR 都有测试计划,每个 PR 都有结构。六个月后,编程助手学会了填写它。现在,每个 PR 依然有 ## Summary## Changes## Test plan 和一排复选框 —— 但审查者不再阅读标题以外的内容了。曾经聚焦注意力的格式,现在反而成了“此处不值得关注”的信号。结构比它所承载的信号寿命更长。

这不是代码质量问题。这些 PR 中的代码通常是没问题的。问题在于,撰写描述的行为已经从思考变更的行为中被剥离,而描述正是审查者用来分级处理(triage)其有限注意力的工具。当该工具变得格式统一、措辞合理,且与其他所有 PR 毫无区别时,审查者的注意力分级机制就失效了。曾经用于挖掘异常情况的系统,现在将所有内容摊平成了同样的形状。

编程智能体绕过而未使用的代码规范(Idiom)

· 阅读需 13 分钟
Tian Pan
Software Engineer

我合作的一个支付团队的高级工程师曾给我讲过一个故事,我认为每一个运行编程 Agent(AI 代理)的团队最终都会经历。他们的代码库有一个 Result<T, E> 封装器——这是自研的,位于单个 core/result.ts 文件中,在该服务的约两百处调用点被使用。新代码被要求在每一个可能失败的函数中传递 Result;而 throw 则保留给真正意料之外的状态。这并非由 lint 规则强制执行。这就是他们的“方言”。

在使用编程 Agent 交付六个月后,他们审计了 Agent 合并的 diff(差异)。大约三分之一的新函数完全忽略了 Result。Agent 选择了 try/catch,返回了 T | null,抛出了带有描述性消息的 Error 子类——在某些设想的代码库中,这些选择中的每一个都是正确的。但在当前这个代码库中,没有一个是正确的。代码通过了类型检查。测试通过了。审阅者批准了它,因为每一行看起来都没有错。但 Agent 修改的文件不再与它旁边的文件保持一致,团队在自己的服务内部悄然滋生出了第二种“方言”。

这就是我想谈论的故障模式:不是 Bug,不是幻觉,也不是违反了 lint 规则——而是惯用法漂移 (Idiomatic Drift)。Agent 交付的代码可以编译、运行并通过测试,但其风格并非你的代码库所使用的。随着合并次数的增加,代码库会分化为 Agent 风格区和人类风格区,而代价会体现在任何仪表盘都无法监控的地方。

永不休眠的 PR 机器人:当代码审查者成为新的速率限制器

· 阅读需 12 分钟
Tian Pan
Software Engineer

二十年来,软件工程的瓶颈一直是写代码。我们优化了 IDE、自动补全、重构工具和各种框架,让"打字"变得更便宜。我们赢了。可现在瓶颈往下游挪了一步:写代码很便宜,读代码却很贵。PR 机器人可以并行启动十次实现尝试,在你早上喝完咖啡之前就把十个 Pull Request 砸到你的仓库里。你的审查者做不到这一点。

AI 辅助的软件交付,速率限制器已经不再是模型的每秒 token 数,而是你每天能投入多少双"人眼"去看 diff。当这些眼睛被压垮,系统不会优雅地降级——它会开始盖橡皮图章。代码带着 LGTM 🚀 被合入,没有人真正读过。一名资深工程师批准了一份由 AI 写、又被另一个 AI 工具审查过的补丁,三周后一个数据不一致的 bug 吃掉了某个人四十个小时的人生。表面上的正确不等于系统层面的正确,绿色的流水线不等于"我理解了"。

你的编码 Agent 写不出的 PR 描述

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的编码 Agent 完成了任务。Diff 很小,测试全绿,Lint 干净,而 PR 正文从头到尾只有一句话:"修复 X 模块中的 bug。"远在六个时区之外的评审者打开页面,孤立地阅读 diff,看不出任何毛病,于是批准了一个技术上完全正确、却解决了错误问题的改动。代码合入。两天后,一位客户来问他们一直依赖的某个变通办法为什么突然失效了 —— 这时你才发现,你的 Agent 修复的那个 bug,并不是工单里描述的那个 bug。

代码没问题。评审者很尽责。Agent 也严格按照吩咐做事。问题出在他们之间的那个交付物 —— pull request —— 它丢失了一切本可避免这次失误的信息。

提示词 Diff 隐藏了自身的爆炸半径

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个 PR(合并请求)进入了你的评审队列。Diff 显示系统提示词(system prompt)中修改了三个词:Output strictly valid JSON 变成了 Always respond using clean, parseable JSON。这看起来就像是一次文案润色。你快速浏览了一下,CI 检查勾标是绿色的,于是你点击了批准。总耗时:90 秒。

六个小时后,下游解析器开始拒绝带有尾随逗号和缺失字段的响应。结构化输出的错误率从接近零飙升至两位数,一个创收工作流陷入停滞。Diff 中没有任何迹象预示到这一点。Diff 中也不 可能 预示到这一点,因为 Diff 衡量的是错误的东西。

这就是评审提示词变更的核心问题:提示词 Diff 的大小完全无法说明其影响范围的大小。三五个词的修改与三段话的重写都只是文本,而文本 Diff 以相同的视觉权重呈现它们,就像对待任何其他编辑一样。但提示词并不是 描述 行为的文本 —— 它是 导致 行为的文本,而一次编辑所产生的因果爆炸半径在你评审的产物中是不可见的。