翻译-Automatic detection of Long Method and God Class code smells through neural source code embeddings

发布时间 2023-05-31 18:05:56作者: 菜鸟C_5022

Automatic detection of Long Method and God Class code smells through neural source code embeddings

通过神经源代码嵌入自动检测 Long Method 和 God Class 代码异味

Aleksandar Kovaˇcevi´c , J 6elena Slivka *, Dragan Vidakovi´c , Katarina-Glorija Gruji´c , Nikola Luburi´c , Simona Proki´c , Goran Sladi´

摘要

​ 代码异味是代码中经常损害其质量的结构。 手动检测代码气味具有挑战性,因此研究人员提出了许多自动检测器。
​ 传统的代码气味检测器采用基于度量的启发式方法,但研究人员最近采用了基于机器学习 (ML) 的方法。本文比较了多个基于 ML 的代码异味检测模型与多个基于度量的启发式算法检测 God Class 和 Long Method 代码异味的性能。
​ 我们评估 ML 的不同源代码表示的有效性:我们评估传统使用的代码度量对代码嵌入(code2vec、code2seq 和 CuBERT)的有效性。
​ 这项研究是第一个据我们所知评估预训练的神经源代码嵌入对代码气味检测的有效性的研究。 这种方法帮助我们利用了迁移学习的力量——我们的研究是第一个探索从代码理解模型中挖掘的知识是否可以转移到代码气味检测的研究。
​ 我们研究的第二个贡献是在相同的大规模手动标记 MLCQ 数据集上系统地评估代码气味检测方法的有效性。 几乎每一项提出检测方法的研究都在该研究独有的数据集上测试了这种方法。因此,我们无法直接比较报告的性能来得出性能最佳的方法。

1、介绍

代码异味是代码中经常损害其质量的结构。传统的代码坏味检测大多是基于度量的启发式方法,研究人员提出采用基于机器学习的方法。本文比较多个基于机器学习的代码异味检测模型与多个基于度量的启发式算法 检测God Class 和 Long Method代码异味的性能。

现有检测器的问题:现有检测器之间的低一致性和对用户定义参数的强烈依赖,没有设计出原则性的定义方式。

使用机器学习算法检测源代码的主要挑战:创建适合应用ML算法的已分析源代码片段的矢量表示。

​ 考虑到源代码与自然语言有相似的统计特性,且大量实验证明自然语言处理在源代码分析中是切实可行的。提出NLP(CuBERT)在自动代码气味检测问题方面的应用。2020年Kanade等人受到自然语言与训练的上下文嵌入成功的启发,提出CuBERT,具有较为先进的性能,同时需要比其替代方案更短的训练呵更少的标记示例。

​ 另一方面考虑到源代码与自然语言之间存在差异,源代码被写成形式语言,与自然语言相反,它表现出最小的歧义,且相同的句子被反复使用,切包含长语义单位。由于这些原因,提出利用代码的结构性质的表示,例如code2veccode2Seq,将代码表现为抽象语法树上的一组组成路径来利用编程语言的语法结构。

​ 基准方法:Rabin,Mukherjee,Gnawali和Alipour (2020)观察到很少有人工设计的特征可以执行到非常接近高维的code2vec嵌入特征,因此可以将手工制作的特征作为基准数据集。由于源代码度量通常是用于代码气味检测的手工制作的功能,因此将人工制作的特征来训练的ML模型视为基线方法。

​ 研究旨在:评估预先训练的神经源代码嵌入对代码其为检测任务的有效性。因此,在MLCQ数据集上进行试验。其中手动检查每个代码样本的代码气味的存在和严重性。并且比较了一下几种用于源代码矢量表示训练的ML分类器的性能。
1、三种神经源代码嵌入 code2vec code2seq CuBERT
2、源代码度量向量
3、基于启发式检测器的源代码度量和投票的向量

包括多个基于启发式的检测器作为基准方法 如图一

image-20221010145218923

​ 处理的目标代码气味是上帝类和长方法。选择原因是这两种代码坏味较为普遍且有较大的负面影响。常与其他种类的代码坏味一起出现。

​ 二分类问题,将代码样本分为臭(正例)和非臭(负例)。使用CuBERT源代码嵌入训练的ML分类器获得了上帝类(F1=0.53)和长方法(F1=0.75)检测的的最佳性能。上帝类检测的挑战性大于长方法,是因为所使用的数据集中类级标注的不一致。可以通过以NLP字段定义明确的划分来缓解类和方法之间粒度的不一致。上帝类检测器需要处理更多的输入可变性。

