ヤフー株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。LINEヤフー Tech Blog

テクノロジー

新規アプリの開発速度と品質の両立 #PayPayモール #iOSアプリ開発

Yahoo! JAPAN Advent Calendar 2019の25日目の記事です。一覧はこちら(外部リンク)

トップ画像

こんにちは!エンジニアの中神(@shtnkgm)です。
今年10月に新規リリースしたPayPayモールアプリのiOS TechLeadを担当しています。

PayPayモールの立ち上げ以前は、Yahoo!ショッピングアプリの技術改善チームに所属しており「パフォーマンスチューニング」や「技術的負債の解消」など、コードにおける課題と向き合ってきました。

PayPayモールiOSアプリの新規開発では、「開発スピード」と「コード品質」を両立するため、さまざまなチャレンジを行ってきました。 本記事ではそのノウハウの一部をご紹介いたします。

コードレビューを省いてガンガンマージ

通常の開発では、品質向上・知見共有などのメリットからコードレビューを実施しています。しかし、今回の新規開発においては、あえてコードレビューを一時的にやめることにしました。

コードレビューをやめたことにより、レビュータスクによる割り込みがなくなりスピーディーに修正がマージされます。結果、開発ブランチが常に最新の状態となるため、コンフリクトの発生もほとんどありませんでした。開発にスピード感が生まれることで、リファクタリングが活発になった点も良かったと感じています。

コード品質をどう担保するか

単にコードレビューをやめただけでは、コード品質が落ちてしまいます。多少のデグレや不具合はリリース前なので許容しましたが、技術的負債の増加やリリース後の不具合につながりかねません。ここからは、コード品質をどう担保していったのかを説明します。

アプリケーションアーキテクチャの統一

アプリケーション全体のコード品質を整えるため、プロジェクト当初にシステムアーキテクチャの設計を行いました。 PayPayモールiOSでは、MVP+Coordinatorを採用しました。さらに、Model層はUseCase+Repository+Entitiyの粒度で責務を分割しています。

アーキテクチャの選定基準は以下の通りです。

  • ViewControllerが責務過多とならないよう、プレゼンテーションロジックや画面遷移ロジックを分離できること
  • オーバーエンジニアリングにならない範囲で最低限の責務分割ができること
  • 学習コストが高くないこと(大人数で開発することが多いため)
  • ユニットテストがしやすい構造であること

また、コード規模が数万〜数十万行となる大規模アプリ開発ではソースコードのビルド時間が課題となります。
そこでEmbedded Frameworkによるマルチモジュール化を行うことで差分・並列ビルドを効きやすくし、ビルド時間の短縮を図りました。

具体的には、以下のようなモジュール構成・アーキテクチャとなっています。(趣旨と逸れるため詳細な説明は割愛)

Embedded Frameworkによるモジュール構成・アーキテクチャ

アーキテクチャをテンプレート化

Xcodeのファイルテンプレート機能を利用することで、雛形に沿ったコードを一瞬で作成できます。 この機能を利用し、アーキテクチャに定義されたコンポーネントをすべてテンプレート化しました。これにより、大枠のコード設計の均一化と開発速度の大幅な改善が見込めます。

例)テンプレートを選択するだけでViewに必要な実装ファイルが生成される(ViewController、Coordinator、Presenterなど)

テンプレートにはCollectionViewやTableViewの初期設定、共通のログ送信処理なども含まれ、新規画面を数秒で作成できます。以下はテンプレートコードの一例です。

protocol ___VARIABLE_NAME___PresenterInput {
    func sendPV()
    func updateData()
    func numberOfSections() -> Int
    func numberOfRows(in section: Int) -> Int
    func sectionType(at section: Int) -> ___VARIABLE_NAME___SectionType
    func cellType(at indexPath: IndexPath) -> ___VARIABLE_NAME___CellType
    func didSelectItem(at indexPath: IndexPath)
}

protocol ___VARIABLE_NAME___TransitionDelegate: AnyObject {
}

protocol ___VARIABLE_NAME___PresenterOutput: AnyObject {
    func updateView()
}

final class ___VARIABLE_NAME___Presenter {
    weak var output: ___VARIABLE_NAME___PresenterOutput?
    weak var transitionDelegate: ___VARIABLE_NAME___TransitionDelegate?
    private let logRepository: LogRepository<___VARIABLE_NAME___LogData>
    private var cellDataSource = CellDataSource<___VARIABLE_NAME___SectionType, ___VARIABLE_NAME___CellType>()

    init(logRepository: LogRepository<___VARIABLE_NAME___LogData>) {
        self.logRepository = logRepository
    }
}

設計課題が生じたら臨時ペアプログラミングを実施

