依存性注入の批判と欠点


118

依存性注入(DI)は、よく知られた流行のパターンです。ほとんどのエンジニアは、次のような利点を知っています。

  • 単体テストの分離を可能/簡単にする
  • クラスの依存関係を明示的に定義する
  • 優れた設計の促進(たとえば、単一責任原則(SRP))
  • すばやく切り替え実装を可能にする(DbLogger代わりにConsoleLogger例えば)

DIは良い、有用なパターンであるという業界全体のコンセンサスがあると思います。現時点ではあまり批判はありません。コミュニティで言及されている欠点は、通常は軽微です。それらのいくつか:

  • クラスの数が増えました
  • 不要なインターフェイスの作成

現在、同僚とアーキテクチャ設計について話し合っています。彼はかなり保守的ですが、心を開いています。ITの多くの人々は最新のトレンドをコピーし、利点を繰り返し、一般的にはあまり考えすぎないため、あまり深く分析しないでください。

お願いしたいことは:

  • 実装が1つしかない場合、依存性注入を使用する必要がありますか?
  • 言語/フレームワーク以外の新しいオブジェクトの作成を禁止すべきですか?
  • 特定のクラスの単体テストを計画しない場合、単一の実装をインジェクトするのは悪い考えです(実装が1つしかないため、「空の」インターフェースを作成したくないとしましょう)。

33
パターンとして依存性注入について本当に質問していますか、それともDIフレームワークの使用について質問していますか?これらは本当に明確なものです。問題のどの部分に興味があるのか​​を明確にするか、両方について明示的に尋ねる必要があります。
Frax

10
フレームワークではなくパターンに関する@Frax
Landeeyo

10
依存関係の反転と依存関係の注入を混同しています。前者は設計原則です。後者は、オブジェクトの階層を構築するための手法です(通常は既存のツールで実装されます)。
jpmc26

3
私は、実際のデータベースを使用して、モックオブジェクトを使用せずにテストを記述することがよくあります。多くの場合、本当にうまく機能します。そして、ほとんどの場合、インターフェイスは必要ありません。あなたがUserServiceそのクラスを持っているなら、ロジックの単なるホルダーです。データベース接続が挿入され、ロールバックされるトランザクション内でテストが実行されます。多くの人はこれを悪い習慣と呼ぶでしょうが、これは非常にうまく機能することがわかりました。テストのためだけにコードをゆがめる必要はありません。統合テストのバグ発見力が得られます。
usr

3
DIはほぼ常に良好です。悪い点は、多くの人々がDIを知っていると思うが、彼らが何をしているのかさえ確信せずに奇妙なフレームワークを使用する方法を知っていることです。最近のDIは、Cargo Cult Programmingの影響を強く受けています。
T. Sar

回答:


160

最初に、フレームワークの概念から設計アプローチを分離したいと思います。最も単純で最も基本的なレベルでの依存性注入は、単純に次のとおりです。

親オブジェクトは、子オブジェクトに必要なすべての依存関係を提供します。

それでおしまい。インターフェース、フレームワーク、インジェクションのスタイルなどを必要とするものは何もないことに注意してください。公平を期すために、私はこのパターンについて20年前に最初に学びました。 新しいものではありません。

依存性注入のコンテキストで、親と子という用語をめぐる混乱が2人を超えているため:

  • 親はそれが使用する子オブジェクトをインスタンス化し、設定するオブジェクトです
  • 子供は受動的にインスタンス化されるように設計されたコンポーネントです。つまり、親によって提供される依存関係を使用するように設計されており、独自の依存関係をインスタンス化しません。

依存性注入は、オブジェクト合成のパターンです。

なぜインターフェースなのか?

インターフェイスは契約です。これらは、2つのオブジェクトがどの程度密結合できるかを制限するために存在します。すべての依存関係がインターフェイスを必要とするわけではありませんが、モジュラーコードの作成に役立ちます。

ユニットテストの概念を追加すると、特定のインターフェイスに2つの概念実装があります。アプリケーションで使用する実際のオブジェクトと、オブジェクトに依存するコードのテストに使用するモックまたはスタブオブジェクトです。それだけでも、インターフェースにとって十分な正当化になります。

なぜフレームワークなのか?

