ZOZOTOWN iOSアプリでのFatViewController解消への取り組み

 ZOZOTOWN iOSアプリでのFatViewController解消への取り組み

はじめに

こんにちは、ZOZOTOWN開発1部iOSブロックのなんしーです。普段はZOZOTOWN iOSアプリの新機能の開発や既存画面のリファクタリングなどを担当しています。

ZOZOTOWN iOSアプリは2010年11月にサービスを開始して以来、ZOZOSUITやZOZOGLASSをはじめ、様々な新機能を提供してきました。最近では、購入した商品に対してレビューが投稿できる「アイテムレビュー」という機能も追加されました。

新機能が追加されていく一方、以下のようなレガシーな部分が残り続けてしまっていることが課題となってきています。

  • Objective-Cで書かれたコードが残ってしまっていること
  • API通信等のビジネスロジックにあたる部分がViewController内部に書かれており、Fat ViewControllerになってしまっていること

今回はZOZOTOWN iOSアプリに残るFat ViewControllerを取り上げ、どのように解消を図っているのかをご紹介します。

目次

商品詳細画面が抱える課題

ZOZOTOWN iOSアプリにはさまざまな画面が存在しています。その中でも商品の詳細情報を確認できる画面(以下、商品詳細画面)は、改修の頻度が高く、CVRにも直結する重要な画面の1つです。

商品詳細画面

そんな重要な画面であるにもかかわらず、商品詳細画面は以下のような課題を抱えていました。

  • API通信など、ビジネスロジックにあたる実装がViewController内に書かれていてる
  • 責務が分割できておらず、複数人で同時に開発できる設計になっていない
  • ユニットテストが存在しておらず、かつ書ける設計になっていない
  • コード行数は2,500行を超えており、可読性が悪い

商品詳細画面はいわゆるFat ViewControllerになってしまっており、ViewControllerが抱えるべきではない責務が多々入り込んでしまっている状況でした。

そんな状況の中、以下のような話が挙がりました。

  • 複数のプロジェクトで商品詳細画面に変更を加える計画が浮上した
  • ZOZOTOWN開発本部では「開発生産性の向上1」が重要な目標の1つとして掲げられた

Fat ViewControllerになってしまっている状況では、複数人が同時に開発することは難しい状況にありました。また、メンテナンス性や可読性の悪いコードベースになってしまっているため、開発生産性の向上を図る上での障壁となっていました。今後も商品詳細画面にはさまざまな改修が想定されるため、大規模なリファクタリングの検討を始めました。

リファクタリングを進めるために

商品詳細画面をリファクタリングするにあたり、以下を目標にリファクタリングを進めることにしました。

  • 責務が分割できており、複数人が同時に開発できる状態であること
  • ユニットテストが書ける状態であること
  • 新規機能を追加するとなった際、今までよりも小さい工数で実装できること

これらを達成するため、次のようなアプローチでリファクタリングを進めていきました。

大規模なリファクタリングのための工数確保

複数のプロジェクトが並行で進むこと、「開発生産性の向上」が目標として掲げられていることに鑑み、大規模なリファクタリングを決断しました。そのため、まずは大規模なリファクタリングを行うための工数を確保するところから始めました。

プロジェクトの合間の時間を使用してリファクタリングを進めることも検討しましたが、リファクタリングの完了までに時間がかかりすぎてしまう懸念がありました。そのため、リファクタリング自体をプロジェクト化して進められないかを検討しました。

ZOZOTOWN開発本部では、時間がかかったり、プロダクト品質向上につながったりする改善タスクはプロジェクト化し、工数を確保して進めるという文化があります。そのため、今回の商品詳細リファクタリングも同様にプロジェクト化し、まとまった工数を確保しつつ進めることになりました。

こうして、大規模なリファクタリングを進めるための工数を確保できました。

新たなアーキテクチャの採用

ZOZOTOWN iOSチームではMVVM(Model-View-ViewModel)アーキテクチャを採用しています。しかし、この構成では1画面での機能が増えた際、ViewModelの責務が肥大化してしまい、Fat ViewModelになってしまうという課題がありました。今回のリファクタリング対象である商品詳細画面は特に機能が多い画面であるため、Fat ViewModelとなる懸念が挙がりました。

そこで、Android Architecture Componentsを参考に、Domain Layer(UseCase)を導入することにしました。Domain Layerを設けることでViewModelからビジネスロジックを分離でき、Fat ViewModelになってしまうことを避けることができました。

