天天看点

Java代码缺陷自动分析工具介绍 1         FindBugs是什么? 2         FindBugs可以做什么? 3         准备使用FindBugs 4         运行 FindBugs 5         其它分析工具 6         志鸿公司使用自动分析工具案例

Java代码缺陷自动分析工具主要有:Findbugs、PMD和CheckStyle工具。这里重点介绍Findbugs的使用,简要提及PMD和CheckStyle工具的使用。

FindBugs 是一个java bytecode静态分析工具,它可以帮助java工程师提高代码质量以及排除隐含的缺陷。

FindBugs检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。

FindBugs提供了35个检测器来检测字节码中可能的缺陷。可以做的事情主要有:

找与 <code>equals()</code> 和 <code>hashCode()</code> 的实现相关的几个问题。这两个方法非常重要,因为几乎所有基于集合的类---List、Map、Set 等都调用它们。一般来说,这个检测器寻找两种不同类型的问题:

①当一个类重写对象的 <code>equals()</code> 方法,但是没有重写它的 <code>hashCode</code> 方法,或者相反的情况时。

②定义一个 co-variant 版本的 <code>equals()</code> 或 <code>compareTo()</code> 方法。例如, <code>Bob</code> 类定义其 <code>equals()</code> 方法为布尔 <code>equals(Bob)</code>,它覆盖了对象中定义的 <code>equals()</code> 方法。因为 Java 代码在编译时解析重载方法的方式,在运行时使用的几乎总是在对象中定义的这个版本的方法,而不是在 <code>Bob</code> 中定义的那一个(除非显式将 <code>equals()</code> 方法的参数强制转换为 <code>Bob</code> 类型)。因此,当这个类的一个实例放入到类集合中的任何一个中时,使用的是 <code>Object.equals()</code> 版本的方法,而不是在 <code>Bob</code> 中定义的版本。在这种情况下, <code>Bob</code> 类应当定义一个接受类型为 <code>Object</code> 的参数的 <code>equals()</code> 方法。

这个检测器查找代码中忽略了不应该忽略的方法返回值的地方。这种情况的一个常见例子是在调用 <code>String</code> 方法时,例如:<code></code>

<code>1 String aString = "bob";</code>

<code>2 b.replace('b', 'p');</code>

<code>3 if(b.equals("pop"))</code>

这个错误很常见。在第 2 行,程序员认为他已经用 p 替换了字符串中的所有 b。确实是这样,但是他忘记了字符串是不可变的。所有这类方法都返回一个新字符串,而从来不会改变消息的接收者。

这个检测器查找两类问题。它查找代码路径将会或者可能造成 null 指针异常的情况,它还查找对 null 的冗余比较的情况。例如,如果两个比较值都为 null,那么它们就是冗余的并可能表明代码错误。FindBugs 在可以确定一个值为 null 而另一个值不为 null 时,检测类似的错误,例如:

<code>1 Person person = aMap.get("bob");</code>

<code>2 if (person != null) {</code>

<code>3      person.updateAccessTime();</code>

<code>4 }</code>

<code>5 String name = person.getName();</code>

在这个例子中,如果第 1 行的 <code>Map</code> 不包括一个名为“bob”的人,那么在第 5 行询问 <code>person</code> 的名字时就会出现 null 指针异常。因为 FindBugs 不知道 map 是否包含“bob”,所以它将第 5 行标记为可能 null 指针异常。

这个检测器寻找在构造函数中初始化之前被读取的字段。这个错误通常是由使用字段名而不是构造函数参数引起的,例如在构造函数中读取未初始化的字段:

<code>1 public class Thing {</code>

<code>2      private List actions;</code>

<code>3      public Thing(String startingActions) {</code>

<code>4       StringTokenizer tokenizer = new StringTokenizer(startingActions);</code>

<code>5          while (tokenizer.hasMoreTokens()) {</code>

<code>6              actions.add(tokenizer.nextToken());</code>

<code>7       }</code>

<code>8      }</code>

<code>9 }</code>

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

对标准 Java 命令规范的测试:变量名称不应太短;方法名称不应过长;类名称应当以小写字母开头;方法和字段名应当以小写字母开头,等等。

查找从未使用的私有字段和本地变量、执行不到的语句、从未调用的私有方法,等等。

例如: switch 语句应当有 default 块,应当避免深度嵌套的 if 块,不应当给参数重新赋值,不应该对 double 值进行相等比较。

检查 import 语句的问题,比如同一个类被导入两次或者被导入 java.lang 的类中。

查找测试用例和测试方法的特定问题,例如方法名称的正确拼写,以及 suite() 方法是不是 static 和 public。

