Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
Tomohiro Imaizumi
May 13, 2022
Technology
1
270
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
iOS Snack bar #1 での発表資料になります
https://ios-snack-bar.connpass.com/event/246443/
Tomohiro Imaizumi
May 13, 2022
Tweet
Share
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
800
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
13
5.4k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.3k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
5
2.6k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
5
4.1k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
6.1k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
3.7k
『「改善Dayを作ろう!」って言ってたけど気づいたらなくなったよね…』を繰り返さないために / Things to Achieve Continuous Improvement in Your Development
imaizume
0
1.5k
(再演)きれいなcommit, pull requestを知りたい/作りたい方のためのgit勉強会
imaizume
5
680
Other Decks in Technology
See All in Technology
Steps toward self-service operations in eureka
fukubaka0825
0
430
Nutanix_Meetup_20220511
keigotomomatsu
0
140
Babylon.js で簡単 WebXR
yuhara0928
2
960
動画配信技術について
yaminoma
0
200
Microsoft 365の中でのPower BIの利用 / M365VM2022
ishiayaya
0
1.5k
モダンデータスタックとかの話(データエンジニアのお仕事とは)
foursue
0
270
長年運用されてきたモノリシックアプリケーションをコンテナ化しようとするとどんな問題に遭遇するか? / SRE NEXT 2022
nulabinc
PRO
15
7.2k
読者のことを考えて書いてみよう / Write with your reader in mind
line_developers
PRO
3
310
msal.jsのあれこれ
takas0522
0
1.5k
jaws-ug-asa-datasync-20220510
hiashisan
0
470
Babylon.jsで3DViewerを作ってみた!!!
iwaken71
1
860
如何使用 Argo Event& Workflow 快速建置自定義的工作流程 @ #CNTUG #47
line_developers_tw
PRO
0
5.2k
Featured
See All Featured
Build your cross-platform service in a week with App Engine
jlugia
219
17k
Become a Pro
speakerdeck
PRO
3
770
Side Projects
sachag
449
37k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_i
21
14k
In The Pink: A Labor of Love
frogandcode
130
21k
The Straight Up "How To Draw Better" Workshop
denniskardys
225
120k
Agile that works and the tools we love
rasmusluckow
319
19k
Learning to Love Humans: Emotional Interface Design
aarron
261
37k
Docker and Python
trallard
27
1.5k
GraphQLの誤解/rethinking-graphql
sonatard
24
6.2k
Embracing the Ebb and Flow
colly
73
3.3k
StorybookのUI Testing Handbookを読んだ
zakiyama
4
2k
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で実現可能な仕様にする まとめ: プロダクトグロースと 技術のベースアップを両立させるには 限られたリソースでプロダクトと技術の成長を両立する 参考事例になれば幸いです 🙏