当前位置: 首页 > 工具软件 > FindBugs > 使用案例 >

FindBugs 是什么?

龚浩宕
2023-12-01

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

1         FindBugs 是什么?

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

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

有了静态分析工具,就可以在不实际运行程序的情况对软件进行分析。FindBugs 不是通过分析类文件的形式或结构来确定程序的意图,而是通常使用 Visitor 模式进行分析(Visitor 模式的更多信息 )。

 

2         FindBugs 可以做什么?

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

2.1       找出 hash equals 不匹配

找与 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() 方法。

 

20090325(周三)补充:

关于equals() 方法 和 hashCode()方法,这两个方法一般是连体儿。某个类继承自Object类,假如重写这两个方法的其中一个时,“ 必须 ”重写另一个方法 (一般情况下是重写 equals() 方法,此时就要注意重写 hashCode()方法了

 

2.2       检测:忽略方法返回值

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

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

 

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

 

20090325(周三)补充:

String replace (char oldChar,  char newChar) 
          Returns a new string resulting from replacing all occurrences of oldChar in this string with newChar .

注意:执行到第三句的时候,此时有两个字符串,一个是aString,此时的值还是“bob”;还有一个,就是replace方法返回的新字符串“pop”。

 

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

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

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

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

 

20090325(周三)补充:

空指针异常,绝大多数情况都是null.***,想对为null的对象进行某种操作,必然会导致空指针异常。这种异常应该是Java编码中出现最频繁的异常了,应该引起足够的重视(在代码中多进行非空check)。

 

2.4       检测:初始化之前读取字段

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

Java代码 
  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. }   
 

 

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

 

20090325(周三)补充:

这个问题,应该不存在吧,Eclipse中出现这个情况会自动报错的,编译通不过,应该不需要劳驾FindBugs出马吧。

 

 

2.5       命名检查

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

 

2.6       未使用的代码检查

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

 

20090325(周三)补充:

对多余代码的检查。私有字段或者私有方法,别人不能直接使用,假如在本类中都没有使用到,那还放在这边干嘛呢。(其实,私有字段和私有方法的出发点,也是只给本类使用的。)

 

2.7       嵌套检查

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

 

20090325(周三)补充:

1.switch 语句的各个case中,应该不能没有break语句,否则会发生case穿透现象;

2.至于default块,肯定也是要有的,这个是兜底保本的。

3.应当避免深度嵌套的 if 块:深度太深,程序的逻辑结构就看得不太清楚了,此时可以考虑重新组织逻辑,或者拆分成多个方法。

4.不应当给参数重新赋值:这个当然了,你既然使用了接受参数,在方法内又把人家传过来的值给重新赋值了,这叫什么事呀(要不就不要传这个参数进来,正所谓:用人不疑,疑人不用)。

5.不应该对 double 值进行相等比较:嗯,这个是要注意,double不像整数,它是不精确的(小数部分是由0101模拟的),所以无法直接对两个double数进行等值比较,一般都是比较两者的差是否小于某个值(比如0.0001,就相当于是精确到小数点后面第4位),若成立,则可以认为两者是”相等“的。

 

2.8       导入语句检查

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

 

20090325(周三)补充:

同一个类导入一次就知道了,何必画蛇添足,导入第二遍呢,吃力不讨好。

导入 java.lang:大哥,这包不导入就能直接使用的,你又何必呢。

 

2.9       JUnit 测试检查

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

 

2.10   字符串检查

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

 

20090325(周三)补充:

重复的字符串标量:此处的字符串标量,应该说的是字符串常量吧,这个字符串常量啊,是存放在内存的Data Segment(数据区)中的,一般一份就够了,没有必要进行重复定义。

对 String 变量调用 toString() 方法:对 String 变量进行打印输出时,默认调用的就是 toString() 方法,返回的就是它自身,何必要显示地写出来呢。

调用 String 构造函数???我一下子没看出这个有何不妥之处 ???

 

2.11   括号检查

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

 

20090325(周三)补充:

这个问题也是要注意一下,开始时只有一句语句时,从执行效果上来讲,加不加这个括号都是没有问题的。

但是不加括号是个很不好的习惯,有隐藏的问题:将来追加代码,直接加在原先一句的后面,目的是想将代码加在语句块内,但是因为前面没有加括号,假如此时也忘记加括号,这样的话,代码的逻辑显然就错了。

所以啊,哪怕此时代码段中的语句只有一句,也要加上括号,避免隐患。

 

2.12   代码尺寸检查

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

 

2.13   终结函数检查

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

 

2.14   克隆检查

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

 

20090325(周三)补充:

protected  Object clone () 
          Creates and returns a copy of this object.

protected Object clone () throws CloneNotSupportedException

 

2.15   耦合检查

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

 

2.16   异常检查

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

 

20090325(周三)补充:

不应该捕获 Throwable:这家伙是可逮可不逮的异常,你逮它干嘛。

 

2.17   日志检查

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

 

2.18   Open—Close 检查

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

 

20090325(周三)补充:

说的意思主要是:资源(文件资源、连接资源)用也用了,但是最后记得帮人家把资源给关了,有始有终嘛。

 

2.19   其它检查

其它缺陷清单可参见:缺陷清单

 

2.20     构建自己的规则集

可以构建自己的规则集

 

 

说明:更多关于使用和其他工具分析的部分请参考:

http://www.cnblogs.com/tester2test/archive/2006/06/08/420832.html

 类似资料: