見出し画像

モバイルアプリの大規模開発における組織的なソフトウェア改善の一事例と考察

こんにちは。mhidakaです。技術書典やDroidKaigiのオーガナイザーという側面以外にもメルペイ所属のAndroidエンジニアという立場も持っています(みなさんあまり知らないと思いますので書いておきます)。

今日はメルカリ・メルペイでのモバイルアプリ大規模開発での、とあるアプローチをメモしておきます。内容は社内レビューを受けてマネージャの承認が取れたものなので安心して読んでください(自分のブログで書いてるのは真面目に書くと大変そうに感じる話題だったのと、なるべく楽しんでもらえるようカジュアルな口調で書きたかったからです)

メルカリ・メルペイでモバイルエンジニアの開発対象というと主にアプリケーションです。大規模開発の重要な要素はアプリケーションだけではありませんが(考慮すべき要素はたくさんあるんですよ)今日はアプリのはなしです。本記事では一般化できるよう努めていますが大規模開発では組織や既存技術といったバックグラウンドが影響して「不思議なことをしているな」と感じる部分が少なからずあります。それには妥当な理由があって、なにかの不備やマジカルな力によるものではありません。もし本記事にそれを感じたらメルカリ・メルペイでの経験をうまく書き起こせなかった筆者の力不足によるものです。それではストーリーをお楽しみください。

どこかで適用事例として話せるといいんだけど、ずるずる書くのを躊躇しているうちに4月になってしまいました。やっていきましょう。概ね6000文字ですので読み終わるには少し時間がいります。

ざっくりまとめ

・Kotlin 1.3.31から1.3.72へアップデートするのに9ヶ月を要した
・大規模開発には想像を絶するポイントがある

ただ一言「Kotlinのバージョンをあげる」だけの作業ですが着手からリリースまで9ヶ月かかりました。具体的には2020年3月に始めて2021年1月にリリースを完了しています。長く感じますよね。ぼくも率直にそう思います。なお、このプロジェクトは順調で9ヶ月かかったリードタイムは大幅に短縮しています。今は最新の1.4.32(または1.5M2)に追従してるよ🙌

大抵のアプリ開発であれば、バージョンをちょっとあげるぐらい、えいやで変更して1日~2日だと思います。何に時間がかかるのかという解説が本記事の注目ポイントです。

個人的にはこのプロジェクトは面白くて、それなりに楽しみましたが、こんな開発環境絶対いやだ!って感じたりするひとも居ると思います。それは「not for me」ということで、めちゃくちゃ理解できます。何の前置きもなく9ヶ月って聞くと「クレイジー!やべー!!」って当然に感じるので、それを整理します。気楽にやれたと書きましたが本人の性格に依存しているというのと、もともとmhidakaの握力が尋常ではなかったという可能性があります。ある程度は読み物として楽しんでください。

大規模開発って何さ

大規模なモバイルアプリケーションのソフトウェア開発と聴いたら何人ぐらいのエンジニアが関与していると感じますか。本記事での大規模開発は20~30人を超えるAndroidエンジニアがバリバリ開発する状態です。

mhidakaの職場ではXX人のAndroidエンジニアがコントリビュートしていますが会社の方針で正確な数は非公開です(申請したら許可されるとおもうのですが面倒くさくてサボりました…。ちゃんとしたカンファレンスで話すときは穴埋めするとおもいます)。

このような大規模な開発では言語バージョンをあげるというのはひどく億劫な作業です。どれぐらいかというとAndroid StudioのAuto Migration機能を使うとプロジェクト内の数万ファイル以上に対して検査がはしり、1時間程度IDEの応答がなくなります。たとえばKotlin言語のマイナーバージョンアップであってもプロダクトのコードのどこかが壊れます

所属組織でKotlin言語のバージョンアップ作業を行っていますが、単体テストが落ちず、コードの変更が不要で、何の問題もなくあげられたケースはまだ一度も経験していません(ソフトウェアが壊れないように、または壊れても検出できるように、ありとあらゆる努力が払われている環境ですが、十分とはいえません。常に生産性を上げるための工夫をしています)。

大規模開発の背景

「壊れる」とはKotlin言語のバージョンに互換性が無い!なんたることか!とPlatformに対して怒りたくなるという意味ではありません。Kotlin言語は日々改善されていて、false positiveな型推定(本来は型が不一致していてコンパイルエラーになってほしいんだけど、運良く通っちゃったコード)などが修正される…JavaとKotlinの相互運用性の改善がある…など、いろんな要因で既存のコードが影響を受けます。

ですのでプロダクトコードの不具合・改善点が見つかったという状況に近いわけです。それを受け入れて前に進む選択が求められます。当たり前の話なんですが。しかし、組織を超えての調整の面倒くささからしばらくアプデを避けてしまっていました。一言添えておくと、ここで言及したKotlin言語のアップデート内容は、多くの開発現場やプロジェクトは影響がありません。保証できます。怖くないので安心してください。

