使用FindBugs改善你的代码质量—为什么使用以及怎样使用

使用FindBugs改善你的代码质量—为什么使用以及怎样使用

原文(作者):
FindBugs, Part 1: Improve the quality of your code
Chris Grindstaff ([email protected]), Software Engineer, IBM
http://www.ibm.com/developerworks/java/library/j-findbug1/

静态代码分析工具承诺不需要开发者太多努力就可以找出程序中存在的Bugs,当然如果你已经有过一段时间的编程经历,你就会知道这样的诺言也许并不总会成功兑现。但是,即便静态代码分析工具在代码检查上达不到完美的程度,一个好的检测工具还是值得添加到你的编程工具箱中。高级软件工程师 Chris Grindstaff用了两部分介绍FindBugs,这是第一部分,主要介绍了FindBugs怎样改善你的代码质量及评价隐蔽Bugs,当你完成阅读这部分内容后,不要错过Chris的第二部分内容。
软件代码质量工具存在一个问题就是这些工具所检测出的缺陷经常令程序员不知所措,因为这些被检测出的缺陷实际上不是Bugs,也就是说误报(False positives)。当存在误报时,开发者往往需要学习忽略检测工具的输出或者干脆放弃它。FindBugs的发起者David Hovemeyer and William Pugh认为检测工具应该尽可能减少误报的数量。和其他静态检测工具不同的是FindBugs不关注代码样式,而是试着查找实际Bugs或者潜在地性能问题。

1 FindBugs是什么?

FindBugs是一个静态代码检测工具,它检测Java类文件或者JAR文件,通过比较文件中的字节码与一系列缺陷模式来查找潜在问题。利用静态检测工具,你可以在不实际运行程序的情况下分析软件缺陷。FindBugs使用了访问者模式而不是分析类文件的构成和结构来决定程序目的。
图1 FindBugs分析一个匿名程序的结果
图1 FindBugs分析一个匿名程序的结果

2 FindBugs能够检测的问题

注意下面只是一些比较有趣的问题举例,并没有囊括FindBugs能够检测的所有问题。
-2.1 检测器:查找Hash Equals方法不匹配问题
这个检测器可以检测几方面的问题,主要与equals()和hashCode()方法的实现有关。由于几乎所有的集合基类List/Maps/Sets等都会调用这两个方法。通常这个检测器检测一个Java类中存在的以下两种不同类型的问题:

  • 一个类重写了equals()和hashCode()方法中的一个,却没有重写另一个。
  • 定义了equals()或者compareTo()方法的协变版本,例如我们为Bob类定义了equals()方法如下boolean equals(Bob b),使用这个方法覆盖Object(所有Java类默认基类)中定义的equals()方法。但由于Java虚拟机在运行时决定具体执行哪个覆盖的方法时默认地将总会执行Object中定义的equals(Object o)方法,而不是我们定义的equals(Bob b)(除非你明确将传递给方法的参数类型强制转换为Bob)。这样处理的缺陷在于当我们把这个类的一个对象放进前面所说的集合基类中时,当集合类比较对象是否相等时默认的调用了Object中定义的equasl方法,而不是我们想要覆盖的equals.

2.2检测器:查找类方法的返回值被忽略问题
这个检测器会检查程序中一些类方法应该被使用的返回值却被程序员忽略的代码,最常见的场景出现在调用String的一些方法时,例如:

1.String aString = "bob";
2.b.replace('b', 'p');
3.if(b.equals("pop"));

这是一个很常见的错误,在第2行程序员认为他已经把字符串中的所有b替换成了p,但是Java中字符串常量是不可变的,所有这类函数的都会返回一个新的字符串,而绝不是简单的改变原有字符串。

2.3 检测器:查找空指针引用及对Null多余比较问题
这个检测器查找两类问题,一是代码路径中将会或可能会引起空指针引用异常;另一个是程序中对不可能是null或一定是null的变量进行多余验证是否为null。例如,如果程序中存在代码比较两个都是null的变量,要么这是多余的,要么意味着程序员写错程序(因为正常人不这么写程序)。同理,如果比较null和一定不是null的两个对象是一样的情况。

1.Person person = aMap.get("bob");
2.if (person != null) {
3    person.updateAccessTime();
4.}
5.String name = person.getName();

这个例子中,如果第1行的aMap中不包含bob这个人,当第5行调用person.getName()方法时会产生空指针引用异常。因为FindBugs不知道aMap是否包含bob,它只会标记这里可能有异常。

2.4检测器:检测类成员变量在初始化之前被读取值的问题
查找类成员变量在构造器中初始化之前被读取值,这个错误经常是由于在构造函数中将成员变量名写成了参数变量名所造成的(虽然这样的情况很少发生),下面的例子更好理解:

1.public class Thing {
2.    private List actions;
3.    public Thing(String startingActions) {
4.        StringTokenizer tokenizer = new StringTokenizer(startingActions);
5.        while (tokenizer.hasMoreTokens()) {
6.             actions.add(tokenizer.nextToken());//空指针异常
7.        }   
8.    }
9.}

