关于静态代码分析
找出代码中已有的缺陷,提高代码质量,排除潜在隐患是每个程序员的责任,不管是入门者还是资深者都被这个问题困扰着。
实际开发中会有特别多的伪问题存在,伪问题就是一些不是问题的问题,也可理解成误报。 本来不是问题的地方被当作成问题,无疑会耽误大量时间去发现和排除。由此可见所以静态代码分析尤为重要。
这里推荐一款我正在使用的工具: Findbugs
Find bugs - This is the web page for FindBugs, a program which uses static analysis to look for bugs in Java code.
http://findbugs.sourceforge.net/
它的特点是不重视格式和样式。
网上有些资料可供参考:
下面的列表没有包括
FindBug
可以找到的
所有
问题。相反,我侧重于一些更有意思的问题。
检测器:找出
hash equals
不匹配
这个检测器寻找与 equals() 和 hashCode() 的实现相关的几个问题。这两个方法非常重要,因为几乎所有基于集合的类 —— List 、 Map 、 Set 等都调用它们。一般来说,这个检测器寻找两种不同类型的问题 —— 当一个类:
这个检测器寻找与 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 中:
这个检测器查找代码中忽略了不应该忽略的方法返回值的地方。这种情况的一个常见例子是在调用 String 方法时,如在清单 1 中:
1.
忽略返回值的例子
1 String aString = "bob";
2 b.replace('b', 'p');
3 if(b.equals("pop"))
|
这个错误很常见。在第
2
行,程序员认为他已经用
p
替换了字符串中的所有
b
。确实是这样,但是他忘记了字符串是不可变的。所有这类方法都返回一个新字符串,而从来不会改变消息的接收者。
检测器:
Null
指针对
null
的解引用(
dereference
)和冗余比较
这个检测器查找两类问题。它查找代码路径将会或者可能造成 null 指针异常的情况,它还查找对 null 的冗余比较的情况。例如,如果两个比较值都为 null ,那么它们就是冗余的并可能表明代码错误。 FindBugs 在可以确定一个值为 null 而另一个值不为 null 时,检测类似的错误,如清单 2 所示:
这个检测器查找两类问题。它查找代码路径将会或者可能造成 null 指针异常的情况,它还查找对 null 的冗余比较的情况。例如,如果两个比较值都为 null ,那么它们就是冗余的并可能表明代码错误。 FindBugs 在可以确定一个值为 null 而另一个值不为 null 时,检测类似的错误,如清单 2 所示:
2. Null
指针示例
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
指针异常。
检测器:初始化之前读取字段
这个检测器寻找在构造函数中初始化之前被读取的字段。这个错误通常是 —— 尽管不总是如此 —— 由使用字段名而不是构造函数参数引起的,如清单 3 所示:
这个检测器寻找在构造函数中初始化之前被读取的字段。这个错误通常是 —— 尽管不总是如此 —— 由使用字段名而不是构造函数参数引起的,如清单 3 所示:
3. 在构造函数中读取未初始化的字段
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
还没有初始化。
FindBugs 暂时
提供总共
35
个检测器。