Contracts是什麼? 在Java中, interface是最常用的設計, 問題是, 給了interface卻無法確定使用interface的程式設計師的設計品質, 這是因為interface只是定義功能與其輸入與輸出, 因此, 不佳的程式設計可能會被暫時隱藏, 卻像個不定時炸彈, 不知在那一隻不斷引用的程式中出問題, 為了能夠確保關鍵的商業邏輯不至處在此種風險之下, 就使用了abstract class來檢查輸入與輸出, 也就犧牲了低耦合的能力了. 這個問題困擾了我很久, 以為沒有特別的方法, 昨日看到了contracts的觀念, 今天又看到了google釋出contracts元件http://code.google.com/p/cofoja/, 發現竟然可以在interface來註記(annotation), 這是java從http://www.eiffel.com/學來的強大功能, 這個功能看起來好像很高深, 在這裡分享的目的是希望初學Java的邦友可以在一開始的階段就使用contracts, 因為這是優質程式設計的習慣.
就拿google上的範例來說明:
interface Time {
...
@Ensures({
"result >= 0",
"result <= 23"
})
int getHour();
@Requires({
"h >= 0",
"h <= 23"
})
@Ensures("getHour() == h")
void setHour(int h);
...
}
未使用contracts的可能長這樣
interface Time {
...
/** 取得時間的小時
* @return 小時, 請確認取得的值是0到23的整數.
*/
int getHour();
/** 設定時間的小時
* @param h 一個0到23的正整數. 和getHour()取得的值一樣.
*/
void setHour(int h);
...
}
差別在哪? 我可以用contracts透過annotation將商業邏輯加在interface中, 如果程式設計師犯了錯, 就可以很精確的揪出錯誤在哪了. 請注意, 雖然這程式碼看起來可以檢查輸入輸出是否正確, 但是contracts不是用來檢查輸入輸出是否正確, 而是用來檢驗程式設計的錯誤, 這是很重要的觀念.
20110211T1340:
再用另一個例子:
/**
* @param left a sorted list of elements
* @param right a sorted list of elements
* @return the contents of the two lists, merged, sorted
*/
List merge(List left, List right);
以往, 在comment的地方"寫得"很清楚, 但是呢, 人有惰性, 每個人的實力都不同, 傳進來的和回傳的都可能疏忽. 用了contracts, 改成這樣:
@Requires({
"Collections.isSorted(left)",
"Collections.isSorted(right)"
})
@Ensures({
"Collections.containsSame(result, Lists.concatenate(left, right))",
"Collections.isSorted(result)"
})
List merge(List left, List right);
不僅清楚的說明了left, right和回傳值的條件, 也實際以程式控制了這些條件, 看起來像是unit testing的一種功能, 但是unit testing是在測methods, 而contracts是在interface的, unit testing要對所有的implementations去測, contracts則在interface就管控了品質, 再者, contracts是透過javaagent參數調用的, 關掉javaagent就不會有任何影響.
interface Time {
...
@Ensures({ "result >= 0", "result <= 23" })
int getHour();
@Requires({ "h >=0", "h <= 23" })
@Ensures("getHour()==h")
void setHour(int h)
...
}
但是contracts不是用來檢查輸入輸出是否正確,
而是用來檢驗程式設計的錯誤, 這是很重要的觀念.
再不同應用領域裡可能會有 Over 23:59 的跨日 現場工時
是計算因子
還是日曆日期時間
EMS - ShopFoldControl 大夜班 23:00 - 31:59 (大夜班是最後一班不是第一班)
一天的開始是 08:00
因此在不同應用空間裡用不同驗證條件::
你剛好就是把contracts看成用來做"資料檢驗"的, 排班問題是應用層級, 時間元件是元件層級, 我寫過複雜的時間元件來解決排班問題, 當時的困擾就是無法將資料檢驗抽離出, 現在有了contracts的元件, 就可以將元件和應用程式的耦合性降低.
看了一下文件,除了Preconditions(Requires)是用OR,其他在繼承時都是用AND邏輯運算來跟parent class/interface定義的條件來做檢查。所以不同的驗證條件,需要在child class寫,但是除了Requires,其他都不能違背parent class/interface定義的條件(這樣永遠通不過驗證)...看起來是這樣?
這些條件寫完,其實跟unit test基本上要做的事情就差不多了(只有基本...)。以前是曾經找過類似的方案,希望用他產出基本的unit test code。
fillano提到:
以前是曾經找過類似的方案,希望用他產出基本的unit test code。
因為unit test寫起來很繁瑣, 重複的動作一再的做, 因此, 我認為在interface上有了contracts, 不是可以減輕一些unit testing的負擔嗎? 因為當跑unit testing時, JVM就會檢驗contracts了, 不用在unit testing中寫囉.
是阿...當初會找這些東西,也是希望節省繁瑣的東西,只是方向不太一樣。當時的想法是,基本規則做好,產出unit test skeleton code,然後再寫額外的unit test。不過這樣做會有一些維護問題...用cofoja做看起來可以解決維護問題,是比較好的方法。
cofoja 應該是從 Bertrand Meyer 所創的 "Design by Contract" 中學來的,很有趣,謝謝你的分享。
關於 Design by Contract 技術,可以參考 Bertrand Meyer 寫的 "Object-Oriented Software Construction, 2nd Ed." 一書。(乖乖,這本書超厚,我記得有 1200 多頁!)
<coopermaa2nd.blogspot.com>