FindBugs测试程序.docx

上传人:b****7 文档编号:9738666 上传时间:2023-02-06 格式:DOCX 页数:26 大小:115.49KB
下载 相关 举报
FindBugs测试程序.docx_第1页
第1页 / 共26页
FindBugs测试程序.docx_第2页
第2页 / 共26页
FindBugs测试程序.docx_第3页
第3页 / 共26页
FindBugs测试程序.docx_第4页
第4页 / 共26页
FindBugs测试程序.docx_第5页
第5页 / 共26页
点击查看更多>>
下载资源
资源描述

FindBugs测试程序.docx

《FindBugs测试程序.docx》由会员分享,可在线阅读,更多相关《FindBugs测试程序.docx(26页珍藏版)》请在冰豆网上搜索。

FindBugs测试程序.docx

FindBugs测试程序

FindBugs第1部分:

提高代码质量

静态分析工具承诺无需开发人员费劲就能找出代码中已有的缺陷。

当然,如果有多年的编写经验,就会知道这些承诺并不是一定能兑现。

尽管如此,好的静态分析工具仍然是工具箱中的无价之宝。

在这个由两部分组成的系列文章的第一部分中,高级软件工程师ChrisGrindstaff分析了FindBugs如何帮助提高代码质量以及排除隐含的缺陷。

代码质量工具的一个问题是它们容易为开发人员提供大量但并非真正问题的问题——即伪问题(falsepositives)。

出现伪问题时,开发人员要学会忽略工具的输出或者放弃它。

FindBugs的设计者DavidHovemeyer和WilliamPugh注意到了这个问题,并努力减少他们所报告的伪问题数量。

与其他静态分析工具不同,FindBugs不注重样式或者格式,它试图只寻找真正的缺陷或者潜在的性能问题。

FindBugs是什么?

FindBugs是一个静态分析工具,它检查类或者JAR文件,将字节码与一组缺陷模式进行对比以发现可能的问题。

有了静态分析工具,就可以在不实际运行程序的情况对软件进行分析。

不是通过分析类文件的形式或结构来确定程序的意图,而是通常使用Visitor模式(请参阅参考资料)。

图1显示了分析一个匿名项目的结果(为防止可怕的犯罪,这里不给出它的名字):

图1.FindBugsUI

让我们看几个FindBugs可以发现的问题。

本系列的第二篇文章“编写自定义检测器”解释了如何编写自定义检测器,以便发现特定于应用程序的问题。

回页首

问题发现的例子

下面的列表没有包括FindBug可以找到的所有问题。

相反,我侧重于一些更有意思的问题。

检测器:

找出hashequals不匹配

这个检测器寻找与equals()和hashCode()的实现相关的几个问题。

这两个方法非常重要,因为几乎所有基于集合的类——List、Map、Set等都调用它们。

一般来说,这个检测器寻找两种不同类型的问题——当一个类:

∙重写对象的equals()方法,但是没有重写它的hashCode方法,或者相反的情况时。

∙定义一个co-variant版本的equals()或compareTo()方法。

例如,Bob类定义其equals()方法为布尔equals(Bob),它覆盖了对象中定义的equals()方法。

因为Java代码在编译时解析重载方法的方式,在运行时使用的几乎总是在对象中定义的这个版本的方法,而不是在Bob中定义的那一个(除非显式将equals()方法的参数强制转换为Bob类型)。

因此,当这个类的一个实例放入到类集合中的任何一个中时,使用的是Object.equals()版本的方法,而不是在Bob中定义的版本。

在这种情况下,Bob类应当定义一个接受类型为Object的参数的equals()方法。

检测器:

忽略方法返回值

这个检测器查找代码中忽略了不应该忽略的方法返回值的地方。

这种情况的一个常见例子是在调用String方法时,如在清单1中:

清单1.忽略返回值的例子

1StringaString="bob";

2b.replace('b','p');

3if(b.equals("pop"))

这个错误很常见。

在第2行,程序员认为他已经用p替换了字符串中的所有b。

确实是这样,但是他忘记了字符串是不可变的。

所有这类方法都返回一个新字符串,而从来不会改变消息的接收者。

检测器:

