Slide 1

Slide 1 text

熟成されたアプリの multi module化(halfway) Shibuya.apk #33

Slide 2

Slide 2 text

自己紹介 • Android, Embedded system, BLE, iOS • DeNA Co., Ltd. Automotive Business Unit. • 最近、少し高めのカメラレンズ(約8.5万円)を購入 tomoya0x00 Twitter, GitHub, Qiita

Slide 3

Slide 3 text

比較的、泥臭い話をします

Slide 4

Slide 4 text

その中で皆様にとって 有益な情報があれば嬉しいです

Slide 5

Slide 5 text

アウトライン 熟成されたアプリとは? なぜmulti module化するのか? 最初のmodule分割 残りをmodule分割 1 4 3 2

Slide 6

Slide 6 text

熟成されたアプリとは?

Slide 7

Slide 7 text

熟成されたアプリと名乗るには 『必要な要素』 だと思ったモノに、 手を上げてください

Slide 8

Slide 8 text

クラスの循環参照が発生 ※Daggerのlazyで無理矢理コンパイルを通している

Slide 9

Slide 9 text

状態をあわらすフラグが多数存在

Slide 10

Slide 10 text

Rxの乱用で 処理を追うのが困難

Slide 11

Slide 11 text

Singletonかつ、 mutableなクラスが多数存在

Slide 12

Slide 12 text

レイヤーを無視した クラス間の参照

Slide 13

Slide 13 text

複数のアーキテクチャが混在

Slide 14

Slide 14 text

パッケージのトップに 色々なファイルが置いてある

Slide 15

Slide 15 text

複数の神クラスが存在 ※神クラス = 巨大かつ様々なクラスに依存されている

Slide 16

Slide 16 text

WebAPIのレスポンスを そのままいたるところで使いまわす ※なお、全てのプロパティがnullable

Slide 17

Slide 17 text

安心して下さい

Slide 18

Slide 18 text

今回お話しする 「熟成されたアプリ」は

Slide 19

Slide 19 text

全ての要素を満たしています!!

Slide 20

Slide 20 text

話を続けましょう

Slide 21

Slide 21 text

なぜmulti module化するのか?

Slide 22

Slide 22 text

理由1 機能ごとのlibrary moduleに分割し、 新規アプリで使いたい

Slide 23

Slide 23 text

理由2 library module間の依存関係を強制し クラス間の循環参照を撲滅したい

Slide 24

Slide 24 text

最初のmodule分割

Slide 25

Slide 25 text

準備

Slide 26

Slide 26 text

準備 1. 参考資料・ソースに目を通す 2. 切り出したい機能と、 仮のmodule名を一覧化する 3. ブランチかフォークか決める

Slide 27

Slide 27 text

参考資料・ソースに目を通す ~ その1 ~ • kgmyshinさんの マルチモジュールのすヽめ と 大きめのAndroidアプリでの 設計を考えてみる~pocket~ • レイヤーによるmodule分割と、 機能によるmodule分割

Slide 28

Slide 28 text

参考資料・ソースに目を通すす ~ その2 ~ • クックパッドアプリの マルチモジュール化への取り組み • 既存の全ての機能を legacy library moduleに移動 • DroidKaigi/conference-app-2019 • 機能ごと(画面も含めて)にlibrary moduleを分割 • interfaceだけのmoduleと、実装のmoduleに分割

Slide 29

Slide 29 text

切り出したい機能と、仮のmodule名を一覧化する • 分析ログ • analytics-log • BLE通信 • ble • 位置情報 • location etc...

Slide 30

Slide 30 text

ブランチかフォークか決める ~ ブランチの場合 ~ • 既存リポジトリでブランチを切って作業 • かなり変更量大のプルリクをつくる事になる • 既存の熟成されたアプリへの影響大 • 途中でdevelopブランチへのマージは厳しい • フルのリグレッションテストを何回も実施??

Slide 31

Slide 31 text

ブランチかフォークか決める ~ フォークの場合 ~ • 既存リポジトリをコピーした 別リポジトリで作業 • 既存リポジトリへの影響無しに作業できる • ただし、どこかのタイミングで 既存リポジトリの差分マージが必要

Slide 32

Slide 32 text

