按合同設計 - 瞭解有關OOP的新內容 Marcell Lipp

banq發表於2019-01-13

在我學習電腦科學的過程中,我遇到了所謂的Hoare邏輯。它的主要思想是,對於每個命令/程式,您可以定義前提條件和後置條件。這些是邏輯表示式。在啟動程式之前必須滿足前提條件。主要是檢查所有輸入是否在正確的範圍內等等。它當然也可以是恆定的,這意味著:這個程式沒有特定的前提條件。執行程式後必須滿足後置條件。就像你的前提條件是x> 0一樣,你的程式除了y:= x之外什麼都不做,那麼你的post條件將是:x> 0和x = y。使用一些預定義規則,您可以使用邏輯方法證明您的程式是否正確。在簡單程式的情況下它很容易,但是在程式複雜的情況下它會變得非常複雜,這就是以這種方式驗證程式程式碼真的不典型的原因。無論如何,它是一種靜態程式碼驗證方法。
所以我學會了它,透過了我的考試並忘記了這一點。
多年後,我讀到了所謂的SOLID原則,還有一個名為Liskov替代原則。我的學習有一個閃回:“嗯,我已經在某個地方聽到了它”。該原則如下:“如果S是T的子型別,則程式中型別T的物件可以用型別S的物件替換而不改變該程式的任何所需屬性”。很明顯,但事實上它意味著什麼?它意味著以下內容:
  • 子型別不能強化前提條件。
  • 後置條件不能在子型別中被削弱。
  • 超型別的不變數必須保留在子型別中


嗯,這聽起來像Hoare邏輯,聽起來像是有用的東西。在這一點上,我透過合同閱讀了更多關於Design的內容,我在那裡學到的東西讓我更好地理解了如何在OOP中實現我的程式。

我想向您展示主要概念。

一個類的合同
我們可以談談三種不同型別的合同:不變數,前置條件和後置條件。不變屬於一類,前後條件屬於一種方法。

不變數只不過是對你l類狀態的限制。類的狀態基本上是類變數的值。例如,如果您有一個直角三角形的類,您的類變數可以是:a邊的長度,b邊的長度和c邊的長度。這些變數及其當前值表示您的類的狀態。我們知道規則:a * a + b * b = c * c,如果是直角三角形。這意味著如果我們的類代表直角三角形,那些不滿足這個條件的值都是無效的,它們不代表直角三角形。所以這個邏輯表示式(a * a + b * b = c * c)可以是你的類不變數。在所有情況下都應如此。它將被檢查:在建構函式的末尾,在啟動任何類方法和類方法結束時。接下來是一個很好的做法,讓你的類變數保持私有並透過setter和getter函式訪問它們,這樣你的類就永遠不會得到一個無法滿足不變數的無效狀態。

方法的前提條件是在啟動方法之前檢查的條件。例如,如果您的三角形的拉伸函式具有一個名為factor的引數,則前提條件可以是此引數大於0。

一種方法的後驗證。在函式結束時檢查,它描述了您對方法的期望。如果拉伸函式in可能類似:a = old(a)* factor和b = old(b)* factor和c = old(c)* factor,其中old表示方法開頭的值。在這裡,您需要考慮class的整體狀態也很重要。因此,如果它具有不應該由方法更改的屬性,則它也應該在post條件中指定。例如,如果您的表示還具有顏色屬性,則應使用以下顏色擴充套件帖子條件:color = old(color)。

到目前為止,它很簡單:每個方法都有一個前置條件和一個後置條件,並且該類具有一個不變數,在構造之後以及每個函式的開始和結束時進行檢查。

子類合同
如果你的類有基類,它會如何改變?

您的類保持其所有基類的不變數。因此,在一個基類的情況下,需要始終滿足以下要求:invariant_of_base_class和invariant_of_your_class。

因此,從子類中,您無法打破基類的不變數。

例如,如果直角三角形類是從三角形類派生的,則三角形類的不變數可以是:a + b> = c(兩個短邊的長度之和應至少為最長的長度側)。這種不變數也應該透過直角三角形來實現。

關於前後條件:
對於重寫函式,前提條件不能作為基類中的前提條件加強。因此它可以減少強度(比如(a <10和b <10)只是(a <10))或與基類相同。

對於重寫函式,後置條件不能作為基類中的後置條件被削弱。所以它可以是相同或更多的力量。

這被稱為Liskov替代原則,它是SOLID原則的一部分。

也許它第一次有點複雜,但它沒有說明:你的子類是你的基類的一個特例,所以它應該適用於所有使用基類的輸入,它應該產生相同的結果同樣。

在程式語言中支援合同設計
對程式碼使用合同設計是一種防禦性程式設計方法,如果違反任何合同(不變數,前提條件或後置條件),程式將會出現執行時錯誤。通常情況下,合同檢查僅在程式碼的測試版本中啟用,並在釋出版本中停用。
有一些程式語言,比如Eiffel,它們以本機方式支援合同,但它們現在並不是很受歡迎。對於當今流行的OOP語言(C ++,C#,Java等),您可以自己實現,也不會太複雜或重用已有的庫。對於C ++ Boost.Contract是一個不錯的選擇。對於Java,有幾種開源解決方案,如Oval或Contract for Java。對於C#,您可以使用程式碼合同。對於Python PyContracts。

總結
當然我知道按合同設計並不是一種常用的程式設計方式,我認為這是完全可以的。另一方面,在我看來,這是一種快速簡便的方法來提高程式碼質量,讓自己更多地思考“我的程式碼實際上在做什麼”。它還使您的程式碼更清晰,因為您將仔細檢查您的類的狀態應該是什麼,並且前後條件也記錄了您的函式的規範。
我認為,即使您沒有使用這種方法開發,也可以瞭解這種程式設計方式並考慮一下您的不變數,前後條件以及實現類以獲得更好的程式碼質量。

(banq注:DDD聚合根實體的設計按這種合同設計)
 

相關文章