讀古今文學網 > Android程序設計:第2版 > 預防Bug並保持代碼整潔 >

預防Bug並保持代碼整潔

可以把Eclipse理解成一種特殊的操作系統:由成千上萬的文件組成,有自己的文件系統,自己運行了一個Web服務器。Eclipse是開放的、可擴展的。Eclipse插件類似於操作系統的應用——編寫相對簡單,Eclipse生態系統包含的擴展要遠遠多於任何Eclipse用戶所安裝和使用的。因為Android代碼是用Java實現的,所以可以把所有的插件應用到Android軟件開發中。

這裡將探討一個非常有價值的Eclipse擴展:靜態分析器,或稱源代碼分析器。

靜態分析器

靜態分析的非正式定義是指它會指向編譯器警告信息出現的地方。在Eclipse中,編譯器告警往往是件好事。雖然好的編譯器會提供警告信息,且這些警告信息有助於捕捉潛在的運行時問題,但是捕捉潛在問題不是編譯器的工作。靜態分析器能夠完成這項工作。

靜態分析器之所以稱為「靜態」的原因是它是在未運行的代碼上執行分析。雖然編譯器執行的某些功能可能屬於靜態分析的範疇——在Eclipse中,Java編譯器很好地執行了編程後碎片的清理工作,如未使用的變量和方法,而靜態分析器做的遠遠不止這些。靜態分析器會盡可能地找出Bug,而不僅僅是隨便給出結果。

FindBugs

下面將通過安裝和使用FindBugs工具來開始靜態分析器的研究。可以在這裡找到FindBugs的文檔和源代碼:http://findbugs.sourceforge.net。後面將較詳細地介紹FindBugs工具的安裝過程,因為它和絕大多數的Eclipse插件安裝過程類似,很有代表性。要安裝FindBugs,必須把FindBugs庫加載到Eclipse的網站列表,從該列表安裝包。通過Help→Install New Software Menu命令,單擊Install對話框的Add按鈕。該操作會打開Add Repository對話框,將FindBugs庫的地址http://findbugs.cs.umd.edu/eclipse添加到對話框中,如圖5-7所示。

圖5-7:添加庫以便在Eclipse環境中安裝一個插件

安裝FindBugs的下一步是從庫中選擇包,如圖5-8所示。在這個例子中,可選的只有一個包。

圖5-8:選擇FindBugs庫中唯一可用的包

一旦選中包,就可以進入下一個對話框,其中顯示了要安裝的軟件包的列表。在這個例子中,該列表中只有一項,如圖5-9所示。

圖5-9:選中了FindBugs庫中唯一可用的包

在下一個對話框中,閱讀許可協議並選擇接受或不接受該包的許可協議,如圖5-10所示。

在安裝Eclipse插件的過程中還有一個問題需要解決。因為包沒有簽名,會收到安全警告,如圖5-11所示。

最後,需要重新啟動Eclipse,如圖5-12所示。

圖5-10:接受FindBugs許可協議

圖5-11:在安裝沒有簽名的包時顯示的安全警告

圖5-12:安裝FindBugs後重新啟動Eclipse

把靜態分析應用到Android代碼中

FindBugs包含一個菜單命令、一個透視圖及一些視圖,你會發現這些視圖在查找bug時很有用。要啟動FindBugs,可以使用項目上下文菜單中的菜單命令,如圖5-13所示。

圖5-13:調用FindBugs

運行了FindBugs之後,可以更改成FindBugs透視圖,如圖5-14所示。FindBugs透視圖包括一些視圖,在這些視圖中顯示了FindBugs發現的潛在問題的層次結構列表,這些列表根據問題類型進行了組織。在Editor視圖中對問題進行了標記。如果打開問題的屬性,會顯示關於問題的詳細解釋,包括在什麼情況下FindBugs會出現誤報(false positive)。

圖5-14:FindBugs透視圖

在這個例子中,我們一起來看看問題「Null check of a value previously dereferenced」,如圖5-15中的Bug Explorer視圖所示。

圖5-15:FindBugs Bug Explorer

驗證一個字段在解除引用後,還包含非空值,這在語義上不算錯誤的Java,但是幾乎可以確定它是無用的,或者顯然是個錯誤。在以下的代碼中,你會發現字段savedState在使用上假定其永遠非null,但是在日誌調用時出現null檢查:


protected void onRestoreInstanceState(Bundle savedState) {
      super.onRestoreInstanceState(savedState);
      // Restore state; we know savedState is not null
      String answer = savedState.getString(\"answer\");
      // This is a gratuitous test, remove it
      Object oldTaskObject = getLastNonConfigurationInstance;
      if (null != oldTaskObject) {
            int oldtask = ((Integer) oldTaskObject).intValue;
            int currentTask = getTaskId;
            // Task should not change across a configuration change
            assert oldtask == currentTask;
      }
      Log.i(TAG, \"onRestoreInstanceState\"
               + (null == savedState ? \"\" : RESTORE) + \" \" + answer);
}
 

實際上,應該在使用savedState之前檢查其是否為null,因為savedState的值並沒有指定為non-null。把上面沒有對savedState進行null測試的代碼修改為如下代碼:


String answer = null != savedState ? savedState.getString(\"answer\") : \"\";
  

再次運行FinsBugs,證實了該修改消除了可能出現null值的問題。

上面這個代碼片段是一個關於bug靜態分析典型例子。靜態分析超出了編譯器警告的處理範疇,因為可能編程人員就是打算這麼處理,而簡單的引用分析使得靜態分析器能夠發現它可能是個Bug,而往往也確實是個Bug。

靜態分析的局限性

靜態分析器存在誤報(false positive)的情況,因為它採取的方式是尋找代碼中的潛在問題。這是靜態分析器和編譯器警告之間的區別之一。如果編譯器錯誤消息對某段實際上並沒有問題的代碼報錯,這會被看做是編譯器的一個Bug。

靜態分析器的一個薄弱之處是它查找不遵循編碼規範的代碼。舉個例子,警告「Class names should start with an upper case letter」(類名稱應該以大寫字母開頭),如圖5-15所示,是由自動生成的代碼觸發的,這些程序員不需要考慮其中是否存在Bug,除非他們懷疑代碼生成器中有Bug。

經驗豐富的程序員經常質疑靜態分析器的實用性,因為在他們的代碼中,靜態分析器能夠捕獲的問題相對較少,而靜態分析器誤報的概率更高。眾所周知,對經驗豐富的程序員所編寫的代碼執行靜態分析,很少會發現Bug,靜態分析無法取代模塊測試和良好的調試技巧。但是,如果你是Java及Android新手,你會發現靜態分析器是避免出現編譯器警告的非常好的輔助工具。