.NET 中的静态分析
如果你想要添加一段以后经常会访问的代码,那么能够得到提醒不是很好吗——可能是在开发过程中需要花费几天才能够找到并修复的 bug,可能是看起来你的团队成员无法重用和扩展的代码,或者是能够影响安全、可靠性和性能的缺陷。
静态分析能够给你这种善意的提醒。. . 并且这确实可以在.NET 开发中实现。
什么是静态分析?
静态分析是一个很有趣的术语,就像“网络管理员(web master)”一样,可能会拥有多种不同的意思。一种有效的定义是:静态分析是对计算机软件执行的一种分析,而不会真正执行要测试的软件。这个定义让我们清楚,静态分析的意义要比我们期望的更广泛,但是它不包括功能测试之类的内容,所以范围还是有限的。
静态分析所涉及到的内容包括:
- 代码美化
- 同行审查(即手动的代码审查和代码检查)
- 基于模式的代码扫描
- 基于流程的代码扫描
- 基于度量的代码扫描
- 编译器以及其它与构建相关的输出
本文会讨论以上各种技术,以及在何时、何地使用它们,还有为什么它们会对我们有帮助。
代码美化
有时候人们会把静态分析归为只是会检查大括号位置是否合适的一种工具,但我们并不会对此感到奇怪。这种用法对我们还是很有帮助的,但是静态分析最强大的功能远远不止如此。
同行审查
同行审查或者手动的代码审查基于这样的观点,同事之间应该互相查看代码,看它是否完成了应有的功能。
同行代码审查是找到代码缺陷的最有效方法。通过代码审查,我们平均可以排除 60% 的缺陷(Boehm 和 Basili 所写的《十种最有效减少软件缺陷的方法》,Computer, January 2001)。这也并不奇怪,毕竟代码审查使用的是最好的分析工具:人类的大脑。
显然你无法自动化进行同行的代码审查,但是你可以使用大量的技术,使得同行审查更有效率,也让你不必一味地对语法之类的低级问题吹毛求疵。例如,你可以自动化检查这些低级的问题,还可以把代码审查的准备、通知和跟踪工作自动化。
基于模式的分析
基于模式的分析会定义代码模式(patterns),它们或者是需要确保出现在你的代码中,或者需要确保不出现在你的代码中。
换句话说,静态分析可以查找好模式和坏模式。例如,它可以确保我的代码打印出版权声明,或者它可以检查一些格式的问题,像括号放置的位置以及是否区分大小写等等。这种方法可以帮助团队检测.NET 代码是否遵守某种规则或者一系列编码实践。
除了能够找到语法问题之外,模式匹配静态分析还能够找到可能导致缺陷的原因。这可能不会显示出已存在的缺陷,而是告诉你大概的区域。这样,我们可以使用基于模式的分析来避免错误,而这也是真正的生产力和质量提升的关键所在。
如果得到合适地应用,静态分析不仅可以找到像代码美观之类简单的问题,还能够帮助开发者识别出潜在的逻辑缺陷,也能够检测到 NullReferenceExceptions 和资源泄漏之类的错误。
即便是最基本的静态分析——检查代码是否遵循了业界接受的标准——也能够立刻暴露出一些问题,而使用单元测试、手动检查和其他验证技术可能需要几个小时。
重要的是,你应该把静态分析的代码标准看成是防止错误出现的一种方式,而不是用来检测错误的。如果违背了基于模式静态分析的地方没有指向显而易见的缺陷,很多开发者会对此表示失望。当他们检查违反规则的代码,发现的是可能出现错误的结构体,而不是真正的错误时,就会认为静态分析规则没用,甚至停止对违反规则了的代码的调查,之后甚至停止执行静态检查。
这就涉及到了软件业界的一个基本问题:大多数开发和测试团队关心的是 _ 移除 _ 错误,而不是如何 _ 避免 _ 错误。
让人惊奇的是,基于模式的静态分析能够提高开发者的工作质量。它会监视着开发者的工作,当做事的方式不理想的时候,就会做出提醒。
例如,一位开发者可能会编写包含 try/catch 块的代码来访问数据库,但是忘了使用 finally 块来释放资源。使用恰当的静态分析规则,当他这么写代码的时候就会收到提醒,这样他就能立刻修正。经过几次这种“提醒”之后,最后开发者就会改变编写代码的方式了。
基于流程的分析
基于模式的静态分析会在特定的文件或者类中查找指定的模式,而流程分析会在应用程序中试图遵循特定的执行路径来查找模式,而不会真正地运行这些代码。
基于流程的工具会模拟应用程序的执行,基于程序逻辑在代码中寻找可能的执行路径,甚至可能试图注入一些坏数据。这样会找到可能会触发运行时缺陷的执行路径,像 NullReferenceExceptions、资源泄漏及 SQL 注入之类的安全缺陷,从而找到应用程序中的真正缺陷。
当我使用数据流分析来扩展基于模式的静态分析,并使用自定义的代码规范对其进一步扩展,它会变得更加强大。创建自定义的标准能够帮助你完全消除很多非常复杂和重要的错误。例如,我们可以使用这些标准暴露你的应用程序所特有的错误,像安全缺陷和 API 使用错误等。这样的错误通过手动测试或检查很难发现,并且,如果在正式运行之前没有检测到,那么就会变得非常难以修复。
使用一组整合的静态分析技术,在软件开发生命周期的早期就暴露出这些重大的缺陷,会节省大量的诊断和可能返工的时间。
基于度量(Metrics-based)的分析
可能最古老的静态分析就是基于度量的分析了,它会对代码的各个方面——像复杂度,或者文件的代码行数和方法个数等——做出度量。
度量分析想要达成的目的有两个:理解代码中的状况,并找到可能发生的问题。当我们在 20 年前开始做静态分析的时候,就有很多种度量。我们会使用它们来诊断无法再现的问题,然后使用调试器来调试度量工具所建议的位置。
基于度量的分析的质量取决于你要查找的是什么。度量对于帮助你从全局了解代码特别有用。例如,如果你检查了应用程序文件的代码行数,并且发现突然出现了巨大的文件,那么你可能就需要对设计做出改进了。组件应该是离散的,拥有已知的输入,并产生已知的输出。如果在其中有大量复杂的逻辑,那么这时你就需要查看你的组件,并考虑对它们进行分解了。
编译器输出
在静态分析中经常被忽视的内容就是编译器的输出。在过去,这些输出经常会被忽略或者掩盖,但是对质量和稳定性感兴趣的开发者发现,清理编译器错误会防止在运行时出现奇怪的问题。
下次你看到编译器错误,并且想要忽略的时候,就要考虑一下你是否有足够的能力编写自己的编译器。如果没有的话,那么就修正错误。
.NET 中的静态分析
现在我们已经讨论了好几种静态分析的方法,接下来让我们专门讨论一下.NET 中的基于模式和基于流程的分析。Parasoft 已经创建了一种叫做 dotTEST 的工具,它能够执行静态分析,当然还有一些其它功能。
Parasoft dotTEST 会整合在 Visual Studio IDE 中,你可以在 VS 解决方案管理器中选择项目或者文件,然后在选定的资源上运行 dotTEST 命令。你可以在 VS 中查看分析结果,重新分配和消除错误,以及诸如此类的工作。
(点击图像可以放大)
dotTEST 的工作原理
dotTEST 会通过检查 IL 代码和源代码执行静态分析。检查 IL 代码让 dotTEST 可以分析所有.NET 语言编写的程序,尽管对一些规则的检查还必须在源代码级别上执行。
dotTEST 使用了多种测试技术,像解析 C#代码、使用.NET 反射 API 以及读取.NET 程序库等等。这些技术让我们可以执行静态分析、度量分析、流程分析(使用 BugDetective)、单元测试以及生成单元测试等等。
最先进的 dotTEST 特性在于流程分析,它会创建恰当的控制流图,并对其进行分析,以找到 NullReferenceExceptions、资源泄漏、不安全的操作以及其它可能出现的异常状况。
dotTEST 还能够计算单元测试的覆盖率,或者在单元测试的过程中重写正在执行的 IL 代码,从而应用 stub,这项操作是在应用程序运行时,将 IL 代码编译成本地代码之前做到的。
dotTEST 中的基于模式的静态分析
dotTEST 中预设置了上百种内建规则——包括微软的.NET 框架设计指南中的内容、《Effective C#》 和 _《.NET Gotchas》_ 书中的内容,以及很多公司软件开发者的经验。
在 dotTEST 中预加载了超过 440 项模式规则对;这些规则覆盖了以下方面:
- 一般错误
- API 设计
- API 使用
- 序列化
- 国际化
- 安全
- Web 应用程序最佳实践
- C#最佳实践
- 资源泄漏和内存使用
每项规则都说明了自身的重要性、遵循的好处、不遵循的风险,以及如何修正它所发现的模式。规则的严格等级是基于可能造成的破坏预设的,并且可以自定义。规则组还可以针对安全、OWASP、NIST SAMATE 以及 IEC 63204 等情况打包。
尽管还是存在可以妥协的需求,但检查代码是否符合这些规则还是很重要——即便是在这些代码已经编写完成之后。
基于模式的静态分析——示例
让我们假设,基于模式的静态分析发现有些代码违反了“避免使用静态集合”规则。
这项规则很重要,因为它能够识别出可能会导致内存泄漏的代码。静态集合对象(像 ArrayList 等)能够保存大量对象,这些对象都可能造成内存泄漏。如果你在“静态”集合中放置了短期使用的对象,并且忘了删除,那么在程序继续运行的时间内,那个对象都会被集合所引用。
通过 profiling 或者负载测试我们可以发现导致的内存泄漏问题,但是那需要设计和实现测试,然后跟踪问题到特定的代码行。使用模式匹配静态分析工具,我们在几秒之内就可以自动检测到这样的代码。
下面的截屏显示了 dotTEST 的模式匹配静态分析是如何识别出一种情况,其中开发者想要使用逻辑与操作,而实际上使用的是按位与操作。
(点击图像可以放大)
由于按位与操作不会报错,所以即便ssn 是null,也会对ssn 进行计算,这就会导致抛出异常。尽管在简单情况下这个异常是显而易见的,但在很多复杂的情况下,我们很可能无法测试出来。
为了确保过程尽可能有效合理,我们还可以在Parasoft 的图形化创建规则界面(RuleWizard)中创建IL 级别和C#级别的规则,从而强迫执行特定的项目和组织的需求,从而避免再次出现特定应用程序中会出现的缺陷。
规则名称以及严格程度分类可以与你的团队的内部编码策略和优先级对应。此外,针对特定情况的抑制(suppression)为我们提供了一种系统方式,它可以在整体上遵循规则,但是做出一些例外,你或者你的团队认为可以接受它们。
dotTEST 中的流程分析
针对基于流程的分析,Parasoft 的 BugDetective 使用了多种分析技术,包括模拟应用程序执行路径,从而识别出可能触发运行时缺陷的路径。可以检测到的缺陷包括使用空引用异常、除零问题以及资源泄漏。
示例 1
例如,下面的图片显示了 BugDetective 在示例银行应用程序中找到的三个问题:
(点击图像可以放大)
让我们详细看下如何避免NullReferenceException 错误,你会注意到BugDetective 显示了导致该问题的完整执行路径。
在这个案例中,在第64 行cust 被设置为null。在第68 行,又调用了LookupCustomerName 方法。在那个方法的第48 行抛出了异常。由红色的圆点标记。然后控制会转到catch 块,其中在第74 行发生了空引用。
这个问题很可能会逃过一般测试的检查。
示例2
这里发现的第二个错误又是一种很难重现和调试的缺陷。这个例子在析构函数中使用了托管的资源resourceCache,并在析构函数中调用了Changed 方法(图中未显示)。这里的问题是,由于垃圾回收程序的固有机制,那时resourceCache 可能已经被回收了。我们在很多应用程序中都看到过这个问题,毋庸置疑,它会让程序崩溃。…这经常是在应用程序准备关闭的时候。
(点击图像可以放大)
总结
静态分析可以为.NET 提供很多功能。它可以执行基于模式的规则,不管它们是基于已验证的标准,还是能够帮助你识别特定应用程序中缺陷的自定义模式。它可以快速扫描所有代码,并定位所有违反了你选定的规则集合的高风险代码。
尽管如此,这种分析技术还是无法检测到所有缺陷。很多缺陷是由于不同的方法和类之间的交互导致的,并且依赖于实际的执行路径。此外,传统的测试方法(像单元测试和应用程序级的测试)通常找不到这样的缺陷,因为我们很难重现发生异常的条件。即便达到了100% 的声明覆盖率,还是有很多路径我们无法覆盖到。所以,拥有能够模拟代码中的大量执行路径的自动化工具会对我们非常有用,它们能够找到可能存在的缺陷。
dotTEST 的流程分析特性所完成的正是这样的工作。它会模拟可能的执行路径,从而显示出可能在运行时发生的缺陷,让你可以在项目达到那个阶段之前就对其采取预防措施。
把这些强大的方法与执行静态分析的制度组合在一起,你就拥有了经过验证的方式来获得软件开发的关键益处:
- 检测到会影响可靠性、安全性和性能的潜在缺陷。
- 执行组织的设计规则和规范(针对特定应用程序、特定用途或者特定平台)以及从已知的特定缺陷中抽象出来的错误预防规则。
- 提升类的设计和代码的组织,从而提高代码的可维护性。
- 应用通用的格式化、命名以及其它样式约定,提升代码的可读性。
不管在使用.NET 开发过程中采用的是哪种静态分析方法,它都会让你找到导致缺陷的根本原因,消除可能产生缺陷的模式,从而预防缺陷的发生。
延伸学习
想要了解更多细节,你可以查看一下资源:
- 整合的错误检测技术,找到.NET 应用程序中更多缺陷——说明了如何将错误检测技术——包括静态代码分析、数据流分析和单元测试——自动化和同步,从而更有效地在.NET 应用程序中找到缺陷。
- 静态分析最佳实践——说明了基于模式的分析、数据流分析以及度量能够如何帮助你的团队提升代码的安全性、可靠性、性能以及可维护性,以及如何尽快无痛地开始这些实践。
- 数据流静态分析:类固醇(steroids)上的静态分析——说明我们能够如何应用数据流分析,从而支持静态分析和单元测试工作。
- Parasoft 静态分析中心——提供了对 Parasoft 针对.NET、Java、C、C++、FDA、高安全标准以及安全应用程序开发方面的静态分析技术的概览。
关于作者
Arthur Hicken是 ParaSoft 的布道师和解决方案架构师,他和公司的高管一起制定战略决定。他在近 20 年以来一直在 Parasoft 参与自动化各种实践的工作。他曾经从事过很多项目,包括数据库开发、软件开发生命周期、web 发布和监控、软件构建自动化的各个方面以及与遗留系统整合等等。此外,Hicken 先生还拥有很多其它方面的经验,包括监管数据库技术、数据挖掘、数据仓库、数据集市等等,它还开发了一个高水准的内部数据库系统。
Hicken 曾经与多家公司的 IT 部门协作,像 Cisco、Vanguard、Motorola 等等,帮助他们提升软件开发的实践。他还在 Parasoft 创建并引领了很多内部技术培训课程。它还为 Parasoft 的客户创建并讲授了很多培训课程。作为领域专家,Arthur 的观点多次被 Business 2.0、Internet Week 以及 CNET news.com 在谈到站点质量问题时引用。
查看英文原文:.NET Static Analysis and Parasoft dotTEST
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论