FindBugs Java程式碼審查工具
阿新 • • 發佈:2019-01-03
簡介編輯
儘管如此,好的靜態分析工具仍然是工具箱中的無價之寶。在這個由兩部分組成的系列文章的第一部分中,高階軟體工程師 Chris Grindstaff 分析了 FindBugs 如何幫助提高程式碼質量以及排除隱含的缺陷。程式碼質量工具的一個問題是它們容易為開發人員提供大量但並非真正問題的問題——即 偽問題(false positives)。出現偽問題時,開發人員要學會忽略工具的輸出或者放棄它。FindBugs 的設計者 David Hovemeyer 和 William Pugh 注意到了這個問題,並努力減少他們所報告的偽問題數量。與其他靜態分析工具不同,FindBugs 不注重樣式或者格式,它試圖只尋找真正的缺陷或者潛在的效能問題。 FindBugs 是什麼?2開發階段編輯
當Developer完成了某一部分功能模組開發的時候(這通常是指程式碼撰寫完成,並已debug通過之後),可藉由FindBugs對該模組涉及的java檔案進行一次掃描,以發現一些不易察覺的bug或是效能問題。交付新版的時候,開發團隊可以跑一下FindBugs,除掉一些隱藏的Bug。FindBugs得出的報告可以作為該版本的一個參考文件一併交付給測試團隊留檔待查。 在開發階段使用FindBugs,一方面開發人員可以對新版的品質更有信心,另一方面,測試人員藉此可以把更多的精力放在業務邏輯的確認上面,而不是花大量精力去進一些要在特殊狀況下才可能出現的BUG(典型的如Null Pointer Dereference)。從而可以提高測試的效率。
3 維護階段編輯
這裡指的是系統已經上線,卻發現因為程式碼中的某一個bug導致系統崩潰。在除掉這個已暴露的bug之後,為了快速的找出類似的但還未暴露的 bug,可以使用FindBugs對該版的程式碼進行掃描。當然,在維護階段使用FindBugs往往是無奈之舉,且時間緊迫。此外,如果本來在新版交付的時候就使用過FindBugs的話,往往意味著這種bug是FindBugs還無法檢測出的。這也是FindBugs侷限的地方。
FindBugs出到目前的版本,功能已經相當強大,不過也有待完善的地方。從實際使用來看,有一些隱藏的bug並不能靠FindBugs直接發現。那麼,可不可以撰寫一個新的 Detector,來發現這種將一個未初始化的reference傳來傳去而形成的潛在的bug呢?理論上來講,應該是可以的。這個 Detector目前還未實現。哪位如果有興趣的話,可以參考FindBugs, Part 2: Writing custom detectors(擴充套件閱讀)這篇文章,幫忙實現這個Detector。實現一個新的Detector,便可以檢測出一種新型的bug,這樣不知又可以幫開發人員省去多少人工檢查的時間,功德無量啊。
FindBugs也不能發現非java的Bug。對於非java撰寫的程式碼,如javascript,SQL等等,要找出其中可能的bug,FindBugs是無能為力的。當然,javascript中的bug似乎還不至於使系統崩潰,而SQL中的bug往往又跟業務邏輯相關,只要測試仔細一些應該是可以發現的。
FindBugs不過是一個工具。作為開發人員,當然首先要在程式設計的時候努力避免引入bug,而不要依賴於某個工具來為自己把關。不過由於程式碼的複雜性,一些隱藏的bug確實很難靠咱們的肉眼發現。這時,應用一些好的工具或許就可以幫你發現這樣的bug。這便是FingBug存在的價值。
為什麼應該將 FindBugs 整合到編譯過程中?
經常問到的第一個問題是為什麼要將 FindBugs 加入到編譯過程中?雖然有大量理由,最明顯的回答是要保證儘可能早地在進行編譯時發現問題。當團隊擴大,並且不可避免地在專案中加入更多新開發人員時,FindBugs 可以作為一個安全網,檢測出已經識別的缺陷模式。我想重申在一篇 FindBugs 論文中表述的一些觀點。如果讓一定數量的開發人員共同工作,那麼在程式碼中就會出現缺陷。像 FindBugs 這樣的工具當然不會找出所有的缺陷,但是它們會幫助找出其中的部分。現在找出部分比客戶在以後找到它們要好——特別是當將 FindBugs
結合到編譯過程中的成本是如此低時。
一旦確定了加入哪些過濾器和類,執行 FindBugs 就沒什麼成本了,而帶來的好處就是它會檢測出新缺陷。如果編寫特定於應用程式的檢測器,則這個好處可能更大。
生成有意義的結果
重要的是要認識到這種成本/效益分析只有在不生成大量誤檢時才有效。換句話說,如果在每次編譯時,不能簡單地確定是否引入了新的缺陷,那麼這個工具的價值就會被抵消。分析越自動化越好。如果修復缺陷意味著必須吃力地分析檢測出的大量不相干的缺陷,那麼您就不會經常使用它,或者至少不會很好地使用它。
確定不關心哪些問題並從編譯中排除它們。也可以挑出 確實關注的一小部分檢測器並只執行它們。另一種選擇是從個別的類中排除一組檢測器,但是其他的類不排除。FindBugs 提供了使用過濾器的極大靈活性,這可幫助生成對團隊有意義的結果,由此我們進入下一節。
確定用 FindBugs 的結果做什麼
可能看來很顯然,但是您想不到我參與的團隊中有多少加入了類似 FindBugs 這樣的工具而沒有真正利用它。讓我們更深入地探討這個問題——用結果做什麼?明確回答這個問題是困難的,因為這與團隊的組織方式、如何處理程式碼所有權問題等有很大關係。不過,下面是一些指導:
可以考慮將 FindBugs 結果加入到原始碼管理(SCM)系統中。一般的經驗做法是不將編譯工件(artifact)放到 SCM 系統中。不過,在這種特定情況下,打破這個規則可能是正確的,因為它使您可以監視程式碼質量隨時間的變化。
可以選擇將 XML 結果轉換為可以傳送到團隊的網站上的 HTML 報告。轉換可以用 XSL 樣式表或者指令碼實現。有關例子請檢視 FindBugs 網站或者郵件列表(請參閱 參考資料)。
像 FindBugs 這樣的工具通常會成為用於敲打團隊或者個人的政治武器。儘量抵制這種做法或者不讓它發生——記住,它只是一個工具,它可以幫助改進程式碼的質量。有了這種思想,在下一部分中,我將展示如何編寫自定義缺陷檢測器。
問題發現的例子
下面的列表沒有包括 FindBug 可以找到的 所有問題。這裡只是列舉了一些比較有意思的部分。
檢測器:找出 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() 方法。檢測器:忽略方法返回值
這個檢測器查詢程式碼中忽略了不應該忽略的方法返回值的地方。這種情況的一個常見例子是在呼叫 String 方法時,如在清單 1 中:
清單 1. 忽略返回值的例子
1 String aString = "bob"; 2 b.replace('b', 'p'); 3 if(b.equals("pop")) |
1 Person person = aMap.get("bob"); 2 if (person != null) { 3 person.updateAccessTime(); 4 } 5 String name = person.getName(); |
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 } |
![](http://d.hiphotos.baidu.com/baike/s%3D220/sign=7c2c6b3f9e2f07085b052d02d925b865/f31fbe096b63f624909a40738744ebf81b4ca3ff.jpg)
<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/> |
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> |
<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" /> |