interface に対してプログラミングするのがベストプラクティスであるとする考え方が、 Java のプログラミングにあるようです。
依存関係を切断・逆転できて便利とか、インターフェースが変わらない限りは実装が変更されても利用するクラスは変更されないなどのメリットがあるんじゃないでしょうか。
オブジェクト指向のなんたるかを論じるほど僕はデキたプログラマーではないので、かわりに Gradlle を使ってビルドするときにどれくらい嬉しいのかを書いていきたいと思います。
なお、これはつい先日(2020/12/11)に行われた JJUG ナイトセミナー Maven/Gradle ビルドツール特集のときにさらりと話した部分の補遺です。
理屈としては、上の画像にあるように、インターフェースを使って実装を分離している場合、
実装(この図では UserRepositoryImpl
クラス)を変更した際に、そのインターフェースである UserRepository
を使う UserService
クラスは何ら影響を受けません。
その場合、 Gradle はこの UserService
クラスのコンパイルが不要であると判断して、前回のコンパイル結果をそのまま使います。
その結果、ビルドの速度が速くなるというのが interface に対してプログラミングすることの Gradle における恩恵になります。
UserService
クラスが UserRepository
を介さずに直接 UserRespositoryImpl
クラスを参照・呼び出ししている場合には、たとえ UserService
の Java コードを変更していなくても UserRepositoryImpl
に変更があれば UserService
クラスの再コンパイルも必要になります。
では、どれくらい時間に差が出るのか気になったので、サンプルで 15,000 ファイルくらいのプロジェクトを作って計測してみました。
結果を先に述べると、インターフェースありのプログラムのほうがコンパイル時間が短くなります。
実験手順
- 一番最初の画像にあるような
FooId
<-Foo
<-FooRepositoryImpl
->FooRepository
<-FooService
というインターフェースを挟んだシンプルな参照関係(参照する側 -> 参照される側)のある Java ファイルが 15,000 組(合計 75,000 ファイル)ほどある Java プロジェクトを作る(Foo
部分の名前がランダムに生成した名前(例えばOnPerfections
)に変わる) - 1.のプロジェクトをビルドする(ここでは
compileJava
タスクを実行する) - 15,000 組の中から 60 組ほどランダムに選び、実装クラスである
FooRepositoryImpl
のインターフェースに影響の出ない部分のコードを変更する - 改めてビルド(
compileJava
タスクを実行)する - 4.のインクリメンタルビルド時間を取得する
- 1.の関係でインターフェースを挟まないようにしたプロジェクト(
FooRepository
を作らず、FooService
が直接FooRepositoryImpl
を参照している)を作成する - 6.に対して上記の 1. 〜 5. を実施、 5. の結果をインターフェースのある場合と比較する
なお、実験環境は以下の通り
実験結果
回数 | インターフェースあり・初回(参考) | インターフェースあり・編集後 | インターフェースなし・初回(参考) | インターフェースなし・編集後 |
---|---|---|---|---|
1 | 17.713 | 4.66 | 20.361 | 6.266 |
2 | 19.228 | 4.93 | 20.495 | 6.331 |
3 | 20.524 | 5.214 | 19.53 | 6.517 |
4 | 23.933 | 5.958 | 18.229 | 6.086 |
5 | 19.598 | 5.14 | 21.349 | 6.636 |
平均 | 20.1992 | 5.1804 | 19.9928 | 6.3672 |
上記の結果から、 インターフェースありのプログラムは 60 ファイル程度の編集でインクリメンタルビルドでの時間が 1 秒程度速くなることがわかった。
感想的なあれ
- 参照関係がシンプルであったために、期待していたほどコンパイル時間に差が発生しなかった
- もう少し参照関係が複雑なプログラムを作成して比較したほうが良さそう
- ビルドキャッシュを設定しなかった場合も調べたほうが良さそう
- 今回の計測には github actions を使ったがちゃんと AWS などでバーストしないタイプのインスタンスで比較したほうが良さそう
インターフェースに対してプログラミングする場合に、わずかながらインクリメンタルビルドの時間が短くなることが示せたが、 1 秒程度の差なら気にせずクラスを直接参照するみたいな考え方もあるだろうと思われる
FooService
側が複数の Repository
的なインターフェース・クラスを参照するより現実的なプログラムであった場合に、より多くの差が発生するかもしれないので、もう少し実験してみたいとは思う