# 第 4 章　采纳率低：问题为什么看起来对，却没人采用

> 预计学习时间：85–105 分钟
> 一句话总结：低采纳率通常不是一个模型问题，而是一组可以沿规则、上下文、建议和反馈证据逐层定位的系统问题。

## 先看四条“没有明显错误”的建议

同一批代码审查里出现了四条候选问题。为了公开教学，代码与业务名已经改写，但拒绝理由保留了原始结构。

第一条说：“请求头可能为空，直接访问字段会触发 panic，建议提前返回。”开发者拒绝，因为空请求头在这条业务链里并不表示非法输入，转换函数仍需产出一个可继续处理的对象。AI 发现了语言层面的空指针风险，却把业务允许的空状态当成了异常状态。

第二条说：“某个对象可能为空，应在调用前补一次检查。”开发者指出，被调用方法内部已经处理了空值。建议如果被执行，不会改善正确性，只会让同一条件在两层代码里重复出现。

第三条说：“表单校验仍使用 callback，应该改成 Promise。”这符合常见开源组件的新版本写法，但当前项目使用的组件封装仍推荐 callback。AI 把公共生态的默认知识覆盖到了本仓库的真实契约上。

第四条正确指出一个异步调用没有 `await`，但给出的修复是直接加 `await`。开发者实际需要的是不阻塞主流程，并在被调用方法内部捕获错误。问题方向有价值，修复方案却改变了时序语义。

这四条评论都能写得很像专业 Code Review。词句流畅，风险描述也有因果关系。它们仍然不值得直接采纳。真正缺少的不是“再解释得详细一点”，而是能证明建议适用于当前改动的仓库证据。

上一章把采纳率定义成 Precision 的工程代理。本章开始处理一个更棘手的事实：未采纳不是单一标签。它可能表示误报、已有处理、业务不同意、修复不可用、问题价值太低，也可能只是反馈来得太早。先把这些原因拆开，后面的优化才不会变成盲目改 Prompt。

## 44 条拒绝样本告诉了我们什么

课程案例包含一组定向收集的 44 条拒绝建议。其中 43 条被归为健壮性问题，1 条被归为安全问题。这个分布不能代表真实生产总体，因为样本就是为了收集 Bad Case 和未采纳案例而建立的。它适合回答“常见拒绝机制有哪些”，不适合回答“某类问题在生产中占多少”。

逐条阅读拒绝证据后，可以把样本归入下面七类。一个样本可能同时命中两类，分类目的是选择验证动作，不是制造互斥统计。

| 诊断类型 | 候选建议的典型说法 | 开发者提供的反证 | 首先检查什么 |
| --- | --- | --- | --- |
| 场景不存在 | “如果字段为空会失败” | 类型、入口或数据生成逻辑保证该状态不会出现 | 类型定义、构造路径、系统边界 |
| 已有处理 | “这里缺少校验或提示” | 上游、下游或公共方法已处理 | 调用链与被调用方法 |
| 业务语义误判 | “应该按角色返回不同值” | 业务明确要求取更晚时间或允许空状态继续 | 需求、测试、领域命名 |
| 框架契约误判 | “旧 API 必须迁移到新写法” | 项目封装、组件版本或注解定义了不同契约 | 依赖版本、封装源码、项目规则 |
| 修复不可用 | “增加 await、默认值或互斥锁” | 改变时序、破坏配置语义，或保护了并不共享的数据 | 建议代码的行为差异 |
| 重复或过时 | “再次报告相同风险” | 历史会话已报告，或当前版本已用其他方式修复 | 历史 Issue 与当前文件 |
| 价值不足 | “补充额外防御、注释或抽象” | 风险很低，修复成本和噪音高于收益 | 严重度、触发概率、影响范围 |

这一组样本有一个很稳定的特征：许多评论不是语言知识错了，而是适用条件错了。`nil` 会导致解引用失败是语言事实；“这个对象在此路径可能为 nil”是仓库事实；“遇到 nil 应直接返回”又是业务决策。AI 经常把第一层事实直接跳到第三层结论。

```mermaid
flowchart TD
  A[候选建议] --> B{语言或框架事实正确吗}
  B -- 否 --> R1[知识错误]
  B -- 是 --> C{触发条件在当前仓库成立吗}
  C -- 否 --> R2[场景误判]
  C -- 是 --> D{问题已在其他位置处理吗}
  D -- 是 --> R3[重复或已有处理]
  D -- 否 --> E{建议代码保持业务语义吗}
  E -- 否 --> R4[修复不可用]
  E -- 是 --> F{收益高于噪音与改动成本吗}
  F -- 否 --> R5[低价值]
  F -- 是 --> K[值得保留]
```

