增是机器,删是人工:LLM 代码编辑中「删除回避」的度量与缓解

  • 关联论文:2607.28887
  • 作者:flyP
  • 更新:2026-08-05

一句话结论

前沿 LLM 修代码时系统性偏向「保留旧代码 + 新增补偿」,SWE-bench Verified 上删除召回最高仅 71.7%,四款前沿模型在新加的「删除校验测试」上从 63.2% 暴跌到 41.9%;论文把这种「不删就加 if/fallback」的失败模式命名为 Guard-and-Go,并用「在 post-training 中教删除」作为可行补丁。

这篇论文解决的真问题

SWE-bench Verified 这类 benchmark 把「通过测试」当成正确性的代理变量。但一个能通过测试的 patch,未必等价于开发者原本的意图——开发者想删的代码,模型可能用 try/except、条件分支或 fallback 包起来让它「继续有效」。结果就是:原仓库日益膨胀、历史 bug 反复隐性复现、开发者最终还得人工清理。

论文把这类失败具象化为 deletion avoidance(删除回避): - 模型知道要改哪个文件(92% 命中正确文件); - 模型不太敢真正切到要删的那一行(精确切行 < 52%); - 模型倾向「裹一层保护壳」让旧代码仍然可达(Guard-and-Go,占通过 patch 的 29.0%)。

这种失败不能从「通过率」里看出,因为原测试几乎从不验证「该删的东西确实被删了」。

核心方法(机制 + 工程路径双轨)

机制:四层诊断 + 一个新基准

  1. 开发者补丁对齐:在 SWE-bench Verified 上,把每条任务的开发者补丁拆成「必须新增的行」和「必须删除的行」,把 LLM 生成的 patch 跟它对齐,定义 deletion recall
  2. Guard-and-Go 检测:用正则 / 启发式 + 静态分析,识别 patch 里被 try/exceptif False:、early-return 等结构「包起来但没删」的代码块。
  3. 逆向测试 retrofit:选 34 条 Verified 任务,在原测试之外加一个反向测试——若目标代码还残存就 fail。四款前沿模型平均分从 63.2% → 41.9%(绝对 -21.3 个百分点)。
  4. 新基准 CanItDelete:从真实 commit 挖 200 条「整个 patch 全部是删除」的任务。这样就把「添 + 删混合」中的添掩盖掉,单独测删除能力。最好模型在这种纯删除任务上仍有 ~20% 失败率,开源小模型跌到 18.0%。

工程路径:怎么用、用在哪

  • 可复现审计工具:以 SWE-bench Verified 为底盘的 patch-diff 工具,能在 leaderboard 分数之外输出「deletion recall + Guard-and-Go 比例」两条曲线,可挂进 CI。
  • post-training 训练数据:把 CanItDelete 的 200 条任务与「开发者 patch 的 diff hunk」配对,构造 reward,让模型在 SFT/RL 阶段学会「真删」而非「保留 + 加 fallback」。
  • prompt 级应急方案:作者在 GPT-5.6 Sol 上做了四组累积 prompt 消融,结论是「不告知行号时改动很小;显式给出待删行号 → deletion recall 几乎拉满,但 success 仅升到 80.5%」,因为模型开始「过度删除 + 把删除改回新增」。说明 prompt 只能救一半。

关键实验与数据

实验 数据 / 规模 主要结果
五款前沿模型 SWE-bench Verified 删除召回 五款均能解决的任务子集 deletion recall ≤ 71.7%;文件命中率 > 92%;精确切行 < 52%
Guard-and-Go 占比 通过 patch 集合 29.0% 的通过 patch 用了 guard / fallback 包裹目标代码
Retrofit 反向测试 34 条 Verified 任务,四款前沿模型 通过率 63.2% → 41.9%(绝对 -21.3 pp)
CanItDelete 纯删除基准 200 条真实 commit 最佳模型仍 ~20% 失败;开源小模型 18.0%
GPT-5.6 Sol prompt 消融 四组累积 prompt 显式给行号几乎消除「不删」;但总成功率仅 80.5%,因为开始「过度删 / 改删为加」
后训练 pilot 在 deletion 数据上微调 deletion avoidance 下降 + 更广泛代码编辑性能提升(原文未给出具体百分点,原文未明确)

数字均直接引自 arxiv abstract 与 paper card TLDR;未对外公布的子任务粒度数字,文中标「原文未明确」。

亮点与局限

亮点 - 把「测试通过 = 修对」这层公知偏差做成了可度量的失败模式,对 SWE-bench 类榜单的可信度是一次正面挑战。 - Retrofit 思路简单可复用——任何「必须删除 X」的语义,都能加一个反向测试。 - CanItDelete 是少见的「纯删除」基准;多数代码编辑基准把删除混在添加里,掩盖了这个问题。

局限 / 反方视角 - 论文只覆盖 Python 仓库(基于 SWE-bench Verified 的语料分布),其他语言下的 Guard-and-Go 比例未量化。 - 后训练 pilot 只是「one potential fix」,未给出完整训练 recipe、超参与对照模型,工业落地还需自行复现。 - 「教删除」的副作用——过度删除、把删改回加——已显式出现,作者没有量化这两种错误各自的频率。 - 对真实生产 PR 的评估缺失:开发者的 patch 本身可能就不干净,把 patch 当 ground truth 会继承噪声。

