转自:http://blog.csdn.net/testing_is_believing/article/details/2068794
注:本译文最早发表在《51测试天地》 电子杂志
原文:
From Primitive to Prominent: The Past, Present, and Future of Automated Code Analysis
- Alberto Savoia
经过几年的努力,自动化代码分析工具终于走出幕后,相比以前,显得越来越受瞩目了,越来越强大,越来越必要。自动化代码分析是一类软件和工具的总称,它们帮助程序员发现、诊断和预防大范围的错误和不符合要求的行为的代码。
自动化代码分析可分成两大类基本类型:静态和动态。静态类型的分析器检查源代码或者目标代码,不需要真正执行代码。动态类型则需要运行代码,在运行过程中检查代码效果。有些问题能被静态分析很好地定位,有些则需要动态分析来定位,而很多情况下需要综合利用静态和动态分析的方法来解决问题。
我会从静态和动态自动化代码分析工具的发展过程谈起 – 从它们朴实无华但是又充满希望的出身,到成为不可或缺的普及技术。我也会介绍一下目前的自动化代码分析工具。然后展望一下它们的美好未来 – 自动化代码分析技术的新发展,可以非常快速高效地帮助开发者定位和解决痛苦的、耗时的各类问题的技术。
基本的代码分析:从Lint到IDE
执行基本代码分析的第一个工具就是编译器。为了顺利地编译,代码必须首先是词法正确的,因此代码分析的第一步就是检查程序是否符合编程语言的语法。在上世纪80年代中期之前,这种编译器执行最低限度的检查的方式被很好地使用,它的特点是缺乏或者拥有很少的诊断能力。那时候对编译器的评论会单独评价它们在预防误报虚假错误信息方面的能力。遗漏一个分号就可能导致编译器的混乱,发出一连串的错误信息,大部分信息是不对的或者是容易让人误解的。有经验的程序员会变得熟练掌握和识别这些长长的信息序列,并且知道哪些是需要修正的 – 通常仅仅需要添加一个遗漏了的分号而已。
早期的编译器在代码分析方面还有另外一个限制。它们的主要目的是把源代码翻译和转换成可执行的二进制代码,而不是通过代码检查来帮助你编写更干净、更健壮的代码。因此,这些编译器只是检查语法错误,而对于那些语法上正确但是非常可疑的代码结构置之不理。这种设计是哪个时代的必然结果。硬件系统资源有限,编译很慢。每次编译要花费额外的时间来执行额外的分析是不值得的,对于编译器厂商和开发者而言都是不愿意接受的。
对于需求而言,自动化的普通代码错误检查确实是需要的。1977年,贝尔实验室的Steve Johnson引入了一个名为Lint的工具 – 名字的由来是感觉从程序中排除错误就像从大衣上摘除细小的棉绒。Lint能检查出类似该使用“==”号,但是却使用了“=”号的错误,或者是那些代码模块之间的不一致的函数接口的错误。Lint被广泛用在Unix社区,尤其是作为代码签入前,或者是大版本的构建之前的最后健全性检查。然而,Lint的用途还是有限,Lint的运行是个冗长的、繁复的、交互性不够强的“编辑-保存-分析”循环。在保存源代码之前不能得到反馈信息,要退出编译器(那时候还没有多窗口系统),然后运行Lint。最主要的是,Lint输出的是命令行的信息,就像下面的:
为了定位警告信息,你必须自己回到代码编辑器,加载出现警告信息的相关源代码文件,找到出现问题的代码行来修改它。然后又要回到Lint中来,看修正后问题是否还会出现。想象一下要处理的是更多的警告信息时结果会怎样。不管怎样,Lint被证实是一个非常有用的工具。
若干年后,随着计算能力的增强,还有集成开发环境(IDE)的出现,让实时的基本源代码分析成为可能,在编辑器中能直接定位和高亮显示警告信息和可能的错误 – 通常是紧跟着开发人员的输入,马上就能提示出来。大部分的IDE都把Lint提供的功能整合进来了。使用现代的IDE,如果我输入下面的代码作为程序的一部分:
IDE会马上让我知道应该使用“==”,而不是“=”。我能当场解决这个问题。这些及时
反馈节省的时间和交互性可能看起来很琐碎,但是当你重复出现大量类似的问题时,IDE的内建静态检查器都能发现这些愚蠢的错误,这样节省下来的时间可能是一个开发人员几天的时间。
除了基本的分析、内建的静态分析外,大部分IDE都有可选的插件来执行更多更全面的分析。截至目前为止,流行的开源的Eclipse已经有26个插件在“源代码分析器”的分类列表中。
这些插件包括增强代码规则或者是代码风格的检查(例如:Checkstyle、JLint、PMD),到检查和移出冗余代码的分析器(例如Duplication Management Framework),它们填补了IDE内建分析器的不足。例如,很多Java的初级程序员有时候会使用“==”来比较字符串的值(例如使用name==”Elvis”,而不是使用name.equals(“Elvis”),这样的话是在比较对象引用而不是比较字符串的相等)。这也许是大部分Java程序员犯的普遍性错误,但是奇怪的是,默认的Eclipse设置不把这个问题当成警告发出,但是大部分的代码规则/风格检查器插件会。这是我从Eclipse得到的警告信息(安装了CheckStyle插件后):
对于静态分析代码而言,分析出普遍的编程错误和编码规则或风格的违反,相对Lint时代已经有了长足的进步。IDE的内建分析器、第三方工具和插件让我们可以在开发过程中发现和定位并修改大量的这些错误。与此同时,这个世界变得更加互联互通、更加复杂、也更加危险。今天运行着的软件必须变得更加健壮,面对新的潜在错误和威胁。幸运的是,新的代码分析工具也出现了。
新时代、新的威胁:为安全和知识产权的违背而分析代码
在可预见的将来,安全将会成为自动化代码分析的主要目标。在网络渗透到企业计算的基础结构之前,应用软件的安全主要通过物理设施的保护、限制网络的范围。这种方式在当今社会很难实施,因为通过快速的网络让所有信息更容易获取这个概念太吸引人了。因此,大部分企业数据理论上都能通过私有或公共的网络访问到,黑客们变得擅长于闯入网络安全区域,通过输入意外的数据使程序失常。
SQL注入和缓冲区溢出是两种很好的例子。SQL注入让黑客可以插入数据库服务器可能错误执行的命令。缓冲区溢出的方式则是通过输入过长的数据企图让程序以黑客可控制的方式异常地中断。不好的编码习惯往往是导致这些安全问题的原因。
有些多用途的代码分析器能帮助你找到造成明显安全风险的代码。开源的代码分析器PMD能分析以下包含风险的代码:
- MethodReturnsInternalArray(返回内部数组的方法):暴露内部的数组,让用户可以直接修改一些关键的代码。返回数组的拷贝会安全些。
- ArrayIsStoredDirectly(直接存储数组):构造函数或方法接收的数组应该克隆对象并存储拷贝。这样可防止将来的用户修改,影响内部功能。
这是两个很好的编码规则,但是它们未能深入到代码的安全隐患的中心位置。为了更加深入,你需要专门的安全分析器。幸运的是,在这几年,出现了像Fortify、Secure这样的软件公司,他们把专门设计用于检测不安全代码的分析工具带到了市场上,很容易预知的是:这类工具将持续占领自动化代码分析工具的市场。
为了确保你的数据不被偷窃或被危机,你需要分析代码的安全漏洞。但是你还需要考虑另外一方面的问题 – 确保你的代码不会侵权或违反知识产权。
在这个开源和在线代码共享的时代,很容易使用或拷贝那些你无权使用或受限制使用的代码。大部分开源的项目,即便是Free Software Foundation的代码,也是受版权保护的。为了使用这些代码,你必须遵循它的许可证。如果你在产品中不注意地使用了这些代码,可能会被迫“开源”你的产品或者付很高的罚款给版权所有者。
幸运的是,有另外一类的自动化代码分析工具,这些工具已经出现在市场上。ProtexIP Suite是Black Duck软件公司的产品,使用了一种被称为“代码印迹”(Code Print)的技术和在线知识库来自动化地识别引入到软件项目代码中的开源和第三方软件代码。
动态代码分析:让你的CPU像你一样勤快地工作
在过去几十年我们亲眼目睹了处理器处理能力的巨幅提高,这让很多静态分析的实时执行,并且给开发人员即时的反馈成为可能。但是现在的处理能力实际上已经远远超越了自动化静态代码分析所需要的能力,因此我们可以发现一个崭新的通过在执行过程中分析代码和整个系统来发现潜在问题的自动化代码分析类别。
动态代码分析的概念并不新颖;像性能分析器、内存泄漏检查器等,都是出现了很久的技术,这些都是需要在代码执行时才能工作的工具。但是新的动态代码分析工具让人感到兴奋,它们能充分利用大幅提高的计算能力。现在有大量过剩的CPU计算周期,而且很多开发人员的CPU在大部分时间是空闲的。这对于程序员来说是一个新的情形。几年前,对于开发人员来说等待若干分钟的时间在等待编译器完成编译工作是很平常的事情。这也是为什么很多程序员会玩边抛边接的杂耍游戏的原因 – 不能上网,玩杂耍是在等待编译完成期间消磨时间的好办法。
现在情况不一样了。当我在开发代码的时候,处理器大部分时间是空闲的,直到我触发一个Build – 然后我的电脑就精力充沛地开始嗡嗡作响。在我想稍作休息时,编译已经完成,处理器回到空闲状态,我又要回到工作。怪不得我的杂耍技巧没有以前那么好了。
考虑到开发人员的时间比CPU时间有价值得多,因此如何让CPU多做工作,从而帮助程序员更加有效地使用自己的时间,看起来就变得很有意义了。
让你的CPU保持忙碌的行为
动态代码分析和软件测试是其中一个需要关注和帮助的区域,也是利用CPU能力的最佳区域。XP和其他敏捷软件开发者重新发现了开发人员自己测试代码重要性,他们认为开发人员应该在整合之前不断地编写和执行单元测试。有赖于这些敏捷方法的流行,很多开发人员和开发经理都认可这种做法:在每个Build版本之前由开发人员编写和执行单元测试是提高软件质量整体水平和降低缺陷代价的最好办法。
不幸的是,开发一整套的测试代码可能比编写真正的代码要更加复杂和耗时。通常100行的代码需要300到400行的测试代码来达到90%到100%的测试覆盖率。测试是各类检查和验证的组合,这让基于动态分析的自动化成为测试的最佳候选,最近这个领域出现了很大的发展,既有学术性上的,也有商业上的。
性能分析和内存使用方面的动态代码分析
性能分析是一个已被很好建立的动态代码分析的示范。开发人员使用性能分析工具来分析代码,然后用一些测试或例子来驱动代码,然后检查性能结果来识别和定位瓶颈。这种类型的代码分析能促进代码性能的提高,但是手工地进行是很困难的、很繁复和耗时的。编译器基于静态分析能优化代码,而一些编译器现在可以自动地基于动态代码分析来优化性能,给开发者节省了大量的工作。例如,Intel系列的编译器使用一个叫做分析指南优化(profile-guided optimization)的技术,在程序的预执行过程中收集数据,利用这些数据分析来提高性能,相比静态分析能达到额外的、更广泛的优化空间。
将来,动态分析能更进一步,考虑并测试更多影响性能的因素。可以想象一个动态的性能分析工具,用一些有代表性的输入数据或测试,分析和识别出影响性能的参数(例如,一个文件读操作的缓冲区大小)。然后它会调整那些参数,运行一系列的性能测试来检查是否对于某个系统配置,不同的设置会改善性能表现。不知道你是否会喜欢这样的性能分析工具,它能告诉你这些:
“基于有代表性的输入和系统配置,经过了342次测试,发现对于变量bufSize来说,最优的值是1024字节(目前是4096字节)。点击这里进行修改。”
充分利用网格计算 – 下一代自动化代码分析技术
上面的动态代码分析的例子不像它听起来那么牵强,尤其是当自动化代码分析工具利用了网格计算时。通过网格计算,成千上万的不同类型的计算节点的处理能力、存储和带宽都能整合在一起,让用户和应用程序能无缝地访问大量IT能力和大量系统配置。科研组织和政府实验室已经使用网格计算很多年了,现在像IBM、SUN这些公司也在设法让这些技术商业化,提供按需索取的计算能力。如果你想在几个小时的时间内完成两百万行代码的深度分析,SUN的Sun Grid可以给你1美元一小时CPU时间的计算能力。
我知道我在扩展自动化代码分析的定义。也许我应该使用一个更加广泛的术语“自动化软件分析”,因为现在的产品代码实际上是一些组件的集合 – 配置文件、安装文件等 – 组装在一起为软件服务。不管命名是怎样,我相信这类型的工具必须持续进化,以便满足开发者的需要,并利用现有的技术。
在代码分析工具的早期,整个软件用一个语言编写,在一个操作系统平台上运行,通常在特定的硬件配置上运行。而现在的软件通常使用几种编程语言,并且需要支持多平台操作系统和各种各样的硬件配置。我有个在大软件企业工作的朋友把这称为“死亡矩阵”(The Matrix of Death)。唯一有效解决死亡矩阵的方法,并且也是迎接像安全、IP侵犯的挑战的方法,是开发和利用新的技术、新的自动化分析工具。
总结
软件比以前更加复杂了,要想确保大范围的系统配置下软件的集成性、功能正确性、性能,还有大量代码的合法性,是让人畏惧的挑战。幸运的是,处理器的性能提高,已经不是Lint时代的表现了,让大量的自动化代码分析技术得以出现,大量非常实用的工具得以出现和进化,并且产生了新的分析类别。我相信网格计算会促使自动化代码分析的进一步发展。在那之前,我建议你开始寻找大量已经出现的开源和商业的产品。如果你把自动化代码分析等同于Lint,你会发现很多惊喜的内容。记住,你应该让CPU像你一样勤快地工作。