web-dev-qa-db-ja.com

コンストラクタで「新しい」を使用することは常に悪いですか?

コンストラクターで "new"を使用する(単純な値のオブジェクト以外のオブジェクトの場合)と、ユニットテストが不可能になるため、これらのコラボレーターを作成する必要があり、モックできないため、これを読んだことがあります。私はユニットテストの経験があまりないので、最初に学ぶルールを集めようとしています。また、これは使用されている言語に関係なく、一般的に有効なルールですか?

37
Ezoela Vacca

常に例外があり、タイトルの「常に」に問題がありますが、そうですガイドラインは一般的に有効であり、コンストラクターの外部にも適用されます。

コンストラクターでnewを使用すると、SOLID(依存関係の逆転の原則)のDに違反します。単体テストはすべて分離に関するものであるため、コードのテストが難しくなります。具体的なクラスがある場合、クラスを分離することは困難です。参照。

ユニットテストだけではありません。リポジトリが2つの異なるデータベースを同時に指すようにしたい場合はどうなりますか?自分のコンテキストで渡す機能により、異なる場所を指す2つの異なるリポジトリをインスタンス化できます。

コンストラクタでnewを使用しないと、コードがより柔軟になります。これは、オブジェクトの初期化にnew以外の構造を使用する可能性がある言語にも当てはまります。

ただし、明らかに、適切な判断を行う必要があります。 newを使用するのが適切な場合や、使用しない方がよい場合はたくさんありますが、悪影響はありません。どこかの時点で、newを呼び出す必要があります。他の多くのクラスが依存するクラス内でnewを呼び出す場合は、十分に注意してください。

コンストラクターで空のプライベートコレクションを初期化するなどの操作を行うことは問題なく、それを注入することはばかげています。

クラスがそれを参照するほど、その内部からnewを呼び出さないように注意する必要があります。

36
TheCatWhisperer

他の複数のオブジェクトを作成するのではなく、単に新しいインスタンスを初期化するためにコンストラクターを使用することに賛成ですが、ヘルパーオブジェクトは問題なく、何かが内部ヘルパーであるかどうかの判断を使用する必要があります。

クラスがコレクションを表す場合、そのクラスには内部ヘルパー配列、リスト、またはハッシュセットがある場合があります。 newを使用してこれらのヘルパーを作成しますが、これは非常に正常と見なされます。このクラスは、さまざまな内部ヘルパーを使用するためのインジェクションを提供しておらず、そのための理由もありません。この場合、オブジェクトのパブリックメソッドをテストする必要があります。これにより、コレクション内の要素の蓄積、削除、置換が行われる可能性があります。


ある意味では、プログラミング言語のクラス構造は、より高いレベルの抽象化を作成するためのメカニズムであり、問​​題ドメインとプログラミング言語プリミティブの間のギャップを埋めるために、そのような抽象化を作成します。ただし、クラスメカニズムは単なるツールです。これはプログラミング言語によって異なり、一部のドメイン抽象化は、一部の言語では、単にプログラミング言語のレベルで複数のオブジェクトを必要とします。

要約すると、呼び出し側からは単一の抽象化としてまだ見られている一方で、抽象化が単に1つ以上の内部/ヘルパーオブジェクトを必要とするのか、それとも他のオブジェクトが呼び出し元に公開されて作成されるのかを判断する必要があります。依存関係の制御。これは、たとえば、呼び出し元がクラスの使用中にこれらの他のオブジェクトを見つけたときに推奨されます。

50
Erik Eidt

すべての共同編集者が個別に単体テストを行うのに十分興味深いわけではありません。ホスティング/インスタンス化クラスを介して(間接的に)共同でテストできます。これは、特に後でテストを行う場合、各クラス、各パブリックメソッドなどをテストする必要があるという一部の人々の考えと一致しない場合があります。 TDDを使用するときは、この「コラボレーター」をリファクタリングして、テストの最初のプロセスからすでに完全にテストされているクラスを抽出できます。

27
Joppe

私はユニットテストの経験があまりないので、最初に学ぶルールを集めようとしています。

あなたが遭遇したことがない問題の「ルール」を注意深く学ぶようにしてください。 「ルール」または「ベストプラクティス」に遭遇した場合は、このルールが「想定」されている場所の簡単なおもちゃの例を見つけて、その問題を解決しようとすることをお勧めします、「ルール」が言うことを無視します。

この場合、2つまたは3つの単純なクラスと、それらが実装する必要があるいくつかの動作を考え出すことができます。自然に感じられる方法でクラスを実装し、各動作の単体テストを記述します。発生した問題のリストを作成します。一方向に機能するものから始めた場合、戻って後で変更する必要がありました。物事がどのように組み合わさるべきかについて混乱した場合;あなたが定型文を書くことにイライラした場合;等.