​ 这项研究是第一个采用预训练做神经源代码编码来检测代码坏味。
​ 采用预训练有两个原因,首先度量提取的过程较为耗时,且不易扩展。通过深度学习自动推断特征可以获得更好的性能。其次采用源代码做embeding可以利用迁移学习的能力。由于手动标记代码气味成本高周期长,因此与NLP其它领域的数据集相比,代码气味的数据集很小。很难在小数据集上训练高质量的ML模型,因为其容易过拟合。解决过拟合的一种方法是迁移学习:利用不同学习任务的共性来实现知识在其中的转移。
​ 在本项实验的设置中,利用从标记数据丰富的代码中学习方法名称的知识,然后使用学到的功能来捕获代码的语义,以进行代码气味检测。其中标签为scarce1

Sharma,Efstathiou,Louridas和Spinellis (2019)等人的一项研究利用迁移学习进行代码气味检测。训练DL模型,在C#代码中进行代码气味检测,并通过Java代码对其进行评估。他们使用了启发式工具自动标记代码从而获得大规模的标记数据集。
本论文工作与他们的研究在两个方面有区分:1、没有用不同的编程语言在相同的代码气味检测上训练DL模型,而是转移了代码理解模型所捕获的知识(迁移学习)。2、其次,使用一个完全手动标记的代码气味数据集,优势在于手动标记的样本可以衡量实践中的实用性。

​ 本论文工作是第一个报告多个代码气味检测器在大规模手动标记的MLCQ数据集上的性能的研究。拥有高质量的基准数据集来比较不同的方法对于任何应用程序领域开发ML算法至关重要。本文强调了当前代码气味检测的情况。几项值得称赞的研究创建了可以作为基准的数据集,但是这些数据集中大多数都存在重现性困难,比如由于半自动标记导致的标签嘈杂,或者代码气味分布不切实际。MLCQ数据集是当前的最佳作为基准数据集的选项。

​ 本文的一个重要贡献是 第一个在单个基准数据集上评估许多传统使用的代码气味检测方法的方法。

本文贡献
我们评估了三种最先进的神经源代码嵌入: code2vec,code2seq和CuBERT在God类和Long方法检测任务上的有效性。这项研究是第一个评估预先训练的神经源代码嵌入对代码的有用性的研究。
本研究首次对MLCQ数据集进行代码气味检测。几乎所有提出检测方法的研究都在该研究独有的数据集上测试了这种方法。因此,我们无法直接比较报告的性能以得出最佳性能方法。
这项研究是第一个比较许多传统使用的代码气味检测方法在相同的大规模,手动标记的数据集上的性能。先前的研究报告了在实质上不同的数据集上的性能,从而禁用了它们的精确比较。(不能进行性能比较的原因)
我们提供了一个全面的复制包研究人员可以用来复制我们的实验。

2、背景工作

2.1 定义目标代码气味

​ 上帝类代码气味表示一个实现很多责任的类,并且包含系统逻辑的很大一部分。好的软件结构应该是高内聚低耦合。上帝类通常是有很多不互联的属性和方法的巨大类,表示该类有较低的内聚性。并且与系统中许多其他组件相关联,耦合程度很高。

​ 长方法代码气味发生在方法冗长时,这些方法倾向于集中类的功能,并且处理来自其他类的大量数据。长方法违反了面向对象编程的单一责任原则。

​ 去除代码气味可以有效提高代码的整体可维护性,上帝类具有最显著的负面影响。并非所有类型的气味对于故障检测都有帮助--上帝类和长方法非常有效。并且这两种代码气味经常与其他气味一起出现。

2.2 基于度量的启发式检测器

启发式方法的局限性

​ 基于度量的方法通过计算一组特定的代码度量并为每个代码度量应用预定义的阈值来检测代码气味。但存在一定的局限性。
​ 现有的工具存在较高的误报率,许多代码气味候选增加了开发人员的工作量,需要实际分析解决实际问题。可能会超过重构的预算。

启发式方法如何应用于基于ML的代码气味检测方法

​ 而基于度量的方法存在一个问题,选择何种度量指标,这些阈值的选择极大地影响气味检测的性能。没有标准的选择方法。也不存在一个普遍标准,代码度量的定义缺乏标准化。

启发式方法为什么要作为基线方法

​ 为了克服启发式方法的局限性,研究人员开始尝试基于ML的气味检测。但是没有建立用于比较不同技术的基准,并且一些研究发现启发式技术的性能优于ML。
​ 因此,需要将基于启发式的方法作为基线方法以显示是否调用ML方法以及何时调用ML方法来检测代码气味。

2.3 使用代码度量指标作为特征来训练ML分类器

​ 本节讨论了传统的基于ML的代码气味检测方法的优缺点。

​ 为了训练用于气味检测的ML模型,首先需要先将数据集中的代码片段表示为固定长度的实数向量,在大多数基于ML的代码气味检测方法中,研究人员通过提取由专家手工设计的特征(即代码度量)来表示代码片段。

​ 本文的基线方法:使用手工制作的特征训练ML模型
对比方法:自动推断特征上训练的ML模型(迁移学习)

人工特征的局优缺点

