熟成されたアプリのmulti module化(halfway)
by
Tomoya Miwa
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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
ありがとうございました