この記事は機械翻訳されたものです。

最先端

コード コントラクト: 継承とリスコフの原理 (機械翻訳)

Dino Esposito

Dino Esposito実際の契約と同じようにソフトウェアの契約は追加の制約をバインドし、時間の面で何かをコストします。 契約が立っているときは、その用語を破るしないかどうかを確認することがあります。 ときに、ソフトウェアの契約になる — で Microsoft コード コントラクトを含みます。NET Framework-ほぼすべての開発者は最終的に周りクラスのコントラクトを持つ計算コストについての疑問マニフェストが。 コントラクトは適切なビルドの種類に関係なく、ソフトウェアのですか? または契約ではなく製品版のコードを削除する必要がありますデバッグ エイド主か。

エッフェル塔、ソフトウェアの契約を導入する最初の言語は、前提条件、事後条件と不変性を定義するのには、ネイティブ言語のキーワードがあります。 エッフェル塔は、したがって、契約の言語の一部です。 クラスのソース コードで使用する場合は、コントラクトは、コードの一部になります。

で。ネット、契約、フレームワークの一部であり、サポートされている言語に属していないが。 これは、ランタイム チェックを有効にすることができますまたは無効にすることを意味します。 特にで。ネットの契約については、ビルドごとの構成に基づいて決定することとしています。 Java では、ほぼ同じもの。 外部フレームワークを使用していずれかのソースをコンパイルするコントラクトのコードを追加または、バイトコードを適宜変更するフレームワーク ツールを求めます。

この資料では、コード コントラクトへの高品質ソフトウェア設計の全体の運転特に役立ついくつかのシナリオを説明します。

コード コントラクトは

ソフトウェア開発者の常緑のベスト プラクティスは、慎重に彼らが表示されるすべての入力パラメーターをチェックするメソッドを書いています。 入力パラメーター、メソッドの期待と一致しない場合は、例外がスローされます。 これは呼ばれ、場合、投パターン。 前提条件の契約では、この同じコードよりよいよりコンパクトに見えます。 前提条件はどのような望ましくないに対してテストするのではなく必要な状態を明確にすることができますのでさらに興味深いは、それも良く、読み取ります。 だから、最初の光景を見て、ソフトウェア契約だけクラスのメソッドで例外を防ぐために、書き込みよりよいアプローチように。 まあ、ちょうど、それをたくさん以上です。

コントラクトの各メソッドのだと思うという単純な事実は、今すぐ詳細これらのメソッドの役割について考えていることを示します。 最後に、設計に召さないと召さないを取得します。 契約も特に目的をリファクタリングのマニュアルの貴重なフォームを表します。

前提条件ソフトウェアの契約をピックアップするの最も簡単な部分ですが、コード コントラクト、ただしへの前提条件、制限されていません。 前提条件、事後条件と不変の組み合わせ- とそれらの大規模なアプリケーションをコード中で — の決定的な利点を提供し、本当に質の高いコードに 。

アサーション対。 コードの契約対。 テスト

コード コントラクトには、アサーションと他のデバッグの楽器のように完全ではありません。 契約、バグを追跡することができますが、彼らは、良好なデバッガーまたはよく行わの単体テストを置き換えることはありません。 ような検証、コード コントラクトは、特定の時点では、プログラムの実行中に確認が必要条件を示します。

失敗したアサーションが何かどこか間違っている症状です。 アサーション、しかし、それが失敗した理由と、問題が発生したを教えてすることはできません。 多くの失敗、コード コントラクト、他の一方で、説明します。 障害の種類についての詳細を共有します。 だからなど、特定のメソッドが容認できない値を受信、予想される戻り値の計算に失敗したかが無効な状態を保持するために、例外が発生したかどうか学ぶことができます。 アサーションは、検出された悪い症状についてのみ説明に対し、コード コントラクト メソッドの使用方法に関する貴重な情報を表示できます。 この情報は、最終的には、特定のアサーションに違反を停止するために修正するものを理解するのに役立ちます。

どのようにソフトウェアの契約単体テストにかかわりますか。 明らかに、1 つ、他を除外しないし、2 つの機能は並べ替え。 テスト ハーネスは、固定の入力を選択したクラスと動作を確認する方法を適用することによって動作する外部プログラムです。 コントラクト クラスが何か間違っていると大声で叫ぶための方法です。 契約をテストするには、しかし、あなた、コードを実行する必要があります。

単体テストは、リファクタリング プロセス深い後回帰をキャッチする優れたツールです。 コントラクトは、おそらくよりメソッドの正常な動作を文書化するためのテストよりも有益な。 テストの設計値を取得するには、テスト駆動開発 (TDD) を実践することがあります。 コントラクトは、おそらく TDD ドキュメントとデザインの方法により簡単なツールです。

契約情報をコードに追加し、この情報は、展開済みのバイナリをしなければならないかどうかを決定するまでまま。 単体テストでは、やっている方法をコードを推定することができます、外部のプロジェクトが含まれます。 契約情報かどうか、はっきり契約情報事前コンパイルかどうかはドキュメントとデザイン援助として非常にことができます。

コード コントラクトと入力データ

