見出し画像

古いコードを MVVM + Coroutine + Testable にリファクタリング

こんにちは、Zaim で Android 開発を担当している @sakai です。
今回は、最近 Android チームが進めているリファクタリングのお話をしていきたいと思います。

現在の Android アプリ開発における技術選択の中で、「MVVM(Model, View, ViewModel) のアーキテクチャで Kotlin で記述する」というのはスタンダードな方法の一つになっているかと思います。
Android 版 Zaim の基本的なアーキテクチャも AAC(Android Architecture Components)を用いた MVVM となっています。

しかし、Zaim は約 10 年という長期に渡って運用を続けているサービスですので、今では少し古い Android の技術を使っている箇所や、Java で書かれている箇所、その他にもいくつか問題点がありました。
今回はモダンな技術を使ったプロジェクトにするために、どのようにアプリを安全にリファクタリングしているか、その実際の作業工程を書いていきたいと思います!

リファクタリング計画


                              現在                                       リファクタリング後
言語                      Java                                        Kotlin
アーキテクチャ   MVP                                       MVVM
非同期処理           RxJava                                  Coroutine
テスト                   Non Testable                        Testable
File IO                    File IO on MainThread         File IO on BackgroundThread


工程一覧

  1. Java から Kotlin へ

  2. MVP から MVVM へ

  3. Repository をテスト可能に

  4. suspend メソッドを Repository に定義

  5. 使いたくないメソッドは @Deprecated へ

  6. ViewModel をテスト可能に

① Java から Kotlin へ

この工程は、言語を Java → Kotlin にすることを目的としています。
また最初にこの工程を実施することで、非同期処理を RxJava → Coroutine にするための下地を作ります。

Android Studio の機能で、Java ファイルは Kotlin ファイルに置換します。

画像1

② MVP から MVVM へ

この工程は、アーキテクチャを MVP(Model, View, Presenter) → MVVM にすることを目的としています。

MVPから MVVMにするために、ViewModel を作り、Presenter の処理内容を全て ViewModel に移します。
MVP だとコード量が増えてしまう傾向にあること、また MVVM の方がより簡単に UI の更新できることから 、MVVM 化を進めています。

③ Repository をテスト可能に

この工程は、Non Testable → Testable にすることを目的としています。

Repository のコンストラクタから CoroutineDispatcher を挿入することで、Repository のテストが可能な状態にできます。
*テスト時は TestCoroutineDispatcher に置き換えられる

画像2
引用元:https://developer.android.com/kotlin/coroutines/coroutines-best-practices?hl=ja

④ suspend メソッドを Repository に定義

この工程は、非同期処理を RxJava → Coroutine にすることと、File IO on MainThread → File IO on BackgroundThreadにすることを目的としています。

現状 RxJava で非同期処理している箇所を、より可読性の高い Coroutine の非同期処理にするために、既存の Repository のメソッドに suspend 修飾詞をつけたメソッドを新しく定義します。
下の画像では既存のメソッドが fetachData() 、新しく定義した suspend メソッドが fetchDataNew() です。処理の内容はほぼ同じで、変わったのは suspend がついていることと、withContext() によって実行スレッドをバックグラウンドスレッドに変更していることだけです。

Zaim では長い歴史的な背景もあり Room が MainThread で動くよう設定しています。しかし、ここ最近の Android 開発 ではネットワーク通信処理や File IO 処理を MainThread で動かすのは非推奨で、バックグラウンドスレッドで実行するのが正しいやり方です。
これから新しくネットワーク通信処理や File IO 処理を記述するときは、Repository 内で必ずバックグラウンドスレッドにします。Room が MainThread で動くよう設定している箇所については、今後すべての File IO on MainThread をなくした後に修正したいと思います。

画像3

⑤ 使いたくないメソッドは @Deprecated へ

この工程は、安全にリファクタリングを進める際のコツです。新旧のコードが存在することによる紛らわしさを無くし、上手く共存させる方法を紹介したいと思います。

先程の工程で、処理の内容がほぼ同じ suspned メソッドを新たに定義したのには理由があります。もしその既存メソッドを使う箇所が少なければ、既存メソッドを修正すれば良いと思います。しかし、Zaim のプロジェクトでは既存の Repository メソッドがさまざまな画面で使われていて、既存メソッドを修正しようとすると修正ファイル数が多くなってしまいます。このため、安全にリファクタリングを進めるために、あえて新しく定義したメソッドを用意しました。

しかし、ほとんど同じ処理内容のメソッドが二つ定義してある状態は好ましくありませんので、どちらのメソッドを使うべきか実装者が分かりやすくなるように、古いメソッドには @Deprecated を付与しました。その際、ReplaceWith で使ってほしい新しい suspend メソッドを指定してあげると、Android Studio が推奨してくれるようになるので、より新メソッドへの移行が進みやすくなります。

画像4
画像5
*Android Studio が ReplaceWith 指定の推奨メソッドとの交換アクションを提示する様子

⑥ ViewModel をテスト可能に

この工程は、Non Testable → Testable にすることを目的としています。

これまでの工程を踏めば、ViewModel をテストできるようにするために特別に何かをする必要はありません。
工程 ④ にて、Repository の suspend メソッド内で File IO 処理やネットワーク通信の処理を実行する直前に実行スレッドを変更しているので、ViewModel はバックグラウンドスレッドにする必要がなく、常にテストが可能な状態になっています。

画像6

まとめ

ここまでで、以下のリファクタリングを説明しました。

Java → Kotlin
MVP → MVVM
RxJava → Coroutine
Non Testable → Testable
File IO on MainThread → File IO on BackgroundThread

これらのリファクタリングを進めていった結果、現状プロジェクトの Kotlin 率については 50% 以上になりました!

今回のリファクタリングの工程のいくつかは Android Developers の Coroutine のベストプラクティスに書いてある内容です。

とても分かりやすく Coroutine のベストプラクティスが書いてあるので、まだ読んだことがない方は是非、読んでみてください!

Zaim の Android チームでは Android のモダンな技術を取り入れて、Android エンジニアとしてのキャリアを前進させるのに良い経験ができるプロジェクトにしたいと考えています。
新しい Android の技術が使える環境で開発がしたいという方は、是非 Zaim の Android チームの話を聞きにきてもらえると嬉しいです!


この記事が気に入ったらサポートをしてみませんか?