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.3k
少数精鋭で戦うための技術的改善について
大人気教育アプリ3社のモバイルアプリ開発秘話 2022/4/20
satoshin21
April 20, 2022
Tweet
Share
More Decks by satoshin21
See All by satoshin21
GTXiLibで小さく始めるAccessibility Testing
satoshin21
0
5.2k
iPhoneのカメラで写真撮影から現像までの技術を紐解く
satoshin21
4
3.6k
try! swift-sh
satoshin21
2
990
Reduxを取り入れて開発はpairs開発はどう変わったか
satoshin21
0
380
レガシーなアプリケーションの 60fps化を目指す為にやっていること
satoshin21
12
4.1k
Introducing CodeLayout with Tips
satoshin21
6
1.7k
World of No Interface Builder
satoshin21
0
1.9k
What I've done to attend WWDC
satoshin21
0
140
Swift Package Manager V4でAlfred Workflowを作ろう
satoshin21
0
280
Other Decks in Programming
See All in Programming
GeistFabrik and AI-augmented software development
adewale
PRO
0
180
AI駆動開発ライフサイクル(AI-DLC)のホワイトペーパーを解説
swxhariu5
0
1.5k
Flutterアプリ運用の現場で役立った監視Tips 5選
ostk0069
1
520
競馬で学ぶ機械学習の基本と実践 / Machine Learning with Horse Racing
shoheimitani
14
14k
Eloquentを使ってどこまでコードの治安を保てるのか?を新人が考察してみた
itokoh0405
0
3.2k
なぜ強調表示できず ** が表示されるのか — Perlで始まったMarkdownの歴史と日本語文書における課題
kwahiro
12
7.2k
「文字列→日付」の落とし穴 〜Ruby Date.parseの意外な挙動〜
sg4k0
0
290
乱雑なコードの整理から学ぶ設計の初歩
masuda220
PRO
32
15k
Private APIの呼び出し方
kishikawakatsumi
3
900
Microservices Platforms: When Team Topologies Meets Microservices Patterns
cer
PRO
0
540
Querying Design System デザインシステムの意思決定を支える構造検索
ikumatadokoro
1
1.2k
AIエージェントでのJava開発がはかどるMCPをAIを使って開発してみた / java mcp for jjug
kishida
4
780
Featured
See All Featured
Understanding Cognitive Biases in Performance Measurement
bluesmoon
31
2.7k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
680
A better future with KSS
kneath
239
18k
GraphQLの誤解/rethinking-graphql
sonatard
73
11k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
Facilitating Awesome Meetings
lara
57
6.6k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.4k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
36
6.1k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Speed Design
sergeychernyshev
33
1.3k
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 エンジニア⼤募集!!!
おわり