​ 优点:能够分析和解释使用它们训练ML模型的行为。在执行ML误差分析时依赖于这些特征
​ 缺点:耗时、需要专业知识,且在分析为特定软件工程任务而设计的功能的环境中没有作用。

这些特征激发了采用自动推断特征的方法

手工制作特征的替代方法:通过深度学习自动推断有用的特征。可能会带来轻微的性能改进但是会牺牲掉模型的可解释性。因此需要将手工特征上的训练的模型作为基线,以估计性能改进是否增加的模型复杂性是合理的。

将神经源代码嵌入用于代码气味检测任务

2.4概述代码嵌入及其在不同软件工程任务中的应用

为什么选择code2vec code2seq CuBERT嵌入进行代码气味检测实验。
当前开发源代码模型的实践可以分为三类:捕获代码结构性质 、NLP模型 、代码更改模型。

​ 结构嵌入对代码的AST、数据流、控制流图进行建模。目前众所周知的由公共工具支持的嵌入模型是code2vec和code2seq。研究人员使用这些嵌入解决各种软件工程任务。捕捉代码结构性质的嵌入向量有利于上帝类和长方法的检测。

​ NLP上下文嵌入捕获文本语义。codeBERT(2020)在自然语言代码搜索和代码文档生成任务上取得了最先进的成果。CuBERT(2020)是一个用于代码理解的BERT模型,在多个软件工程基准任务上优于其他嵌入。由于代码语义对上帝类和长方法检测至关重要,因此将CuBert应用于这些任务。

​ 模型代码更改的嵌入编码这一方法,在更改前后存在语法结构的差异。在本文中没有考虑,因为MLCQ数据集包含来自792个项目的样本,提取所需要的嵌入非常耗时。

3、实验设计

为了比较算法性能,使用相同的实验设置应用到所有的代码气味检测算法。

3.1 使用MLCQ数据集来训练和评估模型的原因

目前大多数用于检测代码气味的数据集都是基于启发式的工具或者某种合成生成的数据产生的数据集,然而自动注释的过程不保证数据集不包含错误标记的实例。基于启发式的工具容易产生高误报率。

Fontana等人在Qualitas语料库中手动标记了四种代码气味的情况,其注释过程使用了五个基于启发式的检测器来识别一组代码气味候选,然后规定三分之一为正例,其余为负例。Di Nucci等人认为人为标记的代码气味的正例和负例的比率与实际存在差异。

MLCQ数据集是当前最大的公开可用数据集,多个注释器手动分析了每个代码实例的气味,该数据集度与其他现有的代码气味的数据集的优势在于,提供了可靠再现的所有信息。每个实例都是手动注释的。

3.2 描述该数据集的细节

MLCQ数据集包括792个与行业相关的Java开源项目的代码样本,为四种代码气味(上帝类,长方法、特征依赖和数据类)注释了近15000个代码样本,包括:
气味的类型和严重程度(无、轻微、主要、严重)
检索代码样本和包含他的软件项目的关键信息(存储链接、修订和源码气味的具体位置)
提供注释的开发人员的 ID

本文使用的是可用的代码示例:2408个类级别实例和2255个方法实例

3.3 解释实验设计和选择的性能度量

​ 使用随即分层抽样将样本80%作为训练集,20%作为测试集。表1列出每个部分中正负样本的比例。image-20221013101507376

​ 本文是研究一个二分类问题,将代码样本分类为臭味(次要、主要、关键)或者无臭味(无),有臭味就是正例,无臭味就是负例。正例是较少数。用image-20221013101933356
进行评价。

在本文中使用多个源代码向量表示来训练ML分类器。
1、三种神经源代码嵌入:code2vec code2seq CuBERT
2、代码度量的向量
3、源代码度量的向量和启发式检测器的投票

​ 在每个向量表示上训练以下ML分类器:随机森林、Bagging(使用支持向量机作为基础算法)、梯度提升树(XGBoost)库中的分类实现。对训练集进行分层5倍交叉验证并使用贝叶斯优化策略来优化这些模型的超参数。
​ 由于数据高度的不平衡,我们从不平衡学习包中尝试了几种采样策略。因此训练每个ML分类器:
1、使用SMOTE(合成少数群体过采样技术)对少数群体类别进行过采样的数据集。
2、使用邻域清理规则下采样的数据集,以从数据集中删除噪声样本
3、使用SMOTE少数群体过采样和编辑最近邻欠采样(SMOTEENN)的组合策略重新采样的数据集。
4、原始数据集(不进行重采样)

​ 重采样时仅对训练集做重采样,保持验证数据(用于优化模型的超参数)和测试集数据不变。为了确保实验的可重复性,在实验中固定了随机种子值。

4、方法

解释实验中每种代码气味检测方法的细节。

4.1 基于启发式的检测

根据Bafandeh Mayvan et al. (2020),并消除一些无法精确度量标准定义的规则,参考用于度量提取的工具的文档中提供的度量定义,定下了表2作为基于启发式的基线方法实施的最终规则集。image-20221013145054836

