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
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル ...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tomohiro Imaizumi
May 13, 2022
Technology
890
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
iOS Snack bar #1 での発表資料になります
https://ios-snack-bar.connpass.com/event/246443/
Tomohiro Imaizumi
May 13, 2022
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
VisionFrameworkで実現する - プライバシーに配慮した「顔ぼかし」機能 / Face blurring with Vision Framework
imaizume
0
360
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development
imaizume
3
4.7k
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.3k
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
14
7.9k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.9k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
6
4.3k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
6
5.3k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8.7k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
5.2k
Other Decks in Technology
See All in Technology
【Cyber-sec+】経営層を"動かす"ための考え方
hssh2_bin
0
190
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
2
550
Socrates × Looker 〜セマンティックレイヤーで進化するデータ分析エージェント〜
hanon52_
3
2.4k
LayerXにおけるセキュリティ管理の現在地と次の一手
tosho
0
210
Snowflakeと仲良くなる第一歩
coco_se
4
480
エンジニアリング戦略の作り方 / Crafting Engineering Strategy
iwashi86
21
7k
Oracle AI Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
6
1.5k
気軽に使える"情報のハブ"としてのNotion活用 〜フロー情報の集積点 と、 Claude Code × Notion AI〜
syucream
1
140
マルチアカウント環境での コーディングエージェントを使った障害調査が大変なので AIエージェントにReadOnly権限を付与してみた / ReadOnly AI Agents for Multi-Account AWS Incident Response
yamaguchitk333
2
110
2026年6月23日 Syncable Tech + Start Python Club にて
hamukazu
0
120
200個のGitHubリポジトリを横断調査したかった
icck
0
130
日本 Fintech 未来予測レポート 2027〜2028年(手動編集版)
8maki
0
2.3k
Featured
See All Featured
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Music & Morning Musume
bryan
47
7.2k
My Coaching Mixtape
mlcsv
0
150
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
580
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
230
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
190
Code Review Best Practice
trishagee
74
20k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
580
Mobile First: as difficult as doing things right
swwweet
225
10k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.6k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
Transcript
プロダクトグロースと 技術のベースアップを両立させる Rettyのアプリ開発スタイル Tomohiro Imaizumi @imaizume iOS Snack bar #1
2022/05/13
自己紹介 Tomohiro Imaizumi (@imaizume) • 2019年11月入社 • 担当業務 ◦ iOS
/ Android / アプリ向けサーバー開発 ◦ Scrum Master / 採用育成 / 業務改善 • 今日話せなかったこと・個人的な話はぜひMeetyで!
🔗 全国20ヶ所以上でワーケーションした私と 外で働く楽しさを語りませんか ? https://meety.net/matches/cmGHdDXSJLHC 🔗 User Happyを目指す iOS開発について語りませんか
https://meety.net/matches/wxbcVgDSiJZF
目次 1. Rettyでのアプリ開発での前提 2. 技術的成果 3. プロダクトグロースと技術のベースアップを両立するための具体的な 取り組み プロダクトと技術の成長を 限られたリソースで両立するときの
参考事例にしてください🙏
Rettyのアプリ開発を取り巻く環境
• チーム構成: 技術横断型 (LeSS) ◦ iOS / Android / APIサーバーを5名前後でメンテ
◦ 得意分野をベースに他領域もカバー (非分業) ◦ App単体だけでなくWeb・toB向けも共同で開発 • プロダクトドリブンな開発 ◦ 施策開発が中心 ◦ 「技術は手段」の位置付け ◦ 2年程前から長期戦略に基づく開発 へシフト Rettyのアプリ開発を取り巻く環境 限られたリソースで施策開発と技術向上を両立する必要 短期的施策から長期戦略に基づく施策へ 1つのバックログ・チームの役割
結果 • 過去の「点ベース」施策による技術負債が溜まりがち ◦ メンテしにくいReactNativeを使った画面 ◦ 特定UIや機能を実現するために導入した、メンテが止まったライブラリ ◦ WebViewによる実装 •
技術的改善やベースアップ専用の時間は取りづらい ◦ ライブラリのバージョンアップが追いつかない ◦ warningやdeprecated API使用箇所の増加 • 今後の長期戦略に耐えられる基礎技術の更新がも必要に ◦ UIKitからの脱却 ◦ 自前CIのメンテコストの削減
最近の成果
直近2~3年のプロダクト的成果 (新機能) • Go To Eat / PayPay キャンペーン ◦
プロダクトへの集客や回遊を上げる • 新人気店ラベル ◦ 「似た好みのユーザーさんたちがオススメするお店」を人気店として再定義 • 好きラベル ◦ その人が好き・詳しいジャンルを可視化し、好みの近い「人からお店が探せる」を目指す • マイベストリニューアル ◦ ユーザーさんのベストなお店のシェア体験を改善 • オススメラベル ◦ おすすめしている人の見える化で、人気の根拠・人から価値の信頼性を上がる • プロフィール編集画面ネイティブ化 ◦ プロフィールの表示設定がスムーズに
新機能 Go To Eatキャンペーン 好きジャンル/マイベスト 新人気店・おすすめラベル プロフィール編集
直近2~3年の技術的成果 • ReactNative/UIKitからの脱却 • SwiftUI/Combineを使った宣言的UI化 • Renovate導入によるライブラリ更新の定常化 • swiftlint/dangerでの自動スタイルチェック •
uber/mockoloで自動Mock生成 • ReSwift-Thunkへの移行 • Bitrise / GitHub ActionsでTest/ベータ配信 • Feature Flagsを使った開発の推進
タイムライン ReactNative廃止 2021/06 SwiftUI製画面リリース 2021/03 Bitrise導入 2021/04 Renovate導入 2021/02
Mockolo導入 2020/11 swiftlint/danger導入 2021/11 ReSwift-Thunk移行完了 2022/01 Feature Flags 2021/11 ネイティブプロフィール編集 2021/12 Go To Eat キャンペーン 2020/10 おすすめラベル 2022/04 マイベストリニューアル 2021/12 好きラベル 2022/02 新人気店リリース 2021/11 PayPayボーナスキャンペーン 2021/02
プロダクトグロースと 技術のベースアップを 両立するための具体的な取り組み
両立するためのポイント 1. タイミング: 新規実装や事故をきっかけに 2. 複利的改善: 運用負荷軽減/開発効率向上を重視 3. スリム化: 標準APIで実現可能な仕様にする
1. タイミング (新規実装や事故をきっかけに)
新規実装でのSwiftUI/Combineの導入 • UIKitでの開発・レビューに限界が • 不安はありつつもGlobal検索画面で導入 (2021/03) ◦ 関心高いメンバーが試験的に実装 & チームに普及
• 新規の画面 / Viewに本格導入を開始 (2021/07) ◦ 新規画面 : SwiftUI + Combine製をデフォルトに ◦ 既存実装: リプレースは基本的に無価値のためやらない • iOS 13サポート切りが必要 ◦ 導入当初はiOS13サポートが一番キツかった 😢 ◦ Rettyでは2021年末でサポートを終了 改善を単体で行わず 日々の開発に取り入れる SwiftUIの導入箇所 完全SwiftUI製 部分的SwiftUI製
struct AttributedText: UIViewRepresentable { private let attributedText: NSAttributedString private let
linkTextAttributes: [NSAttributedString.Key: Any] private let onTap: (URL) -> Void @Binding private var height: CGFloat init( _ attributedText: NSAttributedString, linkTextAttributes: [NSAttributedString.Key: Any] = [:], dynamicHeight: Binding<CGFloat>, onTap: @escaping (URL) -> Void = { _ in } ) { _height = dynamicHeight self.attributedText = attributedText self.linkTextAttributes = linkTextAttributes self.onTap = onTap } func makeUIView(context _: Context) -> UITextView { let view = TextView(onTap: onTap) // 独自のTextViewを実装 view.delegate = view view.attributedText = attributedText view.linkTextAttributes = linkTextAttributes } } Extensionの例 : AttributedTextをSwiftUI向けに実装 class TextView: UITextView, UITextViewDelegate { private var onTap: (URL) -> Void = { _ in } init(onTap: @escaping (URL) -> Void) { self.onTap = onTap super.init(frame: .zero, textContainer: nil) } required init?(coder _: NSCoder) { fatalError() } func textView( _: UITextView, shouldInteractWith url: URL, in _: NSRange, interaction _: UITextItemInteraction ) -> Bool { onTap(url) return false } }
public final class UIHostingCell<Content>: UITableViewCell where Content: View { private
let hostingController = FixedSafeAreaInsetsHostingViewController<Content?>(rootView: nil) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) hostingController.view.backgroundColor = .clear } deinit { removeHostingController() } func configure(_ view: Content, parent: UIViewController) { hostingController.rootView = view hostingController.view.invalidateIntrinsicContentSize() hostingController.view.fillConstraint(to: contentView) … } } Extensionの例 : UIHostingCell https://medium.com/@hongseongho/43321a9e9e90
Renovateの導入 • renovatebot/renovate • インシデントをきかっけに ◦ 未更新ライブラリが原因でインシデント発生 ◦ 更新コスト削減のために導入 •
導入後 ◦ 定常的に更新PRが出ている状態に ◦ ライブラリ起因のインシデントは発生せず ◦ QA項目記載のみで更新可 • 今後 ◦ QAの作業負荷軽減 (UITestの充実など) ◦ SPMへの移行 { "labels": ["renovate"], "extends": ["config:base"], "commitMessagePrefix": "[ci skip]", "packageRules": [ { "groupName": "FBSDK", "managers": ["cocoapods"], "matchPackagePatterns": ["^FBSDK"], "prPriority": 5 }, … ] } renovate.json インシデントの再発防止は 優先度を上げて取り組みやすい
2. 複利的改善 (開発効率向上 / 運用負荷軽減を重視)
SwiftUI統一で技術可用性と採用力強化 • UIKIt + ReactNative ▶ SwiftUIへ一本化 • 新規参入のハードルを下げる ◦
2022新卒もSwiftUI未経験から開始し即戦力に ◦ ペアプロ・レビューも容易でデリバリが高速に ◦ AppCode + Code With Me + johnno1962/InjectionIII • AndroidでもJetpack Composeを導入 ◦ 宣言的UIフレームワークでコードの類似性が高い ◦ Android ⇔ iOS でタスクをシェアしやすくなる ▶ 技術可用性が向上 ◦ 設計・ドメイン用語に一貫性をもたせやすく • 一定基準を満たせばFlutter経験者も採用候補に チーム全体でのアウトプット量を増やす → 新しい事・技術改善がしやすくなる (1.01の法則) ≒
SwiftUIとJetpack Composeの比較 (人気店ラベル) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier
.clip(shape = RoundedCornerShape(2.dp)) .background(brush = level.labelBackground), ) { Text( modifier = Modifier .clip(shape = RoundedCornerShape(1.dp)) .background( brush = level.categoryNameBackground ) .padding( horizontal = 4.dp, vertical = size.categoryNameVerticalPaddingSize ), text = "${name}好き", fontSize = size.fontSize, color = level.categoryNameTextColor, fontWeight = FontWeight.Bold, ) Text( modifier = Modifier.padding( horizontal = size.popularityTextHorizontalPaddingSize ), text = "人気店", fontSize = size.fontSize, color = level.popularityTextColor, fontWeight = FontWeight.Bold, ) } HStack(alignment: .center, spacing: 2) { Text("\(name)好き") .foregroundColor(level.categoryNameTextColor) .fontWeight(.bold) .font(.system(size: size.fontSize)) .padding(.horizontal, 4) .padding(.vertical, size.verticalPadding) .background(level.categoryNameBackground) .cornerRadius(1) Text("人気店") .foregroundColor(level.suffixTextColor) .fontWeight(.bold) .font(.system(size: size.fontSize)) .padding(.horizontal, 4) } .padding(2) .background(level.badgeBackground) .cornerRadius(2) SwiftUI (iOS) Jetpack Compose (Android) 人気店ラベル
運用負荷軽減のための自動化・SaaS利用 • マニュアル作業・自前メンテナンスを極力減らす • β版配信 ◦ 自前Macmini + Firebase App
Distribution(FAD) ▶ GitHub Actions + FAD ◦ 配信用スクリプトのメンテナンスを廃止 • バナーの表出制御 ◦ 自前APIサーバー ▶ Firebase Remote Config ◦ エンジニア不要でキャンペーンバナーの表出が可能に • Slack WF ◦ QAからリリースまで関係者とのコミュニケーションを半自動化 継続的/安定的に本質的プロダクト開発ができる体制を構築
オフショアの活用 継続的/安定的に本質的プロダクト開発ができる体制を構築 • 2021年末からはオフショアを活用 • 主な依頼内容 ◦ warningの解消 (294 ▶
38) ◦ ライブラリ更新 (ReSwift-Thunk移行など一定コストがかかる定形作業 ) ◦ 施策開発の一部 (仕様が明確かつ納期がないもの ) • 国内の開発コストを上げないため ◦ コードレビューコストの削減 (SwiftLint / danger導入) ◦ GitHubカンバン • 国内作業はQA項目作成のみ
オフショアや外部サービスの活用 国内エンジニア 外部サービス 国内エンジニア オフショア 外部サービス 施策開発 施策外開発 運用 運用
施策開発 & 技術改善 施策外開発 運用 運用 施策外開発 施策開発 BEFORE AFTER
3. スリム化 (標準APIで実現可能な仕様にする)
サードパーティーライブラリへの依存を増やさない • 導入から削除まで一定コストが発生 ◦ 技術の比較検討 / バージョン更新と追従 / 対応機能の置換 ◦
Rettyでは極力依存を減らすことがコスト減になると判断 • 削除したライブラリたち ◦ siteline/SwiftUI-Introspect ▪ SwiftUI v1で仕様実現のため導入 ▶ 仕様調整 & OS13終了で不要に ◦ andreamazz/AMPopTip ▪ オンボーディング用ツールチップ表示に利用 ▶ 体験上不要と判断し削除 ◦ CEWendel/SWTableViewCell ▪ 横スワイプ可能なセルの実装に使用 ▶ 標準APIで実現可能なため削除 ◦ Alamofire/AlamofireImage ▪ APIクライアントはAlamofire、画像の取得/表示はKingfisherへ ▪ Background実行は自前実装(BackgroundTaskManager) へ 標準APIで実現するのが長期的に高コスパ
• 前提 ◦ 少人数 & プロダクトドリブンのうえで小さく改善を進める ◦ 施策・技術の両面で少しずつ成果が出始めている段階 • 両立するための取り組みポイント
◦ タイミング: 新規実装や事故をきっかけに ◦ 複利的改善: 運用負荷軽減/開発効率向上を重視 ◦ スリム化: 標準APIで実現可能な仕様にする まとめ: プロダクトグロースと 技術のベースアップを両立させるには 限られたリソースでプロダクトと技術の成長を両立する 参考事例になれば幸いです 🙏