?
态分析工h诺无需开发h员费劲就能找Z码中已有的缺陗当Ӟ如果有多q的~写l验Q就会知道这些承诺ƈ不是一定能兑现。尽如此,好的静态分析工?
仍然是工L中的无h(hun)之宝。在q个׃部分l成的系列文章的W一部分中,高软g工程?Chris Grindstaff 分析?FindBugs
如何帮助提高代码质量以及排除隐含的缺陗?/blockquote>
代码质量工具的一个问题是它们Ҏ(gu)为开发h员提供大量但q真正问题的问题——即
伪问题(false positivesQ?/em>?
出现伪问题时Q开发h员要学会忽略工具的输出或者放弃它。FindBugs 的设计?David Hovemeyer ?William Pugh
注意Cq个问题Qƈ努力减少他们所报告的伪问题数量。与其他静态分析工具不同,FindBugs
不注重样式或者格式,它试囑֏L真正的缺h者潜在的性能问题?
FindBugs 是什么?
FindBugs
是一个静态分析工P它检查类或?JAR
文gQ将字节码与一l缺h式进行对比以发现可能的问题。有了静态分析工P可以在不实际运行程序的情况对Y件进行分析。不是通过分析cL件的形式或结
构来定E序的意图,而是通常使用 Visitor 模式Q请参阅 参考资?/a>Q。图 1 昄了分析一个匿名项目的l果Qؓ防止可怕的犯罪Q这里不l出它的名字Q:
?1. FindBugs UI
让我们看几个 FindBugs 可以发现的问题?/p>
 |
本系列的W二文?#8220;
~写自定义检器”解释了如何编写自定义器Q?以便发现特定于应用程序的问题?
|
|
问题发现的例?/span>
下面的列表没有包?FindBug 可以扑ֈ?
所?/em>问题。相反,我侧重于一些更有意思的问题?
器Q找?hash equals 不匹?/strong>
q个器L?
equals()
?
hashCode()
的实现相关的几个问题。这两个Ҏ(gu)非常重要Q因为几乎所有基于集合的cZ?List、Map、Set {都调用它们。一般来_q个器L两种不同cd的问题——当一个类Q?
- 重写对象?
equals()
Ҏ(gu)Q但是没有重写它?
hashCode
Ҏ(gu)Q或者相反的情况时?
- 定义一?co-variant 版本?
equals()
?
compareTo()
Ҏ(gu)。例如,
Bob
cd义其
equals()
Ҏ(gu)为布?yu)?
equals(Bob)
Q它覆盖了对象中定义?
equals()
Ҏ(gu)。因?Java 代码在编译时解析重蝲Ҏ(gu)的方式,在运行时使用的几乎L在对象中定义的这个版本的Ҏ(gu)Q而不是在
Bob
中定义的那一个(除非昑ּ?
equals()
Ҏ(gu)的参数强制{换ؓ
Bob
cdQ。因此,当这个类的一个实例放入到c集合中的Q何一个中Ӟ使用的是
Object.equals()
版本的方法,而不是在
Bob
中定义的版本。在q种情况下,
Bob
cd当定义一个接受类型ؓ
Object
的参数的
equals()
Ҏ(gu)?
器Q忽略方法返回?/strong>
q个器查找代码中忽略了不应该忽略的Ҏ(gu)q回值的地方。这U情늚一个常见例子是在调?
String
Ҏ(gu)Ӟ如在清单 1 中:
清单 1. 忽略q回值的例子
1 String aString = "bob";
2 b.replace('b', 'p');
3 if(b.equals("pop"))
|
q个错误很常见。在W?2 行,E序员认Z已经?p 替换了字W串中的所?b。确实是q样Q但是他忘记了字W串是不可变的。所有这cL法都q回一个新字符Ԍ而从来不会改变消息的接收者?/p>
器QNull 指针?null 的解引用QdereferenceQ和冗余比较
q个器查找两类问题。它查找代码路径会或者可能造成 null 指针异常的情况,它还查找?null
的冗余比较的情况。例如,如果两个比较值都?nullQ那么它们就是冗余的q可能表明代码错误。FindBugs 在可以确定一个gؓ null
而另一个g?null Ӟ类似的错误Q如清单 2 所C:
清单 2. Null 指针CZ
1 Person person = aMap.get("bob");
2 if (person != null) {
3 person.updateAccessTime();
4 }
5 String name = person.getName();
|
在这个例子中Q如果第 1 行的
Map
不包括一个名?#8220;bob”的hQ那么在W?5 行询?
person
的名字时׃出现 null 指针异常。因?FindBugs 不知?map 是否包含“bob”Q所以它?yu)?5 行标Cؓ可能 null 指针异常?
器Q初始化之前d字段
q个器L在构造函C初始化之前被d的字Dc这个错误通常是——尽不L如此——由使用字段名而不是构造函数参数引LQ如清单 3 所C:
清单 3. 在构造函Cd未初始化的字D?/strong>
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 }
|
在这个例子中Q第 6 行将产生一?null 指针异常Q因为变?
actions
q没有初始化?
q些例子只是 FindBugs 所发现的问题种cȝ一部分(更多信息请参?
参考资?/a>Q。在撰写本文ӞFindBugs 提供d 35 个检器?
开始?FindBugs
要运?FindBugsQ需要一个版?1.4 或者更高的 Java Development Kit QJDKQ,管它可以分析由老的 JDK 创徏的类文g。要做的W一件事是下载ƈ安装最新发布的 FindBugs——当前是 0.7.1 Q请参阅
参考资?/a>Q。幸q的是,下蝲和安全是相当单的。在下蝲?zip 或?tar 文g后,它解压~到所选的目录中。就是这样了——安装就完成了?
?
装完后,对一个示例类q行它。就像一般文章中的情况,我将针对 Windows 用户q行讲解Qƈ假定那些 Unix
信Ԓ者可以熟l地转化q些内容q跟q。打开命o行提C符号ƈq入 FindBugs 的安装目录。对我来_q是
C:\apps\FindBugs-0.7.3?/p>
?FindBugs ȝ录中Q有几个值得注意的目录。文档在 doc 目录中,但是Ҏ(gu)们来说更重要的是Qbin 目录包含了运?FindBugs 的批处理文gQ这使我们进入下一部分?/p>
q行 FindBugs
?
如今的大多数数工具一P可以以多U方式运?FindBugs——从 GUI、从命o行、?Ant、作?Eclipse 插gE序和?
Maven。我简要提及从 GUI q行 FindBugsQ但是重Ҏ(gu)在用 Ant 和命令行q行它。部分原因是׃ GUI
没有提供命o行的所有选项。例如,当前不能指定要加入的qo器或者在 UI 中排除特定的cR但是更重要的原因是我认?FindBugs
最好作为编译的集成部分使用Q?UI 不属于自动编译?/p>
使用 FindBugs UI
使用 FindBugs UI 很直观,但是有几点值得说明。如
?1所C,使用 FindBugs UI 的一个好处是Ҏ(gu)一个检到的问题提供了说明。图 1 昄了缺?
Naked notify in method的说明。对每一U缺h式提供了cM的说明,在第一ơ熟(zhn)这U工hq是很有用的。窗口下面的 Source code 选项卡也同样有用。如果告?FindBugs 在什么地方寻找代码,它就会在转换到相应的选项卡时H出昄有问题的那一行?
值得一提的q有在将 FinBugs 作ؓ Ant d或者在命o行中q行 FindBugs Ӟ选择
xml
作ؓ
ouput
选项Q可以将上一ơ运行的l果装蝲?UI 中。这样做是同时利用基于命令行的工具和 UI 工具的优点的一个很好的Ҏ(gu)?
?FindBugs 作ؓ Ant dq行
?
我们看一下如何在 Ant ~译脚本中?FindBugs。首先将 FindBugs Ant d拯?Ant ?lib 目录中,q样
Ant q道新的Q务。将 FIND_BUGS_HOME\lib\FindBugs-ant.jar 拯?ANT_HOME\lib?/p>
现在看看在编译脚本中要加入什么才能?FindBugs d。因?FindBugs 是一个自定义dQ将需要?
taskdef
d以 Ant 知道装蝲哪一个类。通过在编译文件中加入以下一行做到这一点:
<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/>
|
在定义了
taskdef
后,可以用它的名?
FindBugs
引用它。下一步要在编译中加入使用CQ务的目标Q如清单 4 所C:
清单 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>
|
让我们更详细地分析这D代码中所发生的过E?/p>
W?1 行: 注意
target
取决于编译。一定要C处理的是cL件?
?/em> 是源文gQ这样
target
对应于编译目标保证了 FindBugs 可在最新的cL件运行。FindBugs 可以灉|地接受多U输入,包括一l类文g、JAR 文g、或者一l目录?
W?2 行:必须指定包含 FindBugs 的目录,我是?Ant 的一个属性完成的Q像q样Q?
<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" />
|
可选属?
output
指定 FindBugs 的结果用的输出格式。可能的值有
xml
?
text
或?
emacs
。如果没有指?
outputFile
Q那?FindBugs 会用标准输出。如前所qͼXML 格式有可以在 UI 中观看的额外好处?
W?3 行:
class
元素用于指定?FindBugs 分析哪些 JAR、类文g或者目录。分析多?JAR 或者类文gӞ要ؓ每一个文件指定一个单独的
class
元素。除非加入了
projectFile
元素Q否则需?
class
元素。更多细节请参阅 FindBugs 手册?
W?4 行: 用嵌套元?
auxClasspath
列出应用E序的依赖性。这些是应用E序需要但是不希望 FindBugs 分析的类。如果没有列出应用程序的依赖关系Q那?FindBugs 仍然会尽可能地分析类Q但是在找不C个缺的cLQ它会抱怨。与
class
元素一P可以?FindBugs 元素中指定多?
auxClasspath
元素?
auxClasspath
元素是可选的?
W?5 行: 如果指定?
sourcePath
元素Q那?
path
属性应当表明一个包含应用程序源代码的目录。指定目录 FindBugs 可以?GUI 中查?XML l果时突出显C出错的源代码。这个元素是可选的?
上面是基本内容了。让我们提前几个星期?
qo?/span>
(zhn)?
已经?FindBugs
引入C团队中,q运行它作ؓ(zhn)的每小?每晚~译q程的一部分。当团队来熟(zhn)这个工hQ出于某些原因,(zhn)决定所到的一些缺陷对于团队来说不?
要。也许?zhn)不关心一些类是否q回可能被恶意修改的对象——也许,?
JEditQ有一个真正需要的Qhonest-to-goodnessQ、合法的理由调用 System.gc()
?
L可以选择“关闭”特定?
器。在更细化的水^上,可以在指定的一l类甚至是方法中查找问题Ӟ排除某些器。FindBugs
提供了这U细化的控制Q可以排除或者包含过滤器。当前只有用命o行或?Ant 启动?FindBugs
中支持排除和包含qo器。正如其名字所表明的,使用排除qo器来排除Ҏ(gu)些缺L报告。较为少见但仍然有用的是Q包含过滤器只能用于报告指定的缺陗过?
器是在一?XML 文g中定义的。可以在命o行中用一个排除或者包含开兟뀁或者在 Ant ~译文g中用 excludeFilter
?
includeFilter
指定它们。在下面的例子中Q假定用排除开兟뀂还要注意在下面的讨ZQ我?“bugcode”?#8220;bug” ?#8220;detector”的用具有某U程度的互换性?
可以有不同的方式定义qo器:
- 匚w一个类的过滤器。可以用q些qo?忽略在特定类中发现的所有问题?
- 匚w一个类中特定缺陷代码(bugcodeQ的 qo器。可以用q些qo器忽略在特定cM发现的一些缺陗?
- 匚w一l缺Lqo器。可以用q些qo?忽略所分析的所有类中的一l缺陗?
- 匚w所分析的一个类中的某些Ҏ(gu)的过滤器。可以用q些qo器忽略在一个类中的一l方法中发现的所有缺陗?
- 匚w在所分析的一个类中的Ҏ(gu)中发现的某些~陷的过滤器。可以用q些qo器忽略在一l方法中发现的特定缺陗?/li>
知道了这些就可以开始用了。有兛_他定?FindBugs Ҏ(gu)的更多信息,请参?FindBugs 文档。知道如何设|编译文件以后,p我们更详l地分析如何?FindBugs 集成到编译过E中吧!
?FindBugs 集成到编译过E中
在将 FindBugs 集成到编译过E当中可以有几种选择。L可以在命令行执行 FindBugsQ但是?zhn)很可能已l?Ant q行~译Q所以最自然的方法是使用 FindBugs Ant d。因为我们在
如何q行 FindBugs一节中讨论了?FindBugs Ant d的基本内容,所以现在讨论应当将 FindBugs 加入到编译过E中的几个理由,q讨论几个可能遇到的问题?
Z么应该将 FindBugs 集成到编译过E中Q?/span>
l?
帔R到的W一个问题是Z么要?FindBugs
加入到编译过E中Q虽然有大量理由Q最明显的回{是要保证尽可能早地在进行编译时发现问题。当团队扩大Qƈ且不可避免地在项目中加入更多新开发h?
ӞFindBugs 可以作ؓ一个安全网Q检出已经识别的缺h式。我想重甛_一?FindBugs
论文中表q的一些观炏V如果让一定数量的开发h员共同工作,那么在代码中׃出现~陷。像 FindBugs
q样的工具当然不会找出所有的~陷Q但是它们会帮助扑և其中的部分。现在找出部分比客户在以后找到它们要好——特别是当将 FindBugs
l合到编译过E中的成本是如此低时?/p>
一旦确定了加入哪些qo器和c,q行 FindBugs 没什么成本了Q而带来的好处是它会出新缺陗如果编写特定于应用E序的检器Q则q个好处可能更大?/p>
生成有意义的l果
?
要的是要认识到这U成?效益分析只有在不生成大量误检时才有效。换句话_如果在每ơ编译时Q不能简单地定是否引入了新的缺P那么q个工具的h(hun)值就
会被抉|。分析越自动化越好。如果修复缺h味着必须吃力地分析检出的大量不相干的缺P那么(zhn)就不会l常使用它,或者至不会很好地使用它?/p>
定不关心哪些问题ƈ从编译中排除它们。也可以挑出
实x的一部分检器q只q行它们。另一U选择是从个别的类中排除一l检器Q但是其他的cM排除。FindBugs 提供了用过滤器的极大灵zL,q可帮助生成对团队有意义的结果,由此我们q入下一节?
定?FindBugs 的结果做什?/span>
?
能看来很昄Q但是?zhn)想不到我参与的团队中有多加入了cM FindBugs
q样的工兯没有真正利用它。让我们更深入地探讨q个问题——用l果做什么?明确回答q个问题是困隄Q因与团队的l织方式、如何处理代码所有权问题
{有很大关系。不q,下面是一些指|
- 可以考虑?FindBugs l果加入到源代码理QSCMQ系l中。一般的l验做法是不编译工ӞartifactQ放?SCM pȝ中。不q,在这U特定情况下Q打破这个规则可能是正确的,因ؓ它(zhn)可以监视代码质量随旉的变化?
- 可以选择?XML l果转换为可以发送到团队的网站上?HTML 报告。{换可以用 XSL 样式表或者脚本实现。有关例子请查看 FindBugs |站或者邮件列表(请参?
参考资?/a>Q?
- ?FindBugs q样的工具通常会成为用于敲打团队或者个人的政治武器。尽量抵制这U做法或者不让它发生——记住,它只是一个工P它可以帮助改q代码的质量。有了这U思想Q在下一部分中,我将展示如何~写自定义缺h器?/li>
l束?/span>
?
鼓励读者对自己的代码试用静态分析工P不管?FindBugs、PMD q是其他的。它们是有用的工P可以扑և真正的问题,?FindBugs
是在消除误检斚w做得最好的工具。此外,它的可插入结构提供了~写有h(hun)值的、特定于应用E序的检器的、有意思的试框架。在本系列的 W?2 部分中,我将展示如何~写自定义检器以找出特定于应用E序的问题?
参考资?
关于作?/span>
 |
|
 |
Chris
Grindstaff 是在北加利福g Research Triangle Park 工作?IBM 高软g工程师。Chris ?7
岁时~写了他的第一个程序,当时他让学老师认识?#8220;键入”句子与手写它们一栯力。Chris 目前参与了不同的开放源代码目。他大量使用
Eclipse q编写了几个行?Eclipse 插gE序Q可以在他的 |站扑ֈq些插gE序。可以通过
cgrinds@us.ibm.com或?
chris@gstaff.org?Chrise 联系?
|