岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

InjectableとNewableという概念を用いてコードのテスタビリティを向上させる

Miško Hevery氏の記事「To “new” or not to “new”…」は、オブジェクトをInjectableなものとNewableなものに分類したうえで、コードのテスタビリティを向上させるためのルールを説明する、示唆に富んだ記事です。私の理解をまとめてみます。

InjectableとNewableの定義

すべてのオブジェクトはInjectableなものとNewableなものとに分けられる。

Injectable
オブジェクトグラフ生成時*1にDI可能である
Newable
オブジェクトグラフ生成時にDI不可能である

テスタビリティを守るためのルール

  1. Injectableなオブジェクトは、Newableなオブジェクトをフィールドとして持ってはならない
  2. 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にはモックを渡せる。テストしやすい。

*1:DIフレームワークが各オブジェクトの依存状況を解決するフェーズ

*2:どんな曲をplayすべきかDIフレームワークには分からない