挖掘package聲明的潛力

 Unwrap the package statement's potential
(挖掘package聲明的潛力)
Minimize project complexity and maximize code reuse
(最小化項目的複雜性,最大化代碼的複用程度)
by Laurence Vanhelsuwe

總評
package聲明是Java語言的一項非常強有力的特徵。然而大多數的Java程序員,甚至那些富有經驗的開發者,並沒有正確的挖掘出它的潛力來。更糟的是,許多開發者錯誤的使用了package的聲明,使得它在試圖控制項目複雜性和代碼的複用方面出現問題。感興趣了嗎?那就繼續閱讀下去,看看這樣一個簡單的語言特徵是如何擁有如此巨大的反響的。


爲了避免最高級別的package命名衝突,除了衆所周知的並且通常由Sun微系統公司申明的package命名約定之外,很少有程序員徹底地理解這個既讓人迷惑但又是簡單的package聲明瞭。大多數的編程者認爲關鍵字package最多就是用來將項目中的class整理成組。他們簡單的使用package聲明來爲每個項目創建單一的命名空間。但不幸的是,這種方式並不能經受時間或者範圍的考驗。

當一種過分簡單化的package觀念添加到以團隊爲範圍(先不說企業爲範圍)的Java代碼倉庫中時,您就會逐漸地同時也是痛苦地發現,錯誤地創造和管理Java代碼倉庫中的package層級意味着十分的昂貴和費力。更糟的是,這些問題將隨着您代碼的逐漸龐大而變得越來越嚴重,特別地,還會給代碼帶來項目邊界模糊的問題。

因此,從使用package聲明的那一刻起,就必須正確的做一些選擇和決議。

在這篇文章中,我將解釋爲什麼許多的Java程序員沒有正確的使用package關鍵字,然後演示一個可供選擇的並且已經經受了時間考驗的一些方式。


新手使用Java package的方式
在你最初使用Java來編寫程序的時候,一般是根本不會使用包聲明的。那個用來作爲語言介紹的經典HelloWorld程序確實沒有使用package,也沒有以任何方式來討論Java package或者package關鍵字:


// (沒有使用package聲明!)

class HelloWorld {
   public static void main(String[] args) {
      System.out.println("Hello World");
   }
}

你只是簡單地聲明你的類在缺省的package下(沒有名字的package),所以你可以以最詳細(verbose)的方式來運行你的Java代碼(比如在你控制檯的命令行):

> java HelloWorld

幸運的是,Java的設計並沒有因這種代碼執行的便利性而有任何的削弱。然而優雅並且強大地支持大規模的項目編碼是程序設計的最高目標,這正是通過package這個特徵來顯現出來的。因此,以缺省package的方式來作爲類聲明在你真正實現項目的時候是並不足以支撐下去的。

類名衝突和package的誕生
隨着您對Java越來越適應,您將會很快發現把所有的類都放置在缺省包下,就將受限於這個缺省包空間所能容納的實際的類的數量。比如,假設您起初的一些用於練習的類起名爲Main或者Program,而您的實際的項目同樣需要一個名爲Main或者Program的類來作爲程序主入口,那麼您就會發現類名起了衝突。要麼刪除些舊的類,要麼通過把全局空間細分成多個命名空間來創建多個包空間以解決問題。

Java新手們特別的也是最終發現的關於包聲明的問題在於當他們開始第二個Java項目,並且希望對他們第一個項目中的類和第二個項目中的類有一個清晰分離的時候。

很快的,爲每個新項目創建新的包變成了程序員的第二個習性。不幸的是,許多的Java程序員對於真正的package的理解僅限於這一點上。但是以這種原始的方式在那些跨度比較長的項目中繼續使用Java包特徵無疑是遠不能滿足要求的,尤其是當代碼量由小逐漸變得很大的時候。

代碼複製:堅決說不
簡單的爲每個新項目創建新的包的一個長期的問題是代碼的複製問題。代碼複製是編寫程序的地獄,這是因爲:

  # 維護的代價將由於螺旋狀的上升而變得難以控制
  # 代碼難以閱讀
  # 代碼變得越來越臃腫
  # 系統性能可能會變得緩慢下來

我們都知道這些問題的根源:程序員那標誌性的懶惰。這個過程通常是這樣的:在我們工作的時候,我們會以自己的感覺認爲這些是以前就已經做好的或者已經解決的(代碼中邏輯的部分,或者整個方法,或者(希望不是這樣!)整個類),於是我們高興的把那些代碼複製到新項目中來。這也就是剪切加複製編碼的來源。