子オブジェクトが多数ある場合、基本的に初期化して子オブジェクトへの依存関係を提供するのは困難です。フレームワークには次の利点があります。

  • コンポーネントへの依存関係の自動配線
  • 何らかの設定でコンポーネントを構成する
  • ボイラープレートコードを自動化して、複数の場所に記述されたコードを見る必要がないようにします。

また、次の欠点もあります。

  • 親オブジェクトは「コンテナ」であり、コードには含まれません
  • テストコードで依存関係を直接提供できない場合、テストがより複雑になります。
  • リフレクションや他の多くのトリックを使用してすべての依存関係を解決するため、初期化が遅くなる可能性があります
  • ランタイムデバッグは、特にコンテナがインターフェイスとインターフェイスを実装する実際のコンポーネントの間にプロキシを挿入する場合、より困難になる可能性があります(Springに組み込まれたアスペクト指向プログラミングが思い浮かびます)。コンテナはブラックボックスであり、デバッグプロセスを容易にするというコンセプトで常に構築されているわけではありません。

とはいえ、トレードオフがあります。可動部分が多くなく、DIフレームワークを使用する理由がほとんどない小規模プロジェクトの場合。ただし、特定のコンポーネントがすでに作成されているより複雑なプロジェクトの場合は、フレームワークを正当化できます。

[インターネット上のランダムな記事]はどうですか?

どう?多くの場合、人々は熱心になり、多くの制限を追加し、「1つの真の方法」で物事を行っていない場合はあなたをbeります。真の方法はありません。記事から有用なものを抽出し、同意しないものを無視できるかどうかを確認してください。

要するに、自分で考えてみてください。

「古い頭」での作業

できる限り学びます。70代に取り組んでいる多くの開発者が見つけることは、多くのことについて独断的ではないことを学んだということです。彼らには、正しい結果を生み出す何十年にもわたって取り組んできた方法があります。

私はこれらのいくつかを扱う特権を持っていました、そして彼らは多くの理にかなった残酷に正直なフィードバックを提供することができます。そして、彼らが価値を見出したところで、彼らはレパートリーにそれらのツールを追加します。


6
@ CarlLeth、Springから.netバリアントまでの多くのフレームワークを扱ってきました。Springでは、リフレクション/クラスローダーブラックマジックを使用して、実装をプライベートフィールドに直接注入できます。そのように構築されたコンポーネントをテストする唯一の方法は、コンテナを使用することです。Springにはテスト環境を設定するためのJUnitランナーがありますが、自分で設定するよりも複雑です。ええ、実際の例を挙げました。
ベリンロリチュ

17
トラブルシューティング/メンテナンスの帽子をかぶっていると、フレームワークを介したDIのハードルであることがわかるもう1つの不利な点があります。最悪の場合、依存関係がどのように初期化されて渡されるかを確認するためにコードを実行する必要があります。これは「テスト」のコンテキストで言及しますが、それを実行させようとすることを心がけてください(多くのセットアップが必要になる場合があります)。コードを一見するだけでコードが何をするかを伝える能力を妨げることは悪いことです。
Jeroen Mostert

1
インターフェースはコントラクトではなく、単なるAPIです。契約はセマンティクスを意味します。この答えは、言語固有の用語とJava / C#固有の規則を使用しています。
フランクヒルマン

2
@BerinLoritschあなた自身の答えの主なポイントは、DIの原則!=任意のDIフレームワークです。Springがひどく許せないことを行えるという事実は、一般的なDIフレームワークではなく、Springの欠点です。良いDIフレームワークは、厄介なトリックなしでDIの原則に従うのに役立ちます。
カール・Leth

1
@CarlLeth:すべてのDIフレームワークは、プログラマーが詳細に説明したくないものを削除または自動化するように設計されています。私の知る限りでは、それらはすべてどのように(または知っている能力取り除く場合だけでAとBを見ることで)、クラスAとBが相互作用を-非常に少なくとも、あなたはまた、/ DIのセットアップ/コンフィギュレーションを見てする必要があります慣習。プログラマーにとっては問題ではありません(実際に彼らが望むものです)が、メンテナー/デバッガー(おそらく同じプログラマー)にとっては潜在的な問題です。これは、DIフレームワークが「完璧」であっても、トレードオフです。
イェロエンモステル

