あけましておめでとうございます。
大晦日は実家でプログレ聞きながらコード書いてました。
今さらながら Heldon の Stand by とか聞いてたんですが、Tangerine Dream を思わせるミニマルなシンセサイザーの反復と、リシャール・ピナスによるロバート・フリップばりの暴力的なギターソロが絡みあっており、大変良いですね。

作ったもの

また説明長くなりそうなので、はじめに作ったものの紹介です。

この Dee というのが DI コンテナの本体です。
名前は Ozzy Osbourne ソロ 1st Blizzard of Ozz におけるランディ・ローズのギター曲からです。
50 秒と短く、メタルアルバムの中にあってクラシック風の静かなギター曲ですが、同時にアルバムから欠かせない存在感を放つ名曲です。

何が言いたいかというと、Dee はコンパクトな実装ながらも、Dependency Injection によるクリーンな依存性解決を行う上でなかなか便利なライブラリとなっています。
PHP の Pimple を意識した作りになってます。

Dee 自体は DI コンテナとして最小限の機能だけに留められていて、何らかのアプリケーションやフレームワークに組み込むには、そのための実装が必要となります。
Rails については dee-rails という形で gem にしていて、これはデフォルトのコンテナへのアクセス方法や、サービスプロバイダを記述するルール (ディレクトリの配置とか、命名とか) と、それを生成するためのジェネレータを含んでいます。

ライブラリとしての使い方についてはそれぞれの README.md にとりあえず書いてるので、そちらをご覧ください。

Dee を作った背景、あるいは Ruby と PHP のカルチャーの比較

今年の後半は、知人のために Rails 4 で Web サービスを作ったりしていました。
そちらについては諸々の事情でここには書けないんですが、Rails でまともな Web アプリを書くのは初めてだったので、いろいろ勉強になりました。

基本的には Rails way で行くべきだろうということでやってきたんですが、どうにもしっくり来ないこともいくつかあって、一番気になったのはオブジェクトの取り回しを行う方法が用意されてないところでした。
要するに DI コンテナとかが用意されてない。

PHP だとここ 5 年ぐらいで、各種 Web フレームワークは DI コンテナ付属してて当たり前みたいな雰囲気になっていて、PSR-0 によるオートローディングや、PSR-1 でのクラス定義における副作用の排除と組み合わせることによって、非常にクリーンな形でオブジェクトの取り回しが行えるようになったという印象があります。

Ruby で何かする度にそんなことを考えさせられていて、Twilog から「黒魔術」をキーワードに探したところ、以下のようなツイートが掘り起こされました。

数ヶ月から 1 年以上前のツイートですが、今でも特に Ruby とそのカルチャーに抱く感想は変わってません。

なお、DI コンテナは元々 Java 由来だろうとは思いますが、その辺の歴史については全く知らないので書きませんでした。

Ruby に DI コンテナは不要か

Ruby における DI コンテナの実装は、もちろん全く無いわけではないと思うんですが、ほとんど使われていない印象です。
アレコレ検索してみると、Ruby においては DI コンテナは不要である、みたいな記事もいくつか見つかります。
Matz も「DIってのは硬直した言語のための技術なんだ」と言っています。

その他に参照した記事は大体以下の通り。

その辺の意見をまとめると、大体「Ruby の動的な性質を利用すれば DI コンテナなんか無くても問題ない」みたいな感じだと思います。
より具体的には、Ruby だとクラス自体もオブジェクト (Class オブジェクト) なんだからそいつをファクトリオブジェクトとして渡せば良い、みたいな。
(ちょっと英語の記事なんかはちゃんと読み取れてない可能性があるので、その辺読み違えてるところあればご指摘いただきたいところではあります)

確かに Ruby の動的な性質を利用することで解決できることも結構あると思うんですが、大体の場合は規模が大きくなって来ると破綻するように思われます。
Class オブジェクトをファクトリオブジェクトとして利用する場合も、new メソッドの引数が同じでないと上手く差し替えができなかったりするので、困ったことになります。

これは言い換えると、コンストラクタは API であるか否か、とも言えます。
これに対する僕の答えは「No」です。
もちろん、コンストラクタも API として考えること自体はできなくはないですが、そういうものとして作られたライブラリはあんまり無いように思います。

Rack Middleware のコンストラクタ引数の辛さ

ですが、実を言うと Rack Middleware のコンストラクタは API のようなものが一応決まっていて、Rack::Builder でミドルウェアを積み上げる場合、ミドルウェアのコンストラクタ引数は以下のようにする必要があります。

