增值提效:代码大数据助力研发效能提升

2020-08-10 15:52:51 浏览数 (1)

本文根据复旦大学计算机科学技术学院副院长彭鑫在 GNSEC 线上峰会的分享整理而成。

作者简介

彭鑫,复旦大学计算机科学技术学院副院长、教授、博士生导师。中国计算机学会(CCF)软件工程专委会副主任、CCF YOCSEF 上海2016-2017主席,上海市计算机学会青工委主任《Journal of Software: Evolution and Process》联合主编(Co-Editor)、《软件学报》编委、《Empirical Software Engineering》编委,IEEE软件维护与演化国际会议(ICSME)执委。2016年获得东软-NASAC 青年软件创新奖。主要研究方向包括代码大数据、智能化软件开发、软件维护与演化、移动计算与云计算等。 联系邮件:pengxin@fudan.edu.cn

一、问题分析

当前我们软件研发效能方面存在一系列的障碍,我们认为软件制品及开发过程的不可见性是导致这一系列障碍的根本原因。

我在去年 DevOps 国际峰会报告当中曾经总结了几个软件开发中的普遍问题,首先是软件维护的冰山,我们软件代码当中存在很多质量问题,这些问题形成很多都是冰冻三尺非一日之寒,而且我们程序员在长期软件开发和维护过程中形成击鼓传花、法不责众的心态。

其次是随风消逝的知识。我们很多有用的软件开发知识、软件资产以及有意义的思考过程都没有得到有效的记录和利用,造成代码存在某种神秘主义,很多代码难以理解,无法准确把握代码意图,同时普遍存在重新发明轮子的问题。

然后是开发过程不透明,很难去洞察一个团队和开发人员实际开发的效能,程序员的工作难以评价,能力基线难以建立。

最后是脆弱的软件供应链,大量存在代码拷贝以及对于第三方库的依赖,这样有很多风险。

以上这几点都源自于一个根本性问题,即软件制品以及软件开发过程的高度不可见性。

这些问题对于软件研发效能提升造成了诸多障碍,使得代码中存在很多风险点。这些风险点要么难以被我们及时发现和洞察,要么有一些工具可以给我们一些检测结果和反馈,但是存在误报率过高以及其他问题,使得其结果很难被我们利用。

软件知识难以积累。各种软件项目中包含很多通用代码资产,但是很难被识别和有效利用。还有很多软件开发问题总是反复出现,但是其解决方案很难进行捕捉和积累。文档和其他代码知识难以被有效利用。

优秀实践难以确立。程序员基于热情和责任心会开展一些很好的开发实践,例如精心书写代码和提交注释,但是这些好的开发行为难以被发现和认可。项目和人员的开发能力基线难以建立,很难通过对开发时间以及代码质量的衡量对合理的能力基线进行估计。

说到研发效能,大家很自然就会想到度量。正如很多人所信奉的“如果你不能度量它,你就无法改进它”。然而,在现实中很多企业所开展的软件开发度量很容易陷入一种困境,使得度量指标成为一种教条主义。度量指标的推行者往往忘记了度量的初心是什么,会忽略软件开发上下文差异的影响,忽略了度量指标在不同模块中的含义有所差异。这样以一刀切的方式去推行度量并附加一些强制手段就会出现很多问题。

开发人员大多都是很聪明的,经常会趋利避害,所以经常听到这样的说法:“你衡量什么就得到什么”以及“一抓就死,一放就乱”。比如有的企业用代码重复率作为指标去要求开发人员降低代码重复率,由此造成的结果是很多开发人员故意把自己的函数或者方法一分为二,甚至分解出来的方法和函数命名都带有数字,这种机械和物理式的代码拆分纯粹就是为了降低连续代码的重复率。这样得到的结果反而使代码可维护性、可读性变得更差了,这个不是我们想看到的结果。

二、整体设想

针对以上这些问题,我们提出的思路是建立一种软件开发的质量追溯体系,以代码大数据分析助力研发效能的提升。

这一思想受到了制造业、食品业等领域成功经验的启发。这些行业中已经广泛建立了产品质量追溯体系,可以实现来源可塑、去向可查、责任可追、质量可控的质量管理目标。

参照这种思想建立软件开发质量追溯体系是很难的,因为软件制品及软件开发过程是高度不可见的,而且组成软件的成分很多都难以辨识,并且边界非常不清楚。

这些年以来,DevOps 和云化开发平台有了长足发展,并且得到广泛的应用。基于云的DevOps 开发平台中软件工具高度集成化、流水线化和云化,使得我们可以得到反映开发运维全过程中的丰富数据,使得构造基于代码大数据的软件开发质量追踪体系成为可能。

我们所说的代码大数据其实覆盖了以代码为中心的软件开发演化过程当中的各种软件制品和过程数据。

