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
satoshin21
April 20, 2022
Programming
3
1.1k
少数精鋭で戦うための技術的改善について
大人気教育アプリ3社のモバイルアプリ開発秘話 2022/4/20
satoshin21
April 20, 2022
Tweet
Share
More Decks by satoshin21
See All by satoshin21
GTXiLibで小さく始めるAccessibility Testing
satoshin21
0
4.9k
iPhoneのカメラで写真撮影から現像までの技術を紐解く
satoshin21
4
3.2k
try! swift-sh
satoshin21
2
850
Reduxを取り入れて開発はpairs開発はどう変わったか
satoshin21
0
340
レガシーなアプリケーションの 60fps化を目指す為にやっていること
satoshin21
12
3.8k
Introducing CodeLayout with Tips
satoshin21
6
1.5k
World of No Interface Builder
satoshin21
0
1.8k
What I've done to attend WWDC
satoshin21
0
91
Swift Package Manager V4でAlfred Workflowを作ろう
satoshin21
0
230
Other Decks in Programming
See All in Programming
Amazon Qを使ってIaCを触ろう!
maruto
0
400
Jakarta EE meets AI
ivargrimstad
0
530
タクシーアプリ『GO』のリアルタイムデータ分析基盤における機械学習サービスの活用
mot_techtalk
4
1.4k
ふかぼれ!CSSセレクターモジュール / Fukabore! CSS Selectors Module
petamoriken
0
150
Tauriでネイティブアプリを作りたい
tsucchinoko
0
370
「今のプロジェクトいろいろ大変なんですよ、app/services とかもあって……」/After Kaigi on Rails 2024 LT Night
junk0612
5
2.1k
Arm移行タイムアタック
qnighy
0
320
Ethereum_.pdf
nekomatu
0
460
Flutterを言い訳にしない!アプリの使い心地改善テクニック5選🔥
kno3a87
1
170
Pinia Colada が実現するスマートな非同期処理
naokihaba
4
220
とにかくAWS GameDay!AWSは世界の共通言語! / Anyway, AWS GameDay! AWS is the world's lingua franca!
seike460
PRO
1
860
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
230
Featured
See All Featured
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
720
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
44
2.2k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Fireside Chat
paigeccino
34
3k
Making Projects Easy
brettharned
115
5.9k
Designing for Performance
lara
604
68k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
26
1.4k
Building Adaptive Systems
keathley
38
2.3k
Measuring & Analyzing Core Web Vitals
bluesmoon
4
120
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
42
9.2k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
Transcript
⼤⼈気教育アプリ 3社 のモバイルアプリ開発秘話 2022/4/20 @satoshin21 少数精鋭で戦うための 技術的改善について
会社概要 社名 株式会社mikan 代表 ⾼岡 和正 創業 2014年6⽉16⽇ 資本⾦ 10,000,000円 主要株主
East Venturesパートナー 松⼭太河 所在 東京都渋⾕区桜丘町29-33-307 事業内容 教育サービスの開発運営 ・国内最⼤級の英語アプリ「mikan」 英語アプリmikanを開発する EdTech toC mikan No. 1 EdTech 国内最⼤級の英語アプリ「mikan」
コアバリューが⽀持され国内最⼤級に成⻑ AppStore ランキング 教育無料‧有料ともに カスタマー評価 4.8 位 ★ アプリダウンロード NO.1
1
mikanの実績 *出典 令和⼆年度 ⽂部科学省「⾼等学校教育の現状について」/ 令和⼆年度 ⽂部科学省「⾼等専⾨学校(⾼専)について」 全国の⾼校・⾼専カバー率 100% 国内4,931校*全てに mikan ユーザーがいます フューチャーや受賞多数
ベストトレンドアプリ ベスト⾃⼰改善アプリ e-Learning⼤賞 Edtech部⾨
mikan iOSチーム - フルタイム2⼈ - フリーランス1⼈、業務委託1⼈の計4⼈
mikan iOSチーム - 事業は新しいフェーズへ ‧「英単語」から「英語」への拡⼤ - その分アプリ開発は「攻め」の要素が強め ‧それ⾃体は意思決定の話なので悪いことではない ‧Firestoreへの移⾏など守り要素の強いPjも⾏っていた
whoami - satoshin21 - 2021/11 iOSエンジニアとしてJoin!
のびしろしかないわ - Deployment TargetがiOS 10(当時) - 多くのデッドコード - 多くのwarning -
あらゆる所から参照される巨⼤モジュール など - 2014年のコードも存在 - 施策開発コストは元より、施策に関係のない所でQAコスト の増⼤
のびしろしかないわ - 1⼈のiOSエンジニアがPMに転⾝してフルタイムが3⼈から2 ⼈に! - フルタイム2⼈+2⼈で施策開発を進めることの危機感 ‧mikanというプロダクトに対する知⾒が浅い ‧もう1⼈のフルタイムも1年前にジョイン
今回話すこと - 今回は主に僕が対応したちょっとした技術的改善について お話します - iOSチームとしての活動はpodcastで配信予定
少数で戦うための 技術改善
1. コードのお掃除
Deployment TargetをiOS 12に更新 - QA範囲を狭め、テスト⼯数を最適化する - iOS 11以下のワークアラウンドを削除 - Deployment
TargetをiOS14にする施策は諸事情によりス トップ ‧夏頃に⾏う予定 😁 ‧同時にSwiftUIの導⼊も⾏う予定
デッドコードの削除 - デッドコードは新しい開発者にとって無駄に考慮が必要な 要素になる - peripheryapp/peripheryを⽤いてLocal Swift Package以外 の不要コードを削除 https://github.com/peripheryapp/periphery
2. SwiftPM
Swift Package Managerによるモジュール管理 - もともとXcodeGenによるマルチモジュール構成になってい た - d_date sanの記事(※)、pointfreeco/isowordsの構成に近 づける
- よりモジュール/Target分割を⼿軽に - 環境切替時のxcodeprojの⽣成コストも削減 ※https://www.notion.so/Swift-PM-Build-Configuration-4f14ceac795a4338a5a44748adfeaa40 https://github.com/pointfreeco/isowords
Swift Package Managerによるモジュール管理 let package = Package( name: "mikan", platforms:
[ .iOS(.v12), ], products: [ .library(name: "AppFeature", targets: ["AppFeature"]), ], dependencies: [ .package(name: "Firebase", url: "https://github.com/firebase/firebase-ios-sdk", from: "8.7.0"), .. ], targets: [ .binaryTarget(name: "Helpshift", path: "Frameworks/Helpshift.xcframework"), // 最新バージョンとの差分が多く対応が難しかったため⼀旦Carthageで追加。 .binaryTarget(name: "FMDB", path: "Carthage/Build/FMDB.xcframework"), .target(name: "BookDetail"), .testTarget(name: "BookDetailTests", dependencies: ["BookDetail"]), .. ] )
Swift Package Managerによるモジュール管理 let package = Package( name: "mikan", platforms:
[ .iOS(.v12), ], products: [ .library(name: "AppFeature", targets: ["AppFeature"]), ], dependencies: [ .package(name: "Firebase", url: "https://github.com/firebase/firebase-ios-sdk", from: "8.7.0"), .. ], targets: [ .binaryTarget(name: "Helpshift", path: "Frameworks/Helpshift.xcframework"), // 最新バージョンとの差分が多く対応が難しかったため⼀旦Carthageで追加。 .binaryTarget(name: "FMDB", path: "Carthage/Build/FMDB.xcframework"), .target(name: "BookDetail"), .testTarget(name: "BookDetailTests", dependencies: ["BookDetail"]), .. ] )
Swift Package Managerによるモジュール管理 let package = Package( name: "mikan", platforms:
[ .iOS(.v12), ], products: [ .library(name: "AppFeature", targets: ["AppFeature"]), ], dependencies: [ .package(name: "Firebase", url: "https://github.com/firebase/firebase-ios-sdk", from: "8.7.0"), .. ], targets: [ .binaryTarget(name: "Helpshift", path: "Frameworks/Helpshift.xcframework"), // 最新バージョンとの差分が多く対応が難しかったため⼀旦Carthageで追加。 .binaryTarget(name: "FMDB", path: "Carthage/Build/FMDB.xcframework"), .target(name: "BookDetail"), .testTarget(name: "BookDetailTests", dependencies: ["BookDetail"]), .. ] )
Swift Package Managerによるモジュール管理 let package = Package( name: "mikan", platforms:
[ .iOS(.v12), ], products: [ .library(name: "AppFeature", targets: ["AppFeature"]), ], dependencies: [ .package(name: "Firebase", url: "https://github.com/firebase/firebase-ios-sdk", from: "8.7.0"), .. ], targets: [ .binaryTarget(name: "Helpshift", path: "Frameworks/Helpshift.xcframework"), // 最新バージョンとの差分が多く対応が難しかったため⼀旦Carthageで追加。 .binaryTarget(name: "FMDB", path: "Carthage/Build/FMDB.xcframework"), .target(name: "BookDetail"), .testTarget(name: "BookDetailTests", dependencies: ["BookDetail"]), .. ] )
3. Preview App
SwiftUI Preview / Preview Appの導⼊ - 実装⇔確認のフローを⾼速化するためにプレビューを導⼊ - UIKit +
SwiftUI Preview(※) - Preview Apps - Preview Apps Targetの依存を最⼩限(なるべくUIに限定) にすることで各Previewのビルドを⾼速化 - Preview AppはXcodeGenを⽤いて⽣成 ‧Includeでプロジェクト設定を⼀括管理 ※ https://engineering.mercari.com/blog/entry/2019-12-13-155700/
XcodeGenでPreview⽤のアプリを⽣成 name: SamplePreview include: ../preview_app_project.yml schemes: SamplePreview: build: targets: PreviewApp:
all targets: PreviewApp: type: application platform: iOS sources: - path: Sources dependencies: - package: mikan product: SampleFeature
XcodeGenでPreview⽤のアプリを⽣成
依存を最⼩限に - 実装を依存から切り離す let package = Package( .. products: [
.library(name: "SampleFeature", targets: ["Sample"]), ], dependencies: [ .. ], targets: [ .target(name: "Sample", dependencies: [ "DesignToken", "UseCase", ]), .target(name: "UseCase", dependencies: [ "Entity" ]), .target(name: "UseCaseImpl", dependencies: [ “UseCase", "AppCore" ]),
依存を最⼩限に - 実装を依存から切り離す let package = Package( .. products: [
.library(name: "SampleFeature", targets: ["Sample"]), ], dependencies: [ .. ], targets: [ .target(name: "Sample", dependencies: [ "DesignToken", "UseCase", ]), .target(name: "UseCase", dependencies: [ "Entity" ]), .target(name: "UseCaseImpl", dependencies: [ “UseCase", "AppCore" ]),
依存を最⼩限に - 実装を依存から切り離す let package = Package( .. products: [
.library(name: "SampleFeature", targets: ["Sample"]), ], dependencies: [ .. ], targets: [ .target(name: "Sample", dependencies: [ "DesignToken", "UseCase", ]), .target(name: "UseCase", dependencies: [ "Entity" ]), .target(name: "UseCaseImpl", dependencies: [ “UseCase", "AppCore" ]),
Preview App struct ContentView: View { var body: some View
{ NavigationView { VStack(alignment: .leading, spacing: 20) { NavigationLink { SampleView(state: .success(.mock())) } label: { Text("成功画⾯”) } NavigationLink { SampleView(state: .failed(.session)) } label: { Text("失敗画⾯ [Session Error]”) } NavigationLink { SampleView(state: .failed(.notFound)) } label: { Text("失敗画⾯ [NotFound Error]”) } } } } }
SwiftUI Preview / Preview Appの導⼊ - Preview⽤のアプリ‧SwiftUI Previewを活⽤することで、 実装⇔レイアウト確認のサイクルを⾼速に回すことができ る
- Previewの依存をスリムにすることでそのサイクルをより⾼ 速に回すことができる - デザイナーと同期的にデザインレビューと修正を⾏うこと で、よりアプリをブラッシュアップ
4. ログコード⽣成
Notion + Stencil + Swiftでログ⾃動⽣成 - ログの管理について分析チームと課題感を感じていた ‧必要の無いログ、不要なパラメータ ‧ログを仕込むタイミングの認識不⼀致 など
- mikanで広く活⽤しているNotionと連携し、ログコードの⾃ 動⽣成を実装 ‧コード⽣成はstencilproject/Stencilを活⽤ ‧SwiftでCLIを実装 https://github.com/stencilproject/Stencil
Notionでログ定義
Swift CLIでログ⽣成 $ swift run --package-path BuildTools -c release \
log_gen Sources/ActionLog/Logs.generated.swift # Notion API経由でログ情報を取得 [log_gen] requesting.. # Stencil templateを元にSwiftコードを⽣成 [log_gen] writing.. [log_gen] complete! Log file is generated to Sources/ActionLog/ Logs.generated.swift
⽣成されたログコード /// Sample画⾯が表示された時 impression ログ /// /// [Notion:Sample画⾯が表示された時](https://www.notion.so/page-id) public struct
SampleImpression: LogType { struct Parameters: Encodable { public let parameter1: String public let parameter2: Int enum CodingKeys: String, CodingKey { case parameter1 = "parameter1" case parameter2 = "parameter2" } } let location: String = "sample" let action: String = "impression" let parameters: Parameters? public init(parameter1: String, parameter2: Int) { self.parameters = .init( parameter1: parameter1, parameter2: parameter2 ) } }
Notion + Stencil + Swiftでログ⾃動⽣成 - 静的解析ツールを使えば未使⽤のログコードも検知可能 ‧実装漏れ、不要なログを検知 - ログの詳細を知りたくなったらURLからNotionページに遷移
可能 - 現在は試験運⽤中だが、メンテ‧開発コストは⼤きく下っ ていると感じる
5. 最も効果があった改善は..
None
お⾦で解決! - 発表直後にすぐ⽀給してもらった ‧mikan安⼼キットで最新スペックのPCを導⼊可能 ‧モリモリでいきましょう!ということでMaxにしてもらっ た ‧環境は違うが、8分かかってたビルドが3分で終わるよう に ‧3倍弱仕事できないとおかしいですね
さいごに
さいごに - まだまだこれからも技術的改善‧チャレンジは⾏っていく ‧GraphQL導⼊、SwiftUI、KMM.. - 技術的改善を他プロジェクトと平⾏して進めていくための 開発プロセスについて検討中 - そんな改善に興味のある、mikanを⼀緒に作っていけるiOS エンジニア⼤募集!!!
おわり