让AI修一个筛选器的小bug,回来的diff里却多了六个文件的重命名,还顺手改了一个偏好设置模块的默认值。筛选器的修复在任务范围内,重命名算沾边,偏好设置的改动则完全无关——如果不做这层分组,那个偏好设置的改动就会借着筛选器修复的"好评"一起被合并进去。

打开网易新闻 查看精彩图片

这就是AI生成改动里最常见的评审失败之一:范围蔓延(scope creep)。它指的是AI的编辑超出了被要求完成的任务边界,把代码改到了没人打算碰的地方。接受这些额外改动的原因往往很简单——它们夹在一个整体看起来不错的diff里。

为什么生成的改动总是很宽

AI工具并不总能给请求画出一条干净的边界。一个函数里的修复,可能让模型顺手重命名一个辅助函数、重构相邻模块,或者调整一个共享类型,因为这些改动"看起来相关",于是被打包进同一个diff,哪怕任务只要求改一个行为。

风险不在于额外改动一定是错的,而在于它们绕过了本该获得的评审。一个专注于确认目标修复的评审者,很可能在没有同等注意力的情况下,就批准了周边的重构。

要盯住的几类改动

把改动文件按范围分成三类,是检查边界的第一步:

  • 范围内:任务明确要求的修复。
  • 沾边:与修复相邻的改动,比如辅助函数重命名,需要单独做一次机械性核对。
  • 无关:完全不在任务里的文件,不应因为模型改了它就被捎带合并。

其中风险最高的是共享类型编辑——它会触达从未属于该任务的操作;而完全无关的文件,更不该搭便车。

怎么检查边界

  1. 用一句话复述任务,让预期范围变得明确。
  2. 列出所有改动文件,逐个标记为范围内、沾边或无关。
  3. 先确认目标修复,把核心任务敲定。
  4. 对每个沾边改动追问:它是修复所必需的,还是只是顺手方便?
  5. 接受共享编辑前,顺着共享符号追到所有调用方。
  6. 把无关改动拆出来单独评审,而不是打包合并。

工具层面能帮上忙的地方在于:评审界面会展示项目结构和改动文件,解析器能解析定义、调用方和导入关系,让共享编辑可以被追踪;结构过滤器还会标出被大量引用的文件——那正是宽泛改动触达代码最多的地方。Powerduck把文件列表和解析后的引用放在同一处,评审者可以标记每个改动的范围,并在接受整包之前先跟进共享代码。

哪些额外工作值得留下

不是每个额外改动都该拆出去。当一项附带编辑是修复生效所必需的——比如一个新行为要编译通过就必须改的共享类型,删掉它任务就会坏——它值得留在同一次评审里,哪怕它超出了字面请求。判断标准是依赖关系:如果目标修复离开它就运转不了,它就属于这次评审。留下它,但依然要像对待任何共享编辑那样追踪调用方,并记录它为何必需。

该拆的是那些只是方便或完全无关的改动,而不是修复在结构上依赖的改动。画出这条线,diff才能保持聚焦,又不至于强行拆分把构建搞坏。

诚实的局限

静态结构可能漏掉动态加载和框架入口点,所以一个看起来无关的文件仍可能在运行时被触达;没有入站引用,也不代表一个文件可以安全删除。AI可能提出错误的怀疑,也可能漏掉缺陷,而且它从不运行应用。

当共享编辑改变了API行为,要用真实请求去确认,而不是只依赖调用方列表;在可运行的检查背书之前,AI给出的判断只能当作假设。

常见问题

每个额外改动都是缺陷吗?不是。有些是有用的重构,但每一个都应获得自己的评审,而不是靠"关联"被批准。为什么要拆分无关改动?为了让它们按自身价值被评审,并且可以在不回滚目标修复的情况下被回滚。怎么找到共享代码?解析改动的符号并追踪其调用方,对被大量引用的文件看得最仔细。

一个聚焦的任务照样可能产出宽泛的diff,评审必须画出模型没画的那条边界:确认目标修复,分离附带重构,把共享编辑追到每个调用方,把无关改动拆出去而不是打包合并。额外编辑不是自动错的,但它们绝不该因为夹在一个好的修复里就自我批准。任何行为变化,都用真实请求验证。