表3和表4显示了类级和方法级度量定义以及我们用于计算启发式基线的度量工具。

image-20221013163655929

图2和图3显示了研究人员使用每个度量来指定代码气味的频率。研究人员主要通过大小和复杂性来表征上帝类,有些还包括耦合和内聚的度量。主要通过其大小来描述长方法,包括复杂性。

image-20221013163722269

image-20221013163737897

在选择工具提取度量值时,需要施加某些要求,MLCQ中包含792个项目,我们需要工具来进行批处理计算并以可解析格式导出数据。还寻找了通过静态分析提取方法的工具,避免编译问题。

在实验中,测试了单个规则及其三个聚合的性能。规则的数量表示为k,将实例 x 分类为具有被认为的气味的规则 k 的指标函数,表示为 lk(x),有气味为1,无气味为0。
image-20221013164559991

评估以下三种情形:

ALL-若所有规则都为1,则该代码示例是有气味的

ANY-若任何单独的规则将其归类为某种气味,则改代码示例是有气味的

Weighted vote-计算某代码示例是有气味的概率,作为单个分类器的加权投票

4.2 使用代码度量作为特征训练的ML分类器

预处理代码片段,将其作为源代码度量的向量进行重新发送,用于训练ML分类器。

使用CK Tool和RepositoryMiner度量提取工具提取代码度量,用来促进基于启发式检测器的实现。两种工具都计算相同度量的情况下,选择倾向于基于启发式的检测器中使用的度量。使用46个类级指标检测上帝类,26个方法级指标检测长方法。

预处理:

1、使用标签编码对分类的变量进行编码
2、使用-1表示无法为代码样本提供分析计算的特定度量的值
3、使用 z-归一化 对度量值进行归一化

称这种方法为ML-metrics(在代码中)

此外,测试另一个代码片段表示形式。通过将样本的代码度量值样本的启发式投票向量连接起来构造的向量,对于每个考虑的启发式,启发式投票的向量的值为0(代码段不是启发式的代码气味)或1(代码段是启发式的代码气味),这种方法表示为ML_metrics&votes。

4.3 使用源代码嵌入作为特征训练的ML分类器

本节提供一种将源代码表示为固定长度的实数向量的替代方式:通过训练深度神经网络将某种标识表示为嵌入向量(如方法的名称)。要求用于学习的源代码要保留其语义,也就是语义上相似的源代码片段应该映射为相似的向量。有三种机器学习的模型:code2vec、code2Seq、CuBERT

4.3.1 Code2vec

Alon等人2019提出,输入是代码片段的向量表示,输出是相应的标签。
将代码段表示为AST的无序路径集合,由此得到模型的输入,具体过程:
1、解析代码片段以生成其AST
2、提取所有的成对的AST叶子之间的路径。单个路径是AST中两个叶节点的元组以及他们之间的路径(AST的有序序列)
3、为每个路径分配一个权重,该权重与其对模型输出的重要性相对应。注意力机制被用于了解路径的重要性。
4、代码片段的单个向量表示被计算为路径向量的加权平均值。

用code2vec及那个数据集中的每个方法表示为代码其为检测任务的固定长度向量。基于Alon2019的论文进行实验:
1、首先使用Java语言的路径提取器构建方法的AST路径;
2、将提取到的AST路径输入到Alon等人训练的开源java模型中。在模型中删除最后一个softmax层,并使用特征向量(语义特征)作为模型的输出,384dim。

4.3.2 Code2seq

Code2seq是一种学习源代码矢量表示的编码器-解码器模型。模型的输入是代码片段的向量表示,输出是相应的自然语言序列。(如方法名)

和code2vec一样,输入是从代码片段的AST构建得到的。
1、解析一个代码片段生成AST
2、提取所有成对的AST叶子之间的路径。单个路径表示AST节点的有序序列
3、双向LSTM将每个AST路径编码成固定长度的节点序列
4、解码器在解码时使用注意力机制选择相关路径,以生成输出序列

使用code2seq将数据集中每种方法表示为代码其为检测任务的320dim向量,基于Alon提供了用于预处理源代码的提取器和java开源模型

4.3.3 CuBERT

CuBERT是在代码片段上进行预训练的BERT模型的大变体。CuBERT Large是一个有四层transformer,每层有16个注意力头,为每个token提供1024dim的矢量嵌入。

BERT Large模型的输入是一系列的token,
1、使用kanade等人开发的自定义Java标记器来处理Java代码片段,
2、然后将每个序列(Java代码行)输入CuBERT模型,
3、然后从特殊的开始示例 ([CLS])token中提取1024dim嵌入,用来表示代码的每一行。

4.3.4 嵌入方法和类

