在ANT出現之前,編譯和部署Java應用需要使用包括特定平臺的腳本、Make文件、不同的IDE以及手工操作等組成的大雜燴,F在,幾乎所有的開源Java項目都在使用Ant,許多公司的開發項目也在使用Ant。Ant的大量使用,也自然帶來了對總結Ant最佳實踐的迫切需求。 本文總結了我喜好的Ant最佳實踐,很多是從親身經歷的項目錯誤,或從其他開發者的“恐怖”故事中得到的靈感的。比如,有人告訴我有個項目將XDoclet 生成的代碼放入鎖定文件的版本控制工具中。單開發者修改源代碼時,他必須記住手工檢出(Check out)并鎖定所有將要重生成的文件。然后,手工運行代碼生成器,當他能夠讓Ant編譯代碼時,這一方法還存在一些問題:
- 生成的代碼無法存儲在版本控制系統中
- Ant(本案例中是Xdoclet)應該自動確定下一次構建涉及的源文件,而不應由程序員人工確定。
- Ant的構建文件應該定義好正確的任務依賴關系,這樣程序員不必按照特定順序調用任務。
當我開始一個新項目時,我首先編寫Ant構建文件。文件定義構建的過程,并為團隊中的每個程序員都使用。本文所有的最佳實踐假設Ant構建文件是一個必須精心編寫的重要文件,它應在版本控制系統中得到維護,并定期進行重構。下面是我的十五大Ant最佳實踐。
1. 采用一致的編碼規范
Ant用戶不管是喜歡還是痛恨XML構建文件的語法,都愿意跳進這一迷人的爭論中。讓我們先看一些保持XML構建文件簡潔的方法。
首先,也是最重要的,花費時間格式化你的XML讓它看上去很清晰。不過XML是否美觀,Ant都可以工作。但是丑陋的XML很難讀懂。倘若你在任務之間留出空行,有規則的縮進,每行文字不超過90列,那么XML令人驚訝的易讀。再加上好的編輯器或IDE高亮相應的語句,你就不會有如何閱讀的麻煩。
同樣,精選有意義明確、容易讀懂的詞匯來命名任務和屬性。比如,dir.reports就比rpts好。并不需要特定的編碼規范,只要有一種規范并堅持使用就好。
2. 將build.xml 放在項目根目錄中 Ant構建文件build.xml可以放在如何位置,但是放在項目頂層目錄中可以保持項目簡潔。這是最普遍的規范,使開發者能夠在根目錄找到它。同時,也能夠容易了解項目中不同目錄之間的邏輯關系。以下是一個典型的項目層次:[root dir] | build.xml +--src +--lib (包含第三方 JAR包) +--build (由 build任務生成) +--dist (由 build任務生成)
當build.xml在頂級目錄時,倘若你在項目某個子目錄中,只要輸入:ant -find compile 命令,不需要改變工作目錄就能夠以命令行方式編譯代碼。參數-find告訴Ant尋找存在于上級目錄中的build.xml并執行。
3. 使用單一構建文件 有人喜歡將一個大項目分解到幾個小的構建文件,每個構建文件分擔整個構建過程的一小部分工作。但是應該認識到,將構建文件分割會增加對整個構建過程的理解難度。要注意在單一構建文件能夠清楚表現構建層次的情況下,不要過工程化(over-engineer)。 即使你把項目劃分為多個構建文件,也應使程序員能夠在項目根目錄下找到核心build.xml。盡管該文件只是將實際構建工作委派給下級構建文件,也應保證該文件可用。
4. 提供良好的幫助說明
應盡量使構建文件自文檔化。增加任務描述是最簡單的方法。當你輸入ant -projecthelp時,你就可以看到帶有描述的任務清單。比如,你可以這樣定義任務:<target name="compile" description="Compiles code, output goes to the build dir.">
最簡單的規則是對所有你希望程序員通過命令行直接調用的任務都加上描述。對于一般用來執行中間處理過程的內部任務,比如生成代碼或建立輸出目錄等,就無法使用描述屬性。
這時,可以通過在構建文件中加入XML注釋來處理;蛘邔iT定義一個help任務,當程序員輸入ant help時來顯示詳細的使用說明。<target name="help" description="Display detailed usage information"> <echo>Detailed help...</echo> </target>
5. 提供清空任務 每個構建文件都應包含一個清空任務,刪除所有生成的文件和目錄,使系統回到構建文件執行前的初始狀態。執行清空任務后還存在的文件應處在版本控制系統的管理下。
比如:<target name="clean" description="Destroys all generated files and dirs."> <delete dir="${dir.build}"/> <delete dir="${dir.dist}"/> </target>
6. 使用ANT管理任務從屬關系
假設你的應用由Swing GUI組件、Web界面、EJB層和公共應用代碼組成。在大型系統中,你需要清晰地定義Java包屬于系統的哪一層。否則如何一點修改都要重新編譯成千上百個文件。任務從屬關系管理差會導致過度復雜而脆弱的系統。改變GUI面板的設計不應造成Servlet和EJB的重編譯。 當系統變得龐大后,稍不注意就可能將依賴于客戶端的代碼引入到服務端。這是因為IDE在編譯文件時使用單一的classpath。Ant讓你更有效地控制構建活動。 設計你的構建文件編譯大型項目的步驟:首先,編譯公共應用代碼,將編譯結果打成JAR包文件。然后,編譯上一層的項目代碼,編譯時依靠第一步產生的JAR文件。不斷重復這一過程,直到最高層的代碼編譯完成。 分步構建強化了任務從屬關系管理。如果你工作在底層Java框架上,引用高層的GUI模板組件,這時代碼不需要編譯。這是由于構建文件在編譯底層框架時,在源路徑中沒有包含高層GUI面板組件的代碼。
7. 定義并重用文件路徑
如果文件路徑在一個地方集中定義,并在整個構建文件中得到重用,那么構建文件更易于理解。以下是這樣做的一個例子:<project name="sample" default="compile" basedir="."> <path id="classpath.common"> <pathelement location="${jdom.jar.withpath}"/> ...etc </path> <path id="classpath.client"> <pathelement location="${guistuff.jar.withpath}"/> <pathelement location="${another.jar.withpath}"/> <!-- reuse the common classpath --> <path refid="classpath.common"/> </path> <target name="compile.common" depends="prepare"> <javac destdir="${dir.build}" srcdir="${dir.src}"> <classpath refid="classpath.common"/> <include name="com/oreilly/common/**"/> </javac> </target> </project>
8. 定義恰當的任務參數關系
假設dist任務從屬于jar任務,那么哪個任務從屬于compile任務,哪個任務從屬于prepare任務呢?Ant構建文件最終定義了任務的從屬關系圖,它必須被仔細地定義和維護。 應該定期檢查任務的從屬關系以保證構建工作得到正確執行。大的構建文件隨著時間推移趨向于增加更多的任務,所以到最后由于不必要的從屬關系導致構建工作非常困難。比如,你可能發現在程序員只是需要編譯一些沒有使用EJB的GUI代碼時,重新生成EJB代碼。 以“優化”的名義忽略任務的從屬關系是另一種常見的錯誤。這種錯誤迫使程序員為了得到恰當的結果必須記住并按照特定的順序調用一串任務。更好的做法是:提供描述清晰的公共任務,這些任務包含正確的任務從屬關系;另外提供一套“專家”任務讓你能夠手工執行個別的構建步驟,這些任務不提供完整的構建過程,但是讓那些專家在快速而惱人的編碼期間跳過某些步驟。
9.使用配置屬性
任何需要配置或可能發生變化的信息都應作為Ant屬性定義下來。對于在構建文件中多次出現的值也同樣處理。屬性既可以在構建文件頭部定義,也可以為了更好的靈活性而在單獨的屬性文件中定義。以下是在構建文件中定義屬性的樣式:<project name="sample" default="compile" basedir="."> <property name="dir.build" value="build"/> <property name="dir.src" value="src"/> <property name="jdom.home" value="../java-tools/jdom-b8"/> <property name="jdom.jar" value="jdom.jar"/> <property name="jdom.jar.withpath" value="${jdom.home}/build/${jdom.jar}"/> etc... </project>
或者你可以使用屬性文件:<project name="sample" default="compile" basedir="."> <property file="sample.properties"/> etc... </project>
在屬性文件 sample.properties中:dir.build=build dir.src=src jdom.home=../java-tools/jdom-b8 jdom.jar=jdom.jar jdom.jar.withpath=${jdom.home}/build/${jdom.jar}
10. 保持構建過程獨立 為了最大限度的擴展性,不要應用外部路徑和庫文件。最重要的是不要依賴于程序員的CLASSPATH設置。取而代之的是,在構建文件中使用相對路徑并定義自己的路徑。如果你引用了絕對路徑如C:\java\tools,其他開發者未必使用與你相同的目錄結構,所以就無法使用你的構建文件 如果你部署開發源碼項目,應該提供包括所有需要的JAR文件的發行版本,當然是在遵守許可協議的基礎上。對于內部項目,相關的JAR文件都應在版本控制系統的管理中,并撿出到大家都知道的位置。 當你不得不應用外部路徑時,應將路徑定義為屬性。使程序員能夠涌適合他們自己的機器的參數重載這些屬性。你也可以使用以下語法引用環境變量:<property environment="env"/> <property name="dir.jboss" value="${env.JBOSS_HOME}"/>
11. 使用版本控制系統 構建文件是一個重要的文件,應該象代碼一樣進行版本控制。當你標記你的代碼時,也應用同樣的標簽標記構建文件。這樣當你需要回溯構建舊版本的軟件時,能夠使用相對應的舊版本構建文件。 除構建文件之外,你還應在版本控制中維護第三方JAR文件。同樣,這使你能夠重新構建舊版本的軟件。這也能夠更容易保證所有開發者擁有一致的JAR文件,因為他們都是同構建文件一起從版本控制系統中撿出的。 通常應避免在版本控制系統中存放構建輸出品。倘若你的源代碼很好地得到了版本控制,那么通過構建過程你能夠重新生成任何版本的產品。
12. 把Ant作為“最小公分母”
假設你的開發團隊使用IDE,為什么要為程序員通過點擊圖標就能夠構建整個應用而煩惱呢? IDE的問題在團隊中是一個關于一致性和重現性的問題。幾乎所有的IDE設計初衷都是為了提高程序員的個人生產率,而不是開發團隊的持續構建。典型的IDE要求每個程序員定義自己的項目文件。程序員可能擁有不同的目錄結構,可能使用不同版本的庫文件,還可能工作在不同的平臺上。這將導致出現這種情況:在A那里運行良好的代碼,到B那里就無法運行。 不管你的開發團隊使用何種IDE,一定要建立所有程序員都能夠使用的Ant構建文件。要建立一個程序員在將新代碼提交版本控制系統前必須執行Ant構建文件的規則。這將確保代碼是經過同一個Ant構建文件構建的。當出現問題時,要使用項目標準的Ant構建文件,而不是通過某個IDE來執行一個干凈的構建。
程序員可以自由選擇任何他們習慣使用的IDE。但是Ant應作為公共基線以保證永遠是可構建的。
13. 使用 zipfileset屬性
人們經常使用Ant產生WAR、JAR、ZIP和 EAR文件。這些文件通常都要求有一個特定的內部目錄結構,但其往往與你的源代碼和編譯環境的目錄結構不匹配。 一個最常用的方法是寫一個Ant任務按照期望的目錄結構把一大堆文件拷貝到臨時目錄中,然后生成壓縮文件。這不是最有效的方法。使用zipfileset屬性是更好的解決方案。它讓你從任何位置選擇文件,然后把它們按照不同目錄結構放進壓縮文件中。以下是一個例子:<ear earfile="${dir.dist.server}/payroll.ear" appxml="${dir.resources}/application.xml"> <fileset dir="${dir.build}" includes="commonServer.jar"/> <fileset dir="${dir.build}"> <include name="payroll-ejb.jar"/> </fileset> <zipfileset dir="${dir.build}" prefix="lib"> <include name="hr.jar"/> <include name="billing.jar"/> </zipfileset> <fileset dir="."> <include name="lib/jdom.jar"/> <include name="lib/log4j.jar"/> <include name="lib/ojdbc14.jar"/> </fileset> <zipfileset dir="${dir.generated.src}" prefix="META-INF"> <include name="jboss-app.xml"/> </zipfileset> </ear>
14. 運行 Clean 構建任務的測試
假設你的構建文件中有clean和compile的任務,執行以下的測試。第一步,執行ant clean;第二步,執行ant compile;第三步,再執行ant compile。第三步應該不作任何事情。如果文件再次被編譯,說明你的構建文件有問題。 構建文件應該只在與輸出文件相關聯的輸入文件發生變化時,才應該執行任務。一個構建文件在不必執行諸如編譯、拷貝或其他工作任務的時候執行這些等任務是低效的。當項目規模增長時,即使是小的低效工作也會成為大的問題。
15. Avoid Platform-Specific Ant Wrappers
不管什么原因,有人喜歡用簡單的、名稱叫做compile之類的批文件或腳本裝載他們的產品。當你去看腳本的內容,你會發現以下內容:
ant compile
其實開發人員熟悉Ant,并且完全能夠自己鍵入ant compile。請不要僅僅為了調用Ant而使用特定平臺的腳本。這只會使其他人在首次使用你的腳本時,增加學習和理解的煩擾。除此之外,你不可能提供適用于每個操作系統的腳本,這是真正煩擾其他用戶的地方。
總結 太多的公司依靠手工方法和程序來編譯代碼和生成軟件發布版本。那些不使用Ant或類似工具定義構建過程的開發團隊,花費了令人驚異的時間來捕捉代碼編譯過程中出現的問題,這些在某些開發者那里編譯成功的代碼,到另一些開發者那里卻失敗了。
生成并維護構建腳本不是一項迷人的工作,但卻是一項必需的工作。一個好的Ant構建文件將使你集中到更喜歡的工作——寫代碼中!
參考
· Ant
· AntGraph: Ant依賴性的可視化工具
· Ant: The Definitive Guide, O'Reilly
· Java Extreme Programming Cookbook, O'Reilly
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/