日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

FindBugs,第 1 部分: 提高代碼質(zhì)量

 昵稱24706 2007-04-20

Chris Grindstaff (chris@), 軟件工程師

2004 年 5 月 25 日

靜態(tài)分析工具承諾無需開發(fā)人員費勁就能找出代碼中已有的缺陷。當(dāng)然,如果有多年的編寫經(jīng)驗,就會知道這些承諾并不是一定能兌現(xiàn)。盡管如此,好的靜態(tài)分析工具仍然是工具箱中的無價之寶。在這個由兩部分組成的系列文章的第一部分中,高級軟件工程師 Chris Grindstaff 分析了 FindBugs 如何幫助提高代碼質(zhì)量以及排除隱含的缺陷。

代碼質(zhì)量工具的一個問題是它們?nèi)菀诪殚_發(fā)人員提供大量但并非真正問題的問題——即 偽問題(false positives)。出現(xiàn)偽問題時,開發(fā)人員要學(xué)會忽略工具的輸出或者放棄它。FindBugs 的設(shè)計者 David Hovemeyer 和 William Pugh 注意到了這個問題,并努力減少他們所報告的偽問題數(shù)量。與其他靜態(tài)分析工具不同,F(xiàn)indBugs 不注重樣式或者格式,它試圖只尋找真正的缺陷或者潛在的性能問題。

FindBugs 是什么?

FindBugs 是一個靜態(tài)分析工具,它檢查類或者 JAR 文件,將字節(jié)碼與一組缺陷模式進行對比以發(fā)現(xiàn)可能的問題。有了靜態(tài)分析工具,就可以在不實際運行程序的情況對軟件進行分析。不是通過分析類文件的形式或結(jié)構(gòu)來確定程序的意圖,而是通常使用 Visitor 模式(請參閱 參考資料)。圖 1 顯示了分析一個匿名項目的結(jié)果(為防止可怕的犯罪,這里不給出它的名字):



圖 1. FindBugs UI
Configure Detector 的圖形表示

讓我們看幾個 FindBugs 可以發(fā)現(xiàn)的問題。

本系列的第二篇文章“ 編寫自定義檢測器”解釋了如何編寫自定義檢測器, 以便發(fā)現(xiàn)特定于應(yīng)用程序的問題。







問題發(fā)現(xiàn)的例子

下面的列表沒有包括 FindBug 可以找到的 所有問題。相反,我側(cè)重于一些更有意思的問題。

檢測器:找出 hash equals 不匹配
這個檢測器尋找與 equals()hashCode() 的實現(xiàn)相關(guān)的幾個問題。這兩個方法非常重要,因為幾乎所有基于集合的類—— List、Map、Set 等都調(diào)用它們。一般來說,這個檢測器尋找兩種不同類型的問題——當(dāng)一個類:

  • 重寫對象的 equals() 方法,但是沒有重寫它的 hashCode 方法,或者相反的情況時。

  • 定義一個 co-variant 版本的 equals()compareTo() 方法。例如, Bob 類定義其 equals() 方法為布爾 equals(Bob) ,它覆蓋了對象中定義的 equals() 方法。因為 Java 代碼在編譯時解析重載方法的方式,在運行時使用的幾乎總是在對象中定義的這個版本的方法,而不是在 Bob 中定義的那一個(除非顯式將 equals() 方法的參數(shù)強制轉(zhuǎn)換為 Bob 類型)。因此,當(dāng)這個類的一個實例放入到類集合中的任何一個中時,使用的是 Object.equals() 版本的方法,而不是在 Bob 中定義的版本。在這種情況下, Bob 類應(yīng)當(dāng)定義一個接受類型為 Object 的參數(shù)的 equals() 方法。

檢測器:忽略方法返回值
這個檢測器查找代碼中忽略了不應(yīng)該忽略的方法返回值的地方。這種情況的一個常見例子是在調(diào)用 String 方法時,如在清單 1 中:



清單 1. 忽略返回值的例子
1  String aString = "bob";
                        2  b.replace(‘b‘, ‘p‘);
                        3  if(b.equals("pop"))
                        

這個錯誤很常見。在第 2 行,程序員認(rèn)為他已經(jīng)用 p 替換了字符串中的所有 b。確實是這樣,但是他忘記了字符串是不可變的。所有這類方法都返回一個新字符串,而從來不會改變消息的接收者。

檢測器:Null 指針對 null 的解引用(dereference)和冗余比較
這個檢測器查找兩類問題。它查找代碼路徑將會或者可能造成 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 的名字時就會出現(xiàn) null 指針異常。因為 FindBugs 不知道 map 是否包含“bob”,所以它將第 5 行標(biāo)記為可能 null 指針異常。

