kbigwheelのプログラミング・ソフトウェア技術系ブログ

プログラミング・ソフトウェア技術系のことを書きます

僕がDIを否定する理由

予防線

私が開発当初から関わったシステムでコード的に最大規模のものはScalaで書いた検索システムの2万行ほどのもので、あとは保守開発や1万行以下のサービスばかりです。 なのでその程度のへなちょこエンジニアの主張なんざ聞くに値しないという主張は一理あるかもしれません。 ですのでもっと大規模なシステムでは以下の理屈は通用しないというようなことはあるかもしれません。

きっかけ

これを見てそういえばScalaを始めたときcake patternなど勉強したことを思い出した。 今はTagless Finalというものがあるのか、なるほどと思いつつこの手のものをそういえば使わなくなったのはなぜか思い返したところ、これがDI技術であることが理由だった。 そういえばある時からDIは根本的に良くない気がして使わなくなったのだった。

そもそもDIとは

猿でも分かる! Dependency Injection: 依存性の注入 - Qiita など今はウェブ上に非常に理解しやすい説明があるので好きなやつを見たらいいと思う。

僕流に解釈して説明すると以下のようになる。

関数、プログラム、アプリケーション、サービスのどの単位でも必ずセットになるものがある、入力と出力だ。 そしてテスト*1とは特定の入力に対して期待した出力であるかを試すものである。例外はない。

基本的にテストするときは対象の外側から入力を与えてやる。関数であれば引数、クラスであればコンストラクタ引数、APIであればクエリパラメータやヘッダである。 しかしDI技術はこの原則を無視する裏技を提供する。直接テスト対象のプライベート変数(内部変数)へ値を注入(Inject)できるようにするのである。

僕が思うDIの欠点

直接テスト対象のプライベート変数(内部変数)へ値を注入(Inject)するDIは次の欠点を生み出す。

  1. (コンストラクタ)引数以外は不変であるというクラスの原則・直感を破壊する
  2. テストにおける入力の範囲が曖昧になる
  3. 範囲が曖昧になった結果、本来不変であるまたは不変にできるような値もDIで注入できるようにしてしまう
  4. 入力の変数が増えた結果入力のパターンが爆発的に増加してテストが書きづらくなる

ただ、以上のような欠点をDIを使っている人に何度かぶつけてみるとそれらは一理あるものの以下の要因により必要悪としてDIは使われているという反応が多かった。

  1. コンストラクタで変数へセットするオブジェクトを組み立てているとそこをモックすることが困難になる
  2. 多数の入力(=依存性)があるクラスではそれらをコンストラクタで決まった順番に渡すことは苦痛になりやすい

これらの反論もまた一理あるものであるが、どこか納得出来ないものを抱えてなかなかDIを積極的に使うことができなかった。

解決のヒント

DIについて調べたり軽く使ってみつつ数年経ったのちに読んだ2つのアイデアが解決の糸口になった。

一つは "TDD is dead. Long live testing." 一番最初に読んだ日本語の記事は忘れてしまったけど、この要約など良い。どうやらテスト駆動型開発は死んだようです。これからのCI 重要なのはTDD is deadといいつつ実際にはユニットテスト的な粒度の小さいテスト駆動での開発の否定で、機能テスト・統合テストなどと呼ばれる粒度の高いテストをちゃんとしましょうということが要点(だと僕は捉えた)。

もう一つはMicroservice。 これの良いところ悪いところいろいろあるが僕が目をつけたのはインターフェイスがWeb APIとして定義されること、中身が十分に(Microservice architectureという本によれば、たしか1,2週間で書き直せる、だったかな)シンプルであることだった。

この2つのアイデアを組み合わせて僕は以下のように今は考えている。

結論

  1. テストはAPIそのものに対して行われ、個別のクラス・関数に対しては書かない。さらに各リクエストがAPIサーバ内で相互に影響を与えない*2のであればモッキングが必要なのはAPIサーバが依存するDB、memcahceまたは外部のAPIサーバなどになる。つまりDIによる強引なモックの注入が必要なくなり、ひいてはDIも不要になる。

*1:ユニットテスト結合テストなど粒度を問わない

*2:DBサーバやmemcacheなど外部の永続化層で間接的に影響し合う分には問題ない