コンストラクタで「new」を使用するのは常に悪いですか?


37

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


9
テストが不可能になるわけではありません。ただし、コードの保守とテストが難しくなります。たとえば、新しい接着剤を読んでください。
デビッドアルノ

38
「常に」は常に間違っています。:)ベストプラクティスはありますが、例外がたくさんあります。
ポール

63
これは何語ですか?new異なる言語で異なることを意味します。
user2357112は、

13
この質問は言語に少し依存しますよね?すべての言語にnewキーワードがあるわけではありません。どの言語について質問していますか?
ブライアンオークリー

7
愚かなルール。「新しい」キーワードとはほとんど関係がない場合に依存性注入を使用しない。「依存関係の反転を壊すために使用する場合、新しいものを使用することは問題です」と言う代わりに、「依存関係の反転を壊さないでください」と言うだけです。
マット

回答:


36

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

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

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

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

ただし、明確に判断する必要があります。を使用しても問題ない場合newや、使用しないほうが良い場合がたくさんありますが、マイナスの影響はありません。どこかの時点で、new呼ばれる必要があります。new他の多くのクラスが依存しているクラス内での呼び出しには注意してください。

コンストラクターで空のプライベートコレクションを初期化するなどのことは問題ありませんが、それをインジェクトするのはばかげています。

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


12
本当にこのルールには価値がありません。コード内の密結合を回避する方法に関する規則とガイドラインがあります。このルールはまったく同じ問題に対処しているように見えますが、非常に制限的でありながら付加価値を与えていない点が異なります。だからポイントは何ですか?head = null何らかの方法でコードを改善することから生じる特殊なケースをすべて処理するのではなく、LinkedList実装のダミーヘッダーオブジェクトを作成するのはなぜですか?含まれるコレクションをnullのままにして、必要に応じて作成する方が、コンストラクタで行うよりも優れているのはなぜですか?
Voo

22
ポイントがありません。はい、永続モジュールは絶対に、高レベルのモジュールに依存すべきではありません。しかし、「新しいものを決して使用しない」とは、「いくつかのものを注入する必要があり、直接結合しない」という意味ではありません。アジャイルソフトウェア開発の信頼できるコピーを取ると、マーティンは一般に単一クラスではなくモジュールについて話していることがわかります。「高レベルモジュールはアプリケーションの高レベルポリシーを処理します。それら。"
Voo

11
したがって、ポイントは、モジュール間、特に異なる抽象化レベルのモジュール間の依存関係を回避する必要があるということです。しかし、モジュールは単一のクラス以上のものになる可能性があり、同じ抽象化レイヤーの密結合コード間にインターフェースを構築するだけのポイントはありません。SOLID原則に準拠した適切に設計されたアプリケーションでは、すべてのクラスがインターフェースを実装する必要はなく、すべてのコンストラクターが常にパラメーターとしてインターフェースのみを取る必要はありません。
Voo

18
流行語のスープが多く、実際的な考慮事項についてあまり議論していないので、私は投票しました。2つのDBのレポについて何かがありますが、正直なところ、それは現実の例ではありません。
jpmc26

5
@ jpmc26、BJoBnhこの答えを容認するかどうかに関係なく、その点は非常に明確に表現されています。「依存関係の反転プリンシパル」、「具体的な参照」、または「オブジェクトの初期化」が単なる流行語であると考える場合、実際にはそれらの意味がわかりません。それは答えのせいではありません。
R.シュミッツ

50

他の複数のオブジェクトを作成するのではなく、単に新しいインスタンスを初期化するためにコンストラクターを使用することに賛成していますが、ヘルパーオブジェクトは大丈夫です。

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


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

要約すると、抽象化は単に1つ以上の内部/ヘルパーオブジェクトを必要とするが、呼び出し元からはまだ単一の抽象化として認識されているのか、または他のオブジェクトは依存関係の制御。これは、たとえば、呼び出し元がクラスを使用してこれらの他のオブジェクトを見たときに提案されます。


4
+1。これはまさに正しいです。クラスの意図された動作を特定する必要があり、それにより、どの実装の詳細を公開する必要があるか、どの実装の詳細を公開しないかが決まります。クラスの「責任」が何であるかを決定する際にプレイしなければならない、ある種の微妙な直観ゲームもあります。
jpmc26

27

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


14
Not all collaborators are interesting enough to unit-test separately物語の終わり:-)、このケースは可能であり、誰も言及する勇気はありません @Joppe答えを少し詳しく説明することをお勧めします。たとえば、単なる実装の詳細(置換に適さない)であるクラスの例をいくつか追加し、必要に応じて後者を抽出する方法を追加できます。
ライヴ

