人工智能技术在嵌入式代码审查中的应用与展望*
2020-07-22李杨阳
李杨阳,万 波,梁 森,胡 涛
0 引 言
嵌入式软件是应用于嵌入式硬件中的操作系统和应用软件.随着各个行业中嵌入式计算的应用越来越广,嵌入式软硬件的需求也在不断增多.由于航天领域的特殊性,对嵌入式软硬件的要求更加苛刻.在嵌入式软件开发过程中,软件测试是必不可少的一个环节.软件测试是一种比较实际输出与预期输出间差异的过程,它是一种保证软件在开发过程中的完整性、正确性和安全性的重要工作.软件测试按照测试过程逐步推进的角度可分为单元测试、组装测试、确认测试、系统测试和验收测试.在这些测试中,确认测试是检验软件是否满足软件需求规格说明中规定的软件功能而进行的测试,通常使用黑盒测试方法来设计测试用例.确认测试主要包括功能测试、性能测试、接口测试、安全性测试、余量测试和强度测试等.为了测试整个软件是否符合需求,确认测试对代码测试的覆盖率要求极高,一般要求达到100%.但是,通常确认测试阶段达到语句和分支覆盖100%的要求是很难达到的,一些异常或者故障的情况难以复现并进行测试(比如,通常难以预测哪些进程会出现系统运行时进程之间的资源访问冲突),这时便需要代码审查作补充测试,通过人工分析程序的运行情况找出可能存在的软件缺陷[1].因而代码审查在嵌入式软件在开发过程中有着一定的必要性.
当前的代码审查以人工代码审查为主.随着软件开发节奏变快,软件功能日益复杂多样化,人工代码审查的弊端日益显现:一方面,人工代码审查一方面严重依赖于人的经验,自动化程度低且遗漏率高;另一方面,用于辅助审查人员进行代码审查工作的软件工具功能有限,往往只能进行较为简单的检查功能(如规范性检查),无法进行更复杂的检查(如功能预测、缺陷检测等).这些弊端随着软件的发展日益凸显,严重的情况下会拖延嵌入式软件的开发周期,降低整体的开发效率.为了提高代码审查的效率,势必要减少审查工作对审查人员的审查经验的依赖性,提升辅助审查手段,让审查工作变得友好又高效.
近年来,以深度学习为首的人工智能技术在计算视觉、自然语言处理、生物医学等领域大放异彩.得益于现代计算机强大的计算能力,深度学习模型可以通过训练获取处理特定任务的经验,并将这些经验应用于实际的任务中.目前,一部分研究人员在探究将深度学习应用于代码审查的可行性.他们提出了一些处理特定审查任务的深度学习模型,并比较了与传统方法性能的优劣.
鉴于目前相关研究成果较为分散,缺少一个系统性的总结,本文旨在对目前与代码审查相关的人工智能研究成果进行归纳总结,并探讨未来代码审查发展的方向.
1 嵌入式代码审查
嵌入式代码审查顾名思义,是指嵌入式软件开发过程中的代码审查工作.下面分别介绍嵌入式代码审查相关概念和目前所面临的挑战.
1.1 代码审查
软件测试按照是否运行被测试程序分为静态测试和动态测试,静态测试包括文档评审、代码审查等.在嵌入式软件的第三方测试中广泛应用的测试方式就是代码审查[2].
根据IEEE Std.610.12—1990,代码审查是一种静态分析技术,它依靠目视检查开发的产品,发现错误、发现和开发标准的偏离以及其他问题[1].软件代码审查的目的是检查代码和设计的一致性、代码执行标准的情况、代码逻辑表达的正确性、代码结构的合理性以及代码的可读性[3].
代码审查一般分为三个步骤:背景资料阅读、审查代码和填写检查单.背景资料通常包括任务书、需求说明书、涉及编程的硬件说明书(比如51系列芯片引脚的说明)等.不同工程对审查代码的仔细程度也有不同要求,高可靠性软件的代码往往需要从细节到整体的全方位检查.审查完代码后需要将审查出的问题归纳到检查单中.
代码审查的优点有:①能够尽早测试尽早发现问题;②所需投入的测试资源少;③代码审查所针对的对象是整个软件代码,对消除一些特别细节的错误大有裨益,尤其是那些容易在阅读代码的时候发现的缺陷,比如可维护性与可读性方面的缺陷;④发现缺陷的效率较高,一般能发现软件中30-70%的缺陷[4-5].所以代码审查是软件测试中发现缺陷比较有效的手段之一,也是提高代码质量的最强大的技术之一.正因为这些优点,代码审查得到测试业界的重视.
1.2 嵌入式软件特性[3]
嵌入式软件是基于嵌入式系统设计的软件,它是由程序以及文档组成.嵌入式软件与通用软件的区别主要表现在以下几方面:首先,与通用的软件有很强的兼容性不同,嵌入式软件只能运行在特定的目标机上,与其所属的目标机系统有很强的耦合性,软件的实现细节和目标机系统的结构、I/O 端口配置等都有关系;其次,嵌入式系统需要在规定的时间内完成相关的任务,因此具有很强的软件实时性要求;即使是中断处理方式也会影响到嵌入式系统的实时性能.在实际应用中,由于中断的复杂性,对于嵌入式软件很难进行充分的测试,只能通过充分的中断分析来弥补.
1.3 嵌入式软件与代码审查
由嵌入式软件特性可知,嵌入式软件具有兼容性差、实时性强、种类繁多等特点.因为确认测试对测试的覆盖率有极高的要求,嵌入式软件繁多的特性会极大地增大确认测试地工作量和难度.为了在测试的效率和测试的覆盖率之间达到平衡,确认测试一般通过黑盒测试技术验证嵌入式软件的大部分代码和运行状态的正确性,然后使用代码审查作为补充去验证前面未能覆盖的剩余区域.
嵌入式软件代码审查一般依据代码检查单进行[1],可以针对不同的编程语言的代码、设计文档、需求文档等不同对象分别制定,并可以不断累积和丰富.
检查单的范围可以很广,可以根据需要设置不同的检查项目,如对常见设计缺陷的检查、针对各种编程语言常见问题使用错误的检查、针对软件所使用的芯片常见错误的检查等[1].典型的代码检查单如表1所示.由于代码检查单的特性,代码审查可以较为简便地针对软件中较难进行黑盒测试的情况进行验证,进而避免了测试工作复杂性的提升,一定程度上保证了确认测试的效率.
表1 典型代码检查单示例Tab.1 Typical code checklist examples
1.4 代码审查所面临的挑战
时至今日,传统嵌入式代码审查目前主要面临的几个方面的问题:
(1)代码审查工作效率下降
嵌入式系统软件功能的日益强大,其功能和复杂性也日益增加,这导致代码审查的检查项目增多,检查难度增加,进而降低代码审查的工作效率.
(2)审查人员工作成本增加
根据代码审查的定义,代码审查也是一种程序理解活动.代码审查工作的进行速度依赖于审查人员的专业知识水平和经验.为了保证工作效率,软件复杂度的提高间接提升了审查人员的专业知识和审查经验的门槛,审查人员的工作成本增加.
(3)现有代码审查辅助软件功能不强
现有的辅助软件大多都是以静态分析技术为基础,如软件编程规范检查、资源访问冲突分析(如specchecker)等.这些软件在许多软件功能性的检查项目上能力较弱,只能提供静态数据让审查人员去分析,对审查人员的帮助有限.
以上三个问题制约着嵌入式代码审查的审查效率,是代码审查目前所面临的主要挑战.
1.5 机遇
软件代码审查中,审查人员阅读代码、理解代码、审查代码的过程可以看作是程序理解活动.程序理解存在于认知代码相关的任何领域,同时也是一个研究领域.现今,随着人工智能技术在程序理解领域的应用,出现了一些先进的程序理解算法,这些算法相较于传统方法更加智能,能帮助程序员进行程序理解.这些程序理解算法的出现对代码审查来说是一个机遇:将基于人工智能的程序理解应用于代码审查过程,能够减轻审查人员的认知负担,提升代码审查的工作效率.
2 程序理解
程序理解是软件工程中的一个经典话题,又叫软件理解或系统理解.自软件出现以来,甚至在软件工程提出之前,就有了程序理解这个问题[6].1968 年第一次软件工程研讨会之后,程序理解成为软件工程中的关键活动,在进行软件重用、维护、迁移、逆向工程以及软件系统扩展等任务时,都要依赖于对程序的理解.比如,源码逆向工程通过分析目标系统来识别系统的成分及其相互关系,创建系统的更高抽象层次上的表示,包括构建目标系统的概念模型、抽取程序结构和控制信息以及进行数据抽取和系统抽象.在软件维护和演化中,例如软件适应(adaptive)、软件修复(corrective)、软件复用(reuse)等任务,程序理解也是其首要活动,其他活动都是在程序理解的基础上进行的.例如:完成软件适应任务的流程包括理解系统、定义适应需求、制定适应策略、设计、代码修改、调试、回归测试;软件修复任务的流程包括理解系统、提出/评估问题假设、修复代码、回归测试.随着程序设计语言的不断变化和软件复杂性不断提高,程序理解一直面临着挑战,新的研究问题不断涌现.在实际的软件工程项目中,程序员往往会花费超过一半的时间用于程序理解活动[7],程序理解研究的重要性不言而喻.
本节从三个方面介绍程序理解,分别是程序理解的认知策略、程序理解的方法和程序理解的挑战与机遇.
2.1 程序理解的认知策略和方法
程序理解是一个学习和认知的过程.代码是编程语言形式的知识编码,而程序理解就是要从这些知识编码中学习代码所表达的知识的过程.根据构建式学习的理论,学习和认知的过程是面向目标或者模型驱动的归纳抽象过程.第一,学习是学习者个体从学习材料中通过提取或抽象等手段构建知识体的过程;第二,所构建的知识体除了依赖学习材料,很大程度上还依赖于学习者已有的知识背景和本次学习的目的.从构建式学习理论出发,程序理解的基本策略可以分为三种:
(1)自底向上的策略
该策略从具体的代码开始阅读,然后将具体代码的含义组合,形成更高级的抽象信息,最后在认知层面形成一个具有整体抽象意义的知识集合.例如,一种程序认知框架区分语法和语义两个层次:语法层关注程序的语句和基本单元;语义层与程序语言无关,是逐层抽象直到所抽象的知识能描述应用领域.另一种认知框架区分程序模型(program model)和情景模型(situation model):程序模型对程序的控制流进行抽象;情景模型则是封装了关于数据流抽象和功能抽象的知识,表达了程序的目标层次[8].用传统的软件开发方式可以解释这种策略背后的原因,即,程序理解是为了还原自底向上的软件开发过程.
(2)自顶向下的策略
这种策略将程序理解看作是重新组织程序员自身已有的关于程序应用领域的知识,并建立这些知识到程序源码上的映射.比如,一个典型的过程是:从关于领域应用的假设出发,根据已有的领域知识进行逐层分解,同时与程序源码进行比对或验证,从而建立假设和源代码之间的关联.这种方式体现了模型驱动的思想,也就是:如果模型存在,程序理解就是将具体的程序片段关联到已知的模型上,通过模型的结构和语义去刻画程序的结构和语义.
(3)集成式程序理解模型
集成式程序理解模型结合了自底向上和自顶向下的策略,它认为:对比较熟悉的代码,可以采用自顶向下的方式进行理解,直接建立源码和应用知识之间的关联;而对不太熟悉的代码,则需要采用自底向上的策略从底层开始进行逐层抽象,以获得程序所表达的领域知识.比如von Mayrhauser and Vans的集成式程序理解模型就由自顶向下策略、构建程序模型、构建情景模型、知识领域四个成分组成.
2.2 程序理解的方法
程序理解的方法是程序理解的认知策略在工程上的实现.现有的程序理解方法可以分为基于分析的方法和基于学习的方法.
基于分析的方法分为静态分析方法和动态分析方法.静态分析方法直接分析程序源代码,从中获取相关信息,并且不需要执行程序.静态分析方法由于其特点具有较强的灵活性,在开发的早期阶段就可以进行,相比动态方法更具有普适性,可以覆盖所有可能的执行路径.IDE(集成开发工具)中常用的代码语法纠错就是一种静态分析技术,它在程序员编程阶段就能分析源代码,帮助程序员检查语法错误;动态分析方法是程序执行时运行的分析方法,它主要理解程序运行时所具有的性质.动态方法可以通过提取输入输出关系或者内部状态获得更精确的分析,但是往往是针对特定输入而言,因此普适性不及静态分析.
基于学习的方法是一种应用了数据挖掘或机器学习技术的程序理解方法.近年来,随着计算机运算能力的提升,计算机在短时间内处理大量信息所需要的时间不断缩短,许多基于学习的方法得以广泛应用.数据挖掘和机器学习技术在图像处理、自然语言处理、模式识别等多个领域取得了丰硕的成果.因此,基于学习的技术也受到了更多研究者的关注,一些研究者开始使用这些相关技术进行程序理解.基于学习的程序理解主要是从源代码和代码文档中获取信息,使用数据挖掘或者机器学习技术学习程序的相关特征,并在实际的程序理解使用.例如,文献[9]使用了基于有监督学习的神经网络学习不同编程语言的源代码特征,并使用这些特征进行源代码文件分类任务.
2.3 基于人工智能的程序理解
传统的基于静态或动态的程序理解一般致力于获取并分析程序部分特征来达到认知程序的目的.这么做的优点是能够使用较少的数据和计算量获得较为准确的局部认知结果.随着计算机硬件能力的发展和开源项目的增多,发展基于大量代码的认知计算方法已具备足够的条件,基于机器学习的程序理解方法顺势而生.
早期的基于学习的程序理解技术多使用经典的机器学习方法,如SVM、随机森林、决策树[10]等算法.相较于传统基于分析的程序分析方法来说,机器学习算法可以接受信息密度更高的特征输入,因此程序特征的提取方法可以做得更一般化,同一类程序特征可以供多个相近任务使用.另外,经典的机器学习技术可以通过训练强化与任务相关的程序特征,减弱无关程序特征的影响,一定程序上也防止特征维度过多产生计算复杂度爆炸的情况.
基于传统机器学习的程序理解技术在一定程度上摆脱了过度降维带来的信息损失,但它仍然存在不足.例如,仍然需要降维处理获取特征、过多的特征带来了庞大的计算冗余等.21世纪10年代,人工智能领域迎来了新的革命——深度学习技术.与传统机器学习方法不同的是,深度学习致力于直接从信息的本体,即信息源挖掘信息特征,并进行相关任务.众多领域随着深度学习的兴起迎来新一轮的革命,人工智能技术也随着深度学习技术的普及,正式广泛应用于民用领域.
深度学习技术[11]相较于传统机器学习技术的优势在于,深度学习更不依赖特征工程.例如,对于人脸识别问题,传统方法往往是设计相关特征,让算法能识别出眼镜、嘴巴、鼻子等器官,再综合判断这些特征组合起来是不是一个人脸;而深度学习方法只需要将图像输入神经网络中进行计算,最终直接获得结果.算法模型在训练的过程中自己会在隐藏层中就完成特征的认知、学习和识别.这种优势让深度学习大大降低了对特征工程的依赖.
深度学习技术的特性也让学者们注意到其在程序理解领域应用的潜力.科学家们开始将深度学习技术应用于基于学习的程序理解方法中.如图1,本文调研中收集的程序理解相关文献中,09年后基于学习的论文数量就已经超过了传统基于特征分析方法的文章,15年以后基于学习的程序理解更是占比83%.近五年内发表的基于学习的程序理解研究中,63%的研究是基于深度学习技术,且论文随着时间推移数量呈上升趋势(截至19年11月,一些会议尚未举办,所以19年未计入统计).由此可见深度学习技术在程序理解领域也是同样广受欢迎.
2.4 程序理解的挑战与机遇
程序理解发展到今日已经有了非常丰富的成果,但仍然面临着准确性、可拓展性等问题的挑战.新技术的引入对程序理解来说既是一种新的发展机遇,也对研究人员来说是一种挑战.人工智能技术能否很好地运用在程序理解领域是目前需要探索的一个命题.
近几年深度学习逐渐成为程序理解领域更为关注的技术.如图1所示,近四年的程序理解论文中,基于深度学习技术的文章数量处于一个上升趋势.
图1 程序理解相关论文发表情况Fig.1 The program understands publication
3 人工智能技术在代码审查相关领域应用现状
人工智能应用于实现基于学习的程序理解方法.基于学习的程序理解是一种从程序代码中获取知识,并利用通过学习获得的经验去对这些知识进行处理,最终获得想要的结果输出的过程.因此,实现基于学习的程序理解必须解决以下三个问题:
(1)如何进行知识表示?
(2)如何利用知识获得期望的结果?
(3)如何将软件与实际的程序理解工作相结合?
对于知识表示问题,研究人员普遍采用代码词嵌入作代码知识的表征,将代码与知识相互映射,形成关系;获得了从代码中提取的知识后,需要利用这些知识得到特定的结论才能完成程序理解.目前大多数基于人工智能的程序理解都是使用有监督学习模型实现的,它们的功能可以归类为代码分类;因为代码审查工作是一个小组协作且需要大量人机交互的过程,获取了基于学习的程序理解后,还要将软件与实际的工作相结合.下面将对代码词嵌入、代码分类和结合了人工智能技术的智能代码审查系统作介绍.
3.1 代码特征的表征:代码词嵌入
程序理解中有一类重要的问题是代码的表征,即让计算机将程序语言表示为人可以理解的抽象概念.上述过程可以看成是将一个维数为代码长度的高维空间,嵌入到一个维数低得多且每个向量映射为一个程序概念的连续向量空间中的过程,这与自然语言处理领域(NLP)中语言模型与表征学习非常相似.在NLP中语言模型与表征学习技术统称为词嵌入技术,因而在程序理解领域,我们将代码的语言模型与表征学习技术统称为代码词嵌入.
代码词嵌入的主要目的是程序信息的表征和降维,将代码词嵌入应用于代码理解模型具有深远意义.一般的源代码文件中常含有成百上千行的代码,每行代码由若干单词构成,每个单词由若干字符组成,再加上程序语言的单词种类很少,如果作为程序理解的输入会产生非常多的冗余,这将不利于程序理解保持高效.通过代码词嵌入,可以将代码的含义抽象到低维的向量空间中,让程序代码更易理解且更利于提升更进一步的程序理解的计算效率.
早期的词嵌入使用散列法(Hash),将输入的词语转换为定长度的散列值.文献[12-13]就使用了这一方法去做代码词嵌入.数据处理中最常用的散列法词嵌入为独热编码(One-Hot Encoding),它是一种用N位向量来表示N种状态(抽象含义)的编码方式.由于程序语言的词语种类不多,独热码的长度往往不会很长,因而使用独热编码进行词嵌入既能保证不会占用过多计算机资源,又能获得不错的词嵌入效果.但是,散列法也存在着一定的问题.首先,散列法只能一般用于词语表示,无法囊括更高级的语法表示;其次,散列法获得的词嵌入无法包含一些程序结构信息,所以仍然会产生冗余,单纯使用独热码的词嵌入需要添加很多冗余信息(如括号,分号等)来保持语句的结构性.
为了让词嵌入具有更强的代码抽象能力,研究者们将抽象语法树(Abstract Syntax Tree,AST)应用于词嵌入当中.抽象语法树是一种以树状结构表示程序语言的语法结构,树上的每一个节点都表示代码的一个子结构.一个简单的抽象语法树如图2所示.
图2 简单if语句的抽象语法树.该语法树结构所表示的语句为“if(a==10) printf(“hoge ”);else printf(“piyo ”)”Fig.2 Abstract syntax tree of simple if statements.The sentence represented by the syntax tree structure is“if (a==10) printf (“hoge ”); else printf (“piyo ”)”
抽象语法树利用树形结构简化了程序代码文本,删去了没有实际意义的符号,将具有实意的信息以树的结点的形式保存.抽象语法树常用于程序语言静态分析工具中,它可以结合令牌化(Tokenization)技术完成对程序语言词法、语法和少许语义上的抽象工作.文献[14-15]都在数据预处理阶段采用了抽象语法树和令牌化的代码词嵌入方法,如图3所示.这种方法中抽象语法树的结点信息以向量的形式保存,然后通过一个映射表将结点信息向量中具有实意的字符串与数字进行一一映射,最终结点信息转换为以具有特定意义的数字为样本空间的特征向量,所有特征向量的集合即为整个源程序代码的特征向量.
抽象语法树和令牌化的代码词嵌入技术虽然让输入神经网络的特征向量有了词法、语法和少许语义上的特征信息,但明显这种方法从代码上获取的语义层次的信息是不够的.
在自然语言处理领域,由于语言文字种类繁多且词义组合多变复杂,不同的词语组合会出现不同的语义信息,这些组合产生的爆炸般信息增长让特征向量的维数变得特别大,严重增大了模型的计算负担.显然,语法层次的预处理成为了自然语言处理模型的性能短板.为了进一步减少输入样本空间的大小(维度),模型需要在进行任务之前对文本进行更强的语义级的词嵌入.经过研究者们的努力,学术界诞生了以Word2Vec[16]为代表的语义级文本词嵌入,自然语言处理模型的能力得到了极大的提高.语义级词嵌入的发明意义在于它进一步对输入的信息进行了降维,加强了模型的运行效率;同时它让词语之间产生了一定的语义上联系,提高了模型的泛化能力.程序理解领域也有与自然语言处理相似的预处理问题.程序语言的词语种类虽然不多,但是通过不同的组合,相似语法结构的程序会执行完全不同的功能.比如有的循环语句是用来赋值的,有的循环语句是用于排序数据结构的.再者,由不同的程序语言写成的代码虽然在词法、语法上可能不同,但它们可能执行着相同的功能.如果这样的预处理应用在对代码功能进行分类的模型上,这些预处理获得的特征向量将带有词法、语法层面的信息冗余.倘若模型在预处理阶段就能获得这些代码语义级别的表征,在后续阶段的处理中这些词法、语法级别的冗余内容将会被忽略,模型的效率和泛化能力将会得到进一步加强.
最新的研究成果中,已经有研究者将语义级的词嵌入扩展到代码词嵌入中.文献[17]提出了可用于代码预处理的一种将代码表示为连续的分布式向量的神经网络模型.该模型在抽象语法树的基础之上增添了一个神经网络,该神经网络经过训练后可以获得一个上下文特征向量和一个注意力向量,这两个向量与所有代码语句令牌化的抽象语法树特征向量进行运算后获得了全局代码的特征向量,这个全局的特征代码向量即可直接用于预测相应的标签.简单的来说,代码经过该模型处理,输出的不再是以词句信息或者语法结构信息为主要信息的特征向量,而是以带有上下文信息的语义信息的特征向量.这种特征向量具有更强的信息抽象,因而模型具有了更好的泛化能力.文献[18]提出了另一种语义级的代码词嵌入技术.与文献[17]提出的词嵌入技术不同的是,该模型是基于循环神经网络的编码器-解码器结构实现的,此种结构与自然语言处理中的翻译机的结构相似.模型实现原理略微不同,但是它们都是对代码进行语义级的特征映射,得到相应的特征向量.
图3 抽象语法树和令牌化的代码词嵌入流程图Fig.3 Abstract syntax tree and tokenized code word embedding flowchart
3.2 功能模型:代码分类
程序员通过阅读代码,对代码的功能或特性进行分类是一种常见的程序理解活动,实现这种程序理解活动的人工智能模型统称为代码分类模型.常见的代码分类任务有源码分类、功能分类、缺陷预测等,其中又以缺陷预测最为受到关注.代码审查中,评判源代码是否有缺陷实际上就是一种代码缺陷预测任务.因此,下面围绕以代码缺陷预测为主的代码分类技术的发展作介绍.
早些年,代码分类依托程序分析技术进行.按照是否运行源代码进行分析,程序分析技术可分为静态程序分析和动态程序分析两种,静态程序分析不需要运行源代码直接分析代码的属性,动态程序分析则主要分析程序运行中产生的输入输出以及状态变化.常用的静态程序分析方法有代码度量分析、程序抽象语法树分析、函数调用关系分析等;常用的动态分析技术以数据流分析为主.早期的代码分类模型依托程序分析技术获得程序的相关信息,通过对这些信息的对比来进行代码分类工作.
虽然传统程序分析技术对程序理解工作有一定的帮助,但这些分析技术在某些任务上难以取得比较好的效果.比如抽象语法树只能从程序语言语法层面提取代码特征,遇到语法不同的源代码文件就需要对二者的特征进行归一化处理,归一化处理的过程中会产生信息的丢失或冗余,影响代码分类结果.传统程序分析技术还存在着易用性低、操作略微繁琐的问题.文献[7]中提到有相当一定数量的程序员没有采用或者很少采用审查辅助工具去进行代码审查,其中的原因便是传统审查工具缺少使用友好性,程序员很难将审查工具与自己的审查习惯结合.
随着人工智能技术的发展,人工智能技术在代码分类领域逐渐得到应用.文献[19]使用了多种机器学习算法进行代码缺陷预测工作,这些机器学习算法包括Bagging、Boosting、Decorate、C4.5、Naive Bayes、神经网络、随机森林和RIPPER,并比较了各种机器学习算法的性能.值得一提的是,文献使用软件的代码度量和测试度量作为特性对代码缺陷进行预测.在所有方法中,随机森林总体上取得了较好的结果.文献[13]使用了卷积神经网络进行源代码编程语言地分类.该任务只需要依据源代码编程语言特征进行分类,所以在只使用了简单的代码词嵌入的情况下仍然取得了95%以上的正确率.文献[14]、文献[15]分别使用深度信念网络[20]和卷积神经网络进行代码缺陷预测,后者声称相较于前者的方法获得了更好的结果.这两个模型在预处理阶段使用了相同的抽象语法树结合令牌化的方法,区别只在于后面神经网络使用的不同.根据两个文献的比较可知,卷积神经网络在此类缺陷预测中有更好的效果.
3.3 智能代码审查系统
除了程序理解领域,研究人员也试图将人工智能方法与代码审查工作相结合.
审查辅助软件作为程序理解必不可少的工具,在代码审查工作中的作用却较为有限.这是因为,代码审查大多数是一种静态测试方法,而现有的静态测试工具能完成的程序理解较为基础,实际工作中往往只能给予审查人员更多参考信息,而不能替代审查人员进行程序理解.以往的大多数关于代码审查的研究也显示,现有工具对审查人员进行程序理解的帮助非常有限[7].基于机器学习的人工智能方法让研究人员看到了新的曙光,他们开始尝试将基于人工智能的程序理解引入代码审查系统中,增强代码审查软件的辅助能力.
文献[21]提出了一种基于深度学习的代码审查系统.它自动学习历史同行评审,建立代码段和代码评审与分析之间的联系,并将这种匹配关系应用到新的代码评审中,对新的待评审代码进行分析,生成这个待评审代码的评审项目.生成的评审项目给与评审人员一定的建议,告诉他们哪些地方的代码最有可能需要进行审查以及进行什么类型的审查.该模型是一种交互式的有监督学习模型,模型除了使用前需要进行训练外,随着历史评审数据的增加,模型还能进行二次训练,通过不断累积历史评审数据来达到提升准确性的目的.它虽然做到了帮助审查人员进行一部分的程序理解,但仅限于向审查人员给与审查项目的建议,还无法做到帮助审查人员去理解审查的代码,完成一部分代码缺陷的评判,所以仍然有进一步发展的空间.
文献[26]描述了一种深度学习技术在代码搜索上的应用.该文描述的模型使用了代码词嵌入和文本词嵌入,将代码和文字映射到同一语义空间中,通过匹配代码和文本的语义向量来完成基于文字信息的代码搜索.该研究虽然不是针对代码审查工作,但是它所描述的方法很适合应用于代码审查的场景.例如,在做需求一致性审查时,审查人员往往需要根据需求去寻找对应代码.基于文本的代码匹配就可以很好地帮助审查人员更快地定位代码位置,对代码进行审查.
从审查人员的角度来看,智能化的代码审查系统和他们也是相得益彰的.文献[21]还对他们提出的技术做了用户研究,调查使用者是怎么看代码审查辅助软件的.结果表明,所有被调查者都愿意接纳有更好功能的代码审查软件,即使这个软件只能帮他们推荐评审项目,也明显减少了他们在通用评审上所花的精力,他们也因此可以将更多注意力放在更复杂的功能性审查上.由此可见,在代码审查过程中引入新技术对于审查人员是有显而易见的正面影响的.
4 对智能人工审查的展望
人工智能技术在代码审查领域的应用尚在起步阶段,现阶段应用的技术也较为基础,最新的人工智能技术成果尚未在代码审查领域应用.例如,对于深度学习代码词嵌入有启发性意义的文本词嵌入方法word2vec早在2013年就被著名的科技公司google开源;卷积神经网络、循环神经网络的诞生更能追溯到上个世纪末.之所以这些技术在近几年才引入程序理解领域,是因为这些人工智能技术获得的成就足够成功,这才引起了更多研究人员的兴趣并尝试将这些方法应用于其他领域.参考学术界最新的技术发展,我们分别对未来可能用于程序理解和代码审查领域的技术和可能出现的代码审查辅助软件做一个展望.
4.1 对人工智能技术的展望
当今最火的人工智能技术就是深度学习.深度学习技术通过学习样本数据的内在规律和表示层次让算法具有分析能力,因而在很多任务中都获得了相当好的成果.程序理解作为一种研究对程序代码认知方法的学科,非常适合应用深度学习相关技术.
在代码表征方法上,目前最新的方法使用注意力网络[17]或基于RNN的编码器-解码器结构网络[18],这两种方法都是为了加强模型预处理阶段对代码语义特征的表征.相比于这两种特征抽取神经网络,Transformer[22]拥有更强的表征能力,现在多数最新的自然语言处理模型都基于由transformer组成的Bert模型,并获得了相当可观的进步.除了代码文本的表征方法外,还有代码属性的表征方法.传统的代码属性表征方法是将代码属性直接转换为特征向量输入人工智能模型中计算,而图网络[23-24]的存在提供了另一种思路:使用图网络进行代码属性表征[25],既可以让代码的各种属性保存在图的节点之上,又能让属性之间产生一定相关性,体现代码属性之间的内部关联.虽然代码不像文本语言那种具有丰富的词性和词语之间的相关性,强大的抽象表征方法显得有些“性能过剩”,但将这些先进方法用在程序理解领域也不是一件没有意义的探索.
人工智能技术在程序理解领域的应用相比其他热门领域数量较少,目前它们的主要应用场景还是以缺陷检测、代码分类为主.这些场景执行的功能较为简单,所以模型也并不复杂,深度普遍不高,且以有监督学习模型为主.这也是一个有发展潜力的地方.
4.2 对智能代码审查系统的展望
代码审查工具发展至今日,审查软件仍然以被动的程序理解为主.被动的程序理解是指辅助软件需要审查人员自行操作才能对审查中的代码进行分析,这一特性无疑加大了审查人员审查操作的繁琐程度.如果软件可以替代审查人员进行一部分的程序理解,代码审查的难易度和效率定会有所提升.因此,发展主动的程序理解方法对帮助审查人员提高高效审查工作有深远意义.
另外,代码审查工作是一种多人协同的工作,对一个大项目的审查往往需要数个审查人员组成的审查小组来完成,因此代码审查是拥有一个独立系统的.代码审查软件如果可以将每个审查人员的成功联合,实现群体智能,无疑对审查工作的质量提升也有非常大的帮助.
图4 智能代码审查系统框图Fig.4 Block diagram of intelligent code review system
基于文献[21]所述系统和以上介绍的需求,我们对未来的智能代码审查系统作了番构想,如图4所示.编译器/开发环境位于系统的前端,是软件系统与审查人员进行交互的场所;智能审查工具从编译器/开发环境中独立,负责进行代码信息和知识之间转换处理;云端知识库存放各种知识,并在系统中与每一个人机交互端共享知识.在系统中,智能审查工具独立进行训练和执行,但训练和执行所需要的相关信息需要审查人员进行操作,审查人员对智能审查系统的学习起到了监督的作用,保证代码审查仍然以审查人员为核心进行.
相信随着更多更强的智能程序理解应用于代码审查任务,智能代码审查系统将获得更强的审查能力,进一步帮助审查人员减轻工作负担,审查工作将变得更简单且高效.
5 结 论
现代科技日新月异,造就了嵌入式软件代码审查的需求不断增多,代码审查效率势必要有所提升才能满足这种增长.在已有的人工审查手段成为瓶颈的情况下,引入新的软件技术帮助提高代码审查的效率就显得尤为重要.人工智能技术身为目前发展最为迅速的软件技术,对当前代码审查来说是一个不小的机遇.
本文首先对嵌入式代码审查的情况作了简要的介绍,引出代码审查的必要活动,即程序理解.通过程序理解的定义、方法以及所面临的挑战与机遇的介绍,阐述了现今与代码审查关系较为密切的人工智能程序理解技术的发展与应用情况.由介绍可知,目前以深度学习为首的人工智能技术在程序理解乃至代码审查领域的应用仍然数量较少,落后于最新技术较多.因此,人工智能技术在代码审查领域仍然有很大的应用潜力,这需要更多的研究人员参与到该领域的探索中.未来更加智能化的代码审查系统值得期待.