88

依存性注入は、ほとんどのパターンと同様に、問題の解決策です。そのため、そもそも問題があるかどうかを尋ねることから始めてください。そうでない場合、パターンを使用するとコードが悪化する可能性が最も高くなります。

依存関係を削減または排除できる場合は、まず検討してください。他のすべての条件が等しい場合、システム内の各コンポーネントに可能な限り少ない依存関係が必要です。そして、依存関係がなくなると、注入するかどうかの問題が無意味になります!

外部サービスから一部のデータをダウンロードして解析し、複雑な分析を実行し、結果をファイルに書き込むモジュールを考えてみましょう。

さて、外部サービスへの依存関係がハードコーディングされている場合、このモジュールの内部処理を単体テストすることは本当に困難になります。したがって、外部サービスとファイルシステムをインターフェイスの依存関係としてインジェクトすることを決定できます。これにより、代わりにモックをインジェクトでき​​、内部ロジックの単体テストが可能になります。

しかし、はるかに優れた解決策は、単に分析を入力/出力から分離することです。副作用のないモジュールに分析を抽出すると、テストがはるかに簡単になります。モックはコード臭であることに注意してください-常に回避できるわけではありませんが、一般的には、モックに頼らずにテストできる方が良いでしょう。そのため、依存関係を排除することで、DIによって軽減されるはずの問題を回避できます。このような設計はSRPにもはるかに準拠していることに注意してください。

DIは、SRPや、関心の分離、高凝集/低結合などのような他の優れた設計原則を必ずしも促進するものではないことを強調したいと思います。逆の効果もあるかもしれません。内部で別のクラスBを使用するクラスAを考えます。BはAによってのみ使用されるため、完全にカプセル化され、実装の詳細と見なすことができます。これを変更してBをAのコンストラクターに注入すると、この実装の詳細が公開され、この依存関係とBの初期化方法に関する知識、Bの有効期間などがシステム内の別の場所に個別に存在する必要がありますAから。漏れている懸念がある、全体的に悪いアーキテクチャになります。

一方、DIが本当に役立ついくつかのケースがあります。たとえば、ロガーのような副作用のあるグローバルサービスの場合。

問題は、パターンではなく、アーキテクチャがツールではなくそれ自体で目標になるときです。「DIを使用する必要がありますか?」馬の前にカートを置くようなものです。「問題がありますか?」と尋ねる必要があります。そして「この問題の最良の解決策は何ですか?」

あなたの質問の一部は、「パターンの要求を満たすために余分なインターフェイスを作成する必要がありますか?」おそらくあなたはすでにこれに対する答えを知っています- 絶対にそうではありません!そうでなければあなたに言っている人はあなたに何かを売ろうとしている-最も可能性が高いコンサルティング時間。インターフェイスは、抽象化を表す場合にのみ値を持ちます。単一のクラスの表面を模倣するインターフェイスは「ヘッダーインターフェイス」と呼ばれ、これは既知のアンチパターンです。


15
私はこれ以上同意できませんでした!また、そのために物事をm笑するということは、実際の実装を実際にテストしていないことを意味します。本番環境でA使用さBれているが、に対してのみテストされMockBている場合、テストでは本番環境で機能するかどうかはわかりません。ドメインモデルの純粋な(副作用のない)コンポーネントが互いに注入およびモックしている場合、その結果は全員の時間の膨大な浪費、肥大化した脆弱なコードベース、および結果のシステムに対する低い信頼性です。同じシステムの任意の部分の間ではなく、システムの境界を模擬します。
ウォーボ

17
@CarlLeth DIがコードを「テスト可能かつ保守可能」にし、それなしでコードを少なくすると思うのはなぜですか?JacquesBは、副作用がテスト容易性/保守性を損なうものであることは正しいです。コードに副作用がない場合、他のコードを何/どこで/いつ/どのように呼び出すかは気にしません。シンプルかつ直接的に保つことができます。コードに副作用がある場合、注意する必要があります。DIは、関数から副作用を引き出してパラメーターに入れることができ、これらの関数をよりテスト可能にしますが、プログラムはより複雑になります。避けられない場合があります(DBアクセスなど)。コードに副作用がない場合、DIは無駄な複雑さです。
ウォーボ

