依存性注入はいくらですか?


38

私は、クラスの依存関係である文字通りすべてに(Spring)Dependency Injectionを使用するプロジェクトで働いています。Spring構成ファイルが約4000行に拡大した時点です。少し前、ボブおじさんの講演の1つをYouTubeで見ました(残念ながら、リンクが見つかりませんでした)。配布されます。

このアプローチの利点は、DIフレームワークをアプリケーションの大部分から切り離すことです。また、工場には以前の構成に含まれていたものがより多く含まれるため、Spring構成がよりクリーンになります。それどころか、これにより、多くのファクトリクラス間で作成ロジックが広がり、テストがより難しくなる可能性があります。

ですから、私の質問は、あなたが他のアプローチで見ている他の長所と短所です。ベストプラクティスはありますか?ご回答ありがとうございます!


3
4000行の構成ファイルを持つことは、依存性注入のせいではありません...それを修正する方法はたくさんあります:ファイルを複数の小さなファイルにモジュール化し、注釈ベースの注入に切り替え、xmlの代わりにJavaConfigファイルを使用します。基本的に、発生している問題は、アプリケーションの依存性注入のニーズの複雑さとサイズを管理できないことです。
Maybe_Factor

1
@Maybe_Factor TL; DRは、プロジェクトをより小さなコンポーネントに分割しました。
ウォルフラット

回答:


44

いつものように、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#での作業を余儀なくされた場合、コードベース全体を管理可能な数のテストケースでカバーできるため、きめ細かな依存関係の挿入を好みます。

結局、私の動機は迅速なフィードバックです。それでも、フィードバックを取得する方法は単体テストだけではありません


1
私はこの答えが本当に好きです。特に、Springのコンテキストで使用されるこのステートメントは、「XMLを使用した依存関係の構成は、両方の世界で最悪です。まず、コンパイル時の型安全性を失いますが、何も得られません。XML構成ファイルは、置き換えようとするコードと同じくらい簡単に大きくなる可能性があります。」
トーマスカーライル

@ThomasCarlisleは、XML構成のポイントではなく、コードを変更(コンパイルも)して変更する必要はありませんか?マークが言及した2つの理由により、私はこれを使用したことはありません(またはほとんど使用していません)が、見返りに何かを得ることができます。
エル・マック

1

巨大なDIセットアップクラスは問題です。しかし、それが置き換えるコードを考えてください。

これらすべてのサービスなどをインスタンス化する必要があります。そのためのコードは、app.main()開始点にあり、手動で挿入されるかthis.myService = new MyService();、クラス内で密結合されます。

セットアップクラスを複数のセットアップクラスに分割し、プログラムの開始点から呼び出すことにより、セットアップクラスのサイズを小さくします。すなわち。

main()
{
   var c = new diContainer();
   var service1 = diSetupClass.SetupService1(c);
   var service2 = diSetupClass.SetupService2(c, service1); //if service1 is required by service2
   //etc

   //main logic
}

他のciセットアップメソッドに渡す場合を除き、service1または2を参照する必要はありません。

これは、セットアップを呼び出す順序を強制するため、コンテナから取得しようとするよりも優れています。


1
さらに(プログラム開始時点で、あなたのクラスツリーを構築する)この概念に見てみたい人のために、検索するための最良の用語は、「組成物の根」である
e_i_pi

@Ewanそのci変数は何ですか?
superjos

静的メソッドを使用してCIセットアップクラス
ユアン・

urgはdiである必要があります。「コンテナ」を考え続ける
ユアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.