code2vec和code2seq都是在方法级别,使用方法的主体代码片段作为输入。并且将所得的嵌入向量作为方法的向量表现形式。而code2vec和code2seq不能直接嵌入一个类,因此要在类级别执行预测,可将类视为方法的集合。所以在嵌入类的时候,需要嵌入该类所有的构成方法并计算所得代码嵌入向量的平均值

5、结果和讨论

5.1 在训练集下评估基于启发式的代码气味检测器,然后选择性能最佳的启发式方法

​ 在4.1中列出的是基于启发式的检测器在MLCQ数据集上的性能,在大规模手动标记的数据集上比较启发式结果是本文的主要贡献之一。

​ 表5给出基于启发式规则对于上帝类检测任务的性能,表6是长方法的检测性能。

​ 由于基于启发式的方法不需要进行训练,因此这里比较了测试集、训练集、和整个数据集(train+test)的结果,在现实情况下,只选择训练数据来选择最佳的算法。因此,上帝类检测的最佳个体执行规则是GC8,长方法是ANY

​ 对于上帝类的气味,与单个规则的性能相比,将单个规则聚合也无法提高性能,ALL具有基于所有启发式方法的最佳精度,但是召回率极低。ANY方法的精度很低,但召回率最高。加权投票方式(在训练集中的F1值明显高于其他)与规则GC8具有相同的性能,但GC8比加权投票更简单,所以认为GC8是上帝类检测最佳启发式方法。

image-20221027111709273

指标分类:

​ 大小:NOM方法数、NOF字段数、CLOC类中的代码行数
​ 复杂性:WMC加权方法复杂度
​ 内聚:TCC紧致的类凝聚度、LCOM缺乏凝聚力
​ 耦合:ATFD访问外部数据

image-20221027102107730

image-20221027111745295

指标分类:

​ 大小:MLOC一个方法中的代码行数
​ 复杂度:VG-McCabe的复杂度、NBD嵌套块的深度

​ 在表6,长方法气味的聚合也是同样的趋势(ALL的精确度高,召回率极低;ANY的召回率较高),然而与其他方法相比,ANY模型在训练集上的F1值要高很多,因此ANY方法是长方法气味的最佳检测方法。

​ 在上帝类的检测中,ALL方法具有所有启发式方法的最高精度,但是在整个数据集上的精度仍然只有58%。而我们期望所有的上帝类检测的方法都能有近乎完美的精度,对于被标记为上帝类的样本,对比检测长方法坏味的ALL方法,有97%的精度,为了理解上帝类检测的ALL方法的适度精度,要求领域专家分析这个规则下(ALL)被错误标记为上帝类的类。
​ ALL方法将12个类标记为上帝类,其中有5个类被MLCQ数据集中的注释者多数标记为非气味,因此我们认为在MLCQ中这些类被错误的标记。通过查询开发者文档发现MLCQ数据集中大量的注释样本将这些示例标为‘关键级别上帝类’,剩下的被标记为‘无’,并且没有交叉检擦。因此我们推断MLCQ数据集的缺点可能会影响基于ML的上帝类检测器的训练和评估。为了克服这个问题,需要一种更系统地代码气味标签方法。

​ 本论文是第一个在MLCQ数据集上进行实验的,因此无法将上述结果与现有文献报告中的结果进行比较。尝试将本文的研究与原始研究中的报告进行比较有助于证明结果的有效性,但是表7表示本文的实验与原始研究中的实验是有着差异的,差异有三点:
​ 1、很多原始研究没有一个定量的指标(P R F),一些研究不通过手动标记数据来测量精度和召回率,而是计算其检测结果和其他信号(比如已发布的错误报告的数量)的相关性。
​ 2、将本文的结果和使用手动标记的数据作为计算精度和召回率所需的基本事实的研究进行比较也是有问题的:
​ 2.1 各种研究中使用的代码库各不相同,并且与MLCQ代码库很大不同,即使很多研究使用不同的Java开源项目作为代码库,但是使用的项目数量从1-12不等,但MLCQ包含了792个活跃的与行业相关的Java项目,更新更多样化。
​ 2.2 即使两项研究使用相同的代码库作为数据集,对于注释的方面也可能存在不同。即很多研究都使用不同的注释器,意味着很多研究没有注释出来。若数据集很小,则是否为坏味则变得主观。
​ 3、无法复现所谓的原始研究,数据集和标签未公开。
​ 由于这些原因,本文实验性能和原始研究中报告的性能不同,也无法进行性能比较。(主要是不基于同一数据集,无法进行比较)

image-20221028155554616

​ Fernandes等人使用相同的实验设置公平地比较上帝类和长方法的检测工具。将结果和表8进行比较,本文提出的启发式方法实现了对上帝类检测的低至中等效率。Fernannes等人任务其应用的两种工具具有最大的精度。然而我们的研究没有达到上帝类检测的高精度,F等人展示了长方法检测的更好性能。

​ F等人仅考虑四个工具,并使用由单个Java应用程序组成代码库来计算精确度和召回率。

image-20221028164010228

