InjectableとNewableという概念を用いてコードのテスタビリティを向上させる
Miško Hevery氏の記事「To “new” or not to “new”…」は、オブジェクトをInjectableなものとNewableなものに分類したうえで、コードのテスタビリティを向上させるためのルールを説明する、示唆に富んだ記事です。私の理解をまとめてみます。
InjectableとNewableの定義
すべてのオブジェクトはInjectableなものとNewableなものとに分けられる。
- Injectable
- オブジェクトグラフ生成時*1にDI可能である
- Newable
- オブジェクトグラフ生成時にDI不可能である
テスタビリティを守るためのルール
- Injectableなオブジェクトは、Newableなオブジェクトをフィールドとして持ってはならない
- Newableなオブジェクトは、Injectableなオブジェクトをフィールドとして持ってはならない
ルール1を破った例
class Song { String name; byte[] content; Song(String name, byte[] content); } class MusicPlayer { AudioDevice device; Song song; @Injectable MusicPlayer(AudioDevice device, Song song); play(); }
MusicPlayer(Injectable)がSong(Newable)をフィールドとして持ってしまっている。
オブジェクトグラフ生成時にはSongの各フィールドの値が決められない*2。そのためMusicPlayerが生成できない。
ルール2を破った例
class MusicPlayer { AudioDevice device; @Injectable MusicPlayer(AudioDevice device); } class Song { String name; byte[] content; MusicPlayer palyer; Song(String name, byte[] content, MusicPlayer player); play(); } class SongReader { MusicPlayer player @Injectable SongReader(MusicPlayer player) { this.player = player; } Song read(File file) { return new Song(file.getName(), readBytes(file), player); } }
Song(Newable)がMusicPlayer(Injectable)をフィールドとして持ってしまっている。
SongReaderがMusicPlayerを知る必要がある。Miško Hevery氏の表現を使えば、これは「デメテルの法則を破った」実装である。
(岩本注:Songがplayメソッドを持つ時点で不自然ではある)
ルールを守った例
class Song { String name; byte[] content; Song(String name, byte[] content); } class MusicPlayer { AudioDevice device; @Injectable MusicPlayer(AudioDevice device); play(Song song); }
Song(Newable)はNewableなフィールドのみ(String、byte[])を、MusicPlayer(Injectable)はInjectableなフィールドのみ(AudioDevice)を持つ。
Songは普通にnewできるし、MusicPlayerにはモックを渡せる。テストしやすい。