MoneyEasyのAndroidアプリ開発にCI/CDを導入した話

こんにちは、株式会社フィノバレーでAndroidエンジニア(兼 自称:駆け出しUXリサーチャー)をやっております、島本(id: yoi_ko)です。

COVID-19感染拡大に伴う在宅勤務が始まって以来散歩を日課にしていますが、この暑さで継続の危機に立たされています。夏は好きですが、夜も未練がましく暑いのはどうかと思います。

今回の記事では、フィノバレーの提供するデジタル地域通貨プラットフォーム「MoneyEasy」のAndroidアプリ開発において、昨年にイチから構築した CI/CD環境の概要とその経緯 について紹介したいと思います。

前提

CI/CDの構成をご紹介する前に、MoneyEasyのAndroid開発の基本的な環境について簡単に紹介します。

  • コード管理:GitLab
  • ブランチ戦略:
    • master(リリース用)
    • develop(開発用)
    • feature(Issueに紐ついている作業用、developにマージしたら消す)
  • APK配布:DeployGate

これは、Androidアプリ開発現場ではよく見られる構成だと思います。

ひとつ特殊なのが、MoneyEasyは プラットフォーム だということです。
弊社では、MoneyEasyを使った地域通貨として既に「さるぼぼコイン」「アクアコイン」を運用しています。
Androidアプリは、それぞれの環境ごとにリポジトリを分けてはいません。つまり、先述の両アプリは同じコードで動いています。
アプリ名やテーマカラーをはじめとして、様々な仕様上の違いがありますが、これは productFlavors を分けることで解決しています。

環境は、大きく分けて社内開発用( DEV環境 )、アプリごとのステージング環境( STG環境 )、アプリごとの商用環境( PRD環境 )があります。
機能の新規開発やバグの修正などはDEV環境で行い、ここで実機テストを行っています。
それぞれのアプリごとの機能・デザインのテスト、およびクライアントでの確認はSTG環境で行います。
ここでのクライアントとは、さるぼぼコイン:飛騨信用組合様、アクアコイン:君津信用組合様を指しています。
最後に、リリースされているアプリが動いているのがPRD環境です。

最終的なCI/CD構成(v1.13.0時点)

まず、以下が現在動いているCI/CD構成の全体図になります。

f:id:yoi_ko:20200820174330p:plain

各ステップについて簡単に説明します。

ローカル環境では、 git hook を使って自動で以下の処理を行っています。

  • commit: 開発者のローカル環境で git commit する際、 ./gradlew ktlintFormat を実行
  • push: ブランチを git push する際、 ./gradlew app:lint{DEV環境用のビルドバリアント名} を実行

そして、GitLabでは以下のタイミングでUnit Test、UI Test、APKのビルドを行っています。

  • feature branch push: pushされたfeatureブランチでUnit Testを実行
  • feature -> develop merge: developにfeatureブランチがマージされた際、Unit TestとUI Testを実行
  • add tag: ルールに沿った名前のタグがつけられた際、そのタグ名に応じたビルドバリアントでAPKを作成

全容についてはざっと掴んでいただけたでしょうか?
この構成に至るまでには、第1弾・第2弾と段階を踏んでいます。
次は、それぞれのステップについての詳細と経緯について、時系列で紹介していきます。

ちなみに、この構築にあたっては、2018年に出版された PEAKS(ピークス)|Androidテスト全書 を全面的に参考にしています。
この本がなければ構築できなかったと言っても過言ではありません。まさにバイブルです。
関係者の方々、本当にありがとうございました。

第1弾 目標:テスト→DeployGate配布の自動化

いきなり現在の姿とは違う目標が掲げられていますね。
理由については後述します。

まず最初にCI/CD構築のIssueが立てられたのは、2018年4月でした。
私はまだ入社しておらず、Androidエンジニアは照井さん(id: hotdrop77)だけでした。
ちょうど、Androidのフルリファクタリングが進められていた時期ですね。

照井さん一人によって成し遂げられたフルリファクタリングについては、以下の記事で紹介されています。
finnovalley.hatenablog.com