13
@CarlLeth:DIは、それを禁止する依存関係がある場合、コードを単独でテスト可能にする問題の解決策の1つです。ただし、全体的な複雑さを軽減したり、コードを読みやすくしたりするわけではありません。つまり、必ずしも保守性が向上するわけではありません。ただし、これらの依存関係のすべてを懸念のより良い分離によって排除できる場合、DIの必要性を無効にするため、これはDIの利点を完全に「無効にします」。これは、多くの場合、コードのテスト保守を同時に行うための優れたソリューションです。
ドックブラウン

5
@Warboこれはオリジナルであり、おそらくモックの唯一の有効な使用法です。システムの境界であっても、ほとんど必要ありません。価値のないテストの作成と更新には、多くの時間を費やします。
フランクヒルマン

6
@CarlLeth:わかりました、今では誤解がどこから来たのかわかりました。あなたは依存関係の逆転について話している。しかし、質問、この回答、および私のコメントは、DI =依存性注入に関するものです。
ドックブラウン

36

私の経験では、依存性注入には多くの欠点があります。

まず、DIを使用しても自動化されたテストは宣伝されているほど単純化されません。インターフェイスの模擬実装を使用してクラスを単体テストすると、そのクラスがインターフェイスとどのようにやり取りするかを検証できます。つまり、テスト対象のクラスがインターフェイスによって提供されるコントラクトをどのように使用するかを単体テストできます。ただし、これにより、テスト中のクラスからインターフェイスへの入力が期待どおりであることをはるかに保証できます。テスト対象のクラスがほとんど例外なく模擬出力であるため、インターフェースからの出力に期待どおりに応答するという保証はかなり低く、それ自体がバグ、単純化などの影響を受けます。つまり、インターフェイスの実際の実装でクラスが期待どおりに動作することを検証できません。

第二に、DIにより、コードをナビゲートするのがはるかに難しくなります。関数への入力として使用されるクラスの定義に移動しようとするとき、インターフェイスは軽度の煩わしさ(たとえば、単一の実装がある場合)からメジャータイムシンク(たとえば、IDisposableなどの過度に汎用的なインターフェイスが使用される場合)使用されている実際の実装を見つけようとするとき。これにより、「このロギングステートメントが出力された直後に発生するコード内のnull参照例外を修正する必要がある」などの簡単な演習を1日の努力に変えることができます。

第三に、DIとフレームワークの使用は両刃の剣です。一般的な操作に必要な定型コードの量を大幅に削減できます。ただし、これらの一般的な操作が実際にどのように結び付けられているかを理解するには、特定のDIフレームワークの詳細な知識が必要になります。依存関係がフレームワークにロードされる方法を理解し、注入するフレームワークに新しい依存関係を追加するには、かなりの量の背景資料を読んで、フレームワークに関するいくつかの基本的なチュートリアルに従う必要があります。これにより、いくつかの単純なタスクをかなり時間のかかるタスクに変えることができます。


また、注入回数が多いほど、起動時間が長くなることも付け加えます。ほとんどのDIフレームワークは、使用場所に関係なく、起動時にすべての注入可能なシングルトンインスタンスを作成します。
ロドニーP.バルバティ

7
(モックではなく)実際の実装でクラスをテストする場合は、機能テストを作成できます-単体テストに似ていますが、モックは使用しません。
BЈовић

2
2番目の段落はより微妙なものにする必要があると思います。DI自体は、コードをナビゲートするのを難しくしません。最も単純な場合、DIは単にSOLIDをフォローした結果です。複雑さを増すのは、不要な間接化とDIフレームワークの使用です。それ以外に、この答えは頭に釘を打ちます。
コンラッドルドルフ

4
本当に必要な場合を除いて、依存性注入は、他の余分なコードが大量に存在する可能性があるという警告サインでもあります。経営者は、開発者が複雑さのために複雑さを増すことを知って驚くことがよくあります。
フランクヒルマン

1
+1。これは、尋ねられた質問に対する本当の答えであり、受け入れられた答えであるべきです。
メイソンウィーラー

13

私は、「。NETでの依存性注入」からのMark Seemannのアドバイスに従いました。

