@[toc]
Multi-Pass Review 还不够:为什么还需要专项 Profile 和 Fresh Diff Scope
开篇:多轮审核解决的是“不稳定”,但还没有完全解决“看不全”
在大模型代码审核场景中,Multi-Pass Review 是一个很重要的进步。
它不再把代码审核交给一次临场回答,而是通过多轮独立审查、结构化输出和聚合规则,把大模型的审核行为变得更稳定、更可复查、更接近工程流程。
但是,只做到 Multi-Pass 仍然不够。
因为 Multi-Pass 主要解决的是“单次判断不稳定”的问题,而代码审核中还有另外两个更隐蔽的问题:
- 领域覆盖不完整:模型虽然看了多轮,但每轮可能都在用相同的通用视角看问题,导致领域特定风险长期漏检。
- 审查范围不新鲜:复审时如果沿用旧 packet、旧结论或旧问题清单,模型可能只验证历史问题,而没有重新审查当前 diff 的完整状态。
这两个问题如果不解决,Multi-Pass Review 很容易变成“重复三次通用审核”。它比单轮回答稳定,但还不是一个足够强的工程化代码审核 Skill。
因此,在 Multi-Pass Review 的基础上,还需要两个关键机制:
- 专项 profile 提高领域覆盖率;
- 每次审核都重新生成当前 diff 的审查范围。
前者解决“从什么角度看”的问题。
后者解决“到底在看哪份代码”的问题。
一个成熟的代码审核 Skill,必须同时处理这两件事。
一、Multi-Pass Review 的价值和边界
Multi-Pass Review 的核心思想,是不要完全相信一次模型判断。
这背后的逻辑很直观:大模型输出具有概率性,同一个问题在不同轮次中可能被发现,也可能被遗漏。通过多轮独立审查,可以降低单次上下文偏差、文件顺序偏差和注意力分配偏差。
如果一个问题在多个独立轮次中都被发现,它通常比只出现一次的问题更值得关注。这个思想与大模型推理中的 self-consistency 很接近:不要只采用一次推理路径,而是从多个候选路径中观察哪些结论更稳定。
在代码审核里,这种机制能带来几个直接收益:
- 避免一次回答漏掉关键问题;
- 降低单轮幻觉直接进入最终报告的概率;
- 把审核输出变成结构化中间产物;
- 通过共识聚合区分高优先级问题和低置信建议;
- 让最终报告更适合开发者处理。
但是,Multi-Pass 本身并不自动等于全面。
如果三轮审核都使用同一种通用提示词、同一种风险偏好、同一种问题分类方式,那么它们很可能只是在重复同一类判断。
这种情况下,Multi-Pass 解决了“模型这一次有没有看漏”的一部分问题,但没有解决“模型知不知道应该看什么”的问题。
代码审核不是只有通用正确性。
不同代码领域有不同风险模型:
- 并发代码关注锁顺序、共享状态、重入、竞态、可见性;
- 安全敏感代码关注输入边界、权限检查、注入风险、敏感信息泄露;
- 数据处理代码关注 schema 兼容、迁移路径、空值语义、重复消费;
- API 变更关注契约、调用方、版本兼容、错误码语义;
- 性能敏感代码关注复杂度、分配频率、缓存、阻塞路径;
- 构建和发布代码关注环境变量、路径、产物、失败行为和可复现性;
- 文档和注释变更关注描述是否与真实行为一致,是否会误导使用者。
如果所有 pass 都只问“有没有 bug”,模型可能会集中在显眼的逻辑错误上,而忽略这些领域特定问题。
所以,Multi-Pass 是底座,不是终点。
在这个底座上,必须进一步引入专项 profile。
二、为什么需要专项 Profile
专项 profile 的本质,是给每一轮审核一个明确的“审查视角”。
它不是让模型扮演角色,也不是让报告变得花哨,而是把不同类型风险显式化。
一个通用审核 prompt 往往会写:
1 | 检查逻辑错误、错误处理、并发、安全、性能、兼容性问题。 |
这看起来覆盖很多,但问题是:覆盖太宽就等于没有重点。
模型看到这样的要求时,通常会优先处理最容易在 diff 中直接定位的问题,例如:
- 返回值未检查;
- 空指针;
- 条件判断反了;
- 少释放资源;
- 明显越界;
- 测试缺失。
这些当然重要,但它们不是全部。
很多严重问题并不是“通用 bug 形态”,而是领域规则被破坏。
1. 通用审核容易漏掉领域规则
所谓领域规则,是指某一类代码在特定上下文下必须遵守的约束。
例如:
- 一个配置加载函数必须保持向后兼容;
- 一个序列化字段不能随意改语义;
- 一个公共 API 的错误码不能突然改变;
- 一个任务调度逻辑不能引入新的阻塞点;
- 一个缓存失效策略必须与数据更新路径一致;
- 一个安全检查必须发生在资源访问之前;
- 一个迁移脚本必须支持重复执行;
- 一个构建脚本失败时不能静默跳过产物检查。
这些问题未必能通过“有没有 bug”发现。它们需要模型带着某种专门的问题意识去看。
如果没有专项 profile,模型往往会把注意力放在语言层面的代码质量,而不是领域层面的约束破坏。
2. 领域风险通常不是单点错误,而是关系错误
通用 bug 经常发生在单点:某一行、某个判断、某个返回值。
领域问题则常常发生在关系里:
- 改动 A 要求改动 B,但 B 没有出现;
- 删除 C 后,D 的假设不再成立;
- 新增 E 后,F 的边界条件变了;
- 文档说 G,但实现做的是 H;
- 测试只覆盖成功路径,没有覆盖失败路径;
- 配置默认值变了,但迁移逻辑没有处理旧数据。
这类问题需要模型主动寻找“配套修改是否完整”。
专项 profile 的作用,就是让模型在某一轮中专门追问:
这类改动通常还需要哪些配套条件?当前 diff 是否满足?
这比泛泛地问“有没有问题”更有效。
3. 专项 profile 可以降低注意力竞争
一个 diff 里可能同时存在很多信号:业务逻辑、错误处理、测试、配置、文档、构建脚本。
如果一轮审核试图同时看所有风险,模型的注意力会被分散。它可能看到一个显眼问题后,就围绕这个问题展开,而没有深入其他维度。
专项 profile 可以把注意力分配显式化。
例如:
1 | pass 1: 通用正确性 |
或者:
1 | pass 1: 逻辑和错误路径 |
这样,每轮都有明确优先级。
不是每轮都看全部,而是每轮重点看一类风险,同时保留基本正确性检查。
这比“每轮都泛泛检查所有问题”更容易发现深层风险。
三、专项 Profile 不是强行套模板
专项 profile 的风险是过拟合。
如果设计得太死,它会把无关 checklist 强行套到所有 diff 上,制造大量噪声。
例如,一个纯文档改动不应该被强行检查并发问题;一个配置改动不应该被强行要求分析算法复杂度;一个测试改动不应该被过度推断为生产代码 bug。
所以,专项 profile 必须遵守一个原则:
Profile 是额外视角,不是强制结论。
它应该帮助模型发现特定风险,而不是要求模型一定找出特定问题。
1. Profile 应该描述“关注点”,而不是预设“问题”
不好的 profile 会这样写:
1 | 本轮要找出并发 bug。 |
这会诱导模型为了完成任务而硬找并发问题。
更好的写法是:
1 | 本轮重点检查是否存在共享状态、重入、锁顺序、异步回调、取消路径等并发相关风险。只有在 diff 中存在明确证据时才报告问题。 |
差异很大。
前者要求模型找问题。
后者要求模型在证据存在时报告问题。
2. Profile 应该允许“不适用”
一个成熟的 profile 必须允许模型判断当前 diff 与该视角无关。
例如:
1 | 如果当前 packet 不涉及该 profile 的风险面,则只执行通用正确性检查,不要制造无根据问题。 |
这条规则非常重要。
因为 profile 的目标是提高覆盖率,不是提高问题数量。
3. Profile 应该小而清晰
不要把 profile 写成一篇教材。
一个 profile 最好只包含:
- 适用场景;
- 重点风险;
- 常见遗漏;
- 不应误报的边界;
- 输出要求。
它不应该包含大量背景知识。背景知识越多,模型越容易把文章内容当成当前 diff 的事实。
Profile 应该像审查清单,不应该像百科词条。
四、专项 Profile 如何提高领域覆盖率
领域覆盖率不是指“报告里出现更多领域名词”。
它指的是:某类真实风险被审查流程发现的概率提高。
专项 profile 之所以能提高覆盖率,是因为它改变了模型的搜索策略。
1. 从“找明显 bug”变成“找约束破坏”
通用审核往往关注代码本身是否有明显缺陷。
专项审核则会追问:这段代码所在领域有哪些约束?当前改动有没有破坏这些约束?
例如,一个 API 改动,通用审核可能只看实现是否正确。
契约 profile 会额外关注:
- 返回值语义是否变化;
- 调用方是否同步;
- 错误码是否兼容;
- 是否需要版本迁移;
- 是否影响已有测试;
- 是否需要文档同步。
同一个 diff,不同 profile 看到的问题不同。
这就是覆盖率提升的来源。
2. 从“单文件判断”变成“跨文件关系判断”
很多领域问题藏在跨文件关系里。
例如:
- 实现改了,接口声明没改;
- 配置项改了,默认配置没改;
- schema 改了,迁移脚本没改;
- 错误码改了,测试断言没改;
- 行为改了,文档仍描述旧行为。
专项 profile 会提醒模型主动检查这些关系。
它不是替代静态分析,而是补足静态分析不容易覆盖的语义关系。
3. 从“代码可运行”变成“改动可交付”
代码审核不只是判断代码能不能运行。
一个改动要能交付,通常还要满足:
- 调用方同步;
- 测试同步;
- 配置同步;
- 文档同步;
- 构建同步;
- 兼容策略同步;
- 回滚策略同步。
专项 profile 可以让不同轮次分别检查这些交付维度。
这样,review 不再只是“代码有没有 bug”,而是“这个变更是否完整”。
五、为什么每次审核都要重新生成当前 diff 的审查范围
除了专项 profile,另一个关键机制是 fresh diff scope。
也就是:每次审核都必须基于当前 diff 重新生成审查材料。
这句话看起来像流程细节,但实际上非常重要。
它解决的是复审中的范围污染问题。
1. 复审最容易退化成“只看旧问题”
当第一次审核发现几个问题后,开发者会修改代码,然后要求模型“再审一下”。
这时如果没有明确流程,模型很容易做成:
- 检查上次的问题是否修复;
- 针对旧位置再看几眼;
- 输出“已修复”或“还有一个问题”;
- 忽略当前 diff 中其他变化。
这不是完整复审。
因为修复本身可能引入新问题。
例如:
- 为了修复资源泄漏,引入了重复释放;
- 为了处理错误返回,改变了原有成功路径;
- 为了补测试,修改了测试 fixture,影响其他用例;
- 为了修复兼容性,新增了 fallback,但没有处理边界输入;
- 为了处理空值,吞掉了本应暴露的错误。
如果复审只看旧问题,就会漏掉这些新问题。
2. 当前 diff 才是唯一可信审查输入
代码审核的对象永远应该是当前变更状态,而不是历史回答。
历史问题有价值,但它只是回归检查清单。
它不能替代当前 diff。
如果 Skill 沿用旧 packet 或旧中间产物,会出现几个风险:
- 审查材料与实际代码不一致;
- 已删除的问题仍被报告;
- 新增代码没有进入审查范围;
- 修复后的上下文没有被重新分析;
- 聚合结果混入旧轮次发现;
- 报告看起来完整,实际基于过期输入。
这类问题比普通幻觉更危险,因为它属于流程幻觉:报告形式正确,但审查对象错了。
3. Fresh scope 是可复现性的基础
一个工程化审核流程,必须能回答:
这份报告到底基于哪一份输入生成?
如果不能回答,就无法复查。
每次审核重新生成当前 diff 的审查范围,可以让报告和输入绑定。
例如,中间产物里应该能看到:
- 当前 diff 文件;
- 文件数量;
- 每个文件的新增/删除统计;
- 每轮 packet 内容;
- 每轮 agent 输出;
- 聚合结果;
- 最终报告。
这样,当报告指出一个问题时,开发者可以追溯它来自哪一份 diff、哪一个 packet、哪一个轮次。
没有 fresh scope,复审就会变成对话记忆驱动。
而对话记忆不是可靠的审查输入。
六、Fresh Diff Scope 和大模型幻觉有什么关系
很多人谈大模型幻觉时,只关注模型编造事实。
但在代码审核流程里,还有一种更隐蔽的幻觉:范围幻觉。
范围幻觉指的是:模型以为自己正在审查当前代码,但实际上它的判断受旧上下文、旧 diff、旧问题清单或旧中间产物影响。
表现包括:
- 报告一个已经被删除的问题;
- 忽略新提交的修复代码;
- 把旧问题描述套到新代码上;
- 认为某个路径仍然存在,但当前 diff 已经改掉;
- 复用上一轮结论,而不是重新分析当前输入。
这种幻觉不是模型知识错误,而是流程状态错误。
Fresh diff scope 的目的,就是消除这种状态错误。
每次审核从当前 diff 重新生成 packet,相当于告诉模型:
不要相信旧材料。当前输入才是审查对象。
这对复审尤其重要。
1. 历史发现只能作为回归清单
历史发现不是无用的。
它可以帮助复审确认:
- 上次的问题是否修复;
- 修复是否完整;
- 修复是否引入新风险;
- 同类问题是否还存在其他位置。
但历史发现不能限制审查范围。
正确关系应该是:
1 | 当前 diff = 审查范围 |
而不是:
1 | 历史发现 = 审查范围 |
这是一个很关键的工程边界。
2. 复审不应该只回答“旧问题修好了没”
开发者常说“帮我复审一下”,实际需要的通常不是只看旧问题。
更合理的复审包含三层:
- 旧问题是否已经关闭;
- 修复是否引入新问题;
- 当前完整 diff 是否还有其他风险。
只有第一层是不够的。
Fresh diff scope 强制复审回到当前代码状态,从而避免复审退化成 checklist 勾选。
七、专项 Profile 和 Fresh Scope 是互补关系
专项 profile 和 fresh diff scope 解决的是两个不同维度的问题。
| 机制 | 解决的问题 | 如果缺失会怎样 |
|---|---|---|
| 专项 profile | 从哪些角度看 | 多轮审核仍可能停留在通用视角,领域风险漏检 |
| Fresh diff scope | 看哪份材料 | 复审可能基于旧输入、旧结论或局部修复点 |
一个没有专项 profile 的 Multi-Pass Review,可能稳定但不深入。
一个没有 fresh scope 的 Multi-Pass Review,可能报告完整但对象错误。
两者同时存在,才能让审核流程既有覆盖率,又有输入准确性。
1. Profile 保证“视角新”
每一轮可以用不同视角观察同一个 diff。
这样可以降低单一审查偏好造成的盲区。
2. Fresh scope 保证“材料新”
每次审核都重新读取当前 diff。
这样可以降低历史上下文造成的范围污染。
3. 聚合保证“结论稳”
多轮输出经过聚合,区分共识问题、单轮高置信问题和可选建议。
这样可以降低单轮幻觉造成的行动成本。
三者合起来,才构成完整闭环:
1 | Fresh Scope 保证输入正确 |
八、如何设计专项 Profile
要让专项 profile 真正有用,需要控制粒度。
Profile 太粗,和通用审核没有区别。
Profile 太细,会导致维护成本高、误报多、触发困难。
比较合理的粒度,是围绕风险类型设计,而不是围绕具体项目设计。
1. 按风险类型划分
通用 Skill 可以内置这些 profile 类型:
1 | generic-correctness |
这些名字不绑定具体项目,适用于多数代码库。
每个 profile 只关注一类风险。
2. 每个 profile 包含适用场景
例如:
1 | api-compatibility: |
1 | refactoring-safety: |
1 | test-coverage: |
适用场景可以帮助模型判断当前 profile 是否相关。
3. 每个 profile 包含重点问题
例如 api-compatibility 可以检查:
- 调用方是否同步;
- 返回值语义是否变化;
- 错误码是否兼容;
- 默认值是否变化;
- 旧数据或旧配置是否仍可处理;
- 文档和测试是否同步。
refactoring-safety 可以检查:
- 行为是否真的不变;
- 分支顺序是否变化;
- 副作用是否移动;
- 错误处理路径是否保持;
- 日志、指标、权限检查是否被遗漏;
- 测试是否覆盖重构前后关键路径。
security 可以检查:
- 输入是否校验;
- 权限检查是否在资源访问前;
- 错误信息是否泄露敏感内容;
- 是否引入注入风险;
- 是否绕过原有安全边界;
- 默认配置是否变得更宽松。
4. 每个 profile 包含误报边界
这是 profile 设计中最容易被忽略的部分。
例如:
1 | 不要仅因为出现用户输入字段就报告安全问题;必须指出缺少哪类校验或边界检查。 |
1 | 不要仅因为没有新增测试就报告 MEDIUM;只有当 diff 改变行为、错误路径或兼容语义时,才将测试缺失升级。 |
1 | 不要仅因为函数变长就报告性能问题;必须指出复杂度、阻塞、重复分配或热路径证据。 |
误报边界能显著提高 profile 的实用性。
九、如何设计 Fresh Diff Scope 流程
Fresh diff scope 听起来简单,但实现时必须写成硬规则。
1. 每次审核都是新审核
Skill 应明确规定:
1 | 除非用户明确要求只检查某个指定问题,否则每次审核都视为新的完整审核。 |
这条规则可以防止模型默认走捷径。
用户说“复审”“重新看一下”“修完了再看”“再次审核”,都应该触发新的完整审查范围。
2. 每次重新生成中间产物
中间产物不应复用。
至少应重新生成:
1 | review.patch |
并重新输出:
1 | agent_*.json |
这样可以保证所有结果都绑定当前输入。
3. 旧结果只能作为回归线索
Skill 应明确:
1 | 上一轮发现的问题只能作为 regression checklist。 |
也就是说,复审时先确认旧问题是否关闭,但仍必须扫描当前 diff 的所有文件和 hunk。
4. 报告中声明审查范围
最终报告应该写清楚:
- 本次是 full-scope 还是 targeted review;
- 当前 packet 数量;
- 当前文件数量;
- 是否包含历史问题回归检查;
- 是否仅基于静态 diff 审查。
这可以防止读者误解报告含义。
十、为什么这两个机制对团队很重要
如果只是个人临时使用,大模型审核结果不稳定,最多是多问几次。
但如果要把它作为团队工作流的一部分,就必须控制一致性。
团队场景中,一个审核 Skill 的问题会被放大:
- 不同人输入方式不同;
- 不同 PR 规模不同;
- 不同模块风险不同;
- 同一个 PR 会多次修改和复审;
- 审核报告可能被用于决策;
- 误报会消耗团队时间;
- 漏报会影响交付质量。
在这种情况下,专项 profile 和 fresh scope 的意义就很明显。
1. Profile 让团队沉淀审查经验
团队中很多审核经验不是通用编程知识,而是长期踩坑形成的风险清单。
例如:
- 哪类改动必须补兼容测试;
- 哪类接口不能改变错误码;
- 哪类配置必须支持旧版本;
- 哪类重构最容易改变行为;
- 哪类发布脚本最容易静默失败。
这些经验如果只存在于资深工程师脑子里,很难稳定传递。
Profile 可以把这些经验沉淀成可复用审核视角。
2. Fresh scope 让复审结果可信
团队协作中,PR 经常经历多轮修改。
如果每次复审都不重新建立当前 diff 范围,报告很容易和真实代码脱节。
Fresh scope 能保证每次报告都回答同一个问题:
当前这份变更,还有什么风险?
而不是:
上次指出的问题,现在看起来怎么样?
这两者差别很大。
3. 两者结合让审核可持续改进
当 profile 和 fresh scope 都稳定之后,团队可以进一步优化:
- 哪些 profile 误报最多;
- 哪些风险类型最常出现;
- 哪些问题常常单轮发现但没有共识;
- 哪些复审阶段最容易引入新问题;
- 哪些模块需要更强的专项审查。
这会让 Skill 从工具变成流程资产。
十一、实现时需要注意的边界
引入专项 profile 和 fresh scope 后,Skill 会变强,但也更容易复杂化。
需要注意几个边界。
1. 不要让 profile 数量失控
Profile 不是越多越好。
太多 profile 会带来:
- 选择困难;
- 上下文膨胀;
- 输出噪声;
- 维护成本增加;
- 聚合逻辑复杂。
初期可以只保留少数高价值 profile。
例如:
1 | generic-correctness |
后续根据实际漏报再增加。
2. 不要让 fresh scope 变成重复劳动
Fresh scope 是为了保证输入正确,不是为了浪费时间。
它可以通过脚本自动完成,不应依赖人工手动整理。
否则用户会因为流程麻烦而绕过它。
3. 不要把 profile 输出直接等同于最终问题
专项 profile 发现的问题也需要经过置信度和证据检查。
尤其是单轮专项发现,应该与共识问题分区展示。
建议报告分为:
1 | 共识问题 |
这样既保留专项发现,又不会把所有单轮输出都升级成必须修复。
4. 不要让历史发现污染当前判断
历史发现可以作为输入,但必须明确标记。
例如:
1 | 以下是上一轮问题,仅作为回归检查清单。 |
这能减少模型把旧问题当成当前事实的概率。
十二、一套更完整的 Review Skill 应该长什么样
综合来看,一个更完整的 Multi-Pass Review Skill,可以被设计成下面这样的流程:
1 | 1. 接收当前 diff 或生成当前 diff |
这个流程的关键不是复杂,而是每一步都有明确责任。
- diff 生成负责输入新鲜;
- packet 负责范围固定;
- profile 负责视角分配;
- agent 输出负责语义判断;
- 聚合脚本负责降噪;
- 报告负责可读和可执行。
这样,大模型就不再是自由发挥的评论者,而是流程中的一个受控审查单元。
十三、一个简单示例:没有 Profile 和 Fresh Scope 会发生什么
假设一个变更第一次审核发现了两个问题:
- 错误路径没有返回错误码;
- 测试没有覆盖失败分支。
开发者修复后,再次请求复审。
如果没有 fresh scope,模型可能只检查这两个点,然后说:
1 | 两个问题均已修复。 |
但实际修复可能新增了第三个问题:为了返回错误码,开发者改变了成功路径上的状态更新顺序。
如果复审只看旧问题,这个新问题会漏掉。
如果没有专项 profile,模型可能只做通用正确性检查,而没有意识到这个状态更新顺序属于“行为兼容性”风险。
更好的流程是:
- 重新生成当前 diff;
- 把旧问题作为回归清单;
- 使用通用正确性 profile 检查基础 bug;
- 使用兼容性 profile 检查行为是否变化;
- 使用测试 profile 检查是否覆盖修复后的失败路径;
- 聚合结果并输出报告。
这样才是一次完整复审。
十四、这类设计背后的工程原则
专项 profile 和 fresh scope 看似是两个实现细节,其实对应两条更底层的工程原则。
原则一:审核视角必须显式化
人类代码审核中,资深 reviewer 往往会自然切换视角:
- 先看行为是否正确;
- 再看接口是否兼容;
- 再看错误路径;
- 再看测试;
- 再看可维护性。
大模型不会天然稳定地做这件事。
所以必须把视角显式写进流程。
这就是专项 profile 的意义。
原则二:审核输入必须版本化
任何报告都必须绑定输入。
如果输入不明确,结论就不可验证。
代码审核尤其如此,因为 diff 会不断变化。
每次重新生成当前审查范围,本质上是在给审核输入做版本化。
这就是 fresh diff scope 的意义。
十五、结语:Multi-Pass 是骨架,Profile 和 Fresh Scope 是工程化关键
Multi-Pass Review 让大模型代码审核从单次回答变成多轮流程。
但如果只有 Multi-Pass,还不够。
因为多轮不等于多视角,复审不等于看旧问题。
一个真正可用的代码审核 Skill,需要进一步做到:
- 用专项 profile 明确每轮审查视角,提高领域风险覆盖率;
- 用 fresh diff scope 保证每次审核都基于当前变更状态;
- 用历史问题作为回归线索,而不是审查范围;
- 用聚合规则区分共识问题、专项发现和可选建议;
- 用中间产物保证报告可追溯;
- 用结构化 schema 限制模型幻觉和自由发挥。
可以把它总结成一句话:
Multi-Pass 解决“模型一次看不稳”,Profile 解决“模型不知道从哪些角度看”,Fresh Scope 解决“模型到底在看哪份代码”。
这三者结合,才构成一个真正适合工程使用的 Review Skill。
它不是为了让大模型替代 reviewer,而是为了让大模型成为一个稳定、受控、可复查的审核参与者。
对于任何希望把 AI 引入代码审核流程的团队来说,这才是比“写一个更强 prompt”更重要的事情。