その後2018年10月に私が入社し、その年末からCI/CD構築作業をスタートしました。
当初の目的は、 「各環境に応じて「テスト → DeployGateへのアップロード」というのを1クリックでできるようにする」でした。
APKのビルドはマシンパワーを使いますし、その間コードの変更などができません。
それをすべて自動化するだけでも、大変な作業効率化に繋がると考えてのことでした。

最初に想定していたゴールはこういった形です。(実際のIssueのスクショ)

f:id:yoi_ko:20200820183240p:plain

この時点で、Unit Testはいくつか存在していましたが、UI Testはまったく書いていませんでした。
このため、第1弾では UI Testの実行は省略しています。

また、検討を進めるなかで「DeployGateへのAPK自動アップロードはしない」という結論に至りました。
前述の通り、アプリをリリースするには、STG環境でクライアントに確認していただく必要があります。
ブランチのマージを契機にビルドしたAPKを自動的にDeployGateへ配布すると、クライアントには予告なくアプリを更新してしまうことになります。
それは避けたほうがよいだろうと考えてのことでした。
このため、APKのビルドは 「ルールに沿った名前のタグがつけられた際」に実行しているのです。

タグ名の命名規則は、さるぼぼコイン:sarubobo-* 、アクアコイン: aqua-* としています。
ハイフンの後ろは、DeployGateにアップする際のVersionを指定する運用にしています。

第1弾での対応をまとめます。

  • ローカル環境での静的解析の自動化
  • push/merge契機でのUnit Testの自動化
  • タグ打ち契機でのAPKビルドの自動化

第2弾 目標:UI Testの自動化

UI Testの自動化は、まずテストを書くところからスタートしました。
それまでUI Testというものを一度も書いたことがなく、またこの時点で画面数が相当な量になっていたため、この「テストを書く」というタスクにかなりの時間を割いています。
実装開始が2019年6月中旬、終了が2019年8月頭なので、ざっと2ヶ月弱でしょうか。

これは結構つらい作業でした。
Issueにも、その片鱗が残されています。

f:id:yoi_ko:20200820190852p:plain
混乱しているのが見て取れるコメント

UI Testを実装するにあたっては、以下の記事を大変参考にさせていただきました。
これらの記事がなければ、途中で諦めていたと思います。深い感謝を捧げます。
Kyash Android で UIテストを導入した時の方針 - Konifar's WIP
RxJavaのアプリをEspressoでテストする簡単な方法 - Speaker Deck

UI Testの実行は、 Firebase Test Lab で行っています。
GitLab CIでUI Test用APK作成 -> Firebase Test Labへ投げる、の流れで処理しています。

ハマりポイントとしては、サービスアカウントの設定です。
Firebase Test Labでの実行には、適切な役割のサービスアカウントが必要になります。
Firebase TestLab管理者という名前の、一見それらしきものがありますが、これだと権限エラーになります。
正しくは 編集者 です。(とても紛らわしい…)

そんなわけで、UI Testの実装に苦しんだり、サービスアカウントの罠にハマったりしながらも、第2弾の目標はなんとか達成することができました。

  • UI Testの実装
  • UI Testの自動化(Firebase Test Lab)

まとめ

このように、第1弾/第2弾と段階を経て、CI/CD環境を構築しました。
DeployGateへの自動アップロードをしていないなど、ちょっとした制限はあるものの、静的解析やテストの自動化・APKのビルド対応によって、開発効率はぐんと上がったのではないかと思います。

とはいえ、APKの作成時は時間が掛かってしまっていたり、UI Test自体のメンテナンスが追いついていなかったりと、まだ課題はあります。
CI/CDのような、品質や効率に関わるタスクはどうしても機能開発に劣後してしまいがちですが、ないがしろにしてはいけないと思います。
今後も、最新の情報をキャッチアップしたり、日々変化する開発を取り巻く現状を見直したりしながら、よりよいCI/CDの形を模索していきたいです。

セブン銀行ATM対応のモックアプリをFlutterで作成した話

株式会社フィノバレーでAndroidアプリを開発している照井(id: hotdrop77)です。
弊社MoneyEasyは今年2020年4月、さるぼぼコインとアクアコインでセブン銀行ATMチャージ機能をリリースしました。
プレスリリース記事: https://finnovalley.jp/20200326/1529/

