AI 编程 Agent 可能为标准库或仓库现有工具已能解决的小任务,引入新的运行时依赖。文章提醒,即使构建和测试通过,额外依赖仍会增加供应链、安装与长期维护负担。
Agent 很快就完成了任务,PR 看起来也很干净。检查全绿,diff 整洁,构建通过。然后你打开 package.json,发现里面多了一个你从没要求添加的运行时依赖,只为了完成三行代码的工作,而磁盘上已有的东西本来就能搞定。
这是 AI 辅助编程中最不容易察觉的失败之一。它不会让构建失败,也不会导致测试报错。它只是悄悄地扩大你的软件供应链、延长安装时间、增加攻击面,一次引入一个看似方便的小包。
以快速给出正确答案为目标的 Agent,会选择它知道的最短路径。如果它从训练中记住了某个广受欢迎的库,可以用来解析日期、扁平化嵌套对象,或者重试 HTTP 请求,它就会先用这个包,而不是先检查你的仓库里是否已经有对应的 util。对于感受不到安装成本的模型来说,添加依赖似乎没什么代价。
它也不了解你的依赖预算。它不知道安全团队会标记每一个新增的运行时依赖,不知道你的安装已经需要四分钟,而且每增加一项依赖都会更慢,也不知道它刚刚解决的那个三行代码的任务,如今变成了一份软件,团队在接下来的三年里都得持续为它打补丁。
第一种,重新引入标准库已经提供的能力。日期格式化、深度相等比较、debounce、retry wrapper。语言本身已经提供了够用的实现,或者你的仓库里已经有三个几乎一样的辅助函数,但第四个小包还是出现了。
第二种,其他工具已经解决了同一个问题。你已经依赖一个校验库、一个支持重试策略的 HTTP client,或者已经有一套日志方案。Agent 却又添加了第二个工具来做同样的事,因为它孤立地解决了这个子任务,没有看看代码库目前是怎么做的。
第三种,差一点才合适的抽象。这个包确实解决了问题,但代价是额外引入了传递依赖、原生构建步骤,或者某种行为,而这种行为在处理边界情况时,与你的代码库已有的做法存在细微差异。
我不再尝试逐个列出禁止使用的包,而是在任务里加了一条常驻指令:添加依赖之前,先搜索仓库中是否已有辅助函数,并确认标准库无法完成这项工作;如果仍然需要新依赖,就在 PR 描述中写出它的名字,并说明原因。
这一句话就能拦住大部分问题。Agent 要么找到已有的 util,要么使用 stdlib,要么明确提出添加依赖的建议,让人有机会说不。那些仍然漏过去的情况,会被我真正认真看的 diff 抓出来:审查 Agent 的 PR 时,我最先打开的是 package.json 和 package-lock.json,然后才看逻辑。
对于以 spec 为契约的工作,也是同样的思路。我们最终选择在 Powerduck 中保留本地 API 和契约检查,而没有再接入一个托管服务,因为作为事实依据的文件本来就在磁盘上。对依赖也应该保持同样的倾向:先用已有的,再考虑增加新的。
这次改动真的需要增加什么新东西吗?如果答案是一个依赖、一个配置项,或者一个代码库昨天还没用过的服务,那就不该把它当作无关紧要的内容一扫而过。它最有可能比功能本身存在得更久。
如果需要进一步采取行动,你可以考虑屏蔽此人和/或举报滥用行为。