Null指针对null的解引用(dereference)和冗余比较

这个检测器查找两类问题。

它查找代码路径将会或者可能造成null指针异常的情况,它还查找对null的冗余比较的情况。

例如,如果两个比较值都为null,那么它们就是冗余的并可能表明代码错误。

FindBugs在可以确定一个值为null而另一个值不为null时,检测类似的错误,如清单2所示:

清单2.Null指针示例

1Personperson=aMap.get("bob");

2if(person!

=null){

3person.updateAclearcase/"target="_blank">ccessTime();

4}

5Stringname=person.getName();

在这个例子中,如果第1行的Map不包括一个名为“bob”的人,那么在第5行询问person的名字时就会出现null指针异常。

因为FindBugs不知道map是否包含“bob”,所以它将第5行标记为可能null指针异常。

检测器:

初始化之前读取字段

这个检测器寻找在构造函数中初始化之前被读取的字段。

这个错误通常是——尽管不总是如此——由使用字段名而不是构造函数参数引起的,如清单3所示:

清单3.在构造函数中读取未初始化的字段

1publicclassThing{

2privateListactions;

3publicThing(StringstartingActions){

4StringTokenizertokenizer=newStringTokenizer(startingActions);

5while(tokenizer.hasMoreTokens()){

6actions.add(tokenizer.nextToken());

7}

8}

9}

在这个例子中,第6行将产生一个null指针异常,因为变量actions还没有初始化。

这些例子只是FindBugs所发现的问题种类的一小部分(更多信息请参阅参考资料)。

在撰写本文时,FindBugs提供总共35个检测器。

回页首

开始使用FindBugs

要运行FindBugs,需要一个版本1.4或者更高的JavaDevelopmentKit(JDK),尽管它可以分析由老的JDK创建的类文件。

要做的第一件事是下载并安装最新发布的FindBugs——当前是0.7.1(请参阅参考资料)。

幸运的是,下载和安全是相当简单的。

在下载了zip或者tar文件后,将它解压缩到所选的目录中。

就是这样了——安装就完成了。

安装完后,对一个示例类运行它。

就像一般文章中的情况,我将针对Windows用户进行讲解,并假定那些Unix信仰者可以熟练地转化这些内容并跟进。

打开命令行提示符号并进入FindBugs的安装目录。

对我来说,这是C:

\apps\FindBugs-0.7.3。

在FindBugs主目录中,有几个值得注意的目录。

文档在doc目录中,但是对我们来说更重要的是,bin目录包含了运行FindBugs的批处理文件,这使我们进入下一部分。

回页首

运行FindBugs

像如今的大多数数工具一样,可以以多种方式运行FindBugs——从GUI、从命令行、使用Ant、作为Eclipse插件程序和使用Maven。

我将简要提及从GUI运行FindBugs,但是重点放在用Ant和命令行运行它。

部分原因是由于GUI没有提供命令行的所有选项。

例如,当前不能指定要加入的过滤器或者在UI中排除特定的类。

但是更重要的原因是我认为FindBugs最好作为编译的集成部分使用,而UI不属于自动编译。

使用FindBugsUI

使用FindBugsUI很直观,但是有几点值得说明。

如图1所示,使用FindBugsUI的一个好处是对每一个检测到的问题提供了说明。

图1显示了缺陷Nakednotifyinmethod的说明。

对每一种缺陷模式提供了类似的说明,在第一次熟悉这种工具时这是很有用的。

窗口下面的Sourcecode选项卡也同样有用。

如果告诉FindBugs在什么地方寻找代码,它就会在转换到相应的选项卡时突出显示有问题的那一行。

值得一提的还有在将FinBugs作为Ant任务或者在命令行中运行FindBugs时,选择xml作为ouput选项,可以将上一次运行的结果装载到UI中。

这样做是同时利用基于命令行的工具和UI工具的优点的一个很好的方法。

将FindBugs作为Ant任务运行

让我们看一下如何在Ant编译脚本中使用FindBugs。

首先将FindBugsAnt任务拷贝到Ant的lib目录中,这样Ant就知道新的任务。

将FIND_BUGS_HOME\lib\FindBugs-ant.jar拷贝到ANT_HOME\lib。

