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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
360
Can LLMs Replicate 4 Years of Compose Migration? Exploring the boundaries of automation with 279 XML files from a real product
makun
0
130
No More Writing Test Code: JetBrains AI Assistant Automates Design and Generation of Asynchronous Processing Tests
makun
0
87
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
2.3k
既存コードへのテスト追加とリファクタリングの実践
makun
0
170
Jetpack Composeを本番導入してみた結果と課題
makun
1
280
Compose Compiler Metrics 詳細と活用方法
makun
1
950
Other Decks in Programming
See All in Programming
まだ間に合う!今年の夏こそSchemeのマクロ展開器を完全理解!
omasanori
0
620
Deep dive into the select statement (GopherCon UK)
jespino
0
170
AIエージェント時代のコードレビューを設計する
nogu66
5
2.4k
AWS Step Functions 大規模並列の壁を越える / jaws-sonic-2026-niigata-step-functions
kasacchiful
PRO
0
300
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
140
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
100
Building an Out-of-Order CPU
latte72
0
630
in-process GraphQL のすすめ #ginzajs
izumin5210
4
1.5k
이 함수, 실패하면 어떻게 되나요? null부터 Rich Errors까지, Kotlin 에러 처리 15년
haeti2
0
120
{ Android | Kotlin } Gradle Plugin in 2026
ryunen344
1
270
数年滞っていたダークモード対応をおよそ2週間で完了させる
chigichan24
0
660
What We Talk About When We Talk About XP
m_seki
2
320
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Odyssey Design
rkendrick25
PRO
2
800
Agile that works and the tools we love
rasmusluckow
331
22k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
230
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
410
The SEO Collaboration Effect
kristinabergwall1
1
550
The Cult of Friendly URLs
andyhume
79
7k
Docker and Python
trallard
47
4.2k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
510
WCS-LA-2024
lcolladotor
0
820
Optimizing for Happiness
mojombo
378
71k
Evolving SEO for Evolving Search Engines
ryanjones
0
280
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