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
90
Swift Package Manager V4でAlfred Workflowを作ろう
satoshin21
0
230
Other Decks in Programming
See All in Programming
AWS IaCの注目アップデート 2024年10月版
konokenj
3
3.3k
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
5
1.5k
イベント駆動で成長して委員会
happymana
1
300
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
210
詳細解説! ArrayListの仕組みと実装
yujisoftware
0
560
開発効率向上のためのリファクタリングの一歩目の選択肢 ~コード分割~ / JJUG CCC 2024 Fall
ryounasso
0
440
CSC509 Lecture 12
javiergs
PRO
0
140
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.1k
Hotwire or React? ~アフタートーク・本編に含めなかった話~ / Hotwire or React? after talk
harunatsujita
1
110
Streams APIとTCPフロー制御 / Web Streams API and TCP flow control
tasshi
2
340
CSC509 Lecture 11
javiergs
PRO
0
180
Jakarta EE meets AI
ivargrimstad
0
270
Featured
See All Featured
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
700
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
47
2.1k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
400
Why Our Code Smells
bkeepers
PRO
334
57k
Documentation Writing (for coders)
carmenintech
65
4.4k
Making the Leap to Tech Lead
cromwellryan
133
8.9k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
28
8.2k
4 Signs Your Business is Dying
shpigford
180
21k
Gamification - CAS2011
davidbonilla
80
5k
BBQ
matthewcrist
85
9.3k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
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 エンジニア⼤募集!!!
おわり