依存性注入(DI)の利用によるメリットやメリットの欠如に関する興味深い議論がブロゴスフィアで行われていた。
議論の元となったのはJacob Proffitt氏(ブログ・英語)が書いたブログポストで、DIはうまくスケールしない(source)とProffittは自分の意見を述べている。Proffitt氏によると、DIに人気がある唯一の理由はモッキングである。
しかしながら、DIが最近これほど引っ張りだこになった真の理由は、直交性やカプセル化、その他の「純粋な」アーキテクチャ上の問題に関係しているわけではまったくありません。非常に多くのデベロッパがDIを利用している本当の理由は、モックオブジェクトを使ったユニットテストを促進することです。好きなだけくどくど言っても構いませんが、頭の切れるデベロッパを実際に説得して、もっと単純な実装よりもDIを選ばせるようにするのが、モックオブジェクトを使ったユニットテストという要因なのです。
Proffitt氏がたどり着いた先は、DIはユニットテストにのみ有効という主張である。
しかし、DIにはユニットテストを除いて賞賛に値するような適用性がないことを、皆が認めてくれたらいいのにと思います。
ところが、Proffitt氏はDIなしでユニットテストを利用するのである。TypeMock(サイト・英語)フレームワークを使うのだが、TypeMockフレームワークでは、テスト中のコード内に依存性が作成されたとしても、その依存性の呼び出しをインターセプトできるのだ。つまりProffitt氏は、ユニットテスト用のモックを作成できるようにするために、オブジェクトをデカップルする必要はないと述べている。
RhinoMocks(サイト・英語リエーター、Ayende氏(ブログ・英語)がProffitt氏のブログ(ブログ・英語)に反応を示している。
コードを簡単にモックする能力は魅力的な特性ですが、それは主要メリットの副次的なメリットに過ぎず、主要なメリットはオブジェクト間の結合を低く抑えることです。 給料の計算エンジンに触れることなく、データアクセスコードを修正可能——これが私にとっての主要メリットなのです。
Nate Kohari氏(ブログ・英語)もProffitt氏の最初のポストに答えている。DIコードの例を示した後に、Kohari氏はDIが本当は何であるか(source)について詳しく述べている。
あなたがGoFのファンであるなら、これは実際Strategyパターンなのです。 依存性注入は(私の見るところ)基本的にひとまとめに使われるStrategyパターンです。
Kohari氏はNInject (サイト・英語)DIフレームワークのクリエータであり、DIフレームワークの有用性を盛んに売り込む。
DIフレームワークに依存するコードをひとたび書き始めると、オブジェクト接続に要する費用はほとんどゼロになります。結果として、責任一元化(Single Responsibility)の目標達成が驚くほど簡単になります。
Kohari氏はのちのポストで、 DIはスケールしないというProffitt氏の元々の主張(source)に対し、フレームワークを使う重要性を再度述べて返答している。
現実世界のシナリオでは、手動で行う依存性注入はただ単にスケールしないのです。
Proffitt氏の意見は異なる。
呼び出される側のニーズを呼び出し側に提供させることが依存性注入の唯一の目的である場合、依存性注入(制御パターンの完全反転をしようとしているわけではありませんが、その可能性もあります。制御パターンの反転については、まだ結論が出ていませんから)により疎結合のユニットが作成され、そのユニットが簡単に再利用できるとなぜ言えるのでしょう。どのような合理的な査定をしても、カプリングが増加することになります。カプリングの作業負荷をフレームワークに押しつけても、オブジェクトの利用に外部材料の供給が必要であるという事実に変わりはありません。
Kohari氏は、Kohari氏のコンフィギュレーションではほとんどの場合、特定タイプのオブジェクトをどのように作成し、注入すればよいかを一度だけ設定すればよく、その設定を行うのは呼び出し側ではなくフレームワークであると説明している。
Kohari氏はさらに、コードの可変性についても語っている。
・・・簡単に言えば、依存性注入によりコードの変更が容易になります。アジャイルの連中の間でDIの人気が高い理由はそこにあります。アジャイルの人達のソフトウェア開発は全体的に、工程内で迅速に変更を行うことを中心に調整しますから。
TypeMockを作った会社の最高技術責任者Eli Lopian氏(ブログ・英語)が、この議論の中核(source)を定義づける2、3の論点を挙げて議論に加わる。
DIを「特効薬」として使うと、プログラミング言語がもつ能力の半分以上を失っていることになります。staticメソッドや「新しい」キーワード、sealed型を使うことはできません。それに、全メソッドをバーチャルにする必要もあります。
Lopian氏はまた、変更を可能にするためだけにDIを使用することはYAGNI(You Aren't Going to Need It=それは将来必要にならない)の原則(source)に反すると主張する。
Lopian氏はさらに続ける。
TDD が開始になったとき、論じられた最初の問題の1つが、「テストできるようにするためにコードを変更すべきか」でした。コードの可視度を変えるべきでしょうか。コードのデザインを変えるべきでしょうか。これにより、テスト可能なコードとOOカプセル化の間に衝突が生じているのです。テストを可能にするために、デベロッパがコードの密閉部をさらし始めたのです。最初は密閉フィールドやメソッドでしたが、今ではデザイン全部をさらしているのです。
これは議論の典型である。よりテストしやすくする目的でコードを変更することは良いことだと論ずる人達もいれば、その目的のためにカプセル化を解くのは良くない、と主張する人達もいる。
カプセル化と依存性に関するKohariの見解は次のとおりである。
依存性注入を価値あるものとする秘密はこうです——依存性に関していえば、カプセル化は悪なのです。
ユニットテストを目的として行った変更によって疎結合がもたらされるとしたら(Proffittはこれを疑問視しているが)、それは良いことなのか、そうではないのか。
疎結合とカプセル化は両方ともOOプロパティと評価されるが、両者のバランスをどう扱えばいいのだろう。どちらが正しい道だろう?