「揮発性の依存関係」がある場合は、DIを使用する必要があります。たとえば、変更される可能性がある程度あります。

したがって、将来的に複数の実装がある場合や実装が変更される可能性がある場合は、DIを使用してください。それ以外の場合newは問題ありません。


5
彼はまた、オブジェクト指向と関数型言語のためのさまざまなアドバイスを提供します注意blog.ploeh.dk/2017/01/27/...
JKを。

1
それは良い点です。デフォルトですべての依存関係のインターフェースを作成する場合、それは何らかの形でYAGNIに反します。
ランディヨ

4
「.NETでの依存性注入」への参照を提供できますか?
ピーターモーテンセン

1
単体テストを行っている場合、依存関係が不安定である可能性が高くなります。
ジャックス

11
良いことは、開発者は常に完璧な精度で未来を予測できることです。
フランクヒルマン

5

DIに関する私の最大の嫌悪感は、いくつかの回答ですでに言及されていますが、ここで少し詳しく説明します。DI(今日はコンテナなどを使用してほとんど行われているように)本当に、コードの可読性が本当に損なわれます。そして、コードの可読性が、今日のほとんどのプログラミング革新の背後にある理由であると考えられます。誰かが言ったように-コードの記述は簡単です。コードを読むのは難しいです。しかし、ある種の小さな追記型の使い捨てユーティリティを書いているのでなければ、それは非常に重要です。

これに関するDIの問題は、不透明であるということです。コンテナはブラックボックスです。オブジェクトはどこかから単純に表示されますが、誰がいつ作成したかわかりません。コンストラクターに何が渡されましたか?このインスタンスを誰と共有していますか?知るか...

また、主にインターフェイスを使用する場合、IDEのすべての「定義に移動」機能が煙になります。プログラムを実行せずにプログラムの流れを追跡し、この特定の場所で使用されているインターフェイスの実装を確認するためにステップスルーすることは非常に困難です。また、時には技術的なハードルがあり、それを乗り越えることができません。そして、たとえできたとしても、DIコンテナーのねじれた腸を通過することを伴う場合、全体の出来事はすぐにフラストレーションのエクササイズになります。

DIを使用したコードを効率的に使用するには、コードに精通し、何がどこに行くのかを既に知っている必要があります。


3

実装の切り替えをすばやく有効にします(ConsoleLoggerの代わりにDbLoggerなど)

一般的にDIは確かに良いことですが、すべてにDIを盲目的に使用しないことをお勧めします。たとえば、ロガーを挿入することはありません。DIの利点の1つは、依存関係を明示的かつ明確にすることです。ILoggerほぼすべてのクラスの依存関係としてリストすることには意味がありません-それはただの混乱です。必要な柔軟性を提供するのはロガーの責任です。すべてのロガーは静的な最終メンバーです。非静的なロガーが必要な場合は、ロガーを挿入することを検討できます。

クラスの数が増えました

これは、DI自体ではなく、特定のDIフレームワークまたはモックフレームワークの欠点です。ほとんどの場所で、私のクラスは具象クラスに依存しています。つまり、ボイラープレートは不要です。Guice(Java DIフレームワーク)はデフォルトでクラスをそれ自体にバインドします。テストでバインドをオーバーライドするだけです(または、代わりに手動でそれらを配線します)。

不要なインターフェイスの作成

必要なときにのみインターフェイスを作成します(これはかなりまれです)。これは、クラスのすべての出現をインターフェイスで置き換える必要がある場合があることを意味しますが、IDEでこれを行うことができます。

実装が1つしかない場合、依存性注入を使用する必要がありますか?

はい。ただし、追加の定型文は避けてください

言語/フレームワーク以外の新しいオブジェクトの作成を禁止すべきですか?

いいえ。多くの値(不変)クラスとデータ(可変)クラスがあり、インスタンスが作成されて渡されるだけで、インスタンスを注入しても意味がありません。そのようなオブジェクト)。

彼らにとっては、代わりにファクトリをインジェクトする必要があるかもしれませんが、ほとんどの場合、それは意味をなしません(例えば、@Value class NamedUrl {private final String name; private final URL url;}ここでファクトリを本当に必要とせず、インジェクトするものは何もありません)。

