診斷Java代碼:設計輕鬆的代碼維護

  有效調試源自良好的編程。設計易於維護的程序是程序員面臨的最困難挑戰之一,其部分原因在於程序通常並不是由那些編寫代碼的程序員維護的。爲了有效維護這樣的程序,新程序員必須能夠快速瞭解程序的工作原理,如果程序員能夠單獨理解整個程序中各個小部分,那麼就可以容易地瞭解程序的工作原理。

  通過討論可變性、可譯碼性、私有方法、最終方法、最終類、本地代碼、單元測試以及重構問題,我們將簡述編寫程序的一些方法,以幫助使程序更易理解和維護。

  可變性和可譯碼性
  首先討論可變性問題。如果在一個程序的計算期間,其每一部分所處理的數據都沒有被該程序的其它、遠程部分更改,那麼就很容易單獨理解該程序的各個部分。

  太多信息
  例如,請考慮一個使用容器類實例的程序,可以修改其中的成分鏈接。每次將容器從程序某一部分上的方法傳遞到該程序其它部分的方法,以及每次調用 new 表達式(其中容器被作爲參數傳遞)時,容器就可能脫離調用方法的控制發生改變。

  在我們首先理解調用方法調用的每個方法如何修改容器之前,我們不能真正確保我們理解了調用方法,由此我們診斷錯誤的能力也就更差。如果這些被調用的每個方法都依次調用其它修改方法,那麼維護程序員爲了理解單個方法必須閱讀的代碼總量會迅速增加,多得無法控制。

  由於這個原因,對可變容器和不可變容器使用不同類會非常有利。在不可變版本中,容器的字段可以標記成 final。

  求助於函數樣式
  相對於修改舊數據,爲構造新數據而進行代碼編寫稱爲函數樣式,因爲程序的方法與數學函數相似,其行爲是根據每個輸入所返回的輸出來單獨描述的。

  函數樣式經常被忽略的優點是相當容易單獨理解程序的個別組件。如果方法所操縱的數據決不會被其主體中執行的任何操作改變,那麼程序員要理解該方法必須做的就是理解那些操作返回的結果。將之與前面的一個方法調用幾個其它方法的方案相對照,那個方案中的其它幾個方法都修改這一方法所操作的數據結構。

  Java 語言的一個相當好的特性是它允許我們使用 final 關鍵字(作爲類型檢查器的僞指令)來聲明何時我們要使某個數據成爲不可變。

  使用 final 關鍵字來避免變化是“釘住”類的方法行爲的一個好方法。每次修改字段時,都有可能改變引用該字段的方法的行爲。另外,將字段標記爲 final 讓閱讀程序的其他程序員立即知道:不管整個程序有多大,決不要修改該字段。例如,請考慮下列表示不可變列表的類層次結構。

清單 1. 表示不可變列表的類層次結構