这棵树比“采纳/未采纳”多走了几步，却给出了可行动的结果。知识错误需要修规则或模型；场景误判需要上下文；已有处理需要调用链和历史过滤；修复不可用要把问题判断与建议代码分开验收；低价值则要校准严重度和报告门槛。

## 采纳率低不是一个根因

### 规则缺失：系统不知道团队认为什么重要

不同仓库对同一写法可能有相反判断。某个前端项目要求所有用户可见文案进入国际化函数；另一个项目的管理端脚本可能允许硬编码。某个后端团队禁止手写 SQL；另一个模块可能因为兼容旧查询层而暂时允许。若审查器只收到通用规则，它只能用训练数据里的常见偏好填空。

规则缺失会产生两种噪音。第一种是把团队允许的写法判错，例如强制把 callback 改成 Promise。第二种是把真正重要的项目约束降成普通建议，例如仓库明确禁止动态拼接国际化文案，模型却只给 2 分风格评论。

诊断规则缺失不能只问“有没有规则文件”。还要看三个证据：当前问题是否命中明确规则 ID；规则是否描述了适用条件；规则与正反例是否进入了这一批 Prompt。一个文件存在磁盘上，但加载失败、解析为空或没有被注入，运行效果仍等于没有规则。

课程案例的 `promptBuilder.ts` 会组合基础角色规则、规则库中的系统条目和仓库级 Cursor Rules。这个实现说明规则不是一段大 Prompt，而是多个来源在运行时装配。第 5 章会继续拆这三层怎样分工。

### 上下文不足：模型看见了 Diff，却没看见事实

Diff 能回答“这几行改了什么”，很难独立回答下面的问题：

- 这个字段由谁构造，是否真的可能为空？
- 被调用方法是否已经处理错误或设置了默认值？
- 当前组件是公共版本，还是团队二次封装？
- 这个配置允许为 0，是异常还是有意关闭？
- 同一需求的上一轮修改是否已经解决该问题？

Bad Case 中的“已有可选链”“方法内部已有空处理”“配置会在启动时校验”“只有当前协程更新该字段”，都需要 Diff 之外的证据。只把更多相邻行塞给模型并不够。需要的是与待验证假设有关的上下文：符号定义、一层调用方和被调用方、类型注解、项目规则、依赖版本、测试与需求约束。

可以把上下文不足分成检索失败和判断失败。检索失败是没有拿到正确文件；判断失败是拿到了文件，却没有把其中的保证用于结论。两者的修复完全不同。前者要改文件关联或工具调用，后者要让 Recheck 明确验证假设，并保存使用了什么证据。

```mermaid
flowchart LR
  D[Diff 中的可疑代码] --> H[提出触发假设]
  H --> T[类型与注解]
  H --> U[上游构造与校验]
  H --> V[下游处理与副作用]
  H --> P[项目规则与组件契约]
  T --> J{证据是否支持触发}
  U --> J
  V --> J
  P --> J
  J -- 支持 --> I[保留 Issue]
  J -- 不支持 --> X[标记无效并记录原因]
```

图里最重要的是“提出触发假设”。如果只让 Agent 漫无目的地搜索仓库，它会收集很多相关文本，却未必能推翻自己的第一判断。好的验证问题应该可以被证伪，例如：“`accountInfo` 是否存在任何返回成功但字段为空的构造路径？”而不是“请检查更多上下文”。

### 问题判断和修复建议绑得太紧

一条评论可能发现方向正确，建议代码却不适用。异步调用缺少错误处理是问题；是否加 `await` 取决于调用方是否应等待。配置缺少检查可能值得关注；是否提供默认值取决于配置错误时系统应该失败还是继续。共享对象的写入值得检查；是否加锁取决于是否真有多个并发写者。

如果评估只问“是否照着 improve_code 修改”，这类评论会被当成完全错误。更合理的内部判断至少拆成四项：

| 维度 | 判断问题 | 可能结果 |
| --- | --- | --- |
| 定位 | 文件和代码位置对吗 | 正确、偏移、与本次变更无关 |
| 风险 | 描述的失败机制成立吗 | 成立、不成立、证据不足 |
| 严重度 | 触发概率和影响是否配得上分数 | 合理、过高、过低 |
| 修复 | 建议保持当前业务与时序语义吗 | 可直接用、需改写、不可用 |

采纳反馈目前往往只有一个状态，因此诊断时要结合拒绝原因和最终代码。若大量样本是“方向对，修复不对”，继续增加规则可能没有帮助；应该让审查器先给出证据和最小修复约束，再生成代码建议，或者把修复建议交给独立步骤复核。