特定のクラスの単体テストを計画しない場合、単一の実装をインジェクトするのは悪い考えです(実装が1つしかないため、「空の」インターフェースを作成したくないとしましょう)。

私見では、コードが肥大化しない限り問題ありません。依存関係を挿入しますが、後で手間をかけずに行うことができるので、インターフェイスを作成しないでください(そして、クレイジーな構成XMLはありません!)。

実際、私の現在のプロジェクトには4つのクラス(数百から)がありますが、データオブジェクトを含む多くの場所で使用される単純なクラスであるため、DIから除外することにしました。


ほとんどのDIフレームワークのもう1つの欠点は、実行時のオーバーヘッドです。これはコンパイル時に移動できます(Javaの場合、Daggerがありますが、他の言語についてはわかりません)。

さらに別の欠点は、あらゆる場所で発生する魔法であり、これは調整することができます(たとえば、Guiceを使用するときにプロキシ作成を無効にしました)。


-4

私の意見では、依存性注入の概念全体が過大評価されていると言わざるを得ません。

DIは、グローバルな値に相当する現代のものです。注入するものは、グローバルシングルトンと純粋なコードオブジェクトです。それ以外の場合は、注入できません。特定のライブラリ(JPA、Spring Dataなど)を使用するために、DIのほとんどの使用が強制されます。ほとんどの場合、DIはスパゲッティの育成と栽培に最適な環境を提供します。

正直なところ、クラスをテストする最も簡単な方法は、すべての依存関係がオーバーライド可能なメソッドで作成されることを確認することです。次に、実際のクラスから派生したTestクラスを作成し、そのメソッドをオーバーライドします。

次に、Testクラスをインスタンス化し、そのすべてのメソッドをテストします。これは一部の人には明らかではありません。テストしているメソッドは、テスト中のクラスに属するメソッドです。そして、これらのすべてのメソッドテストは、単一のクラスファイル(テスト対象のクラスに関連付けられている単体テストクラス)で行われます。ここにはオーバーヘッドがありません-これがユニットテストの仕組みです。

コードでは、この概念は次のようになります...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

結果は、DIを使用した場合とまったく同じです。つまり、ClassUnderTestはテスト用に構成されています。

唯一の違いは、このコードが完全に簡潔で、完全にカプセル化され、コーディングが容易で、理解が容易で、高速で、メモリ使用量が少ない、代替構成が不要、フレームワークが不要、4ページの原因にならないことです(WTF!)作成した正確にZERO(0)クラスを含むスタックトレース。初心者からGuruまで、OOの知識が少しでもある人には完全に明らかです(あなたは思うでしょうが、間違いです)。

そうは言っても、もちろんそれを使用することはできません-それはあまりにも明白であり、十分にトレンディではありません。

しかし、結局のところ、DIに関する私の最大の懸念は、私が見たプロジェクトが惨めに失敗することです。それらはすべて、DIがすべてを結びつける接着剤である大規模なコードベースでした。DIはアーキテクチャではありません-それは実際にはほんの一握りの状況に関連するだけで、そのほとんどは別のライブラリ(JPA、Spring Dataなど)を使用するためにあなたに強制されます。ほとんどの場合、適切に設計されたコードベースでは、DIのほとんどの使用は、毎日の開発アクティビティが行われるレベルよりも低いレベルで発生します。


6
同等ではなく、依存性注入の反対について説明しました。モデルでは、すべてのオブジェクトはそのすべての依存関係の具体的な実装を知る必要がありますが、「メイン」コンポーネントの責任となるDIを使用して、適切な実装を結合します。DIは他の DIと密接に関係していることに注意してください-依存関係の反転では、高レベルのコンポーネントが低レベルのコンポーネントに強く依存しないようにします。
ローガンピックアップ

1
1レベルのクラス継承と1レベルの依存関係しかない場合に便利です。確かに、それは拡大するにつれて地球上で地獄に変わるでしょうか?
ユアン

4
インターフェイスを使用し、initializeDependencies()をコンストラクターに移動した場合、それは同じですがよりきれいです。次のステップである構築パラメータを追加すると、すべてのTestClassesを廃止できます。
ユアン

