こんにちは。 この記事は、技術本部 Mobile ApplicationグループでSansan(※プロダクトとしてのSansan)のAndroid開発を行っている、桑原、小林、鎌田、原田の共著でお届けします。
今回は、アプリで使用しているライブラリのアップデートについて、
- Sansanではどのようなポリシーで行っているのか
- そのポリシーを守るためにしていること
- そこから見えてくる課題
- そして今後について
をお話します。
Sansanにおけるライブラリアップデートのポリシー
アプリで使用しているさまざまなライブラリは日々更新されており、機能の追加や変更、脆弱性の修正などが行われています。つまりアップデートを長期間放置していると、新機能を使えないことで開発生産性が落ちるなど、重大なセキュリティリスクを抱えることにつながります。
Sansan / Eightでは、お客さまの大切なデータをお預かりしているため、特にセキュリティリスクは強く意識しなければなりません。
そこでMobile Applicationグループでは、インシデントが発生した際に対応にあたるCSIRTと協議し、四半期ごとに1回、アプリで使用しているライブラリのアップデートを実施することとしています。ただし、プロダクトの開発で緊急対応をしていたり、優先すべき案件があったりする場合はこの限りではありませんが、基本的に実施できています。
上記の要件を実施するため、プロダクトごとにそれぞれ業務フローに合わせてライブラリアップデートを実施しています。その中でも今回はSansan Androidチームでの取り組みを具体的にお話していきます。
ライブラリアップデートの実際の業務フローについて
Sansanプロダクトの開発では、プロダクトの機能開発案件と、ライブラリアップデートのようなより技術面の課題を解決する案件を優先度判断する際、同じ土俵で比較して判断しています。 「機能開発ばかりが優先され、技術的な負債の返済やチャレンジが疎かになる」ようなことはない環境と言えます。
ライブラリアップデートについても、四半期ごとに毎回まずは優先度判断を比較する場で議論して始めます。このプロセスは、Sansanの技術基盤を最新かつ安全に保つために不可欠です。
ここからは、この優先度設定から一歩進んで、Sansan Androidチームがどのようにライブラリアップデートを具体的に実施しているかを詳しく見ていきましょう。
まず、ライブラリアップデートのプロセスを効率化するために、私たちはRenovateというツールを採用しました。このツールが重要な役割を果たしている理由を、次に説明します。
RenovateはMend社が開発した無料の依存関係更新ツールで、SansanのAndroidチームでは昨年からこのツールを導入しました。
導入前は、Android Studioの提案する更新を基に、手動で1つずつ確認していましたが、これが非常に非効率的でした。そのため、更新プロセスを少なくとも半自動化することで効率を向上させたいと考え、Renovateを導入しました。
Dependabotも同様の機能を提供していますが、PR(プルリクエスト)のグループ化や更新ルールのカスタマイズが柔軟に行えるため、Renovateを選択しました。
Renovateの特徴
- 新しいライブラリのバージョンがリリースされると、自動的に更新用のPRを生成してくれます。
- マージプロセスの自動化も可能です。
- 内部統制により、2人からApproveを得ないとPRをマージできないため自動マージは行わない方針にしています。
renovate.json
を通じてPRのグルーピングや特定のライブラリの更新除外が設定できます。- 例: Compose関連のライブラリは一括でグループ化する。
Sansan Androidチームのrenovate.json設定
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", ":timezone(Asia/Tokyo)" ], "branchPrefix": "library_update_17y_1q/feature/", "baseBranches": [ "library_update_17y_1q/develop" ], "draftPR": true, "prConcurrentLimit": 100, "packageRules": [ { "groupName": "Jetpack Compose", "matchPackagePatterns": [ "^androidx\\.compose:", "^androidx\\.compose\\.animation:", "^androidx\\.compose\\.compiler:", "^androidx\\.compose\\.foundation:", "^androidx\\.compose\\.material:", "^androidx\\.compose\\.runtime:", "^androidx\\.compose\\.ui:" ] }, ・・・ ], "labels": [ "library_update_17Y1Q" ] }
Renovateのこれらの特徴を活かして、次に私たちの具体的な更新プロセスを紹介します。
具体的な運用(更新プロセス)
ライブラリアップデートを効率的かつ効果的に行うため、次の更新プロセスで運用しています。
PRのドラフト解除
Renovateはライブラリが更新される度にPRを自動的に更新します。これは非常に便利ですが、PRが更新されるとビルド、テスト、Lintなどのプロセスが自動的に実行され、結果としてCIプロセスが頻繁にトリガーされてしまいます。
そのため、無駄なCI実行を防ぐためにRenovateによって作成されたPRは初期状態でドラフトとして設定しています。作業を開始する前に、このドラフト状態を解除します。
リベースの実行
プロジェクトの
develop
ブランチの最新状態を反映させるためにリベースを行います。Renovateによって作成されたPRには、GUIで簡単にリベースを実行できるチェックボックスが提供されています。PRに調査内容の記載
Sansan Androidチームでは全てのライブラリアップデートに対し、その変更点をひとつずつ丁寧に検証し、その結果を詳細に記録するという徹底した風習を守っています。このプロセスにおいて、次の点を特に重視しています。
- ライブラリの概要
- メジャーではないライブラリに関しては、レビュワーが理解しやすいよう、その基本情報をまず整理します。
バージョン間の変更点とそのアプリへの影響
- 各ライブラリのバージョンアップに伴う変更点を調査し、それが当社のアプリケーションに与える影響を詳細に分析します。
- 変更点を調べるにあたって、Renovateは差分をサマリしてくれるため非常に便利です。
確認した事項
- 変更点に基づいて行った確認作業の内容を、記録し、アプリケーションの安全性と品質を保証します。
- ライブラリの概要
更新しないPRのクローズ
特定の事情(他のライブラリやAndroid SDKとの依存関係等)により更新しない場合はPRをクローズします。クローズされたPRは、更新があっても自動的には生成されません。
そのため、次回のライブラリアップデートのプロジェクトで再度確認対象に戻します。
レビュー依頼
問題がないと判断した場合は、レビューを依頼します。
マージ
2名の承認(approve)を得た後、PRをマージします。
このプロセスを繰り返し、全ての更新対象ライブラリがアップデートされた後、次のライブラリアップデートの準備作業に移ります。
この準備作業では、後述するGitHubのカンバンボードを自動更新する設定をしています。これには renovate.json
ファイルの設定更新が必要です。
具体的には、次回のアップデート対象に合わせて labels、baseBranch、branchPrefix の値を更新する作業が含まれます。
ライブラリのアップデートが効率的に進むようにするためには、適切なタスク管理が必要です。
次に、タスク管理のアプローチを紹介します。
具体的な運用(タスク管理)
プロジェクトを進行させる上で、タスク管理の効率化は重要です。
この目的を達成するために、Sansan Androidチームではプロジェクト開始時にGitHubのカンバンボードを活用し、タスクのステータスを適切に分類しています。このアプローチにより、タスクの進捗状況やタスクの粒度が明確になり、残っているタスクを基にプロジェクト完了までの時間を推定できます。
このタスク管理方法はRenovateの機能とは独立していますが、タスクの透明性を確保し、管理をよりスムーズに行うためにこの運用方式を組み立てました。
- ステータスの種類:
- NoStatus: RenovateがPRを生成した際、Automationによりここに配置されます。
- ToDo:案件開始時にNoStatusのタスクをすべてここに移動します
- L:CIの修正や調査に時間がかかると予想される、優先度の高いタスク。
- M:CIが失敗しているタスク。
- S:CIが成功しているタスク。
- In Progress:現在作業中のタスク。
- Merged:マージ済みのタスク。
- Closed:クローズされたタスク。
- 運用のポイント:
- GitHub ProjectのAutomation機能の活用:
- チケットのステータス移動を自動化しています。タスクがマージされると自動的に「Merged」カテゴリに、クローズされると「Closed」カテゴリに移動されます。
- Renovateによって自動生成されたPRは「NoStatus」カテゴリに追加されるように設定しています。これにより、案件開始時に作成されたPRは「ToDo」カテゴリに、それ以降に生成されたPRは「NoStatus」カテゴリに分類され、効果的に区別できます。
- 緊急性の高いタスクへの対応:
- リリースリスクや未知の問題を早期に解決するため、CIエラーやビルドの失敗など緊急性の高いPRは「L」または「M」として分類し、優先的に取り組んでいます。
- GitHub ProjectのAutomation機能の活用:
このセクションでは、Sansan Androidのライブラリアップデートのフローについて、Renovateツールの導入から更新プロセスの具体的なステップ、タスク管理の方法まで、詳しく見てきました。
これにより、ライブラリを最新の状態に保ちながら、高品質なアプリケーション開発を継続するための基盤を築いています。
しかし、このプロセスは完璧ではありません。
次のセクション「ライブラリアップデートの品質保証に係る課題と解決」では、このアップデートプロセスにおける品質保証の課題に焦点を当て、それらに対する私たちの解決策を紹介します。
ライブラリアップデートの品質保証の難しさとその対策
アプリ開発において、品質保証は重要です。それはライブラリアップデートでも例外ではなく、それどころか通常の開発案件以上に気をつけるべきポイントがあります。ここでは、ライブラリアップデートでの品質保証の難しさと、それに立ち向かうため私たちが行っている対策についてお話しします。
思わぬところがデグレする可能性がある
ライブラリ更新による影響範囲が広い場合や、リリースノートの情報が不完全であると問題を見落としやすくなります。また、ライブラリ自体に公開されていないバグが潜んでいる可能性もあります。Sansanでは、以前にライブラリアップデートで更新しようとしたComposeでバグを踏んでNested Scrollを行う画面でスクロール時にカクつきが生じる問題がありました。一見すると気づかないような細かな問題にも注意をする必要があります。
本番ビルドでのみ発生する問題
開発ビルドでは発生しないが、本番ビルドしたときに発生する問題もあります。これはコード圧縮・難読化の適用有無によるものが多いです。ライブラリのアップデートによってProguardまわりに変更があっても、ビルド時に適用していないと気づけません。最悪の場合、リリースまで問題に気づかないということもあり得るため、開発中もコード圧縮・難読化を有効にしてビルド・動作確認をすることは重要です。
Sansanにおけるテスト体制
では、問題へ立ち向かうために私たちがどのようにテストを行っているかについてお話します。Sansanでは、大きく分けて開発者によるホワイトボックステストと、QAチームによるブラックボックステストを行っています。
- 開発者によるテスト ではPRを出す前にライブラリの変更点から推測される影響箇所を要点絞って確認します。
- QAチームによるテスト ではアプリの機能全体を網羅的に確認します。確認項目が多いこと、サポートしているOSを網羅するため実施には18人日程度の工数を要します。
前述した「思わぬところがデグレする可能性がある」ことに加えて、影響範囲がアプリ全体に及ぶことから、通常の開発案件よりも時間をかけて手厚くテストを行っています。
リリース
手厚くテストを行ったとしても、問題がすり抜けてしまうリスクはまだあります。そのため、リスクの高い案件をリリースする時は一気に100%公開にするのではなく、一週間程度かけて段階的にリリースする運用をしています。そうすることで、問題があった場合のユーザーへの被害を抑えることができます。
以上、品質保証の難しさとそれに立ち向かうための取り組みでした。アプリ全体への影響をカバーするために開発開始からリリースまでのリードタイムがどうしても長くなってしまっているため、今後はデリバリー面の向上も図っていきたいですね。
品質保証以外の観点で生じた課題
ここまではライブラリアップデートを通じた課題を品質保証観点でお伝えしましたが、もちろん実際にアップデート作業をしていて発生する問題はそれだけではありません。アップデートの具体的な課題やタスク運用上起こった課題とその対応に関してお伝えします。
他のライブラリとの依存を考慮する必要がある
プロジェクトに導入されているライブラリは相互に依存しあっています。
そのためライブラリの依存関係を考えずに手当たり次第アップデート作業を進めようとすると、思いがけない所でビルドエラーが起こり、思うように進められないことが多くあります。
そのため、依存関係を把握しながらエラーが起こらないように順番を考えながらアップデートを進める必要があります。
Sansan Androidチームでは、次のような課題が挙げられていました。
- 思いがけないエラーの存在を考慮するためアップデートにかかる時間の見積もりが他の開発案件と比較して困難なこと
- アップデート内容などによって対応を変える必要があるため過去のナレッジを適用しにくいこと
アップデートする順番は各ライブラリのアップデート内容によって変わるため、具体的な対応策は一概には言えません。が、直近のライブラリアップデートでアップデートが簡単ではなかった例をナレッジの一部として以下に挙げます。
- 多くのライブラリが依存しうるライブラリや、多くの箇所で使用されうるライブラリは影響範囲が広い分エラーが起こりやすいため先にアップデートするのが無難です。
kotlinはdagger・hiltやandroidxなど多くのライブラリが依存しているので真っ先にアップデートした方が良さそうです。
- 例えばdaggerの(2024/01/19時点で)最新であるv2.50を、kotlin v1.7.0が導入されている環境でビルドすると、対応バージョンが異なる旨のエラーが出ます。エラーメッセージを見る限り、kotlinをv1.7.1以降にアップデートしてからdaggerのアップデートを行うべきのようです。
Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.9.0, expected version is 1.7.1.
Androidアプリのほとんど全ての画面で使用されているであろうライブラリも先にアップデートした方が無難かもしれません。
- firebase系のライブラリを使用している場合は、firebase-bomあるいはfirebase-coreのような中心となるライブラリを先にアップデートしないと、バージョンが対応していない旨のエラーが起こり他のfirebase系ライブラリをアップデートしないといけない可能性があります。
- kotlinとjetpack_compose_compilerはバージョンごとに1対1対応しているため、kotlinをアップデートしたい時はjetpack_compose_compilerも必ず同時にアップデートしないといけません。
運用についての章で少し触れたように、Renovateには複数のライブラリをひとまとめのPRにするグルーピング機能があります。次回のライブラリアップデートから、これらの他のライブラリと依存関係の強いライブラリはグルーピングして、一気に動作確認と対応を進められるようにしようと考えています。
SDKバージョンとの依存を考慮する必要がある
targetSDK34対応をしていない状態でライブラリアップデートに取り掛かると、targetSDK 34が前提のライブラリでは必ずビルドが失敗します。
この場合、CIが失敗している全てのPRで、
- 「targetSDKが低い」ことが原因でビルド失敗しているのかどうかを確認して
- そのライブラリでtargetSDK33に対応しているバージョンを探してダウングレードして
- ダウングレードした状態であらためてビルドできるか確認する
- 他の原因でビルドできなかったらその解決にあたる
…という作業が多くのライブラリで発生して、事前にtargetSDK34対応していれば発生しなかったであろう工数を使うことになってしまいます。
今回はtargetSDK対応のプロジェクトがライブラリアップデートより後になってしまったためこのような問題が発生しました。 新しいバージョンのAndroidがリリースされた際、既存のアプリは新しいAndroid上で動作確認をしているのですが、targetSDKの更新は別途確認する必要のある項目があるためプロジェクトを分けています。 次回からはライブラリアップデートより前にtargetSDK対応のプロジェクトを行うようにする予定です。
Renovateが自動でPRを作り続けるため、PRがなくならない
Renovateはライブラリのアップデートを検知するたびに自動でPRを作成します。
もちろんライブラリアップデートを行なっている最中にもPRが作成されるため、「今存在する全てのPRをアップデートしよう」と考えてしまうといくらアップデート作業を続けていてもPRが増え続けて終わりが見えない作業になってしまいます。
そのため、事前にどのライブラリをどのバージョンまでアップデートするかをチームやプロダクトのステークホルダーなどと取り決めてから、ライブラリアップデートの着手をするようにしましょう。
Sansan Androidチームではライブラリアップデート開始前にすでに作成されていたPRと、ライブラリアップデート開始後に自動で作成されたPRは別のカラムに分類されるように設定しており、ライブラリアップデート開始前にすでに作成されていたPRのみ着手すると取り決めています。
以上が、Sansan Androidチームのライブラリアップデートについての取り組みです。 ライブラリの定期的なアップデートは、アプリが生きている限りセキュリティや生産性を維持するために必要かつ重要な業務です。
私たちは、今後もより良いアプリを作っていくため、この運用が継続して行えるよう、フローを見直しながらさらに良くしていくつもりです。
この記事がライブラリアップデートの運用で悩める方の参考になれば幸いです。 また、共にSansanのモバイルアプリ開発する仲間を募集中です!選考とは関係なく、現場のエンジニアの生の声が聞けるカジュアル面談もあるので、ご興味ありましたらぜひ面談だけでもご応募ください!