首先,我们可以对代码快照可以进行代码成分检测,包括组成代码的文件、类、方法等代码单元以及相关的代码度量、通过静态扫描发现的代码缺陷等。其次,以代码提交 Commit 为抓手,可以对代码演化过程进行连续的追踪。

针对每个 COMMIT 可以分析对应的开发者是谁,对应的开发任务(例如说问题单)是哪个,以及 COMMIT 中的代码修改内容,这个可以通过代码差异比较来识别。进一步可以把不同版本中的代码单元与其它问题(如静态扫描工具发现的静态缺陷)进行追踪,通过演化追溯去了解相关问题最早在哪个快照当中引入以及后续整个发展过程是怎么样的。

还有前面提到过的代码中所存在的外部来源,可以通过高效的克隆检测来发现所引用的外部代码,通过依赖分析发现第三方库依赖以及背后所隐藏的间接调用链,在此基础上可以形成软件供应链的追踪分析。还有产品构建和发布,可以通过构建日志分析对于构建的过程以及构建过程当中使用的代码元素版本、工具环境等进行一个追踪和记录。

通过这种方式构建的代码大数据平台包含三个方面内容。

首先是代码快照,其中包含代码的成分信息(包括类、方法、API 代码元素以及它们之间关系),还有各种质量分析、代码度量、缺陷漏洞、代码克隆、代码坏味信息等。

第二是演化过程,通过演化过程可以了解代码差异理解,还有代码演化追溯,在这个基础通过过程追踪把代码修改与对应的开发任务以及后续的代码测试、评审、持续集成活动等进行追踪关系。

第三部分是外部依赖,包括第三方库依赖,还有重复代码以及代码的来源以及重复代码副本的分布情况。

总的来说代码大数据平台是建立在软件开发制品和过程数据全面链接和追踪基础之上的,通过代码大数据平台能够系统性地实现软件开发过程的反馈数据搜集和利用。

通过代码大数据平台,我们希望能够系统性实现代码质量追踪和开发知识积累。在代码质量追踪方面希望通过纵向贯穿演化历史,横向覆盖外部项目来源的方式来实现代码以及相关的质量问题全方位追溯分析。在开发知识积累方面,希望以全面链接和追踪的方式来记录各类问题的产生、发展和解决过程,将工具的输出与人工的反馈相结合,沉淀各种软件开发知识。基于代码质量追踪和开发知识积累,我们希望能够面向开发人员以及质量保障和管理人员提供精准效能分析,从而来助力软件开发的提质增效。

三、实践探索

代码大数据分析实践以一系列代码分析技术为基础,包括以下几个方面。

静态分析,例如代码静态缺陷扫描、代码度量和坏味道分析等,这方面可以集成现有的SonarQube等第三方分析工具的输出结果。

演化分析技术,包括代码的逻辑差异比较,意味着对于每一个Commit可以分析代码逻辑差异,而不是物理代码行的差异。逻辑差异包括比如说是不是新增了一个方法,是不是修改了方法的参数,是不是新增了API调用等。此外还可以通过演化历史切片技术把所关心的代码单元或者是代码问题相关的发展变化历史切取出来进行针对性的分析。

克隆检测,这方面我们自己研发了一种高性能的代码克隆检测技术,可以支持多粒度、多形态的代码克隆分析。

依赖分析,包括内部和外部代码依赖分析,例如内部不同的模块或者是不同的文件之间的依赖以及第三方库依赖分析。

在以上这些分析能力基础上可以开展丰富多彩的代码大数据分析应用,这里通过几个典型分析方式进行举例说明。

首先看代码缺陷跟踪,是指通过各种静态扫描工具能够检测发现潜在代码质量问题,包括代码的坏味道以及潜在的漏洞和功能性的缺陷等等。针对代码的版本快照,我们可以使用 SonarQube 或者是 FindBugs 这样的静态分析工具来发现潜在的代码缺陷。如果没有大数据分析能力,仅仅拿到这些工具的分析结果很难有效利用,工具检测结果很难成为软件开发的数据积累。

使用代码大数据分析对于当前文件历史版本持续开展检测和监控,并且通过我们的演化追溯技术把每个版本扫描到的问题建立追踪关系,从而让我们知道版本快照所扫描代码缺陷其实已经不是新问题,而是已经存在一定时间的老问题。同时保持追踪过程可能发现到某一个版本经过一次代码提交之后问题已经不复存在了,这说明对应的程序员可能已经把这个问题修掉了。

通过这种代码缺陷追溯过程,可以针对当前代码缺陷建立一个案例记录,就像公安部门针对一个案子开了一个档案一样,那么这个案例不是针对某一个版本快照上的问题,而是针对在连续的代码演化过程中,在同样代码的位置上代码缺陷的连续案例的记录。