现在看看在编译脚本中要加入什么才能使用FindBugs任务。

因为FindBugs是一个自定义任务,将需要使用taskdef任务以使Ant知道装载哪一个类。

通过在编译文件中加入以下一行做到这一点:

在定义了taskdef后,可以用它的名字FindBugs引用它。

下一步要在编译中加入使用新任务的目标,如清单4所示:

清单4.创建FindBugs目录

1

2

3

\apps\JEdit4.1\jedit.jar"/>

4

5

\tempcbg\jedit"/>

6

7

让我们更详细地分析这段代码中所发生的过程。

第1行:

注意target取决于编译。

一定要记住处理的是类文件而不是源文件,这样使target对应于编译目标保证了FindBugs可在最新的类文件运行。

FindBugs可以灵活地接受多种输入,包括一组类文件、JAR文件、或者一组目录。

第2行:

必须指定包含FindBugs的目录,我是用Ant的一个属性完成的,像这样:

\apps\FindBugs-0.7.3"/>

可选属性output指定FindBugs的结果使用的输出格式。

可能的值有xml、text或者emacs。

如果没有指定outputFile,那么FindBugs会使用标准输出。

如前所述,XML格式有可以在UI中观看的额外好处。

第3行:

class元素用于指定要FindBugs分析哪些JAR、类文件或者目录。

分析多个JAR或者类文件时,要为每一个文件指定一个单独的class元素。

除非加入了projectFile元素,否则需要class元素。

更多细节请参阅FindBugs手册。

第4行:

用嵌套元素auxClasspath列出应用程序的依赖性。

这些是应用程序需要但是不希望FindBugs分析的类。

如果没有列出应用程序的依赖关系,那么FindBugs仍然会尽可能地分析类,但是在找不到一个缺少的类时,它会抱怨。

与class元素一样,可以在FindBugs元素中指定多个auxClasspath元素。

auxClasspath元素是可选的。

第5行:

如果指定了sourcePath元素,那么path属性应当表明一个包含应用程序源代码的目录。

指定目录使FindBugs可以在GUI中查看XML结果时突出显示出错的源代码。

这个元素是可选的。

上面就是基本内容了。

让我们提前几个星期。

过滤器

您已经将FindBugs引入到了团队中,并运行它作为您的每小时/每晚编译过程的一部分。

当团队越来越熟悉这个工具时,出于某些原因,您决定所检测到的一些缺陷对于团队来说不重要。

也许您不关心一些类是否返回可能被恶意修改的对象——也许,像JEdit,有一个真正需要的(honest-to-goodness)、合法的理由调用System.gc()。

总是可以选择“关闭”特定的检测器。

在更细化的水平上,可以在指定的一组类甚至是方法中查找问题时,排除某些检测器。

FindBugs提供了这种细化的控制,可以排除或者包含过滤器。

当前只有用命令行或者Ant启动的FindBugs中支持排除和包含过滤器。

正如其名字所表明的,使用排除过滤器来排除对某些缺陷的报告。

较为少见但仍然有用的是,包含过滤器只能用于报告指定的缺陷。

过滤器是在一个XML文件中定义的。

可以在命令行中用一个排除或者包含开关、或者在Ant编译文件中用excludeFilter和includeFilter指定它们。

在下面的例子中,假定使用排除开关。

还要注意在下面的讨论中,我对“bugcode”、“bug”和“detector”的使用具有某种程度的互换性。

可以有不同的方式定义过滤器:

∙匹配一个类的过滤器。

可以用这些过滤器忽略在特定类中发现的所有问题。

∙匹配一个类中特定缺陷代码(bugcode)的过滤器。

可以用这些过滤器忽略在特定类中发现的一些缺陷。

∙匹配一组缺陷的过滤器。

可以用这些过滤器忽略所分析的所有类中的一组缺陷。

∙匹配所分析的一个类中的某些方法的过滤器。

可以用这些过滤器忽略在一个类中的一组方法中发现的所有缺陷。

∙匹配在所分析的一个类中的方法中发现的某些缺陷的过滤器。

可以用这些过滤器忽略在一组方法中发现的特定缺陷。

知道了这些就可以开始使用了。

