これは私が取っているJavaクラス用です。この本は事前条件と事後条件について言及していますが、それらをコーディングする方法の例は示していません。アサートについての話が続きますが、私はそれをダウンしていますが、私が行っている課題では、前提条件を挿入し、アサートで前提条件をテストすることを具体的に述べています。
どんな助けでも素晴らしいでしょう。
これは私が取っているJavaクラス用です。この本は事前条件と事後条件について言及していますが、それらをコーディングする方法の例は示していません。アサートについての話が続きますが、私はそれをダウンしていますが、私が行っている課題では、前提条件を挿入し、アサートで前提条件をテストすることを具体的に述べています。
どんな助けでも素晴らしいでしょう。
ここに示すいくつかの例では、パラメーターの検証として前提条件を表現し、アサーションでそれらを実行します。非プライベート メソッドは、その呼び出し元がその実装の範囲外にあるため、常にパラメーターの検証を実行する必要があります。
オブジェクト指向システムのコンテキストでは、パラメーターの検証は前提条件を構成しないと主張する傾向がありますが、このアプローチの例はグーグルの世界でたくさん見られます。
契約に関する私の理解は [BINDER1999] から始まりました。テスト対象のオブジェクトの状態に関して、不変、事前条件、および事後条件を定義しました。ブール述語として表現されます。この処理では、メソッドが状態間の遷移を表すクラスによってカプセル化された状態空間がどのように管理されるかを考慮します。
パラメータと戻り値に関する事前条件と事後条件の議論は、状態空間に関する議論よりもはるかに簡単に伝えることができます! ですから、この見方がより一般的である理由がわかります。
要約すると、議論中の契約には 3 つのタイプがあります。
オーバーロードされたメソッドが意味的に同等でなければならないという (賢明な) アプローチを採用する場合、クラス内の特定のメソッドのオーバーロードの事前条件と事後条件は同じです。
インタータンスとオーバーライドされたメソッドが考慮される場合、コントラクト主導の設計はリスコフ置換原則に従わなければなりません。これにより、次のルールが生成されます。
もちろん、事前条件または事後条件をテストするときはいつでも、テスト対象クラスの不変条件もテストする必要があることを忘れないでください。
Java では、コントラクトは保護されたブール述語として記述できます。例えば:
// class invariant predicate
protected bool _invariant_ ()
{
bool result = super._invariant_ (); // if derived
bool local = ... // invariant condition within this implementation
return result & local;
}
protected bool foo_pre_ ()
{
bool result = super.foo_pre_ (); // if foo() overridden
bool local = ... // pre-condition for foo() within this implementation
return result || local;
}
protected bool foo_post_ ()
{
bool result = super.foo_post_ (); // if foo() is overridden
bool local = ... // post-condition for foo() with this implementation
return result && local;
}
public Result foo (Parameters... p)
{
boolean success = false;
assert (_invariant_ () && foo_pre_ ()); // pre-condition check under assertion
try
{
Result result = foo_impl_ (p); // optionally wrap a private implementation function
success = true;
return result;
}
finally
{
// post-condition check on success, or pre-condition on exception
assert (_invariant_ () && (success ? foo_post_ () : foo_pre_ ());
}
}
private Result foo_impl_ (Parameters... p)
{
... // parameter validation as part of normal code
}
不変述語を前条件述語または事後条件述語にロールしないでください。これにより、派生クラスの各テスト ポイントで不変条件が複数回呼び出されることになります。
このアプローチでは、メソッド アンダー テストのラッパーを使用します。その実装は現在プライベート実装メソッドにあり、実装の本体はコントラクト アサーションの影響を受けません。ラッパーは例外的な動作も処理します。この場合、実装が例外をスローすると、例外セーフな実装で期待されるように、前提条件が再度チェックされます。
上記の例で、'foo_impl_()' が例外をスローし、'finally' ブロック内の後続の前提条件アサーションも失敗した場合、'foo_impl_()' からの元の例外は失われることに注意してください。アサーションの失敗。
以上、勝手に書いたので間違いがあるかもしれませんがご了承ください。
参照:
2014-05-19 更新
入力と出力のコントラクトに関して、基本に戻りました。
[BINDER1999] に基づく上記の議論では、テスト対象オブジェクトの状態空間の観点から契約を検討しました。強力にカプセル化された状態空間としてクラスをモデル化することは、スケーラブルな方法でソフトウェアを構築するための基本ですが、それは別のトピックです...
継承を検討する際に、リスコフ置換原則 (LSP) 1がどのように適用されるか (および必要とされるか) を考慮すると、次のようになります。
派生クラスのオーバーライドされたメソッドは、基本クラスの同じメソッドに置き換え可能でなければなりません。
置換可能であるためには、派生クラスのメソッドは、基本クラスのメソッドよりも入力パラメーターを制限してはなりません。そうしないと、基本クラスのメソッドが成功したところで失敗し、LSP 1が壊れます。
同様に、出力値と戻り値の型 (メソッド シグネチャの一部ではない場合) は、基本クラスのメソッドによって生成されたものに置き換え可能でなければなりません。少なくとも出力値を制限する必要があります。そうしないと、これも LSP を壊してしまいます。1 . これは戻り値の型にも適用されることに注意してください。そこから、共変の戻り値の型に関する規則を導き出すことができます。
したがって、オーバーライドされたメソッドの入力値と出力値のコントラクトは、それぞれ継承および事前条件と事後条件での組み合わせに対して同じ規則に従います。これらのルールを効果的に実装するには、適用されるメソッドとは別に実装する必要があります。
protected bool foo_input_ (Parameters... p)
{
bool result = super.foo_input_ (p); // if foo() overridden
bool local = ... // input-condition for foo() within this implementation
return result || local;
}
protected bool foo_output_ (Return r, Parameters... p)
{
bool result = super.foo_output_ (r, p); // if foo() is overridden
bool local = ... // output-condition for foo() with this implementation
return result && local;
}
これらはそれぞれ および とほぼ同じでfoo_pre_()
ありfoo_post_()
、これらのコントラクトと同じテスト ポイントでテスト ハーネスで呼び出す必要があることに注意してください。
事前条件と事後条件は、メソッド ファミリに対して定義されます。同じ条件が、メソッドのオーバーロードされたすべてのバリアントに適用されます。入力および出力コントラクトは、特定のメソッド シグネチャに適用されます。ただし、これらを安全かつ予測どおりに使用するには、言語と実装の署名検索規則を理解する必要があります ( C++を参照using
)。
上記では、Parameters... p
パラメーターの型と名前の任意のセットの省略形として式を使用していることに注意してください。可変長メソッドを意味するものではありません!
Eiffel のような言語は、言語の基本部分として「前提条件」と「事後条件」をサポートしています。
「オブジェクトコンストラクター」の全体的な目的は、まさに「クラス不変条件」を確立することであるという説得力のある議論をすることができます。
しかし、Java の場合 (C++ 以降のほぼすべてのオブジェクト指向言語と同様)、ほとんどの場合、それを偽造する必要があります。
以下は、Java の「アサート」の活用に関する優れたテクニカル ノートです。
Javaで前提条件と事後条件の手法を適用するには、実行時にアサーションを定義して実行する必要があります。実際には、コード内でランタイムチェックを定義できます。
public boolean isInEditMode() {
...
}
/**
* Sets a new text.
* Precondition: isEditMode()
* Precondition: text != null
* Postcondition: getText() == text
* @param name
*/
public void setText(String text) {
assert isInEditMode() : "Precondition: isInEditMode()";
assert text != null : "Precondition: text != null";
this.text = text;
assert getText() == text : "Postcondition: getText() == text";
}
/**
* Delivers the text.
* Precondition: isEditMode()
* Postcondition: result != null
* @return
*/
public String getText() {
assert isInEditMode() : "Precondition: isInEditMode()";
String result = text;
assert result != null : "Postcondition: result != null";
return result;
}
上記のコードの詳細については、こちらをご覧ください
assert
前提条件をコーディングするために使用するだけです。例えば:
...
private double n = 0;
private double sum = 0;
...
public double mean(){
assert n > 0;
return sum/n;
}
...
前提条件は、プログラムの実行が正しく続行される場合に、プログラムの実行の特定の時点で保持する必要がある条件です。たとえば、ステートメント「x = A[i];」A が null でないこと、および 0 <= i < A.length という 2 つの前提条件があります。これらの前提条件のいずれかに違反すると、ステートメントの実行でエラーが生成されます。
また、サブルーチンの前提条件は、サブルーチンが正しく機能するために、サブルーチンが呼び出されたときに真でなければならない条件です。
詳細はこちら
以下に例を示します: preconditions-postconditions-invariants