树人论文网一个专业的学术咨询网站!!!
树人论文网

软件开发问答网站代码片段自动分类方法研究

来源: 树人论文网发表时间:2021-08-17
简要:摘 要 诸如 Stack Overflow 这种软件开发问答网站已成为开发者在编程中寻找问题解决方案的主要手段,它们通过众包的方式为开发者提供解决方案和代码片段作为参考。自动识别代码片段

  摘 要 诸如 Stack Overflow 这种软件开发问答网站已成为开发者在编程中寻找问题解决方案的主要手段,它们通过众包的方式为开发者提供解决方案和代码片段作为参考。自动识别代码片段的用途将为软件开发问答网站的知识抽取提供支持。通过对 Stack Overflow 上的问题及代码片段进行研究,总结出 4 种问题类型和 8 种代码片段类型。在此基础上,实现基于朴素贝叶斯的自动分类方法。实验表明,8 个类型代码片段的分类准确率都在 50% 以上,整体准确率达到 70% 以上。

软件开发问答网站代码片段自动分类方法研究

  谢文凯; 彭鑫; 赵文耘, 计算机应用与软件 发表时间:2021-08-12

  关键词 软件开发问答网站 代码片段 经验研究 分类 机器学习

  0 引 言

  近些年来,互联网技术的发展日新月异。新技术层出不穷,同时一些旧的技术也在加快迭代的速度,焕发出新的活力,这导致了软件开发所涉及的技术体系也越来越繁琐、越来越复杂。但由于人的精力有限,软件开发人员也不会掌握全部的知识,他们在开发过程中也经常会遇到一些自己难以解决的问题。于是,一些能够专门为开发人员提供交流平台的编程类问答网站也逐渐变得火热起来。用户能够针对自己遇到的问题寻求帮助,也能够针对别人的问题提供自己的见解和解决方案。Stack Overflow 是目前所有的编程类问答网站中最活跃、使用最广泛的其中之一。

  与以往的用户能够自由交流讨论、畅所欲言的论坛不同,Stack Overflow 的目标是希望成为编程类问答的一个超级数据库。在网站建立之初,它就要求用户在提问时要坚持“practical,answerable questions basedon actual problems that you face”的原则,所以网站上的每个问题都不止是为了仅仅帮助提问者本人,更重要的是希望将来当别人遇到同样的问题时,同样能够提供帮助。

  而在 Stack Overflow 的所有问答帖中,存在着大量的代码片段,它们可能是存在问题的代码,也可能是针对遇到问题所提出的解决问题的代码,当然也可能仅仅只是一个可能会用到的 API 等。如果能将这些代码片段进行合理、准确的分类,一方面,能够使得展示在网站上的代码片段更加直观、易于理解,能够帮助开发人员更加容易地分辨清楚哪些是自己需要的作为解决方案的代码片段,哪些是出现问题、需要进行修改或者优化的代码片段; 另一方面,如果能够自动化地将问答帖中的代码片段进行分类,那么分类后的代码片段将更能够帮助构建起一个更加完整、更加结构化的针对 answerable questions 的知识库。

  1 相关工作

  1. 1 Stack Overflow 研究

  Stack Overflow 是隶属于 Stack Exchange 旗下的编程类问答网站,自 2009 年创建以来,每隔 2 ~ 3 个月 Stack Exchange 都会将进行处理后的网站数据公开发布,需要的用户可以直接去官网下载相应的 dmp 文件。得益于此,现在每年都会有大量基于 Stack Overflow 所做的研究。然而,大部分关于 Stack Overflow 的文献主要的关注点还是集中于 Stack Overflow 本身或是网站所提供的数据进行的[1 - 4],例如对网站数据文本的挖掘、问题和内容的分析等。

  Arora 等[2]基于 Stack Overflow 提供的数据,采用了一种将问题向量转换为特征空间中的不同向量的方法,不同于以往单独地从问题中学习分类边界,对离散项空间、从文献载体嵌入了实数的连续向量空间进行探索,实现了一种能够将新发布的问题进行分类的方法,将分类的准确率提高了 8% 。Joorabchi 等[3]采用了一种启发式研究方法,结合文本挖掘的方法来研究 Stack Overflow 网站上问答帖的主题和类别,对近 18 600 个问答帖进行了分析,使用维基百科对人群进行分类,建立它们之间的语义关系,并通过 Gephi 等可视化软件将数据转化为图像呈现出来,确定发布回答是否确切主题,并缩小其类别,以帮助确定学习者的学习难题。Ye 等[4]利用 Stack Overflow 所提供的数据,在现有的命名实体识别技术的基础上,提出了一种应用于软件工程领域的基于机器学习的命名实体识别技术,可以识别广泛的流行编程语言、平台、API 等软件实体。

  而对于问答帖中的大量存在的代码片段等,几乎没有专门针对此内容的研究,从这一方面着手开展研究存在着很大的潜力。

  1. 2 机器学习

  机器学习主要致力于研究如何通过计算的手段,利用经验来改善系统自身的性能。在计算机系统中, “经验”通常以“数据”的形式存在,因此,机器学习所研究的主要内容是关于在计算机上从数据中产生“模型”的算法,即“学习算法”。学习算法能够基于提供的经验数据产生模型; 在面对新情况时,模型会提供相应的判断。如果说计算机科学是研究关于“算法”的学问,那么类似地,可以说机器学习是研究关于“学习算法”的学问[5]。

  现如今,机器学习已经发展成为一个相当大的学科领域。从学习方式的角度来分,机器学习可以分为无监督学习、有监督学习和半监督学习。有监督学习的常见场景如分类问题和回归问题,无监督学习的常见场景包括关联规则的学习和聚类等,半监督学习的常见算法包括图论推理算法等[6]。而从算法的角度来分,则可以分为基于树的算法、基于神经网络的算法、贝叶斯方法等。其中,贝叶斯方法的主要原理就是贝叶斯公式,这一类方法主要应用于分类问题。而朴素贝叶斯方法是一种基于条件独立假设的贝叶斯方法,与其他的方法相比,其收敛速度更快,模型也更加简单,也有很多的学者运用朴素贝叶斯方法做了大量的研究。

  Catal 等[7]为 Java 程序开发了一款能够基于 Eclipse 的软件故障检测工具,并运用了朴素贝叶斯方法,将软件度量与软件数据故障相结合,来简化故预测过程。 Zhang 等[8]针对云计算环境中调度算法的重复性和分配的不均衡,提出了一种基于朴素贝叶斯算法的负载均衡技术,该技术利用心跳机制全面收集每个节点的负载信息,从而基于朴素贝叶斯算法对所有节点的负载状态进行分类,根据分类对每个节点的任务和资源进行合理调度。

  2 经验研究

  2. 1 问题类型

  若想能够对 Stack Overflow 上的代码片段进行比较全面、完整的分类,首先就需要先了解开发者经常会提问的问题的类型。在研究开始之前,首先对 Stack Overflow 上包含 Java 标签的问答帖进行了大量的分析和研究。经过大量的分析后,本文将 Stack Overflow 上包含代码片段的问题主要归纳为以下几个类型,并总结了这些问题类型的特点。

  1) 第一类问题是 debug 类型的问题,这一类问题主要是开发者在实际开发的过程中实际得到结果与预期不相符并且自己又难以解决时提出的。在提问时,开发者一般首先会对自己所做的工作及遇到的问题进行简单的描述,大部分情况下也会贴出存在问题的代码片段或者出现问题时的报错栈等,这一类问题通常会包括“error”“exception”等单词,在网站上比较常见。例如,在问题“Getting Invalid Address with javax. mail when the addresses are fine”中,开发者在利用 javax. mail 开发邮件系统时使用合法的邮件地址却抛出了 “Invalid Address”的异常,于是贴出自己的问题代码求助,得到了其他开发者合理的解答。

  2) 第二类问题属于改进类型的问题,这一类问题主要是因为开发者对当前程序性能不太满意,例如耗时较长等,希望在原有的版本基础上寻求更加优化的方案而提出的。在提问时通常会包括“need a better method”“not efficient”等语句。例如,在问题“Get Last Friday of Month in Java”中,开发者在没有利用现有 API 的情况下实现了一个能够得到每月的最后一个星期五的程序,但是效率不够高而且代码比较繁琐,可重用性较差,希望能够得到一个效率更高的代码,其属于改进类型的问题。

  3) 第三类问题是具体实现的问题,这一类问题主要是因为开发者相对缺乏当前开发程序所需的知识,在开发过程中遇到了困难而提出的。通常,在这类问题中会包括“how can I”等词语。例如,在问题“How do I restrict JFileChooser to a directory?”中,开发者希望对用户的浏览目录进行限制,而“Parent Directory”可以浏览任意目录,开发者希望能够寻求比较完整的具体实现的代码片段。

  4) 第四类问题与第三类问题相对,属于抽象类型的问题。在这类问题中,开发者不再是针对具体的实现问题寻求解决方案,而是上升到了比较抽象的层面,一般在回答中的代码片段只是用来举例,使抽象的问题更加具体化,通常,会包括“what”等词语。例如在问题“What is the difference between an int and an Integer in Java and C#?”中,回答中包含的代码片段如“Integer i = new Integer( 6) ; ”“int i = 6; ”只是为了说明 Integer 和 int 的区别,而并不是针对问题提出的解决方案。

  2. 2 种类划分

  在 Stack Overflow 网站上的所有问答帖中,代码片段一般由两种存在形式: 一种是一段完整的代码片段,与其他的文本描述分隔开来,被包裹在 < pre > < code > < /code > < /pre > 标签中; 另一种主要存在于问题和答案的文本之间,一般只是用来表示变量、数据类型等,被包裹在 < code >  < /code > 标签中。按照标签的不同,可以将代码片段分为“大代码片段”和“小代码片段”两种类型。

  根据 2. 1 节中对 Stack Overflow 问答帖的研究,本文将“大代码片段”划分为了如下六个类型:

  1) 开发者在开发过程中出现问题或 bug 的完整代码,这类代码主要存在于问答帖的问题中。

  2) 开发者在开发过程中的完整代码,没有明显的 bug 和 error,但是可以进行优化的代码,这类代码主要存在于问答帖的问题中。

  3) 没有 bug 或 error,用来作为举例说明的代码,这类代码即为 2. 1 节中第四类问题使用的代码,在问答帖的问题和回答中都会存在。

  4) Stack Overflow 用户针对开发者所遇到的问题、 bug 或针对可优化的代码提出的解决方案,这类代码主要存在于问答帖的回答中,在网站中最为常见。

  5) 开发者在问题或回答中对内容进行补充说明时所用到的输入输出,这类虽然不是真正的代码,但是也由“大代码片段”标签包裹,在问答帖的问题和回答中都会存在。

  6) 开发者对表述内容进行补充说明时所用到的程序出现问题时抛出的异常信息、报错栈等,主要存在于问答帖的问题中。

  “小代码片段”主要存在于问答帖的文本描述中,用于表示开发者在问题或答案中可能会涉及到的 API、变量等。与“大代码片段”相比,这些“小代码片段”价 值 相 对 较 低。“小 代 码 片 段”分 为 如 下 两 个类型:

  1) 开发者在编写问题或答案时可能会用到的代码中的类名、API 等信息,在问题和回答中都会存在。

  2) 对于“小代码片段”,除去上述第一类,其余全部都归于此类中,常见的如变量名、数据及一些软件工程相关的通用知识等,同样在问题和回答中都会存在。

  3 自动分类

  3. 1 数据筛选

  代码片段的上下文非常重要,那么选取合适的文本作为上下文就极为关键。文本太短可能会导致相同类型的不同代码片段上下文之间没有太多的相似性;过长又可能会带来更多的噪声,造成相同类型的不同代码片段上下文之间的无关联系增加,干扰判断。

  为最大限度地选取合适的上下文内容,保证上下文内容的质量,本文针对“大代码片段”和“小代码片段”采取了两种不同的策略。

  “小代码片段”策略比较简单,因为其存在于成段的文本中,不同段落之间影响较小,因此其上下文内容均为其所在段落的文本。

  而对于“大代码片段”而言,经过分析发现可以以 “小代码片段”所在的段落作为边界,因为在“大代码片段”附近出现的“小代码片段”,很大程度上是为了解释“大代码片段”中的一些信息。因此可以采取如下策略: ( 1) 从“大代码片段”的位置向上( 下) 查找直到某段文本中出现“小代码片段”为止( 包含此段文本) ; ( 2) 如果没有“小代码片段”,则与相邻“大代码片段”之间的文本作为相应的上文或下文; ( 3) 如果以上情况( 1) 、( 2) 均不存在,则该代码片段上( 下) 面的所有文本作为相应的上( 下) 文。

  3. 2 数据预处理

  Stack Exchange 在发布数据文件时,只是将从 HTML 中提取出的数据放入到数据库中,并没有对其做相应的去标签化处理。同时,因为开发者在 Stack Overflow 网站的问答帖中编辑问题或答案时,在描述时有时会用到一些符号,简单的分词和分句会造成分词不正确,例如“exception) ”“question,”,会导致程序在处理数据时出现误差; 此外,单词在不同场景下,会有不同表示形式,例如“question”和“questions”是同一个单词的单复数形式,“better”是“good”的比较级; 不同的代码片段之间的上下文之间可能会出现重复现象,如果同一段文本的重复次数过多,在进行特征词提取时,会导致重复文本出现次数异常增多,在最后的特征词集中也会出现过多的噪声,对最后的预测准确性造成很大的影响。

  基于以上四种情况,本文对数据进行了以下四个方面的预处理。

  1) 去标签化。对于数据中存在的 HTML 标签,因为其比较固定,均是包裹在“< … > ”中,而数据中原本存 在 的“ < ”和“ > ”,则是由转义字符“< ”和 “> ”表示。因此,将数据中原本由“< … > ”包裹的所有标签替换掉,而对原本在数据中表示“<”“> ”等的转义字符,按照对照表修改为对应的字符即可。

  2) 去字符化。与中文不同,在英文中可能为了缩写某些单词会在单词中间添加标点符号等作为标志,同时在某些字段中间的一些字符也需要进行保留,例如“C: \Users”等,这就导致不能以偏概全的将单词中所有出现的字符、标点等全部去除掉。在这样的前提下,本文通过数据分析,归纳出了单词中的无用字符出现的一些特点:

  ( 1) 在单词开头或结尾地方出现的字符一般为无用字符,例如“( exception”、“example,”等;

  ( 2) 一些特殊的字符,如“/”“<”“> ”和不在单词结尾的“. ”等,有特殊的含义,不可去掉。

  结合单词的这些特点,本文采用正则表达式对单词进行匹配,对于不符合条件的单词去除相应的字符来实现单词的去符号化。

  3) 词干化。本文利用 Java 库 TT4J( TreeTagger for Java) 进行词干化,这个库的主要功能是识别语句中每个单词在语句中的词性,并能够根据单词的词性得到单词的词干。不仅如此,在不同的环境下相同的单词可能因为语境的不同可以得到不同的结果,例 如, “better”在不同的语境下可能会被转化为“good”或 “well”。

  4) 去重。两段代码片段的上下文之间出现重复现象,如果这两段代码片段的类型一样,那么就直接把重复的上下文去除掉,不考虑重复文本; 如果这两段代码片段的类型不同,那么就按照正常的逻辑将重复文本划为不同类型,对于每段代码片段都采用这样的方式检查。

  3. 3 朴素贝叶斯算法

  朴素贝叶斯法是指基于条件独立假设和贝叶斯定理的分类方法[9 - 10]。设 x = { a1,a2,…,am } 为一个待分类项,而 x 中的每个元素 ai为 x 的特征属性,设 C = { y1,y2,…,yn } 为类别集合,C 中每个元素 yi为每个可能的类别。分别计算 P( y1 x) 、P( y2 x) 、…、P( yn x) ,如果 P( yk x) = max( P( y1 x) ,P( y1 x) ,…,P( y1 x) ) ,则 x∈yk。

  若想得到 x 属于 C 中的哪个类别,关键就是得到各个条件概率,根据贝叶斯定理:

  ( 1) 确定已知分类的数据集合作为训练样本 N;

  2) 对于每个特征属性在各个类别下的条件概率: P( a1 y1 ) ,P( a2 y1 ) ,…,P( am y1 ) ; …; P( a1 yn ) , P( a2 yn ) ,…,P( am yn ) 。

  可以通过极大似然估计来求得,公式如下: P( am yn ) = N( am yn) Nyn ( 1)

  ( 3) 根据条件独立假设及贝叶斯定理,可得: P( yi x) = P( x yi ) P( yi ) P( X) ( 2)

  ( 4) 因为分母中的 P( X) 对于所有类别来说都是常数,因此只需保证分子最大化即可。又因为各个特征属性是条件独立的,故 P( x yi ) P( yi ) = P( a1 yi ) P( a2 yi ) … P( am yi ) P( yi ) = P( yi ) ∏ m j = 1 P( aj yi ) 。在实际的编码中,大量的乘法计算会导致计算的时间开销比较大。因此,本文采取通过比较 x 在不同类型 yi情况下互信息量的大小来对应 x 在不同类型 yi 情况下的概率的大小。

  3. 4 互信息

  互信息主要用于度量两个事件集合之间的相关性,可以看成是一个随机变量由于已知另一个随机变量而减少的不肯定性[11]。

  设两个随机变量( X,Y) 的联合分布为 P( X,Y) ,边际分布分别为 P( X) 、P( Y) ,互信息 I( X; Y) 是联合分布 P( X,Y) 与乘积分布 P( X) P( Y) 的相对熵,即: I( X; Y) = ∫X ∫Y P( X,Y) log P( X,Y) P( X) P( Y) ( 3) 对于本文来讲,设变量 X 表示数据集中的不同文本,变量 Y 表示可选择类型,互信息 I( X; Y) 就可以表示不同的文本与可选择类型的联系程度,互信息越大,那么该文本与该类型之间的联系越紧密,该文本属于该类型的概率也就越大。

  3. 5 算法流程

  本文主要运用了机器学习中的朴素贝叶斯算法,根据通过分析、总结出的 Stack Overflow 中的八种常见的代码片段的类型,实现了对提取出的代码片段的种类进行预测的算法。算法流程如算法 1 所示。

  算法 1 朴素贝叶斯分类器 1. function classification( ) { 2. InitialCode = readData( ) 3. CodeSnippets = preprocess( InitialCode) 4. TrainSet = part of CodeSnippets 5. TestSet = part of CodeSnippets 6. FeatureWords = extractFeatureWords( TrainSet) 7. BayesModel = trainModel( FeatureWords) 8. Result = predict( FeatureWords,BayesModel,TestSet) 9. return Result 10. }

  首先,从数据库中读取出未经处理过的原始数据集,经过数据预处理后,得到可以用于朴素贝叶斯算法的数据集,并将该数据集随机划分为训练集和测试集,其中训练集与测试集的比例为 9 ∶ 1。

  对训练集中的数据,进行提取特征词操作,计算训练集中的每条数据中所有单词对于不同类型的互信息,对于每种类型提取出 50 个互信息较大的单词,合并后作为特征词集。

  接着,对训练集的数据进行训练,计算特征词集中的每个单词在不同类型下出现的概率,作为训练好的贝叶斯模型。

  最后,对测试集中的数据进行种类预测,计算测试集中的每条数据在不同类型中的概率,得到该数据最可能的类型。

  4 实 验

  本实验为探究不同类型数据对准确率的影响程度,即探究算法能够比较高效的预测哪些类型的数据、对哪些类型的数据预测能力较差,主要测试了算法对测试集中不同类型的数据进行预测所得到的准确率和召回率,并通过对所得结果进行分析,总结出产生这些差异的原因。

  4. 1 实验设置

  本文所使用的数据主要来自 2019 年 3 月发布的公共数据集,主要选取了包含 Java 标签的 31 153 条数据作为原始数据集,其中,4 998 条数据属于问答帖中的问题,而剩余 26 155 条数据属于问答帖中的回答部分。随机对 8 000 条数据进行了人工标注,得到每种代码片段类型的数据量如表 1 所示。

  实验一共选取了 800 个不同的代码片段作为总数据集,其中每个类别各包含 100 个代码片段。一共进行两次实验,每次分别取各种类型数据的一半一共 400 个代码片段进行实验,最后取两次实验的平均值作为最后的结果。

  实验环境是在 macOS 操作系统的 PyCharm 编译器,运行服务器配置为 2. 3 GHz 四核 Intel Core i5 和 16 GB 内存,实验的评价标准是准确率 P 和召回率 R。

  4. 2 实验结果

  利用本文的自动分类方法进行实验,得到八种代码片段对应的准确 率 和 召 回 率 分 别 如 图 1 和 图 2 所示。

  可以看出,大代码片段中第一种和第六种代码片段及两种小代码片段类型对应的准确率较高,均达到了 70% 以上; 第二和第三种类型的代码片段准确率较低,在 50% ~ 60% 之间,第四种和第五种类型代码片段准确率在 60% ~ 70% 之间。大代码片段中前五种类型的召回率比较低,均在 50% ~ 75% 之间,第六种类型数据的召回率较高; 小代码片段中第七种类型数据的召回率超过了 90% ,而第八种类型的召回率在 70% ~ 80% 之间。

  4. 3 结果分析

  对于预测准确率较高的大代码片段中的第一种和第六种类型,这两种类型的代码片段上下文更容易提取特征词,例如,在第一类代码的上下文描述中,一般都会用“I have a /an question /exception”等语句来引出存在问题的代码片段,question、exception 等单词出现的评率较高。两种小代码片段类型因为上下文仅选取了所在的段落,遇到的噪声较小,特征更加明显,准确率也更高。

  而准确率较差的这几种代码片段类型,通过对它们的代码片段上下文及所提取的特征词集的分析,发现它们之间的差异并不明显,一方面是因为训练集中这几种类型的数据较少,特征词的频率较低,另外一方面是因为利用特征词进行预测还不能获取到所有的信息,还需要进一步结合上下文描述中语义层次的信息,采用一些诸如 word embedding 的技术来提升实验效果。

  5 结 语

  本文基于机器学习算法,利用 Stack Overflow 网站中大量存在的代码片段,对代码片段的用途分类进行研究。通过对 Stack Overflow 网站上的问答帖的大量分析,归纳出了八种比较常见的代码片段的类别,并利用机器学习中的朴素贝叶斯方法实现了这种分类方法。实验结果表明,预测的准确率能够达到 70% 以上,在一定程度上解决了最初提出的问题,未来可以考虑利用一些更加成熟的技术来对效果进行改进。