Upgrade to Pro — share decks privately, control downloads, hide ads and more …

少数精鋭で戦うための技術的改善について

 少数精鋭で戦うための技術的改善について

大人気教育アプリ3社のモバイルアプリ開発秘話 2022/4/20

satoshin21

April 20, 2022
Tweet

More Decks by satoshin21

Other Decks in Programming

Transcript

  1. 会社概要 社名 株式会社mikan 代表 ⾼岡 和正 創業 2014年6⽉16⽇ 資本⾦ 10,000,000円 主要株主

    East Venturesパートナー 松⼭太河 所在 東京都渋⾕区桜丘町29-33-307 事業内容 教育サービスの開発運営 ・国内最⼤級の英語アプリ「mikan」 英語アプリmikanを開発する EdTech toC mikan 
 No. 1 EdTech 
 国内最⼤級の英語アプリ「mikan」
  2. のびしろしかないわ - Deployment TargetがiOS 10(当時) - 多くのデッドコード - 多くのwarning -

    あらゆる所から参照される巨⼤モジュール など - 2014年のコードも存在 - 施策開発コストは元より、施策に関係のない所でQAコスト の増⼤
  3. Deployment TargetをiOS 12に更新 - QA範囲を狭め、テスト⼯数を最適化する - iOS 11以下のワークアラウンドを削除 - Deployment

    TargetをiOS14にする施策は諸事情によりス トップ ‧夏頃に⾏う予定 😁 ‧同時にSwiftUIの導⼊も⾏う予定
  4. 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
  5. 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"]), .. ] )
  6. 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"]), .. ] )
  7. 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"]), .. ] )
  8. 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"]), .. ] )
  9. 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/
  10. 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
  11. 依存を最⼩限に - 実装を依存から切り離す 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" ]),
  12. 依存を最⼩限に - 実装を依存から切り離す 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" ]),
  13. 依存を最⼩限に - 実装を依存から切り離す 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" ]),
  14. 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]”) } } } } }
  15. SwiftUI Preview / Preview Appの導⼊ - Preview⽤のアプリ‧SwiftUI Previewを活⽤することで、 実装⇔レイアウト確認のサイクルを⾼速に回すことができ る

    - Previewの依存をスリムにすることでそのサイクルをより⾼ 速に回すことができる - デザイナーと同期的にデザインレビューと修正を⾏うこと で、よりアプリをブラッシュアップ
  16. Notion + Stencil + Swiftでログ⾃動⽣成 - ログの管理について分析チームと課題感を感じていた ‧必要の無いログ、不要なパラメータ ‧ログを仕込むタイミングの認識不⼀致 など

    - mikanで広く活⽤しているNotionと連携し、ログコードの⾃ 動⽣成を実装 ‧コード⽣成はstencilproject/Stencilを活⽤ ‧SwiftでCLIを実装 https://github.com/stencilproject/Stencil
  17. 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
  18. ⽣成されたログコード /// 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 ) } }