​ 表8比较的是论文中基于度量的启发式方法的性能和F等人提出的方法进行比较。F等人报告了四个工具在单个程序上的性能,这里的值是四个工具性能的平均值。本方法在这里的数值是8个上帝类检测和3个长方法的检测性能的平均值。

5.2 比较不同代码气味检测方法的性能

​ 本节比较的是本研究中考虑的所有代码气味检测方法的性能,表9和表10分别总结了上帝类和长方法在测试集上不用方法的性能。启发式的模型选择的是上帝类识别的GC8方法和长方法检测的ANY模型。基于ML的模型,通过在训练集上交叉验证程序评估的少数群体类别的F度量值,调整超参之后选择性能最佳的模型。

​ 从表9和表10可以看到上帝类和长方法检测的最佳方法是使用CuBERT进行源码嵌入进行训练的ML_CuBERT分类器。其次是使用代码度量的ML_metrics分类器和使用代码度量和基于启发式的检测器的投票作为特征的ML_metrics&votes组合训练的ML分类器。

image-20221029134116001

image-20221029134139490

​ 除了code2vec和code2seq两种特征训练的分类器以外,在本实验中发现ML分类器的性能要优于基于启发式的方法,这一结果与Pecodelli等人在2020年的研究认为启发式技术的效果要优于ML方法这一观点相违背。对于这一点本文的解释是,由于代码度量定义的模糊性和不同的代码度量提取工具对于同一度量的提取的可能存在的差异,基于启发式的方法是脆弱的,因此我们可以将源代码度量作为一种评估指标,而不是在度量提取上使用某种工具重新调整阈值。(如何看待基于启发式的方法跟ML方法各有长处)

​ 对比ML_metrics和ML_metrics&vote,前者只是用了代码度量值,后者加入了启发式规则的投票机制,但是并没有提高很多性能,上帝类检测仅提高1%,长方法检测下降了4%

​ code2vec性能最差,归因于code2vec的地通用性。code2seq是code2vec的改进,但是都不如ML_metrics方法,原因是code2vec和code2seq的嵌入主要集中在代码的语法和语法属性上,并没有语义含义的参与。而CuBERT嵌入包含了代码气味检测需要的语义含义。

​ 上帝类检测中,ML_CuBERT的性能比使用源代码度量的ML_metrics改进2%,比基于代码度量和启发式投票的ML_metrics&vote改进1%。但在长方法检测上,有较大改进,比ML_metrics&vote改进12%,比ML_metrics改进8%。

​ 比较ML_CuBERT和基于启发式的方法H_metrics时,二者都是依赖于手工制作的元代码度量,上帝类检测改进4%,长方法检测改进27%。

​ 根据结果表明,CuBERT为代码气味检测提供更好的源代码表示。但是所有方法在上帝类检测的过程中提升都比较一般,归因于以下几点:
​ 1、在MLCQ数据集中,对类级别的标注分歧多于方法级别,因此需要一种更系统地方法来标注代码气味,以得到更高质量的标签。但目前并没有其他公开可用的代码气味标注的数据集。
​ 2、相比于方法级别的气味,上帝类是类级别的气味,数量相较于方法级别的数据集会更少,因此上帝类的检测器需要更多的标记数据。

​ 将本文的基于ML的方法ML_metrics的性能与文献中提出的类似方法进行比较。如表11,代码气味的检测非常依赖于数据集,当将类似的基于ML的方法(一篇2018年的论文)应用到MLCQ数据集时,效果很一般(F1值6%的上帝类检测,F1值19%的长方法检测),与本文的实验结果存在较大的差异,来源可能是因为:
​ 1、代码库的差异。Qualitas语料库比MLCQ小得多。MLCQ比现有的这方面的论文使用的数据集的开源项目的覆盖面要更大更广,更符合当代编码风格。
​ 2、特征集合的差异:DiNucci等人使用了Qualitas语料库中提供的一组代码度量指标,但是他只使用了这组度量里面的有特征标签的正例,大约是整个组内的1/3。本文使用了一组较小的指标(46个类级和26个方法级指标),存在一定的局限性:对指标的选择取决于使用的指标提取工具的功能。
​ 3、特征值差异:与用于创建Qualitas语料库的工具相比,本文依赖于不同的度量值提取工具。因为在度量提取的过程中包含歧义,导致度量提取工具在提取同一代码段的时候产生不同的结果,然而ML方法的将度量值视为潜在的噪声信号,而不是精确的值,本文使用对噪声特征木鲁棒性的ML分类器来减轻这一因素的影响。

image-20221102150844095

​ 表11是将MLCQ数据集(本文的实验)上基于度量的分类器的F1值与以往的相关实验进行比较,在MLCQ数据集上,本文实现的ML_metrics上帝类检测0.52,长方法检测0.67;启发式方法在上帝类检测上0.49,长方法检测上0.48。