class SomeRackMiddleware
  def initialize(app, *args)
    # ~~~
  end
end

Rack::Builder におけるミドルウェアはまさに Class オブジェクトをファクトリオブジェクト、コンストラクタを API として考えているような感じになっていて、このような実装を半分強制されています。
これの何が問題かというと、コンストラクタパラメータのチェックや、デフォルト値のセットを自分で書かないといけなくなることです。

なお、これは Rack の仕様外の決まりで、Rack::Builder を使用するとき特有の問題だと思われます。

この問題については、コード例を示したこともありました。

DCI と ActiveSupport::Concern と古き良き Service について

DI コンテナと直接関係はないものの、「ドメインロジックをどこに実装するか」という問題において考えると、DCI や ActiveSupport::Concern に触れる必要が出て来ると思います。

DCI は未だにちゃんと理解できてないですが、ドメインオブジェクトをそれ自体とコンテキスト (ロール) に分けて考えよう、みたいな理解でいます。
より実装に近い感じでいうと、ドメインオブジェクトの状態をロールとして切り出した形に用意して、どこかに実装していたロジックが、ロールに勝手にすげ変わるようになってるよ、という感じでしょうか。
これを実現するにはそれなりのフレームワークが必要になるので、具体的にどう書くか、みたいなところはそのフレームワークに依存することでしょう。
Module.extend で頑張ることもできそうだけどまぁ辛そうですし、なんというか DCI 自体めんどくさそうな印象です。
(やったことなくて言ってますが)

あと ActiveSupport::Concern は複数のモデルとかで共通の処理を切り出してるだけだと思うんですが、どうでしょうか。
module のミックスイン + ちょいとした拡張機能みたいな感じ?
Scala や PHP の trait なんかもそうですが、どういう単位で切り出すかが問題になるので、結構難しいと思ってます。
それで済ませられる範囲で言うと便利だと思うんですけど。
まぁこれもほとんど使ったこと無くて言ってますが。

ぶっちゃけ新しいこと覚えるのがめんどくさくて避けてるだけなんですが、実際のところ大きな問題だと思うのは、ビジネスロジックの実装が何かしらのフレームワークに依存してしまうということです。
ActiveSupport::Concern はもちろん ActiveSupport に依存しますし、DCI もそれを実現するための何らかのフレームワークに依存することになるでしょう。

その点、古き良き Service は別に何らかのフレームワークに依存することを強制されてはいません。
その生成に関しては何かしらの DI コンテナに任せることになったとしても、別に個々の Service 自体が DI コンテナのことを知ってるわけではありません。
(知っていたとすればそれは DI Container ではなく Service Locator, Dependency Injection でなく Dependency Lookup になっているはず)

まとめ

  • Ruby だと DI コンテナなんかなくてもなんとかできたりするケースもある
  • だけど規模によっては辛くなるもので、そこはやはり DI コンテナで解決できるはず
  • DCI も ActiveSupport::Concern も面倒だし Service でいいじゃん
  • よかったら Dee 使ってみてね

大分とっ散らかった感じになってますが、Ruby や Rails においても DI コンテナは便利なのではないか、という問題提起として、Dee を作ってみました。
まだそれで実際に何か作ったわけではないのですが、それはまた追々…

今年もよろしくお願いします。

2014-01-01 14:00:00 追記

一部ご意見いただいた点などについて、追記します。

「DCI や ActiveSupport::Concern は DI コンテナとは関係ないだろ」というご意見

はい、直接的には関係ありません!
でも、間接的には関係してくると思ってます。

DCI や ActiveSupport::Concern の比較対象とすべきは、DI コンテナではなくて Service ですね。
どれも「Rails でいうところの Model (ActiveRecord::Base 継承したヤツ) をはみ出るロジックをどこに記述するか」という点では競合して来ると思います。

DI コンテナは Service をクリーンに記述するのを支援するツールに過ぎません。
DI コンテナ無しで Service を記述することはできなくないですし、DCI フレームワークを DI な感じに実装することも当然考えられます (あぁややこしい)。
ここについては僕の書き方が誤解を招いていたと思います。

DI 不要論と DCI 便利そう論は別に前々関係無いところで起こっていたと思うので、「DCI でいいから DI 要らないよね」みたいな人は居なかったと思います。
ですが、DCI 不要論の中では「DCI とか難しいし Service でいいじゃん」という意見を 2013 年の前半頃によく見かけたと思います。

「なんで突然 DCI や ActiveSupport::Concern Dis ってるんだ」というご意見