本記事では、そのセブン銀行ATMチャージ機能のサーバーサイド開発を支援するために作成したモックアプリについて書きました。
本当はもっとモックアプリの業務仕様やコードを載せたかったのですが、そうするとセブン銀行ATMの仕様に触れる必要が出てきてしまい、それは守秘義務の関係で公開することができません。
そのため、作成に至った背景やモックアプリの設計概要などを書いています。

背景

セブン銀行ATM対応はサーバーサイド開発がメインでしたが、当時サーバーエンジニアのリソースが不足しており厳しいスケジュールでした。

特に困難だったのがセブン銀行ATMとの疎通テストで、ATMのテスト筐体がポンポン使えるものではなく担当者様とテスト実施日を調整し現地に赴いてテストをする必要があったため、テスト時点である程度の品質を担保して臨む必要がありました。
(少なくとも正常系は一通り動く状態で現地で障害解析もある程度できるレベル)

逆にモバイルアプリ側は簡易な画面を数個作るのみで比較的余裕がありました。
サーバーサイド開発を手伝いたかったのですが自分がGo入門レベルでサーバー側の設計も把握していなかったため期間を考えると邪魔にしかならないことが目に見えていました。

この期間内で有益な支援ができないか考えた結果 「ATMと通信する部分の検証がきつそうなのでそれならモックアプリを用意すれば疎通テストも楽になるのでは?」
と思って私の方でモックアプリを作ることにしました。

モックアプリは当初作成予定はなく、サーバーエンジニアさんが気を遣ってくれる方で
「照井さんもアプリの改修あるし、無理せず時間あったらお願いしますm( )m」
という感じだったので余計に火がついて絶対完成させて負担を軽減してあげようと決意しました。

なお、モックアプリの作成期間はこの話が決まった2019年12月中旬からサーバーがモックアプリでテストを始められるであろう1月中旬くらいの約1ヶ月間でした。

Flutterを採用した経緯について

モックアプリの想定利用者は自分とサーバーエンジニアさんの2名のみであり、完成して使い物になればどんな言語でどう作ろうが問題ありませんでした。

セブン銀行ATM画面の操作をエミュレートするなら画面操作がしやすいものが良く「モックアプリやっぱ無理でしたー」というのは自分の中ではありえないので無難にAndroidで作る想定でいました。

ただ、この頃はFlutterに興味を持っていて趣味でアプリを作成していました。
万が一テストでiPhoneユーザーがこのアプリを使う事態になったり、ブラウザで通信ログやエラーログを見たいという要望が出てきた場合にFlutterならワンチャン対応が可能だと考えました。

この時期はFlutterのWebサポートもそこそこ出来てきており上記の要望に答えられる可能性があったことと、自己学習を兼ねられることからFlutterで作ることに決めました。

モックアプリ概要

モックアプリの大まかな機能は以下の通りです。

  1. セブン銀行ATMのチャージ操作機能
    • ここでは「コード読み取り→金額入力→金額投入→チャージ完了」の一連の流れを画面操作で再現するとともにリクエスト/レスポンスが逐次見えるように作成しました。
  2. 暗号通信の検証機能
    • この部分については何も書けませんがこういう機能を作成しました。
  3. エンドポイントや電文項目の設定機能
    • 通常変更されることはないエンドポイントや電文の固定項目などもモックアプリでは「もしここを変更したら正しくチェックが走るか?」をテストできるようにしました。
  4. 履歴機能
    • 過去のリクエスト/レスポンスが閲覧できる機能です。
      生データからデコードしたものまで段階的に見れるように工夫したのですが、情報量が多くて見辛い画面になってしまいました。
  5. テストモード
    • エラーが発生した場合にモックアプリのバグなのかサーバーのバグなのかを切り分ける最低限の情報を得るためこのモードを作成しました。
      このモードはサーバー通信が発生する操作をしたら仕様書に記載されているサンプル電文をそのまま返す仕様となっています。
      最低限のバグ切り分けには使えましたが、実際にはそんな単純ではなく何回か切り分け調査はすることになりました。

トップ画面はこんな感じですが、諸事情により色々伏せているのであまり分からないですね・・雰囲気だけ読み取ってください。

f:id:hotdrop77:20200729122810p:plain
モックアプリのトップ画面