檢測器:初始化之前讀取字段
這個檢測器尋找在構(gòu)造函數(shù)中初始化之前被讀取的字段。這個錯誤通常是——盡管不總是如此——由使用字段名而不是構(gòu)造函數(shù)參數(shù)引起的,如清單 3 所示:



清單 3. 在構(gòu)造函數(shù)中讀取未初始化的字段
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 行將產(chǎn)生一個 null 指針異常,因為變量 actions 還沒有初始化。

這些例子只是 FindBugs 所發(fā)現(xiàn)的問題種類的一小部分(更多信息請參閱 參考資料)。在撰寫本文時,F(xiàn)indBugs 提供總共 35 個檢測器。







開始使用 FindBugs

要運行 FindBugs,需要一個版本 1.4 或者更高的 Java Development Kit (JDK),盡管它可以分析由老的 JDK 創(chuàng)建的類文件。要做的第一件事是下載并安裝最新發(fā)布的 FindBugs——當(dāng)前是 0.7.1 (請參閱 參考資料)。幸運的是,下載和安全是相當(dāng)簡單的。在下載了 zip 或者 tar 文件后,將它解壓縮到所選的目錄中。就是這樣了——安裝就完成了。

安裝完后,對一個示例類運行它。就像一般文章中的情況,我將針對 Windows 用戶進行講解,并假定那些 Unix 信仰者可以熟練地轉(zhuǎn)化這些內(nèi)容并跟進。打開命令行提示符號并進入 FindBugs 的安裝目錄。對我來說,這是 C:\apps\FindBugs-0.7.3。

在 FindBugs 主目錄中,有幾個值得注意的目錄。文檔在 doc 目錄中,但是對我們來說更重要的是,bin 目錄包含了運行 FindBugs 的批處理文件,這使我們進入下一部分。






運行 FindBugs

像如今的大多數(shù)數(shù)工具一樣,可以以多種方式運行 FindBugs——從 GUI、從命令行、使用 Ant、作為 Eclipse 插件程序和使用 Maven。我將簡要提及從 GUI 運行 FindBugs,但是重點放在用 Ant 和命令行運行它。部分原因是由于 GUI 沒有提供命令行的所有選項。例如,當(dāng)前不能指定要加入的過濾器或者在 UI 中排除特定的類。但是更重要的原因是我認(rèn)為 FindBugs 最好作為編譯的集成部分使用,而 UI 不屬于自動編譯。

使用 FindBugs UI

使用 FindBugs UI 很直觀,但是有幾點值得說明。如 圖 1所示,使用 FindBugs UI 的一個好處是對每一個檢測到的問題提供了說明。圖 1 顯示了缺陷 Naked notify in method的說明。對每一種缺陷模式提供了類似的說明,在第一次熟悉這種工具時這是很有用的。窗口下面的 Source code 選項卡也同樣有用。如果告訴 FindBugs 在什么地方尋找代碼,它就會在轉(zhuǎn)換到相應(yīng)的選項卡時突出顯示有問題的那一行。

值得一提的還有在將 FinBugs 作為 Ant 任務(wù)或者在命令行中運行 FindBugs 時,選擇 xml 作為 ouput 選項,可以將上一次運行的結(jié)果裝載到 UI 中。這樣做是同時利用基于命令行的工具和 UI 工具的優(yōu)點的一個很好的方法。

將 FindBugs 作為 Ant 任務(wù)運行

讓我們看一下如何在 Ant 編譯腳本中使用 FindBugs。首先將 FindBugs Ant 任務(wù)拷貝到 Ant 的 lib 目錄中,這樣 Ant 就知道新的任務(wù)。將 FIND_BUGS_HOME\lib\FindBugs-ant.jar 拷貝到 ANT_HOME\lib。

現(xiàn)在看看在編譯腳本中要加入什么才能使用 FindBugs 任務(wù)。因為 FindBugs 是一個自定義任務(wù),將需要使用 taskdef 任務(wù)以使 Ant 知道裝載哪一個類。通過在編譯文件中加入以下一行做到這一點:

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

在定義了 taskdef 后,可以用它的名字 FindBugs 引用它。下一步要在編譯中加入使用新任務(wù)的目標(biāo),如清單 4 所示:



清單 4. 創(chuàng)建 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>
                        

讓我們更詳細(xì)地分析這段代碼中所發(fā)生的過程。

第 1 行: 注意 target 取決于編譯。一定要記住處理的是類文件而 是源文件,這樣使 target 對應(yīng)于編譯目標(biāo)保證了 FindBugs 可在最新的類文件運行。FindBugs 可以靈活地接受多種輸入,包括一組類文件、JAR 文件、或者一組目錄。

第 2 行:必須指定包含 FindBugs 的目錄,我是用 Ant 的一個屬性完成的,像這樣:

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