この文脈で DCI や ActiveSupport::Concern を挙げた理由については、先に書いた通りです。

Dis ってる、という点については特にそういう風には思ってません。
開発規模とかによってどれがより便利か違ってくることはあっても、どれかが絶対的に優れているとかいうことはないと思います。
とはいえこの記事でいうと、DCI や ActiveSupport::Concern について否定的になっている感は確かにあると思います。
どちらかというと、どっちも自分には難しくて挫折しただけなんですが。

それぞれ比較して、どういうときに便利そうか、自分なりの考えをまとめておきます。

長くなったので先に結論。

  • DCI は単純で重複の多い問題がたくさんあるときに便利そう
  • ActiveSupport::Concern は割と何でもできるので少数精鋭での開発なら便利そう
  • Service は小規模でも大規模でも使えるけど大規模だと優秀なアーキテクトが必要になりそう

DCI はやはり、それを実現するフレームワークで、コレというものが出てくるまでは手が出せないんじゃないかと思います。
確かに考え方としてはふむふむなるほどという感じがしますが、実装として考えてみると、どうしても複雑化してしまうように思います。
とはいえ、それが実現されたら、それが有効な問題領域に「DCI というレールが敷かれている」状態になるので、Rails の目指している方向性には合致しているように思えます。

レールとして考えると、やはり「大体これで解決できるようにしといた。それでできない残り少ない部分は各位なんとかしろ」みたいな感じになると思います。
例えば「複数の Model (ActiveRecord::Base 継承したヤツ) にまたがるロジック」はうまく解決できても、「そもそも ActiveRecord 一切関係無い何か (Web API 使った処理とか)」については解決できない、みたいな感じになりそうです。
そういった割り切りの無いレールというのは、それはそれで複雑で誰にも使われないことでしょう。

単純かつ頻出の問題がとにかくたくさんある、という場合には DCI は便利になりそうな気がします。

で、Rails は Model をはみ出るロジックの記述に ActiveSupport::Concern を採用しています。
これも単純にミックスインを使うのに比べたら、ディレクトリの配置とか記述のしかたとかがある程度規約化されているので、それなりに「ActiveSupport::Concern というレールが敷かれている」状態にはなっていると思います。
とはいえこれはかなり自由度が高いレールなので、レールっぽさは低いんじゃないかなーとも思います。
その分、広い範囲で使えそうではありますね。

自由度が高いと、本来それを使うべきでない状況での濫用が問題になりそうです。
使うのにセンスが求められると思います。
少数精鋭での開発であれば大いに役立ちそうです。
別に他の方法と排他的ではないと思うので、ちょこっとだけ使う、ということもアリでしょう。

これらと比較すると、Service はレールでもなんでもありません。
単に何らかの処理を、専用のクラスに分割して書くだけなんですから。
何のフレームワークやライブラリへの依存も前提としていません。
例えある Service が ActiveRecord に依存していたとしても、ActiveRecord をラップすることで、Service それ自体から ActiveRecord を見えなくすることもできます。

とはいえ本当に生の状態で Service を利用せよ、となるとそれはさすがに辛いので、DI コンテナと、それへのアクセス方法だけレールとして用意したのが Dee と dee-rails ということになります。

Service を使ったやり方は本当に自由なので、基本的にはどんな状況にも対応できます。
シンプルな問題を素朴に解決することも、複雑な問題を複雑に (でもインターフェイスはシンプルに) 解決することもできます。
似たようなたくさんの問題を効率よく解決する方法については、自分で考える必要があるでしょう。
大規模な開発でも適用可能だと思いますが、何人かは GoF とか PofEAA とか DDD とか読んでて欲しいところです。

以下、関連するツイートを並べます。

あと、この記事に関して残る火種も紹介します。

, , , ,

この記事は TDD Advent Calendar jp: 2011 の 14 日目です.

この記事の概要

  • TDD で開発することで設計上の問題点に気づきやすくなる
  • Singleton はグローバル変数である
  • Singleton の使用はできる限り避けるべきである

テスタビリティを意識しよう

TDD では, 原則としてユニットテストを書いてから実際のコードを実装します.
なので, 自然と「テストのしやすさ (テスタビリティ)」を意識して実装することになります.

そして, TDD においては一般的に, テスタビリティを意識することで, 設計が改善されるとされています.

