web-dev-qa-db-ja.com

コンストラクターの複雑さ

コンストラクターが実行できる作業について、同僚と話し合っています。私はクラスBを持っていますが、これは内部で別のオブジェクトAを必要とします。オブジェクトAは、クラスBがその仕事を行うために必要な数少ないメンバーの1つです。すべてのパブリックメソッドは内部オブジェクトAに依存しています。オブジェクトAに関する情報はDBに格納されているので、コンストラクターでDBを検索して検証し、取得しようとします。同僚は、コンストラクターがコンストラクターパラメーターをキャプチャする以外に多くの作業を行うべきではないことを指摘しました。コンストラクターへの入力を使用してオブジェクトAが見つからない場合、すべてのパブリックメソッドが失敗するため、インスタンスの作成を許可して後で失敗させるのではなく、コンストラクターの早い段階でスローするほうが実際には良いと主張しました。すべてのクラスがこれを行うわけではありませんが、ファイルのオープンに問題があり、最初のWriteの呼び出しまで検証を遅らせない場合、StreamWriterコンストラクターもスローすることがわかりました。

他の人はどう思いますか?違いがある場合は、C#を使用しています。

読み取り コンストラクターですべてのオブジェクトの作業を行う理由はありますか? DBに移動してオブジェクトAをフェッチすることは、「オブジェクトを使用できるようにするために必要なその他の初期化」の一部であるため、ユーザーが誤った値をコンストラクターに渡した場合、そのパブリックメソッドを使用できません。

コンストラクターは、オブジェクトのフィールドをインスタンス化し、オブジェクトを使用できるようにするために必要なその他の初期化を行う必要があります。これは通常、コンストラクターが小さいことを意味しますが、これがかなりの量の作業になるシナリオもあります。

20
Taein Kim

あなたの質問は2つの完全に別の部分から構成されています:

コンストラクタから例外をスローする必要がありますか、それともメソッドを失敗させる必要がありますか?

これは明らかに Fail-fast 原則の適用です。コンストラクタを失敗させることは、メソッドが失敗する理由を見つける必要がある場合と比較して、デバッグがはるかに簡単です。たとえば、コードの他の一部からすでに作成されているインスタンスを取得し、メソッドを呼び出すときにエラーが発生する場合があります。オブジェクトが間違って作成されていることは明らかですか?番号。

「try/catchで呼び出しをラップする」問題については。例外はexceptionalであることを意味します。一部のコードが例外をスローすることがわかっている場合は、コードをtry/catchでラップせず、例外をスローする可能性のあるコードを実行する前に、そのコードのパラメーターを検証します。例外は、システムが無効な状態にならないようにする方法としてのみ意図されています。一部の入力パラメーターが無効な状態につながる可能性があることがわかっている場合は、それらのパラメーターが発生しないようにします。このようにして、通常はシステムの境界にある例外を論理的に処理できる場所で、try/catchを実行するだけで済みます。

コンストラクターから「DBのようなシステムの他の部分」にアクセスできます。

これは 最小の驚きの原則 に反すると思います。コンストラクタがDBにアクセスすることを期待する人は多くありません。ですから、そうすべきではありません。

22
Euphoric

ああ。

コンストラクターはできる限り少ないことを行うべきです-コンストラクターでの例外動作が扱いにくいという問題があります。適切な動作(継承シナリオを含む)を知っているプログラマーはほとんどいません。また、ユーザーにすべてのインスタンス化を試行/キャッチするよう強制することは...せいぜい苦痛です。

これには2つの部分、inコンストラクターとsingコンストラクターがあります。例外が発生した場合、コンストラクターで多くのことを行うことができないため、コンストラクターでは扱いにくいです。別の型を返すことはできません。 nullを返すことはできません。基本的には、例外を再スローしたり、壊れたオブジェクト(不正なコンストラクタ)を返したり、(最良の場合)内部パーツを正しいデフォルトに置き換えたりできます。 singコンストラクタは厄介です。インスタンス化(およびすべての派生型のインスタンス化)がスローされる可能性があるためです。その後、それらをメンバー初期化子で使用することはできません。ロジックの例外を使用して、「作成は成功しましたか?」を確認します。どこでもtry/catchによって引き起こされる読みやすさ(および脆弱性)を超えています。

コンストラクタが重要なこと、または失敗すると合理的に予想できることを実行している場合は、代わりに静的なCreateメソッド(非パブリックコンストラクタを使用)を検討してください。はるかに使いやすく、デバッグが簡単ですが、成功した場合でも完全に構築されたインスタンスを取得できます。

18
Telastyn

コンストラクター-メソッドの複雑さのバランスは、確かに良いスタイルについての議論の問題です。多くの場合、空のコンストラクタClass() {}が使用され、より複雑なものにはメソッドInit(..) {..}が使用されます。考慮すべき議論の中で:

  • クラスのシリアル化の可能性
  • コンストラクターからInitメソッドを分離すると、同じシーケンスClass x = new Class(); x.Init(..);を部分テストで部分的に繰り返し実行する必要があることがよくあります。あまりよくありませんが、読書には非常にクリアです。時には、私はそれらを1行のコードに入れるだけです。
  • ユニットテストの目的がクラスの初期化テストにすぎない場合は、独自のInitを使用し、中間アサートを使用して1つずつ複雑なアクションを実行することをお勧めします。
0
Pavel Khrapkin