​ “半自动”标记程序表示启发式检测器提供了代码气味的候选列表,由注释者手动检查误报,但不能保证没有假阴性。
​ “交叉检查标签”表示是否有多个注释器检查每个实例以避免标签的主观性
​ “注释者培训”是指注释者在注释代码气味之前是否接受指导、培训并且进行概念验证注释。

​ Pecoreli等人在2020年的研究与本文最相似,因为它比较了基于ML的检测器和用于代码其为检测的启发式方法的性能,作者认为启发式方法的性能略好于基于ML的方法。本文的方法比其更加优越,在ML的检测中,上帝类检测提升11%,长方法提升33%;在启发式方法的检测中,上帝类提升33%,长方法提升4%。原因如下:
​ 1、代码库(项目源文件)的差异:MLCQ数据集包含更广泛的开源项目,较新并且完全手动标记。
​ 2、特征集合的区别:P 等人使用的是比本文小得多的代码度量集,对代码度量的选择取决于他们使用的基于启发式的方法。使用启发式方法没有考虑到的度量指标可能是对训练有益的,ML方法可能会发现度量值和代码气味之间不太明显的关系。
​ 3、考虑采用的启发式方法的差异:本文的研究考虑了从最近有关代码气味启发式方法的系统文献调查中提取的不同检测规则。但如4.1节,只能找到精确度量定义一匹配工具提取的度量的规则,这通常是我们研究和基于启发式检测的局限性。
​ 4、特征值差异: 本文的度量提取工具与P等人的不同,这可能会影响启发式的检测,但是不会显著地影响ML的检测。

5.3 性能分析

​ 这一部分介绍误差分析,以分析CuBERT自动推断特征和手动代码度量特征的不同性能的原因。本文将ML_Cu ­BERT,ML_metrics和H_metrics应用于测试集中的示例,并且收集这些方法里检测错误的示例,请教专家分析这些代码示例。目的是:手动识别影响这些模型性能的代码特征。

​ 上帝类检测的性能是轻微的,但是错误分析报告表明CuBERT可以捕获检测代码气味所需的性能。ML_CuBERT正确的标记了基于度量的方法错误标记的上帝类,(为了确保基于度量的方法可以检测到这些样本,我们需要开发度量可以相应的数量并且可以自动计算的度量)。与ML_metrics相比,ML_CuBERT还正确标记了非典型类。例如:
​ 1、具有很多自动生成注释的类;
​ 2、具有很多方法和字段但在语义上不遭受上帝类的气味,因为他们相对简单并且不处理多个职责;
​ 3、遵循设计模式的类;
​ 4、具有单个复杂方法但在其他情况下很简单的类。

​ 另一方面,ML_CuBERT可能会受益于将RFC度量包含在源代码表示中,因为他错误的标记了几个具有ML_metrics正确检测到的大型RFC的类。 DIT(继承树的深度)CBO(对象之间的耦合)RFC(类的响应)。

​ ML_CuBERT对ML_metrics的长方法代码气味的性能改进是很大的,根据这个结果可以得到,自动推断特征而不是手动制作特征,对于不同代码气味检测任务是有好处的。

​ 使用代码度量作为特征的一个缺点就是:这种方法依赖于复杂的度量指标提取。如4.1节,度量提取工作是复杂的,度量实现不能够完全按照某种意图完全实现,因为度量定义通常是不完整的。如:如果工具在计算外部类的度量时考虑内部类,结果会是模棱两可的,也会增加度量计算的复杂性。总之就是基于度量的启发式方法中使用的阈值与度量提取工具有关,因此很脆弱。

​ 此外,度量计算可能取决于包括或者排除library类,并且计算特征的代码片段的度量可能要解析整个项目,例如,从MLCQ数据集中提取表示代码样本所需的指标非常耗时,需要检查整个项目以准确计算一些代码度量。这就需要包括library类,对某些项目来说提取出来难度很大。

​ 例如,与H_metrics和ML_metrics相反,ML_CuBERT可以检测到以很多单词和宽表达式为特征的长方法,虽然H_metrics不只是依赖单词的数量,但是ML_metrics使用此度量作为预测变量之一,然而,领域专家认为这种样本的数量计算价值偏低。

​ 因此,可能针对于某些样本并不能准确地计算出这些样本的某些度量,从而影响到了基于度量的检测器,因此CuBERT嵌入仅仅需要分析代码样本作为输入,因此,计算CuBERT更简单切不易出错。(为什么选用CuBERT代替基于度量的嵌入方式)

​ 又考虑到需要一种系统的方法来编码气味标签,而人工对代码气味数据集进行打标总是存在一些分歧。(我们的领域专家还指出了一些可能相关的准则,用于在错误分析过程中注释代码气味。例如,如果类的复杂性源于单个长方法,则他不会将该类标记为神类。但是,如果该类有几个长方法,他会将其标记为神类。另一个指导示例是在决定其气味时以不同的方式对待遵循设计模式的类。如NLP领域的标准那样,就此类准则达成共识将大大提高代码气味数据集的质量,从而提高该领域的最新技术水平。)

