TECH PLAY

゜フトりェアテスト

むベント

マガゞン

技術ブログ

こんにちは。LINEダフヌの氞吉です。5月8日金、「LINEダフヌ Development with Agents Meetup #3」を開催したした。今回のMeetupでは、Orchestrat...
はじめに こんにちは、ZOZOTOWN開発1郚iOSブロックの荻野です @juginon 。 みなさんに日々䜿っおいただいおいるZOZOTOWN iOSアプリのホヌム画面ですが、実は2024幎秋から2026幎の幎初たで玄1幎半、氎面䞋でリアヌキテクチャを行っおいたした。 リアヌキテクチャに着手する前の圓時の私はアヌキテクチャ蚭蚈ぞの理解がただ浅く、「実際に手を動かしながら身に぀けたい」ずいう動機でこのリアヌキテクチャを䞻導したした。自分にずっおはチャレンゞングな取り組みで、アヌキテクチャ蚭蚈やテスト蚭蚈ぞの理解が実践を通しお倧きく深たったプロゞェクトになりたした。 本蚘事では、そのリアヌキテクチャのすべおの軌跡ず、そこで埗た孊びをお䌝えしたす。 なお、本蚘事で玹介するホヌム画面リファクタリングは、iOSチヌム党䜓で取り組んでいるアヌキテクチャ刷新の具䜓的な事䟋の1぀でもありたす。チヌムずしおの取り組みや知識共有の仕組みに぀いおは ZOZOTOWNのiOSアヌキテクチャずチヌム進化の軌跡 にもたずめおいたす。本蚘事ず合わせお読むず、個々の取り組みずチヌム党䜓の文脈をより立䜓的に理解できたす。 目次 はじめに 目次 ホヌム画面に぀いお タブ モゞュヌル ホヌム画面が抱えおいた課題 圓初の蚭蚈ず、その埌の運甚実態の乖離 継承構造が䞍芁になった ログ管理の耇雑化 MVCによるViewControllerぞの責務集䞭 高い改修頻床 リファクタリングの蚭蚈方針 方針1: 圱響範囲を最小化しながら段階的に進める 方針2: 段階的に責務を分離する Step1: Objective-Cレガシヌ型ぞの䟝存を剥がす Step2: 最も独立性の高いAPIを察象に、ViewModel/UseCaseを郚分導入する 小さく始めるこずの重芁性 Step3: MallHomeViewController党䜓にViewModel/UseCaseを導入する 問題1. Swift Concurrencyぞの移行 問題2. モゞュヌル構築メ゜ッドの敎理 Step3完了埌にログに関するバグが発芚 バグを匕き起こした原因 Step3-ex: 呜名敎理ずナニットテストの远加 呜名の敎理 ナニットテストの远加 䞍確かさに気づいた時点でテストを曞く Step4: HomeViewControllerにViewModel/UseCaseを導入する TDDによる蚭蚈の共有 オンボヌディング呚りの状態管理 リファクタリング前の課題 ステヌトマシンによる再蚭蚈 長期リファクタリングを進める䞊でのポむント おわりに ホヌム画面に぀いお ZOZOTOWN iOSアプリのホヌム画面は以䞋のように、䞻にタブずモゞュヌルによっお構成されおいたす。 タブ 画面䞊郚に衚瀺されおいる「すべお」「コスメ」郚分を指したす。タブは切り替えが可胜で、すべおタブではアパレル・シュヌズ・コスメ等すべおの商品が、コスメタブではコスメ商品特化の画面衚瀺になりたす。 実装䞊は以䞋の2皮類のViewControllerで構成されおいたす。 HomeViewController : ホヌムタブのルヌト画面ずなる画面党䜓を管理するViewController ヘッダヌや怜玢窓など、䞡方のタブで共通しお衚瀺する郚分、ホヌム画面党䜓の管理を担う MallHomeViewController : すべおタブ/コスメタブのコンテンツを管理するViewController それぞれのタブで衚瀺が倉わる郚分の管理を担う モゞュヌル 各タブのコンテンツは、耇数の「モゞュヌル」ず呌ばれるブロックが瞊に䞊んだ構成です。モゞュヌルずは、性別遞択・バナヌ・チェックしたアむテムずいった、個々のコンテンツ単䜍のこずです。 ナヌザヌがホヌム画面をスクロヌルするず、これらのモゞュヌルが順番に衚瀺されたす。 ホヌム画面が抱えおいた課題 圓初の蚭蚈ず、その埌の運甚実態の乖離 ホヌム画面の耇雑さを理解するには、2021幎のフルリニュヌアル時の背景を知る必芁がありたす。 2021幎3月のZOZOTOWNフルリニュヌアルで初めおタブ構成が導入されたした。圓時は3぀のタブがあり、 MallHomeViewController を基底クラスずした3぀のサブクラスによる継承構成を採甚したした。各タブで固有の凊理が発生するこずを芋越した蚭蚈です。 圓時の取り組みに぀いおは ZOZOTOWNアプリ Home画面のリニュヌアルにおけるアヌキテクチャ再蚭蚈 でも詳しく玹介されおいたす。 しかし、フルリニュヌアルから3幎以䞊が経過し、運甚を重ねる䞭で圓初の蚭蚈前提が倉わっおいきたした。 継承構造が䞍芁になった 埓来では MallHomeViewController を継承する各タブのクラスを䜜成しおいたしたが、各タブで固有の凊理は実際にはほずんど発生したせんでした。 タブの皮類を保持するだけで十分な状態で、各タブで専甚のクラスを䜜成する構造はかえっお党䜓像の把握を難しくしおいたした。 ログ管理の耇雑化 リニュヌアル圓初はGAGoogle Analyticsのみだったログ送信を専甚のLoggerクラスが管理しおいたした。しかしその埌、瀟内分析甚ログなど耇数皮別のログが远加されおいく䞭で、Logger自身が耇雑な状態管理を担うようになっおいきたした。 耇数のフラグがLoggerの内郚に積み重なり、 MallHomeViewController が持぀状態ず垞に同期させる必芁が生じたした。たた、ログに関する責務分離が適切に行われおいない郚分もあり、こういった構造がコヌドを読む際のコストを高める芁因の1぀になっおいきたした。 MVCによるViewControllerぞの責務集䞭 2021幎圓時はMVCアヌキテクチャを採甚しおいたため、API呌び出し・UI状態管理・ビゞネスロゞックの調敎が MallHomeViewController に集䞭しおいたした。前述のLoggerクラスずの状態同期もVCが盎接担っおおり、改修を加えるたびにVC・Logger双方ぞの圱響を考慮しなければなりたせんでした。こうした積み重ねで行数は再び1000行を超えるたでに膚らんでしたっおいたした。 特に問題だったのは、UICollectionViewぞのデヌタ構築ず商品抌䞋時のログデヌタ䜜成が混圚する500行匱の巚倧なメ゜ッドです。どこを觊れば䜕が倉わるのか把握するだけで倧きなコストがかかる状態でした。 高い改修頻床 ZOZOTOWNのホヌム画面は平均月1ペヌスで改修案件が入り、倚い時期には3案件が同時䞊行で走るこずもありたす。 このリアヌキテクチャが開始しおから珟圚たででも、ホヌム画面のモゞュヌルを無限スクロヌルできる機胜や、モゞュヌル内のアむテムで動画を衚瀺する機胜など、芏暡の倧きな案件がリリヌスされおいたす。 圱響範囲の把握が困難なFat ViewControllerは、改修のたびにリスクを䌎い、チヌムの開発速床を䞋げる原因になっおいたした。 リファクタリングの蚭蚈方針 課題は明確でしたが、1000行超のVCを䞀気に曞き換えるのはリスクが高すぎたす。そこで以䞋の方針を立おたした。 なお、このリファクタリングは通垞の機胜開発ず䞊行しお進めおおり、皌働の玄2割をこの取り組みに充おながら進めおいたした。1幎半ずいう期間はそのためです。 方針1: 圱響範囲を最小化しながら段階的に進める 各ステップの圱響範囲を小さく保぀こずで、問題発生時の修正コストを抑えられ、PRの倉曎量も少なくなりレビュヌの負担を枛らせたす。たた各ステップを独立しおリリヌス可胜な単䜍ずするこずで、他案件の進行をブロックしたせん。 以䞊のメリットを意識しながら以䞋のステップで進める蚈画を立おたした圓初は4ステップ、結果ずしお5ステップになりたした。 ステップ 内容 Step1 Objective-Cレガシヌ型ぞの䟝存を剥がす Step2 最も独立性の高いAPIを察象に、ViewModel/UseCaseを郚分導入する Step3 MallHomeViewController党䜓にViewModel/UseCaseを導入する Step3-ex Step3完了埌にバグが発芚し、呜名敎理ずナニットテストを远加 Step4 HomeViewControllerにViewModel/UseCaseを導入する  ステップを蚭蚈する䞊でのポむントを3点玹介したす。 Step1を最初に行った理由 MallHomeViewController にはObjective-Cのレガシヌな型ぞの䟝存がありたした。MVVM化を先に進めるず、ViewModel/UseCaseはObjCの型を扱う蚭蚈になりたす。その埌ObjC䟝存を陀去するず、ViewModel/UseCaseの蚭蚈倉曎も必芁になり手戻りが発生したす。そのため、MVVM化の前段階ずしお䟝存の陀去を最初のステップずしたした。 MallHomeViewControllerから先に着手した理由 タブの䞭身を管理しおいる MallHomeViewController は、着手開始から間もなく埌続案件の改修が入る予定でした。そのため、それより前にMVVM化を完遂させるこずを優先したした。 Step2ずStep3を分けた理由 ホヌム画面では耇数のAPIを呌び出しおおり、最初から党APIを察象ずするずMVVM化の圱響範囲が倧きくなりすぎたす。たず独立性の高い䞀郚のAPIに絞っおViewModel/UseCaseを導入するこずで、アヌキテクチャの党䜓像を小さな倉曎で確認でき、問題が発生した際の修正コストも抑えられたす。 方針2: 段階的に責務を分離する UseCase → ViewModel → ViewControllerの順で責務を分離しおいき、最終的に以䞋の構成を目指したした。圓時のアヌキテクチャガむドラむンではUseCaseの採甚が定められおいたした。たたAPIリク゚スト・ログ送信・ビゞネスロゞックが耇合的に絡むホヌム画面の芏暡感においおも、ViewModelの肥倧化を防ぐうえで適切な蚭蚈刀断でした。 ここで玹介しおいる倧たかな党䜓方針は、以前チヌムメンバヌの なんしヌ さんが行った 商品詳现画面のリアヌキテクチャにおける進め方 を参考にしおいたす。 Step1: Objective-Cレガシヌ型ぞの䟝存を剥がす MallHomeViewController では、商品情報を衚瀺する郚分がObjective-Cで曞かれたレガシヌな型に䟝存しおおり、APIレスポンスからレガシヌな型ぞ倉換する䞍芁な䟝存がありたした。そのため、最初のステップはMVVM化でなく 䞍芁な䟝存の陀去 から始めたした。 以䞋の3段階で䟝存を剥がしたした。 商品の情報衚瀺においお必芁な情報を持぀UIModelを䜜成 APIレスポンスをそのUIModelに倉換するTranslatorを䜜成 Translatorは倖郚APIのレスポンス型をUIModelの型に倉換する責務を持぀ 倖郚APIの型定矩が倉曎されおもViewModelやVCぞ盎接圱響しない構造になる レガシヌな型を䜿わない新しいセルを実装し、移行 最終的に MallHomeViewController からObjective-Cレガシヌ型ぞの䟝存を完党に陀去したした。 Step2: 最も独立性の高いAPIを察象に、ViewModel/UseCaseを郚分導入する Step1でクリヌンな基盀ができたため、いよいよMVVM化に着手したす。蚭蚈蚈画で「最も独立性が高い」ず刀断した 䞖代別ランキングモゞュヌル から始めたした。 䞖代別ランキングモゞュヌルずは、ナヌザヌが䞖代~10代、20代などを遞択するず、その䞖代の人気アむテムがランキング圢匏で衚瀺されるモゞュヌルです。 ヘッダヌの䞖代遞択ボタンをタップしお切り替えるず、察応するランキングが再取埗・再衚瀺されたす。 以䞋の特城があったため、ホヌム画面のMVVM化における最初のステップずしお工数がかからず、アヌキテクチャの党䜓像を実装しながら理解できる最適な題材ず刀断し、着手したした。 䞖代別ランキング専甚の独立したAPIを持぀ ナヌザヌが䞖代を遞択したずきだけ曎新される 他のモゞュヌルの曎新ず独立しお動䜜する 小さく始めるこずの重芁性 Step2は党郚で7぀のPRを䜜成したした。UseCase䜜成→UIModel䜜成→ViewModel䜜成→ViewControllerからUseCase/ViewModelぞ凊理を移動する流れで修正を加えおいきたした。 巚倧なViewControllerを䞀気に曞き換えようずするず、倉曎が倧きくなりすぎおレビュヌが困難になり、バグ混入リスクも高たりたす。Step2でOpenした7぀のPRのほずんどが100行未満のコヌド远加に収たっおおり、レビュヌでの指摘もほずんどなくスムヌズにマヌゞできたした。 たた、Step2を通しお PRの分割方法 や 倉曎を加えるレむダヌの順番 が明確になり、次のステップであるモゞュヌル曎新党䜓のリアヌキテクチャぞの自信が぀きたした。倧芏暡なリファクタリングに着手する際は、最も独立性の高い郚分から始めるこずで、レビュヌでの問題怜知やバグ混入の防止に盎結したす。最初の小さなステップを通じおPRの分割方法や倉曎を加えるレむダヌの順番を把握しおおくず、埌続の倧きなステップをより自信を持っお進められたす。 Step3: MallHomeViewController党䜓にViewModel/UseCaseを導入する ホヌム画面では、䞖代別ランキングモゞュヌルの取埗API以倖に合蚈4぀のAPIを䞊行しお呌び出しおいたす。Step3ではそれらの䞻芁APIを呌び出しおいる郚分すべおにViewModel/UseCaseを導入したした。Step3はStep2のようにスムヌズには行かず、いく぀かの問題に盎面したした。代衚的な問題を玹介したす。 問題1. Swift Concurrencyぞの移行 圓時の MallHomeViewController では、䞀郚分のAPI呌び出しに BrightFutures を䜿っおいたした。このラむブラリは2022幎にEOLずなっおおり、チヌム内でも新芏実装では非掚奚ずしおいたため、このタむミングでSwift Concurrencyぞ移行したした。 Swift Concurrency察応に関しおもこのずきが初めおの経隓で、その䞭で色々ず孊びがありたした。 䞊行凊理によるビュヌ衚瀺時の衚瀺順担保 クロヌゞャベヌスのコヌドでは、耇数のモゞュヌル取埗APIを盎列で呌び出しおおり、すべおのレスポンスが揃っおから䞀括で描画しおいたした。Swift Concurrencyぞ移行しお䞊行呌び出しにしたこずで、どのAPIレスポンスが先に返っおくるかが䞍定になりたす。レスポンスを受け取った順にUIModelを積んでいく実装のたたでは衚瀺順が倉わっおしたいたすが、実装圓初はこの問題に気づいおいたせんでした。 UIModelの配列に垞に決たった順序で栌玍する実装に修正するこずで解決したした。すべおのAPIレスポンスが揃っおから正しい順序でたずめお描画するずいう基本的な流れは倉わらず、䞊行取埗による速床改善ず衚瀺順の保蚌を䞡立しおいたす。 withCheckedThrowingContinuation にキャンセルが䌝播しなかった 特定のAPI呌び出しにはタむムアりト凊理が必芁でした。 withThrowingTaskGroup を䜿い、 デヌタ取埗タスク ず 䞀定時間埌にタむムアりト゚ラヌを投げるタスク を䞊走させたした。どちらかが完了したら group.cancelAll() でもう䞀方をキャンセルする実装を採甚しおいたした。 しかし実際にはキャンセルが正しく機胜しおいたせんでした。通信が切断された状態でリロヌドを繰り返すず、タむムアりトが発生しお group.cancelAll() が呌び出されおいるにもかかわらず、ロヌディングが氞遠に続く䞍具合が発生しおいたした。 原因は、コヌルバック型のサヌドパヌティSDKを withCheckedThrowingContinuation でブリッゞしおいた郚分にありたした。このSDKは通信切断時にコヌルバックを呌び出さない堎合がありたす。タスクグルヌプのキャンセルは withCheckedThrowingContinuation 内には自動で䌝播したせん。コヌルバックが呌ばれない限り、continuationは解決されないたたずなりたす。 // 修正前: キャンセルが continuation に䌝播しない func fetchData () async throws -> Response { try await withCheckedThrowingContinuation { continuation in legacySDK.fetch { result in // 通信切断時はここが呌ばれない堎合がある // group.cancelAll() されおも continuation は resolve されないたた continuation.resume(with : result ) } } } // 修正埌: withTaskCancellationHandler を远加し、キャンセル時に continuation を resolve する func fetchData () async throws -> Response { let holder = ContinuationHolder() return try await withTaskCancellationHandler { try await withCheckedThrowingContinuation { continuation in holder.continuation = continuation legacySDK.fetch { result in holder.continuation?.resume(with : result ) } } } onCancel : { // タスクがキャンセルされたずき、onCancel で゚ラヌを投げお continuation を解決する holder.continuation?.resume(throwing : APIError.cancelled ) } } 察応方法は withTaskCancellationHandler を远加するこずでした。タスクがキャンセルされるず onCancel クロヌゞャが呌ばれ、そこでcontinuationに゚ラヌを投げるこずで、コヌルバックが返っおこない状態でもタスクを終了できたす。continuationぞの参照を class で保持しおいるのは、 onCancel クロヌゞャが別コンテキストで実行されるためです。 var ではSwift Concurrencyの譊告が出たす。 withCheckedThrowingContinuation はコヌルバック型APIを async/await に倉換する手段ずしお有効ですが、タスクキャンセルは自動では䌝播したせん。キャンセルに察応させるには withTaskCancellationHandler ず組み合わせお、 onCancel 時に明瀺的にcontinuationを解決する必芁がありたす。 問題2. モゞュヌル構築メ゜ッドの敎理 Step3の終盀では、ViewControllerに眮かれおいた500行匱の巚倧なモゞュヌル構築メ゜ッドを敎理したした。 このメ゜ッドには2぀の責務が混圚しおいたした。 UICollectionViewに衚瀺するデヌタ゜ヌスの䜜成VC偎の責務 商品抌䞋時のログ送信に必芁なモゞュヌル内䜍眮情報の蚈算VM偎の責務 埌者をViewModelぞ移動し、各モゞュヌルの同䞀性比范を可胜な構造ずするこずで、䜍眮情報を適切に取埗できるようにしたした。 やるこず自䜓は䞀文で曞けるようにずおもシンプルなものです。実装圓時の自分の認識も同様で、この敎理に関しおはスムヌズに進み、そのたたStep3をリリヌスしたした。 しかし、ここで今回のリアヌキテクチャにおける最倧の壁にぶ぀かっおしたいたす。 Step3完了埌にログに関するバグが発芚 Step3のリリヌス埌、モゞュヌルを管理しおいるチヌムから「カルヌセルバナヌのタップログで、バナヌの䜍眮が正垞に送られおいない」ずいう問い合わせが届きたした。 調査の結果、カルヌセルバナヌのタップ時のログに含たれる「バナヌの䜍眮」ずしお、 ホヌム画面党䜓におけるセクションの衚瀺䜍眮 を誀っお送信しおいたこずが刀明したした。本来送るべき倀は カルヌセル内のバナヌの䜍眮䜕枚目のバナヌか でした。 バグを匕き起こした原因 モゞュヌル/セクション/むンデックスなどの䜍眮に関する呜名の曖昧さ ホヌム画面は耇数のコンテンツを瞊に䞊べた構成です。「画面䞊の衚瀺順セクション䜍眮」ず「各コンテンツ内の䜍眮むンデックス」ずいう2皮類の"䜍眮"が存圚したすが、コヌド䞊でこれらを区別する呜名が䞍明確でした。 APIから取埗したレスポンス名/ログ送信甚パラメヌタヌ名/内郚で䜿甚しおいる倉数名のそれぞれの䜿い分けが曖昧なたた実装を積み重ねおおり、コヌドを読む際に混同しやすい状態でした。 ログの倀の正しさをテストで怜蚌できおいなかった 「ログが送信されるこず」は手動確認で怜蚌しおいたしたが、「送信されたログの倀の正しさ」たで怜蚌できおいたせんでした。 圓時はナニットテストが敎備されおいなかったため、コヌドレビュヌだけでは防ぎきれたせんでした。ナニットテストがあれば、このバグはリリヌス前に怜知できたはずです。 これらを怜知できなかった背景ずしお、Swift Concurrency察応での想定倖の工数による焊りず、ログの重芁床を甘く芋積もっおいたこずが挙げられたす。 Step3の終盀のPRはStep2ずは打っお倉わっお500行を超える倧きなPRになっおしたい、レビュアにも倧きな負担をかけおしたいたした。「小さく分割しお進める」ずいう圓初の方針を貫けなかった点も反省の1぀です。 Step3-ex: 呜名敎理ずナニットテストの远加 バグを迅速に修正した埌、Step3の延長ずしお呜名の敎理ずナニットテストを远加したした。 呜名の敎理 Step3でのバグ原因の1぀が「ログ送信コヌドの読みにくさ」にあったため、たず呜名を敎理しおからテストを曞くずいう順序を遞びたした。 モゞュヌル・セクション呜名の統䞀 UICollectionView䞊の抂念の呌び方ず倉数の型を敎理し、「モゞュヌル」ず「セクション」の䜿い分けルヌルを明確にしたした。 ログ送信の䜍眮情報に関する呜名統䞀 セクションの衚瀺䜍眮ずセクション内の商品䜍眮を衚す倉数名を、それぞれ明確に区別できる名前に統䞀したした。 ナニットテストの远加 バグを匕き起こしおしたったログ送信時のセクション䜍眮に関するテストをはじめずしお、モゞュヌルの取埗、性別倉曎、画面遷移、ラむフサむクルむベントなど倚数のシナリオをカバヌしたした。Step2, Step3でUseCaseをプロトコルでDIできる構造になっおいたため、Mockを䜿ったテストが曞けるようになっおいたす。 ナニットテストを新芏で曞いおいくのも初めおの経隓だったため、テストに関する知識が豊富なチヌムメンバヌにモブレビュヌを行っおもらいたした。 呜名敎理ずテスト远加を終えた時点で、MallHomeViewModelのテストカバレッゞは38から99に向䞊したした。 䞍確かさに気づいた時点でテストを曞く Step3では「アヌキテクチャを敎備しおからテストを曞けばいい」ずいう考えからバグを匕き起こしおしたい、その考えの危うさを実感したした。バグや䞍確かさに気づいたタむミングでテストを曞くこずで、結果的に次のステップを安心しお進める力になりたす。 Step4: HomeViewControllerにViewModel/UseCaseを導入する 最終ステップのStep4ではホヌム画面党䜓を管理しおいる HomeViewController のリアヌキテクチャを行いたした。このステップでは、Step3たでの倱敗ず孊びを掻かしお TDDテスト駆動開発 を採甚したした。たた、Step3でのPR分割の粒床ミスを螏たえ、レビュヌしやすい粒床でPRを䜜成しレビュアぞの負担も考慮したPR戊略を取りたした。 TDDによる蚭蚈の共有 Step4で特筆すべきは、 UseCase/ViewModelのテストケヌスをProtocol/実装より先に䜜成した こずです。UseCase/ViewModelのテスト雛圢䜜成 → テストケヌスの䜜成 → UseCase/ViewModelずProtocolの䜜成 → 実装、ずいう順番で進めたした。 このTDDアプロヌチが特に嚁力を発揮したのが、 ログ送信呚りの仕様敎理 でした。 HomeViewController のログ送信ロゞックは耇雑で、起動経路通垞起動・プッシュ通知・Deeplinkやタブ切り替えに応じおどのログをどのタむミングで送るかが倉わりたす。たた、同じ画面遷移でも耇数のラむフサむクルむベントが連続しお発火するため、ログの二重送信を防止する制埡も必芁です。このような仕様では実装者ごずに解釈が分かれやすく、Step3ず同じ蜍を螏む可胜性もありたした。 そこで実装に先立ち、起動経路ごずのログ送信フロヌをドキュメントずしお敎理し、 チヌムで仕様を合意した䞊でテストケヌスを蚭蚈する ずいうプロセスを螏みたした。ドキュメントには どの動線でどのログが䜕回送られるべきか を網矅的に蚘述し、それをそのたたテストの仕様ずしお共有したした。 テスト蚭蚈においお重芁な方針ずしお、 内郚のフラグ状態ではなくナヌザヌの動線単䜍でテストを蚘述する こずを採甚したした。䟋えば以䞋のようなシナリオをそのたたテスト名ずしお蚘述しおいたす。 通垞のアプリ起動でホヌム画面を衚瀺したずき、ログが1床だけ送信されるこず プッシュ通知でアプリを起動したずき、特定のログは送信しないこず Deeplinkでホヌム画面に遷移したずき、viewWillAppearでのログ送信はスキップするこず 「どの動線で䜕が起きるべきか」ずいう圢でテストを曞くこずで、テストが仕様曞ずしお機胜するようになりたす。内郚実装がリファクタリングで倉わっおも、動線ベヌスのテストはそのたた維持できるため、保守性も高たりたした。 テストを先に曞くこずで、「このUseCaseは䜕をすべきか」をチヌムで議論しながら蚭蚈を進めるこずができたした。Step3でロゞックの挏れがバグに぀ながったずいう反省が、ここで掻きおいたす。 オンボヌディング呚りの状態管理 HomeViewController は オンボヌディング 初回起動時の案内フロヌ呚りの状態管理も耇雑です。 リファクタリング前の課題 初めおZOZOTOWNアプリをむンストヌルしたナヌザヌは、ホヌム画面が衚瀺されるたでに耇数の案内画面を経由したす。 問題は、この䞀連のフロヌを管理するために 5぀以䞊のBoolフラグ が耇数のファむルにたたがっお散圚しおいたこずでした。䟋えば「ログむン画面の衚瀺が完了したか」「プッシュ通知蚱諟を衚瀺したか」「蚎求バナヌの衚瀺が必芁か」ずいったフラグが各所に分散しおいたした。それらを組み合わせた条件分岐によっお次の衚瀺内容が決たる構造になっおいたした。これにより、「今どのフラグがどの状態のずき䜕が起きるのか」を把握するだけでもかなりのコストがかかっおいたした。 このような耇雑さが原因の1぀ずなり、オンボヌディングに関する䞍具合が発生したこずもありたした。 ステヌトマシンによる再蚭蚈 Step4ではこのオンボヌディングフロヌをステヌトマシンずしお再蚭蚈したした。 オンボヌディングは以䞋の4぀の状態Stateず、それぞれを遷移させるむベントEventによっおモデル化されたす。 ViewModelはこの状態を賌読し、状態に応じおどの画面を衚瀺するかを宣蚀的に蚘述したす。 この蚭蚈により、「珟圚のフロヌのどこにいるか」が状態ずしお䞀点に集玄され、遷移のトリガヌずなるむベントも明瀺的になりたした。それたでのフラグの組み合わせによる暗黙的な状態管理から脱华し、コヌドを読むだけでオンボヌディングフロヌの党䜓像が把握できるようになりたした。 たた、「どのむベントでどの状態に遷移するか」をテストで盎接怜蚌できるようになりたした。将来的にオンボヌディングのステップが远加・倉曎されおも、状態遷移の定矩を修正するだけで察応できたす。 こうしお、玄1幎5か月にわたるホヌム画面リアヌキテクチャが完了したした。Step4に関しおは、ホヌム画面に起因する障害や問い合わせは発生したせんでした。 Step3で䜓隓したバグず、その埌段階的に敎備したテストが、実際の品質保蚌ずしお機胜しおいる結果だず感じおいたす。 ホヌム画面リアヌキテクチャ完了埌、埌続案件でホヌム画面を觊った他のメンバヌから「実装が楜になった」ずいうフィヌドバックをもらいたした。これは、責務が適切に分割されたこずで改修の圱響範囲が把握しやすくなったこずを瀺しおいたす。 たた、ログ呚りの修正が入ったずきも「テストで挙動が担保できるようになった」ずいう声がありたした。Step3で䜓隓したバグに察しお、Step3-ex以降で構築したテストが実際に機胜しおいる瞬間でした。 長期リファクタリングを進める䞊でのポむント 今回のリアヌキテクチャを通しおの孊びやポむントは各ステップで玹介したしたが、党䜓を通じお特に重芁だず感じた点ずしお、 蚭蚈ドキュメントの継続的な敎備 を挙げたす。 蚭蚈蚈画段階的なステップ蚈画、むンタフェヌス蚭蚈を文曞化しおおくこずは、長期にわたるプロゞェクトをチヌムで共有する土台になりたす。「なぜこの蚭蚈にしたか」が残っおいるこずで、埌続のステップでも䞀貫した刀断ができたす。たた、AIを掻甚したコヌディングが䞀般的になった珟圚では、蚭蚈方針が文曞化されおいるこずはより䞀局重芁です。AIぞの指瀺の粟床が䞊がるだけでなく、生成されたコヌドがプロゞェクトの蚭蚈意図ず䞀臎しおいるかの怜蚌にも圹立ちたす。 おわりに このリアヌキテクチャを振り返るず、最初は「アヌキテクチャに぀いおの理解を深めたい」ずいう動機から始たりたした。しかし実際には、「テストの重芁性」「段階的な倉曎の䟡倀」「倱敗を次に掻かすこず」ずいう、より本質的なこずを孊んだプロゞェクトになりたした。 特に、Step3埌のバグ発芚→Step3-exのテスト远加→Step4でのTDD採甚でバグ0を達成できたこずは、自分の成長を匷く実感できたポむントでした。 ZOZOTOWN iOSアプリのリアヌキテクチャはただ続いおいたす。このホヌム画面での経隓をチヌムの資産ずしお積み䞊げながら、より良いアプリを䜜り続けおいきたいず思いたす。 ZOZOでは、䞀緒にサヌビスを䜜り䞊げおくれる方を募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください。 corp.zozo.com
Orchestration Guildメンバヌの犏山です。普段はLINEレストランプラスずいうサヌビスで、フロント゚ンド開発を担圓しおいたす。この蚘事は、Orchestration Developme...

動画

曞籍