abstract class List {...}
class Empty extends List {...}
class Cons extends List {
private final Object first;
private final List rest;
}

  這些類中的所有字段都被標記成 final。要確保這些類的實例不可變,這樣做夠了嗎?不太夠。當然,即使字段被標記成 final,該字段本身的組件可能不是 final,記住這一點很重要。當那些組件更改時,引用那些組件的程序的任何部分可能會被修改,而不管字段本身是否改變。在上面的示例中,儘管列表的組成元素不能被修改,但是我們必須檢查那些元素本身沒有包含可能被修改的非最終字段。

  在這種情形中,儘管列表可能包含可變元素,但是我們可以看到存儲在給定列表中的元素序列由於以下原因而不可變:Empty 列表(即,長度爲零的列表)的實例根本不包含任何元素;因此不能修改它們。Cons(非空列表)實例包含兩個字段,都是 final。第一個字段包含該列表的第一個元素,它不能被修改;第二個字段包含一個列表,其中包含所有剩餘元素。如果這個列表的內容不可變,那麼該包含列表也不可變。

  但是包含在這第二個字段中的列表比包含列表的長度小一,所以如果我們知道長度爲 n 的所有列表都不可變,那麼我們就知道長度爲 n + 1 的列表也不可變。因爲我們已經知道長度爲零的列表不可變,所以我們也知道長度爲 1、2、3 等的列表同樣不可變。

  跟蹤與此類似的數據結構連接會很乏味,但當您能確定這種結構的全局特性(諸如不可變性)時,這樣做是值得的。

  控制變化
  防止出現不期望的變化的最佳策略就是儘可能避免所有變化。只有當出現一定要改變的原因時(例如,當這樣做大大簡化了代碼結構時),我們才應該使用它。當可以避免變化時,所產生的好處是巨大的(在較低的維護費用和增強的健壯性方面)。

  即使存在一定要改變數據的原因,最好還是設法控制那種變化,從而儘可能限制可能產生的破壞。迭代器和流是數據結構的極佳示例,這些數據結構明確設計成通過允許我們以常規的、定義良好的形式利用一系列元素,而不是明確修改這些元素的某個句柄來控制變化。

  私有方法
  就如同將字段設置成 final 有助於限制對字段值產生外部影響一樣,將它們設置成 private 有助於限制它們對程序其它部分產生的影響。如果字段是私有的,那麼我們可以確信該程序的其它部分都不與它直接相關。如果我們除去了該字段,並替換了該類數據的內部表示,那麼我們只要關心修正該類內部的方法,以正確訪問新數據。

  在前面的示例中,請注意類 Cons 的字段是私有的。這樣的話,我們就可以通過讀方法(getter)及類似方法來控制如何訪問那些元素。如果我們列表的未來維護人員有時想要修改列表的內部表示(例如,可以論證在某些平臺上,基於數組的列表或許更有效),那麼程序員可以這樣做,而不必修改或甚至查看那些列表的任何客戶機。他只要重寫 getter 就可以對新數據採取適當的操作。

  最終方法、最終類和理解本地代碼
  與將字段標記成 final 形成對比的是,將方法標記成 final 通常被指責爲與 OO 設計目標不一致,因爲這樣禁止繼承多態性。但是在嘗試理解大型程序的行爲時,這樣有助於瞭解什麼方法沒有被重寫。

  現在良好的 OO 設計涉及使用大量繼承,這的確是事實。事實上,繼承是許多 OO 設計模式的核心。但是那並不意味着我們應該允許我們編寫的每個方法都被重寫。通常程序將隱式地依賴某些沒有被重寫的關鍵方法。通過將這樣的方法標記成 final,我們將允許其他程序員更好地理解調用該方法的表達式行爲。

  另外,將類標記成 final 會極大提高可譯碼性。它會真正有助於初步瞭解程序中哪些類決不會被子類化。事實上,我認爲:只有不應被標記成 final 的類纔是程序中真正被子類化的類,以及那些有意從外部組件上被子類化的類(作爲程序設計的固有部分)。

  有人可能認爲這個概念會束縛將來的代碼維護人員,使他們不能擴展代碼。我認爲這肯定不會限制他們。如果程序將來的維護人員需要擴展代碼以包含以前不存在的子類,那麼只要他們擁有對源代碼的訪問權(如果他們無權訪問,那麼如何成爲該代碼的“維護人員”呢?),刪除相應類上的 final 關鍵字並重新編譯並不太困難。

  同時,那個被添加的關鍵字充當關於該程序的重要不變量的自動驗證文檔形式(“自動驗證”是因爲如果該文檔被破壞,那麼該程序甚至不會編譯)。通過強制開發人員自覺選擇何時要刪除這樣的不變量,我們可以幫助減少錯誤的引入。

  單元測試和變化
  單元測試總是能夠有助於理解具有副作用的代碼。如果一套單元測試充分證明了方法在程序中的作用,那麼程序員只要通過閱讀其單元測試就可以更迅速理解每個方法。當然,單元測試是否真的涵蓋了所有的作用是個大問題。類似於 Clover 的有效範圍分析工具在這裏可以提供某種程度的幫助。

  但是,請注意單元測試本身要比編寫嚴格的函數方法簡單得多。要測試嚴格的函數方法,涉及的全部就是用各種具有代表性的輸入調用這些方法,並檢查它們的輸出(並確保它們在應該拋出異常時能拋出)。

  在測試修改數據結構狀態的方法時,我們必須首先執行這樣的操作,這些操作是將輸入數據放入該方法所期望的狀態中所需要的,然後在調用該方法之後,檢查是否正確執行了客戶機所期望的數據的每次修改。

  用重構工具封裝
  在編寫新代碼時這些技巧很有用,但是當您必須維護幾乎不能譯碼的舊代碼時,怎麼辦呢?重構、重構、還是重構。

  儘管重構舊代碼很費時,但是這些時間是值得的,特別是所有支持重構的工具現在也支持 Java 代碼了。現在已經有許多自動重構 Java 代碼的強大工具,這些工具可以自動保存關鍵的不變量。

  重構 Java 代碼的一個功能相當齊全的工具是 IDEA 開發環境。該環境對相當多的 Martin Fowler 重構模式提供自動支持。我找到的另一個非常有用的工具是 CodeGuide,它是一個來自德國的 IDE。儘管相對於 IDEA,其自動重構的列表很小,但是它顯示了一個極其強大的特性 — 連續編譯。當您輸入新代碼時,CodeGuide 分析它並告知您項目中是否不完整(當然,這產生很短的延遲,它防止對每次擊鍵發出錯誤信號)。

  儘管連續編譯對響應產生負面影響,但是在某些上下文中非常值得這樣的等待。例如,您可以在字段前輸入 final,會立即看到項目中是否不完整。如果無,那麼您知道該字段在該程序的任何地方都沒有被修改。同樣,您可以在字段之前輸入 private,那麼立即獲得對該字段的所有外部訪問的列表(以錯誤的形式)。

  CodeGuide 的另一個極佳特性是它用泛型類型對 JSR-14 實驗擴展提供了無縫支持(計劃正式添加到 Java 1.5)。

  儘管爲了可譯碼性而編寫代碼會花費非常多的時間和精力,但是它會有助於提高代碼的生命期和健壯性,而且它可以顯著提高面臨維護代碼任務的那些程序員的生活質量。最後,重構舊代碼使之更易維護很費時,但當您下次必須修正錯誤時您就會知道這樣做是值得的。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章