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
880
Reduxを取り入れて開発はpairs開発はどう変わったか
satoshin21
0
350
レガシーなアプリケーションの 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
95
Swift Package Manager V4でAlfred Workflowを作ろう
satoshin21
0
230
Other Decks in Programming
See All in Programming
プロダクトの品質に コミットする / Commit to Product Quality
pekepek
2
770
バグを見つけた?それAppleに直してもらおう!
uetyo
0
180
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
160
14 Years of iOS: Lessons and Key Points
seyfoyun
1
770
KMP와 kotlinx.rpc로 서버와 클라이언트 동기화
kwakeuijin
0
140
[JAWS-UG横浜 #76] イケてるアップデートを宇宙いち早く紹介するよ!
maroon1st
0
450
ゆるやかにgolangci-lintのルールを強くする / Kyoto.go #56
utgwkk
1
370
Cloudflare MCP ServerでClaude Desktop からWeb APIを構築
kutakutat
1
540
Refactor your code - refactor yourself
xosofox
1
260
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
190
CSC305 Lecture 26
javiergs
PRO
0
140
range over funcの使い道と非同期N+1リゾルバーの夢 / about a range over func
mackee
0
110
Featured
See All Featured
Optimising Largest Contentful Paint
csswizardry
33
3k
Building a Scalable Design System with Sketch
lauravandoore
460
33k
Code Reviewing Like a Champion
maltzj
520
39k
Raft: Consensus for Rubyists
vanstee
137
6.7k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
17
2.3k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
GraphQLとの向き合い方2022年版
quramy
44
13k
Product Roadmaps are Hard
iamctodd
PRO
49
11k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.2k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
38
1.9k
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 エンジニア⼤募集!!!
おわり