設計概要

画面は8つと小規模なアプリです。設計は一番慣れているAndroid Architecture ComponentsのMVVMチックな設計としています。
状態管理はProviderを使用し、Flutterアプリでよく出てくるBLoCパターンについては採用を見送りました。
個人的にはBLoCは小規模アプリで採用するには過剰実装だと思っていて今回のような小さいモックアプリではProviderだけで十分かなという認識なのですが、とはいえ言うほどBLoCで実装したことないので一瞬使ってみてもいいかなと考え、でも今回は見送りました。

ViewModelChangeNotifierextendsしておりバインド的にフィールドを使用しました。
本当はLiveDataのように個別に購読できると良かったのですが、その場合はStreamRxDartを使う選択になってしまうと思うのでこのアプリではそこまでやりませんでした。
Providerの4系で登場したselectが使えると近しいことができると思うのですが、この当時はまだ3系を使用しており使えませんでした。

UseCase層は作らずViewModelの下はRepository層としました。
いつも通りRepositoryでAPIを実行するためのネットワーク通信処理やローカルDBへの保存/取得処理を行なっています。
それぞれ使用したライブラリはこんな感じです。

  • ネットワーク通信: http
  • 設定系の保存: shared_preferences
  • 履歴データなどの保存: sqflite

ローカルDBのライブラリは使い慣れているsqfliteにするか、この時期ちょっといいなと思っていたhiveにするか迷いました。
この時点ではhiveはまだ冒険すぎたのでこの時はsqfliteを採用しました。
hiveは別の検証アプリで使ったのでそのアプリについて記事にすることがあればそこで書こうと思います。)

苦労した点

苦労した点は順にこんな感じです。

  1. セブン銀行ATMの仕様理解
  2. バイナリデータの扱い
  3. 暗号方式の扱い

1. セブン銀行ATMの仕様理解

難易度が一番高かったのがこの仕様理解です。
実は12月当初は資料が不足しておりそれが無駄に難易度を高めていたのですが、ここでサーバーエンジニアさんとあーだこーだ色々と話して理解を深めていました。
この話し合いの中でネックになりそうな処理や重点的に見ておいたほうがいい処理が大体わかってきたので、そのままモックアプリの仕様に繋げました。

2. バイナリデータの扱い

Dartでバイナリデータを扱うこと自体はさほど難しくなかったのですが、慣れていないためすぐString型で扱おうとしてしまったり型を変換せずにリクエストにのせようとしてデータ長が狂ったりしました。
本当はAPIを投げる直前の出入り口でMapperを用意してそれぞれ扱う型に変換すれば良いのですが、このモックアプリではリクエスト/レスポンスの生データと複合化したデータ両方を画面表示していたためModelクラスに両データを持つ必要がありました。

3. 暗号方式の扱い

とりあえず資料に出てくる用語は概要レベルでも頭に叩き込むのが望ましいのでそれを最初にやりました。
暗号については以前読んだ結城先生の「暗号技術入門」で基本的なことは把握していました。まあ忘れてることが多かったので再度読み直したのですが。。

そして何よりFlutterの暗号ライブラリEncryptが素晴らしくかなり助けられました。これ一つ導入すれば主要な暗号アルゴリズムはだいたい網羅できると思います。
最初はこんな便利なライブラリの想定をしていなかったため、最悪PlatformChannelでネイティブレイヤーまで降りてガリガリコードを書く覚悟でした。

とはいえこの覚悟も無駄ではなく、どうしても無理なところがあって一部Javaコードで解決しました。
そのため、最初はWeb/iOSでも使えるライブラリだけで頑張っていたのですがこのタイミングで断念し、最終的に完成したモックアプリはAndroidでしか動きません。
もちろんSwiftで同じ処理を書けばiOSでも動くと思いますが、結局他のプラットフォームで動かす要望もなさそうだったためそのまま突き進みました。

まとめ

サーバーエンジニアの方が優秀だったこともありモックアプリでいくつかバグを摘出&修正したのち、予定通りテスト筐体での検証はすんなりいってそのままオンスケで本機能をリリースすることができました。
多少モックアプリのバグで足を引っ張った気もしますが、かなり役に立ってくれたようですし無事にリリースできて良かったと思います。

