论文方法:面向大语言模型(LLM)的异常代码生成系统(ExCoder)
原文标题:Retrofitting Code Using LLMs to Support Exceptional Behavior
第一署名单位:得州大学奥斯汀分校
具体领域:LLM代码异常修复
首次公开:2026年9月10日(北京时间,arXiv v1)
原论文:https://arxiv.org/abs/2609.10397
官方代码:https://github.com/EngineeringSoftware/excoder
官方数据:https://huggingface.co/datasets/EngineeringSoftware/Excoder
龙哥导读
代码大模型最容易展示的是“正常路径”:输入对了,函数跑起来,示例也漂亮。真正折磨维护团队的却是失败路径——空值、非法参数、状态冲突、下游调用异常,到底该在哪里检查、抛什么异常、会不会破坏旧行为?得州大学奥斯汀分校等机构提出ExCoder,把异常行为测试、静态作用域和真实执行路径整理成结构化上下文,再让模型补回throw、保护条件与try-catch。最强配置使用320亿参数Qwen2.5代码模型,在75个Java项目上把全部开发者编写测试的pass@10从基础提示的73.36%提高到86.51%,提升13.15个百分点;但人工检查也发现,约四分之一的“测试通过”结果仍不等价于原实现。
写代码助手的人常遇到一个尴尬:模型能迅速补出快乐路径,却对异常处理犹豫不决。空指针该在入口拦截,还是让下游自然失败?参数越界该抛非法参数异常,还是返回空结果?一个看似合理的保护条件,可能让测试变绿,也可能悄悄改变调用方依赖多年的行为。异常代码不是普通样板,它其实是接口契约最尖锐的一部分。
这篇论文的价值,在于没有把问题笼统地叫作“让大模型修Bug”。它提出一个边界很清楚的新任务:先从已有方法里拿走异常相关代码,再给模型目标方法和异常行为测试,让系统把缺失的异常逻辑补回来。输出是否合格,不靠语言模型自评,而是依次看能否编译、能否通过异常行为测试、能否通过普通回归测试,以及能否通过额外生成的工具测试。
这也延续了我们前面讨论大模型可靠性时的一条主线:模型的自信不是验收标准,可执行反馈才是。不过ExCoder又把这句话推进了一步。执行结果只有“红灯或绿灯”还不够,模型还需要知道红灯发生在哪条路径、当前位置能调用什么、异常该怎么构造。换句话说,真正有价值的不是把整仓库一股脑塞进上下文,而是把程序分析变成模型能够消费的局部契约。
一、它要补的不是一行throw,而是失败路径的行为合同
论文把异常相关代码分成几类:直接抛出异常的throw语句;守在throw前面的if或switch条件;以及捕获、转换或清理异常的try-catch结构。它们共同决定程序遇到异常状态时如何响应。对一个大型Java系统来说,这类代码数量多、分散广,还往往夹在正常业务逻辑中间,人工补齐既机械又容易漏。
ExCoder要求开发者提供异常行为测试。可以把它理解为一份可执行需求:当参数为null时,方法应该抛出某种异常;当对象处于非法状态时,调用应该失败;而普通输入仍须维持原有输出。测试没有直接告诉模型代码应该写在哪一行,却明确了外部可观察行为。
难点恰恰在“测试到实现”的距离。仅把测试源码和目标方法交给模型,模型可能知道要抛异常,却不知道现成异常构造器需要哪些参数,不知道某个字段是否可见,也不知道哪条语句只在特定异常测试下被覆盖。它生成的代码可能语法正确,甚至能通过目标测试,但在普通路径上误伤旧功能。
所以,这不是让模型把一句自然语言翻译成一行代码。它要回答四个问题:异常条件是什么、插入位置在哪里、当前位置有哪些合法符号、修改后是否保持原有行为。传统提示通常只覆盖第一个问题;ExCoder尝试把另外三个问题也变成显式上下文。
根据论文图2重绘。蓝色输入层给出目标方法与异常行为测试;静态分析提供异常构造器和可用符号,动态分析提供真实异常、行覆盖与普通测试;最后把这些信息写成结构化提示,生成代码后再经编译与测试验证。
二、五类上下文怎样拼成模型能用的“程序地图”
第一类上下文是可用符号。系统分析目标方法的作用域,列出可以直接使用的局部变量、参数、字段和方法。这个步骤看起来朴素,却直接影响生成代码能否编译。模型在长代码库里最常见的错误之一,就是调用一个语义上合理、当前位置却不可见或签名不匹配的名字。
第二类是异常构造器。异常行为测试通常只规定异常类型,但Java异常类可能有无参、字符串、原因链或自定义对象等多种构造方式。ExCoder通过静态分析收集目标异常可用的构造器,让模型不用凭训练记忆猜参数。它不是告诉模型唯一答案,而是缩小合法表达空间。
第三类是运行时抛出的异常。系统执行异常行为测试,记录目标方法内部哪些位置真实抛出了什么异常。这能帮助模型区分“业务上应该主动拒绝”与“下游偶然炸掉”。例如空值最后触发了一个底层空指针异常,测试却要求入口抛出非法参数异常,那么主动保护通常应该放在更靠前的位置。
第四类是逐测试的行覆盖。系统在目标方法里标注每个异常行为测试走过哪些行。覆盖信息不是一句抽象描述,而是一张执行路径地图:哪个测试进入了哪个分支,哪一段代码与期望异常最相关。论文将异常位置和覆盖信息以内联注释写回目标方法,模型读代码时就能把行为要求贴到具体位置。
第五类是非异常行为测试,也就是普通回归测试。它们的作用不是指导模型“怎么抛”,而是提醒模型“别把原来能跑的路径改坏”。这点非常关键。异常修复若只盯着异常测试,最容易出现的就是条件写得太宽:目标异常确实抛出来了,但一批合法输入也被挡在门外。
论文中的提示组合公式
带运行异常与覆盖注释的目标方法 ⊕ 异常行为测试 ⊕ 异常构造器 ⊕ 可用符号 ⊕ 普通回归测试
论文原式用M表示目标方法、E表示异常行为测试,用五个上下文变量表示运行时异常、逐测试行覆盖、异常构造器、可用符号和普通测试。注释函数把路径信息写回方法,连接符表示继续拼成结构化提示。重点不是字符串越长越好,而是不同信息以适合其语义的位置出现。
这里有一个很值得工程团队借鉴的细节:并非所有上下文都放在提示末尾的“资料区”。异常位置和覆盖被写回代码行旁边,因为它们和位置强相关;构造器、符号和测试则作为独立区块,因为它们是全局候选或约束。上下文工程的核心不是收集更多文本,而是让证据出现在模型做决定的地方。
三、304个评测方法是怎样构造出来的
论文从代码搜索网数据集中的Java项目出发,要求项目使用Maven、能够成功编译,并且公开使用条款允许研究者处理其数据。研究者寻找已经存在异常行为测试的方法,再把开发者原本写好的异常相关代码系统性移除。这样既有明确的行为测试,也有原始实现可供人工对照。
初始收集覆盖150个项目、1099个方法。之后只保留异常确实来自目标方法内部throw语句的样本,降到546个;再要求测试期待的异常类型与原实现一致,剩525个。若原始实现自己都过不了对应测试,样本就不能作为可达目标,因此继续剔除。
还有一类容易污染结果的样本:删除异常相关代码后,程序仍可能因为下游调用而自然抛出同类型异常。此时模型什么也不补,测试照样通过,任务就失去意义。论文把这类情况排除,最终得到75个项目、304个评测方法和640个异常行为测试。完整数据则含118个项目、518个方法、934个异常行为测试与100种异常类型。
根据论文数据章节整理。筛选不仅要求项目可构建,还要求异常来源、异常类型和原始实现均可验证,并剔除删除异常代码后仍会自然通过测试的样本,最终形成304个评测方法。
这套构造方式的优点是能大规模得到可执行任务,而且每个任务都来自真实开源项目。不过它仍是“从完整实现中挖洞再修补”的实验。真实遗留系统可能没有现成异常行为测试,需求也可能只存在于文档、工单或调用方约定里。论文证明的是:当行为测试已经存在时,程序分析上下文能显著帮助补回异常代码;它没有证明系统能自动发现所有缺失的异常需求。
四、主结果:提升13.15个百分点,究竟对应哪一层通过
实验覆盖五种不同规模和架构的代码模型,包括Llama、Phi、Qwen与一类闭源模型。每个任务采样10份输出,再计算pass@1、pass@5和pass@10:如果随机取k份候选,至少一份通过验证的概率有多高。这个口径很适合生成式系统,但也意味着pass@10反映的是候选池能力,不等同于一次调用就稳定成功。
最强结果来自320亿参数的Qwen2.5代码模型。以全部开发者编写测试为准,ExCoder的pass@1、pass@5和pass@10分别达到85.92%、86.18%和86.51%,相对基础提示分别提升12.56、12.82和13.15个百分点。标题中的13.15个百分点对应pass@10的最大提升,不是相对百分比,也不是所有模型的平均提升。
在编译这一关,完整方案pass@1达到95.10%。编译率高于测试通过率并不意外:知道可用符号和构造器,能明显减少不存在的方法、错误参数和作用域问题;但代码能编译,只说明它符合语言与类型规则,不说明异常条件、异常类型和旧行为都正确。
论文还加入自动生成的工具测试,形成比开发者测试更严格的一层。Qwen完整方案在“全部开发者测试加工具测试”上的pass@1是75.72%,比只看开发者测试低了10.20个百分点。这一落差非常有信息量:测试集越窄,模型越容易找到能过关但不等价的捷径。
论文表3的移动端整理。320亿参数Qwen2.5代码模型在全部开发者测试上的pass@10达到86.51%,比基础提示高13.15个百分点;加入工具生成测试后,pass@1降至75.72%,说明额外测试能揭露部分假阳性。
跨模型结果同样重要。不同模型的绝对水平有差异,但完整上下文相对各自基础提示均有提升。这表明收益并非来自某个模型偶然熟悉一种提示模板,而是程序分析信息普遍补上了代码模型的认知缺口。当然,论文评估的仍是特定模型版本和本地推理/服务配置,未来更强模型是否会缩小或放大收益,需要重新测量。
五、消融实验告诉我们:代码模型最缺的是“当前位置能做什么”
完整方案有五类上下文,哪一类贡献最大?在320亿参数Qwen2.5代码模型上,只提供可用符号时,全部开发者测试pass@1达到81.45%;只提供行覆盖时是72.11%。两者都明显提供帮助,但单独使用仍不及完整方案的85.92%。
可用符号强,说明代码生成首先受制于局部可执行性。大模型可能知道异常处理的一般模式,却未必知道这个方法里有哪些字段、辅助函数和对象可以用。把这些候选明确列出来,相当于把开放式写作题改成有边界的组件选择题。
行覆盖的作用不同。它不负责告诉模型有哪些程序接口,而是把测试与执行路径对齐。某个异常行为测试覆盖到哪几行,能提示保护条件应该靠近哪段逻辑。仅有覆盖仍可能缺少异常类型构造和合法符号,因此效果低于完整组合。
其他上下文看似各自增益有限,组合后却继续抬高结果。异常构造器减少类型层面的猜测,运行时异常揭示当前失败模式,普通测试约束回归行为。这是一组互补证据:作用域决定“能写什么”,路径决定“写在哪”,测试决定“写完是否还算对”。
依据论文表4整理。可用符号是最强的单一组件,行覆盖提供路径定位;完整五类上下文达到最高的85.92% pass@1。卡片只展示关键对比,完整表格还包含编译、异常测试与工具测试等口径。
六、自修复把pass@5推到96.22%,为什么仍不能直接放心合并
论文进一步把ExCoder与大模型自修复结合。第一次生成失败后,把编译错误或测试错误连同原上下文再次交给模型,最多迭代四轮。基础提示的全部开发者测试pass@5从73.36%提高到84.47%;ExCoder则从86.51%提高到96.22%。执行反馈确实能让模型修正不少语法、类型和行为错误。
但这组漂亮数字不能脱离评测循环理解。每一轮都把失败信息送回模型,意味着一次任务会产生更多推理调用和更多候选。它适合离线维护、自动补丁候选和有人审核的流水线,却未必适合对延迟敏感的即时补全。团队要比较的不只是pass率,还包括调用成本、修复轮数和人工审核负担。
更重要的是,论文对通过测试的代码做了人工语义核验。基础提示与ExCoder生成结果的等价率分别为54.61%和65.13%。ExCoder确实提高了约10.52个百分点,但65.13%也意味着:即便自动指标看起来很亮眼,仍有相当一部分代码没有完整复现开发者原本的异常语义。
研究者进一步统计假阳性。在基础提示已通过的223个目标方法中,25.56%存在语义问题;ExCoder已通过的263个方法中,这一比例是24.71%。问题包括条件太宽、条件太严、异常处理方式错误,以及为了让测试通过而破坏原有代码。两者假阳性比例接近,说明ExCoder最大的贡献是让更多任务达到可用候选,而不是彻底消灭规格不完整。
依据论文表5、表6及人工检查结果整理。四轮执行反馈将ExCoder的pass@5推到96.22%,但人工语义等价率为65.13%,已通过任务中仍有24.71%假阳性。测试闭环很强,却不能替代完整规格和代码审查。
七、和相似工作相比,ExCoder补上了哪一环
自动代码漫游器面向更广的仓库级程序改进:根据GitHub问题描述搜索代码、定位缺陷,再生成补丁,并利用抽象语法树和测试定位缩小上下文。它解决的是“一个真实问题单该改哪里、怎么改”。ExCoder把范围收窄到异常行为,把现成异常测试当作规格,并专门提取异常构造器、运行时异常和逐测试覆盖。前者更通用,后者在垂直任务上给出了更精细的程序语义提示。
代码强化学习(CodeRL)则把单元测试反馈用于代码模型训练与推理筛选。它通过强化学习中的评论器学习功能正确性,并在推理阶段重新采样。ExCoder不训练一个新模型,也不要求改变模型参数;它把静态和动态分析结果组织成上下文,直接服务现有代码模型。两者共同说明测试反馈重要,但路径不同:CodeRL把反馈吸收到模型能力里,ExCoder把反馈留在任务级工具链里。
论文相关工作还提到面向异常处理的推荐与生成方法。有些系统判断是否需要try-catch、选择try块语句或预测异常类型;有些方法通过知识驱动的多步提示生成异常处理。ExCoder的区别是先定义“异常行为测试驱动的旧代码改造”,再用真实执行路径约束具体插入位置。它不是从零写一个完整方法,而是在尽量保持正常行为的前提下补失败路径。
把这些路线放在一起看,会得到一个更清晰的演进:早期方法重在预测异常结构,通用代码模型重在生成能力,仓库级智能体重在搜索与修复流程,而ExCoder强调把测试、作用域、类型和运行轨迹组织成局部可执行契约。这可能比继续堆一段更长的自然语言提示更有工程价值。
八、代码、数据与复现:开放得比较完整,运行成本也要看清
官方仓库提供代码、数据与结果说明,并给出容器运行路径。环境包含Python、Java 8、Maven 3.8.3和llama.cpp;本地模型需要权重目录与图形处理器,部分实验还需要外部模型服务凭据。官方Hugging Face数据集可下载到工作目录,用于复现论文而不必重新跑完整数据构建或全部模型生成。
这套开放方式对研究复查有价值:读者可以先检查数据、结果和定性样本,再决定是否投入算力重跑生成实验。仓库也提供数据流水线、实验流水线、定性分析和论文表格生成入口。不过仓库页面没有检测到明确许可证,因此本文只提供链接,不把代码或资产打包再分发;实际使用前还应确认许可边界。
资源入口如下。官方代码:
https://github.com/EngineeringSoftware/excoder
官方数据与实验结果:
https://huggingface.co/datasets/EngineeringSoftware/Excoder
九、龙哥点评:AI写代码的下一道门槛,是把“通过”拆成多层合同
龙哥最认可这篇工作的地方,不是85.92%或96.22%本身,而是它承认模型缺少什么。异常修复不是语言风格问题,也不只是更大模型问题;它需要程序作用域、异常类型、真实路径和回归行为共同约束。把这些证据做成工具链,比指望模型凭参数记住一个陌生仓库更可靠。
对软件团队来说,ExCoder最现实的落点不是无人值守地修改生产代码,而是生成经过编译和测试筛选的补丁候选。它可以接在测试驱动开发、遗留代码治理、静态分析告警和代码审查之前,把工程师从重复的保护条件与异常构造中解放出来。最终合并仍需要更强测试和人工审查,尤其要检查条件是否过宽、错误是否被错误吞掉、异常类型是否影响上层调用。
对代码智能体团队,这篇论文还给出一个更普遍的产品启示:可靠性壁垒正在从“模型会不会写”转向“系统能否构造可执行上下文并分层验收”。模型更强之后,简单语法错误会减少,但仓库约束、业务语义和回归风险不会自动消失。谁能把测试、编译器、静态分析、动态轨迹和审核流程接成闭环,谁才更接近可用的软件工程代理。
当然,也要克制。当前实验只覆盖Java、Maven项目和目标方法内部直接抛出的异常;异常需求已经由测试给出,且每题会采样多份候选。跨方法资源释放、并发异常、分布式失败、异步回调、日志与可观测性等真实工程问题,都远比单方法throw或try-catch复杂。ExCoder证明了一个重要起点,不是异常处理自动化的终局。
十、龙迷三问
第一,如果团队没有异常行为测试,系统还能从哪里得到规格?工单、接口文档、调用方捕获逻辑、日志与线上事故都可能提供线索,但它们噪声更大,也缺少可直接执行的判定。未来系统需要先生成和确认行为测试,再进入代码修复。
第二,怎样降低“测试通过但语义不等价”的假阳性?答案不会只是多采样几次。更有效的方向包括变异测试、属性测试、调用方测试、边界值生成、差分执行,以及让审查模型解释新旧行为差异。测试集合本身必须比模型更难钻空子。
第三,这种方法会不会改变初级工程师的工作?会减少机械补保护条件的时间,但不会减少对异常契约的理解要求。工程师更需要会写行为测试、判断失败边界、设计可观测性,并看懂模型补丁对调用方的影响。AI接走的是部分敲代码动作,不是责任。
十一、总结
ExCoder把一个长期被当作细枝末节的问题,重新定义成可执行的代码生成任务:给定缺少异常相关代码的方法与异常行为测试,利用静态和动态程序分析构造上下文,再让大模型生成并通过分层测试筛选。320亿参数Qwen2.5代码模型在全部开发者测试上的pass@10达到86.51%,比基础提示高13.15个百分点;四轮自修复进一步把pass@5推到96.22%。
真正需要记住的边界同样清楚:人工语义等价率只有65.13%,已通过任务中仍有24.71%假阳性。这篇论文最重要的结论不是“大模型会自动补异常”,而是“程序分析上下文能显著提高候选质量,测试与人工审查仍必须守住最后一道门”。
主要参考资料
原论文:
https://arxiv.org/abs/2609.10397
官方网页全文:
https://arxiv.org/html/2609.10397v1
官方论文文件:
https://arxiv.org/pdf/2609.10397v1
相似工作“自动代码漫游器”:
https://arxiv.org/abs/2404.05427
相似工作“代码强化学习”:
https://arxiv.org/abs/2207.01780
本文为论文解读,不替代原论文或生产环境安全审查。关键数字来自论文v1正文与官方仓库说明,实际采用前请结合自身代码库与测试体系复核。