### 范围错位：评论没有属于当前变更

现代仓库常有大量历史问题。AI 读取整个文件后，可能发现一个真实缺陷，但它位于未修改的旧代码里，与当前 MR 没有直接关系。对代码质量而言它“是真的”；对本次审查而言它仍可能是噪音，因为开发者没有上下文、预算或授权在当前需求里处理。

课程案例用 `diff_scope` 保存问题位置与差异范围的关系：

- `changed_line`：定位在新增或修改行。
- `context_line`：位于 Diff 上下文行，需要证明本次改动直接触发它。
- `out_of_diff_scope`：不在当前 Diff hunk 中，Recheck 直接判为无效。
- `unknown`：无法确定范围时，必须用文件、原代码和建议代码证明关联，否则不保留。

范围判断不是“只允许评论绿色行”。跨文件依赖、接口变化和数据结构影响可能落在未修改文件中。区别在于是否有一条可解释的变更因果链。没有因果链的全仓扫描结果应该进入独立治理任务，而不是混进当前 MR 的阻断评论。

### 重复、冲突与已经修复的问题

同一需求可能多次运行审查。第一次建议从 A 改为 B，第二次又因上下文变化建议改回 A；或者两次都报告同一个位置的同一风险。即使每条单独看都有理由，连续展示会让使用者觉得工具没有记忆。

重复有三个层级。文本重复可以用哈希或相似度处理；根因重复需要理解两个描述是否指向同一失败；历史重复还要判断旧问题是否已被当前代码用另一种方式修复。简单把历史 Issue 全部拼进 Prompt，既浪费上下文，也可能让模型机械地重复旧结论。

当前案例的最终 `filter` 会在远端模式最后一批比较当前与同一需求的历史有效问题，支持 `duplicate_of_history`、`conflict_with_history`、`overlap_with_history`、`history_resolved` 和 `history_changed`。这是一层收尾过滤，不等于所有本地审查都自动拥有相同能力。

### 严重度和价值没有校准

如果每个“理论上可能”都标 4 分，开发者很快会忽略整个报告。风险分数至少需要回答触发可能性、影响范围、现有防护与恢复成本。一个只在不可能输入下触发的 panic，不应仅凭“panic 很严重”得到最高分。

课程案例中的已采纳样本也提供了对照。后端样本里，错误变量引用、`nil && len(x)` 的逻辑错误、分页字段误赋、循环层级错误等问题具有直接证据，修复范围也清楚。前端样本里，国际化规则、拼写错误、行键不稳定等建议同样可以由项目规则或可观察行为支撑。它们和拒绝样本的差异不是措辞更强，而是证据链更短、更可复核。

严重度校准可以从“同类已采纳与已拒绝对照”开始。不要先设一个普适阈值。对每个仓库分别观察：4–5 分问题中哪些有明确触发路径，哪些总被判为防御性建议；修正规则与评分后，再看分层采纳率和召回护栏。

## 从数据对象里找证据

诊断不能停在阅读评论。一次低采纳调查至少需要把 Issue、Session、代码版本和反馈连起来。课程案例的关键字段可以抽象成下面的证据链。

```mermaid
erDiagram
  SESSION ||--o{ ISSUE : produces
  ISSUE }o--o{ RULE : matches
  ISSUE ||--o{ FEEDBACK : receives
  ISSUE ||--o{ CONTEXT_EVIDENCE : validated_by
  SESSION {
    string session_id
    string project_id
    string source_branch
    string target_branch
  }
  ISSUE {
    string file_path
    string diff_scope
    int score
    int is_valid
    int is_applied
    string reject_type
    string source_rule_ids
  }
  FEEDBACK {
    string source
    string reason
    datetime checked_at
  }
  CONTEXT_EVIDENCE {
    string symbol
    string file_revision
    string conclusion
  }
```

当前实现已经保存了 Issue 的规则命中、有效性、无效原因、采纳状态和拒绝类型等字段，但“本次 Recheck 具体读取了哪些符号与版本”仍主要存在 Agent 过程里。若团队经常争论上下文是否充分，可以增加结构化证据引用，例如 `evidence_files`、`evidence_symbols`、`assumption` 与 `verification_result`。这会增加存储和 Prompt 约束，收益是 Bad Case 可以复盘，而不是只剩一句“模型判断错误”。