比如说针对代码的缺陷案例可以记录缺陷的类型、内容、严重程度、存续的时间,缺陷从首次引入到最后移除的间隔时间,以及谁引入、谁修复的。

这种档案化的管理还可以结合人工反馈形成有效的数据积累。人工反馈是指程序员在某些时候通过代码数据平台去查看当前代码缺陷给出评论和反馈,比如说是不是误报,问题严重程度如何等。这种人工反馈可以成为代码大数据的一部分。在此基础上可以通过代码差异比较发现修复同类缺陷的代码修改模式,这样抽取缺陷修复方案之后就可以形成有效的知识积累,在有需要的时候将缺陷修复方案推荐给其他开发人员使用。

我们的研发过程追踪分析能力可以对于代码问题进行有效追溯分析问题,比如针对代码中存在的坏味道以及过高的复杂度等问题,可以去追溯并分析其成因及相关的人员。例如,当前这个方法的圈复杂度非常。通过演化过程的追踪,可以将对于当前方法的圈复杂度影响比较大的几次代码修改给选取出来,这个可通过演化切片技术来实现。可以看到,有三次代码修改对于当前的方法的圈复杂度有比较大的影响,第一次是有所下降,从9到5,第二次有比较大提升,到最后是变成23。

在此基础上可以查看三次的具体代码修改。第一次做了类似于重构的操作,把一些处理逻辑进行了抽取,所以使得圈复杂度有所下降。而后面两次都是进行其他的修改操作,造成圈复杂度的上升,每次修改对应的时间以及开发者都可以通过分析来知道。

代码克隆危害分析。代码克隆也就是重复代码,普遍存在于开源和企业项目当中,有一些项目看到代码重复率搞到30%到50%甚至更高,这么多代码克隆完全消除是几乎不可能。那么如何评估代码克隆的危害度,这件事情应该追根溯源。

考虑一下我们为什么经常说代码克隆是有害。首先是因为额外克隆副本维护开销,有很多重复副本,如果需要对这些副本进行一致性的修改,每次修改要修改很多次不同的地方,那么就会带来额外的维护开销。其次,遗漏某些克隆副本的修改可能导致其中问题的残留,从而导致质量问题。此外,代码克隆还会对设计造成影响。

基于代码大数据分析的代码克隆危害评价分为以下几个类别,首先是代码克隆副本无需修改,引入之后多个副本一直保持不变。第二种情况是克隆副本需要进行修改,但是不需要保持一致,就是说克隆副本有修改但是大家各自修改不需要保持一致。

第三种情况是克隆副本保持一致性修改,但是比较好保持同步,可以在第一时间完成所有克隆副本的修改。

第四种情况是克隆副本需要进行一致性修改,而且不同克隆副本之间的一致性修改存在时延。

最后一种情况是克隆副本存在一致性修改时延,并且这种一致性修改的拖延导致了缺陷的残留。

可以看到刚才讲到这两种克隆的危害情形主要对应后面三种情况。基于我们对于代码克隆危害的解读,可以发现这里5种情况的克隆危害度各不相同。

第一种和第二种是无害的,克隆副本引入并没有为我们带来额外的维护开销也没有带来缺陷的残留。第三种有轻微危害,因为使工作量有所上升,但是从历史数据分析来看还是能够很好去解决一致性修改的问题。

第四种情况有中等的风险,这个时候克隆副本一定修改有所延迟的,使得我们对于未来缺陷的出现可能会有一定的风险,所以是中等的危害。最后一种是严重风险,因为历史上因为有代码克隆副本的延迟修改而导致缺陷的残留。

基于这几个分级可以定义一套代码数据分析指标来帮助我们去分析代码克隆的危害,比如说可以形成以下一系列的分析指标,包括克隆代码修改率、一致性修改率、一致性修改时间差、还有一致性修改延迟导致缺陷数等。基于这样代码大数据分析为代码克隆的危害给出相对定量的评价。

基于以上所介绍的基于代码大数据的代码克隆危害分析方法,我们针对4个开源的项目和3个企业软件项目开展了案例研究。所涉及的代码总行数约 1700 万行,其中克隆代码约 357 万行,共包含 23,560 个克隆组。可以看到这些代码克隆中,超过 75% 的都没有危害,而存在低、中、高危害的代码克隆分别占 14.1%、10.2%、0.5%。

在分析案例当中实际发现了与代码克隆相关的代码缺陷。在 SQLITE 中 FTS3ICUC 和FTS2ICUC 两个文件中存在相似的函数 ICUOPEN。2008年9月2日项目增加了一个功能,其中 FTS3ICUC 文件中的 ICUOPEN 函数进行上述修改,2008年12月进行了 FTS2ICUC 文件中同名函数也进行了同样的修改,目标是修复同样问题。这里的一致性修改延迟实际导致了代码缺陷残留。因此,需要对代码克隆产生和演化过程保持持续管理和监控。