找出处理字符串时遇到的常见问题,例如重复的字符串标量,调用 String 构造函数,对 String 变量调用 toString() 方法。

检查 for、 if、 while 和 else 语句是否使用了括号。

测试过长的方法、有太多方法的类以及重构方面的类似问题。

因为在 Java 语言中, finalize() 方法不是那么普遍,它们的使用规则虽然很详细,但是人们对它们相对不是很熟悉。这类检查查找 finalize() 方法的各种问题,例如空的终结函数,调用其他方法的 finalize() 方法,对 finalize() 的显式调用,等等。

用于 clone() 方法的新规则。凡是重写 clone() 方法的类都必须实现 Cloneable, clone() 方法应该调用 super.clone(),而 clone() 方法应该声明抛出 CloneNotSupportedException 异常,即使实际上没有抛出异常,也要如此。

查找类之间过度耦合的迹象,比如导入内容太多;在超类型或接口就已经够用的时候使用子类的类型;类中的字段、变量和返回类型过多等。

针对异常的检查:不应该声明该方法而抛出 java.lang.Exception 异常,不应当将异常用于流控制,不应该捕获 Throwable,等等。

查找 java.util.logging.Logger 的不当使用,包括非终状态(nonfinal)、非静态的记录器,以及在一个类中有多个记录器。

检查文件或通讯方面,是否忘记Close的情况。

可以构建自己的规则集

也可以到StarTeam取相应安装文件到本地,将它解压缩到所选的目录中,安装就完成了。

要运行 FindBugs,需要一个版本 1.4 或者更高的 Java Development Kit (JDK)。

安装完后,要增加两个环境变量:

FINDBUGS_HOME,例如:FINDBUGS_HOME =D:\findbugs-0.9.3

JAVA_HOME,例如:JAVA_HOME=D:\j2sdk1.4.2_06

在 FindBugs 主目录中,有几个值得注意的目录。文档在 doc 目录中,但是对我们来说更重要的是bin 目录,该包含了运行 FindBugs 的批处理文件。

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

这里将重点提及从 GUI 运行 FindBugs,简要提及使用Ant 和命令行运行。

使用 FindBugs UI 很直观。使用 FindBugs UI 的一个好处是对每一个检测到的问题提供了说明,图 1 显示了缺陷 <b>Test</b>的说明。

图一:项目Test的缺陷说明

对每一种缺陷模式提供了类似的说明。窗口下面的 Source code 选项卡很有用。如果告诉 FindBugs 在什么地方寻找代码,它就会在转换到相应的选项卡时突出显示有问题的那一行。

图一中的上部有根据不同类别来检查缺陷的选项,选项有(By Class、By Package、By Bug Type、By Bug Category)。

图二是【file】选项,主要功能有:创建新项目、打开/关闭项目、储存项目、储存Bugs信息、装载Buugs信息

图三是【View】选项,这些选项很有用,可以切换不同模式的视图。

图四是【Settings】选项,是对检测的缺陷模式进行选择。

图五是【创建新项目】选项

<b>第一次使用</b>

(1)       依照图五【创建新项目】选项说明,先创建新项目,输入被检查的class或jar路径、java源文件路径及程序运行依赖的class或jar路径

(2)       按【FingBugs】按钮开始检查

(3)       如果出现下图六的警告

说明程序运行依赖的class或jar路径不对或依赖文件不完整,应依据警告信息完善。

(4)       按【OK】后即进入上图一的错误信息显示

(5)       选择【file】储存新建的项目

(6)       选择【file】储存新扫描的Bugs信息(xml文件)

<b>非第一次使用</b>

(1)       选择【file】装载上次新建的项目并运行

(2)       也可以选择【file】装载上次储存的Bugs信息来查看

值得一提的还有在将 FinBugs 作为 Ant 任务或者在命令行中运行 FindBugs 时,选择 <code>xml</code> 作为 <code>ouput</code> 选项,可以将上一次运行的结果装载到 UI 中。这样做是同时利用基于命令行的工具和 UI 工具的优点的一个很好的方法。

如何在 Ant 编译脚本中使用 FindBugs?首先将 FindBugs Ant 任务拷贝到 Ant 的 lib 目录中,这样 Ant 就知道新的任务。将 FIND_BUGS_HOME\lib\FindBugs-ant.jar 拷贝到 ANT_HOME\lib。

现在看看在编译脚本中要加入什么才能使用 FindBugs 任务。因为 FindBugs 是一个自定义任务,将需要使用 <code>taskdef</code>任务以使 Ant 知道装载哪一个类。通过在编译文件中加入以下一行:

