使用FindBugs改善你的代碼質量—爲什麼使用以及怎樣使用

使用FindBugs改善你的代碼質量—爲什麼使用以及怎樣使用

原文(作者):
FindBugs, Part 1: Improve the quality of your code
Chris Grindstaff ([email protected]), Software Engineer, IBM
http://www.ibm.com/developerworks/java/library/j-findbug1/

靜態代碼分析工具承諾不需要開發者太多努力就可以找出程序中存在的Bugs,當然如果你已經有過一段時間的編程經歷,你就會知道這樣的諾言也許並不總會成功兌現。但是,即便靜態代碼分析工具在代碼檢查上達不到完美的程度,一個好的檢測工具還是值得添加到你的編程工具箱中。高級軟件工程師 Chris Grindstaff用了兩部分介紹FindBugs,這是第一部分,主要介紹了FindBugs怎樣改善你的代碼質量及評價隱蔽Bugs,當你完成閱讀這部分內容後,不要錯過Chris的第二部分內容。
軟件代碼質量工具存在一個問題就是這些工具所檢測出的缺陷經常令程序員不知所措,因爲這些被檢測出的缺陷實際上不是Bugs,也就是說誤報(False positives)。當存在誤報時,開發者往往需要學習忽略檢測工具的輸出或者乾脆放棄它。FindBugs的發起者David Hovemeyer and William Pugh認爲檢測工具應該儘可能減少誤報的數量。和其他靜態檢測工具不同的是FindBugs不關注代碼樣式,而是試着查找實際Bugs或者潛在地性能問題。

1 FindBugs是什麼?

FindBugs是一個靜態代碼檢測工具,它檢測Java類文件或者JAR文件,通過比較文件中的字節碼與一系列缺陷模式來查找潛在問題。利用靜態檢測工具,你可以在不實際運行程序的情況下分析軟件缺陷。FindBugs使用了訪問者模式而不是分析類文件的構成和結構來決定程序目的。
圖1 FindBugs分析一個匿名程序的結果
圖1 FindBugs分析一個匿名程序的結果

2 FindBugs能夠檢測的問題

注意下面只是一些比較有趣的問題舉例,並沒有囊括FindBugs能夠檢測的所有問題。
-2.1 檢測器:查找Hash Equals方法不匹配問題
這個檢測器可以檢測幾方面的問題,主要與equals()和hashCode()方法的實現有關。由於幾乎所有的集合基類List/Maps/Sets等都會調用這兩個方法。通常這個檢測器檢測一個Java類中存在的以下兩種不同類型的問題:

  • 一個類重寫了equals()和hashCode()方法中的一個,卻沒有重寫另一個。
  • 定義了equals()或者compareTo()方法的協變版本,例如我們爲Bob類定義了equals()方法如下boolean equals(Bob b),使用這個方法覆蓋Object(所有Java類默認基類)中定義的equals()方法。但由於Java虛擬機在運行時決定具體執行哪個覆蓋的方法時默認地將總會執行Object中定義的equals(Object o)方法,而不是我們定義的equals(Bob b)(除非你明確將傳遞給方法的參數類型強制轉換爲Bob)。這樣處理的缺陷在於當我們把這個類的一個對象放進前面所說的集合基類中時,當集合類比較對象是否相等時默認的調用了Object中定義的equasl方法,而不是我們想要覆蓋的equals.

2.2檢測器:查找類方法的返回值被忽略問題
這個檢測器會檢查程序中一些類方法應該被使用的返回值卻被程序員忽略的代碼,最常見的場景出現在調用String的一些方法時,例如:

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

這是一個很常見的錯誤,在第2行程序員認爲他已經把字符串中的所有b替換成了p,但是Java中字符串常量是不可變的,所有這類函數的都會返回一個新的字符串,而絕不是簡單的改變原有字符串。

2.3 檢測器:查找空指針引用及對Null多餘比較問題
這個檢測器查找兩類問題,一是代碼路徑中將會或可能會引起空指針引用異常;另一個是程序中對不可能是null或一定是null的變量進行多餘驗證是否爲null。例如,如果程序中存在代碼比較兩個都是null的變量,要麼這是多餘的,要麼意味着程序員寫錯程序(因爲正常人不這麼寫程序)。同理,如果比較null和一定不是null的兩個對象是一樣的情況。

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

這個例子中,如果第1行的aMap中不包含bob這個人,當第5行調用person.getName()方法時會產生空指針引用異常。因爲FindBugs不知道aMap是否包含bob,它只會標記這裏可能有異常。

2.4檢測器:檢測類成員變量在初始化之前被讀取值的問題
查找類成員變量在構造器中初始化之前被讀取值,這個錯誤經常是由於在構造函數中將成員變量名寫成了參數變量名所造成的(雖然這樣的情況很少發生),下面的例子更好理解:

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.}