代码克隆也蕴含一些通用的代码资产,所以我们也进行了一些基于代码克隆的代码资产抽取实践探索。通过研究我们发现在跨项目的代码克隆里面存在以下几种常见公共代码资产的类型,包括测试代码(很多测试代码过程是相似),还有通用技术功能,比如说JSON处理相关的通用代码等等。

这里我们可以看到一些例子,包括在不同项目中发现的FTP文件传输不同的实现代码副本,还有关于 BASE64 加密重复副本等。通过重复代码克隆代码的分析,我们可以及时发现重复的代码,并且及时进行公共代码资产的抽取。

我们还可以把代码克隆的分析与代码依赖分析相结合,对于整个设计质量进行评价,通过跨项目、跨模块克隆检测识别通过功能实现,然后分析其中在各自代码依赖结构中的位置。

我们可以看打在当前这两个项目当中,我们可以发现有一些通用的功能实现,例如数据库操作文件操作报表的操作网络打印等等,这些是通用功能实现,通过代码克隆的检测来发现他们在不同的项目里面实现的副本。

另外我们本身对这两个项目各自代码依赖结构,箭头代表他们的依赖调用的关系,这样形成了依赖的层次结构,在这个层次结构可以把重复代码单元可以把位置标注上,通过这样叠加分析可以发现设计上的问题。

第一个项目对于通用功能进行统一分装,避免重复实现,并且通用的功能在依赖结构上面是一个比较合适的层级,位于底层,封装了通用的功能使得上面的公用调用它,类似实现散步的各处,所处的以来结构层级也不合适。

我们前面也提到过软件所直接或者间接依赖第三方库蕴含很多质量和其他方面的风险。这其中主要包含两个问题:

第三方库版本过时,可能有缺陷和漏洞,但是考虑新版本不兼容API,可能不想去升级或者不知道要升级,因为需要付出升级的代价。从当前第三方库版本升级更高版本可能存在API差异,意味着升级会付出一定的代价。

第三方库版本不一致,软件包含很多模块,不同模块都会依赖同一个第三方库甚至不同的版本,这种版本不一致增加开发人员维护代价还引起依赖冲突,这个方面提供第三方版本统一的推荐。

首先实现版本不一致,当前项目在当前依赖路径当中存在不一致第三方库的版本,另外也会量化统一代价通过API差异来实现,在第三方库依赖追溯,STACK OVERFLOW 进行直接依赖和分析,在这个基础上增加额外的描述,第三方库功能包括开发人员、许可证、演化历史、客户端代码升级案例,形成了第三方库基础上汇集多方知识建立知识图谱,支持多种追溯分析目标。

而且我们也结合依赖分析的技术来实现对于第三方库影响的分析,有第三方库报出来某个漏洞,但是这个漏洞对于当前影响有没有影响,可以根据当前分析得到分析影响,漏洞1通过影响分析发现影响某一个第三方库对外API,但是对外API没有用到,所以漏洞1对于应用项目没有影响。

那么分析漏洞 2,通过依赖链条发现对于应用项目的模块 2 是有影响,所以安全漏洞 2对于应用是有影响,还有第三方库还有存在间接的依赖,在当前片子第三方库以来第三方库,这样间接依赖也可能导致间接的影响,间接依赖可能存在相应的漏洞,这个漏洞可能会对我们当前应用项目产生间接影响,那么发现安全漏洞2对于当前的应用项目也是有影响。

四、总结与展望

我们在分析软件研发效能提升障碍的基础上,介绍了我们代码大数据平台的思想,我们代码大数据平台包括代码快照演化过程外部依赖等等多个内容,核心是视频与过程数据全面连接与追踪,系统实现软件开发知识的反馈收集和凝练,通过多维度、有追溯代码大数据分析助力软件开发提质增效。

最后展望一下,我们希望通过有追溯代码大数据分析来实现有效度量和分析,围绕着现在软件的开发者以及软件开发团队在开展一系列基于软件大数据深层次开发,分析每个开发人员贡献度,分析缺陷引入和排除的比率。

我们还会分析代码的创新度,衡量代码中有多大创新性考虑到代码重复度API通用程度。还会分析代码有效的代码量,意味着你的代码最后经过一个长时间演化之后最后存活并且保留在代码中。还有代码损耗率,在演化过程当中曾经写过,但是并没有发挥效能,这个是所谓的代码损耗。

还有特性实现难度与创新度,这个都是实践探索,通过代码大数据呈现更加深层次度量和分析的结果,从而解决前面提到当前软件研发实践当中我们软件度量所面临的困境。

0 人点赞