静态代码扫描实践
总篇89篇 2020年 第13篇
前言
在很久以前,我们发现在我们的研发测试流程中,大部分缺陷是在代码开发的过程中引入的,然后通过各种测试活动将这些缺陷发现出来,并且修复。我们都知道随着缺陷发现的越晚,缺陷的修复成本越高。那么有没有一种方法,在编码过程中就能将这些缺陷找到呢?
静态代码检查其实就是要在编码阶段发现缺陷,是测试左移的一种手段,当然测试左移手段还有很多,比如单元测试、CodeReview、开发自测等,今天主要介绍的是静态代码检查。我们希望经过静态代码扫描,可以在开发阶段就能够发现大部分缺陷,减少后期功能测试、集成测试、系统测试等过程的缺陷量,从而提高质量和效率,节省成本。本篇文章主要介绍静态代码扫描的基本原理和工具,以及经销商技术部这边近三年来静态代码扫描的演进情况。
静态代码扫描原理和扫描工具
静态代码是指在不运行代码的方式下,通过词法分析、语法分析、抽象语法树分析等技术对程序代码进行扫描,验证代码是否满⾜规范性、安全性、可靠性、可维护性等指标的一种代码分析技术。我们平时听到的静态分析、静态扫描、静态代码扫描、静态代码分析、静态代码质量检查等只不过是名字不同,其实指的都是这种技术,即在非运行时状态检查出代码的某些缺陷。
一般来说静态代码分析程序会分析程序的源码,以及编译后的中间文件。如果是java程序的话就会分析.java后缀的源码文件,以及编译后的.class后缀的字节码文件。静态代码分析主要用的技术分为两类,第一类是缺陷模式匹配,第二类是数据流分析。缺陷模式匹配主要就是通过事先总结好的经验认知,去匹配代码编译过程的token流、抽象语法树和中间代码文件,只要匹配到了就是缺陷。数据流分析是通过收集代码中引用到的变量信息,从而分析变量在程序中的赋值、引用以及传递等情况。
目前市面上有一些针对java的静态代码分析工具。其中Checkstyle 更偏重于代码编写格式,及是否符合编码规范的检验, 对代码 bug 的发现功能较弱;而 FindBugs,PMD着重于发现代码缺陷。在开展静态代码检查中,可以结合使用。
在静态代码分析领域还有个工具—Sonar,Sonar是一款静态代码质量管理平台,它本身不仅有很多代码规则可以用,而且可以集成很多第三方插件来增强静态代码分析能力,比如上面介绍的那三款,就可以集成到Sonar中。
sonar主要由4部分组成:
第一部分是SonarQube Server,SonarQube Server包含一个web服务主要是供用户浏览代码质量相关的信息,以及通过界面配置SonarQube。一个基于Elasticsearch 的搜索服务,可以供用户高速检索代码问题。一个计算引擎服务,负责处理代码分析数据以及将数据保存到数据库中。
第二部分是SonarQube的数据库,主要负责存储已扫描到的项目代码质量相关的信息,以及相关的配置信息,比如代码规则的配置,系统本身的配置等。
第三部分是集成的第三方插件,比如汉化语言插件、checkstyle插件等。
第四部分是一个代码扫描器,这个扫描器主要负责扫描代码并且上传扫描后的结果到sonar服务器。
sonar最强大的功能之一就是可以集成很多插件,sonar有个插件市场,可以从里面选择自己想用的插件,当然sonar也公开了插件开发规范,可以开发出自定义的插件。
经销商技术部代码扫描实践
经销商技术部从2016年下半年到2017年初逐步开始开展静态代码扫描。当时我们的主要目的就是想了解我们的代码质量现状到底是什么情况,调研静态代码扫描工具如何使用,观测和推动一些常规度量指标得到提升,所以我们基于这几个目的开始的寻找方案进行落地。
由于我们的项目采用Jenkins进行编译构建,所以就基于Jenkins简单粗暴的为每个开发小组都创建一个Jenkins job,每个job里串行编译扫描该组的所有项目,然后每天晚上定时执行这个job,这样在sonar上就可以查看到这些项目的代码质量数据。
如果想统计一个组的代码质量情况,比如一个组的坏味道、单测数等指标的总量是多少,需要人工进行统计。这样的统计方式其实很不方便,而且由于所有项目都是串行执行的,耗时很长,经常由于一个项目执行失败,这个job就中断了,后面的项目的也就不执行了。所以我们希望对静态代码扫描方案进行一次迭代。迭代的主要目的有三个:第一是解决串行job的问题。第二是代码问题实时查看,每天晚上扫描一次,实时性太低,开发修改完问题后,想查看问题是否已经修复,需要第二天才能知道结果,所以需要有一种方式能改完代码后立即看到问题是否改成功了。第三是能够查看所有项目的综合情况,在sonar上只能一个项目一个项目的看,想看一个组的综合情况就需要人工统计。
解决第一个问题就需要串行改并行,我们所有的项目都是用Jenkins编译构建的,所以我们在每个项目的编译job里加了一个静态代码扫描的过程。为了解决静态代码扫描影响编译构建,我们就针对每个项目在CI上的job创建了一个子job,在项目编译的同时异步触发子job上的静态代码扫描脚本。
由于当时的项目有二三百个,每个项目都这样配置成本太高,而且后期Jenkins上的配置还有可能更改,每次改一下所有项目都要改。所以我们做了个工具,如果有项目想接入静态代码扫描,就从这个页面上填写这个项目的基本信息,这个页面会调用Jenkins接口修改这个项目的CI脚本,添加静态代码扫描相关的逻辑。这个页面也可以展示出已接入静态代码扫描的项目当前的代码质量数据。我们还采集了sonar里所有项目的代码质量数据,并且做了一个页面经过聚合运算可以展示一个组的代码质量信息,可以看现状,也可以看历史趋势。我们根据历史数据制定了一个基线,由此可以得出我们当前的代码质量得分,并且通过坏味道、复杂方法、重复块、漏洞、bugs、单测等多维度综合反映我们的代码质量情况,有了度量,我们就能有针对性的优化和改进。
经过近一年的运行以及收集开发同学的反馈,我们发现一些新的需求。第一是开发想提交代码后就立即收到本次提交的代码质量反馈信息。第二是所有分支都纳入静态代码扫描,也就是说提交哪个分支就扫描那个分支,之前是我们从流水线上编译主干的次数比编译分支的次数多,这样就会导致大量的代码缺陷都被在主干上发现。在主干上才发现问题已经有点晚了,在文章开头我们介绍了缺陷发现的越提前,修复这些缺陷所投入的成本越少,而修复主干的代码相对来说也有风险,所以就期望将代码缺陷发现和消灭在特性分支开发阶段。
通过捕获git上的Push事件,任何项目的任何分支一旦有Push事件,那么就拉下这个分支的代码进行扫描。Sonar处理完毕后就会调用增量问题发现服务将相关提醒通过钉钉消息方式发送给开发。从代码扫描到发现缺陷再到缺陷分配和解决,整个过程形成了一个闭环体系。开发在本地开发代码的过程中可以在本地通过一些插件检查问题,修复问题,提交到git仓库后,由自动化代码扫描服务扫描,发现到的问题会推送给开发,开发解决并且再次提交。
开发提交代码后收到提醒,可以看到是具体的哪个开发同学,在什么时间,在哪个项目、哪个分支里引入了哪些缺陷。点击链接就可以跳转到具体的缺陷页面。
以上就是经销商技术部这边近三年的静态代码扫描实践演进情况,这样的演进也会继续进行下去。我们根据当前的痛点和问题,制定相应的计划和改进,再进行数据进行验证和纠偏,通过迭代不断的完善静态代码扫描的工具和服务。通过近三年的数据可以看到静态代码扫描发现缺陷到这些缺陷被修复的平均修复时长越来越短。
静态代码扫描落地的关键点
开发工具和扫描到问题不是最终目的。驱动问题得到解决,给代码质量逐渐变好以及维持高质量的代码提供一系列的机制才是静态代码质量保证体系要解决的核心问题。