はじめに
こんにちは。with で Android エンジニアをしている石田です。Android エンジニアになって丸 2 年が経ち、時の流れはあっという間だなと思う今日このごろです。
with の Android チーム(以下 with-android)では新機能の開発に加えて日々積極的に技術的負債の解消に取り組んでいます。 2022 年度はマルチモジュール化に取り組みはじめました。おそらく 1 年間くらいかかるであろう長期の取り組みになりますが、本記事ではその途中経過を考察を交えてご紹介したいと思います。
公式のモジュール分割ガイド
先日公式にて Guide to Android app modularization というモジュール分割に関するガイドが公開されました。これによりモジュール分割も Android 開発において当たり前のことになっていくのかなと思います。 このガイドでは 推奨アーキテクチャ における推奨モジュール構成が分かりやすく詳細に説明されています。
例えばモジュール分割によってのみ達成できる利点として以下の 3 つが挙げられています。
- Reusability (再利用性)
- Strict visibility control (厳格な可視性の制御)
- Customizable delivery (カスタマイズ可能なデリバリ)
- ※with-android では現在 Dynamic Feature を使用していません
また、モジュール分割によっても達成することができる利点として以下の 5 つが挙げられています。
- Scalability (スケーラビリティ)
- Ownership (オーナーシップ)
- Encapsulation (カプセル化)
- Testability (テスタビリティ)
- Build time (ビルド時間)
このようにモジュール分割をすることには非常に多くの利点があり、その名前以上に大きなパワーを持っていると言えると思います。ある程度の規模のコードベースの Android アプリではやらない手はないかなと思います。反対にモジュール分割における落とし穴の記載もあるので注意しておくとよさそうです。
nowinandroid はちょうどこの公式ガイド通りのモジュール構成になっているので、実際のコードで確認したい場合はこちらを参照するとよさそうです。
以下、本記事における「マルチモジュール」とはこの公式ガイドにあるようなモジュール構成を指すものとします。
with-android におけるこれまでのモジュール構成
モジュール分割に着手する前の with-android のモジュール構成を説明します。
with-android ではクリーンアーキテクチャを意識した 標準的な MVVM を採用しています。モジュール構成も MVVM をそのまま表現した形になっていて、ざっくりいうと data
, domain
, presentation
といったモジュールが存在するだけでした(実際のモジュール数は約 20 個)。
■ domain モジュール: モデル、リポジトリ、ユースケース など ■ data モジュール: API、エンティティ、永続化 など ■ presentation: UI、ViewModel、遷移 など ※ 図の矢印は依存の方向を示しています
モジュールの数は最小限で with-android の歴史とともにそれぞれのモジュールの肥大化が問題となっており、そろそろ何とかしたいなという状況でした。
モジュール分割に踏み切った理由
モジュール分割による利点は上で紹介した公式ガイドの通りなのですが、with-android において重要なポイントは次のようなものが挙げられると考えました。
ビルド高速化
Gradle には キャッシュや Incremental build の仕組みがあるので一概には言えないですが、1 つ 1 つのモジュールサイズが大きいことはビルドパフォーマス低下を招きます。 ちょっとコードを変更しただけなのにビルドに時間がかかると待ち時間が発生するだけでなく、作業の集中力が切れてしまう原因にもなります。 マルチモジュール化によりビルドの並列性が向上し、ビルドの高速化が期待できます。
厳格な可視性の制御
Kotlin には package private
がありません。クラスの可視性を狭いスコープに閉じ込めるにはモジュールを分ける必要があります。
マルチモジュール化によりモジュールの外から見える必要のないクラスを非公開にすることができます。
目的のコードを早く見つけられる
モジュール分割を細かく行っていないと Android Studio 上ですべてのモジュールを読み込む必要があります。すると目的のコードを見つけるのに時間がかかるので気持ちよく開発することができません。Project View にも大量のコードのリストが並ぶことになり、視覚的にもノイズになります。 マルチモジュール化により、作業中のモジュールが依存しているモジュールだけを読み込んで作業できるようになります。
再利用性
with-android ではまだ至っていませんが、マルチモジュール化により with の一部の機能だけ(例えばトーク機能のみ)を切り出したミニアプリを作ることができるようになります。今後のサービス成長の中でアプリの機能は増え続けることが予想されるため、素早い開発サイクルを回すためにも近々実現したいと考えています。
モジュール分割作業
ボトルネックはどこか
with-android の場合、一番のボトルネックは presentation モジュールが巨大であることです。計測したところ presentation モジュールの全体に対するコード行数の割合は 7 割であり、ビルドコスト も Build Analyzer によると 7 割を占めていました。
presentation モジュールに問題が集中していることが分かったので、presentation モジュールを共通機能(core)と個別機能(feature)で分割していくことにしました。このあたりは公式ガイドと同じ方針です。
domain や data モジュールも同様に分割していきたい気持ちはありますが、まずは presentation モジュールの分割に集中することにしました。
実際の作業
施策の実装で既存機能に改修を入れるとき、新規機能を作るとき、手が空いたときなど様々なタイミングでモジュール分割を行っていきました。with-android は 4 名のチームですが、分担するとコンフリクトが起きやすくなったりなどの影響がありそうだったので筆者がほとんどの作業を行いました。
肝心のどうやるかですが、presentation モジュール内のクラスの依存関係はこれまで特にルールが存在していなかったためある程度の複雑度があり、残念ながら自動でモジュールを分割するといったことは不可能であると考えました。よって Kotlin, XML, リソースなどを手動で移動していきエラーが出た箇所をよしなに修正するという作業を繰り返しました。
Android Studio(IntelliJ)には 依存関係分析機能 があり、あるクラスがどのクラスに依存しているか(またはどののクラスから依存されているか)を調べることができます。 特に複雑な部分ではこの機能を使いながら紐解いていきました。
Code > Analyze code > Dependencies... Code > Analyze code > Backward dependencies...
モジュール分割を進めていくと画面遷移をどうするかという問題が発生するのですが、今のところは遷移用のインターフェースを介して遷移するというシンプルな方針を取っています。
結果
作業前後のモジュールサイズの変化を可視化してみました1。 1つ1つの円がモジュールに相当し、面積はモジュールサイズを表しています(簡単のために モジュールサイズ = Kotlin の LOC としています)。 色が黄色に近いものほど大きいことを表しています。
【Before 2022/03】
半年前はモジュールが 約 20 個で、上部にある黄色の presentation モジュールが圧倒的に大きいことが確認できます。この状態が半年間で次のように変化しました
【After 2022/08】
前後で比較すると以下のことが言えます。
- モジュール数が約 2 倍に増えた (約 20 個 → 約 40 個)
- モジュールサイズの差が小さくなり、バランスが良くなった
- 依然として presentation モジュールが最も大きい
いかがでしょうか。作業途中ではありますが前後で比較するとそこそこモジュール分割の度合いが改善されたのが確認できると思います。
モジュール分割作業を進めていく中で思ったこと
- モジュール分割作業は自動化が難しく手作業になるため、根気のいる作業になります。担当者を交代しながら集中的に作業するのがよさそうだと思いました。
internal
が付いていることで「あ、このクラスはこのモジュールだけで使うんだな」ということがひと目で分かるようになり、妙な安心感があるなと感じました。また普段から Visibility を意識する癖がついたように思いました。- 日々の開発の中でビルドが明らかに早くなったと感じる場面が多々あり、開発がしやすくなったなと思いました。
- 頭で分かっていてもついつい NG な依存関係を作ってしまっている部分がありました。そこで module-graph-assert という Gradle プラグインを導入してモジュール依存関係のルールを強制するようにしました。 これは最初にやっておけばよかったと後になった後悔したことの 1 つです。
- モジュールの数がかなり増えたので、
build.gradle
の記述を Composite build で共通化する必要がありそうだなと思いました。
まとめ
本記事では with-android におけるモジュール分割の取り組みについてご紹介しました。まだ道半ばではありますが、その効果にはかなり満足しています。より綺麗で快適なコードベースを目指して残りの作業も行っていきたいと思います。モジュール分割作業完了後は with の一部の機能だけを切り出したミニアプリを実現していきたいと思っています。
参考リンク
- Guide to Android app modularization
- Guide to app architecture
- nowinandroid
- Dependencies analysis
- module-graph-assert
- Composite build
-
図は R の packcircles というライブラリで作成しました↩