@Laivドメインモデルは通常、具体的で非抽象的であり、ネストされたオブジェクトをそこに注入することはありません。ロジックを持たない単純な値オブジェクト/複雑なオブジェクトも候補です。
ジョッペ

4
+1、絶対に。言語はあなたがそのように設定されている場合は持って呼び出すためにnew File()何もファイル関連行うには、その呼び出しを禁止するには意味がありません。あなたは何をするつもりFileですか、stdlibのモジュールに対して回帰テストを書きますか?ありそうにない。一方、呼び出し元new自己考案クラスは、より疑わしいです。
キリアンフォス

7
@KilianFoth:ただし、を直接呼び出すものはすべて単体テストしnew File()ます。
フォシ

1
それは当て推量です。意味をなさないケース、役に立たないケース、意味を持ち有用であるケースを見つけることができます。それはニーズと好みの問題です。
ライヴ

13

ユニットテストの経験があまりないため、最初に学習するいくつかのルールを収集しようとしています。

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

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

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


あなたの実際の質問に関しては、「コアロジック」と「サービス」を区別するポートとアダプターのアプローチを好む傾向があります(これは純粋な関数と効果的な手順を区別することに似ています)。

コアロジックとは、問題の領域に基づいて、アプリケーションの「内部」で計算することです。それはのようなクラスが含まれている場合がありますUserDocumentOrderInvoiceコアクラスを呼び出す持っている、などのそれの罰金をnew彼らだ「内部」実装の詳細以来、他のコアクラスのために。たとえば、をOrder作成するInvoiceと、Documentを作成し、注文したものの詳細を作成することもできます。これらはテストしたい実際のものなので、テスト中にこれらをモックする必要はありません!

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

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

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


6

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

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

コンストラクタで新しい使用する違反DSOLID(依存反転プリンシパル)。単体テストはすべて分離に関するものであるため、コードのテストが難しくなります。具体的な参照がある場合、クラスを分離するのは困難です。

-TheCatWhisperer-

はい、new内部コンストラクターを使用すると、多くの場合、設計の欠陥(たとえば、密結合)が発生し、設計が硬直します。はい、テストするのは難しいですが、不可能ではありません。ここでの役割は、弾力性(変化に対する耐性)1です。

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

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

-デビッドアルノ-

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

さらに、SOLIDカルトがこれらのクラスを抽出させる場合、別の良い原則に違反している可能性があります。いわゆるデメテルの法則。一方で、これは設計の観点から非常に重要だと思います。

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

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


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

注: TheWhisperCat、David Arno、私が引用したことを気にしないでください。


3

簡単な例として、次の擬似コードを考えます

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

ためnewの純粋な実装の詳細でありFoo、そして両方Foo::BarFoo::Bazの一部であるFoo、のときにユニットテストFoo存在は、モック部分にはポイントがありませんFoo。単体テストの場合は、外部 の部品のみをモックしFooますFoo


-3

はい、アプリケーションのルートクラスで「新規」を使用するのはコードのにおいです。これは、特定の実装を使用してクラスをロックすることを意味し、別のクラスを置き換えることはできません。依存関係をコンストラクターに注入することを常に選択します。そうすることで、テスト中にモックされた依存関係を簡単に注入できるだけでなく、必要に応じて異なる実装をすばやく置き換えることができるため、アプリケーションの柔軟性が大幅に向上します。

EDIT:downvotersについて-ここでの可能なコードのにおいとして「新しい」低迷ソフトウェア開発帳へのリンクだ:https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 #v = onepage&q = new%20keyword%20code%20smell&f = false


15
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.なぜこれが問題なのですか?依存関係ツリーにないすべての単一の依存性は、交換にオープンする必要があります
LAIV

4
@Paul:デフォルトの実装を使用するということは、デフォルトとして指定された具象クラスへの緊密な参照を取得することを意味します。ただし、それはいわゆる「コード臭」にはなりません。
ロバートハーヴェイ

8
@EzoelaVacca:どんな文脈でも「コードの匂い」という言葉を使うことには注意が必要です。「共和党員」や「民主党員」と言うようなものです。それらの言葉はどういう意味ですか?そのようなラベルを付けるとすぐに、実際の問題について考えるのをやめ、学習が停止します。
ロバートハーヴェイ

4
「より柔軟」は自動的に「より良い」ものではありません。
-whatsisname

4
@EzoelaVacca:newキーワードの使用は悪い習慣ではありません。重要なのはツールの使い方です。たとえば、ボールピーンハンマーで十分な場合、ハンマーを使用しません。
ロバートハーヴェイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.