今回モックアプリをFlutterで作成してみて、やはりFlutterもDartも良いものだなと改めて思いました。
Androidの画面開発はxmlに慣れていてJetpack Composeはどうも好きになれないのですが、宣言的UIが嫌いなわけではなくFlutterはWidgetとホットリロードの恩恵によってUI実装がXMLより早く書けるしMaterialDesignも簡単にできて素晴らしいと思います。

Dartは個人的に好きなのですがKotlinに比べると色々惜しくて、でも最近の進化が早くそろそろnullセーフ機能も入りそうですし期待しています。

まだまだエンジニアの確保をはじめとしてプロダクトに導入するには会社的にハードルが高いと思いますので、まずはモックやプロトタイプなどのアプリを素早く作って事業に貢献していければなと考えています。

MoneyEasyのAndroidアプリを大規模リファクタリングした話

はじめに

初投稿となります。株式会社フィノバレーでAndroidアプリを開発している照井(id: hotdrop77)といいます。
私は2018年1月に入社し、以降ずっとMoneyEasyのAndroidアプリ開発を担当してきました。
本記事では、私が入社してから最初に行った大工事であるAndroidアプリのフルリファクタリングについて書いていこうと思います。

入社当時の状況

2017年末にMoneyEasyプラットフォームを使用した最初のサービス「さるぼぼコイン」がリリースされました。
私はこのリリースから1ヶ月後くらいに入社したのですが、当時様々な事情がありAndroidアプリの保守運用は引き継ぎなく私一人で開発することになりました。
この時のAndroidアプリの状態を簡単に表すと

  • FatActivity
  • ドキュメントなし

という「まあよくあるよね」という状態でした。
誤解がないよう補足しておくと、収益が見込めるかまだ分からないサービスに対してはとにかくスピード重視で迅速にサービスをローンチすることが重要だと思うのでこういう状態になってしまうのは仕方ないのかなと思っています。
私自身はエンジニア界隈でも言われている通り品質とスピードは相反するものではないという考えに賛同しますが、一方で「とにかく動くものを今の最適リソースで作るにはこれしかなかった」という事情も理解できるのです。
そんなわけで入社直後はこの状態下で溜まっている様々な課題や機能追加を担当することとなりました。

こんな状態で大丈夫?

私は前職がガチガチのSIerで、Androidといえば趣味&ポートフォリオ用にアプリを作った経験のみでした。(DroidKaigiアプリには毎年お世話になっております。)
そんな私にとって幸か不幸か当時のAndroidアプリのコードは上述の通りViewに「描画処理、ビジネスロジック、Http通信」まで全てが1クラスに集約されており、また使用しているライブラリも最低限であったため構成の把握は比較的容易でした。
中途半端にトリッキーなDIをされていたりオレオレMVXな構成でなかったことは不幸中の幸いだったと思います。
そうはいっても、このコードを今後メンテしていくモチベーションを保つのはなかなか辛いものがありました。
せっかくSIerから転身したしDroidkaigiのアプリを主に見ていた関係でもっとモダンな設計やライブラリを使って開発したかったのです。

そして2ヶ月後

入社して1ヶ月後くらいにDroidkaigi2018が開催されました。2017年まで私は個人参加していましたが、この年から会社のスポンサー枠で参加させていただけることになりました。
ブースの手伝いは最低限で好きなセッションどんどん見て良いよと言われましたが、お金出していただいているしちょっとは手伝いしないと悪いと思って、結局ブースの方が他社のアプリ開発者と話せる機会が圧倒的に多いので半日以上いた記憶があります。
2017年のGoogle I/OAndroidの正式言語にKotlinが含まれたこともあり、当時はよく「そちらのAndroidアプリにはもうKotlin導入しました?」みたいな感じで会話のきっかけを作っていました。
Droidkaigアプリのコードも読んでいてとても面白く、そして羨ましかったことを覚えています。

