😬
[Swift]同じスクリーンを複数人で分担して作業するときのTipsと感じた課題
概要
FatVCを3人で分担して、リファクタリングしたときに工夫したことや、課題に感じたことを共有する。
- 工夫したこと
- 下準備に時間を掛けることで、コンフリクト予防や一貫性の向上ができた。
- 共通部分は分担作業が始まる前に完了させる。(コンフリクト予防)
- プロトコルやコメントで、実装の方向性を伝える。(実装の一貫性)
- 必要になりそうなファイルは先に用意しておく。(コンフリクト予防、命名の一貫性)
- 下準備に時間を掛けることで、コンフリクト予防や一貫性の向上ができた。
- 課題に感じたこと
- 下準備に多くの時間がかかってしまった。
- それでも、コンフリクトは発生する。(e.g. xcodeprojファイル)
背景
リファクタリング前の実装
担当しているプロジェクトでは、以下のように、3つのサブスクリーンから構成されるスクリーンが存在する。
実装としては、3つのサブスクリーンのコードが1つのViewController(以下VC)と1つのViewModel(以下VM)に実装されており(いわゆるFatVC, FatVM)、新機能の追加やバグ修正のしづらいスクリーンだった。
スクリーンのイメージ
以下は、FatVCの実装のイメージ
enum Tab {
case screen1
case screen2
case screen3
}
class FatVC: UIViewController {
//選択中のスクリーン
var selectedTab: Tab = .screen1
//各スクリーンのデータ
var dataForScreen1: [Data1] = []
var dataForScreen2: [Data2] = []
var dataForScreen3: [Data3] = []
// 1つのCollectionViewを、Screen1~3で使い回す
var collectionView: UICollectionView
//選ばれているTabに応じて、表示するCellを切り変える
func collectionView(cellForItemAt: IndexPath) {
switch selectedTab {
case .screen1:
if let cellForScreen1 = cell as? Screen1Cell {
cellForScreen1.setup(for: dataForScreen1[indexPath.row]
return cellForScreen1
}
case .screen2:
~~~ 似た実装
case .screen3:
~~~ 似た実装
}
}
以下、この様な、`switch selectedTab`の分岐がたくさんでてくる
~~~
}
今回のリファクタリング
今回のリファクタリングにおいて、1つのVCにまとめられていた3スクリーンの実装を分割する作業を行った。
MVVMのアーキテクチャを採用しており、今回の変更は、以下のイメージ
リファクタリング後の実装
WrapperVC
が、3つのスクリーンのインスタンスをもつ。
WrapperVCの実装のイメージ
WrapperVC.swift
protocol ChildScreenVCProtocol {
func loadData()
func tabSelected()
~~~
}
class WrapperVC: UIViewController {
var selectedTab: Tab
// 3つのVCを`ChildVC`として持つ
var screen1VC = Screen1VC()
var screen2VC = Screen2VC()
var screen3VC = Screen3VC()
// 子Screenの関数は,主にWrapperVCから呼び出す
var selectedVC: ChildScreenVCProtocol {
switch selectedTab {
case .screen1: screen1VC
case .screen2: screen1VC
case .screen3: screen1VC
}
}
func loadData() {
selectedVC.loadData()
}
func tabSelected(newTab: Tab) {
self.selectedTab = newTab
selectedVC.tabSelected()
}
~~~
}
各ScreenVC
が、それぞれUICollectionView
を持ち、ChildScreenVCProtocol
を実装する。
各ChildScreenVCの実装のイメージ
Screen1VC.swift
class Screen1VC: ChildScreenVCProtocol {
var viewModel = ChildScreen1ViewModel()
var collectionView: UICollectionView
~~~
}
class Screen1ViewModel {
var data: [Data1] = []
func fetchData() -> Result<[Data1], Error> {
}
}
リファクタリングの進め方
3人で手分けをし、それぞれが1スクリーンを担当し、最後にマージするという戦略で、リファクタリングに取り組んだ。
手順は以下の通りで、5ステップで作業を行った。
作業を複数人で分業することの良い点と難しい点
分担の良い点
- 並列で作業できるため短い時間で完了できる
分担の難しい点
- マージのときに、コンフリクトしやすい
- 特に、
xcodeproj
のファイルがコンフリクトしやすく、修正も難しいことが多い。
- 特に、
- スクリーン間で実装の一貫性が弱くなる(違う人がそれぞれのスクリーンを実装すため)
工夫したこと
下準備
特に、下準備には力を入れ、以下の点を工夫した。
一貫性に関しては、してほしいことと(e.g. Protocol)、してほしくないこと(e.g. deprecated)を伝える。
- 事前にファイル構成を決め、先にテンプレートのファイルを用意しておく。(コンフリクト予防、命名の一貫性)
- 必要となるVCとVMのファイルを用意しておく。
- UnitTestのファイルも、それぞれのVC、VMのものを作成しておく。(中身はまだEmpty)
- 共通のコードやプロトコルを準備する (コンフリクト予防、一貫性)
- 上の例だと、ChildScreenVCProtocolとWrapperVCの実装は完了させておく。
- 自分の担当のスクリーンは、下準備の段階である程度完了させておくと、ProtocolやWrapperVCに何が必要なのかが知れる。
- 実装期間中に、他の人がWrapperVCやProtocolを変更しなくても、実装できるようにする。
- 上の例だと、ChildScreenVCProtocolとWrapperVCの実装は完了させておく。
- リファクタリング後の実装で 使わないでほしいコード に、印をつけておく。(実装の一貫性)
-
@available(*, deprecated, message:)
を使用する。 - コード上にコメントを残す。
- ベースとなるPRを作っておいて、そのにコメントしておく。
-
実装期間
- 各々が早めにWIPのPRを作成し、軽く目を通す。実装方法が異なる部分は、方向性を擦り合わせる。
- ハドルなどで、遭遇したエッジケースや難しかった点を共有し、同じ問題に他の人が悩まないようにする、また解決方法をそろえる。
コードレビュー
スクリーン間での一貫性や、挙動の違いに注意する。
反省点
うまくいったこと
- 下準備に時間を費やした効果で、
- コンフリクトは最低限のものしか発生せず、しかも修正も簡単なものが多かった。
- 実装や挙動の一貫性を実現できた。
課題に感じた点
- 下準備に多くの時間が必要になった。
- トレードオフ: 下準備に時間を掛けるVSマージ時するときに時間を掛けるか
- それでも、コンフリクトは発生する。(e.g. xcodeprojファイル)
まとめ
複数人で分担して作業した経験から、工夫した点と課題に感じた点を述べた。
特に、以下の点を実行したことで、コンフリクトや一貫性の問題を軽減できた。
- 事前にファイル構成を決め、先にテンプレートのファイルを用意しておく。(コンフリクト予防、一貫性)
- プロトコルを作る(コンフリクト予防、一貫性)
- リファクタリング後の実装では使わないでほしいコードに、印をつけておく。(一貫性)
- 早めにWIPのPRを作成し、それぞれが軽く目を通す。実装方法が異なる部分は、方向性を擦り合わせる。
こういう場合にほかの人がどういう戦略をとっているのか気になる🤔(コメントなどで教えてもらえると、うれしいです)
Discussion