最終的なclass構成と依存関係は以下のようになります。

商品詳細画面の全体設計

UseCaseを導入した結果、ビジネスロジックをViewModelから切り離すことができ、ViewModelは画面の状態管理の責務を担うようにできました。その結果、ViewModelの肥大化を防ぎ、コードの保守性が大幅に向上しました。

チームとしてのリファクタリングの進め方

ここまではリファクタリングを進めるための前段階の話をご紹介しました。ここからは、ZOZOTOWN iOSアプリチームでのリファクタリングの進め方についてお話しします。

以下のようなステップに分け、チームで認識のすり合わせをしながらリファクタリングを進めました。

  1. アーキテクチャレビューの実施
  2. リファクタリングを複数のStepを分割
  3. Stepごとにリファクタリングを実施
  4. StepごとにQA、リリース

アーキテクチャレビュー

ZOZOTOWN iOSチームでは、新規で画面を作る際にアーキテクチャレビューという会を行なっています。アーキテクチャレビューでは、「基本設計を実現するために必要なアーキテクチャを把握し、それをチーム内でどう開発すべきかを合意すること」を目的としています。実装前に擦り合わせをしておくことで、実装時の手戻りを減らすだけでなく、Pull Requestをレビューする際の負担軽減も期待できます。

アーキテクチャレビューでレビューしている内容は以下の2点です。

  • レイヤー間の値の受け渡し
  • レイヤー内の責務分割

レイヤー間の値の受け渡しでは、各レイヤー間でどのような値が、どのようなインタフェースで実現されているか、レイヤーを跨ぐ際に値がどう変換されるかをレビューします。アーキテクチャレビューでは、各レイヤーのProtocolをもとにレビューをします。

/// APIからのレスポンスをパースするためのModel
struct GoodsDetailResponse: Decodable {}

/// 商品の詳細情報を表現する、View用のModel
struct GoodsDetail {}

protocol GoodsDetailAPIClientProtocol {
    func getGoodsDetail(
        id: Int,
        completion: @escaping ((Result<GoodsDetailResponse, Error>) -> Void)
    )
}

protocol GoodsDetailUseCaseProtocol {
    func fetchGoodsDetail(
        id: Int,
        completion: @escaping ((Result<GoodsDetail, Error>) -> Void)
    )
}

protocol GoodsDetailViewModelInputsProtocol {
    func viewDidLoad()
}

protocol GoodsDetailViewModelOutputsProtocol {
    var goodsDetailAnyPublisher: AnyPublisher<GoodsDetail, Never>
}

上記のようなProtocolをもとにアーキテクチャレビューを実施します。このProtocolに沿って実装すると、成果物となるコードもある程度予想でき、設計時と実装時の認識のズレを減らすことができるのではと期待しています。

リファクタリングを複数のStepに分割

商品詳細画面は抱える機能も多く、変更量はとても多くなることが予想されました。一気にリファクタリングした場合、不具合を発生させるリスクやQAでの見落としが懸念されました。そのため、今回のリファクタリングでは複数のStepに分割し、段階的なリリースをする方針で進めました。

商品詳細画面リファクタリングのStep分割は、APIの呼び出し単位で分割しました。APIの呼び出し単位でStepを分割することで、Pull Requestのレビューがしやすいといったメリットもありました。また、Stepを細かく分割していることで、急遽優先度の高いプロジェクトが入ってきた時でも柔軟に対応できました。

おわりに

本記事では、Fat ViewControllerになってしまっている商品詳細画面をリファクタリングする話をご紹介しました。リファクタリングを始めるタイミングで設定した目標通り、責務は適切な粒度で分割され、ユニットテストも書かれている状態になりました。まだ比較はできていませんが、新機能を追加するとなった場合も今までより少ない工数で実装できる見込みです。

商品詳細画面だけでなく、ZOZOTOWN iOSアプリにはまだまだレガシーなコードが残っています。今後も負債と向き合いつつ、より良いコードを目指し改善を進めていく予定です。

ZOZOでは、一緒にサービスを作り上げてくれる仲間、レガシーなコードを書き換えていく仲間を募集中です。ご興味がある方は、以下のリンクからぜひご応募ください!

hrmos.co

corp.zozo.com


  1. ZOZOTOWNにおける開発生産性の向上に関する取り組みに関しては、「ZOZOTOWNにおける開発生産性向上に関する取り組み」のスライドをご覧ください。
カテゴリー