`reject_type` 也不能完全依赖关键词。当前自动归因会把“误报、不是问题”映射为 `false_positive`，把“**建议不可用**、修复不对”映射为 `bad_fix`，把“不重要、忽略”映射为 `low_value`，还识别风格分歧与已经处理。它适合做粗分桶，不适合替代人工抽样。拒绝理由很短时，`unknown` 往往比武断分类更诚实。

## 五个根因工作台：怎样从评论走到证据

根因树只有在能指导取证时才有价值。下面选取五类高频拒绝，完整走一遍“现象、假设、证据、判断和改造归属”。案例均经过等价改写。

### 工作台一：理论上的空值，真实链路里的合法空状态

候选评论认为 `header` 可能为空，建议直接返回 `nil`。开发者的反证是：“空 header 也要继续。”这句话至少包含两种解释。第一种是 header 的字段有默认零值，转换函数应该继续生成参数；第二种是当前调用者会在更高层补齐 header，提前返回反而破坏流程。仅凭拒绝文本无法选择。

先画出对象生命周期：外部请求怎样反序列化；转换函数是否处于系统边界；空 header 由协议允许、兼容历史请求，还是只在测试替身里出现；转换结果的字段是否允许零值；下游在哪一步完成最终校验。然后构造两个最小测试：header 为空时当前实现输出什么；加入提前返回后调用方行为怎样变化。

若空 header 是协议允许状态，问题属于业务语义误判，修复建议有害。规则层应写清“此转换器允许缺少 header，并以零值继续”。若空 header 本应被入口拒绝，只是当前测试没有覆盖，那么开发者拒绝并不能推翻风险，问题应保留但修复位置可能从转换函数移到入口。

这个案例提醒我们，反馈不是天然真值。开发者最了解局部业务，也可能只描述了当前习惯。诊断需要把反馈转换成可验证假设。

### 工作台二：忽略解析错误，还是有意使用零值兜底

候选评论看到 `strconv.ParseInt` 的错误被忽略，建议显式检查 `err`。拒绝理由是：“ID 一定是整数，并且后续 `> 0` 已经安全兜底。”

这里有三层证据。类型层：ID 在上游是字符串还是数字，为什么需要解析。数据层：该字符串是否只来自数据库整数列，还是也来自用户输入、消息和旧数据。行为层：解析失败变成 0 后，`> 0` 分支跳过是否就是期望结果，还是会静默丢数据。

如果字符串完全由受控整数列生成，且 0 与“无有效 ID”语义一致，显式处理错误可能只增加日志噪音。若数据穿过外部边界，静默跳过会隐藏污染，建议方向成立。还有第三种情况：风险成立，但正确修复是在反序列化入口保证类型，而不是在每个使用点检查。

因此标注不能只写 `false_positive`。可以记录 `valid_risk_wrong_location`，让规则学习“报告系统边界缺失校验”，不要在所有消费点重复报告。

### 工作台三：公共框架知识和项目封装冲突

候选评论看到表单 validator 的 callback 风格，认为旧 API 会导致校验失败，建议返回 Promise。拒绝理由是“组件库推荐用法”。

验证顺序应从仓库向外，而不是先搜索公共文档。先确定导入路径，找到项目封装的类型声明和版本；再查看同仓库中由维护者编写的例子；必要时运行最小表单测试，观察 callback 是否被调用、错误是否显示。只有项目代码无法解释时，才用上游文档补充。

若项目封装确实支持 callback，这条评论属于框架契约误判。解决方案不是把“callback 永远允许”写进通用规则，而是在仓库规则中声明具体组件与版本。组件升级后，规则必须同步失效或迁移。没有版本字段的规则会把今天的正确经验变成明天的误报来源。

这种案例还暴露了检索优先级。Agent 如果先访问公共知识，容易形成锚定；后续即使读到项目封装，也可能把它当作落后实现。Recheck 应明确规定“实际依赖和仓库封装优先于无版本的通用最佳实践”。

### 工作台四：已经处理，但处理位置与形式不同

候选评论说“照片上传失败时没有提示”，开发者回答“调用方法已有统一错误提示”。要验证的不只是有没有 Toast 字符串，还要看失败是否沿调用链传播。

需要检查上传方法失败时返回什么；循环是否吞掉错误；调用者如何区分部分成功和全部失败；统一提示是否覆盖当前路径；重复提示会不会造成两个弹窗。若底层抛错且上层统一 catch，当前建议会重复处理。若底层只返回空结果而不上抛，开发者以为已有提示，实际路径可能仍静默失败。

“已有处理”根因最适合保存证据引用：callee 的错误分支、caller 的 catch、统一提示组件和对应测试。没有这些引用，下一轮模型无法区分真正已有处理和开发者口头声称。

