Design by Contract(1)

http://blog.donews.com/maverick/archive/2006/04/22/841290.aspx

 

      Design by Contract是Bertrand Meyer總結的一項設計技巧,也是Meyer發明的Eiffel語言的主要特點。不過,這條原則的作用範圍並不侷限於Eiffel,而是所有的程序設計語言。

      Design by Contract的核心是斷言(assertion)。所謂“斷言”,是指永遠爲真的布爾型語句,如果不爲真,則程序必然存在錯誤。通常情況下,檢查斷言的時機,應該侷限於調試(debug)階段,而不是代碼的實際執行階段。實際上,完成的程序永遠不應期望斷言會被檢查。

     Design by Contract使用了三類斷言:後繼條件(post-conditions),前提條件(pre-conditions),以及不變量(invariants)。前驅條件與後繼條件都是針對操作(operation)而言的。所謂“後繼條件”, 是指操作執行完之後的情況。舉例來說,如果我們定義對某個數的“求平方根”操作,則該操作的後繼條件爲:input = result * result,這裏的result是輸出結果,而input是輸入的數值。在描述“做什麼”(what we do)而不涉及“怎樣做”(how we do it)——換言之,將接口和實現分離開來——時,後繼條件是非常有用的方法。

所謂“前提條件”,是指在執行操作之前,期望具備的環境。對“求平方根”操作來說,前提條件可以定義爲input >= 0。根據該前提條件,對某個負數執行“求平方根”操作本身就是錯誤的,而且結果無可預料。

      初看起來,這一切顯得亂糟糟的,因爲我們必須設置一些檢查,來保證“平方根”操作能夠被正確執行。然而,重要的問題在於,哪一方負責進行這種檢查。

      根 據前提條件來判斷,檢查的義務無疑落在調用方(caller)。如果責任劃分不明確,則我們或者需要設置太多的檢查——每一方都要檢查,或者需要太少的檢 查——每一方都期望對方進行檢查。檢查太多是很糟糕的,因爲它會造成大量重複的代碼,增加系統的複雜程度。明確哪一方應該進行檢查,能夠避免這種問題。調 用方或許會忘記進行檢查,但我們可以通過調試(debugging)和測試(testing)來降低這種風險。

      基於以上對前提條件和後繼條件的定義,我們可以得到關於術語“異常”(exception)的嚴格定義。如果在滿足前提條件的情況下調用某操作,不能滿足後繼條件,這種情況即稱爲異常。

      所謂“不變量”(invariant), 使關於類(class)的斷言。例如,某個帳戶類(Account class)可能有不變量表示balance == sun(entries.amount())(也就是說,餘額等於所有賬目記錄的總和)。對於該類的所有實例來說,不變量應該恆爲真(always true)。這裏說的“恆”,是指“無論是否能對該對象調用某種操作”。

      從本質上說,這意味着,不變量可以應用於特定類暴露的所有公開操作的前提條件與後繼條件。在方法的執行過程中,不變量可能爲假,但是,在其他任何對象能夠與被調用方進行交互的時刻,不變量斷言必須恢復爲真。

      在 繼承關係中,斷言扮演着獨特的角色。繼承的風險之一在於,開發人員爲子類重新定義的行爲,可能會違背父類的行爲。斷言減少了這種風險。對某個類來說,其不 變量和後繼條件必須能夠應用於所有的子類。子類可以加強這兩類斷言(也就是說,增加更多的限制——譯者注),而不能削弱它們。而前提條件則只能削弱,而不 能增強。

      乍看起來,這顯得有些古怪,但對於動態綁定(dynamic binding)來說,這是極其重要的。開發人員必須時刻記得,把子類對象作爲父類的實例來對待。如果子類對象增強了前提條件,那麼調用其父類的方法時,就可能出現錯誤。

 

 

-------------------------------------------------

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