<code>&lt;taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/&gt;</code>

在定义了 <code>taskdef</code> 后,可以用它的名字 <code>FindBugs</code> 引用它。下一步要在编译中加入使用新任务的目标,示例如下:

<code>1 &lt;target name="FindBugs" depends="compile"&gt;</code>

<code>2         </code><code>    &lt;FindBugs</code>

<code>home="${FindBugs.home}" output="xml" outputFile="jedit-output.xml"&gt;</code>

<code>3          &lt;class location="c:\apps\JEdit4.1\jedit.jar" /&gt;</code>

<code>4          &lt;auxClasspath path="${basedir}/lib/Regex.jar" /&gt;</code>

<code>5          &lt;sourcePath path="c:\tempcbg\jedit" /&gt;</code>

<code>6      &lt;/FindBugs&gt;</code>

<code>7 &lt;/target&gt;</code>

<b>第 1 行:</b> 注意 <code>target</code> 取决于编译。一定要记住处理的是类文件而 不 是源文件,这样使 <code>target</code> 对应于编译目标保证了FindBugs 可在最新的类文件运行。FindBugs 可以灵活地接受多种输入,包括一组类文件、JAR 文件、或者一组目录。

<b>第 2 行:</b>必须指定包含 FindBugs 的目录,我是用 Ant 的一个属性完成的,像这样:

<code>&lt;property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" /&gt;</code>

可选属性 <code>output</code> 指定 FindBugs 的结果使用的输出格式。可能的值有 <code>xml</code> 、 <code>text</code> 或者 <code>emacs</code> 。如果没有指定 <code>outputFile</code> ,那么 FindBugs 会使用标准输出。如前所述,XML 格式有可以在 UI 中观看的额外好处。

<b>第 3 行:</b> <code>class</code> 元素用于指定要 FindBugs 分析哪些 JAR、类文件或者目录。分析多个 JAR 或者类文件时,要为每一个文件指定一个单独的 <code>class</code> 元素。除非加入了 <code>projectFile</code> 元素,否则需要 <code>class</code> 元素。

<b>第 4 行:</b> 用嵌套元素 <code>auxClasspath</code> 列出应用程序的依赖性。这些是应用程序需要但是不希望 FindBugs 分析的类。如果没有列出应用程序的依赖关系,那么 FindBugs 仍然会尽可能地分析类。与 <code>class</code> 元素一样,可以在 FindBugs 元素中指定多个 <code>auxClasspath</code> 元素。 <code>auxClasspath</code> 元素是可选的。

<b>第 5 行:</b> 如果指定了 <code>sourcePath</code> 元素,那么 <code>path</code> 属性应当表明一个包含应用程序源代码的目录。指定目录使 FindBugs 可以在 GUI 中查看 XML 结果时突出显示出错的源代码。这个元素是可选的。

除FingBugs静态分析工具外,还有PMD和Checkstyle,FingBugs、PMD和Checkstyle三个工具各有不同的特点,联合使用有助于减少误报错误,提高报告的准确率。

这三个工具检查的侧重点各有不同:

工具

目的

主要检查内容

FindBugs

基于Bug Patterns概念,查找java bytecode中的潜在bug。在目前版本中,它不检查java源文件。

主要检查bytecode中的bug patterns,也允许用户自定义特定的bug patterns。

PMD

检查java源文件中的潜在问题。

主要包括:

  -   空try/catch/finally/switch语句块

 -        未使用的局部变量、参数和private方法

 -        空if/while语句

 -        过于复杂的表达式,如不必要的if语句等

 -        复杂类

CheckStyle

检查java源文件是否与代码规范相符

主要包括

 -        Javadoc注释

 -        命名规范

 -        Headers

 -        Imports

 -        Size冲突和度量,如过长的方法

 -        Whitespace

 -        Modifiers

 -        Blocks

 -        Coding Problems

 -        Class Design

 -        重复代码

 -        Miscellaneous Checks

 -        Optional Checks

PMD的运行环境是j2se1.3或以后版本,安装过程同样也是解压即可。

(1)       把lib中所有的jar复制到项目的classpath中。

(2)       将pmd-2.0.jar中的rulesets解压到指定目录,这里面定义了分析所需要的规则集合。

(3)       修改build.xml文件。在这一版本中,提供了2个ant task。一个是pmd使用规则集合进行分析;另一个是检查代码中Copy &amp; Paste代码。这2个任务对应的ant task使用:

