⚔️

InterfaceとImplを分けるべきなのか論争

2024/12/20に公開

この記事は CAMPFIREアドベントカレンダー 20日目です。
https://qiita.com/advent-calendar/2024/campfire

本記事はSteve Sudoとの共著です。
私たちは今、CAMPFIREの子会社LiveforでFlutterアプリを開発・運用しています。

昨日はCAMPFIRE全社の忘年会で、芸人さんに来ていただいたり、過去のクラウドファンディングのリターンを景品にしたBINGO大会で大盛り上がりでした 🍻🍺🎉

前置き

さて、読者のみなさんはこんなコードを見たことはありませんか?

project_model.dart
abstract interface class ProjectModel implements ModelBase {
  // 色々なinterface定義
}
project_model.impl.dart
class ProjectModelImpl implements ProjectModel {
  // interfaceの実装
}

わたしはあります。
(現在運用中のコードがこうなっているのだから当たり前ですね。)

このコードを見て疑問を抱きました。
果たして、本当に分けて書くべきなのか?と 🤔
というわけで、なぜ分けているのかを深堀っていきましょう。

🍏分ける派 🆚 分けない派🍅 それぞれの主張

🍏分ける派の主張は以下のようなものが挙がりました

  • テスタビリティの向上
  • サマライズに利用する
  • 依存性の逆転に必要

対して、
🍅分けない派の主張はこちらです

  • コードリーディングしやすい
  • ボイラープレートが減る

それぞれを両者の視点から検討してみましょう

1つめ テスタビリティの向上

🍏分ける派
InterfaceとImplを分けてDI(Dependency Injection)するとImplではなく、MockをInjectすることで、
Unit Testが書けるようになります。

🍅分けない派
ツールが無い頃は分けていないと、できなかったかもしれないですが、
Mockito(自動生成ツール)があれば、分けなくてもMock可能です

2つめ サマライズ

🍏分ける派
InterfaceはそのInterfaceが提供している機能の一覧がわかりやすいです。

🍅分けない派
実装classのみであっても、IDEの機能で補えるのではないでしょうか?

  • VS Codeなら OUTLINE機能
  • Android Studioなら Structure機能

※ コメント(ドキュメント)が見えない分 微妙にわかりずらいかもしれません

3つめ 依存性の逆転

恥ずかしながら、この記事を書くまでイマイチ理解できていなかったです。
依存性の逆転については解説記事が色々公開されていますが、これが個人的にわかりやすかったです。
https://www.nuits.jp/entry/easiest-clean-architecture-2019-09

今回の論争における重要なポイントだけ抜き出します。

🍏分ける派
依存の方向と処理の方向を逆転させるためには必須の技法であることです。
先程の記事では、依存の方向を
usecase → infrastructure
から
infrastructure → usecase
にすることにできています。
これはimportの関係としてそのようになっているということになります。

そうすることで、オニオン型のアーキテクチャの真ん中にusecaseを置くことができて
安定したアプリケーションが実現できる。
また、多くのファイルからimportされているファイルを更新するとビルド時間が増えるので、
安定したもの(先程の記事ではusecase)を依存の中心とすべきである。

という主張です。

🍅分けない派
import関係としての依存の方向は本質的ではなく、この部分を重視すべきだと考えます。

Usecaseの関数内で、APIの戻り値を、usecaseのオブジェクトに詰めなおしています。
つまりusecaseがWebサービスに依存してしまっているのです。
ではどうすれば、よいでしょうか?
JSONオブジェクトからusecaseのオブジェクトへの詰め替えはInfrastructureに実装します。

また、ビルド時間に関しては、C#やC++のようなビルドに時間がかかる言語ではかなり重視すべき内容ですが、
Flutter開発においては依存関係は大きな問題になったことがなく、無視して良い観点であるという主張です

4つめ コードリーディングしやすさ

🍅分けない派
別れていると、コードリーディングの際にInterfaceに当たると、実装を探すのに時間がかかり、
コード理解の邪魔になってしまいます。
実装に飛ぶショートカットもあるが、自動生成のMockもヒットするため、探し出すのに時間がかかり、思考が途切れてしまう。

🍏分ける派
「コードを読み込まなくてもいいのは良いコード」と、よく言われます。
転じて、Interfaceを読めば構成がわかるようなコードであれば良いコードであるという主張です。

  • VS Code: Go to implementation(⌘ + F12)
  • JetBrains: Go to implementation(⌥ + ⌘ + B)

5つめ ボイラープレートが減る

🍅分けない派
規模の大きなプロジェクトにおいてはスピードを優先した方がいいケースが多々ある。

🍏分ける派
必要なものとの割り切りが必要。誰が書いてもコーディングが同じようなものになる仕組み作りに寄与する。

整理

表にするとこんな感じでしょうか?

🍏分ける 🍅分けない
テスタビリティ 😍 😍
サマライズ 😍 😭
依存性の逆転 😍 😶
コードリーディング 😭 😍
ボイラープレート 😭 😍

まとめ

さて、いかがでしたでしょうか?結論はあえて、書かないことにします。
皆さんのご意見・ご感想・反論記事へのリンク等、コメントいただけると嬉しいです。

📺 宣伝1

Liveforではエンジニアを募集しています。
https://www.wantedly.com/projects/1817166

📻 宣伝2

個人でも友人と個人開発でゲーミフィケーション記録アプリ「HibaQuest」を作っています。
よければお試しください。 → https://hiba.quest

Discussion