引用:Arianna Blasi, Alberto Goffi, Konstantin Kuznetsov, Alessandra Gorla, Michael D. Ernst, Mauro Pezzè, and Sergio Delgado Castellanos. 2018. Translating Code Comments to Procedure Specifications. In Proceedings of 27th ACM SIG- SOFT International Symposium on Software Testing and Analysis (ISSTA’18). ACM, New York, NY, USA, 12 pages. https://doi.org/10.1145/3213846.3213872.
摘要
过程规范在许多软件开发任务中很有用。例如,在自动生成测试用例的过程中,它们可以指导测试,充当能够发现错误并识别非法输入的测试先知。尽管实际上很少有正式的规范,但是对于开发人员来说,使用半结构化注释来记录其代码是标准做法。这些注释通过预定义标签和自然语言的组合来表达过程规范。本文介绍了 Jdoctor,该方法结合了模式,词法和语义匹配,将 JAVAdoc 注释转换为以 Java 表达式编写的可执行过程规范。在实证评估中,将 Javadoc 转换为过程规范时,Jdoctor 的精度达到 92%,召回率达到 83%。
我们还将 Jdoctor 衍生的规范提供给自动测试用例生成工具 Randoop。规范使 Randoop 能够生成测试案例,从而减少误报并揭示更多缺陷。
1 简介
程序规范表达了预期的程序行为,因此可以启用或自动化许多软件工程任务。在软件测试中,它用作确定哪些输入合法和哪些输出正确的预言 [4, 17, 31, 37]。在调试中,它标识错误的语句 [29, 54]。在代码合成中,这是合成器致力于的目标 [28, 47]。在重构中,它确保转换是一致的 [23]。在形式验证中,它区分正确和错误的实现 [53]。在运行时监视中,它可以识别异常行为 [15]。自动化(部分)任务需要工具可以操纵的机器可读格式。正式的规范很好地达到了这个目的。但是,很少有正式的规范,因为编写规范不是常见的软件开发实践。
相比之下,非正式规范很容易以自然语言编写的半结构化和非结构化文档的形式提供。程序员在过程文档中指定前提条件、后置条件和特殊行为是标准做法。 Javadoc 标记语言和工具于 1995 年在 Java 的第一个版本中出现,而类似但跨语言的工具 Doxygen 在 2 年后出现了。 Java IDE 会自动插入 Javadoc 注释的模板。最重要的是,程序员已经习惯于编写这些注释。结果,大量代码包含了非正式的 Javadoc 规范。但是,软件工程工具很少使用这些非正式规范。
本文介绍了 Jdoctor,它是一种从程序员已经创建的工件(即 Javadoc 代码注释)自动构建可执行过程规范的技术,而无需程序员更改开发实践或做额外的工作。可执行规范是可以执行的规范,例如,因为它是用编程语言而不是其他某种逻辑编写的。它也需要在过程上表达,而不是(例如)声明性地要求某些值的存在而不指示如何计算它。由 Jdoctor 生成的过程规范可用于多种软件工程任务中,例如自动生成测试用例,如本文所示。
1.1 应用:测试用例生成
自动生成测试用例可以降低开发成本和软件故障的社会成本 [5, 27, 44]。要自动生成测试用例,测试生成器必须生成一个输入,该输入使被测程序执行某些操作,并生成一个 oracle 以确定程序是否正常运行。
创建准确的测试 Oracle 仍然是一个未解决的问题。正如我们现在所解释的,这将导致测试生成器同时遭受错误警报(当被测程序正确时测试失败)和错过警报(当被测程序有错误时测试通过)。例如,假设自动生成的测试中的方法调用引发了异常。抛出此异常并不一定意味着所测试的方法有错误。存在以下可能性:
(1)引发的异常实际上揭示了实现缺陷。一个示例是在主题程序中触发断言,或者以其他方式无法完成所请求的操作。
(2)抛出异常是预期的、期望的行为。一个示例是在不可变对象上调用变量时发生的IllegalOperationException。
(3)引发的异常是允许的,但不是必需的行为。例如,如果二进制搜索例程的参数数组未排序,则其行为不确定。允许引发任何异常或返回与搜索到的元素是否在数组中不一致的结果。
在没有规范的情况下,测试生成工具可以使用试探法来猜测给定行为是正确还是不正确。一种试探法是,如果某个方法的参数之一为null,则以NullPointerException结尾的方法执行被认为是正确的,而不管是否可能将其视为可接受行为的特定要求 [40]。另一试探法是使用回归预言,它认为正确的行为是受测试软件的早期版本所暴露,并且认为任何其他行为是错误的,即使其他行为也为软件设计人员所接受 [19]。
这些猜测还会导致误报或警报遗漏,其中该工具无法报告暴露出错误行为的测试。当调用应该引发异常但失败时,或者调用引发异常但工具试探性地忽略该异常以避免错误警报时,可能会发生这种情况。
我们建议测试生成器应该利用通过 Jdoctor 自动生成的可执行过程规范来依赖程序员编写的非正式规范。可执行过程规范可以充当 oracle,从而使自动生成的测试用例更加有效,并且可以避免生成由易于出错的启发式方法派生的无效测试用例,如第 6 节所述。
1.2 贡献
本文记录的研究工作的主要贡献是 Jdoctor,它是一种将非正式 Javadoc 代码注释转换为可执行过程规范的方法。这种方法是自然语言解析和模式,词汇和语义匹配技术的新颖组合。第二个贡献是该方法的开放源代码实现,该方法可以复制本文中提到的实验并执行其他实验。第三个贡献是对该方法的多次实验评估,证实了该方法的准确性和实用性,从而减少了自动生成的测试报告的错误警报的数量。
我们通过实验评估了 Jdoctor 将 Javadoc 注释转换为过程规范的准确性。 Jdoctor 的性能明显优于其他最新方法。然后,我们证明 Jdoctor 不仅准确,而且在测试生成领域也很有用。与测试生成工具(Randoop)集成后,Jdoctor 的规范可减少误报。
2 代码注释分析
我们的工作受代码注释分析的启发并重用了其中的想法。与我们的分析代码注释技术有关的最接近的工作是@ tComment [50]、ALICS [41]和 Toradocu [24]。 @tComment 使用模式匹配来确定与参数空缺有关的三种前提条件属性。它可以达到很高的精度,并且可以召回与模式匹配的注释,但是这些模式非常狭窄,并且不能一概而论。 ALICS 使用词性标记,然后针对少量硬编码名词和术语进行模式匹配,从而根据代码注释生成过程前置条件和后置条件。同样,可概括性一点也不明显,并且需要为每个新域进行手动扩展。 Toradocu 是我们最初的工作,因此产生了 Jdoctor。它使用自然语言解析,并使用近似词典序法匹配将识别出的名词和动词与任意程序表达式和运算进行匹配。 Toradocu 仅在特殊情况下起作用,这仅占 Javadoc 注释的一小部分。 @tComment 和 Toradocu 都将其提取的属性应用于测试生成问题。 ALICS 尚未应用于任何开发任务。
Jdoctor 旨在结合并适当扩展这些技术中的最佳技术。模式匹配无法捕获 Javadoc 标签中自然语言的表现力和多样性,即使仅在我们案例研究中的程序上进行评估也是如此。 Jdoctor 与自然语言解析一起对模式匹配进行了补充,ALICS 和 Toradocu 也是如此。但是,与以前的工作不同,Jdoctor 不仅限于特定注释或规范的小语法,而且可以根据要分析的程序中定义的抽象来表达规范。与以前的所有工作不同,Jdoctor 添加了一种新颖的语义相似性概念。这样处理的注释使用的术语尽管在语义上是相关的,但与代码中的标识符不同。
简而言之,Jdoctor 会为各种过程行为生成规范:先决条件(与 Toradocu 不同)、常规后置条件(与@tComment 和 Toradocu 不同)和特殊后置条件(与@tComment 不同),并且比以前的工作更具通用性。与@tComment 不同,Jdoctor 不需要程序针对非法输入遵守特定行为(对于@tComment,则为与 null 相关的行为)。与@tComment 和 ALICS 不同,Jdoctor 不仅限于特定注释或规范的小语法,而且可以根据要分析的程序中定义的抽象表示规范。 Jdoctor 结合了文本模式匹配和自然语言处理(不同于@tComment),并引入了比 ALICS 和 Toradocu 更先进,更有效的新技术。与 ALICS 不同,Jdoctor 规范是可执行的,并且涉及数据结构,例如数组和集合以及数学表达式。
3 启发性的 Javadoc 例子
Java 开发人员的标准做法是使用 Javadoc 注释形式的非正式规范对代码进行注释。 Javadoc 工具会根据此类注释自动生成 html 格式的 API 文档。 Javadoc 注释由自由格式的文本组成,其中一些前面带有“标签”:用于前提条件的@param标签以及分别用于常规后置条件和异常行为的@return和@throws标签。现在,我们介绍一些例子。 从流行的开放源代码 Java 代码中摘录的 Javadoc 注释的示例,以及 Jdoctor 产生的输出,以突出显示此工作的挑战以及先前工作的局限性。
3.1 前置条件
@param标记可表征方法参数并声明调用者必须遵守的前提条件。 考虑以下注释,该注释来自 google Guava 的BloomFilter类。 Jdoctor 将此注释转换为可执行规范,该规范显示在 Javadoc 注释下方的框中。 这些子句与 Java 条件运算符“和”(&&)结合在一起以形成完整的过程规范。
Jdoctor 使用数学表达式(第二和第三@param注释)和复合条件(第三@param注释)正确处理注释。 Jdoctor 还了解到,有关第一个参数的注释未指定任何前提条件,因此不会产生有关参数漏斗的任何说明。
3.2 特殊的后置条件
@throws和@exception标记表示特殊执行的后置条件。 让我们考虑以下来自 Apache Commons Collections 库的ClosureUtils类的摘录。
第一个特殊的后置条件很简单,并且可以使用最新技术进行处理。 但是,只有 Jdoctor 能够理解与容器中元素相关的属性,如第二条@throws注释中所示。
Jdoctor 还处理更复杂的注释,例如以下注释,该注释来自 Apache Commons Collections 库的CollectionUtils类:
尽管此注释描述的是无效条件,但现有技术无法产生正确的断言。 Jdoctor 确定“两个集合”均指参数a和b,“比较器”指参数c。
3.3 普通后置条件
@return标记表示方法定期执行的后置条件,并且变化最大,因此最具挑战性。 这是三个其他技术无法处理的示例。
第一个示例来自 Apache Commons Collections 库中的BagUtils类:
Jdoctor 使用在类Bag中声明的常量产生后置条件。 将代码注释与要测试的代码中的方法或其他代码元素匹配并不总是那么简单,正如以下针对JGraphT库的Graph.addEdge()方法的注释所说明的那样。
Jdoctor 推断“此图”是指图实例本身,并且containsEdge方法可以检查后置条件。 Jdoctor 正确传递两个顶点作为此方法的参数以形成边。
将代码注释与代码元素匹配可能还需要一些语义相似性概念。 从同一个 Graph 类中获取以下示例。
由于 Jdoctor 新颖的语义相似性分析,Jdoctor 推断“未找到”在语义上与“容器中包含一个元素”的概念有关。 @tComment 和 Toradocu 缺乏任何语义相似性分析,而 ALICS 仅支持有限数量的手动定义同义词。
4 JDOCTOR
Jdoctor 将与构造函数和方法有关的 Javadoc 注释转换为可执行的 Java 表达式。它的主要见解是观察到,自然语言注释中的名词倾向于与代码中的变量或表达式相对应,注释中的动词/谓词与操作或方法相对应。 Jdoctor 处理 Javadoc 前置条件(@param)、普通后置条件(@return)和特殊后置条件(@throws或@exception)。
Jdoctor 的工作分为四个步骤:
(1)文本规范化(第 4.1 节):Jdoctor 对 Javadoc @ param、@ return、@ throws和@exception块标记中的文本进行预处理,以准备分析自然语言。此阶段包括几个文本转换,以方便后续步骤。
(2)命题识别(第 4.2 节):Jdoctor 使用自然语言解析器来识别注释中每个子句的命题(主语-谓词对)。
(3)命题翻译(第 4.3 节):Jdoctor 将每个识别的命题与 Java 元素(例如表达式或操作)匹配。此步骤是 Jdoctor 的核心,它依赖于模式,词汇和语义相似性匹配的组合。
(4)创建规范:Jdoctor 创建 Java 布尔表达式,该表达式对自然语言 Javadoc 注释进行编码。它用 Java 代码替换文本中的每个主题和谓词,并遍历解析树以创建合法的 Java 代码,包括方法调用,操作和布尔连接符(来自语法连接)。
4.1 文本规范化
Javadoc 注释很少是语法上完整的英语句子。例如,他们经常缺乏标点符号,具有隐含的主语和/或动词,并且将数学符号与英语混在一起。当前的 NLP 解析器不能总是处理程序员编写的 Javadoc 注释样式。 Jdoctor 通过在使用 NLP 解析器之前将其预处理为语法英语句来使文本可解析。这个阶段适当地扩展了先前的工作,并允许处理更多的 Javadoc 注释。
标点符号:Jdoctor 会在缺少句号时添加一个句号。它还消除了初始标点符号,这些标点符号是由于程序员(错误地)使用 Javadoc 注释中的逗号将参数或异常名称与其描述分开而产生的。
隐式主语:评论可能引用事先提及的主语。例如,典型的@param注释是“永远不会为空”。由于 Jdoctor 孤立地分析句子,因此每个句子都需要一个明确的主题。对于@param注释,Jdoctor 在注释文本的开头添加参数名称。 Jdoctor 还启发式地解析代词(例如“ it”),将其替换为注释中最后使用的名词。
隐式动词:某些注释具有隐式动词,例如“ @ param num,a positive number”。 Jdoctor 会根据被认为是主语的第一个名词是单数还是复数来添加“is”或“are“。
句子不完整:如果没有主从句,Jdoctor 会将从属从句转换为主从句。
词汇标准化:为了适应以后的模式匹配,Jdoctor 标准化了与 null、if 和 empty 模式有关的文本。例如,Jdoctor 将“ non-null”和“ nonnull”标准化为“ not null”。
数学符号:Jdoctor 将不等式转换为可以解析为形容词的占位符。例如,如果{@code e} <0,则 Jdoctor 将子句转换为表达式 e <0,并进一步转换为 e is LT0。
4.2 命题识别给定一个英语句子,Jdoctor 会识别<主语,谓语>对,也称为命题 [14],以及连接命题(如果有)的连接词或析取词。从自然语言句子中提取<主语,谓语>对称为开放信息提取(OIE)[1, 14, 18, 46]。
Jdoctor 首先执行部分 POS 标记 [41]。它将参数名称标记为名词,将不等号占位符(例如 LT0)标记为形容词。 Jdoctor 通过 Stanford Parser [34]完成 POS 标记过程,该过程产生代表输入语句的语义图(丰富的分析树)。语义图的节点对应于句子的单词,边缘对应于单词之间的语法关系。
Jdoctor 根据句子结构和图中编码的语法角色,识别构成主题的单词和构成谓词的单词。更精确地讲,给定在语义图中被标记为主题的单个节点(即单词),Jdoctor 通过访问以主题节点为根节点的子图并收集涉及复合类型关系的所有单词来识别完整的主题短语、状语修饰语、形容词修饰语、确定语和名词修饰语。这与 ALICS [41]完全不同,ALICS 仅使用由 Stanford Parser 提供的即用型 POS 标记,因此缺少所有其他词的信息。 Jdoctor 通过收集具有以下语法关系的单词来识别谓语:助词、系词、合词、直接宾语、开放式补语以及形容词、否定词和数字修饰语。
Jdoctor 从简单句子中提取一个命题,从多子句中提取多个命题。通过处理语义图中诸如“和”和“或”之类的语法连接的专用边缘,Jdoctor 可以正确支持多子句。在遍历图时,Jdoctor 会确定与正确的布尔连词或析取词相互关联的命题,这些命题反映了输入句子中的语法连词。
4.3 命题翻译
Jdoctor 通过连续应用模式匹配(第 4.3.1 节)和词汇匹配(第 4.3.2 节)的补充启发式方法并结合语义相似性分析(第 4.3.3 节)来翻译每个命题。 Jdoctor 使用第一个能翻译成功的方法。
本节描述单个命题的翻译。 Jdoctor 通过根据输入注释中的语法连接(“或”,“和”)合并组件命题的翻译来处理多个命题。
算法 1 显示 Jdoctor 如何处理 Javadoc 注释的(规范化)文本。文本可能包含多个命题。该算法独立地翻译每个命题。然后,将这些翻译(它们是 Java 表达式和操作)重新组合以创建完整的可执行规范。对@return注释会进行特殊重组。 Jdoctor 首先确定 guard、true 属性和 false 属性。例如,Apache Commons Collections 中ArrayStack.search()的返回注释是“对象堆栈中从 1 开始的深度,如果找不到,则为-1”。 Jdoctor 将“如果未找到”标识为 guard 对象,将“从对象的堆栈开始的基于 1 的深度”标识为 true 属性,即,当 guard 为 true 时保留的属性,而将“ -1”标识为 false 属性,即将 guard 评估为 false 时保留的属性。
将命题转换为 Java 表达式时(第 15 行),Jdoctor 尝试匹配每个主题并断言为代码元素。从直觉上讲,名词对应于对象,在源代码中以表达式表示对象,而动词对应于动作或谓词,在源代码中以操作符表示。
Jdoctor 首先分析命题的主题(第 17 行),然后尝试将该主题与代码元素进行匹配,该代码元素可以是方法的参数,类的字段或类本身。 Jdoctor 检索范围内所有代码元素的标识符和类型作为候选者,并寻找最佳匹配。例如,当在第 3 节中处理注释“如果比较器为空”时,Jdoctor 将主题“比较器”与类型为Comparator的方法的参数匹配。 Jdoctor 通过词法匹配来实现此任务,我们将在 4.3.2 节中对此进行说明。
如果 Jdoctor 找到主题的匹配表达式,它将继续寻找相应的匹配谓词(例如示例中的“ is null”)。更详细地讲,Jdoctor 检索主题范围内所有公共方法和字段的代码标识符,以作为可能的候选者。例如,在将主题比较器匹配为Comparator类型之后,Jdoctor 检索公共方法的完整列表和Comparator类的字段作为可能的候选对象。一旦确定了可能的候选者,Jdoctor 就会逐步利用一组启发式方法来推断可能候选者中谓词与代码元素的正确匹配:(i)检查谓词是否与一组预定义翻译(行)匹配 22),(ii)查找词汇相似的匹配项(第 24 行),(iii)根据语义相似性搜索匹配项(第 27 行)。
4.3.1模式匹配。 Jdoctor 使用模式匹配将常见短语(例如“是正”,“是负”和“是空”)映射到 Java 表达式片段> 0,<0 和== null。与以前的工作一致 [41, 51, 55],Jdoctor 采用了一组可扩展的模式,这些模式涵盖了原始类型、字符串和无效性检查的属性。模式匹配可以有效地转换常见模式,但不能处理特定于域的概念或特定于代码的行话,例如“如果在图中找不到顶点”中的概念“顶点”和“图形”。
4.3.2词法匹配。 Jdoctor 会根据直觉认为 Javadoc 注释中的单词在词法上类似于代码元素,从而尝试将主题或谓词与相应的代码元素进行匹配。 Jdoctor(i)根据驼峰案例惯例将代码候选词标记化为单独的术语,(ii)计算每个词与主题/谓词中每个单词之间的 Levenshtein 距离,(iii)选择 Levenshtein 距离最小的候选词,只要不超过阈值即可(默认阈值非常小(即两个),以尽可能避免错误匹配)。例如,在处理第 3 节中出现的注释“如果比较器为空”时,Jdoctor 将主题“比较器”与方法类型为距离为 0 的比较器的参数进行匹配。
Jdoctor 使用词法匹配的方式类似于 Toradocu [24]。这项技术优于简单的模式匹配(例如@tComment 实现的模式匹配)和简单的自然语言处理(例如 ALICS 中实现的模式)。它的局限性是基于这样的假设,即 Javadoc 注释中的名词应与代码中的标识符相似,但实际上并不总是如此。
4.3.3语义匹配。为了说明模式和词法匹配的限制,请考虑谓词“在图中未找到”。所需的转换为!graph.containsVertex(vertex)。模式匹配仅在有特定模式可用于处理这种情况时才有效。词法匹配失败,因为代码元素containsVertex在词法上与注释中出现的“在图中找不到”相近。 Jdoctor 语义匹配方法建立在以下观察的基础上,即语法上不同的术语可能具有紧密的语义。例如,代码中的方法containsVertex和注释中的“在图形中找不到”的概念在词法上是不同的,尽管它们的语义是相关的。由于单词之间的语义相似性,Jdoctor 能够处理模式匹配和词法匹配都失败的情况。 Jdoctor 使用单词嵌入,这已被证明是表示语义单词关系的强大方法。它将单词嵌入到高维向量空间中,以使单词之间的距离与语义相似性紧密相关,而与语法差异无关。 Jdoctor 为此使用了两层神经网络模型 GloVe。
Jdoctor 从谓词中删除自定义的停用词列表,并在使用 GloVe 计算语义相似度之前应用词形还原。词形还原将术语转换为它们的原形,例如,“playing”和“played”变为“play”,以减少域中术语的数量来简化语义匹配。默认情况下,Jdoctor 使用停用词列表,其中包括属于 Java 语言的冠词和单词,例如“ for”,“ do”和“ null”。但是,Glove 模型本身只能捕获单个术语的语义相似性。因此,它将报告语义上相关的术语“顶点”和“图形”。但是,大多数情况下,谓词和代码标识符由多个单词组成。例如,在JGraphT中,注释摘录“找到了顶点”应与方法containsVertex相匹配。为了一次比较多个单词,Jdoctor 使用了单词移动器的距离(WMD)算法 [30]。
WMD 测量两个文本片段之间的语义距离,因为注释中的所有单词(在这种情况下为[vertex,is, found])必须与代码元素标识符中的单词(在这种情况下为[contain,vertex])中的单词完全相同。与 Jdoctor 进行词法匹配时类似,它会选择给定阈值内语义距离最接近的候选词。
尽管提供了不同的匹配策略,但 Jdoctor 仅对主语匹配使用词法相似性。这种方法迫使 Jdoctor 将主语与具有很高精确度的代码元素进行匹配(尽管它可能会丢失一些匹配项)。这种保守的决定对于 Jdoctor 的性能至关重要,因为主语匹配为以后匹配谓词提供了范围。更广泛的(可能是错误的)范围将使谓词匹配的搜索指向完全错误的路径。
Jdoctor 生成单个 Java 布尔条件作为@param注释的翻译,并生成一对“预期的异常类型,Java 布尔条件”作为@throws注释的翻译。 @return注释的翻译不是 Java 的布尔值;取而代之的是,单个转换由对应于 guard、true 和 false 属性的三个 Java 布尔条件组成。
5 评估:翻译准确性
我们通过回答以下研究问题来评估 Jdoctor 的翻译准确性。
RQ1 在将 Javadoc 注释转换为过程规范时,Jdoctor 的有效性(准确率和召回率)如何?
RQ2 Jdoctor 的有效性与最新方法@tComment 和 Toradocu 相比如何?
我们根据准确率和召回率(信息检索任务的标准指标)来衡量有效性,例如将 Javadoc 注释翻译为过程规范。
准确率衡量正确输出相对于缺失和错误输出的比例。当 Jdoctor 产生与预期规范匹配的规范时,输出正确(C)。当 Jdoctor 不产生任何规范时,输出丢失(M)。当 Jdoctor 在没有预期的规格(W1)或与预期的规格不匹配的规格(W2)产生规格时,输出错误。准确率定义为正确输出数与输出总数之间的比率:
召回率衡量工具产生的期望输出的比例,它定义为正确输出的数量与期望输出的总数之间的比率:
我们的评估保守地认为部分正确的翻译是错误的。 例如,如果注释为“@throws Exception如果x为负或y为空”,则翻译“ x <0”被视为错误。
5.1 实验设置
为了进行评估,我们选择了 6 个维护良好的开源 Java 系统(请参阅表 1)。对于每个系统,我们(i)丢弃没有文档或文档数量有限的类,即 Javadoc 注释少于 5 个,以及(ii)忽略注释了从java.lang.Object继承的方法、getter 和 setter(其名称为方法的注释)以“ get”或“ set”开头)。表 1 中的“文档班级”列报告了满足每个学科这些条件的班级数。
然后,我们选择并手动分析了至少 10%的已记录类和方法:分别在“已分析类”和“已分析方法”列中。为此,我们应用了 PPS(概率正比于规模)抽样方法,选择每个类别的概率与该类别中的方法数量成正比。对于每个选定类中的每个分析方法,我们手动确定其基本事实-将 Javadoc 注释正确转换为可执行方法规范。为此,我们阅读了用英语表达的 Javadoc 注释,并编写了与每个@ param、@ return、@ throws和@exception标记相对应的可执行规范。至少有两位作者独立审查了每种翻译。
有时,与 Javadoc 标记对应的文本不能表示为可执行规范。一个示例是“如果在读取文件时遇到问题,则@throws IOException”。对于此类注释,我们不希望 Jdoctor 产生任何输出。我们丢弃了不包含任何可转换为可执行规范的注释的类,即不属于已分析类的列表。这给我们留下了 118 个分析过的类和 829 个 Javadoc 注释,我们为它们手动生成了真实的可执行规范。我们的实验比较了三种工具,所有这些工具均由程序员编写的非正式英语规范来创建可执行过程规范。
l @tComment [50]与三种不同类型的无效性规范的预定模板匹配。我们希望使用@tComment 实现,但是由于文档数量有限而无法使用(例如,其文件格式未记录)。我们发现,更容易根据发布的描述重新实现@tComment [50]。我们实现的@tComment 取得了与本文相似的结果,并公开供外部检查。
l Toradocu [24]通过 NLP 和字符串匹配的组合,从@throws 注释中生成特殊的后置条件。我们使用了来自 GitHub 的 Toradocu 实现。
l Jdoctor 是本文描述的工具。我们没有在比较中考虑 ALICS [42],因为即使在作者的支持下,我们也无法使该工具正常运行(有关此问题的更多信息,请参见第 8 节)。而且,ALICS 没有产生可执行的规范,因此很难进行比较。
5.2 准确度结果 (RQ1, RQ2)
表 2 报告了@ tComment、Toradocu 和 Jdoctor 在表 1 的主语类上的准确性。如第 2 节所述,Toradocu 不处理先决条件,Toradocu 和@tComment 都不处理正常的后置条件(表中的值 n.a. )。表格中的数据表明,Jdoctor 的精度可与最新方法相媲美,而 Jdoctor 的召回率远高于最新方法。
前置条件。 Jdoctor 处理不同类型的前置条件,而@tComment 仅处理通过简单分析处理的与 null 相关的检查。 @tCom 的精度比 Jdoctor 更高,这得益于其模式的特殊性,例如“may be null”,“must not be null”和“ @throws IllegalArgu.mentException if...is null”。但是,对@tComment 的简单分析会错过许多翻译,导致召回率比 Jdoctor 低得多。
正常的后置条件。 Jdoctor 是唯一足以表达@return注释的方法。由于常见返回条件的复杂性,@return注释的精度和召回率均低于其他标记。例如,注释“ @return 排序了的数组”声明后置条件为检查例程返回的数组是否已排序。该检查涉及对阵列的循环,在 Jdoctor 的当前实现中不支持此检查。其他难以检查的条件是,例如 Commons Math 中的FastMath类中的注释“ @return n + 1,如果没有发生溢出”,其警卫应检查是否发生了溢出。精度和召回率相对较低的另一个原因是后置条件通常包括两个或多个元素之间的比较。目前,Jdoctor 假定主题和谓词转换为单个 Java 元素,并且无法处理更复杂的情况,例如“如果a和c的大小差不等于 1”,“如果最大迭代次数小于或等于最小迭代次数”和“如果xval和yval大小不同”。
特殊的后置条件。与@tComment 相比,Jdoctor 具有更好的精度和召回率。 @tComment 仅翻译与空相关的注释,即在特定上下文中包含单词“空”的注释。例如,Jdoctor 可以翻译表达式“ aString 不为空”,而@tComment 则不能。在分析复杂的句子时,Jdoctor 的句子分析比@tComment 的模式匹配更有效。 Jdoctor 可以翻译由许多从句与特定的语法连接相联系的从句组成的句子,例如x和y为正或z为负,而@tComment 的模式匹配则不然。 Jdoctor 比 Toradocu 具有更好的精度和召回率。 Jdoctor 的更好结果归功于更精确的算法,可以将主语和谓词转换为 Java 元素,并获得了更多受支持的注释集(请参见第 4 节)。
92%的整体精度和 83%的召回率支持对 RQ1 的肯定回答:Jdoctor 在将 Javadoc 注释转换为过程规范方面有效且准确。我们的评估还支持对 RQ2 的肯定回答:与最先进的技术@tComment 和 Toradocu 相比,Jdoctor 的准确率更高。
5.3 不一致的规范
通过检查每个 Javadoc 注释的 Jdoctor 输出,我们手动检查了第 5.2 节中报告的 Jdoctor 的准确性。检查表明 Jdoctor 可以产生正确但不一致的规范。尽管目前只能通过手动分析 Jdoctor 的输出来找到许多这些问题,但我们计划扩展该工具以自动报告它们。现在,我们描述我们遇到的许多不一致之处。我们发现,许多开发人员编写的规范在逻辑上是不一致的-也就是说,它们无法实现或不能被客户端使用。这突出了将非正式的英语规范转换为可执行形式的附带好处:可执行形式中的不一致性比非正式表述并不明显,并且可以自动检查机器可读的规范。下面我们报告 Jdoctor 可能突出显示的六类不一致和错误。
一些规范会转换为并非总是定义良好的表达式,因为它们的求值可能会引发异常。例如,当 arg 为 null 时 arg.f> 0,这可能导致两种合理但不同的解释:arg.f 表达式始终具有一个值(即 arg!= null && arg.f> 0)或条件只要 arg.f 具有值(即 arg!= null || arg.f> 0),则为 true。两种解释都在实践中出现,尽管它们的含义对于客户和实施者而言都大相径庭。
许多规范具有冲突的前提条件和后置条件。 Commons Collections 中的CollectionUtils类的一个简单示例是
l @param a第一个集合,不能为null
l @throws NullPointerException 如果a为null
@throws子句指的是违反@param子句的情况,导致两种规则上的解释,尽管相互矛盾:(i)方法的域是所有值(允许程序员传递null),并承诺当传入null时会发生什么,或(ii)该域都是非空值,程序员不应传递空值。两种解释都是合理的,并且没有办法知道设计者的意图,但两者之间的区别是微不足道的:前者指出,维护者不得在将来更改实施,而后者则规定实施者可以在将来的实现中自由更改,依赖异常的客户代码将可能出错。
一些规范指示给定条件的多个结果。例如,让我们考虑
l @throws NullPointerException 如果arg1为null
l @throws IllegalArgumentException 如果arg2为负数
如果arg1为null且arg2为 0,则该例程需要同时抛出NullPointerException和IllegalArgumentException,这在 Java 中是不可能的。没有注意到不一致的客户可能会遇到意外的行为。这种不一致性的一个例子是JGraphT的KShortestPaths类。后置条件中也会出现类似的不一致之处。
Jdoctor 自动识别 Javadoc 中的一些很可能是由于复制和粘贴错误引起的错误[6]。例如,Guava 19.0 中CharMatcher.matchesNoneOf方法的文档指出:“如果此匹配器匹配序列中的每个字符(包括序列为空时),则返回 true”,同时应指出该方法不匹配序列中的任何字符。 Jdoctor 将拼写错误“匹配每个字符”正确转换为使用方法matchAllOf的 Java 表达式,并且此断言在运行时失败,突出显示了不正确的 Javadoc。
某些过程的 Javadoc 引用了不正确的形式参数名称。常见的原因是 Javadoc 从重写的实现中继承,并伴随着形式参数名称的更改。 HTML API 文档的读者会在 Javadoc 中看到一组名称,在方法名称中看到另一组名称。通常,这种对应是显而易见的,但并非总是如此,读者不必推论它。
Jdoctor 还自动在 Javadoc 中报告了一些拼写错误。例如,在 GraphStream 项目的Node类中,Javadoc 错误地说方法可能抛出IndexOutOfBoundException,而不是正确的IndexOutOfBoundsException。 Jdoctor 可能会报告问题,因为它没有在其类路径中找到类IndexOutOfBoundException。