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.2k
少数精鋭で戦うための技術的改善について
大人気教育アプリ3社のモバイルアプリ開発秘話 2022/4/20
satoshin21
April 20, 2022
Tweet
Share
More Decks by satoshin21
See All by satoshin21
GTXiLibで小さく始めるAccessibility Testing
satoshin21
0
5k
iPhoneのカメラで写真撮影から現像までの技術を紐解く
satoshin21
4
3.3k
try! swift-sh
satoshin21
2
910
Reduxを取り入れて開発はpairs開発はどう変わったか
satoshin21
0
350
レガシーなアプリケーションの 60fps化を目指す為にやっていること
satoshin21
12
3.8k
Introducing CodeLayout with Tips
satoshin21
6
1.6k
World of No Interface Builder
satoshin21
0
1.8k
What I've done to attend WWDC
satoshin21
0
100
Swift Package Manager V4でAlfred Workflowを作ろう
satoshin21
0
240
Other Decks in Programming
See All in Programming
Linux && Docker 研修/Linux && Docker training
forrep
23
4.5k
Grafana Loki によるサーバログのコスト削減
mot_techtalk
1
110
お前もAI鬼にならないか?👹Bolt & Cursor & Supabase & Vercelで人間をやめるぞ、ジョジョー!👺
taishiyade
5
3.8k
Amazon S3 TablesとAmazon S3 Metadataを触ってみた / 20250201-jawsug-tochigi-s3tables-s3metadata
kasacchiful
0
100
第3回 Snowflake 中部ユーザ会- dbt × Snowflake ハンズオン
hoto17296
4
360
一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜
atsumim
0
110
Ruby on cygwin 2025-02
fd0
0
140
社内フレームワークとその依存性解決 / in-house framework and its dependency management
vvakame
1
550
密集、ドキュメントのコロケーション with AWS Lambda
satoshi256kbyte
0
170
バックエンドのためのアプリ内課金入門 (サブスク編)
qnighy
8
1.7k
Rails アプリ地図考 Flush Cut
makicamel
1
110
Pulsar2 を雰囲気で使ってみよう
anoken
0
230
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.8k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.3k
Code Review Best Practice
trishagee
66
17k
Mobile First: as difficult as doing things right
swwweet
223
9.3k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.8k
Why Our Code Smells
bkeepers
PRO
335
57k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.5k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.1k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7.1k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
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 エンジニア⼤募集!!!
おわり