話はMoneyEasyに戻ります。
比較的大きな機能をリリースした2018年3月くらいでしょうか、開発メンバーから技術的負債返却の声が上がりはじめました。
当然、Androidアプリだけがこんな構成になっている訳ではなくサービス全体が同様の構成になっており、今後需要が高まりそうなサービスでしたので早めに負債を返却することが急務となりました。
この辺りはチーム内でとても色々なやりとりがあったのですが、それだけでこの記事が埋まってしまうので割愛します。 当時サーバーサイドを担当していたメンバーが強烈に後押ししてくれたこととPMが「責任を持ってやってくれれば自由にしていいよ」と理解ある感じだったため自分はAndroidアプリのリファクタリングを行うことになりました。
責任を持ってやるのは当たり前だ!という話もありますが「そんなリスクは許容できない」「全テストが必要なのでダメ」などそもそもチャンスすら与えられない環境が多い中、入社して浅い自分にリファクタリングを任せてもらえたことはとても嬉しかったです。
顧客も金融機関では珍しく技術に理解を示してくれているようで各ステークホルダーに恵まれた環境なのかもしれません。

大規模リファクタリングにあたっての制約

今回のリファクタリングでは一部分ではなく根幹から全て修正することにしました。
リファクタリングを行うにあたっての業務的な制約に加え個人的な制約(誓いに近いです)を念頭に置いて計画を立てました。

  1. 業務的な制約
    1. スケジュールの関係で7末までに完了させること(8月に機能全テストが必要なリリースがあるのでそれに間に合うことが必須)
    2. 既存アプリへの機能拡張&保守運用は最優先で行う
  2. 個人的な誓い
    1. MoneyEasyのAndroidアプリの設計や実装について胸を張って話せるようになる
    2. チームの期待を裏切ることはしない

2-1が私の中で一番大きな要因で、自身が開発しているアプリのことは業務的にもコード的にも胸を張って話したいので出来るだけモダンな設計/実装にしたい想いがありました。
また、PMや開発メンバーが信頼して任せてくれたこととサーバーチームの方々にも設計や方針検討など協力してもらったため、期待通りにやりきることと重大なバグを発生させないことを最優先に考えて取り組みました。

取り組む前の設計/検討

この大規模リファクタリングに当たって考えることは山のようにありました。
コードの改善はもちろんのこと、CI/CDやテストコード導入などやりたいことは多くありましたがリソースとスケジュールの兼ね合いで断念したものも多くありました。
最終的に以下の6項目に分けてそれぞれ設計/検討しました。

  1. アーキテクチャ見直し
  2. 採用言語の検討
  3. 使用ライブラリ検討
  4. リソース設計
  5. ログ設計
  6. エラー設計

これらは一つ一つが1記事書けるくらいになってしまうし、今となってはより良い設計がデファクトスタンダードになっているものもあるため詳細は割愛します。
スケジュールがタイトでしたがいきなりコードを触り始めると絶対失敗するので4月の1ヶ月間はこれらの設計および検討に注力することにしました。
そのため、コードに手を入れる期間はGW明けの5月〜7月末の3ヶ月間となりました。
最終的にKotlinを採用しAndroid Architecture Components(この表現は長いので以下AACとします)を使ったMVVM構成で、各レイヤーの繋ぎこみはRxJavaを使用、DIはDagger2という構成で再構築することにしました。
ちなみに構成はDroidkaigi2018の影響を思いっきり受けています。

具体的な進め方

必要な設計/検討はどれも重要でしたが、それとは別にどうやって進めていくのが最も良いのかを考えることも重要でした。
なにせまだAndroidアプリの仕様全貌はつかめておらず不明な機能もそこそこある状態でした。ドキュメントもテストコードもないため動かして確認するしかありません。
最終的に次の2つを厳守した状態で進めていくことにしました。

  1. 既存アプリとリポジトリを完全に分割
  2. 常に動く状態を保つ

1については既存アプリとそのコードは完全にそのままの状態とし無用な事故を防ぐ目的もありました。
2については本当に細心の注意を払いました。
想定する設計でリファクタリングしてしまうと元コードの状態が跡形もなく消えて無くなるため、ある程度リファクタリングを進めてしまった後にビルドエラーや不思議な挙動でどうにもならなくなった場合、最悪動いていたところまで手戻りが発生し調査に無駄な時間がかかります。
当然、全てリファクタリングした後に動かすなんてリスクが高すぎて取れません。
こういう時テストコードがあれば心がとても安らぐのですがこの状態でテストコードを書くのは不可能に近かかったので(書くならせめてビジネスロジックをViewから分離した後)この時は「常にアプリが動作する状態にする」ことを徹底しました。

