こんにちは、Sansan 技術本部 Mobile Application Group に所属している iOS アプリエンジニアの相川です。
本記事は Sansan Advent Calendar 2021 の23日目の記事になります。
現在 Sansan の iOS アプリチームでは開発環境の改善を目的としたマルチモジュール化を行っています。
絶賛行っている最中のマルチモジュール化の技術的な実現方法などについては、作業が進んだらいつか記事にしたいなと思っています。
本記事では Sansan の iOS アプリチームでマルチモジュール化を推進するためにどのようなことをやってきたのかということについて焦点を当てて以下の流れで説明していきます。
- 過去のマルチモジュール化の失敗とそこから得た学び
- マルチモジュール化のために実施していること
- マルチモジュール化ロードマップの整理
- チーム内での共通認識を固めるための Discussion の実施
- おわりに
- マルチモジュール化を行う上で参考にさせて頂いている発表・記事
過去のマルチモジュール化の失敗とそこから得た学び
実は Sansan iOS チームでは昨年にマルチモジュール化を少し進めていたことがありました。
しかし、そのマルチモジュール化は結果としては失敗が多く残るものとなってしまいました。
具体的には以下のような失敗がありました。
- マルチモジュール化についての知識を持つメンバーが不足していたため、手探りで進める形になってしまった
- マルチモジュール化の目的・メリットをしっかりと定めないまま実施してしまった
- チームへの共有が不足していたため、開発メンバーに混乱を招いてしまった
振り返ると多くの失敗がありましたが、そこから学べたことは数多くありました。
それらについて軽く説明しようと思います。
マルチモジュール化についての知識を持つメンバーが不足していたため、手探りで進める形になってしまった
弊チームには本格的なマルチモジュール化の経験を持つメンバーはいない状態でした。そのため、インターネット上に存在するマルチモジュール化についての記事を漁ったりはしたものの、マルチモジュール化がどのようなものでどのように進めるべきかという明確な指針はないまま作業を進めることとなりました。
もちろん知識を持つメンバーがいないこと自体は様々な開発においてある程度仕方のないことではあると思います。しかし、知識が不足していることに加えて次以降に挙げるような問題があったため、結果としてしっかりと意味のあるマルチモジュール化を行うことができたとは言えない状態になりました。
マルチモジュール化の目的・メリットをしっかりと定めないまま実施してしまった
こちらが1番の失敗だったと思っています。
今年は iOSDC などを始めとして iOS 界隈のマルチモジュール化についての話が熱かったと思っているため、マルチモジュール化に関する知見はさらに蓄積されていると感じます。
しかし、過去にマルチモジュール化を行っていた当時は自分の調査方法も悪かったせいか、マルチモジュール化のメリットとして「インクリメンタルビルド時間の向上が期待できる・モジュール間の依存関係が明確になる」といったものしか理解することができませんでした。
これらのメリットについて詳しく説明されている記事はありましたが、Sansan iOS アプリでマルチモジュール化を行った際に「どれほどインクリメンタルビルド時間を向上させることができるのか」「どれほどモジュール間の依存関係が明確になるのか」という部分をはっきりさせることができないまま、実際にマルチモジュール化を進めてしまいました。
このようにメリットをしっかりと理解していなかったため、当然マルチモジュール化の目的も曖昧なものになっていたと感じます。
目的が曖昧ということにより、マルチモジュール化を行った際の効果測定がしにくくかったり、開発メンバーを含むステークホルダー(特に PdM など)に対してメリットを伝えにくくなってしまうという問題が実際に起きてしまったため、目的を明確にすることの重要性を改めて認識することができました。
チームへの共有が不足していたため、開発メンバーに混乱を招いてしまった
こちらも大きな問題の一つでした。
もちろんマルチモジュール化のような設計に大きな影響を与えるものについてはチームにしっかりと共有することが非常に重要であることは認識していました。
しかし、それであっても十分に共有できていたとは言えず、結果として最初のマルチモジュール化を行った後は開発メンバーが混乱してしまうという事態が発生していました。
ちなみに当時実際に自分が行ったチームへの共有は以下のようなものでした。
- 開発用 Wiki にマルチモジュール化の進行状況・ディレクトリ構成等について記載しそれを共有
- 一度チームミーティングを開催し、上記 Wiki を用いながらチームの疑問点などを解消
これらの何がいけなかったかというと、「共通認識を固めずに進めたことに加え、全てが事後の共有になってしまった」ことに尽きると認識しています。
具体的に説明するとマルチモジュール化の案件に主に関わっていたメンバーは自分を含めると3人ほどであり、他の iOS チームのメンバーにはマルチモジュール化についての結果のみ共有するという状態になってしまっていました。マルチモジュール化の作業をどのように進めているのか、どのような考えでマルチモジュール化を行っているのかということについても完了後に共有することしかできていませんでした。
そのため、マルチモジュール化を行っていた以外の iOS メンバーからはマルチモジュール化後に疑問点が多く上がったり、もっとこうすべきだったのではないか?のような声も出ていました。
マルチモジュール化のために実施していること
前述したような多くの失敗、そこから得た学びを活かして今現在 Sansan iOS チームでは再度マルチモジュール化に取り組んでいます。
取り組みの前提として、世に出ているマルチモジュール化についての知見は時間をかけてしっかりとキャッチアップしました。キャッチアップ方法については愚直に様々な発表や記事を見ていっただけではあるので、本記事の最後に参考にさせて頂いた発表や記事を記載するだけにしようと思います。
キャッチアップを行った上でマルチモジュール化を円滑に進めるために、弊チームでは以下のようなことを実施しつつマルチモジュール化に取り組んでいます。
- マルチモジュール化のメリットの整理
- マルチモジュール化ロードマップの整理
- チーム内での共通認識を固めるための Discussion の実施
それぞれについて詳しく説明していこうと思います。
マルチモジュール化のメリットの整理
まず、マルチモジュール化を行うメリットについて整理しました。
前述したように、以前はざっくりと「インクリメンタルビルド時間の向上が期待できる・モジュール間の依存関係が明確になる」というメリットを掲げていました。
しかし、メリットが曖昧であり根拠に乏しく、同時期に M1 Mac が開発メンバーに支給されるということもあって「ビルド時間の問題はある程度 M1 Mac で解消されるのでは?」という疑問もありました。
そのため、マルチモジュール化についての知見をキャッチアップしたことを踏まえて、マルチモジュール化のメリットについて再考することにしました。
実際の流れとしては、自分ともう一人のメンバーで「マルチモジュール化メリットの叩き台」を議論しながら作成し、その叩き台をメンバーに共有して認識の齟齬がないようにさらに議論を進めつつメリットを整理しました。
結果として Sansan iOS アプリをマルチモジュール化する際に自分たちが掲げた目的・メリットは以下のようのものとなりました。
マルチモジュール化の目的
不要なコードのビルドを避けられる環境を作ることによって、開発効率を向上させること
マルチモジュール化による主なメリット・副次的なメリット
主なメリット
- View の開発・デザインに関わる調整(デザインレビューも含む)にかかる時間が削減できる
- モジュールごとに Xcode Previews を動作させることができるため、UI 開発サイクルを素早く回せる
- 機能開発にかかるビルドの時間が削減できる
- モジュールごとに動作するミニアプリ(参考)を作成することによって、モジュールごとの機能開発が可能になる
- QCD( Quality Cost Delivery ) を改善できる可能性がある
- モジュールごとにテストターゲットを紐づけることができるようになる。それにより、各モジュールに必要なテストだけビルドできるようになり、テスト時のビルド時間が軽減されテストサイクルを素早く回せる。安定してテストを書くことができる環境が整うことにより、今までよりテストの量・質を高めることができ、それが QA の工数減に繋がることが期待できる
- ミニアプリなどによって PR の動作確認を手軽に行えるようになるため、レビュワーの負担の軽減・レビュー品質の向上が期待できる
副次的なメリット
- メインアプリターゲットにおけるインクリメンタルビルド時間の減少が期待できる
- 自分のモジュール以外のモジュールのコードを必要とする際は import が必須になることによって、チームメンバーがモジュール間の依存関係を意識できる。静的検査も容易になる可能性がある
ここに挙げたメリットのそれぞれについて説明してしまうと、記事がさらに膨大なものとなってしまうため要点だけ掻い摘んで説明しようと思います。
前提として、私は様々なマルチモジュール化についての知見をキャッチアップしましたが、特に Cookpad さんが取り組まれている Feature 単位でのモジュール分割に関する記事等に影響を受けていて、それが上に掲げたメリットにも反映されています。
上に掲げたメリットのほとんどは、Feature 単位でモジュールを分割(+ Feature 単位のモジュールごとのミニアプリの構築)することによって享受できるものだと思っています。
Feature が何であるかということについてはアプリについても異なってくると思いますし、Sansan でも絶賛議論中ではあります。
しかし、Feature という一定の粒度によってモジュールを分割することによって、モジュールごとのビルドが可能になったりモジュールごとに Test Target を紐付けることが可能となるため、上に掲げたような多くのメリットを享受することができ得ると思っています。
メリットの詳細については割愛しますが、元々ざっくりと掲げてしまっていたメリットから脱してチーム内で事前に目的・メリットレベルでの共通認識を固めることができたことは良いことであったと感じています。
マルチモジュール化ロードマップの整理
目的・メリットについてはしっかりと整理できたものの、Sansan iOS アプリとしてどのようなマルチモジュール化を目指すかという理想像・その理想像に到達するためのロードマップについて考えることは重要でした。
実際、最初に取り組んだマルチモジュール化時点ではその辺りについて考慮できておらず、行き当たりばったりであることが多かったと思います。
理想像を考える上で重要だったことは以下の3点だったと思っています。
- 目的・メリットについて整理・理解する(前述した内容)
- Sansan iOS アプリの現状の問題点について理解する
- 目的を達成するために、どのように問題点を解決していくかを考える
目的・メリット的な部分については、異なるアプリでもある程度似通ってくるものかもしれませんが、アプリごとに抱える問題というものは異なってくるため、その部分をしっかりと理解することは非常に重要でした。
Sansan iOS アプリが抱える問題は様々なものがありましたが、マルチモジュール化していく上で特に弊害になりそうだったものは「MVC 時代(現在は VIPER アーキテクチャで開発している)に作られた LegacyModel が数多く存在している」というものでした。
具体的には VIPER アーキテクチャで開発を進めている現在は、VIPER における Entity*1 を定義しつつ開発していますが、MVC 時代に作られたいわゆる LegacyModel には以下のような問題があり、それをそのまま別モジュールに切り出してしまうことには抵抗がありました。
- 同じような View で複数の Model を共通のものとして扱うために定義された protocol に Model 自身が依存している
- レスポンスのフィールドだけではなく、View で Model を利用する時のためのロジックなどが多分に含まれている
- RealmObjects と呼ばれる Realm で扱うための Model が数多く存在している
現在は Entity を定義しながら開発しているため、上記のような LegacyModel 群をモジュールに含ませてしまうとレガシーコードによってモジュールが汚染されてしまう懸念があり、弊チームではマルチモジュール化のロードマップとして以下のようなものを定義しました。
- LegacyModel の Entity 化
- Model 自身が protocol に依存している問題の解決
- Model にロジックが含まれている問題の解決
- 適切な粒度(Feature 単位)でのマルチモジュール化
- メンバーの負担にならない・継続可能なマルチモジュール化方法の考案
- 既存のコードを全てモジュール化するのではなく、資産として扱えるようにする方法の考案
- RealmObjects の適切な扱い方についての検討
- 現時点では RealmObjects の扱い方にバラツキがあり、適切な扱い方が出来ていない部分もあるため扱い方を検討する必要がある
- Feature モジュールから RealmObjects を扱う方法の検討
もちろんこのロードマップについてもチームに共有・議論を行いながら進めていて、現在は絶賛 LegacyModel の Entity 化を行っている最中です。
また、マルチモジュール化を円滑に進めていくために「適切な粒度でのマルチモジュール化」についてもしっかりと考える必要がありますが、こちらについても少しずつ議論しながら進めています。現時点では「適切な粒度でのマルチモジュール化」を進めていくためには、以下のような点が重要であると感じています。
- Feature モジュール間の依存関係をどのように解決するか
- ある Feature A モジュール内の画面から Feature B モジュール内の画面に遷移したいなど
- 新規 Feature を作成するための負担をどのように軽減するか
- 既存 Feature に対してレガシーな機能をどのように移植していくか
この辺りについては以下のような発表・記事等を主に参照させて頂きつつ、チーム内で議論をしっかりと行いながら開発者の負担にならないようなマルチモジュール化を行っていきたいと思っています。
- クックパッド開発者ブログ: コード生成を用いたiOSアプリマルチモジュール化のための依存解決
- iOSDC Japan 2021: 大規模なアプリのマルチモジュール構成の実践
- Mobile Act ONLINE #6: uber/needleを用いたモジュール間の画面遷移とDI
チーム内での共通認識を固めるための Discussion の実施
先ほどからちらちらと話に出てきていますが、最初に行ったマルチモジュール化から得た学びを活かして、チーム内での共通認識を固めるための活動は特に意識して行っています。
弊チームの案件の進め方としては、1プロジェクトにつきおおよそ1~2人がアサインされ案件を進めていきます。
そのため、そのプロジェクトにアサインされていないメンバーはアサイン外のプロジェクトに対して、PR レビューなどでしか主な関わりを持つことができていません。
しかし、マルチモジュール化で何をやっているのかということは逐一メンバーにも把握していて欲しいため、Slack の案件チャンネルに iOS チームのメンバーを全員招待したり、週次で行っている GKPT(Good・Keep・Problem・Try) という活動内でも積極的に共有したりしています。
また、上記のような活動だけだと一方的な周知に留まってしまう可能性があるため、積極的にマルチモジュール化に関する議論の機会を作ることを意識しています。
弊チームでは週次でチームミーティングを行うために1時間ほどの時間が確保されているのですが、その時間を活用して頻繁に議論しています。
具体的には自分が GitHub Discussion 上に議論の叩き台を作成し、叩き台をもとにした議論を以下のように定期的にチームミーティングの時間を活用して行っています。
議論を定期的に行うことによって、実際にチーム内での共通認識を固めることができていると感じますし、議論中には様々なチームメンバーからの意見が飛び交うため、リモートで中々得られない活発な議論の機会にもなって非常に良いと個人的には感じています。
おわりに
本記事では「Sansan iOS アプリチームでマルチモジュール化を推進していくためにどのようなことを行っているか」ということについて焦点を当てて説明を行いました。
本記事で説明した通り最初のマルチモジュール化では多くの失敗がありましたが、その失敗から学んだことを活かして今では順調にマルチモジュール化を進めることができていると感じます。 絶賛マルチモジュール化を行っている最中ではありますが、今後もしっかりと議論を行いながらチームが納得できるマルチモジュール化を進めていきたいと思っています!
プロダクト・チームによって、マルチモジュール化の推進・実施方法は異なってくると思いますが、弊チームの推進方法がこれからマルチモジュール化を行っていくチームの一助となれば幸いです。また、アドバイスなど頂ければ非常に嬉しいので、ぜひよろしくお願いいたします!
マルチモジュール化を行う上で参考にさせて頂いている発表・記事
マルチモジュール化を適切な流れで進めていくために多くの発表や記事を参照させて頂いています。
以下に特に参照させて頂いたものを列挙させて頂いています。
- MTC2018 - Implementing TypeErasure in ViewController
- コード生成を用いたiOSアプリマルチモジュール化のための依存解決
- 大規模なアプリのマルチモジュール構成の実践
- クックパッドのエンジニアが語る、巨大で歴史あるアプリにおける破壊と創造
- 大規模なiOSアプリの画面開発を効率化するために動作確認用ミニアプリを構築する
- 機能ごとに動作するミニアプリでプレビューサイクルを爆速にした話
- ライブラリのインポートとリンクの仕組み完全解説
- 新規機能開発からモジュール分割を始めてみる
- Swift Package中心のプロジェクト構成とその実践
- iOS Tech Talk 〜 Multi module 戦略座談会 〜
- iOS Tech Talk 〜 Multi module 戦略座談会 vol.2 〜
- Mobile Act ONLINE #6 | uber/needleを用いたモジュール間の画面遷移とDI
*1:API から返却されるレスポンスのフィールドのみが定義された綺麗な構造体。View のことは気にしないため、それに関わるロジックなども含まない