いつものように、It Depends™。答えは、解決しようとしている問題によって異なります。この回答では、いくつかの一般的な動機付けの力に取り組みます。
より小さなコードベースを好む
4,000行のSpring構成コードがある場合、コードベースには何千ものクラスがあると思います。
事後に対処できる問題はほとんどありませんが、経験則として、より小さなコードベースでより小さなアプリケーションを好む傾向があります。あなたがに興味があればドメイン駆動設計、あなたは、例えば、有界コンテキストあたりのコードベースを作ることができます。
私はこれまでのキャリアのほとんどでWebベースの基幹業務コードを記述してきたため、このアドバイスは私の限られた経験に基づいています。デスクトップアプリケーションや組み込みシステムなどを開発している場合、物事を分解するのは難しいと想像できます。
この最初のアドバイスは簡単に実用的ではないことを理解していますが、それが最も重要であると信じています。コードの複雑さは、コードベースのサイズによって非線形に(指数関数的に)変化します。
純粋なDIを好む
この質問は既存の状況を示していることを今でも認識していますが、Pure DIをお勧めします。DIコンテナは使用しないでください。ただし、使用する場合は、少なくともコンベンションベースの構成を実装するために使用してください。
私はSpringを実際に使った経験はありませんが、構成ファイルではXMLファイルが暗示されていると想定しています。
XMLを使用した依存関係の構成は、両方の世界で最悪です。まず、コンパイル時の型安全性は失われますが、何も得られません。XML構成ファイルは、置き換えようとするコードと同じくらい簡単に大きくすることができます。
対処することを目的とする問題と比較して、依存性注入構成ファイルは、構成の複雑さのクロックで間違った場所を占めます。
粗粒度の依存性注入のケース
粗粒度の依存性注入のケースを作成できます。また、きめ細かな依存性注入のケースも作成できます(次のセクションを参照)。
いくつかの「中央」依存関係のみを注入する場合、ほとんどのクラスは次のようになります。
public class Foo
{
private readonly Bar bar;
public Foo()
{
this.bar = new Bar();
}
// Members go here...
}
これはフィットまだデザインパターンのクラス継承の上に賛成のオブジェクト合成をするので、Foo
構成しますBar
。構成を変更する必要がある場合は、のソースコードを編集するだけなので、保守性の観点から、これは保守可能と見なすことができますFoo
。
これは、依存性注入よりも保守性が低いことはほとんどありません。実際、Bar
依存性注入に固有の間接参照に従う必要はなく、を使用するクラスを直接編集する方が簡単だと思います。
Dependency Injectionに関する私の本の初版では、 volatileとstableの依存関係を区別しています。
揮発性依存関係は、注入を検討する必要がある依存関係です。彼らが含まれます
- コンパイル後に再構成する必要がある依存関係
- 別のチームが並行して開発した依存関係
- 非決定的な動作を伴う依存関係、または副作用を伴う動作
一方、安定した依存関係は、明確に定義された方法で動作する依存関係です。ある意味では、この区別が粗粒度の依存性注入の場合に当てはまると主張することができますが、本を書いたときにそれを完全に理解していないことを認めなければなりません。
ただし、テストの観点からは、これによりユニットテストが難しくなります。からFoo
独立して単体テストを行うことはできなくなりましたBar
。以下のようJB Rainsbergerは説明し、統合テストは、複雑さのcombinatoric爆発に苦しんでいます。4〜5クラスの統合ですべてのパスをカバーする場合、文字通り何万ものテストケースを記述する必要があります。
それに対する反論は、多くの場合、あなたの仕事はクラスをプログラムすることではないということです。あなたの仕事は、いくつかの特定の問題を解決するシステムを開発することです。これが、行動駆動開発(BDD)の背後にある動機です。
これに関する別の見解は、DHHによって提示されます。DHHは、TDDがテストによる設計損傷につながると主張しています。また、粗粒度の統合テストも好みます。
ソフトウェア開発でこの観点をとる場合、粗粒度の依存性注入は理にかなっています。
きめ細かな依存性注入のケース
一方、きめ細かな依存性注入は、すべてのものを注入するものとして説明できます。
粒度の粗い依存性注入に関する私の主な懸念は、JB Rainsbergerによって表明された批判です。統合テストですべてのコードパスをカバーすることはできません。すべてのコードパスをカバーするために、文字通り数千または数万のテストケースを記述する必要があるためです。
BDDの支持者は、すべてのコードパスをテストでカバーする必要はないという議論に反論します。ビジネス価値を生み出すものだけをカバーする必要があります。
ただし、私の経験では、すべての「エキゾチックな」コードパスも大量展開で実行され、テストされていない場合、それらの多くに欠陥があり、ランタイム例外(多くの場合、null参照例外)が発生します。
これにより、すべてのオブジェクトの不変条件を個別にテストできるため、きめの細かい依存関係の注入を好むようになりました。
関数型プログラミングを支持する
私はきめ細かな依存性注入に傾倒していますが、本質的にテスト可能なため、他の理由の中でも特に関数型プログラミングに重点をシフトしました。
SOLIDコードに向かって進むほど、機能的になります。遅かれ早かれ、あなたも急落するかもしれません。機能的アーキテクチャはポートおよびアダプタアーキテクチャであり、依存性注入も試みであり、ポートおよびアダプタです。ただし、違いは、Haskellのような言語がその型システムを介してそのアーキテクチャを実施することです。
静的に型付けされた関数型プログラミングを支持する
この時点で、オブジェクト指向プログラミング(OOP)は本質的にあきらめましたが、OOPの問題の多くは、本質的にJavaやC#などの主流言語と本質的に結びついています。
主流のOOP言語の問題は、テストされていない実行時例外につながる組み合わせ爆発の問題を回避することがほぼ不可能であることです。一方、HaskellやF#などの静的に型付けされた言語を使用すると、型システムで多くの決定ポイントをエンコードできます。これは、コンパイラーが何千ものテストを書く代わりに、考えられるすべてのコードパスを処理したかどうかを単純に通知することを意味します(ある程度、特効薬ではありません)。
また、依存性注入は機能しません。真の関数型プログラミングは、依存関係の概念全体を拒否する必要があります。結果はより単純なコードです。
概要
C#での作業を余儀なくされた場合、コードベース全体を管理可能な数のテストケースでカバーできるため、きめ細かな依存関係の挿入を好みます。
結局、私の動機は迅速なフィードバックです。それでも、フィードバックを取得する方法は単体テストだけではありません。