「常にアプリが動作する状態にする」にした強い理由がもう一つあって8月頭から全機能テストが始まるため、7末に実装を完了させたところで「アプリを1度も動かしていない状態」ではテスト工程に入れるレベルの品質に達していないのは明白で話になりません。
かといって、7月中旬あたりにリファクタリングを全て終わらせて2週間くらいで動作確認しまくる、というスケジュールも高確率で破綻すると考えました。
それならばチマチマ動作確認をしながら進めることで実装完了とともに簡単な動作確認も完了させてしまうのが最も早そうという結論に達しました。
この進め方にはメリットがあって、この方針でいく場合は機能間の結合度が高いとチマチマ動作確認する意味がなくなりますので可能な限り機能同士が疎結合になることを意識して進めました。
これは改めて意識するまでもなくコード書く上で当たり前のことですが今回は後述するようにある程度元コードを保った状態で進めていくことにしたので油断すると元コードの密結合をそのまま引き継いでしまう恐れがあったのです。それを回避するため改めて意識する必要がありました。

この状態を維持しつつFatActivityからいきなりAACを使用したMVVM構成にするのは難易度が高すぎるので段階的にリファクタリングできるよう4つのポイントを設けることにしました。

4つのポイント

リファクタリング期間3ヶ月を大きく4つのポイントで区切って段階開発していきました。
各ポイントの概要は以下の通りです。

  1. ポイント1: FatActivityからビジネスロジックを切り離しレイヤー構造にする
  2. ポイント2: AAC以外のライブラリ導入
  3. ポイント3: 1機能のみKotlin+AAC対応
  4. ポイント4: 全機能Kotlin+AAC対応

これらはどのポイントでリファクタリングを中断しても一応コードは使える状態であることを意識しています。
6月にいきなり既存アプリへ大きな機能が追加となる可能性も0ではなかったため、その場合は4月〜5月のコストがなるべく無駄にならないように配慮した結果でした。
ポイント4まで到達しないと自分の目的は達成できないのでそういったことがないことを祈りましたが。

ポイント1 FatActivityからビジネスロジックを切り離しレイヤー構造にする

何をやろうにもまずFatActivityだとどうにもならないので、真っ先にやることはViewからビジネスロジックを切り離すとともにクラスやメソッドを意味のある単位で分割しレイヤー構造にすることでした。
具体的には以下のイメージ図のように内部構成を変えました。

f:id:hotdrop77:20200619175138p:plain

このポイント1は5月中旬くらいまでに完了させる予定で行い、ほぼオンスケで完遂できました。

ポイント1での注意点は「内部ロジックは可能な限り変更を加えない」ことでした。
これをやっている間、凄まじくリファクタリング衝動にかられましたが無駄になるかもしれないし余計な混乱を招く恐れが高かったため変数名一つ変えず極力そのままレイヤー分割に徹しました。
ただ、そうはいってもどうしても追加修正しないといけないコードは出てくるのでそういったものは修正しています。
なぜこんな注意点に徹したかという理由ですが、コンパイルエラーやおかしな動作をした時に元コードとの比較が容易になるためです。
たまにおかしな変数がおかしな値を持っていることがあり、そのままにしておくと元コードでの検索が容易になるため何を入れていて何をしていたかの調査が容易でした。
実はSIer時代にこの時とはちょっと違いますが巨大な機能のリファクタリングしたことがあってその時の失敗経験をここで活かすことができました。

ポイント2 AAC以外のライブラリ導入

AACを導入するとViewModelが間に挟まり元コードの名残がだいぶ消えてなくなるので、まずそれ以外のライブラリを順次導入していきました。
ライブラリの選定はとても慎重にやりました。ここはAndroidエンジニアでないと選定が難しいので自分一人で検討することになりますが、ありがたいことにOSS自由に選定して良いという方針でした。
ただ、MoneyEasyはお金を扱うサービスですのでちょっと便利だからという理由でライブラリを入れまくることはしたくありませんでした。
自分がよく知っているもの、デファクトといっていいくらいAndroid界隈では有名なもの、活動が活発でドキュメントが需実しているものなどいくつか基準を設けてかなり絞って選定しました。

