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
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
分析用コードをアプリから 切り離す設計の実現
#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
83
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
2.3k
既存コードへのテスト追加とリファクタリングの実践
makun
0
160
Jetpack Composeを本番導入してみた結果と課題
makun
1
280
Compose Compiler Metrics 詳細と活用方法
makun
1
950
Other Decks in Programming
See All in Programming
わからない話を追いかけたら、プログラミング言語を作る側にいた
ydah
3
520
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
3
800
<title><a id="</title>君はこのHTMLをパースできるか"></a></title> #雑LT_study
pizzacat83
0
150
GDG Korea Android: 2026 I/O Extended ~ What's new in Android development tools
pluu
0
230
the container ship “Apple Silicon”@WWDC26 Recap -Japan-\(region).swift
shingangan
0
130
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
se4weed
1
390
楽しそうなつよつよエンジニアと目が死んでる僕/A brilliant engineer having a blast, and dead-eyed me.
3l4l5
2
190
ソフトウェアエンジニアにとっての生成AI - 特性を知って使い倒す / generative ai for software enginner
kishida
5
1.9k
AI時代に設計が 最大の生産性レバーになる 意図駆動開発とデータを消さない設計|Don't Delete Your Data or Your Intent — Design as the Deepest Lever in the AI Era
tomohisa
1
1k
変わらないものが、変わるものを決める — 意図駆動開発 × イベントソーシング × イミュータブル | What Doesn't Change Decides What Can — IDD × Event Sourcing × Immutability
tomohisa
0
1.8k
Go 1.27 における memory allocation の高速化
andpad
0
240
リアルな遅延を測る仕様
kota_yata
1
120
Featured
See All Featured
The Language of Interfaces
destraynor
162
27k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
370
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
230
KATA
mclloyd
PRO
35
15k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
The untapped power of vector embeddings
frankvandijk
2
1.8k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
430
Between Models and Reality
mayunak
4
390
A designer walks into a library…
pauljervisheath
211
24k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
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