ant——你要是不會,出門都不好意思跟人打招呼的那個ant,每個人都用過。
它是一個build tool,用xml來描述target,用xml來設置每個task的屬性。
ant的好處我們都體會到了
1。什么都是xml。而xml地球人都知道。
2。功能強大。從編譯java文件到checkin cvs,反正幾乎你想得到的功能它都能作。
3。擴展容易,如果你發現某個功能ant沒有,自己實現一個Task類就是。
4。一些功能設計得很合理。比如javac和java自動檢查時間戳和依賴關系檢查等等。
但是,用多了,發現缺點也不少
1。什么都是xml。而xml的語法有些時候顯得很繁瑣。
2。xml用來描述邏輯異常笨拙。
3。所有的邏輯都只能在java里用Task實現。要做一些跨越不同Task之間的通訊很困難。比如:先讀取第一個文件的時間戳,再讀取另一個文件中儲存的時間戳,再根據兩個時間戳之間的距離判斷下一步調用哪個task或者target。
4。xml的代碼重用困難。很難定義一些常用的xml element作為庫,然后再不同文件甚至項目中重用。
5。對module的支持有限。
仔細想想,其實,需求發展到邏輯重用,模塊管理,不同task通訊等,已經離描述數據這個xml最擅長的領域越來越遠了。
如果把task作為基本的組成元件,那么,上面提出的幾點需求,都是關注于對這些基本元件的管理和組合;蛘哒f,glue。
到此,口號呼之欲出,那就是:script。
很多script,作為一個完整的語言,是做glue的最理想選手。
下面談談我對一個基于script的built tool的構想。
首先,這個build tool仍然需要允許通過java來自定義task。
我們定義這樣一個接口:
java代碼:
interface Command{
Object execute(CommandContext ctxt)
throws Throwable;
}
我們計劃讓所有的task(我們這里叫它們command)都實現這個接口。
CommandContext負責傳遞一些象log之類的信息。
這個execute返回一個Object,這個值作為這個Command的返回值,可以用來和其它的Command通信。
我們允許這個函數拋出任何異常,這個framework將會處理這些異常。
然后,定義一些基本的Command,比如,ReturnCommand負責直接返回某一個值
java代碼:
class ReturnCommand implements Command{
private final Object v;
public Object execute(CommandContext ctxt){
return v;
}
ReturnCommand(Object v){this.v=v;}
}
PrintCommand負責打印一句話
java代碼:
class PrintCommand implements Command{
private final String msg;
public Object execute(CommandContext ctxt){
ctxt.getLogger().log(msg);
return null;
}
PrintCommand(String msg){this.msg=msg;}
}
FailCommand負責報告錯誤
java代碼:
class FailCommand implements Command{
private final String msg;
public Object execute(CommandContext ctxt){
throw new CommandException(msg);
}
FailCommand (String msg){this.msg=msg;}
}
如此等等。這樣的基本元件還有很多,比如file copy, javac, zip, jar等。
但是,如果僅僅如此,那么這個工具的能力最多也就和ant一樣。
我們最需要的,是把不同的command組合起來的能力。
而組合,最常見的,就是順序執行。
java代碼:
class SeqCommand implements Command{
private final Command c1;
private final Command c2;
public Object execute(CommandContext ctxt){
c1.execute(ctxt);
return c2.execute(ctxt);
}
SeqCommand (Command c1, Command c2){
this.c1 = c1;
this.c2 = c2;
}
}
上面這個簡單的Command,就負責按照順序執行連續的兩個command。
除了順序執行,還有錯誤處理。我們也許會希望,當某個步驟執行失敗時,去執行另外一個動作,為此,我們需要先定義一個接口來描述錯誤恢復:
java代碼:
interface CommandRecovery{
Command recover(Throwable th)
throws Throwable;
}
當某個command失敗的時候,這個接口會被調用。實現這個接口,可以有選擇地對某一種或者幾種錯誤進行恢復。
然后定義具體的錯誤恢復邏輯
java代碼:
class RecoveredCommand implements Command{
private final Command c1;
private final CommandRecovery c2;
public Object execute(CommandContext ctxt){
try{
return c1.execute(ctxt);
}
catch(Throwable th){
return c2.recover(th).execute(ctxt);
}
}
RecoveredCommand (Command c1, CommandRecovery c2){
this.c1 = c1;
this.c2 = c2;
}
}
有try-catch,就有try-finally,我們也可以定義一個command,讓它保證某個關鍵動作必然運行
java代碼:
class FinallyCommand implements Command{
private final Command c1;
private final Command c2;
public Object execute(CommandContext ctxt){
try{
return c1.execute(ctxt);
}
finally{
c2.execute(ctxt);
}
}
FinallyCommand (Command c1, Command 2){
this.c1 = c1;
this.c2 = c2;
}
}
前面的順序執行,我們是直接扔掉了前一個command的返回值。但是有些時候,我們也許希望根據第一個command的返回值來決定下一步的走向。為此,仿照CommandRecovery接口,定義CommandBinder接口:
java代碼:
interface CommandBinder{
Command bind(Object v);
}
然后定義BoundCommand類:
java代碼:
class BoundCommand implements Command{
private final Command c1;
private final CommandBinder c2;
public Object execute(CommandContext ctxt){
final Object v = return c1.execute(ctxt);
return c2.bind(v).execute(ctxt);
}
BoundCommand (Command c1, CommandBinder c2){
this.c1 = c1;
this.c2 = c2;
}
}
先透露一下,這個BoundCommand非常重要,就是它負責在不同的command間傳遞信息。
基本上的框架搭好了,下面,假設我們用一個類似groovy的腳本來寫某個target,我們的目標是先取得當前時間,然后打印出這個時間,然后調用javac,最后在程序結束后,打印程序結束的信息:
java代碼:
new BoundCommand(
new GetTimeCommand(),
new CommandBinder(){
public Command bind(Object v){
final Command c2 = new PrintCommand("build time is "+v);
final Command javacc = new JavaCCommand();
final Command done = new PrintCommand("build successful");
return new SeqCommand(c2, new SeqCommand(javacc, done));
}
}
);
上面的代碼,先調用GetTimeCommand,取得當前時間,然后把這個實現傳遞到這個匿名類中去,這個匿名類根據這個時間2,創建了下一步的command c2。
接下來,它調用兩次SeqCommand來表達兩次順序執行。
最終,當這個command被執行的時候,它就會完成我們上面要求的幾個步驟。
不錯,挺好。達到了在步驟間任意傳遞信息的要求。甚至,我們也可以重用某些command或者函數。
唯一一個問題:這個代碼他媽的比xml還惡心!
這還是很簡單的情況,如果我們綜合順序,錯誤處理,分支等等,代碼會丑陋得不忍卒睹。
看來,不是隨便什么script都可以勝任的。
那么,讓我們先靜下心來反過來想想,我們到底希望有什么樣的語法呢?
寫偽碼,應該是這樣:
java代碼:
time <- getCurrentTime
print time
javac
print "success"
我們的目標是:用腳本語言把前面繁雜的java代碼屏蔽起來,讓語法簡潔的腳本自動調用上面那些臃腫的代碼。
幸好,我手頭有一個腳本語言可以達到類似的語法,
java代碼:
do {time=now} $
info.print time >>
javac {classpath=...; fork=...; compatibility="1.4";...} >>
info.print "build successful"
這些do, >>等函數其實是用SeqCommand, BoundCommand等實現的,只不過表面上看不到了。
更加復雜的邏輯,比如包含順序執行,也包含錯誤處理的:
java代碼:
auto (info.println "build done") $
do {time=now} $
info.println ("build starting at " + time) >>
do {t1 = readFile "file1"} $
do {t2 = readFile "file2"} $
let
diff = t2 - t1;
writeFile "file3" diff
end
這段腳本要先讀取當前時間,然后打印build start;然后先后從file1和file2讀取兩個數;然后把這兩個數的差額寫入file3, 最后,無論成功與否,打印build done。
auto函數的意思是:當后面那些東西執行完畢后,無論是否出現exception,都要打印"build done"。
你如果感興趣可以試著用java或者groovy寫寫,看看結果多么可怕。
如此,一個完整的build框架就建立起來了,我們只要填空式地給系統加入各種command實現,一個靈活優美的build tool就出爐了。
最后,預告一下,基于這個思想的open source 項目Neptune即將啟動,歡迎有志之士參加。
你可以參與這個框架核心的搭建(跟我合作),也可以編寫獨立的各種Command來豐富框架的功能。
文章來源于領測軟件測試網 http://www.kjueaiud.com/
版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備10010545號-5
技術支持和業務聯系:info@testage.com.cn 電話:010-51297073
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月