本文章是由機器翻譯。

切削刃

程式碼合約:繼承和里氏原則 (Liskov Principle)

Dino 埃斯波西托

就像現實生活合同軟體合同將您綁定到附加約束和成本你東西的時間。當站在一份合同時,可能要確保你不會職權。當它來到軟體合同 — — 包括 microsoft 的代碼的合同。NET 框架 — — 幾乎每個開發人員將最終體現的周圍類合同計算成本的疑慮。合同是否適合您的軟體,不管的版本的類型嗎?或者是合同而大多是應該脫掉零售代碼的調試援助嗎?

埃菲爾,介紹軟體合同的第一語言有本機語言關鍵字定義的先決條件,應該和不變數。因此,在埃菲爾,合同是語言的一部分。在原始程式碼中使用的類,如果合同將成為代碼的一個組成部分。

中。淨,雖然合約是框架的一部分,並不屬於受支援的語言。這意味著運行時檢查可以啟用或禁用在將。特別是,在。您就可以在每個生成配置基礎上決定有關合同的網。在 Java 中,東西都幾乎相同。使用外部框架,或者將合同代碼添加到要編譯的源或問周圍框架,相應地修改位元組碼的工具。

在本文中,我將討論在代碼合同證明特別有用的駕駛您對軟體的總體設計的高品質的幾個方案。

代碼合同是什麼

軟體發展人員的常綠的最佳實踐寫仔細檢查他們收到的任何輸入的參數的方法。如果輸入的參數不匹配方法的期望,則引發異常。這種稱為如果再拋模式。合同的前提條件,與此相同的代碼看起來更好、 更緊湊。更有趣的是,它還會讀取更好,因為前提讓你清楚只是需要的而不是測試針對什麼不是很理想。所以,乍一看,軟體合同只是看起來好寫方法,以防止類方法中的異常。嗯,有它比剛才更多。

你認為合同的每個方法的簡單的事實表明你現在在想更多關於這些方法的作用。最後,設計獲取 terser 和 terser。與合同也代表了有價值的文檔,特別是重構目的形式。

代碼的合同,不過,不限於先決條件,即使先決條件是最容易的部份的軟體合同撿起。前提條件,應該和不變數組合 — — 在整個代碼中的廣泛應用 — — 為您提供了決定性的優勢,並帶來了一些高品質的代碼。

斷言 vs。代碼合同 vs。測試

代碼合同不完全像斷言和調試的其他文書。雖然合同可以説明您跟蹤的 bug,他們不要更換好的調試器或全熟組的單元測試。斷言,像代碼合同說明必須在某一時刻驗證程式的執行過程中的一個條件。

失敗的斷言是一種症狀,有什麼地方不對勁。斷言,但是,不能告訴你它失敗的原因和問題的來源。代碼合同的失敗,另一方面,告訴你很多。它共用有關一種失敗的詳細資訊。因此,例如,您可以瞭解是否引發異常,因為給定的方法收到不能接受的值、 計算預期的返回值中失敗或包含無效的狀態。而斷言告訴您只對檢測到的不良症狀,則代碼合同可以顯示寶貴的資訊應如何使用該方法。此資訊可能最終説明您瞭解什麼有固定為停止違反給定的斷言。

軟體合同與單元測試如何相關的?很明顯,一個並不排除其他和兩個功能是種正交。測試工具是一個外部程式,通過應用選定的類和方法,看他們的行為方式輸入固定的工作。合同是要喊出時有什麼不舒服, 的類方法。要測試的合同,但是,您必須運行該代碼。

單元測試是一個偉大的工具,趕上回歸後重構過程很深。合同也許是更多比測試方法的預期的行為的文檔資訊。若要獲取設計值的測試,你必須練習測試驅動的開發 (TDD)。合約可能是比 TDD 文檔和設計方法的簡單工具。

合同向代碼中添加額外的資訊,並把它留給你來決定是否該資訊應使它已部署的二進位檔案。單元測試包括外部的專案,可以估計代碼如何做的。是否您編譯合同資訊或不,有事先明確合同資訊有助於為文檔和設計的援助。

代碼合同和輸入資料

合同請參閱總是在正常執行流的程式適用的條件。這似乎表明您可能要使用合同的理想的地方是只服從輸入嚴格控制由開發人員的內部圖書館。直接暴露對使用者輸入的類不一定是合同的好地方。如果您對未篩選的輸入資料設置的先決條件,合同可能會失敗並引發異常。但這真的是你想要什麼?大多數情況下,您要柔和或有禮貌的消息返回給使用者。你不想異常和不想拋出,然後陷阱的例外只是正常恢復。

中。網、 代碼合同屬於庫,可能是好的補充 (以及在某些情況下,替換) 資料注釋。資料注釋很大程度上的對 UI,因為在 Silverlight 和 ASP。必須瞭解這些批註和調整代碼或 HTML 元件的網路輸出。域層,不過,您經常需要不僅僅是屬性,和代碼合同是理想的替代品。我不說你不能得到相同的功能,你可以用代碼合同的屬性。我找到的可讀性和表現力,結果卻與代碼合同屬性比任何時候都更好。(順便說一句,這正是為什麼代碼合同團隊通過屬性喜歡純代碼。)

繼承的合同

軟體合同是在幾乎所有平臺,支援他們,可繼承的。NET 框架也不例外。當您從一個現有派生新類時,派生的類拾取行為、 上下文和父母的合同。這似乎是理所當然的事情。繼承的合同不會造成任何不變數和應該的問題。雖然是有點問題的前提條件。讓我們來解決不變數和考慮中的代碼圖 1