ブランチかフォークか決める ~ 結論 ~ • フォークしてモジュール分割を進め、 新規アプリ開発を優先 • モジュール分割が落ち着いたら 既存リポジトリの差分をマージ • いずれ既存アプリも、 フォークした別リポジトリからリリース

Slide 33

Slide 33 text

まずは一つ moduleを分割して 様子をみる

Slide 34

Slide 34 text

今後のmodule分割作業の 見積もり精度が上がる

Slide 35

Slide 35 text

どの機能をmodule分割するか?

Slide 36

Slide 36 text

自分がほとんど書いた機能 (BLE通信周り) ならば、難易度が低いと判断

Slide 37

Slide 37 text

自分が知っている機能なら 楽勝でしょ?

Slide 38

Slide 38 text

考えが甘かった (結構大変だった)

Slide 39

Slide 39 text

一週間ぐらいかかった

Slide 40

Slide 40 text

最初のmodule分割で やって良かったことや学びなど

Slide 41

Slide 41 text

最初のmodule分割でやって良かったことや学びなど • Dep.ktとVersions.ktの導入 • 具体的なmodule分割の手順 • Android Studio様は強い味方 • 細かいTips • ディレクトリ、BuildConfig、ProductFlavor • Dagger周り

Slide 42

Slide 42 text

Dep.ktとVersions.ktの導入

Slide 43

Slide 43 text

Dep.ktとVersions.ktの導入 multi module化すると 各library moduleにbuild.gradleがあるため、 指定するバージョンなどがバラバラになる危険性 Kotlinで各dependenciesやバージョンを記入し、 各build.gradleで使用する 参考: DroidKaigi/conference-app-2019のdependenciesディレクトリ Kotlin + buildSrc for Better Gradle Dependency Management

Slide 44

Slide 44 text

Dep.ktとVersions.ktの導入 ~ Dep.kt ~ Dep.kt: 各dependenciesを記入 object Moshi { private val version = "1.8.0" val moshi = "com.squareup.moshi:moshi-kotlin:$version" val codegen = "com.squareup.moshi:moshi-kotlin-codegen:$version" }

Slide 45

Slide 45 text

Dep.ktとVersions.ktの導入 ~ Versions.kt ~ Versions.kt: compileSdkVersionやversionCode(※ソースでは省略)などを記入 object Versions { const val androidCompileSdkVersion = 28 const val androidMinSdkVersion = 23 const val androidTargetSdkVersion = 28 }

Slide 46

Slide 46 text

具体的なmodule分割の手順

Slide 47

Slide 47 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 48

Slide 48 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 49

Slide 49 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 50

Slide 50 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 51

Slide 51 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 52

Slide 52 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 53

Slide 53 text

名前にハイフンが入っているとダメ →後からリネームならOK

Slide 54

Slide 54 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 55

Slide 55 text

具体的なmodule分割の手順 ~ module新規追加 ~

Slide 56

Slide 56 text

具体的なmodule分割作業の手順 ~ build.gradle編集 ~ • library moduleのbuild.gradle編集 • DroidKaigi/conference-app-2019の android.gradleを参考に、 library moduleで共通してapplyするBuild Script導入がお勧め • 各dependenciesの記入 • app moduleや、必要に応じて既存のlibrary moduleのdependenciesに追加

Slide 57

Slide 57 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~

Slide 58

Slide 58 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~

Slide 59

Slide 59 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~

Slide 60

Slide 60 text

意外と楽ちん!?

Slide 61

Slide 61 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~

Slide 62

Slide 62 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~ • 参照できないクラスが色々あって怒られてる • Retrofit周り • library moduleのbuild.gradleに追加 • API Responseのclass • app moduleにあるので参照できない • library moduleへ同時に移動する

Slide 63

Slide 63 text

より効率良くおこなうならば?

Slide 64

Slide 64 text

具体的なmodule分割作業の手順 ~ クラスの移動 ~ 1. 移動するクラスの依存先をリストアップ a. import文でわかる 2. Retrofitなどlibraryはbuild.gradleに追加 3. アプリ内部のクラスであれば同時に移動 a. 単純に移動できない場合は、interface抽出して 一旦依存性を切り離すなど

Slide 65

Slide 65 text

Android Studio様は強い味方

Slide 66

Slide 66 text