大規模開発の背後には数多くのアプリ利用者がいます。アプリ利用者が100万人いたら発生確率が0.001%の不具合も発生期待値が10件になります(バグのはなしもたくさんしたいのですがオフトピックなので省略しますね)。というわけで大規模開発ではエッジケースだとおもっていたものを絶対に踏み抜きます。さらに実験的な実装もプロジェクトのそこかしこにあります。古い実装だって山のように残っています。辛い作業かもしれませんが、それらを改善しながら進むわけです。

所属企業では、ちょっとこの話題から目を離してしまい、1年以上古いKotlinのバージョンを使い続けることになっていました。ありていにいってバージョンアップ作業を侮ったのです。この問題はKotlin言語だけではなくてRxJavaやもろもろのライブラリも同様です。利用環境によって影響範囲の大小がありますが、これらのメンテナンスはソフトウェアエンジニアが判断して解決する問題です。

バージョンアップはサボっても、しばらくは影響がありません(Stableを使っている場合、意図的にバージョンアップしないこともあるため、サボっているのか今必要ないのかがわかりにくいかもしれません)。しかし、いつか言語の支援が欲しいケースがでてきます。CoroutinesやFlow、Channelなど使えないと悲しいですね。もしkotlinx.serializationをつかいたいとおもっても言語バージョンに依存が発生してしまうので古いままでは使えません。生産性を考えると必要なときに最適な道具を利用できるメリットは大事です。そのためにアプデも組織的にやりましょうというわけです。

課題のまとめ

盛り上がってきました。実務上のミッションは次のとおりです。「Kotlin言語やライブラリのバージョンを最新に(より正しいコンテキストでは生産性を最大にできるStableな位置に)維持する」

これを阻むことになるかも知れないAndroidプロジェクトの事情は、100モジュールに近づくほどのマルチモジュール構成とDependency Hell数万+に及ぶ対象ファイルなどがあります。

とくにモバイルアプリケーションでは配布形式が単一のバイナリファイルになるのが厄介だと感じます。マルチモジュールは独立して開発できるよう緩やかに連携できる良い方法ですが(機能開発にコミットするのに数十人とコミュニケーションをとらないといけない状態は辟易します)、最終的にビルドするものは1つのAPKファイルなので、モジュール毎でライブラリなどのバージョンが不一致するのは大変マズいわけです。

また組織的な要求もあります。バージョンアップに際して、数十人のモバイルエンジニアが協調して作業し続けられることや既存コードへの影響範囲のみきわめ、QAによる品質保証などが発生します。

コードオーナーに変更内容を確認してもらいApprovedをもらうこと、さらには2週間単位でのリリースへの追従といった既存の開発スケジュールを考えるとバージョンアップという作業1つをとっても開発プロセスがあるほうが良く、プロジェクトを進めるには、そのための整備が必要でした。

プロセス改善のストラテジー

ここからは実際に社内でつかった資料で秘情報を含まない部分を紹介します(おおむね、このプロジェクトで面白いと感じる背景は解説しおわったのであとは実際のTODOメモという趣です。追加情報がほしいとかあればmhidakaにpingしてみてください)。また正式なドキュメントは英語のもので、こちらはサブとなる日本語ドキュメントです。

画像1

画像2

画像3

アップデートに伴うリスク管理

リスクの捉え方やエンジニアリング観点でのフォローアップなども整理します。エンジニアにとって既知のことがらでも組織の関係者全員が知ってる・知った上で同じ意見である・そのように考えているというケースは稀です。ですのでこういうケアも積極的にドキュメント化しておきます。

画像4

画像5

アップデートの整理

開発プロセスは各社いろんなアプローチをとっていると思いますが特に大規模開発ではドキュメント化が大事です。音声やSlackといった瞬間的なコミュニケーションは有益で、情報量が豊富ですが何十人も参照する開発方針はドキュメントがあるに越したことはありません。

ドキュメントを書く手間という先行投資はあるものの何かを伝えるときに再利用性が高い側面や自分自身の考えの変化もキャッチアップできるストレージの側面が便利です(資料をみてもっとこうしたほうがいいよというのは教えていただきたいです🙏)

画像6

画像7

画像8

画像9

実際どうやったの?

Kotlin言語を1.3.31から1.3.72にあげるのに実際に変更が発生したファイル数はこちらのスクリーンショットのとおり全体の1%ぐらいとなりました(プロジェクトには2~3万ファイルあるので300ファイル弱の修正で概ね1%で、ユニットテストなども含まれます)。

画像10

今回のプロジェクトでは1年ごしの久しぶりのアップデートという事情以外にも、KotlinのExperimental APIの修正(kotlinx.serializationのstable対応等)も含まれていたので多少変更量が増えてしまいました。しかし9ヶ月の間もっとも大変な作業はここではありません。9ヶ月間、常にdevelopブランチに追従していくメンテナンスです。