可選屬性 output 指定 FindBugs 的結(jié)果使用的輸出格式。可能的值有 xml 、 text 或者 emacs 。如果沒有指定 outputFile ,那么 FindBugs 會使用標(biāo)準(zhǔn)輸出。如前所述,XML 格式有可以在 UI 中觀看的額外好處。

第 3 行: class 元素用于指定要 FindBugs 分析哪些 JAR、類文件或者目錄。分析多個 JAR 或者類文件時,要為每一個文件指定一個單獨的 class 元素。除非加入了 projectFile 元素,否則需要 class 元素。更多細(xì)節(jié)請參閱 FindBugs 手冊。

第 4 行: 用嵌套元素 auxClasspath 列出應(yīng)用程序的依賴性。這些是應(yīng)用程序需要但是不希望 FindBugs 分析的類。如果沒有列出應(yīng)用程序的依賴關(guān)系,那么 FindBugs 仍然會盡可能地分析類,但是在找不到一個缺少的類時,它會抱怨。與 class 元素一樣,可以在 FindBugs 元素中指定多個 auxClasspath 元素。 auxClasspath 元素是可選的。

第 5 行: 如果指定了 sourcePath 元素,那么 path 屬性應(yīng)當(dāng)表明一個包含應(yīng)用程序源代碼的目錄。指定目錄使 FindBugs 可以在 GUI 中查看 XML 結(jié)果時突出顯示出錯的源代碼。這個元素是可選的。

上面就是基本內(nèi)容了。讓我們提前幾個星期。

過濾器

您已經(jīng)將 FindBugs 引入到了團隊中,并運行它作為您的每小時/每晚編譯過程的一部分。當(dāng)團隊越來越熟悉這個工具時,出于某些原因,您決定所檢測到的一些缺陷對于團隊來說不重要。也許您不關(guān)心一些類是否返回可能被惡意修改的對象——也許,像 JEdit,有一個真正需要的(honest-to-goodness)、合法的理由調(diào)用 System.gc() 。

總是可以選擇“關(guān)閉”特定的檢測器。在更細(xì)化的水平上,可以在指定的一組類甚至是方法中查找問題時,排除某些檢測器。FindBugs 提供了這種細(xì)化的控制,可以排除或者包含過濾器。當(dāng)前只有用命令行或者 Ant 啟動的 FindBugs 中支持排除和包含過濾器。正如其名字所表明的,使用排除過濾器來排除對某些缺陷的報告。較為少見但仍然有用的是,包含過濾器只能用于報告指定的缺陷。過濾器是在一個 XML 文件中定義的??梢栽诿钚兄杏靡粋€排除或者包含開關(guān)、或者在 Ant 編譯文件中用 excludeFilterincludeFilter 指定它們。在下面的例子中,假定使用排除開關(guān)。還要注意在下面的討論中,我對 “bugcode”、“bug” 和“detector”的使用具有某種程度的互換性。

可以有不同的方式定義過濾器:

  • 匹配一個類的過濾器??梢杂眠@些過濾器 忽略在特定類中發(fā)現(xiàn)的所有問題。

  • 匹配一個類中特定缺陷代碼(bugcode)的 過濾器??梢杂眠@些過濾器忽略在特定類中發(fā)現(xiàn)的一些缺陷。

  • 匹配一組缺陷的過濾器??梢杂眠@些過濾器 忽略所分析的所有類中的一組缺陷。

  • 匹配所分析的一個類中的某些方法的過濾器??梢杂眠@些過濾器忽略在一個類中的一組方法中發(fā)現(xiàn)的所有缺陷。

  • 匹配在所分析的一個類中的方法中發(fā)現(xiàn)的某些缺陷的過濾器。可以用這些過濾器忽略在一組方法中發(fā)現(xiàn)的特定缺陷。

知道了這些就可以開始使用了。有關(guān)其他定制 FindBugs 方法的更多信息,請參閱 FindBugs 文檔。知道如何設(shè)置編譯文件以后,就讓我們更詳細(xì)地分析如何將 FindBugs 集成到編譯過程中吧!







將 FindBugs 集成到編譯過程中

在將 FindBugs 集成到編譯過程當(dāng)中可以有幾種選擇??偸强梢栽诿钚袌?zhí)行 FindBugs,但是您很可能已經(jīng)使用 Ant 進行編譯,所以最自然的方法是使用 FindBugs Ant 任務(wù)。因為我們在 如何運行 FindBugs一節(jié)中討論了使用 FindBugs Ant 任務(wù)的基本內(nèi)容,所以現(xiàn)在討論應(yīng)當(dāng)將 FindBugs 加入到編譯過程中的幾個理由,并討論幾個可能遇到的問題。