还要注意修复层级。即使当前路径缺少提示，把 Toast 加在数据访问层也可能违反架构边界。AI 评论应该指出缺失的用户可见反馈，而不是强制某一层直接调用 UI API。

### 工作台五：并发风险存在于变量，还是存在于执行模型

候选评论发现共享 Map 的字段在 goroutine 中被写入，建议加互斥锁。开发者说：“只有这个协程更新该字段。”

看到共享对象不等于存在数据竞争。需要找出 goroutine 的创建位置、对象是否被多个任务复用、其他路径是否读写同一字段，以及当前容器是否线程安全。单一 goroutine 内部顺序写入普通 Map 不需要锁；多个 goroutine 写不同 key 仍可能不安全；单写多读也要看读写是否并发。

最小验证可以运行竞态检测或构造并发测试，但工具结果必须和覆盖路径一起解释。测试没有报 race 可能是分支未命中，不代表不存在；代码证明只有单写者时，加锁反而增加复杂度并可能引入锁顺序问题。

若此类误报反复出现，规则不应写成“不要报告 Map 并发问题”。应写成条件化判断：先定位并发创建点和所有写者；无法证明至少两个并发访问时，不给高分确定性结论，可以提示需要人工确认。

## 采纳失败和反馈失败要分开

有些低采纳不是建议质量问题，而是反馈系统没有正确观察到采用。开发者可能手工实现了语义等价修复，没有点击采纳；外部生码平台可能覆盖代码却未回传状态；建议只改了配置或测试，自动检测读取了错误分支；合并后文件重命名导致路径匹配失败。

可以把调查分成两个连续问题：

1. 建议在当时是否值得采用？
2. 现有反馈机制是否正确记录了采用结果？

第一个问题需要代码与业务评审，第二个问题需要状态来源、时间戳和最终版本。若两者混在一起，团队可能为了修检测器而改规则，也可能把检测漏报误认为开发者不信任系统。

```mermaid
flowchart TD
  I[候选 Issue] --> Q{当时是否成立且值得处理}
  Q -- 否 --> FP[建议质量问题]
  Q -- 是 --> A{最终代码是否解决根因}
  A -- 否 --> NA[真实未采纳]
  A -- 是 --> S{状态是否记录为采纳}
  S -- 是 --> OK[反馈一致]
  S -- 否 --> OBS[反馈观测失败]
  OBS --> O1[语义等价修复]
  OBS --> O2[路径/分支/时间错误]
  OBS --> O3[外部反馈未回传]
```

调查反馈失败时，要保存 Issue 创建 commit、检测所用 branch、最终 merge commit、文件重命名映射和状态更新时间。尤其不要用当前主干去判断几周前的建议，文件可能已经经历其他修改。

### 无反馈不是未采纳

`is_applied=null` 表示没有明确判断。它可能是开发者尚未处理、没有看到评论、审查任务中断，或反馈入口不可用。把 null 直接算未采纳，会惩罚反馈覆盖差的仓库；直接排除又可能只留下愿意互动的用户。

诊断报告应并列显示明确反馈率、反馈延迟和未反馈样本抽查。若优化后采纳率升高而反馈覆盖率下降，不能直接宣布系统变好。可能只是更少的人愿意点击。

### 自动未采纳也不是拒绝理由

行级检测发现建议代码没有出现在最终文件，只能说明文本没有按当前算法匹配。它不知道开发者为何没有采用，也不知道是否做了语义等价修复。自动状态适合触发补充核验，不应直接生成“开发者认为建议错误”的负向规则。

## 标签争议：谁有权决定一条评论是否正确

代码审查包含技术事实、业务语义和团队选择。单一评审者很难覆盖全部。可以按争议类型指定仲裁：

| 争议 | 首要评审者 | 必需证据 | 不能单独决定的人 |
| --- | --- | --- | --- |
| 语言/并发/类型事实 | 对应技术栈维护者 | 可运行测试、类型或规范 | 只凭业务习惯的使用者 |
| 业务允许状态 | 模块 Owner/TL | 需求、接口契约、测试 | 只看公共最佳实践的模型 |
| 组件封装行为 | 组件维护者 | 版本、源码、推荐用法 | 未确认版本的外部资料 |
| 严重度 | 模块 Owner + 质量角色 | 触发概率、影响与恢复 | 只依据错误名称的评审者 |
| 修复可用性 | 代码作者 + 维护者 | 行为差异、回归测试 | 只检查语法可编译的人 |

仲裁不需要每条 Issue 都开会。可以先抽样建立规则，再把明确案例自动化。对高风险、意见冲突或证据不足的样本保留 `uncertain`，不要为了报表完整强制二选一。