這幾個例子僅僅是一小部分FindBugs能夠檢測的問題,可以查看更多

3 使用FindBugs

爲了運行FindBugs至少需要JDK1.4或更高的版本,不過FindBugs能夠分析被更早的版本創建的類文件,開始使用它所要做的第一件事就是下載並安裝最新發布的FindBugs——當前爲0.7.1.值得慶幸地是下載和安裝FindBugs是非常簡單的,只需要下載壓縮文件並解壓到指定的文件夾下,安裝過程就完成了。(這篇文章已經舊的不能再舊了)
完成安裝以後,我們可以在一個簡單的類上運行它,和以前一樣,我將僅討論Windows用戶的使用並假設Unix用戶能夠輕鬆地理解和跟上我想說的內容,打開命令行提示符並定位到findbugs的安裝目錄。在findbugs的根目錄下,有一些我們感興趣的文件夾,documentation被放在doc文件夾中,但我們更感興趣的是bin文件夾,它包含了可以運行FindBugs的批處理文件,下面我會講到。

4 運行FindBugs

Findbugs能夠在多種方式被運行,包括命令行、圖形界面、Ant、Eclipse插件或者Maven,我簡單提到了通過圖形界面運行FindBugs,然後主要關注使用Ant和命令行運行它。一定程度上由於圖形界面的功能沒有完全實現命令行的某些選項。例如,現在的圖形界面版本中你不能夠爲指定過濾性包含某些類或排除某些類。但是,更重要的原因是因爲我認爲我們最好使用FindBugs集成到我們的編譯過程中,而圖形界面並不是很方面使用。
4.1 使用FindBugs圖形界面
FindBugs圖形界面的使用非常簡單,但有幾點需要注意。如圖所示,使用圖形界面的好處之一就是它提供了每種類型的檢測器的描述,前面的圖1中顯示了錯誤Naked notify in method 的描述,同時,它也爲每個缺陷模式提供了相似的描述,當你剛剛接觸這個工具時這是非常有用的,圖形界面的下端有一個源碼窗口也是非常有用的,當你爲findBugs指明源碼位置,當你切換到這個tab窗口時,它會突出顯示那行有問題的代碼。
當從命令行或者Ant方式運行findBugs時,如果你選擇使用xml作爲輸出選項是非常重要的,你可以使用UI加載前一次運行的結果,這樣做可以同時利用圖形界面和命令行工具的優勢,是一種很好的方式。

5 運行FindBugs作爲Ant 任務
讓我看看如何從Ant編譯腳本(build script)運行findBugs。首先,把FindBugs Ant任務拷貝到Ant庫文件夾使其檢測到新的任務。也就是說拷貝FIND_BUGS_HOME\lib\FindBugs-ant.jar到ANT_HOME\lib。
現在你需要增加哪些配置到你的編譯腳本中。由於findBugs屬於客戶化任務,你需要使用taskdef任務標籤告訴Ant哪個類被加載,如下所示:

<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/>

在定義之後,你就能夠通過名字來使用它,這裏就是FindBugs,下一步你需要增加一個target標籤到你想使用這個任務的編譯腳本,如下所示:
Listing 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>

讓我們更加更進一步看看到底這段代碼有什麼作用。
第1行:注意到target依賴於compile,findBugs利用類文件而不是源文件執行檢測,所以使target依賴於compile來確保FindBugs將會運行在最新編譯類文件上。它可以就接受一系列類文件,Jar文件以及文件夾作爲輸入。
第2行:你必須指明FindBugs的文件路徑,這裏我使用了Ant屬性定義了FindBugs.home

<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" />

Output輸出屬性指明瞭FindBugs結果輸出格式,可選的格式有xml,text,emacs.如果不指定特殊的輸出格式,其將結果打印到標準輸出流。正如前面所說,使用XML格式可以通過圖形界面來查看結果。
第3行:類class用來指明你想分析的文件位置,包括類文件,jar或者文件夾,如果想分析多個類文件或者Jar文件,需要爲每個文件指明一個class標籤,class標籤是必須的除非projectFile標籤被使用。可以查看FindBugs手冊查看更多。
第4行:通過使用auxClasspath標籤列出應用依賴庫,這是一些你的應用需要的類文件,但是你不想讓FindBugs分析,如果你不列出你不想分析的文件,FindBugs將會儘可能的分析所有的類文件。但是當它不能夠找到某一個丟失的類文件時就是都出警示或提示。和class標籤一樣,可以使用多個或者不使用。
第5行:如果你的sourcePath標籤被指明,path屬性應該包含你應用程序的源碼,指明這個路徑後,當時用findBugs查看XML格式的錯誤結果時,FindBugs將會在源碼中突出顯示錯誤行。sourcePath是可選的。


未完待續

發佈了34 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章