為什么應(yīng)該將 FindBugs 集成到編譯過程中?

經(jīng)常問到的第一個問題是為什么要將 FindBugs 加入到編譯過程中?雖然有大量理由,最明顯的回答是要保證盡可能早地在進行編譯時發(fā)現(xiàn)問題。當(dāng)團隊擴大,并且不可避免地在項目中加入更多新開發(fā)人員時,F(xiàn)indBugs 可以作為一個安全網(wǎng),檢測出已經(jīng)識別的缺陷模式。我想重申在一篇 FindBugs 論文中表述的一些觀點。如果讓一定數(shù)量的開發(fā)人員共同工作,那么在代碼中就會出現(xiàn)缺陷。像 FindBugs 這樣的工具當(dāng)然不會找出所有的缺陷,但是它們會幫助找出其中的部分?,F(xiàn)在找出部分比客戶在以后找到它們要好——特別是當(dāng)將 FindBugs 結(jié)合到編譯過程中的成本是如此低時。

一旦確定了加入哪些過濾器和類,運行 FindBugs 就沒什么成本了,而帶來的好處就是它會檢測出新缺陷。如果編寫特定于應(yīng)用程序的檢測器,則這個好處可能更大。

生成有意義的結(jié)果

重要的是要認(rèn)識到這種成本/效益分析只有在不生成大量誤檢時才有效。換句話說,如果在每次編譯時,不能簡單地確定是否引入了新的缺陷,那么這個工具的價值就會被抵消。分析越自動化越好。如果修復(fù)缺陷意味著必須吃力地分析檢測出的大量不相干的缺陷,那么您就不會經(jīng)常使用它,或者至少不會很好地使用它。

確定不關(guān)心哪些問題并從編譯中排除它們。也可以挑出 確實關(guān)注的一小部分檢測器并只運行它們。另一種選擇是從個別的類中排除一組檢測器,但是其他的類不排除。FindBugs 提供了使用過濾器的極大靈活性,這可幫助生成對團隊有意義的結(jié)果,由此我們進入下一節(jié)。

確定用 FindBugs 的結(jié)果做什么

可能看來很顯然,但是您想不到我參與的團隊中有多少加入了類似 FindBugs 這樣的工具而沒有真正利用它。讓我們更深入地探討這個問題——用結(jié)果做什么?明確回答這個問題是困難的,因為這與團隊的組織方式、如何處理代碼所有權(quán)問題等有很大關(guān)系。不過,下面是一些指導(dǎo):

  • 可以考慮將 FindBugs 結(jié)果加入到源代碼管理(SCM)系統(tǒng)中。一般的經(jīng)驗做法是不將編譯工件(artifact)放到 SCM 系統(tǒng)中。不過,在這種特定情況下,打破這個規(guī)則可能是正確的,因為它使您可以監(jiān)視代碼質(zhì)量隨時間的變化。

  • 可以選擇將 XML 結(jié)果轉(zhuǎn)換為可以發(fā)送到團隊的網(wǎng)站上的 HTML 報告。轉(zhuǎn)換可以用 XSL 樣式表或者腳本實現(xiàn)。有關(guān)例子請查看 FindBugs 網(wǎng)站或者郵件列表(請參閱 參考資料)。

  • 像 FindBugs 這樣的工具通常會成為用于敲打團隊或者個人的政治武器。盡量抵制這種做法或者不讓它發(fā)生——記住,它只是一個工具,它可以幫助改進代碼的質(zhì)量。有了這種思想,在下一部分中,我將展示如何編寫自定義缺陷檢測器。






結(jié)束語

我鼓勵讀者對自己的代碼試用靜態(tài)分析工具,不管是 FindBugs、PMD 還是其他的。它們是有用的工具,可以找出真正的問題,而 FindBugs 是在消除誤檢方面做得最好的工具。此外,它的可插入結(jié)構(gòu)提供了編寫有價值的、特定于應(yīng)用程序的檢測器的、有意思的測試框架。在本系列的 第 2 部分中,我將展示如何編寫自定義檢測器以找出特定于應(yīng)用程序的問題。



參考資料



關(guān)于作者

 

Chris Grindstaff 是在北加利福尼亞 Research Triangle Park 工作的 IBM 高級軟件工程師。Chris 在 7 歲時編寫了他的第一個程序,當(dāng)時他讓小學(xué)老師認(rèn)識到“鍵入”句子與手寫它們一樣費力。Chris 目前參與了不同的開放源代碼項目。他大量使用 Eclipse 并編寫了幾個流行的 Eclipse 插件程序,可以在他的 網(wǎng)站找到這些插件程序。可以通過 cgrinds@us.ibm.com或者 chris@與 Chrise 聯(lián)系。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多