Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
分析用コードをアプリから 切り離す設計の実現
Search
makun
November 22, 2019
Programming
150
0
Share
分析用コードをアプリから 切り離す設計の実現
#pixiv_app_night
makun
November 22, 2019
More Decks by makun
See All by makun
No More Writing Test Code: JetBrains AI Assistant Automates Design and Generation of Asynchronous Processing Tests
makun
0
68
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
2.2k
既存コードへのテスト追加とリファクタリングの実践
makun
0
140
Jetpack Composeを本番導入してみた結果と課題
makun
1
270
Compose Compiler Metrics 詳細と活用方法
makun
1
930
Other Decks in Programming
See All in Programming
AI駆動開発で崩れていくコードベースを立て直す
kyoko_nr_nr
1
380
分析エージェント精度向上における データアナリストの役割
oura_shoya
0
120
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
5
690
デフォルト運用のCodeRabbit、1年で何が変わったか / How CodeRabbit Changed Our Code Review in 1 Year
bake0937
1
110
サークル参加から学ぶ、小さな事業の回し方
yuzneri
0
240
3Dシーンの圧縮
fadis
1
350
Make SRE Operations Easier with Azure SRE Agent
kkamegawa
0
760
ふつうのFeature Flag実践入門
irof
6
2.9k
TypeScriptだけでAIエージェントを作る フロント・エージェント・インフラのフルスタック実践
har1101
6
1k
Modding RubyKaigi for Myself
yui_knk
0
470
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
150
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
0
120
Featured
See All Featured
Bash Introduction
62gerente
615
210k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
70
39k
First, design no harm
axbom
PRO
2
1.2k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
180
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
410
Amusing Abliteration
ianozsvald
1
180
Site-Speed That Sticks
csswizardry
13
1.2k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
340
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
190
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
120
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
250
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
65
54k
Transcript
分析用コードをアプリから 切り離す設計の実現 pixiv Inc. makun 2019.11.22
2 自己紹介 • 18年 新卒入社 • Androidアプリ開発を担当 • エンジニア採用を担当 •
好きなのは設計やアイデアだし • 苦手なのは収束させること makun アプリエンジニア
3 • マンガサービス • 講談社と協業 • 女性がターゲット • 女性誌特集なども •
Android、iOS
アーキテクチャ 4
5 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server
Repository Database Entity Action Action Item Entity Remote Model Local Model
6 Database Entity Core Feature Production App Repository Development App
Feature Feature Repository Repository Database Database Remote Model Local Model Presentation Domain Data Resources
分析コードとは 7
ユーザーの行動を把握しアプリのマーケティン グやパフォーマンス改善に関する 意思決定を行うためのデータを得ることのでき るコード、もしくは得るためのコード 8
分析コードを 実装するとは 9
意思決定を行うためのデータを得ることのでき るコード、もしくは得るためのコードが動作する よう記述する 10
パルシィのコードに 分析コードを実装してみる 11
12
class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch
{ … } } } 13
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics .getInstance(context.applicationContext) .logEvent(“follow_comic”, bundleOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 14
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro.track(“【フォロ】コミック”, mapOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 15
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro.track(…) AppsFlyerLib.getInstance().trackEvent( context.applicationContext, “key_follow_comic”, mapOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 16
本当にこの実装でいいんだっけ? 17
Context受け取るようになったけど テストのことちゃんと考慮してる? 18
同じ分析コードを埋め込む箇所が 増えたときはどうする? 19
機能モジュールの依存増えてない? ビルド時間とかも大丈夫? 20
分析ツールが 増えたり減ったりしたときは どうする? 21
そもそもなんでこんなに 分析ツールあんの? 22
23
24 ツール 目的 Firebase ピクシブ全体で積極的に利用している BigQueryにデータをあげて全体的な分析に利用 Repro 協業先に利用実績があり 特定のアクションに対してアプリ内メッセージをだしたりに利用 AppsFlyer
協業先に利用実績があり アプリの流入や経路別のコンバージョンをみるために利用 分析用ツールを複数使う理由
分析コードを実装する場 合の設計を考える 25
26 それぞれの視点で考える 設計時にやること
27 機能を開発する人の視点
28 分析コードを追加する人の視点
29 今回は触れない視点 • アプリを利用するユーザーの視点 • テストの視点 • リファクタリングの視点
30 設計次以外のメリット • コードを書くときにも有効 • 視点ごとにプルリクをわけられる • 実装時の思考コストが減る • レビュー時の思考コストが減る
• 意味のあるまとまりで開発をすすめられる
機能を開発する人の視点でみてみる 31
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 32
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 33 ユーザーがボタンをタップし たときの処理をここにつくる
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 34 要求だとFirebase、Repro、 AppsFlyerにイベントを送信す る必要があるぞ
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 35 分析コードの実装のために Contextを受け取らないと いけないぞ
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 36 今回の要求だとComicの Entityだけでデータはおくれ そうだぞ
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 37 Context追加しちゃったから メソッドを実行してる部分に もContextわたさないといけ ないぞ
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 38 あとテストも書き直さないと いけないなぁ
39 Entity Core Feature Production App Feature Feature Firebase Repro
AppsFlyer
分析コードを実装する人の視点でみてみる 40
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 41 まずはこのコードまでたどりつく必要がある
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 42 フォローボタンをタップした ときで、すでにフォローして いたかどうかが判定されて いない
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ if (comic.isFollowed.not()) { FirebaseAnalytics… Repro… AppsFlyerLib… } launch { … } } } 43
44 分析コードはロジックをもつ
45 ユーザー体験とは関係ないはずなのに・・ • ユーザー体験にかかわるところ (Featureモジュール) にコードをかく • 既存の処理を変更したりしないといけないことがある • 変更をくわえちゃうから実機のテストもちゃんとしておきたくなる
• テストもちゃんとかいておきたくなる • 考えないといけないところが増えた気がする
パルシィでの設計 46
Flux + Tracker 47
48 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server
Repository Database Entity Action Action Item Entity Remote Model Local Model
49 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server
Repository Database Tracker Entity Action Action Item Action Entity Remote Model Local Model
// Actionを受け取れる概念をinterfaceにする interface ActionReceiver { fun receive(action: Action) } 50
// Dispatcherを継承する本番用Dispatcher class MainDispatcher( // ActionReceiverを実装したクラスを受け取る private vararg val receivers:
ActionReceiver ) : Dispatcher() { override fun dispatch(action: Action) { // 全てのActionReceiverにActionを送信する receivers.forEach { it.receive(action) } super.dispatch(action) } } 51
// Dispatcherを継承する本番用Dispatcher class MainDispatcher( // ActionReceiverを実装したクラスを受け取る private vararg val receivers:
ActionReceiver ) : Dispatcher() { override fun dispatch(action: Action) { // 全てのActionReceiverにActionを送信する receivers.forEach { it.receive(action) } super.dispatch(action) } } 52
// Firebase用にActionReceiverを実装したクラス // このクラスのインスタンスをMainDispatcherにわたす // パルシィではKoinでBeanをつくる class FirebaseTracker( private val
app: Application ) : ActionReceiver { override fun receive(action: Action) { FirebaseAnalytics… } } 53
class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch
{ dispatch(TapFollowComicAction(comic)) // 以下に実際のフォロー処理をかく … } } } 54
55 Entity Core Feature Production App Feature Feature Firebase Repro
AppsFlyer
56 Entity Core Feature Production App Feature Feature Tracker
結果と考察 57
class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic)
{ FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 58
class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch
{ dispatch(TapFollowComicAction(comic)) // 以下に実際のフォロー処理をかく … } } } 59 ・分析コードをかく必要がない ・機能開発だけに集中できる ・レビューコストが減る
60 Entity Core Feature Production App Feature Feature Firebase Repro
AppsFlyer
61 Entity Core Feature Production App Feature Feature Tracker 機能開発がモジュール内だけで完結
62 Entity Core Feature Production App Feature Feature Tracker 機能開発がモジュール内だけで完結
分析コードのロジックが Trackerモジュールにまとまる
63 Firebase Tracker Repro Tracker AppsFlyer Tracker Tracker ツールそれぞれのロジックが 各モジュールにまとまる
Production App
64 Feature Tracker Production App Firebase Tracker 分析コード実装時の考える依存が少ない
65 Production App Firebase Tracker 分析コード実装時の手を加えたり、考えるス クープはさらに小さい
快適な実装 66
ミスの少ない実装 67
最高 68