有关其他定制FindBugs方法的更多信息,请参阅FindBugs文档。

知道如何设置编译文件以后,就让我们更详细地分析如何将FindBugs集成到编译过程中吧!

回页首

将FindBugs集成到编译过程中

在将FindBugs集成到编译过程当中可以有几种选择。

总是可以在命令行执行FindBugs,但是您很可能已经使用Ant进行编译,所以最自然的方法是使用FindBugsAnt任务。

因为我们在如何运行FindBugs一节中讨论了使用FindBugsAnt任务的基本内容,所以现在讨论应当将FindBugs加入到编译过程中的几个理由,并讨论几个可能遇到的问题。

为什么应该将FindBugs集成到编译过程中?

经常问到的第一个问题是为什么要将FindBugs加入到编译过程中?

虽然有大量理由,最明显的回答是要保证尽可能早地在进行编译时发现问题。

当团队扩大,并且不可避免地在项目中加入更多新开发人员时,FindBugs可以作为一个安全网,检测出已经识别的缺陷模式。

我想重申在一篇FindBugs论文中表述的一些观点。

如果让一定数量的开发人员共同工作,那么在代码中就会出现缺陷。

像FindBugs这样的工具当然不会找出所有的缺陷,但是它们会帮助找出其中的部分。

现在找出部分比客户在以后找到它们要好——特别是当将FindBugs结合到编译过程中的成本是如此低时。

一旦确定了加入哪些过滤器和类,运行FindBugs就没什么成本了,而带来的好处就是它会检测出新缺陷。

如果编写特定于应用程序的检测器,则这个好处可能更大。

生成有意义的结果

重要的是要认识到这种成本/效益分析只有在不生成大量误检时才有效。

换句话说,如果在每次编译时,不能简单地确定是否引入了新的缺陷,那么这个工具的价值就会被抵消。

分析越自动化越好。

如果修复缺陷意味着必须吃力地分析检测出的大量不相干的缺陷,那么您就不会经常使用它,或者至少不会很好地使用它。

确定不关心哪些问题并从编译中排除它们。

也可以挑出确实关注的一小部分检测器并只运行它们。

另一种选择是从个别的类中排除一组检测器,但是其他的类不排除。

FindBugs提供了使用过滤器的极大灵活性,这可帮助生成对团队有意义的结果,由此我们进入下一节。

确定用FindBugs的结果做什么

可能看来很显然,但是您想不到我参与的团队中有多少加入了类似FindBugs这样的工具而没有真正利用它。

让我们更深入地探讨这个问题——用结果做什么?

明确回答这个问题是困难的,因为这与团队的组织方式、如何处理代码所有权问题等有很大关系。

不过,下面是一些指导:

∙可以考虑将FindBugs结果加入到源代码管理(SCM)系统中。

一般的经验做法是不将编译工件(artifact)放到SCM系统中。

不过,在这种特定情况下,打破这个规则可能是正确的,因为它使您可以监视代码质量随时间的变化。

∙可以选择将XML结果转换为可以发送到团队的网站上的HTML报告。

转换可以用XSL样式表或者脚本实现。

有关例子请查看FindBugs网站或者邮件列表(请参阅参考资料)。

∙像FindBugs这样的工具通常会成为用于敲打团队或者个人的政治武器。

尽量抵制这种做法或者不让它发生——记住,它只是一个工具,它可以帮助改进代码的质量。

有了这种思想,在下一部分中,我将展示如何编写自定义缺陷检测器。

回页首

结束语

我鼓励读者对自己的代码试用静态分析工具,不管是FindBugs、PMD还是其他的。

它们是有用的工具,可以找出真正的问题,而FindBugs是在消除误检方面做得最好的工具。

此外,它的可插入结构提供了编写有价值的、特定于应用程序的检测器的、有意思的测试框架。

在本系列的第2部分中,我将展示如何编写自定义检测器以找出特定于应用程序的问题。

FindBugs,第2部分:

编写自定义检测器

FindBugs,第2部分:

编写自定义检测器

FindBugs,第2部分:

编写自定义检测器

如何编写自定义检测器以查找特定于应用程序的问题

FindBugs是一种可以扩展和定制以满足自己团队独特要求的静态分析工具。

