Kotlin Multiplatform(KMP)撤退の意思決定と得られたもの

はじめに

この記事では、KMPからの撤退について書かれていますが、
KMPを否定する記事ではありません。
KMPのトレードオフと、現状の弊プロダクト・チーム・今後の戦略を見た上で判断をしています。

KMPの恩恵は実装コスト削減に留まらない

KMPの恩恵として誰もが知るのは、ロジックの共通化です。

ロジックの共通化によって実装コストの削減ができるのはもちろんそうですが、
業務レベルでの認知コスト削減にも貢献できます。

ネイティブチームとして同じ仕様の1つのアプリを運用する以上は、
「仕様と実装が一致していること」「両OSで実装が一致していること」の確認・運用コストが発生します。

この確認コストは、ネイティブサイドに留まらずサーバ、ビジネス、QAと全職種に影響します。

「AIが台頭し、実装コストが下がったことでトレードオフが逆転している」

という見方が広がる中、この業務コスト削減の恩恵を無視することはできません。

KMPの恩恵を受けるために

KMPの恩恵を受けるためには、3つの条件が必要と考えます。

  1. チームの統合
    iOS, Androidチームを統合し、KMPチームとして構造から業務プロセスを統合していく必要があります。
    チームを統合することで責務が明確になり、自然とKMPでの実装を進める、といった状態になると考えます。
  2. 両OSと、KMPの知識
    iOSとAndroid、またそれらをブリッジするKMP固有の知識が最低限必要です。
    ライフサイクル、メモリ管理、オーバーヘッド、ビルド環境、CICD、など
    管理を怠ればこれらが原因でパフォーマンスが落ちたり、CICDが止まってしまいます。
  3. KMPの適用範囲拡大
    検証で部分的に導入する事例は非常に多いかと思います。
    個人的には、導入するからには撤退までを考えた設計と適用範囲を上げる覚悟を持つべきだと考えます。

KMPの導入は、たとえそれがRepositoryなどの部分的導入だったとしても、
プラットフォームを結合する事に変わりありません。
プラットフォームが結合された時点でビルド時間は伸び、一方の変更がもう一方へと影響するようになります。
固定コストを導入時点で差し出しているので、
適用範囲を上げてメリットを享受・大きくしていかないと、負債を払い続ける事になります。

KMPの恩恵を受けるために、受け入れるトレードオフ

KMPの運用をしてきた中で私たちが直面したのは大きく
「プラットフォーム間の影響」「パフォーマンス」「KMP基準の依存管理」
の3つです。

「プラットフォーム間の影響」
依存管理やキャッシュ管理といった、直接的・間接的な影響はどうしても発生するので、持続的な管理が必要です。

「パフォーマンス」
KMPを取り込む過程がある以上は、バイナリサイズは肥大化し、ビルド時間は伸びます。
私たちの場合はビルド時間が10~20%、バイナリサイズがインストールサイズで40MB、ダウンロードサイズで4.7MBでした。
CICDの特定のWorkflowでは、KMP関連のフェーズに4~7分かかっていました。(マシンスペックにもよる)

「KMP基準の依存管理」
私たちの組織では、Appleが提供する機能への追従を重要視している側面があります。
Swift6.0に上げたときに、KMP側でクラッシュする問題があり、対応を進められない期間が1年ほどありました。
また、SwiftUI.PreviewでKMPへアクセスするとクラッシュする問題は今も健在で、ワークアラウンドによって動かしています。
iOSだけでなく、Android側でもライブラリのバージョンを上げられないケースがありました。
そういった互換性の問題は必ずあります。

KMPからの撤退を決めた理由

結論から言うと、「最新の標準機能への追従」「OS間で互いに影響しないこと」「パフォーマンスの最適化」を優先したためです。

また、KMPのカバレッジがまだ小さく、撤退のコストを抑えられる段階だったことも判断を後押ししました。

AIの台頭によって、パフォーマンス最適化の手段も見つけやすくなりました。

認知コストが減るメリットは、共通化しているロジックが多いほど大きくなります。
私たちはAPIクライアントとRepositoryに加え、約10画面のPresenter、コア機能のビジネスロジック、ログをKMPで共通化していました。
それでもアプリ全体から見れば一部で、ビルド時間やバイナリサイズ、依存管理の制約は、導入範囲によらずアプリ全体にかかっていました。

撤退後に両OSの仕様ずれをどう防ぐか

KMPで担保していた「両OSで実装が一致していること」は、撤退後は次の方法で担保しています。

  • API
    BFFを更新すると、OpenAPI定義(openapi.yaml)がiOS・Androidのモノレポにコミットされます。両OSともこの定義を唯一のソースとしてクライアントコードを生成します。
  • ログ
    ログ仕様書から、両OSのログ定義を自動生成します。
  • コア機能のビジネスロジック
    KMP側にあったテストケースを両OSに移植し、テストの入出力をそろえています。テストを比較することで、ロジックのずれを検出します。

ロジックを両OSで別々に実装する以上、今後の運用でずれが生じるリスクは残ります。これはKMPのメリットを手放した代償です。

撤退した結果

KMP撤退による結果は以下の通りですが、その他の指標も最大化するために改善を重ねていく予定です。

  • ネイティブ全体のFeatureModule完遂が可能に
  • iOS, Android側共にKMPのバージョンに縛られずに依存管理が可能に
  • Swift6や#Previewをはじめ、動かすために入れていたワークアラウンドの削除
    • iOS標準の機能を制限なく導入・運用が可能に
  • インストールサイズ40MB減、ダウンロードサイズ4.7MB減 (AppStore Connectのバイナリ情報より)
  • フルビルドの総実行時間20~30%減によって、CI CDの特定Workflowのコストも削減
  • 起動時間50ms削減 (Xcode Organizer)

バイナリサイズ削減を最大化

KMPの撤退をMergeable Librariesや画像圧縮対応とも組み合わせたことで、
最終的なバイナリサイズは以下の通り大幅削減できました。

download size 96.9MB → 73.9MB (-23MB)
install size 233MB → 166MB (-67MB)

appstore_tapple_size_before appstore_tapple_size_after

※追加で30MBほど削減できる打ち手がまだあります

 

起動時間の削減も最大化しました。

改善データが積み上がるにつれて削減時間も大きくなっていますが、
現時点のデータでは148ms削減されています。

おわりに

いかがでしたでしょうか。
技術の変化が早い中で、その時々で方向性を変えてしまっては導入と撤退を繰り返すだけになってしまいます。
チーム・組織にどのような影響をもたらすのか。
他の技術との掛け合わせで成果を最大化できないか、とさまざまな視点で考える良い経験となりました。