すべてのコンストラクターでデータの有効性をチェックしますか、それともデータが正しいと想定して、パラメーターに問題がある特定の関数で例外をスローしますか?
9 に答える
コンストラクターも関数です-なぜ区別するのですか?
オブジェクトを作成するということは、すべての整合性チェックが完了したことを意味します。コンストラクターでパラメーターをチェックし、不正な値が検出されたら例外をスローすることは完全に合理的です。
これらすべての中で、デバッグが簡素化されます。プログラムがコンストラクターで例外をスローすると、スタックトレースを監視し、多くの場合、すぐに原因を確認できます。チェックを遅らせると、現在のエラーの原因となった以前のイベントを検出するために、さらに調査を行う必要があります。
最初からすべての不変条件が「満たされている」完全に構築されたオブジェクトを持っている方が常に良いです。ただし、非管理言語でコンストラクターから例外をスローすると、メモリリークが発生する可能性があるため、注意してください。
私は常にコンストラクターで値を強制します。ユーザーがルールに従うのが面倒なら、私は彼らに言わずに黙って強制します。
したがって、107% の値が渡された場合は、100% に設定します。ドキュメントで、それが起こることを明確にしています。
明らかな論理的強制がない場合にのみ、例外をスローします。私はこれを「ドキュメントを読むのがあまりにも怠惰で愚かな人にとって最も驚くべきこと」と呼んでいます。
コンストラクターを投入すると、スタックトレースは、間違った値がどこから来ているかを示す可能性が高くなります。
一般的なケースではsharptoothに同意しますが、一部の関数が有効な状態と無効な状態を持つことができるオブジェクトが存在する場合があります。このような状況では、これらの特定の依存関係を持つ関数へのチェックを延期することをお勧めします。
オブジェクトが常に有効な状態になることを意味する場合に限り、通常はコンストラクターで検証する必要があります。ただし、一部の種類のオブジェクトには関数固有のチェックが必要であり、それでも問題ありません。
私はいつもできるだけ早く失敗するようにしています。したがって、コンストラクターのパラメーターを確実にチェックします。
これは難しい質問です。ここ数年、この質問に関連する習慣を何度か変えてきました。
- コンストラクターで引数をチェックすることは、従来のメソッドの引数をチェックするようなものです...したがって、ここでは区別しません...
- もちろん、メソッドの引数をチェックすると、渡されたパラメーターが正しいことを確認するのに役立ちますが、記述しなければならないコードがさらに多くなり、私の意見では、これは常に報われるとは限りません...特にデリゲートする短いメソッドを記述する場合非常に頻繁に他のメソッドにアクセスすると、コードの 50% がパラメータ チェックを行うだけになると思います...
- さらに、クラスの単体テストを作成するときに、すべてのメソッドに有効なパラメーターを渡す必要がないことを非常によく考慮してください...現在のテストケースにとって重要なパラメーターのみを渡すことが理にかなっていることがよくあります。 、そのため、チェックを行うと単体テストの作成が遅くなります...
これは本当に状況によると思います...パラメータが外部システム(ユーザー入力、データベース、Webサービスなど)から来ていることがわかっている場合にのみパラメータチェックを書くことになりました... )。データが自分のシステムから来ている場合、私はほとんどの場合テストを書きません。
理論的には、呼び出しコードは、関数を呼び出す前に、前提条件が満たされていることを常に確認する必要があります。コンストラクターについても同様です。
実際には、プログラマーは怠け者であり、確実に前提条件を確認するのが最善です。そこで便利なのがアサートです。
例。私の非中括弧構文を許してください:
// precondition b<>0
function divide(a,b:double):double;
begin
assert(b<>0); // in case of a programming error.
result := a / b;
end;
// calling code should be:
if foo<>0 then
bar := divide(1,0)
else
// do whatever you need to do when foo equals 0
または、いつでも前提条件を変更できます。コンストラクターの場合、これはあまり便利ではありません。
// no preconditions.. still need to check the result
function divide(a,b:double; out hasResult:boolean):double;
begin
hasResult := b<>0;
if not hasResult then
Exit;
result := a / b;
end;
// calling code:
bar := divide(1,0,result);
if not result then
// do whatever you need to do when the division failed
常にできるだけ早く失敗します。このプラクティスの良い例は、ランタイムによって示されます。 * 配列に無効なインデックスを使用しようとすると、例外が発生します。* キャスティングは、後の段階ではなく、試みたときにすぐに失敗します。
悪いキャストが参照に残っていて、それを使用しようとするたびに例外が発生した場合、災害コードがどのようなものになるか想像してみてください。唯一の賢明なアプローチは、できるだけ早く失敗し、できるだけ早く悪い/無効な状態を回避しようとすることです。