<b>PMD</b><b>任务:</b>

    &lt;target name="pmd"&gt;

    &lt;!-- 定义任务和任务所属类所在的classpath引用 --&gt;

        &lt;taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask"

                        classpathref="classpath"/&gt;

     &lt;!-- 检查使用的规则文件 --&gt;

        &lt;pmd rulesetfiles="junit_lib/rulesets/imports.xml"&gt;

         &lt;!-- 输出格式和文件名 --&gt;

            &lt;formatter type="html" toFile="pmd_report.html"/&gt;

         &lt;!-- pmd所需要依赖包的classpath引用 --&gt;

            &lt;classpath refid="classpath"/&gt;

         &lt;!-- 要检查的项目源文件根目录 --&gt;

            &lt;fileset dir="src"&gt;

                &lt;include name="**/*.java"/&gt;

            &lt;/fileset&gt;

        &lt;/pmd&gt;

    &lt;/target&gt;

<b>CPD</b><b>任务:</b>

&lt;target name="cpd"&gt;

     &lt;!-- 定义任务和任务所属类所在的classpath --&gt;

        &lt;taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask"

                            classpathref="classpath"/&gt;

     &lt;!-- 指明输出文件和判断属于copy &amp; paste的标准 --&gt;

        &lt;cpd minimumTokenCount="100" outputFile="cpd.txtl"&gt;

        &lt;/cpd&gt;

(4)       运行ant pmd和ant cpd即可。

(5)       参数说明:

-        formatter,指明输出格式和文件。

-        rulesetfiles,指明分析所需的规则文件,不同文件使用逗号分隔。

-        failonerror,pmd执行出错是否中止构建过程。

-        failOnRuleViolation,如果与规则冲突,是否中止构建过程。

-        classpath,pmd所需的classpath。

-        printToConsole,在发现问题时是否打印到ant log或控制台。

-        shortFilenames,在输出报告中是否使用短文件名。

-        targetjdk13,是否把目标定为jdk13,如不能使用assert。

-        failuresPropertyName,在任务结束时,插入违反规则的号码

-        encoding,读源文件时所采用的编码,如utf-8。

把bin目录下的pmd.bat修改为:

rulesets/controversial.xml,rulesets/coupling.xml,rulesets/design.xml,rulesets/favorites.

xml,rulesets/finalizers.xml,rulesets/unusedcode.xml,rulesets/sunsecure.xml,

rulesets/strings.xml,rulesets/strictexception.xml,rulesets/scratchpad.xml,

rulesets/optimizations.xml,rulesets/naming.xml,rulesets/my-rules.xml,rulesets/logging-java.xml,rulesets/logging-jakarta-commons.xml,rulesets/junit.xml,rulesets/javabeans.xml,rulesets/imports.xml

CheckStyle的运行环境是j2se1.3或以后版本,安装过程同样也是解压即可。

(1)       复制checkstyle-4.0-beta6.jar到项目的classpath中。

(2)       修改build.xml文件:

&lt;taskdef resource="checkstyletask.properties"

       classpath="${weblib.dir}/checkstyle-all-3.3.jar"/&gt;

&lt;target name="checkstyle" depends="init"&gt;

        &lt;!-- 指明checkstyle的分析所需的规则文件 --&gt;

        &lt;checkstyle config="checkstyle33.xml"&gt;

            &lt;!-- 要检查的文件 --&gt;

            &lt;fileset dir="${src.code}" includes="**/*.java"/&gt;

            &lt;!-- 指明输出格式和文件名 --&gt;

            &lt;formatter type="xml" toFile="report.xml"/&gt;

        &lt;/checkstyle&gt;

        &lt;!-- 将xml文件转换成html文件 --&gt;

        &lt;style in="report.xml" out="report.html" style="checkstyle-frames"/&gt;

&lt;/target&gt;

(3)       运行ant checkstyle即可。

checkstyle的规则文件,即项目的代码规范,建议不要手工书写。可以使用checkstyle plug in在Eclipse配置后再导出。Checkstyle提供了缺省的xslt,用来进行xml的格式转换。它们都放在contrib目录中。Checkstyle同样也提供了自定义的check,但与PMD相比,书写要复杂。

志鸿公司目前在HPC项目中使用Java代码缺陷自动分析工具。

该项目以使用FingBugs UI工具为主,PMD为辅助工具。自动分析输出的结果经过验证确认后,作为缺陷管理的组成部分之一。

经过一个多月来的运作,证实自动分析工具对改进代码的质量起到很大的作用。

<a></a>

本文转自 fish_yy 51CTO博客,原文链接:http://blog.51cto.com/tester2test/137384,如需转载请自行联系原作者