首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》预防Bug并保持代码整洁

关灯直达底部

可以把Eclipse理解成一种特殊的操作系统:由成千上万的文件组成,有自己的文件系统,自己运行了一个Web服务器。Eclipse是开放的、可扩展的。Eclipse插件类似于操作系统的应用——编写相对简单,Eclipse生态系统包含的扩展要远远多于任何Eclipse用户所安装和使用的。因为Android代码是用Java实现的,所以可以把所有的插件应用到Android软件开发中。

这里将探讨一个非常有价值的Eclipse扩展:静态分析器,或称源代码分析器。

静态分析器

静态分析的非正式定义是指它会指向编译器警告信息出现的地方。在Eclipse中,编译器告警往往是件好事。虽然好的编译器会提供警告信息,且这些警告信息有助于捕捉潜在的运行时问题,但是捕捉潜在问题不是编译器的工作。静态分析器能够完成这项工作。

静态分析器之所以称为“静态”的原因是它是在未运行的代码上执行分析。虽然编译器执行的某些功能可能属于静态分析的范畴——在Eclipse中,Java编译器很好地执行了编程后碎片的清理工作,如未使用的变量和方法,而静态分析器做的远远不止这些。静态分析器会尽可能地找出Bug,而不仅仅是随便给出结果。

FindBugs

下面将通过安装和使用FindBugs工具来开始静态分析器的研究。可以在这里找到FindBugs的文档和源代码:http://findbugs.sourceforge.net。后面将较详细地介绍FindBugs工具的安装过程,因为它和绝大多数的Eclipse插件安装过程类似,很有代表性。要安装FindBugs,必须把FindBugs库加载到Eclipse的网站列表,从该列表安装包。通过Help→Install New Software Menu命令,单击Install对话框的Add按钮。该操作会打开Add Repository对话框,将FindBugs库的地址http://findbugs.cs.umd.edu/eclipse添加到对话框中,如图5-7所示。

图5-7:添加库以便在Eclipse环境中安装一个插件

安装FindBugs的下一步是从库中选择包,如图5-8所示。在这个例子中,可选的只有一个包。

图5-8:选择FindBugs库中唯一可用的包

一旦选中包,就可以进入下一个对话框,其中显示了要安装的软件包的列表。在这个例子中,该列表中只有一项,如图5-9所示。

图5-9:选中了FindBugs库中唯一可用的包

在下一个对话框中,阅读许可协议并选择接受或不接受该包的许可协议,如图5-10所示。

在安装Eclipse插件的过程中还有一个问题需要解决。因为包没有签名,会收到安全警告,如图5-11所示。

最后,需要重新启动Eclipse,如图5-12所示。

图5-10:接受FindBugs许可协议

图5-11:在安装没有签名的包时显示的安全警告

图5-12:安装FindBugs后重新启动Eclipse

把静态分析应用到Android代码中

FindBugs包含一个菜单命令、一个透视图及一些视图,你会发现这些视图在查找bug时很有用。要启动FindBugs,可以使用项目上下文菜单中的菜单命令,如图5-13所示。

图5-13:调用FindBugs

运行了FindBugs之后,可以更改成FindBugs透视图,如图5-14所示。FindBugs透视图包括一些视图,在这些视图中显示了FindBugs发现的潜在问题的层次结构列表,这些列表根据问题类型进行了组织。在Editor视图中对问题进行了标记。如果打开问题的属性,会显示关于问题的详细解释,包括在什么情况下FindBugs会出现误报(false positive)。

图5-14:FindBugs透视图

在这个例子中,我们一起来看看问题“Null check of a value previously dereferenced”,如图5-15中的Bug Explorer视图所示。

图5-15:FindBugs Bug Explorer

验证一个字段在解除引用后,还包含非空值,这在语义上不算错误的Java,但是几乎可以确定它是无用的,或者显然是个错误。在以下的代码中,你会发现字段savedState在使用上假定其永远非null,但是在日志调用时出现null检查:


protected void onRestoreInstanceState(Bundle savedState) {      super.onRestoreInstanceState(savedState);      // Restore state; we know savedState is not null      String answer = savedState.getString(/"answer/");      // This is a gratuitous test, remove it      Object oldTaskObject = getLastNonConfigurationInstance;      if (null != oldTaskObject) {            int oldtask = ((Integer) oldTaskObject).intValue;            int currentTask = getTaskId;            // Task should not change across a configuration change            assert oldtask == currentTask;      }      Log.i(TAG, /"onRestoreInstanceState/"               + (null == savedState ? /"/" : RESTORE) + /" /" + answer);} 

实际上,应该在使用savedState之前检查其是否为null,因为savedState的值并没有指定为non-null。把上面没有对savedState进行null测试的代码修改为如下代码:


String answer = null != savedState ? savedState.getString(/"answer/") : /"/";  

再次运行FinsBugs,证实了该修改消除了可能出现null值的问题。

上面这个代码片段是一个关于bug静态分析典型例子。静态分析超出了编译器警告的处理范畴,因为可能编程人员就是打算这么处理,而简单的引用分析使得静态分析器能够发现它可能是个Bug,而往往也确实是个Bug。

静态分析的局限性

静态分析器存在误报(false positive)的情况,因为它采取的方式是寻找代码中的潜在问题。这是静态分析器和编译器警告之间的区别之一。如果编译器错误消息对某段实际上并没有问题的代码报错,这会被看做是编译器的一个Bug。

静态分析器的一个薄弱之处是它查找不遵循编码规范的代码。举个例子,警告“Class names should start with an upper case letter”(类名称应该以大写字母开头),如图5-15所示,是由自动生成的代码触发的,这些程序员不需要考虑其中是否存在Bug,除非他们怀疑代码生成器中有Bug。

经验丰富的程序员经常质疑静态分析器的实用性,因为在他们的代码中,静态分析器能够捕获的问题相对较少,而静态分析器误报的概率更高。众所周知,对经验丰富的程序员所编写的代码执行静态分析,很少会发现Bug,静态分析无法取代模块测试和良好的调试技巧。但是,如果你是Java及Android新手,你会发现静态分析器是避免出现编译器警告的非常好的辅助工具。