次に、「ルール」に従って同じ問題を解決してみます。ここでも、発生した問題のリストを作成します。リストを比較して、ルールに従うときにどの状況が良いか、そうでないかを考えてください。


あなたの実際の質問については、私は ports and adapters アプローチを好む傾向があります。このアプローチでは、「コアロジック」と「サービス」を区別します(これは、純粋な関数と効果的なプロシージャの区別に似ています) 。

コアロジックはすべて、問題の領域に基づいて、アプリケーションの「内部」で計算することです。 UserDocumentOrderInvoiceなどのクラスが含まれる場合があります。コアクラスが他のクラスのnewを呼び出すようにしても問題ありません。コアクラス。「内部」実装の詳細であるため。たとえば、Orderを作成すると、注文内容の詳細を示すInvoiceDocumentも作成される場合があります。これらは実際にテストしたいものなので、テスト中にこれらを模擬する必要はありません。

ポートとアダプターは、コアロジックが外界と相互作用する方法です。これは、DatabaseConfigFileEmailSenderなどのものが存在する場所です。これらはテストを困難にするものなので、これらをコアロジックの外部に作成し、必要に応じて(依存関係の注入またはメソッドの引数として)渡すことをお勧めします。 。)。

このようにして、データベース、ファイル、メールなどを気にすることなく、コアロジック(重要なビジネスロジックが存在し、最もチャーンされるアプリケーション固有の部分)を単体でテストできます。いくつかのサンプル値を渡して、正しい出力値が得られることを確認できます。

ビジネスロジックを気にすることなく、データベースやファイルシステムなどのモックを使用して、ポートとアダプタを個別にテストできます。いくつかのサンプル値を渡して、それらが格納/読み取り/送信/などされていることを確認できます。適切に。

13
Warbo

質問に答えさせてください。ここで私が重要なポイントと考えるものを集めます。簡潔にするために、一部のユーザーを引用します。

例外は常にありますが、はい、このルールは一般的に有効であり、コンストラクターの外部にも適用されます。

コンストラクタでnewを使用すると、[〜#〜] d [〜#〜] in [〜#〜] solid [〜#に違反します〜] (依存関係逆転プリンシパル)。単体テストはすべて分離に関するものであるため、コードのテストが困難になります。具体的な参照がある場合、クラスを分離するのは困難です。

-TheCatWhisperer-

はい、コンストラクター内でnewを使用すると、設計の欠陥(たとえば、密結合)につながることが多く、設計が厳格になります。 テストするのは難しいはい、しかし不可能ではない。ここで機能しているプロパティは、 resilience (変更への耐性)です。1

それにもかかわらず、上記の引用は常に正しいとは限りません。場合によっては、密結合となるクラスが存在する可能性があります。デビッド・アーノはカップルにコメントしました。

もちろん、クラスが不変の値オブジェクト、実装の詳細などの例外があります。どこで密結合されているはずです

-David Arno-

丁度。一部のクラス(たとえば、内部クラス)は、メインクラスの単なる実装の詳細である場合があります。これらはメインクラスと一緒にテストされることを意図しており、必ずしも置き換え可能または拡張可能ではありません。

さらに、 [〜#〜] solid [〜#〜] カルトによってこれらのクラスが抽出される場合、別の良い原則に違反している可能性があります。いわゆる デメテルの法則 。一方、デザインの観点からは、これは非常に重要であると思います。

ですから、いつものように、おそらく答えは depends です。 コンストラクタ内でnewを使用することは悪い習慣になることがあります。しかし、常に体系的ではありません。

したがって、クラスがメインクラスの実装の詳細(ほとんどの場合ではない)かどうかを評価する必要があります。もしそうなら、放っておいてください。そうでない場合は、 Composition Root または IoC Containersによる依存性注入 のような手法を検討してください。


1:SOLIDの主な目的は、コードをテストしやすくすることではありません。コードを変更に対してより寛容にすることです。より柔軟で、結果としてテストが容易になります

注:David Arno、TheWhisperCat、私はあなたが引用したことを気にしないことを望みます。

6
Laiv

簡単な例として、次の疑似コードを考えてみましょう

class Foo {
  private:
     class Bar {}
     class Baz inherits Bar {}
     Bar myBar
  public:
     Foo(bool useBaz) { if (useBaz) myBar = new Baz else myBar = new Bar; }
}

newFooの純粋な実装詳細であり、両方のFoo::BarおよびFoo::BazFooの一部です。Fooの単体テストでは、Fooの一部をモック化しても意味がありません。 Fooの単体テストでは、部品のモックoutsideFooのみをモックします。

3
MSalters