对工程落地的启发

  1. CI 多挂一条「删除回归」检查:对要求删除某个 API / dead code 的 PR,跑一遍 git blame + 行内容匹配,若目标行仍存在于合并后代码则 fail。
  2. 代码 review 模板化:在 PR 模板里要求「如需删除,请同时说明 Guard-and-Go 是否被有意保留」,避免 reviewer 漏看「包起来但没删」。
  3. SWE-agent 评测升级:内部排行榜除「测试通过率」外,加 deletion recall 与 Guard-and-Go 占比两条曲线,三条一起看。
  4. 训练数据补全:在 SFT / RL 阶段混入「纯删除」轨迹,把 CanItDelete 作为 reward signal 的一部分。
  5. prompt 工程的边界:当 prompt 里写「请删除 X 函数」仍无效时,直接附上行号 + diff;但要同步监控「是否被改成新增」,否则 prompt 一拐就拐到「改删为加」。

与同方向工作的关系

  • SWE-bench Verified 本身 形成互补:后者定义任务与通过测试,本论文指出通过测试并不等价于意图达成。
  • RefactorBenchSWE-bench Multilingual 等以「测试通过率为主要指标」的工作共享同一个盲区——本论文给出了可复用的反向测试方法学。
  • 与「Agentic code repair with test feedback」一类工作(如 AutoCodeRover、SWT-bench)形成对照:那些工作关注「怎么找到对的修改」,本文关注「找到了以后敢不敢真删」。
  • HumanEval / MBPP / LiveCodeBench 等生成式编码基准互补:那些测「从零写」,本文测「在已有代码上做减法」。

适合谁读

  • 代码 Agent / SWE-bot 团队:leaderboard 分数涨不动时,先看是不是 deletion recall 拖后腿。
  • DevTools / Code Review 厂商:把 Guard-and-Go 检测做成 IDE 静态分析规则。
  • LLM 评测研究者:想给「通过率」加补丁、加新轴的人,这篇提供了模板。
  • ML 平台 / RLHF 团队:把 CanItDelete 接进 reward 模型,比单纯的 test-pass reward 更稳。

不确定处

  • 后训练 pilot 的具体百分点、对照模型、消融细节在 abstract 中未给出(原文未明确)。
  • 「四款前沿模型」具体型号与版本未在 abstract 里逐一点名(原文未明确,需查正文 Table 1)。
  • CanItDelete 的 200 条任务在不同编程语言 / 项目类型上的分布未在 abstract 中披露(原文未明确)。

工程落地与核查(Jay)

实际系统怎么用

1. SWE-agent 评测流水线接入 将 CanItDelete 的 200 条纯删除任务直接引入内部评测套件,无需复现完整 retrofit 流程。关键是把「deletion recall」作为独立指标卡点:每轮 PR 或模型版本迭代,必须 deletion recall ≥ 75%(当前前沿最高 71.7%,意味着即使是 SOTA 模型也需要监控)。

# 最小可跑验证
git diff --name-only <base> <head>     # 找删除了哪些文件
git diff <base> <head>                 # 提取 diff hunk
grep -E "try:|except:|if False:" diff  # Guard-and-Go 启发式检测

若要自动化,加一条:「在 diff 中出现的被删函数/变量名,是否仍在合并后代码的任何位置出现」,出现则该删没删干净,标记 Guard-and-Go 风险。

2. Guard-and-Go 检测正则包(生产可直接用)

# Python AST 级别检测(比正则鲁棒)
import ast, re
GUARD_PATTERNS = [
    re.compile(r'try:\s*.*?\s*except'),
    re.compile(r'if\s+False\s*:'),
    re.compile(r'if\s+__debug__\s*:'),
    re.compile(r'return\s+None\s*#\s*deprecated'),
]
def has_guard(patch_hunk: str) -> bool:
    return any(p.search(patch_hunk) for p in GUARD_PATTERNS)

这套启发式是论文用的基线,精确召回依赖 AST 解析,实际落地建议以 AST 为主、正则为辅。

3. 监控看板 建议三条线一起看:① 传统测试通过率(越越高越好)② deletion recall(越高越好,越低说明越倾向于「该删不删」)③ Guard-and-Go 占比(越低越好,>15% 说明模型在用「包起来」代替「真删」)。任一条出现趋势逆转都值得追查。

坑在哪

  • 「显式给行号」只能救 prompt 层:GPT-5.6 Sol 给出行号后 deletion recall 几乎拉满,但总成功率仅 80.5%(因为开始「过度删除」)。这说明行号指令同时解锁了两个能力:「真删」和「乱删」——两者无法通过单一 prompt 指令解耦。需要配合后训练才能真正教会「该删几分」。
  • 纯 Python 覆盖,不能外推:论文语料全部来自 SWE-bench Verified 的 Python 仓库;JavaScript/Go/Rust 项目的 Guard-and-Go 比例未验证,迁移时要重新测。语言特性和测试文化差异(Python 的 try/except 习惯 vs Go 的错误返回)可能导致模式完全不同。
  • 后训练 recipe 未公开:论文 pilot 只证明「方向有效」,训练超参、数据配比、是否需要混合通用编辑数据防止灾难性退化——全未公开。工业级复现至少需要 3× 对照实验(删数据比例 × 是否加 RL)。
  • ground truth 依赖开发者 patch 质量:SWE-bench Verified 的开发者 patch 本身并不完全干净(可能包含次优解),用它们做 deletion recall 的分母,理论上可能继承 ground truth 的噪声。

可信度核查

声明 核查结论
deletion recall ≤ 71.7% 摘要级声明,可信(直接来自 abstract)
Guard-and-Go 占通过 patch 29.0% 摘要级声明,可信
Retrofit 34 条任务:63.2%→41.9% 摘要级声明,可信
CanItDelete 200 条最佳模型 ~20% 失败 摘要级声明,可信
prompt 给行号 success 80.5% 摘要级声明,可信
后训练 pilot 有效(具体数字未给出) abstract 明确未量化,原文未明确,轻标