如果您對於自己的那些用近乎於一致的邏輯、方法甚至類所堆砌成的重複代碼產生了難以控制的感覺,那麼您就有必要對您每天使用的Java開發方法論進行反思,以釋放包申明真正的力量。


The Big Bang...uh, I mean split [不能確切的翻譯,故保留]
讓我們從理論上來分析一下代碼複製的問題,認定所有的代碼複製是非法的並且根除它,任何重要的代碼片斷都應該出現並且只出現一次。這就是說,所有任何的

  # 通用的邏輯
  # 通用的數據分組
  # 通用的方法/程序
  # 通用的常量
  # 通用的類
  # 通用的接口

都不應當被申明在應用特定的包中。

這一關鍵的結論讓我們有了如下的有關包結構的第一個黃金規則:

黃金規則一
永遠不要將通用的代碼直接混合在應用代碼中!

比如:在com.company或者org.yourorg這一層上,將你的包層次再分爲兩個功能完全不同的分支:

  1. 可複用的代碼分支
  2. 項目(應用)特定的分支

應用代碼總是會用到通用的代碼(類庫以及程序),但是它們自己並不會包含這樣的代碼。相反的情況是:類庫代碼也永遠不會包含任何應用特定的代碼或者是和應用有依賴關係的代碼。

如果你還從來沒有考慮(編寫)過這樣兩種不同類型的基本代碼的話,那麼你有必要在你日常的編碼過程中考慮一下這種基本的代碼歸類(dichotomy)方法。這是在你的組織中應用代碼複用能力,以及一次性的消除代碼複製問題的關鍵所在。

這種代碼的歸類方法運用到package上的時候,邏輯上需要從最高級別上分爲通用(複用)package的主分支以及非通用(非複用)(比如:應用特定的)主分支。

舉個例子,在過去的五年中,我已經把org.lv這個最高級的命名空間分爲了org.lv.lego和org.lv.apps這兩個子空間。(lv並不代表什麼,只是我最初的命名)這些基本的高級別的分支在以後的日子裏面又分成了更多的子空間。比如lego分支目前分成如下的一些子空間:

org.lv.lego.adt
org.lv.lego.animation
org.lv.lego.Applets
org.lv.lego.beans
org.lv.lego.comms
org.lv.lego.crunch
org.lv.lego.database
org.lv.lego.files
org.lv.lego.games
org.lv.lego.graphics
org.lv.lego.gui
org.lv.lego.html
org.lv.lego.image
org.lv.lego.java
org.lv.lego.jgl
org.lv.lego.math
org.lv.lego.realtime
org.lv.lego.science
org.lv.lego.streams
org.lv.lego.text
org.lv.lego.threads

請注意的是,基本上所有這些包結構的邏輯內容都自我證明了它們是經過仔細選擇後的結果,並且也是從字面上就可以分辨出它們的含義(可以和java.*結構做對照)。這一點對於釋放可複用代碼真正的潛力作用是非常關鍵的,比如那些可複用的邏輯、程序、常量、類以及接口。很差的包命名,就象很差的類/接口的命名一樣,會對它們的使用者產生歧義並且破壞資源的複用潛力。

在這些較深層次的包級別上,您仍然需要對如何進一步組織您的包結構非常的小心。

這裏是第二條黃金規則:

黃金規則二
保持分等級的包結構
總是試圖創建象平衡的、不規則形狀的樹結構那樣的包層次。

如果你的包層次中的某些層次最終形成(退化爲)線性結構,那表明了你並沒有正確的使用Java包特性。經典的錯誤是簡單的將項目包羅列在你的最高層應用包分支下面,比如在我的org.lv.apps包。這是錯誤的使用方式,因爲它並不是層級結構的。線性羅列對於人腦來說是難以記憶很長時間的;而層級結構卻正是適應我們大腦的神經系統網絡結構的。

項目總是能由關鍵原則來進行歸類,這原則或者說觀念應該在你的Java包層次中反應出來。下面是我的org.lv.apps包目前是如何進行細分的:

org.lv.apps.comms
org.lv.apps.dirs
org.lv.apps.files
org.lv.apps.games
org.lv.apps.image
org.lv.apps.java
org.lv.apps.math

很明顯,你們的細分肯定和我的不相同,但是重要的是從大處考慮並且在腦子裏始終保留對將來的擴展能力。深層次的包結構是有益的。而淺層次的則不然。

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