Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
分析用コードをアプリから 切り離す設計の実現
Search
makun
November 22, 2019
Programming
0
130
分析用コードをアプリから 切り離す設計の実現
#pixiv_app_night
makun
November 22, 2019
Tweet
Share
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
36
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
1.7k
既存コードへのテスト追加とリファクタリングの実践
makun
0
120
Jetpack Composeを本番導入してみた結果と課題
makun
1
240
Compose Compiler Metrics 詳細と活用方法
makun
1
890
Other Decks in Programming
See All in Programming
C-Shared Buildで突破するAI Agent バックテストの壁
po3rin
0
360
AIコーディングエージェント(Manus)
kondai24
0
150
CloudNative Days Winter 2025: 一週間で作る低レイヤコンテナランタイム
ternbusty
7
2k
NUMA環境とコンテナランタイム ― youki における Linux Memory Policy 実装
n4mlz
1
200
tparseでgo testの出力を見やすくする
utgwkk
1
160
【CA.ai #3】Google ADKを活用したAI Agent開発と運用知見
harappa80
0
290
非同期処理の迷宮を抜ける: 初学者がつまづく構造的な原因
pd1xx
1
660
AIコーディングエージェント(Gemini)
kondai24
0
180
WebRTC、 綺麗に見るか滑らかに見るか
sublimer
1
160
Socio-Technical Evolution: Growing an Architecture and Its Organization for Fast Flow
cer
PRO
0
300
WebRTC と Rust と8K 60fps
tnoho
2
1.9k
ID管理機能開発の裏側 高速にSaaS連携を実現したチームのAI活用編
atzzcokek
0
200
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
970
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Being A Developer After 40
akosma
91
590k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
KATA
mclloyd
PRO
32
15k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Designing Experiences People Love
moore
143
24k
Why Our Code Smells
bkeepers
PRO
340
57k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
380
GitHub's CSS Performance
jonrohan
1032
470k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
1
87
Site-Speed That Sticks
csswizardry
13
990
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