これは同時に開発が進んでいるブランチのGUI表示ですが、じつはごく一部しかスクリーンショットに映っていません。同時に開発が進むブランチは50ぐらいはあると思います。もう多すぎて数えなくなりましたし、数えることに意味がなくなりました(独立性が高く開発されているので知らなくても普通は問題ないという構造です)。

こんな状態なので「developに追従」が意味することは日々リリースのためにmergeされまくる超巨大なマルチモジュール構造に追いつくということです。所属企業ではmerge --squashでdevelopブランチに取り込まれるので大体の概算になりますが1週間に数千コミットは進みます。容赦がない。

このような巨大なものごとを扱う当事者となるとは思いませんでしたが、言語のアップデートのリリースに至るまでの苦労には、毎週発生するコンクリフトへの修正や、全モジュールへの影響を確認するためのビルド&テスト(運がいいと10インスタンス以上、ほぼ無尽蔵のリソース利用を許されたCircleCI環境で40~50分で終わります)が含まれています。

一番つらかったのは、ビルドエラー等よりはUnit Testのエラー解析です。不具合を見つけるのは良いフィードバックなのですがローカル環境での再現および修正確認には無尽蔵のリソースがつぎ込めません。しかも把握していない機能のテストです。どうしても1試行あたり数十分かかり非効率を感じました。またマルチモジュール構造がゆえに各々のモジュールに対してビルド&テストしないといけないのもGradleキャッシュが効きにくく(部分的に効いていますが、特定の機能モジュール開発ほど最適化されていません。結局Clean Buildすることも多かったです)これは時間がかかって大変でした。手元のPCのメモリとCPUを全部もっていくので何もできません。最も記憶に残るのはdevelop取り込み時のmerge衝突解消の困難さです。squashしているのでgit mvなコミットも吹き飛んでおり、ファイル移動やリネームがうまくmergeされないケースがありました(出来るケースもあったはずですが出来る場合は一瞬なので残念ながら記憶に残りません)。というわけでいくつかは手でマージし、コンクリフトを解消することになります(一番つらかったことを書くはずが3つも書いてしまいました)。

これらはWeeklyで対応していましたが、mergeと動作確認というメンテナンスでmhidakaの工数換算で1日かかりました。1週間につき20%の工数を消費したわけですが(これは規模を考えるとリーズナブルだったのかも…)都度、最新の開発状況に応じてKotlinアップデートのため、こうしてほしいというフィードバックや良い書き方などのディスカッションをコードオーナーに伝えていきます(全員が言語の変更点に熟知するのは難しいためメルペイ/メルカリなど組織ごとにリードを置いて作業していくことになりました)。

もちろんmhidakaが頑張っている9ヶ月の間に、Kotlin言語もアップデートしていきます。我々がリリースする間際は年末ということもあり「JetBrainsのKotlin開発チーム、もういい…!休んでくれ…!ハッピーホリデーしてくれ!たのむ!」という気持ちがなかったといえば嘘になります。

得られたもの

mhidakaが欲しかったものは端的にはGitHub PRでのLGTMでありMergedでした。幸いリリース後は起因するトラブルなく、過ごせています。

画像11

言語のバージョンアップをきちんとした結果、ビルド速度は30%以上速くなり、型推論や言語機能の改善により、ソースコードの品質にも貢献しています。日常的なライブラリ更新(またはアップデートするかの判断の組み込み)は、この規模のプロジェクトでも十分にペイするし、しなければ全体のパフォーマンスに影響すると断言できます。

振り返ると、今回はアップデートをサボりすぎましたね、というほかないのですがそれでもユニットテストの拡充や開発プロセスの整備による組織横断な合意形成も得られました。得難い先例であり、成功だったと思います。

2021年1月のリリース以降もKotlin言語のバージョンアップに追従していっており、整備された開発プロセスに基づいて今回より断然に短いイテレーションでリリースできそうです。いいですね。

今回のおはなしは機能開発からは一歩離れています。機能開発はモジュール単位で行われているのでテストやビルドが遅いと感じることはありません。

Androidアプリケーションはビルドすると単一のバイナリになるという点において大規模開発と相性が悪いところがあり、言語やライブラリなどのアップデートを困難にしていました。解決のためには適切な開発プロセスの整備・エンジニア組織での合意形成を通じて、これらの困難にチャレンジしていくことになります。mhidakaが欲しがったLGTMとMergedのためにはエンジニアリングでの整備とアプローチが必要でした。

もっと詳しい話や資料が見たい、質問があるというかたは個別にnoteでメッセージを送っていただいてもいいですし、Twitterで @mhidaka に連絡ください。

次はもうちょっと雑な記事でお会いしましょう!

この記事が気に入ったらサポートをしてみませんか?