コードコントラクト/アサート:重複チェックとは?


10

私はアサート、コントラクト、または私が使用している言語で利用可能なあらゆる種類のチェックを書くことの大ファンです。少し気になることの1つは、重複チェックを処理するための一般的な方法が何なのかがわからないことです。

状況例:最初に次の関数を記述します

void DoSomething( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  //code using obj
}

その後数時間後、最初の関数を呼び出す別の関数を記述します。すべてがまだメモリ内にあるのでDoSomething、nullオブジェクトを既にチェックしていることがわかっているので、コントラクトを複製しないことにします。

void DoSomethingElse( object obj )
{
  //no Requires here: DoSomething will do that already
  DoSomething( obj );
  //code using obj
}

明らかな問題:objがnullでないことを確認するためにDoSomethingElse依存しDoSomethingています。したがってDoSomething、これ以上チェックしないことを決定する必要があります。または、別の関数を使用する場合、objはもうチェックされない可能性があります。結局、私はこの実装を書くことにつながります:

void DoSomethingElse( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  DoSomething( obj );
  //code using obj
}

常に安全で、心配する必要はありません。ただし、状況が大きくなった場合、同じオブジェクトが何度もチェックされる可能性があり、それは複製の一種であり、それはそれほど良くないことです。

このような状況で最も一般的な方法は何ですか?


3
ArgumentBullException?それは新しいものです:)
CVn

笑@私のタイピングスキル...私はそれを編集します。
stijn

回答:


13

個人的には、nullを取得した場合に失敗する関数ではnullをチェックし、nullを取得しない関数ではチェックしません。

したがって、上記の例では、doSomethingElse()がobjを逆参照する必要がない場合、objでnullをチェックしません。

DoSomething()がobjを逆参照する場合、nullをチェックする必要があります。

両方の関数がそれを逆参照する場合、両方ともチェックする必要があります。したがって、DoSomethingElseがobjを逆参照する場合は、nullをチェックする必要がありますが、DoSomethingは別のパスから呼び出される可能性があるため、nullもチェックする必要があります。

このようにして、コードをかなりクリーンなままにして、チェックが正しい場所にあることを保証できます。


1
私は完全に同意します。各メソッドの前提条件は、独自のものである必要があります。DoSomething()前提条件が不要になるように書き直し(この特定のケースでは可能性は低いが、別の状況で発生する可能性がある)、前提条件チェックを削除すると想像してください。前提条件が欠落しているために、一見まったく関連がないように見えるメソッドが壊れています。このような奇妙な失敗を明確にするために、コードを少しだけ重複させて、いつでも数行のコードを保存したいという願望から取りあげます。
CVn

2

すごい!.NETのコードコントラクトについて知ったと思います。コードコントラクトは、平均的なアサーションよりもはるかに進んでいます。静的チェッカーが最も良い例です。これは、Visual Studio Premium以降がインストールされていない場合は使用できない場合がありますが、コードコントラクトを使用する場合は、その背後にある意図を理解することが重要です。

関数にコントラクトを適用すると、文字通りコントラクトになります。その関数は、契約に従って動作することを保証し、契約で定義されたとおりにのみ使用されることが保証されています。

与えられた例では、nullを渡すことができるため、DoSomethingElse()関数はで指定されたコントラクトに準拠していませんDoSomething()。静的チェッカーがこの問題を示します。これを解決するには、同じコントラクトをに追加しDoSomethingElse()ます。

これは、重複があることを意味しますが、2つの関数に機能公開することを選択した場合、この重複が必要になります。これらの関数はプライベートですが、クラスのさまざまな場所から呼び出すこともできるため、特定の呼び出しから引数がnullにならないことを保証する唯一の方法は、コントラクトを複製することです。

これにより、最初に動作を2つの関数に分割する理由を再考する必要があります。1つの場所からしか呼び出されない関数は分割しない方いいと常に思っています(一般的な考えとは異なります)。契約を適用してカプセル化を公開することで、これはさらに明白になります。私の目的のために余分な議論を見つけたようです!ありがとうございました!:)


最後の段落について:実際のコードでは、両方の関数が2つの異なるクラスのメンバーだったため、分割されています。それとは別に、私は次のような状況に何度も遭遇しました。長い関数を記述し、分割しないことにしました。後で、ロジックの一部が他の場所で重複していることがわかったので、とにかくそれを分割します。または、1年後にもう一度読んで読めなくなったので、とにかく分割してください。またはデバッグ時:関数の分割= F10キーを叩くことを少なくします。理由は他にもあるので、個人的には分割することを好みます。
stijn

(1)「ロジックの一部が別の場所に複製されていることを後で把握する」。そのため、単純に関数を分割するよりも、常に「APIに向かって開発する」ことが重要であると私は思います。現在のクラス内だけでなく、常に再利用について考えます。(2)「または1年後にもう一度読んで読めない」関数には名前があるので、これの方がいいですか?私のブログのコメンターが「コード段落」と表現したものを使用するとさらに読みやすくなります。(3)「スプリット機能= F10キーを軽く叩く」 ...理由はわかりません。
スティーブンジュリス

(1)同意済み(2)読みやすさは個人の好みなので、私にとってはあまり議論されていません。(3)20行の関数を通過するには、F10を20回押す必要があります。スプリット関数でこれらの行が10行ある関数を実行するには、F10を11回押すだけでよいという選択肢があります。はい、最初のケースでは、ブレークポイントを配置したり、「カーソルにジャンプ」を選択したりできますが、それでも2番目のケースよりも手間がかかります。
stijn

@stijn:(2)同意済み; p(3)明確化に感謝!
Steven Jeuris
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.