マネーフォワード ME(以降ME)のモバイルエンジニアの椎名です
今回、MEのモバイル開発のコストを大きく削減し、生産性を向上させた話をします
どれくらいかというと、体感値ですが少なく見積もって 1/5くらい にはなったかな、とは思います(2018年頃と比較)
この事は何か1つの取り組みによって達成されたのではなく、いくつもの取り組みによって数年かけて達成されました
ここでは数々の取り組みの中から、"これは効果があった!"というものを独断と偏見でランキング形式にして紹介します
少しでも皆様のサービスに役立てていただければ幸いです
1位 リファクタリング
1位がありきたりな事で恐縮ですが、今のMEのモバイル開発を支えているのは間違いなく過去に行った大規模なリファクタリングです
2018年頃iOS版MEは、複雑化したアーキテクチャとコードに悩まされていて
当時、行き詰まりを打開するためにフルリプレイスの話が定期的に上がるような状況でした
しかし、フルリプレイスにはビッグバンリリースのリスクがあります
フルリプレイスをするか、リファクタリングにコストをかけるかを議論し、リファクタリングすることを選びました
結果リファクタリングは成功し、コードリーディングや追加機能の実装スピードなどを大きく上げることが出来ました
この結果は誰か一人の行動では絶対に成功しなかったと思います
チームの多大なサポートがあったからこそ、成功を収めることができました
上記のコントリビューションは、膨大なPullRequestのレビューやデバッグ、それに費やすコストへの理解などのおかげです
Tips
Tipsについては同僚が社内記事を英訳してくれたものがありますので、よかったらそちらをご参考ください
Massive Project Code Refactoring Tips by cafedeichi
2位 Flutter導入
2位は、MEへFlutterを導入したことです
MEは2023年2月末にリリースされた資産形成アドバンスコースの機能をFlutterで開発し
それにより開発効率の高さが証明されたことで、本格的なFlutter導入がされることとなりました
既に大部分をネイティブで開発していたMEでは、AddToAppという技術を用いていて
ネイティブの実装はそのままで、新規作成する画面は基本的にFlutterで実装する、といった状況になっています
この状況には運用当初、開発環境が煩雑になって生産性が下がってしまうのではないか?という懸念があり、実際に煩雑にはなりました
ですがその煩雑は、後記のTips煩雑さには便利ツールで対応
によってある程度は軽減され
トータルでみると開発コストは、ネイティブでの画面開発と比較しては体感1/2以下くらいになっています
Tips
まずは手段として適切か判断する
Flutterに限らず、マルチプラットフォーム開発ツールは銀の弾丸ではありません
状況次第ではネイティブでの開発よりパフォーマンスが下がる事もありますし、人的リソース考慮した上で慎重に技術選定をすべきです
Flutterという手段が目的化しないようにしましょう
粘り強く仲間を集める
既にネイティブで長く運用されているサービスの場合、急にFlutterによるマルチプラットフォーム開発に舵を切ることは難しいです
例えば、MEでは以下のような課題があり、Flutter導入を妨げていました
- そもそもマルチプラットフォーム開発がどういうものか知らない人が多い
- 知識はあっても、実際に経験した人が少ない
- ネイティブで開発することに強いこだわりを持つエンジニアの存在
- AndroidとiOSの開発が分断されていて、両エンジニアのコミュニケーションがほとんど無い
- マルチプラットフォーム開発では両者の協力が必要不可欠
ではどうしたかいうと、まずはFlutterとはどういうものなのか、どういうメリットとデメリットがあるのか少しずつチームに共有することから始め
AndroidとiOSの分断が解消されるよう、人と人の交流から、開発環境の共通化などを行いました
そういった地味な活動をおよそ半年間続けた結果、一人、二人と仲間が増えFlutter導入のチームが結成されるに至りました
技術調査を万全に
MEではAddToAppという方法でFlutterを導入しています
この方法はネットでの情報が少なく 1 、導入に当たって不安な点が多数ありました
そういった点は本格的な実装を始める前に、可能な限り解消するようにしました
本格的な実装の最中に課題が見つかると、スケジュールが遅延するかもしれませんし
最悪、開発自体がストップする危険もあります
そうなると"Flutter導入はダメだ"という意見が広まり、そのプロジェクトだけでなく未来の芽も摘んでしまうかもしれません
はじめが肝心
Flutterに限らず新しい技術を導入する際、運用時の方針や認識合わせをしたほうがリスクもコストも低いので、できるだけやっておきましょう
MEでは開発に関する事だけではなく、Flutter実装する画面の場合はどういったデザインにすべきか?
などといった他職種との認識合わせも早い段階で行いました
これらをしたことで大きな混乱がなくスタートができただけでなく、非エンジニアのFlutterへの理解度を上げることに繋がりました
煩雑さには便利ツールで対応
MEのAddToAppでの開発では、Android・iOS・FlutterのGitレポジトリが存在し、シンプルとは言えない構成になっています
FlutterのレポジトリはAndroid・iOSレポジトリのsubmoduleとなっており、Flutterの機能を開発すると3つのPullRequestが発生します
こういった煩雑な開発環境を少しでも簡単に運用するために、さまざまな便利ツールが導入されています
例えば、FlutterレポジトリのPullRequestをmergeすると、自動でAndroid・iOSレポジトリへPRが生成されるGitHubActionsであったり
Flutterレポジトリの機能だけを動作確認するためのデバッグ機能など、MEでは色々な便利ツールが導入されています
3位 リアーキテクチャとテンプレート化
かつてiOS版MEでは正しいアーキテクチャが何か誰も分からず、それに伴いコードも危険で効率の悪い構造になっていました
そのため、アーキテクチャを再構築し読みやすく管理しやすいことを意識して2019年頃リアーキテクチャしました
ただ、リアーキテクチャしたからといってすごく生産性が上がったかというと、そうではありません
アーキテクチャの設計や実装よりも大事なのはその運用です
アーキテクチャはチームのみんなで運用できて初めて効果を発揮します
一人のアーキテクトだけに属人した運用は、退職などですぐに破綻します
属人化を防ぐためのアーキテクチャの思想をドキュメント化しておくという方法もありますが
それだけでは不足と感じましたので、MEでは アーキテクチャをテンプレート化する ということをしました
こちらがどういうものかというと
GUIから、あるいは1コマンドを打つだけで、既にアーキテクチャ通りに実装されたコードが生成されます
そちらにはコードを説明するコメント
や、サンプルコード
が既に有り、あとはそれに合わせてコードを書くだけです
こちらの取り組みはかなりうまく回りました
入社1ヶ月目のエンジニアが、入社数年のエンジニアと設計に関しては同程度のコードを実装できる状況になっています
Tips
サービスに合ったものにする
アーキテクチャを考えるとき、まず何を考えるべきでしょうか?
MEではサービスに適したものはどんなものか?をまず考え、現メンバーや未来のメンバーのスキルセットや、デザイナーやPdMと認識が合うことに重きを置いて作られました
後者はアーキテクチャに関係無いと思われる方もいますが、僕はかなり重要だと考えています
例えばデザイナーは画面同士が疎になるデザインを想定していたのに、実装が各画面で共通コンポーネントを多用するような設計になっていた場合、この運用は認識のズレによって良くない方向に進む可能性があります
細かい箇所まで設計する
アーキテクチャを概念的な説明だけで理解することは難しいです
百聞は一見にしかず、というように、具体的にテンプレートのコードで示す必要があります
MEのテンプレートは基本的に必須となる実装が予め書かれている事はもちろん
サンプルコードや、関連ドキュメントへのリンク、なぜそうすべきなのか等コメントで記載されています
実際のコードを載せるわけにはいかないので、イメージで恐縮ですがご参考ください
/* 微妙なテンプレート */ class FooTranslator { BarClass translate () {} }
/* ME風テンプレート */ import Bar; // TEMPLATE: WebAPIのレスポンスをWidgetで使用するClassに変換する。staticなメソッドしか定義してはいけない // see: https://{このクラスの役割を記載したドキュメントなどへのリンクなど} class FooTranslator { // TEMPLATE: メソッドのprefixは必ずtranslateで統一する // TEMPLATE: 仮実装。初回リクエスト用の値代入などで使う static BarClass translateHogeToBar({required BarClass bar, required int hoge}) { bar.hoge = hoge; return bar; } /// TODO: コメント必須。用途とタイミングを記載する static BarClass translate({required BarClass bar, required Entity? entity}) { if (entity == null) { // TEMPLATE: 画面固有のerrorの表示があればstateを更新する。なければ何もしない return bar; } return bar; }
テンプレートはGitで管理
このテンプレートはGit上でサービスのソースコードと共に管理されていて
誰でもコミットでき、改善ができる運用になっています
それにより、何名ものエンジニアによってリファインされつづけた結果、今の状況となりました
4位 OpenAPIの導入
以前はWebAPIのドキュメントというものは管理されておらず、レスポンスJSON読めばわかるでしょ(わからない)状態でした
そのため、たった1つのパラメータの意味を知るために、多大な労力がかかっていました
また、どのパラメータがrequiredなのか、どんな型になるのかも分からない状況でしたので
すべてOptionalにする必要があったり、型も本当にこれで合っているのか?という疑いを持って実装する必要がありました
そこでOpenAPIのyamlにWebAPIの仕様を記載するということを始めました
その結果、パラメータの認識ミスは無くなり、構造についても理解度も格段に上がりました
更にopenapi-generatorを導入することで、通信部分のロジックを自動生成できるようになり、WebAPI関連の生産性は目に見えて良くなりました
Tips
みんなで運用する
始めは周囲の理解が得られず、iOSエンジニアだけでOpenAPIを運用していました
具体的にはバックエンドエンジニアが書いたマークダウンのメモとヒアリングを元に、OpenAPIのフォーマットに変換していました
当然ですがこの運用は効率が悪かったです
そのまま運用を続けていたら、どこかで破綻していたでしょう
あるとき、若手のバックエンドエンジニアがOpenAPIに興味を持ち、運用を引き継いでくれたことをきっかけに
少しずつ理解してくれる方が増え、今ではチームみんなが協力して管理運用しています
openapi-generatorは要検証
openapi-generatorによるコードの自動生成は非常に便利です
ですが、書き出す言語やプラットフォームによっては使いにくいものが生成されることがあります
ワークアラウンドを探したり、独自のフォーマットで書き出せるようにカスタマイズするのにはそれなりのコストがかかりますので、まずは検証してみることをオススメします
versionを更新する事を忘れない
yamlを変更したら、version情報を更新する事を忘れないようにしましょう
このversionはopenapi-generatorしたコードに問題があった場合などで、どのversionから発生したのかを特定することに役立ちます
5位 ドキュメンテーション
かつては仕様などのドキュメントを保守する文化がありませんでした
そのためソースコード、書き捨てされた過去のドキュメント、過去の社内チャットから仕様を掘り起こす作業が必要でした
そういったドキュメントが無い環境だと、実装時に困ることが多々あり
特に"既存と同じで" や "この箇所と同じ仕様で" などと何度も言われたときには、調査コストが馬鹿になりません
そのため、UI仕様ドキュメントを全画面作成し、関連するドキュメントをそれにリンクし集約する作業をしました
当初はドキュメントをまとめる事に対し、OpenAPI同様あまり注目はされていなかったのですが
ドキュメントを数年に渡って運用し続けた事により、当初は少なかった情報量が徐々に増えたことで
既存仕様の解像度向上から、新たに入ったメンバーのキャッチアップに利用されるなど、皆が見てくれるドキュメントへとなっていきました
今ではチームのドキュメントに対する意識も変化していて
UI仕様ドキュメントをデザイナーが引き継いで管理してくれる様になったり、他職種の方もドキュメントの保守や集約する事に協力してもらえるようになりました
Tips
まず自分が役立つと思うものを作る
ドキュメントは自身が思ってるより誰かのためにはなりません
"こういうときにこの資料読んでくれたら良いのに..."と思うことがあるかもしれませんが、読まれません
読んでもらえるように努力することも大事ですが、まずは自分にとって大事な情報を自分のために書く努力をしてみましょう
少しでも誰かの役に立っていれば儲けもの、くらいのマインドだと気が楽なのでオススメです
テキストだけではなく図や画像を多用する
テキストだけのドキュメントを退屈だと感じるのは僕だけでは無いと思います
退屈だと感じたら、本質的な情報にたどり着く前に挫折してしまいます
なので図や画像で表現できるものは、できるだけそうしましょう
運用する
ドキュメントは時間経過と共に信頼性が低くなっていきます
本当に重要な情報であれば、定期的にメンテナンスされる必要があります
Gitで管理する、目につく箇所にリンクしておく等、色々な手段を用いてメンテナンスされる状況を作ることをオススメします
まとめ
生産性を上げた取り組み5位までを紹介させていただきましたが、こちらはほんの一部です
デバッグ機能の導入
や共通コンポーネントの再構築
、XcodeGen導入
、CIをJenckinsからBitriseへ
等など他にも様々な取り組みをしていて、中には失敗したものもたくさんあります
何か1つの銀の弾丸によって大きく改善したわけではありません
粘り強く改善を積み重ね続けることと、チームメンバーの協力があって初めて今の状況となりました
また、僕達はこの良い状況がいつまでも続くものだとも思っていません
更により良いサービス開発をするために、今後も生産性を上げる取り組みをしていこうと考えています