オブジェクト指向には難しい概念がたくさん登場します.
単一責任の原則 (Single Responsibility Principle) とか, 関心事の分離 (Separation of Concerns)とか, 依存性の注入 (Dependency Injection) とか…
これらを, 書籍などで読むだけで理解できた, という人は少ないのではないでしょうか.
少なくとも私は, TDD を実践することで初めて, これらの原則の言いたいことを実感し, 理解につなげることができたと考えています.

オブジェクト指向設計の大切なことは TDD に学んだ, と言っても過言ではありません.
あまりにもたくさんのことを学んだので, それらをここで全て紹介するのは無理なことです.
なので, 今回はその中から一つだけ, 簡単なものを紹介しようと思います.

この記事では, 明日から意識できるポイントとして, 例えば, Singleton を避ける, ということについて紹介します.

Singleton とは

いわゆる Gang of Four の書籍で紹介されているデザインパターンのひとつで, 以下のようなことを実現するために利用されます.

  • あるクラスのインスタンスが 1 つであることを保証する
  • 常に同一へのインスタンスを取得するためのアクセスポイントを提供する

数あるデザインパターンの中でも比較的理解しやすく, 実装も簡単であるため, これを最初に覚えたという方も多いでしょう.
私自身もそうであったように思います.

Singleton パターンを適用してみる

例えば, PHP においては以下のように実装されます.
ここでは, ありがちな例として, アプリケーション中からグローバルに参照される Config (設定) クラスを作ってみましょう.

要点は以下の 3 点です.

  • コンストラクタ (__construct メソッド) を private にすることで, new を禁止している
  • 代わりに getInstance() という static メソッドでオブジェクトの生成をする
  • getInstance() では初回だけ Config オブジェクトを生成して返すが, 次回以降は最初に作ったオブジェクトをそのまま返す

Singleton をテストする

次に Config オブジェクトをテストするコードを書いてみましょう.
テスティングフレームワークには PHPUnit を使用します.

このふたつのテストはいずれも成功します.

Green

テストは問題無く通った

ですが, 記述する順序を逆にしてみると, いとも簡単に失敗してしまいます.

Red

テストは壊れてしまった

一体, どうしてでしょうか.

Singleton はグローバル変数である

クラスを定義して, クラスメソッドを通してのアクセスを行っていますが, Config クラスのオブジェクトはグローバルに参照可能です.
本来ローカルスコープであるはずのメソッド間を超えて, 状態が共有されてしまいます.
そのため, 一度設定値 foo がセットされると, その後のテストで「foo がセットされていない状態」をテストすることができなくなってしまいました.

このように, Singleton パターンを適用したクラスのオブジェクトは, 本質的にはほとんどグローバル変数なのです.

テストは新鮮なうちに

ユニットテストを書く上で大切なことは, 出来る限りクリーンな状態で始めることです.

グローバル変数, データベース, ファイルシステムなどは, 状態として考えることができます.
コードやテストがこれらの状態に依存してしまうと, 状態の変化に弱くなり, 壊れやすくなります.

Singleton でない通常のクラスであれば, 複数のテストメソッド間で状態を共有されません.
スコープがテストメソッドごとに区切られるので, テスト対象のオブジェクトはガベージコレクションにより, 二度と再利用されないためです.
(もちろん, テストケースクラスのインスタンス変数などに入れてしまえば別ですが)

Singleton では, このガベージコレクションによる状態のクリアが起こらないため, 前のテストコンテキストを引きずったまま, 次のテストを実行することになってしまいます.
複数のテストが影響し合うことによりメンテナンス性が下がるだけでなく, 暗黙的なコンテキストの増大により, 理解もしづらくなります.

さらなる問題

Config 自体はとてもシンプルなので, さほど問題にはならないかもしれません.
ですが, Config::getInstance() がアプリケーションのあちこちで呼び出されていたらどうでしょうか.

例えば, ウェブアプリケーションにおいて, アプリケーションそれ自体を表す Application クラスについて考えてみます.

Application は設定によりデバッグモードの On/Off が切り替えられるとしましょう.
その設定は, Config オブジェクトが保持することになります.

デバッグモードであるかどうかは, Application オブジェクトの isDebug() メソッドで確認できます.
今度はそれをテストするコードを書いてみましょう.

そしてこのテストが通るように Application クラスを実装します.

これで, テストは問題なく通ります.
以下は Config のテストもあわせて実行した結果です.

Green

テストは問題無く通った

ですが, さっきと同じで, このテストも順序を逆にすると失敗してしまいます.

Red

テストはまたしても壊れてしまった