一致性也要测。让两位评审者独立标注同一批样本，观察分歧集中在哪些类别。若“业务语义误判”长期分歧大，说明标签说明不足或需要模块 Owner 参与；若“范围错位”分歧大，可能是 Diff 归因规则不清。

## 观察当前实现时，应该问哪些问题

阅读采纳率链路的实现代码，不必把几千行代码逐行抄进课程。沿控制点提出问题更有效。

在 `promptBuilder.ts` 检查：规则来自哪里；项目规则解压失败是否可见；规则与示例是否有 ID；Issue 是否回写命中规则。在 `extensionChecker.ts` 检查：Recheck 何时执行；`diff_scope` 怎样影响结论；无效原因是否保留；检查器是否可能被配置关闭。

在 `filterChecker.ts` 检查：历史任务怎样关联；本地与远端是否行为一致；重复、冲突和已修复分别怎样落状态。在 `memoryDistiller.ts` 与 `memoryChecker.ts` 检查：拒绝信号怎样去重；项目级与用户级怎样隔离；规则是否需要确认、是否过期、命中后如何审计。

最后在 `adoptionChecker.ts` 检查：检测读取哪个分支和文件；注释、空白、重命名和短代码怎样处理；部分采纳是否进入正式指标；拒绝类型是事实字段还是关键词推断。

这些问题能把“采纳率低”映射到具体代码对象。若调查只停在模型输出文本，后续团队很难判断应该改 Prompt、检索、状态机、数据模型还是反馈入口。

## 建立一张可复盘的根因仲裁卡

调查结束后，不要只在会议纪要里写“AI 误报”。可以为每个抽样 Issue 保存一张仲裁卡，让同类问题能够比较。

```text
issue_id: teaching-042
review_revision: commit-a
feedback_revision: commit-b
candidate: 建议为内部对象字段增加空值检查

scope_decision: caused_by_change
trigger_hypothesis: 成功构造对象的字段可能为空
evidence_for:
  - 字段类型允许空值
evidence_against:
  - 只有一个成功构造入口
  - 构造入口在返回前填充该字段
  - 测试覆盖空输入并在边界拒绝

issue_validity: invalid
fix_quality: harmful
root_cause: missing_repository_context
recommended_control: recheck_callee_and_constructor
reviewer_roles: module_owner, backend_reviewer
label_version: adoption-diagnosis-v1
```

这张卡把结论和证据分开。以后构造入口改变时，可以重新判断旧标签；Recheck 改造后，也可以检查它是否读取了相同证据。若只保存“误报”，规则提炼只能模仿一句结论，无法知道何时不适用。

仲裁卡还应记录反事实：什么变化会让当前结论翻转。上例中，只要发现另一个能绕过成功构造函数的反序列化入口，空值风险就需要重新评估。可翻转条件让规则保持开放，而不是把一次拒绝变成永久事实。

### 用证据强度决定自动化程度

不同证据的确定性不同。编译错误、类型不匹配、确定的数据流通常能自动验证；业务允许状态、用户体验和未来扩展需要人参与；“可能发生”但检索失败的情况只能标不确定。

| 证据等级 | 示例 | 适合的动作 |
| --- | --- | --- |
| 确定证据 | 编译失败、明确类型约束、可复现竞态 | 自动保留或过滤，并保存工具结果 |
| 强仓库证据 | 单一构造路径、项目规则、稳定测试 | Recheck 决定，定期抽样 |
| 业务证据 | 需求允许空值、模块 Owner 解释 | 人工确认后形成项目规则 |
| 弱推断 | 常见最佳实践、相似代码、模型常识 | 只能提出假设，不应给高分结论 |
| 证据缺失 | 文件无权限、符号检索失败 | 标记不确定，不能当作不存在 |

这张表也约束负向反馈。弱推断被拒绝可以帮助建立检索计划，不能直接生成绝对过滤规则。只有稳定、可复核、范围清楚的证据才适合自动化。

### 把修复位置也纳入仲裁

不少建议的问题方向成立，修复位置不对。例如入口缺少校验，却在每个消费点增加防御；统一错误处理缺失，却在底层库直接弹 Toast；异步任务需要观测，却在调用方强行 `await`。仲裁卡应记录 `recommended_layer`：boundary、domain、service、caller、callee、UI 或 infrastructure。

这样做能区分两种改造。若 `issue_validity=valid` 但 `fix_quality=harmful` 集中出现，优先改修复生成和架构约束；若 `issue_validity=invalid`，才去改规则、上下文与 Recheck。把两类都算成“未采纳”会浪费工程投入。

