設計の世代交代を乗り越える、14年続くAndroidアプリの開発戦略
by
SansanTech
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Slide 1
Slide 1 text
Sansan技術本部 設計の世代交代を乗り越える、 14年続くAndroidアプリの開発戦略 Sansan株式会社 技術本部 技術本部 Eight Engineering Unit Mobile Applicationグループ 荒翔太 若⽥直希
Slide 2
Slide 2 text
荒 翔太 技術本部 Eight Engineering Unit Mobile Applicationグループ - 新卒⼊社7年⽬ - 普段は関⻄に⽣息しています - 今年から通信制⼤学に通っています
Slide 3
Slide 3 text
若⽥ 直希 技術本部 Eight Engineering Unit Mobile Applicationグループ - Sansan株式会社に2025/11に中途⼊社 - 北海道⽣まれ北海道育ちの左利き - マラソンにハマっています
Slide 4
Slide 4 text
No content
Slide 5
Slide 5 text
長く運用されているアプリで、 設計由来の負債にどう向き合うか
Slide 6
Slide 6 text
アジェンダ 1. Eightアプリに積み重なった設計 2. なぜ開発しやすくならないのか 3. 運⽤とリファクタで課題を解決する a. 新たに積み重ねない b. 積み重なった設計を減らす 4. 次の世代に向けて 5. まとめ
Slide 7
Slide 7 text
Eightアプリに積み重なった設計
Slide 8
Slide 8 text
Eightアプリに積み重なった設計 14年の歴史を持つ⼤規模なコードベース Initial commit Java/Kotlin lines 設計パターン 2012-11-20 450,000+ 7世代
Slide 9
Slide 9 text
設計パターンの変遷 初期 1. MVC 2. MVP Rx基盤 3. 独⾃Flux 4. RecyclerView + 独⾃Flux 5. 独⾃Flux + ViewModel MVVM 6. Activity + MVVM + UseCase 7. Compose + MVVM
Slide 10
Slide 10 text
これまで⾏ってきた改善 最新の設計への改善は続けてきた - RxからCoroutinesベースの設計への移⾏ - Composable対応 - Fluxベースの独⾃アーキテクチャからMVVMへの変更
Slide 11
Slide 11 text
Eightアプリに積み重なった設計 しかしその結果として、7世代の設計が 同じコードベース上に混在している
Slide 12
Slide 12 text
Eightアプリに積み重なった設計 設計パターンが増えたことで認知負荷も 上がっていった
Slide 13
Slide 13 text
設計が積み重なったことによる認知負荷の上昇 開発時に、影響範囲にある全ての設計パターンに ついて考慮する必要がある - 開発速度の低下 - 調査コストの増加 - 修正難易度の上昇 - オンボーディング速度の低下
Slide 14
Slide 14 text
なぜ開発しやすくならないのか
Slide 15
Slide 15 text
改善の落とし⽳ 多くの改善はプレゼンテーション層に閉じていた プレゼンテーション層 MVC MVP Flux MVVM トレンドへの追従 モデル層/データ層 以前からの実装が参照され続けた
Slide 16
Slide 16 text
なぜデータ層への改善が遅れたのか - データ層はわかりやすいトレンドがなく、 外部要因で刷新されづらい - データの永続化を担っており、 改善や刷新のコスト, リスクが⼤きい
Slide 17
Slide 17 text
過去のデータ層が参照され続けると 新設計に閉じた開発ができず、 認知負荷の上昇に繋がる
Slide 18
Slide 18 text
課題が顕在化した理由
Slide 19
Slide 19 text
Eightの過去のデータ層が抱えていた問題 - 広範なオフライン対応 - 特定ライブラリへの依存が表出している - ⼀つのデータ更新が複数の機能に伝播する
Slide 20
Slide 20 text
広範なオフライン対応 - Eightは紙の名刺管理に劣らない体験を提供する ため、ローカルDBを利⽤している機能が多い - 開発初期には基準が曖昧で、アプリの多くの モデルがローカルDBで管理されていた
Slide 21
Slide 21 text
広範なオフライン対応 新機能の実装時に、データの更新やモデルの 変更と、アプリ内 DBとの整合性を取るために、 古い実装を参照する必要がある
Slide 22
Slide 22 text
特定ライブラリへの依存が表出している - Repositoryの引数や返り値に、ライブラリや フレームワークの型が指定されている - Deprecated化やライブラリの置き換え後も、 古い型を扱う必要がある
Slide 23
Slide 23 text
課題が顕在化した理由 - 広範なオフライン対応 → 新規実装をする際に、以前のデータ層を 参照する必要がある - 特定ライブラリへの依存が表出している → 以前のデータ層を参照すると過去世代と 切り離せなくなる
Slide 24
Slide 24 text
課題が顕在化した理由 1. 以前の設計思想への依存が増え続ける構造
Slide 25
Slide 25 text
⼀つのデータ更新が複数の機能に伝播する 名刺に関連するデータを参照する画⾯が多く、 データの更新、削除などで多数の画⾯が 影響範囲になる
Slide 26
Slide 26 text
⼀つのデータ更新が複数の機能に伝播する - 主要機能は影響範囲が絡まり合っている → ⼩さな単位でのリファクタリングが難しい
Slide 27
Slide 27 text
課題が顕在化した理由 1. 以前の設計思想への依存が増え続ける構造 2. 以前の設計思想への依存が減らせない構造
Slide 28
Slide 28 text
Eightアプリで起きていた問題
Slide 29
Slide 29 text
Eightアプリで起きていた問題 - 考慮するべき設計の世代が重なると、 認知負荷が増⼤し開発が難しくなる - 逐次的な改善では考慮するべき設計の世代を 減らせない構造になっていた
Slide 30
Slide 30 text
運⽤とリファクタで課題を解決する
Slide 31
Slide 31 text
問題のおさらい
Slide 32
Slide 32 text
Eightのアプリに設計が積み重なった原因 データ層を変更しづらい 新設計が古いデータ層に依存 MVC MVP Flux MVVM データ層 設計が世代の数だけ積み重なる
Slide 33
Slide 33 text
設計が重なった原因に対する打ち⼿ 2つのアプローチで、設計の積み重なりに対処する データ層を変更しづらい 設計を新たに積み重ねない 新設計が古いデータ層に依存 設計が世代の数だけ積み重なる 積み重なった設計を減らす
Slide 34
Slide 34 text
設計を新たに積み重ねない
Slide 35
Slide 35 text
具体的なシチュエーションを想像 - 新しい画⾯を新しいアーキテクチャで作ることを想定 - 古いデータ層の情報が欲しい場合 UI Layer MVC MVP Flux MVVM Domain Layer(optional) データ層 Data Layer 引用:アプリ アーキテクチャ ガイド 別の⾮同期処理 / ローカルDB
Slide 36
Slide 36 text
具体的なシチュエーションを想像 直接依存は設計が積み重なってしまう MVC MVP Flux MVVM データ層 UI Layer Domain Layer(optional) Data Layer 引用:アプリ アーキテクチャ ガイド 作り直しは影響範囲が広すぎる 過去実装に依存してしまう
Slide 37
Slide 37 text
古いデータ層への依存が増えないように、境界を設計する 1. 境界を作る 新旧の実装を橋渡しする仕組みを設計 2. 境界を守る 決めた境界が破られない仕組みを作成 新 UI Layer Domain Layer(optional) 旧 データ層 Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 38
Slide 38 text
境界を作る 新旧の橋渡しをする中間層を置く 新 UI Layer Domain Layer(optional) 旧 Bridge データ層 Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 39
Slide 39 text
境界を作る Bridgeが古いデータ層に直接依存するのはNG❌ 新実装から旧実装を剥がせなくなる 新 UI Layer Domain Layer(optional) 旧 Bridge データ層 Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 40
Slide 40 text
境界を作る - 古い実装には直接依存させない 依存性を逆転させる!! 新 UI Layer 旧 BridgeImpl データ層 Domain Layer(optional) Bridge Interface Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 41
Slide 41 text
境界を作る - 古い実装には直接依存させない 依存性を逆転させる!! 腐敗防⽌層として機能させる!! - Realmのモデル → Bridgeの型 - RxJava → Coroutines / Flow 新 UI Layer 旧 BridgeImpl データ層 Domain Layer(optional) Bridge Interface Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 42
Slide 42 text
境界を守る 境界を作っても無意識的にルールを破ってしまう レビューでも⾒逃してしまう OK 旧 新 新 interface 旧 NG 新 旧 静的に検査し、機械的に保証する - 依存グラフを検査するGradleプラ グイン - 違反があれば失敗させCIで検出
Slide 43
Slide 43 text
設計を新たに積み重ねない ― まとめ 新しい世代を⾜しても、古いデータ層へ直接設計が 積み重ならない状態を整えた - 境界を作る:中間層を置き、新→旧の依存の⽮印そのも のをなくした - 境界を守る:依存グラフを静的に検査するGradleプラグ インで、ルール違反をCIで機械的に検出できるように した
Slide 44
Slide 44 text
積み重なった設計を減らす
Slide 45
Slide 45 text
次に、⼟台の上で積み重なった設計を減らす データ層を変更しづらい 設計を新たに積み重ねない 新設計が古いデータ層に依存 設計が世代の数だけ積み重なる 積み重なった設計を減らす
Slide 46
Slide 46 text
増えた設計パターンを減らす 開発速度の低下‧調査コストの増加、修正難易度の上昇、 オンボーディング速度の低下 を解消するために統⼀ UI Layer MVC Flux MVP MVVM Domain Layer(optional) Data Layer 引用:アプリ アーキテクチャ ガイド
Slide 47
Slide 47 text
積み重なった設計を減らす移⾏戦略 プロジェクトの有無にかかわらず移⾏⽅針を決めることが ⼤事 01 02 03 04 対象を決める 小さく 統合する 段階的に 公開する 旧実装を 削除する 新旧の境界を設計し積み重ならない状態にする
Slide 48
Slide 48 text
対象を決める 全てを置き換えるのは⾮現実的。優先度の⾼い画⾯を選定 全画⾯ 定量評価 約200画⾯ 同じ状態‧旧DBを複数画⾯で参照しており、依存を広げている画⾯ 定性判断 末端の画⾯はやらない。今後変更する可能性が⾼い画⾯ 対象画⾯ 65画⾯
Slide 49
Slide 49 text
⼩さく統合する ― Feature Flagsで未完成機能を隠す ⻑命ブランチを避け、未完成の状態でもトランクへ⼩さく統合して いく - データ層‧UiState‧ViewModel‧UIなどに実装を分割し、⼩ さく実装‧⼩さくレビュー - Feature Flagsで未完成機能を隠し、トランクへの統合を可能 にする
Slide 50
Slide 50 text
段階的に公開する― Firebase Remote Configを利⽤ Firebase Remote Configを⽤いて段階的に公開を⾏いユーザーへ の影響を最⼩限に抑える 社内限定 特定のIDに 配信 1% 100% 設定削除 2⽇後 7⽇後 14⽇後 ロールバック:重⼤な不具合の発⽣時は旧実装へ戻す
Slide 51
Slide 51 text
旧実装を削除する 旧実装だけでなく、それと移⾏に付随したものを削除する 削除までを移⾏完了の条件とすることで、アプリ全体の複雑さを統⼀ できる 旧実装 Feature Flag Remote Config Bridge 削除 削除 削除 削除
Slide 52
Slide 52 text
まとめ ― 設計の積み重なりに対する2つの打ち⼿ データ層が変えづらいという制約の中で 新しい世代を⾜しても設計が積み重ならないようにし すでに積み重なった設計も着実に減らしている。 - 65画⾯中、30画⾯が開発完了。6画⾯がリリース済み。
Slide 53
Slide 53 text
次の世代に向けて
Slide 54
Slide 54 text
次の世代に向けて 今のアーキテクチャから移⾏する将来も 想定しなければいけない
Slide 55
Slide 55 text
次の世代に向けて - 想定できる将来 - 例えばKMPや、他のCompose-drivenな アーキテクチャなど - 避けるべき状態 - 古いアーキテクチャが切り離せず、複雑性が 溜まっていく状態
Slide 56
Slide 56 text
Eightが抱えていた問題を先回りして対応する
Slide 57
Slide 57 text
Eightが抱えていた問題を先回りして対応する 1. 複雑性の源だったオフライン要件の⾒直し 2. 適切なクラス間の境界の設計
Slide 58
Slide 58 text
複雑性の源だったオフライン要件の⾒直し モデル層の影響範囲を⼤きくしていた 仕様⾃体を整理する
Slide 59
Slide 59 text
複雑性の源だったオフライン要件の⾒直し Before 明確な基準がなく、利⽤可能な多くの場所で オフラインキャッシュが追加、利⽤されてきた After プロダクトオーナーと相談し、サービスに必須な オフライン対応の要件の線引きをする
Slide 60
Slide 60 text
複雑性の源だったオフライン要件の⾒直し 仕様⾯からのアプローチでユーザーのコア体験を 毀損せずに実装の複雑化の原因を軽減する
Slide 61
Slide 61 text
適切なクラス間の境界の設計 疎なインターフェースを意識して設計, 実装を⾏う
Slide 62
Slide 62 text
適切なクラス間の境界の設計 - レイヤーごとに独⽴した責務を持たせる - ライブラリの型を境界に使わない
Slide 63
Slide 63 text
適切なクラス間の境界の設計 Before - ライブラリの都合が、参照元に漏れ出していた - 古いライブラリの型を意識し続ける必要があった After - 独⽴したクラス, メソッド定義を⾏う - 特定のライブラリの型は表出させず、呼び出し元に影響を 与えず差し替え可能にする
Slide 64
Slide 64 text
次の世代に向けて - 疎結合なクラス設計を保つことは設計の世代を 乗り越えるのを容易にしてくれる - 複雑さの根元が仕様にある場合には 仕様に⼿を⼊れることも有効
Slide 65
Slide 65 text
まとめ
Slide 66
Slide 66 text
設計の世代が積み重なったことによる課題 実装時に過去の設計への考慮が必要だと、 コードベースの認知負荷が上がり、 開発速度の低下などの問題を引き起こす
Slide 67
Slide 67 text
課題に対して私たちがどのように向き合っているか 影響の境界を定め、リアーキテクチャは形骸化 しないよう筋道を⽴てて進める必要がある
Slide 68
Slide 68 text
課題を受けて、将来に向けて何を意識しているか 設計変更⾃体を避けるのではなく、 変わる可能性を想定してアプリの状態を 整えておく
Slide 69
Slide 69 text
No content