契約は、常に、プログラムの通常の実行フローに当てはまる条件を参照してください。 これは、コントラクトを使用する理想的な場所のみ入力が厳密には開発者によって制御される内部のライブラリであることを示唆するようです。 ユーザーの入力に直接公開されるクラス必ずしも契約のためには良い場所ではないです。 フィルターされていない入力データの前提条件を設定すると、コントラクトは失敗し、例外をスローします。 しかし、これは、本当に何をしたいですか? ほとんどの場合、正常に低下または丁寧なメッセージをユーザーに返すを。 例外をしたくないとは正常に回復すると、例外をトラップする必要はありません。

で。ネット、コード コントラクト ライブラリに属しているし、は良い補完する (と、いくつかの場合は、代わりに) データ注釈があります。 データ注釈は、UI に関連して素晴らしい仕事をしないでください Silverlight と ASP で。これらの注釈を理解し、コードまたは HTML を調整するコンポーネントがあるネットを出力します。 ドメイン層には、多くの場合、属性だけでなく必要がありますありコード コントラクトは理想的な代替。 必ずしも、コード コントラクトを使用することができます属性と同じ機能を得ることができないと述べています。 私を見つける、しかし、読みやすさと表現性、結果の面では、属性よりもコードの契約で優れています。 (ところで、この正確になぜコード コントラクト チーム プレーンなコード属性を好むです。

継承した契約

ソフトウェアの契約はそれらを支えるほとんどのプラットフォームで継承可能とします。NET Framework では、例外ではありません。 新しいクラスを派生すると、派生クラスは、動作、コンテキストと、親の契約を取得します。 自然の成り行きのようです。 コントラクトの継承は、不変性と事後条件の問題を発生しません。 前提条件は、少し問題があるには。 Let's に取り組むための不変性と内のコードを検討図 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);
  }
  ...
}

基本クラスの四角形を 2 つの不変性が: 幅と高さは 0 より大きい。 派生クラスの広場別の不変条件を追加します。 幅と高さに一致する必要があります。 論理的な観点からも、これは意味します。 制約がある正方形、四角形のようです: 幅と高さは常に同じにする必要があります。

事後条件のものはほとんどと同じように動作します。 メソッドをオーバーライドしより多くの事後だけ追加する派生クラスは、基本クラスの機能し、はすべて、親とは、親クラスの特殊なケースのような役割を果たします。

どのような前提条件、その後は? まさになぜ契約をクラス階層全体の合計、微妙な操作です。 論理的には、クラスのメソッドは、数値演算関数と同じです。 いくつかの入力値を get し、いくつかの出力を生成します。 数学では、関数によって生成された値の範囲は、codomain として知られている; ドメインは、入力値の範囲です。 不変性と事後条件、派生クラスのメソッドを追加すると、だけ、メソッドの codomain のサイズを増やします。 しかし、前提条件を追加すると、メソッドのドメインを制限します。 これは何か、本当に心配する必要がありますか? お読みください。

リスコフの原則

固体単一責任、オープン/クローズド、インターフェイスの偏析と依存関係の逆転を含むソフトウェア デザインの 5 つの基本原則の頭文字から検索結果が、人気の頭字語です。 固体の 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);
  }
}

クラスの正方形から四角形を継承し、ちょうど 1 つの前提条件を追加します。 この時点で、(可能な限り反例を表します)、次のコードは失敗します。

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

メソッド変換最初の四角形のクラスのインスタンスに対応する記述し、それは非常によく仕事を。 1 日、システムを拡張する同じ (そのまま) コードには、正方形のインスタンスを開始すると次のようします。

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

正方形と四角形の関係によっては、明白な説明なしに失敗 Transform メソッドを開始できます。

悪いまだ、簡単に、問題を修正する方法スポット可能性がありますが、クラスの階層構造のため、それ何かを軽くすることができません。 あなたを回避して、バグの修正ここに示すように終わりそう。

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

しかし、あなたの努力に関係なく、泥の悪名高いボールは大きく成長し始めています。 についての素晴らしい事。ネットと c# コンパイラがコード コントラクトを使用する前提条件を表現する場合は、リスコフの原則を違反している場合、警告はコンパイラからは、(図 3 を参照)。

The Warning You Get When You’re Violating the Liskov Principle

図 3場合に得られるリスコフの原則に違反している警告

最も理解し、少なくとも適用固体の原則

学んだことを。今年のカップルのためのネット デザインのクラス、私は安全に固体の原則のリスコフの原則まで、少なくとも理解と応用と言うことができると思います。 かなり頻繁に、ソフトウェア システムの検出、奇妙な動作リスコフの原則の違反をダウン追跡できます。 素敵なコンパイラ警告を注意して見る場合だけコード コントラクトは大幅にこの領域では、ことができます。

Dino Esposito は、著者の「プログラミング Microsoft ASP。NET 4"(Microsoft Press、2011年)、『「Microsoft。ネット:Architecting Applications for the Enterprise』(Microsoft Press、2008 年) の共著者でもあります。イタリアでをに基づいて、Esposito 世界の業界イベントで頻繁にスピーカーです。Twitter で彼をフォローすることができますtwitter.com/despos

この記事のレビュー、技術スタッフに感謝:マヌエル ・ Fahndrich