Android Studio様は強い味方 ~ 強力なリファクタリング機能 ~ • クラスを別moduleへ移動 • 「具体的なmodule分割作業の手順」で紹介済み • interface抽出 • クラスからPublicなプロパティ、メソッドを選択してinterface 抽出 • ネストされたクラスの移動 • 抽出したinterfaceが、実装クラスにネストされた Enumクラスに依存している場合などに便利

Slide 67

Slide 67 text

Android Studio様は強い味方 ~ interface抽出 ~

Slide 68

Slide 68 text

Android Studio様は強い味方 ~ interface抽出 ~

Slide 69

Slide 69 text

Android Studio様は強い味方 ~ interface抽出 ~

Slide 70

Slide 70 text

Android Studio様は強い味方 ~ ネストされたクラスの移動 ~

Slide 71

Slide 71 text

Android Studio様は強い味方 ~ ネストされたクラスの移動 ~

Slide 72

Slide 72 text

Android Studio様は強い味方 ~ ネストされたクラスの移動 ~

Slide 73

Slide 73 text

細かいTips • 階層化にディレクトリを使うか否か • library module で BuildConfig を参照 • Product Flavorとリファクタリング

Slide 74

Slide 74 text

細かいTips ~ 階層化にディレクトリを使うか否か ~

Slide 75

Slide 75 text

細かいTips ~ 階層化にディレクトリを使うか否か ~

Slide 76

Slide 76 text

Android View だとディレクトリ無視 →moduleがフラットに名前順で並ぶ

Slide 77

Slide 77 text

気になる場合は ディレクトリでは無く module名の接頭語で階層化

Slide 78

Slide 78 text

細かいTips ~ 階層化にディレクトリを使うか否か ~

Slide 79

Slide 79 text

細かいTips ~ library module で BuildConfig を参照 ~ • BuildConfigから参照したいプロパティの interfaceを作成 • library moduleは上記のinterfaceに依存 • app moduleで実装をlibrary moduleに注入

Slide 80

Slide 80 text

細かいTips ~ library module で BuildConfig を参照 ~ interface EnvVar { val BUILD_TYPE: String val API_HOST: String } object EnvVarImpl : EnvVar { override val BUILD_TYPE = BuildConfig.BUILD_TYPE override val API_HOST= BuildConfig.API_HOST }

Slide 81

Slide 81 text

細かいTips ~ Product Flavorとリファクタリング ~ Product Flavorでソース切替している場合 現在のFlavor以外はリファクタリング対象外

Slide 82

Slide 82 text

Dagger周り DroidKaigi/conference-app-2019 を参考に library moduleごとに ModuleとComponentを作成

Slide 83

Slide 83 text

残りをmodule分割

Slide 84

Slide 84 text

ふむふむ、やり方はわかった

Slide 85

Slide 85 text

色々踏み抜いたし残りは楽勝っしょ!

Slide 86

Slide 86 text

module分割を続行・・・

Slide 87

Slide 87 text

できない

Slide 88

Slide 88 text

元々の依存関係がぐちゃぐちゃで interfaceの実装クラスを 各moduleに切り出せない・・・

Slide 89

Slide 89 text

interface抽出して 依存関係を逆転させても 実装自体の依存関係が 逆転するわけではない

Slide 90

Slide 90 text

どうするのか?

Slide 91

Slide 91 text

正攻法で立ち向かう

Slide 92

Slide 92 text

何が依存関係を複雑にしているのか?

Slide 93

Slide 93 text

何が依存関係を複雑にしているのか? ● APIレスポンスを保持して提供する 神クラスが複数存在 ○ ビジネスロジックも持っている ● 分析ログ機能 ○ 複数の神クラスに依存

Slide 94

Slide 94 text

2種類のリポジトリを導入

Slide 95

Slide 95 text

WebApiをラップするRepository ● レスポンスをmodelに変換して Observableで提供 ● データ更新の関数は結果を返さず、 更新のトリガーとするだけ

Slide 96

Slide 96 text

神クラス公開情報を保存し、提供するRepository ● 神クラスのみが書き込む ● 神クラス以外は読み込みオンリー ● これで神クラスへの直接依存を防げる

Slide 97

Slide 97 text

module分割が続行可能となった

Slide 98

Slide 98 text

迷っているところ

Slide 99

Slide 99 text

multi moduleでのDagger周りの 話しを皆様から聞きたい!!

Slide 100

Slide 100 text

ありがとうございました