在本系列的第2部分中,高级软件工程师ChrisGrindstaff向您展示如何创建特定于应用程序的缺陷检测器。

1

评论:

ChrisGrindstaff(chris@gstaff.org),软件工程师

关闭[x]

ChrisGrindstaff是在北加利福尼亚ResearchTrianglePark工作的IBM高级软件工程师。

Chris在7岁时编写了他的第一个程序,当时他让小学老师认识到“键入”句子与手写它们一样费力。

Chris目前参与了不同的开放源代码项目。

他大量使用Eclipse并编写了几个流行的Eclipse插件程序,可以在他的网站找到这些插件程序。

可以通过cgrinds@或者chris@gstaff.org与Chrise联系。

2004年6月01日

内容

o编写自定义缺陷检测器

o结束语

o参考资料

o评论

在本系统的第一篇文章中,我展示了如何设置和执行FindBugs。

现在我们将分析FindBugs最强大的功能——自定义检测器。

首先,我将说明为什么自定义缺陷检测器很有用,然后我将引导读者完成一个详细的例子。

编写自定义缺陷检测器

为什么要编写自定义缺陷检测器?

我在被要求对一个小组的性能问题进行检查时遇到了这个问题。

很显然小组自已开发的日志框架(像所有日志框架一样)随着时间而增大。

原来是随意地大量调用Logger。

不幸的是,随着小组的扩大,他们的应用程序的性能也在变差,因为他们总是产生昂贵的日志消息——而当日志框架发现禁用日志后,这些消息只能是被它抛弃。

解决这个问题的标准方式是在构造昂贵的日志消息之前,首先检查是否启用了日志。

换句话说,使用一个像清单1这样的监护子句:

清单1.监护日志示例

if(Logger.isLogging()){

Logger.log("perf",anObjectWithExpensiveToString+anotherExpensiveToString);

}

本系列的第一篇文章“改进代码质量”介绍了FindBugs,这是一个检查类或者JAR文件以发现可能存在的问题的静态分析工具,并展示了如何有效地使用它。

这个小组认定这是一种恰当的日志做法,并对现有的代码加以改变以体现新的做法。

在这个非常大的项目的截止时间快到时听到还有很多地方未改完是不会让人感到意外的。

小组需要有更好的方法找出尚未修改的地方。

因为本文讨论的是FindBugs,所以我们将用FindBugs解决这个问题。

目标是编写一个FindBugs检测器,它将找出代码中调用日志框架而未被包装在监护子句中的所有地方。

当初编写这个检测器时,我将问题分解为几个单独的步骤:

1.首先用一条未监护的日志语句编写一个测试案例。

2.其次,查看FindBugs源代码以查找类似于我要编写的检测器类似的检测器。

3.然后创建正确打包的JAR文件(使用编译脚本),使FindBugs知道如何装载未监护检测器。

4.运行这个测试案例并实现代码使测试通过。

5.最后,加入更多测试案例,继续这个过程直到最后完成。

在浏览代码时,我特别检查了BytecodeScanningDetector和ByteCodePatternDetector的子类型。

实现扫描检测器要做更多工作,但是它们能检测更一般类型的问题。

如果所要检测的问题可以表述为一组字节码模式,则模式检测器是一种好的选择。

它的一个好例子是BCPMethodReturnCheck检测器——它查找那些不同方法的返回类型有可能被忽略的地方。

BCPMethodReturnCheck可以很容易地描述为一组模式,它查找在POP或者POP2指令后面调用某些方法的地方。

绝大多数检测器目前被编写为扫描检测器,尽管我认为这仅仅是因为开发人员还没有足够的时间转移到ByteCodePatternDetector。

我决定使用FindRunInvocations作为例子,主要是因为它是最小的一种检测器。

对我来说如何实现使用一组模式的检测器还不是很明确。

FindBugs利用了ByteCodeEngineeringLibrary,或称为BCEL(请参阅参考资料),以实现其检测器。

所有字节码扫描检测器都基于visitor模式,FindBugs实现了这个模式。

它提供了这些方法的默认实现,在实现自定义检测器时要覆盖这些方法。

请分析BetterVisitor及其子类以获得更多细节

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1