## 一套可复用的低采纳诊断流程

### 第一步：固定指标版本与样本窗口

先记录采纳率口径、反馈成熟窗口、仓库、语言、模型版本、规则版本和审查入口。不要把自动状态、人工状态和外部反馈随意混在一起。第 3 章已经说明，当前正式口径只纳入明确状态 0/1/4/5，并过滤低分和明确无效问题。

还要记录反馈覆盖率。假设 1,000 条高分候选中只有 120 条得到明确反馈，60% 采纳率只能描述这 120 条。若反馈者只在非常满意或非常不满时点击，样本仍然偏斜。

### 第二步：抽取“成对证据”

每个样本至少包含候选问题、当时 Diff、相关仓库上下文、开发者反馈和最终代码。缺少最终代码时，不能断言建议未实施；缺少当时版本时，也不能用今天的文件反推当时上下文。

抽样要同时包含已采纳与未采纳。只看 Bad Case 会把系统描绘得过度悲观，也无法找出同类问题何时值得报告。课程案例的三份定向数据正好说明这个边界：34 条已采纳与 44 条拒绝可以做对照教学，却不能拼成总体比例。

### 第三步：先判问题，再判修复

对每条建议依次回答：位置属于本次变更吗；触发条件能否由当前代码证明；是否已有处理；严重度是否合理；建议代码是否保持业务语义。评审者意见不一致时，记录分歧点并仲裁，不要强行压成一个模糊标签。

可以使用如下最小标注结构：

```text
issue_validity: valid | invalid | uncertain
scope: changed | caused_by_change | historical | unknown
root_cause: rule | context | duplicate | scoring | none
fix_quality: usable | needs_revision | harmful | absent
evidence: [type_or_annotation, caller, callee, test, requirement]
```

这段结构是教学建议，不是当前数据库字段。它的价值在于把“没采纳”拆成可修的原因。

### 第四步：按可控制层聚合

聚合维度应直接对应工程动作：

- `false_positive + scene_impossible`：检查类型、边界和 Recheck。
- `already_handled`：补调用链与重复检测。
- `bad_fix`：分离风险判断和修复生成。
- `style_disagree`：把团队偏好写入仓库规则，或降低严重度。
- `low_value`：调整报告门槛与分层展示。
- `duplicate/history`：改历史 Issue 过滤和问题身份键。

如果团队只能看到“健壮性问题采纳率 32%”，仍然不知道下一步做什么。根因桶应该稳定到足以观察趋势，又不能细到每个样本一个标签。

### 第五步：只改变一个控制点

规则、上下文、Recheck 和记忆同时上线，指标变化后无法归因。先选择最大的根因桶做最小实验。例如：

> 假设：`already_handled` 占比高，是因为首轮审查没有读取被调用方法。处理组在 Recheck 中强制读取 callee 与一层 caller；对照组保持不变。主要指标看该根因桶的明确反馈采纳率，护栏看高分 Recall、时延、工具调用次数和无法判断率。

若处理组减少了误报，却让审查时间翻倍，需要评估是否只对高分候选或特定子类触发。工程优化不是把所有证据都塞进每一次请求，而是让证据成本随风险变化。

```mermaid
flowchart LR
  S[固定版本与窗口] --> P[采纳和拒绝成对抽样]
  P --> L[问题/范围/修复分层标注]
  L --> G[按可控制根因聚合]
  G --> H[提出可证伪假设]
  H --> E[**单变量实验**]
  E --> M[采纳率 + Recall + 成本护栏]
  M --> G
```

## 诊断练习：不要急着给解决方案

下面五条拒绝理由来自脱敏后的教学样本。先写出最可能的根因，再指定一项能推翻你判断的证据。

1. “这个字段不会为空，类型构造时已保证。”不要直接归为“模型不懂业务”。先查看构造函数是否覆盖全部入口，以及反序列化或测试替身能否绕过保证。
2. “方法内部已经做了空处理。”检查被调用方法的具体版本和返回语义。若内部只记录日志但仍解引用，开发者反证可能不完整。
3. “组件库就推荐 callback。”检查实际依赖版本、封装文档和仓库中的同类代码。公共文档不能代替项目契约。
4. “不需要 await，内部会捕获错误。”检查调用者是否依赖完成顺序、内部是否真的覆盖全部拒绝路径，以及未等待 Promise 是否会产生未处理拒绝。
5. “只有这个协程写该字段。”寻找所有闭包捕获、共享引用和异步入口。若确实只有单写者，加锁建议应判无效；若读取方与写入方并发，还要判断数据结构是否允许。