システムアーキテクチャの定義だけでは、各画面での細かな設計課題に対応できません。そこで導入したのがペアプログラミングです。アプリケーション全体の基盤となる共通機能の開発や開発を進める上で迷いが生じた場合には、随時ペアプログラミングを行い、複数人で設計を議論しながら開発を進めました。複数人で一緒に考えることで、より良い設計ができ、開発につまずくことが減ったように思います。

PayPayモールでは実装機能の新規性や難易度に応じ、ソロプログラミングとペアプログラミングを使い分ける新しいスタイルで生産性の向上にチャレンジしました。

実装箇所のローテーション

ソロプログラミングで同じ機能ばかり開発しているとコードが属人化してしまうため、実装箇所のローテーションも推奨しました。 コードレビューをやめた代わりに、一つの画面の実装に対し複眼でコードを見ることになるため、品質の向上と知見共有につながります。

具体的には実装タスクを細かく分割し、自分のやりたいタスクを取っていくという手法をとりました。タスク管理はGitHubのissueに画面ごとのTODOリストを作成し、Project機能でカンバン管理しました。

実装チェックリスト

こうすることで以下のようなメリットも得られました。

  • チェックボックスで進捗が把握しやすくなる、達成感が得られる
  • 自分にあった難易度のタスクを行えるため、モチベーションアップにつながる
  • 実施タスクが被らないため、複数人での並行作業がしやすい

コード整形、静的解析、修正の自動化

Xcodeのビルド時にタスクを実行できるRun Scriptという機能を利用し、nicklockwood/SwiftFormatでのコードの自動整形、およびrealm/SwiftLintでのコードの静的解析・自動修正を行うようになっています。

特にSwiftLintは、正規表現を用いたカスタムルールを柔軟に定義でき、プロダクトの方針にあった警告を表示できます。例えば、以下のようなルールを定義しています。

  • アーキテクチャに従っていない
  • タイプセーフでない
  • 推奨でないAPIを利用している
  • 不要なコードを含んでいる
  • 変数名とその型に一貫性がない
  • 文法やスペルが正しくない

SwiftLintのエラー画面

実装箇所のローテーションによって発見したコードの問題もカスタムルールにすることで、今後もずっとコードの自動チェックをしてくれる強い味方になります。 PayPayモールアプリでは、現在40を超えるカスタムルールが定義されています。以下にその一例をご紹介します。

# タイプセーフにするため、適切な型が利用されているかチェックする
use_custom_type:
  name: "use custom type"
  regex: "((itemId|itemCode|storeId|categoryId|brandId|imageId|catalogId|orderId): String)"
  message: "idを表現する型はString型でなく、Coreに定義された独自型を利用してください"
  severity: error

# IBOutletのアクセスレベルをチェックする
private_set_iboutlet:
  included: "./(PayPayMall|CoreUIComponent)/.*\\.swift"
  name: "IBOutlet private(set)"
  regex: "@IBOutlet weak"
  message: "IBOutletのアクセスレベルはprivate(set)にしてください"
  severity: error

# モジュール間のimport関係をチェックする
import_in_presenter:
  included: "./(PayPayMall)/.*Presenter\\.swift"
  name: "import in Presenter"
  regex: "import (UIKit|CoreUIComponents)$"
  message: "PresenterではUIKitやCoreUIComponentsのimportは禁止です"
  severity: error

その他の取り組み

上記以外にも、さまざまな自動化・開発効率の改善に取り組んでいます。こちらについてはまた別の機会にご紹介します。

  • Interface Builderの設定の静的解析(デザインシステムで定義した色やフォントに沿っているか)
  • carthage-verifyでのOSSバージョンのチェック
  • SwiftGen/SwiftGenによるリソース取得コード生成
  • Brightify/Cuckooによるユニットテスト用モックコード生成
  • yahoojapan/Hosted Danger(提供終了)によるプルリクエストの自動レビュー
  • SwiftyBeaver/SwiftyBeaverによるロギングやassertionコードによる実装ミスの早期発見

さいごに

PayPayモールiOSアプリの新規開発では、以下の手法により「開発スピード」と「コード品質」の両立にチャレンジしました。

  • アーキテクチャの統一
  • アーキテクチャのテンプレート化
  • 部分的なペアプログラミング
  • 実装箇所のローテーション
  • コード整形、静的解析、修正の自動化

その結果、大きな不具合もなくアプリを無事リリースすることができました。
今後も開発環境の効率化や機能改善に取り組み、より良いアプリを皆様にお届けしていきます。

まだアプリを使ったことがない方はぜひ使ってみてください!(PayPayモール)

こちらの記事のご感想を聞かせください。

  • 学びがある
  • わかりやすい
  • 新しい視点

ご感想ありがとうございました


Shota Nakagami(@shtnkgm

エンジニア

このページの先頭へ