導入の際にインパクトが大きかったのはDagger2とRxJavaでした。特にDagger2はApplicationからDIするので部分導入できずほぼ全クラス修正が必要となりなかなか苦戦しました。
また、Dagger2は以前から趣味アプリで使っていましたがComponentsやModuleを理解せず使用していたためこのタイミングで1からDagger2を学習しました。
Rxは自分のお気に入りライブラリトップ3に入るくらい好きで以前から使っていたため学習は不要でしたがこれも導入時は大量に修正が必要でしたので時間がかかりました。
このポイント2までは5末までに完了させる予定でした。実はここでちょっと足が出て6月1週目に食い込んでいます。

ポイント3 1機能のみKotlin+AAC対応

次は1機能だけKotlin+AACで動作させるようリファクタリングしました。
これをやると完全に元コードの面影が消滅するのでエラー解析が今までより面倒になりますが、まずView〜RepositoryまでKotlin化してしまい一度動作確認、それからAACを導入することでUseCase〜Repositoryは動作確認できている状態になるため調査範囲をViewModel&Viewに限定できて効率化できたかなと思います。
Kotlinらしいコードに書き換えるのはこの後にやりました。
ポイント3は6月中旬くらいまでかける想定でしたが、ポイント2でちょっと足が出ていたため予定より期間はありませんでした。
ただここはちょっとバッファを持っていたためリカバリでき、当初予定通り6月中旬ちょっと前にこのポイントを終えることができました。

f:id:hotdrop77:20200619175314p:plain

この最終構成はDroidkaigi2019などで弊社ブースで配布していたポストカードにも載せています。

f:id:hotdrop77:20200619175547j:plain

ポイント4 全機能Kotlin+AAC対応

ここからは7末までひたすら全力全開で全機能リファクタリングをしていきました。当時は大雑把に分割しても25機能でだいたい70画面くらいあったと思います。
ポイント3で大体やり方がわかったのでこのポイントは比較的すんなり進めたのですが、それとは別に「KotlinがJavaと100%互換である」という素晴らしい特性を持っていたことですんなり進めたと思います。このおかげで1機能ずつ「リファクタリング→動作確認」という手段を取れました。

ポイント4は無事7末で完了でき、動作確認も逐一行なっていたためそのままテスト工程へ突入しても差し支えない程度の状態にすることができました。
実際この後、8月に全機能テストをしていただき無事にリリースすることができました。
当然テストではバグがかなり出たのですが致命的なものはほとんどなく、またテストをしっかり行なっていただけたこともあってリリース後に大きなバグも発生しませんでした。

まとめ

細かい点はまだまだ多く苦労した点も面白かった点もたくさんあるのですが、それを書くと長編になってしまうのでまたの機会にしたいと思います。
リファクタリングはエンドユーザーにはほとんど見えない部分ですが実はTextFieldなどはMaterialDesignに準拠して改善していたので所々UIに小さな変化を加えていましたし、全体的にかなり性能改善をすることができました。
特に起動周りとお店一覧は劇的に改善させたのでPMや開発メンバーは気づいてくれて嬉しかったです。

自分にとってこのリファクタリング作業はとても大きな成果でした。
強制的に全コードを読む羽目になったためAndroidアプリの全容を知ることができ、可読性も凄じく向上したためその後の開発効率が格段に上がりました。

それから少ししてAndroidエンジニアがもう一名参画し、ユニットテスト/UIテスト、CI環境などを整備してくれて開発効率がさらに上がりました。
やはり一人では限界があるのでとても感謝しております。

この大工事からそろそろ2年が経ち、少しずつ当時の設計や導入ライブラリが劣化していると思っています。
ライブラリは小まめにバージョンアップしているものの影響範囲が大きいものはなかなか入れ替えが難しいのが現状です。
しかし、継続的なリファクタリングはサービスの寿命も伸ばしますしエンジニアのモチベーションにも開発効率の向上にも繋がるはずです。
次の大きなバージョンアップではCoroutineを導入する予定で、これからも継続的なメンテナンスは大事にしていきたいと思います。