练习的完成标准不是猜中开发者答案，而是给出可判读的证据。证据显示假设不成立时，应允许改变结论。

完成练习后，再检查自己的取证顺序。若第一步总是搜索公共最佳实践，说明还没有把仓库事实放在优先位置；若只接受开发者一句“不会发生”，说明没有验证业务反证；若发现风险成立就默认建议代码正确，又把问题判断和修复质量合并了。成熟的诊断允许三种结论并存：问题成立且建议可用，问题成立但建议需重写，问题在当前范围不成立。

把五条练习的证据放进根因仲裁卡，然后尝试聚合。只有至少两条样本共享相同触发条件、排除条件和改造动作时，才考虑形成规则。表面都在讨论空值，不代表它们应进入同一条规则。



## 单变量实验设计

采纳率低时最容易犯的错误是一口气改 Prompt、加规则、调上下文，然后看到数字变了就说"优化有效"。**你不知道哪个动作真正起效，也不知道哪个动作在帮倒忙。**

正确的诊断顺序是：先用拒绝原因把问题分层，找到最粗的根因桶，然后**一次只改一个变量**，在固定样本上验证效果。

假设分层后发现 60% 的拒绝来自"已有处理"和"场景不存在"。这两类都与上下文不足有关——模型没看到被调用方法的内部实现，没看到入口的类型约束。那第一个实验应该是增加上下文（读取被调用方法的签名和文档），不改 Prompt、不改规则、不改模型。**如果采纳率提升，说明根因判断正确；如果没有，说明这两类拒绝另有原因。**

实验单位必须防止污染。对同一 MR 同时运行两版而把两版评论都展示给开发者，反馈会互相影响。可以离线双跑，或按仓库/用户分流；任何在线实验都需要说明风险与人工兜底。

结果也要区分**统计显著与工程意义**。即使样本足够让 1 个百分点差异稳定，如果它换来两倍成本，未必值得上线；样本较小但发现高危漏报，则可能先采取保守防护再继续收集。指标辅助决策，不替代风险责任。

## 发布门槛

门槛应由仓库基线决定，下面是一组结构，不是固定数字：

- 目标误报子类相对下降，并达到最小样本量。
- 固定 Benchmark 的高风险 Recall 不低于基线容忍区间。
- 被 Recheck 或负向规则过滤的高分问题完成人工抽查。
- 审查时延和 Token 成本没有超过服务预算。
- 规则命中、过滤原因和版本可以追溯。
- 无法判断率没有因"更谨慎"大幅上升。

采纳率上升只能算一个条件。**Recall、成本、反馈覆盖与可追溯性共同决定能否推广。**

## 迁移任务：为另一个仓库设计降噪闭环

选择一个你熟悉的仓库，找出一种经常被拒绝的评论。不要直接写新 Prompt，先提交一页设计：

1. 给出三条成对样本：候选问题、拒绝证据、最终代码。
2. 判断根因属于规则、上下文、范围、修复、历史还是价值。
3. 写一条带触发条件、排除条件和证据要求的规则。
4. 设计 Recheck 需要读取的最小上下文与停止条件。
5. 决定这条经验应是组织、项目还是个人范围，保存多久，谁能禁用。
6. 定义主要指标、Recall 护栏、成本预算与回退条件。

一个合格方案应该允许评审者指出"哪项证据会让这条规则不再成立"。**如果规则无法被证伪，它很可能只是偏好口号。**


## 本章收束

低采纳率不是“评论写得不像人”这么简单。课程案例的拒绝样本反复出现同一条因果链：模型掌握通用风险，缺少当前仓库的适用条件，于是把可能性写成事实，再给出改变业务语义或重复现有防护的修复。

诊断时先固定指标口径，收集候选问题、当时上下文、反馈与最终代码。然后把位置、触发条件、已有处理、严重度和修复质量分开判断。规则缺失、上下文不足、Recheck 失效、历史重复和低价值问题需要不同控制点，不能都靠扩写 Prompt。

第 5 章会把这些根因映射成一条分层改造链：先建立可版本化规则和最小上下文，再用 Recheck 过滤首轮候选，用负向记忆吸收反复拒绝，用历史过滤减少重复，最后用人工反馈与自动检测验证效果。每层都要保留开关、证据和 Recall 护栏。

## 参考文献

本章以课程案例当前代码、脱敏后的 44 条定向拒绝样本和已采纳对照样本为主要事实来源。样本用于诊断机制教学，不代表生产总体分布，也不用于计算总体采纳率。


---
