想象一下你是一位经营着一家繁忙餐厅的主厨。大多数时候,你都在烹饪完美的佳肴,让顾客满意(这是正常行为)。但有时,事情会出错:烤箱坏了、顾客点了一个你没有的食材,或者送货迟到了(这些都是异常情况)。
在计算机编程的世界里,这些“出错的情况”被称为异常(exceptions)。开发者编写特殊的代码来捕捉并优雅地处理这些错误,以免整个餐厅被烧毁。
这篇论文就像是一支由食品检查员组成的团队,他们走访了 25 家不同的真实世界餐厅(软件系统),观察员工在日常演习(测试套件)中实际练习处理这些灾难的情况。
以下是他们的发现,简单分类如下:
1. “演习”与“实战”
检查员发现,虽然厨师(开发者)非常擅长练习如何烹饪完美的佳肴,但他们很少练习当烤箱起火时该怎么办。
- 数据统计: 在他们检查的每 100 个烹饪工位(方法)中,只有大约 21 个在演习期间真正遇到过问题。
- 类比: 这就像是一场消防演习,100 个人里有 79 个人甚至从未假装过火警铃响过。他们只是在继续烹饪。
2. 错误到底发生的频率有多高?
对于那些确实会遇到问题的工位,检查员观察了这些错误发生的频率。
- 数据统计: 平均而言,对于一个可能出现问题的工位,在他们尝试烹饪的 10 次中,只有 1 次 真的发生了问题。
- 类比: 想象一位可能会煎糊牛排的厨师。如果他煎 100 块牛排,他只会煎糊 10 块。剩下的 90 块都是完美的。大多数时候,“煎糊”是一个罕见的事件。
3. “罕见”灾难 vs. “常见”灾难
检查员注意到两类截然不同的“易发生灾难”的工位:
- “罕见”灾难(占 80% 的情况): 大多数可能出错的工位,几乎从不出错。例如,某个工位可能有一条规则:“如果顾客点了‘独角兽汉堡’,就大发雷霆。”但因为从来没人点“独角兽汉堡”,所以厨师从未需要过大发雷霆。
- “常见”灾难(占 20% 的情况): 有些工位一直在出错。想象一个工位规定:“如果顾客点了‘无麸质披萨’,就大发雷霆。”如果 90% 的顾客都点无麸质披萨,那么这位厨师就会不停地大发雷霆。
- 转折点: 在这些罕见的情况下,“大发雷霆”(抛出异常)实际上是该工位工作的常态!论文认为,仅仅因为一段计算机代码抛出了错误,并不一定意味着出了“故障”或“异常”。有时,这个错误本身就是预期的结果。
4. “隐藏”的错误
最有趣的发现之一是关于那些发生了但从未被“经理”(测试套件)察觉到的错误。
- 类比: 想象一名副厨掉了一个盘子,但主厨戴着降噪耳机,没听到声音。副厨迅速把盘子捡起来并继续烹饪。经理认为一切正常,但盘子确实掉过。
- 现实情况: 研究发现,许多错误发生在代码内部,被安全网(
try/except 代码块)立即捕捉并处理了,因此从未到达顶层测试。测试程序甚至不知道这些错误曾经发生过。
5. “昂贵”的安全网
最后,论文指出了能量的浪费。
- 类比: 想象一位厨师在炉灶旁放了一个巨大、沉重且昂贵的灭火器,以防万一。但他每年只用一次。这个灭火器又重又占地方。
- 建议: 论文建议,对于那些错误发生得极其罕见(如“独角兽汉堡”示例)的工位,与其准备一个沉重的灭火器,不如直接在烹饪前检查订单是否有效。这会让厨房运行得更快、更高效。
总结
这篇论文告诉我们:
- 大多数错误是罕见的: 我们很少测试“如果出错怎么办”的情景,因为在现实生活中它们并不经常发生。
- 有些错误是正常的: 对于某些特定任务,所谓的“失败”实际上是系统工作的标准方式。
- 我们会漏掉隐藏的错误: 许多错误发生后被立即修复,因此我们的测试甚至无法感知它们的存在。
- 我们可以更高效: 有时,我们正在为几乎不会发生的问题使用沉重且昂贵的防护机制,我们可以将其更换为更简单的检查。
作者建议,我们需要更好的工具来帮助厨师(开发者)练习那些罕见的灾难场景,并弄清楚哪些安全网实在是太沉重、不值得随身携带。
技术摘要:异常行为:它们的测试频率如何?
问题陈述
异常(Exceptions)是编程中一种基础的构建块,用于处理预期会发生但不频繁发生的错误情况,从而允许开发者避免使用过多的 if/else 检查来使代码变得臃肿。虽然现有的研究和最佳实践表明,高质量的测试套件应该同时覆盖正常行为和异常行为,以防止回归并捕捉 Bug,但经验证据表明,开发者主要测试的是正常(“快乐路径”)行为。
当前文献中存在一个关键空白:先前的研究几乎完全集中在异常测试上——即那些显式断言异常会传播到测试层级的测试(例如使用 assertRaises)。这些研究并未考虑到那些在运行时被抛出但在到达测试程序之前就被代码内部(通过 try/except 块)捕获并处理的异常。因此,目前尚不清楚在现实世界的系统中,异常行为实际上被测试套件执行的频率如何,包括那些不会传播到测试框架中的异常。此外,从测试的角度来看,异常在运行时抛出的频率——是从罕见发生到异常成为常态的行为——尚未得到深入探讨。
研究方法
作者通过分析 25 个真实世界 Python 系统的测试套件进行了实证研究。该数据集包括了一系列流行的第三方库(如 Flask、Requests、Pylint)和标准库模块(如 email、pathlib、argparse),涵盖了 5,372 个已执行的方法、1,790 万次方法调用以及 140 万次抛出的异常。
该研究采用了以下方法论:
- 插桩(Instrumentation): 作者利用基于 Python
sys.settrace 函数的动态分析工具 SpotFlow 对测试套件进行插桩,以在方法层面监控执行情况。
- 数据收集: 在测试执行期间,插桩后的系统记录了:
- 哪些方法被执行了。
- 特定方法是否在运行时抛出了异常。
- 每个方法的总调用次数。
- 导致异常的调用次数(异常抛出调用)。
- 分析: 收集到的数据被用于回答三个针对不同粒度的研究问题(RQ):
- RQ1(方法级): 有多少方法在运行时抛出异常?
- RQ2(调用级): 在抛出异常的方法中,实际导致异常的调用频率是多少?
- RQ3(系统级): 异常抛出的方法和调用在不同系统之间是如何变化的?
关键结果
RQ1:异常抛出方法的普遍性
- 21.4% 的已执行方法(5,372 个中的 1,150 个)在测试执行期间至少抛出了一个异常。
- 剩余的 78.6% 是无异常的。
- 异常抛出方法被发现比无异常方法更复杂且被更频繁地执行。在中位数上,异常抛出方法接收到的调用次数是无异常方法的 4 倍,执行的路径是无异常方法的 3 倍。
- 研究识别出了 200 种不同的异常类型。大多数是通用的(如
ValueError、TypeError),而特定异常通常由单个方法抛出。
RQ2:异常抛出的频率
- 在会抛出异常的方法中,异常抛出调用的中位频率为 10%(10 次调用中发生 1 次)。
- 异常抛出频率的分布呈现高度偏态:
- 罕见(≤10%): 50% 的异常抛出方法属于此类。
- 偶尔(>10% 至 ≤50%): 28.4% 的方法。
- 常见(>50% 至 <90%): 9.6% 的方法。
- 几乎总是(≥90%): 11.8% 的方法。
- 观察结论: 接近 80% 的抛出异常的方法其发生频率较低。然而,有相当一部分少数派(约 20%)频繁地抛出异常,并且在某些情况下(例如特定的 Pylint 方法),抛出异常是其“预期”或“正常”的行为(在 100% 的调用中发生)。
RQ3:系统间的差异
- 25 个系统中的 22 个包含比异常抛出方法更多的无异常方法。
- 25 个系统中的 19 个每个方法的异常抛出调用中位数比例低于 30%。
- 系统之间存在显著差异;例如,
csv 库有 53.3% 的异常抛出方法,而 Error Corrector 仅有 5.6%。
意义与启示
本文认为,由于忽略了在本地处理的异常,当前对异常行为测试的理解是不完整的。研究结果挑战了“异常抛出总是‘异常’或罕见事件”的假设。
作者针对研究人员和从业者提出了三个主要启示:
开发新型测试工具:
由于大多数异常抛出方法执行频率较低(通常 <10% 的调用),现有的测试套件可能会留下大量的错误处理代码未被测试。作者建议开发能够识别覆盖异常情况测试的工具,包括那些异常在本地被捕获且不会传播到测试层级的场景。此类工具可以提醒开发者错误处理逻辑中缺失的覆盖率。
重新评估“异常”行为:
本研究强调,异常抛出并不一定意味着“异常”。对于某些方法(例如属于“几乎总是”类别的方法),抛出异常是标准控制流的一部分。从事异常行为测试的研究人员必须区分哪些方法中的异常代表 Bug/边缘情况,以及哪些方法中的异常是预期的结果。未能做出这种区分可能会导致对测试质量或 Bug 检测能力的错误评估。
重构高开销的 try/except 块:
作者指出,在 Python 中,EAFP(“请求宽恕比请求许可更容易”)的编码风格经常导致 try/except 块的使用。虽然在异常罕见时效率很高,但捕获异常的计算开销很大。研究识别出的那些频繁抛出异常的方法(例如在 90% 以上的调用中出现 KeyError 或 AttributeError)是进行重构的强力候选对象。将这些替换为显式检查(例如 if key in dict)可以显著提高性能。
结论
这项实证研究提供了对现实世界 Python 系统中异常行为被测试频率的首次全面观察,同时兼顾了传播型异常和本地处理型异常。结果表明,虽然大多数异常抛出方法执行频率较低,但代码库中有相当一部分依赖于异常作为一种频繁的控制机制。作者得出结论,需要更好的工具来支持这些行为的测试,并且可以通过重构那些异常抛出过于频繁的代码模式来实现性能优化。
每周获取最佳 computer science 论文。
受到斯坦福、剑桥和法国科学院研究人员的信赖。
请查收邮箱确认订阅。
出了点问题,再试一次?
无垃圾邮件,随时退订。