这几个例子仅仅是一小部分FindBugs能够检测的问题,可以查看更多

3 使用FindBugs

为了运行FindBugs至少需要JDK1.4或更高的版本,不过FindBugs能够分析被更早的版本创建的类文件,开始使用它所要做的第一件事就是下载并安装最新发布的FindBugs——当前为0.7.1.值得庆幸地是下载和安装FindBugs是非常简单的,只需要下载压缩文件并解压到指定的文件夹下,安装过程就完成了。(这篇文章已经旧的不能再旧了)
完成安装以后,我们可以在一个简单的类上运行它,和以前一样,我将仅讨论Windows用户的使用并假设Unix用户能够轻松地理解和跟上我想说的内容,打开命令行提示符并定位到findbugs的安装目录。在findbugs的根目录下,有一些我们感兴趣的文件夹,documentation被放在doc文件夹中,但我们更感兴趣的是bin文件夹,它包含了可以运行FindBugs的批处理文件,下面我会讲到。

4 运行FindBugs

Findbugs能够在多种方式被运行,包括命令行、图形界面、Ant、Eclipse插件或者Maven,我简单提到了通过图形界面运行FindBugs,然后主要关注使用Ant和命令行运行它。一定程度上由于图形界面的功能没有完全实现命令行的某些选项。例如,现在的图形界面版本中你不能够为指定过滤性包含某些类或排除某些类。但是,更重要的原因是因为我认为我们最好使用FindBugs集成到我们的编译过程中,而图形界面并不是很方面使用。
4.1 使用FindBugs图形界面
FindBugs图形界面的使用非常简单,但有几点需要注意。如图所示,使用图形界面的好处之一就是它提供了每种类型的检测器的描述,前面的图1中显示了错误Naked notify in method 的描述,同时,它也为每个缺陷模式提供了相似的描述,当你刚刚接触这个工具时这是非常有用的,图形界面的下端有一个源码窗口也是非常有用的,当你为findBugs指明源码位置,当你切换到这个tab窗口时,它会突出显示那行有问题的代码。
当从命令行或者Ant方式运行findBugs时,如果你选择使用xml作为输出选项是非常重要的,你可以使用UI加载前一次运行的结果,这样做可以同时利用图形界面和命令行工具的优势,是一种很好的方式。

5 运行FindBugs作为Ant 任务
让我看看如何从Ant编译脚本(build script)运行findBugs。首先,把FindBugs Ant任务拷贝到Ant库文件夹使其检测到新的任务。也就是说拷贝FIND_BUGS_HOME\lib\FindBugs-ant.jar到ANT_HOME\lib。
现在你需要增加哪些配置到你的编译脚本中。由于findBugs属于客户化任务,你需要使用taskdef任务标签告诉Ant哪个类被加载,如下所示:

<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/>

在定义之后,你就能够通过名字来使用它,这里就是FindBugs,下一步你需要增加一个target标签到你想使用这个任务的编译脚本,如下所示:
Listing 4.创建FindBugs目标

1.<target name="FindBugs" depends="compile">
2.    <FindBugs home="${FindBugs.home}" output="xml" outputFile="jedit-output.xml">
3.        <class location="c:\apps\JEdit4.1\jedit.jar" />
4.        <auxClasspath path="${basedir}/lib/Regex.jar" />
5.        <sourcePath path="c:\tempcbg\jedit" />
6.    </FindBugs>
7.</target>

让我们更加更进一步看看到底这段代码有什么作用。
第1行:注意到target依赖于compile,findBugs利用类文件而不是源文件执行检测,所以使target依赖于compile来确保FindBugs将会运行在最新编译类文件上。它可以就接受一系列类文件,Jar文件以及文件夹作为输入。
第2行:你必须指明FindBugs的文件路径,这里我使用了Ant属性定义了FindBugs.home

<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" />

Output输出属性指明了FindBugs结果输出格式,可选的格式有xml,text,emacs.如果不指定特殊的输出格式,其将结果打印到标准输出流。正如前面所说,使用XML格式可以通过图形界面来查看结果。
第3行:类class用来指明你想分析的文件位置,包括类文件,jar或者文件夹,如果想分析多个类文件或者Jar文件,需要为每个文件指明一个class标签,class标签是必须的除非projectFile标签被使用。可以查看FindBugs手册查看更多。
第4行:通过使用auxClasspath标签列出应用依赖库,这是一些你的应用需要的类文件,但是你不想让FindBugs分析,如果你不列出你不想分析的文件,FindBugs将会尽可能的分析所有的类文件。但是当它不能够找到某一个丢失的类文件时就是都出警示或提示。和class标签一样,可以使用多个或者不使用。
第5行:如果你的sourcePath标签被指明,path属性应该包含你应用程序的源码,指明这个路径后,当时用findBugs查看XML格式的错误结果时,FindBugs将会在源码中突出显示错误行。sourcePath是可选的。


未完待续

发布了34 篇原创文章 · 获赞 7 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章