​ 总之得到结论,在本文实验中最佳的源代码表示形式就是CuBERT,原因如下:
​ 1、CuBERT特征比代码度量更好的捕获到上帝类和长方法所需要的语义特征
​ 2、度量提取实现是复杂的,因为度量的定义可能是不明确的
​ 3、度量提取是耗时的
​ 4、度量值可能计算错误,检测难度大

6、对有效性的威胁

这里讨论可能影响到结论的因素:内部、外部和结构有效性

6.1对内部有效性的威胁

​ 对内部有效性的威胁与实验结果的正确性有关。
​ MLCQ数据集的标注人员分布广,存在标注差异的问题。另一个因素是对MLCQ数据集中实力的标签采用聚合策略,若一个实例的标签存在不同,则选择多数投票机制得到每个实例的最后标签。
​ 尽管实施规则简单,但可能原始的度量提取工具中度量描述或者阈值也存在差异,可以使用公开可用和经过测试的工具执行代码气味检测来缓解此问题,然而代码度量和启发式设计人员并不能保证所有的可能性,因此无法为每种可能的情况精确定义度量。
​ 为了评估ML模型,本文进行分层单保留验证,重复保留验证有助于产生稳健的性能估计,本文将单个测试集比较应用于不同的方法。领域专家队最终结果进行错误分析,较易受到主观性的影响

6.2对外部有效性威胁

对外部有效性的威胁考虑到实验结果的普遍性,使用的MLCQ数据集包含许多活跃的与行业相关的当代项目,减轻了普遍性的问题,都是JAVA编写的,不保证可以推广到其他编程语言编写的其他行业项目。

6.3对结构有效性的威胁

​ 本文实验中使用多种基于度量的启发式方法来显示ML方法的有效性。如4.1节,由于度量定义及其实现之间的差异,应用基于度量的启发式方法是脆弱的,且如果度量标准定义与实现不同,则启发式方法作者定义的阈值也存在差异,从而影响到启发式方法的性能。通过选择开源并且已被使用的度量标准提取工具来减轻这种威胁。因此可以检查度量实现是否符合其定义。但是度量标准的定义总是存在着模糊性,,例如不清楚内部类的属性 是否作为影响外部类计算的 一个度量。
​ 为了计算需要的指标,本文使用了这个领域内其他研究人员使用的公开可用的开源指标提取工具,然而MLCQ数据集包含很多个项目(792),其中某些度量之可能由于解析错误被错误计算,当使用可被人们接受的工具时,希望计算的度量值的数量微不足道。

7、结论

​ 在本文中,对上帝类和长方法代码进行了基于ML的检测。在不同的项目源代码上尝试训练多种ML分类器:(1)预训练的code2vec、code2seq、CuBERT嵌入;(2)传统上使用的手工代码度量的代表向量;(3)源代码度量和流行气味检测启发式算法的投票方法的组合。为了测试基于ML方法的有效性,将流行的气味检测启发式方法作为baseline。在MLCQ数据集上评估方法,MLCQ数据集是最大的、手动标记的、可在线的用于代码气味检测的数据集。并且做了深入的误差分析,以了解所考虑方法的优点和局限性。(本文的工作)

​ 结果表明,预训练的code2vec和code2seq嵌入不能很好的推广到代码气味检测中。另一方面,预训练的CuBERT嵌入上训练的ML模型要优于其他的所有方法。并且,使用手工制作的代码度量训练的ML模型要优于基于度量的方法。(本文的结果)

​ 本文的错误分析论中了使用自动推断的CuBERT特征嵌入表示源码的优势,而不是基于度量的表示来进行代码气味检测。度量表示的劣势在于:度量提取过程耗时,不可靠,并且无法捕获检测代码气味以及CuBERT功能所需要的语义特征。

​ 本文是第一个评估与训练的神经源代码嵌入对气味检测的有用性的研究。这个方法能解决以下两个 限制传统意义上使用代码度量作为特征的采用ML的方法 的问题:1)度量标准提取过程易错、耗时且不可扩展;2)能够使用迁移学习的概念,这是第一个探索从代码理解模型中挖掘的知识是否可以转移到代码气味检测的研究。

​ 本文也是第一个在MLCQ数据集上进行不同代码气味检测器的研究。在本文中提到的所有方法都在唯一数据集上进行测试。不幸的是我们认为在MLCQ中有两个严重的局限性,1)并非所有标记的实例都由不同的注释器进行交叉检查;2)注释者之间存在严重分歧,MLCQ作者并未给注释者提供指导或者注释训练。因此,我们认为基于ML的代码气味检测领域在极大程度上依赖于代码气味的标签的系统方法,受到NLP的启发,未来工作主要在开发代码气味标签的系统方法。