5
これには多くの間違いがあります。他の人が言ったように、あなたの「DI同等」の例は依存性注入ではなく、アンチテーゼであり、概念の完全な理解の欠如を示し、同様に他の潜在的な落とし穴を紹介します:部分的に初期化されたオブジェクトはコードの匂いです As Ewan初期化をコンストラクタに移動し、コンストラクタパラメータを介してそれらを渡すことをお勧めします。その後、DIがあります
...-ミンダーミドル

3
@ Mr.Mindorに追加:初期化だけに適用されない、より一般的なアンチパターン「シーケンシャルカップリング」があります。オブジェクトのメソッド(または、より一般的にはAPIの呼び出し)を特定の順序で実行する必要がある場合、たとえばのbarfooにのみ呼び出すことができる場合、それは悪いAPIです。だと主張する(機能を提供するためにbar)、私たちは実際には(以来、それを使用することはできませんfooと呼ばれていない可能性があります)。initializeDependencies(アンチ?)パターンに固執したい場合は、少なくともプライベート/保護し、コンストラクタから自動的に呼び出す必要がありますので、APIは誠実です。
ウォーボ

-6

本当にあなたの質問は「ユニットテストは悪いですか?」

注入する代替クラスの99%は、単体テストを可能にするモックです。

DIを使用せずに単体テストを行う場合、クラスに模擬データまたは模擬サービスを使用させる方法の問題があります。「ロジックの一部」と言ってみましょう。サービスに分離できない場合があります。

これには別の方法もありますが、DIは優れた柔軟性のある方法です。テストのためにそれを入れると、他のコードが必要なため、具体的な型をインスタンス化するいわゆる「貧乏人のDI」でさえ、事実上どこでも使用することを余儀なくされます。

単体テストの利点が圧倒されるほど悪い欠点を想像することは困難です。


13
DIは単体テストに関するものであるというあなたの主張には同意しません。単体テストの促進は、DIの利点の1つにすぎず、おそらく最も重要なものではありません。
ロバートハーヴェイ

5
ユニットテストとDIが非常に近いというあなたの前提に同意しません。モック/スタブを使用することで、テストスイートをもう少しうそにします。テスト対象のシステムは実際のシステムからさらに進んでいます。それは客観的に悪いです。場合によっては、それよりも利点があります。FS呼び出しのモックはクリーンアップを必要としません。モックされたHTTPリクエストは高速で確定的であり、オフラインで動作します。対照的に、newメソッド内でハードコーディングされたコードを使用するたびに、実稼働環境で実行されている同じコードがテスト中に実行されいたことがわかります。
ウォーボ

8
いいえ、それは「ユニットテストが悪いですか?」ではなく、「(a)本当に必要であり、(b)複雑さが増すだけの価値があるか?」ということは、まったく別の質問です。単体テスト悪くありません(文字通り、誰もこれについて議論しているわけではありません)。ただし、すべてのユニットテストにモックが必要なわけではなく、モックにはかなりのコストがかかるため、少なくとも慎重に使用する必要があります。
コンラッドルドルフ

6
@Ewan最後のコメントの後、私たちは同意しないと思います。ほとんどの単体テストはモックを必要としないため、ほとんどの単体テストはDI [frameworks]を必要としないと言っています。実際、これをコード品質のヒューリスティックとして使用することもあります。DI/モックオブジェクトなしでほとんどのコードをユニットテストできない場合、適切に結合された不良コードを記述したことになります。ほとんどのコードは、高度に分離され、単一の責任と汎用性があり、単独で簡単にテストできる必要があります。
コンラッドルドルフ

5
@Ewanリンクは、単体テストの適切な定義を提供します。その定義により、私のOrder例は単体テストです:メソッド(のtotalメソッドOrder)をテストしています。あなたはそれが2つのクラスからコードを呼び出していると不平を言っています、私の応答はそうですか 「一度に2つのクラス」をテストするのではなく、totalメソッドをテストしています。私たちは気にしてはならないメソッドはその仕事をしていませんか:これらは実装の詳細です。それらをテストすると、脆弱性、密結合などが発生します。クラス/モジュール/ CPUレジスタなどではなく、メソッドの動作(戻り値と副作用)のみを考慮します。プロセスで使用しました。
ウォーボ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.