圖 1繼承不變數

public class Rectangle
{
  public virtual Int32 Width { get; set; }
  public virtual Int32 Height { get; set; }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width > 0);
    Contract.Invariant(Height > 0);
  }
}
public class Square : Rectangle
{
  public Square()
  {
  }

  public Square(Int32 size)
  {
    Width = size;
    Height = size;
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width == Height);
  }
  ...
}

矩形基類有兩個變數: 寬度和高度都大於零。廣場的派生的類中添加另一個不變的條件: 寬度和高度必須匹配。即使從邏輯的角度來看,這是有意義。廣場就像一個矩形除外,它具有附加約束: 寬度和高度必須始終是相同的。

應該,事情大多是工作方式相同。派生的類重寫的方法,只是添加更多的應該補充了基類的功能,像是一種特殊情況的所有父不會不會的父類別和更多。

先決條件,然後又如何?這正是為什麼總結合同跨類層次結構是一個微妙的操作。類方法邏輯上來說,是一個數學函數相同。獲取一些輸入的值和產生一些輸出。在數學中,生成的函數值的範圍是稱為圓錐曲線 ; 域是可能的輸入值的範圍。通過添加不變數和應該到派生的類的方法,你只是增加方法的圓錐曲線的大小。但通過添加先決條件,您限制方法的域。這是你真的應該擔心的東西嗎?閱讀上。

利斯科夫原則

固是受歡迎的首字母縮寫而產生的五個關鍵原則的軟體設計,包括單一責任、 打開/關閉、 介面隔離和依賴倒置的縮寫。固體中的 L 代表替換原則。您可以瞭解很多有關利斯科夫原則在bit.ly/lKXCxF

簡單地說,利斯科夫原則指出應該始終是安全的在父類別預計的任何地方使用子類。這是為重點的聽起來,我們走出與普通物件定位框的東西。沒有任何的物件導向的語言的編譯器可以確保始終保存的原則的魔法。

它是精確開發商有責任確保它是安全的地方父類別預計使用派生的任何類。通知說,"安全"。平原物件定位使得在父類別預計的地方使用派生的任何類。"有可能"並不相同,"安全"。要實現利斯科夫原則,您需要堅持簡單規則: 域的一種方法不能收縮在子類中。

代碼合同和利斯科夫原則

除了正式和抽象的定義,利斯科夫原則有很多事來做軟體合同,並可以輕鬆地將特定的技術等方面。NET 代碼的合同。關鍵的一點是派生的類不能只是添加的先決條件。在這樣做時,它會限制被接受的一種方法,可能創建運行時失敗的可能值的範圍。

請務必注意違反原則,並不一定導致運行時異常或不良行為。但是,它是一個可能的反例中斷您的代碼的符號。換句話說,違反的影響可能會波及整個代碼庫和邪惡症狀明顯不相關的領域。它使整個基本代碼更加努力,維護和發展 — — 這幾天一樁大罪。想像你在代碼圖 2

圖 2說明利斯科夫原則

public class Rectangle
{
  public Int32 Width { get; private set; }
  public Int32 Height { get; private set; }

  public virtual void SetSize(Int32 width, Int32 height)
  {
    Width = width;
    Height = height;
  }
}
public class Square : Rectangle
{
  public override void SetSize(Int32 width, Int32 height)
  {
    Contract.Requires<ArgumentException>(width == height);
    base.SetSize(width, width);
  }
}

類廣場從矩形繼承,並只添加一個前提條件。 此時,將會失敗 (這表示可能的反例) 下麵的代碼:

private static void Transform(Rectangle rect)
  {
    // Height becomes twice the width
    rect.SetSize(rect.Width, 2*rect.Width);
  }

變換最初編寫方法對付矩形類的實例,它做得很好。 假設有一天您擴展系統,開始將廣場的實例傳遞給相同 (非接觸) 代碼,如下所示:

var square = new Square();
square.SetSize(20, 20);
Transform(square);

取決於方形和矩形之間的關係,變換方法可能會啟動失敗沒有明顯的解釋。

更糟的是,你可能很容易魔如何解決這個問題,但由於類的層次結構,它不可能要掉以輕心的東西。 因此你最終會修復 bug 的一種解決方法,如下所示:

private static void Transform(Rectangle rect)
{
  // Height becomes twice the width
  if (rect is Square)
  {
    // ...
return;
  }
  rect.SetSize(rect.Width, 2*rect.Width);
}

但你的努力,不管的臭名昭著的泥球剛剛開始變大。談好的事情。NET 和 C# 編譯器是如果您使用代碼合同來表達的先決條件,您得到警告從編譯器如果你違反利斯科夫原則 (請參見圖 3)。

圖 3你當你違反利斯科夫原則的警告

最瞭解、 最適用的固體原則

因教過。幾年來的淨設計類,我認為我可以安全地說固體的原則,利斯科夫原則是到目前為止最不理解和應用。很多時候,在軟體系統中檢測到的怪異行為可以跟蹤,利斯科夫原則的違反。好不夠,代碼合同可以説明大大在這一領域,只要你仔細看看編譯器警告。

Dino Esposito  是作者的"程式設計微軟 ASP。NET 4"(微軟出版社,2011年) 和她的"Microsoft。網:架構的企業應用程式 」 (在 [微軟出版品,2008年)。埃斯波西托在義大利是頻繁的揚聲器,在全球範圍內的行業活動。你可以跟隨他在 Twitter 上twitter.com/despos

感謝至下列技術專家檢閱這份文件:Manuel Fahndrich