原因は, 先ほど Config のテストが失敗したのと全く同じです.
既に debug という設定値が入ってしまっているため, false を明示的に値をセットしない限りは, デバッグする設定のままになります.

このように, Singleton で実装したクラスそれ自体だけでなく, それを呼び出すクラスにまで, テストのしにくさが伝染してしまう可能性があるのです.

処方箋 1: 状態を初期化できるようにする

Config クラスのテストしにくさは, 状態がクリーンでないことにありました.
そこで, init() メソッドを実装することで, 状態を初期化できるようにしてみます.

テストもこのように書き換えます.

変更点は, Config::getInstance() した後に, init() を呼び出すようにしている点のみです.

これにより, いずれのテストもクリーンな状態でテストが実行されることになったので, 例え順番を変えても失敗しなくなりました.
Application クラスにおいても, 同じアプローチを採ることで, テストの実行順序に依存することは無くなります.

処方箋 2: 依存性の注入 (Dependency Injection) の利用

とはいえ, これで問題が無くなったわけではありません.
この Application クラスには, Config というクラス名がハードコードされており, 2 つのクラスは密結合になってしまっています.

これでは, Config に似た動きをした別のクラス (例えば CachedConfig とか) を作っても, 差し替えるには全ての Config を書き換える必要が出てしまいます.

ではどうするか.
オブジェクト指向が本来持つモジュール性を活かすのであれば, 引数で Config オブジェクトを渡すことを検討しましょう.
ここでいう引数とは, コンストラクタ引数でも, メソッド引数のどちらでも構いません.

先ほどよりもややコードが増えてしまいましたが, Config クラスのハードコードが無くなりました.
Application と Config の間の結合は緩くなり, CachedConfig や YamlConfig など, 同じように振る舞う別クラスへの差し替えが容易になりました.
また, モックスタブを使ってのユニットテストも書きやすくなっています.

このように, 依存するオブジェクトのクラス名をハードコードするのではなく, 引数で渡すことを依存性の注入 (Dependency Injection) といいます.

ところで, クラス名のハードコードが問題になるのは, 何も Singleton だけの問題ではありません.
例え Singleton ではなくとも, new するクラス名をハードコードすれば, 同じようなことが問題になります.

とはいえ, Singleton を利用すると, getInstance() メソッドを利用した依存にしてしまいがちです.
出来る限り依存性の注入を利用し, クラス名をハードコードする箇所を最小限に留めることで, 後の改修時のコストを大幅に下げることができます.

処方箋 3: そのクラスは本当に Singleton なのか

あなたが今実装しようとしているクラスは, 本当に唯一なのでしょうか.
また, 通常唯一であるとしても, コンストラクタを private にしないと実現できないことなのでしょうか.

確かに, Singleton の「常に同一のインスタンスを返す」という特性は便利です.
ですが, 少なくとも私の経験においては, 「インスタンスが 1 つであることの保証」は, そんなに気張るほどのことでは無かったのではないか, と考えてしまいます.

また, 見方を変えれば, Singleton は, そのクラス本来の役割 (Config であれば設定の保持) とは別にもうひとつ, 「オブジェクト生成の管理」という, 全く違った責任を持ってしまっています.
これは, 単一責任の原則に違反していると言えるでしょう.

これをどう解決するか.
少々場当たり的ですが, 例えば Config と, その生成を責任とする ConfigManager に分割する, といったことが考えられます.

この実装自体はあまりオススメしませんが, とりあえず設定の保持 (Config) という責任と, 設定オブジェクト生成の管理 (ConfigManager) という責任を, 個別のクラスに分割することができました.
これにより, Config クラスのコンストラクタを private にするという縛りは無くとも, ConfigManager を利用する限りにおいては, 常に同一のインスタンスを得ることができるようになりました.

コンストラクタが使用できる以上は, オブジェクトの生成は自由になったので, テスト時は毎回オブジェクトを生成・破棄し, クリーンな状態でのテストが可能になりました.
もう init() は必要ありません.

テストメソッドごとにコンテキストが分割されたので, ガベージコレクション任せで安心してテストに集中することができるようになりました.

まとめ

Singleton の話ばかりしましたが, この記事で私が言いたいのは, TDD で開発することで設計上の問題点に気づきやすくなる, ということです.

TDD の実践から学べることは多いにあるので, まだやったことないという方には, まず趣味の開発でいいので, 軽い気持ちでやってみることをオススメします.
業務に TDD 自体を導入する前の段階でも, 設計について新たな目線を得ることができるので, 何かしら良い効果を得ることでしょう.

, , ,