머플러 리팩토링 - 1. 리팩토링 계획 세우기 (레거시 코드 정리)
머플러, 초심으로 돌아가는 설 연휴 프로젝트머플러 앱, 잊혔지만 잊혀지지 않은 첫 프로젝트 약 8개월 전, iOS 앱스토어에 출시한 머플러는 UMC 동아리에서 진행했던 첫 번째 iOS 프로젝트였습니
qkrrmsdud.tistory.com
머플러 프로젝트의 리팩토링을 진행하면서 가장 먼저 손을 본 것은 아키텍처의 변화였다. 기존 코드베이스는 MVC(Model-View-Controller) 패턴을 따르고 있었지만, 여러 가지 한계를 보였다. 특히 ViewController가 지나치게 비대해지고, 비즈니스 로직과 UI 코드가 섞여 있어 유지보수성과 테스트 용이성이 떨어졌다. 이에 따라 MVVM(Model-View-ViewModel) 패턴으로 전환하여 비즈니스 로직과 UI 로직을 분리하는 작업을 진행했다.
이번 글에서는 MVC에서 MVVM으로의 전환 과정과 SwiftLint 적용을 통해 코드 스타일을 정리한 과정을 기록해보려고 한다.
🔥 기존 코드의 문제점: MVC의 한계
기존 MVC 패턴에서는 ViewController가 너무 많은 역할을 담당하고 있었다.
특히 다음과 같은 문제점들이 두드러졌다.
1. ViewController의 비대화
ViewController는 본래 UI를 담당하는 역할이어야 하지만, 네트워크 요청과 데이터 가공까지 직접 수행하고 있었다. 이로 인해 파일이 길어지고 복잡도가 증가하면서 코드 가독성이 떨어지는 문제가 발생했다.
2. 테스트하기 어려운 구조
ViewController 내부에서 직접 API를 호출하고 데이터 변환을 수행하다 보니, 단위 테스트(Unit Test)를 적용하기 어려웠다. UI 요소와 데이터 로직이 결합되어 있어, ViewController 없이 테스트를 수행하는 것이 거의 불가능한 상황이었다.
3. 코드 재사용성 부족
여러 ViewController에서 비슷한 데이터 변환 및 네트워크 요청을 중복으로 수행하고 있었다. 이를 해결하기 위해 ViewModel을 도입하고, 비즈니스 로직을 별도 계층으로 분리하는 것이 필요했다.
4. 코드 스타일 일관성 부족 (SwiftLint 적용 필요)
SwiftLint를 적용하지 않았기 때문에, 네이밍 컨벤션이 일관되지 않고, 파일별 코드 스타일이 제각각이었다. 일부 파일은 너무 크고, Cyclomatic Complexity(순환 복잡도)가 높은 코드도 존재했다. 이를 해결하기 위해 SwiftLint를 적용하고, 자동 수정(swiftlint autocorrect)을 수행할 필요가 있었다.
✅ MVVM으로의 전환 과정
1️⃣ ViewModel을 생성하여 UI 로직과 데이터 로직 분리
가장 먼저, 기존 ViewController에서 직접 데이터를 가공하고 API를 호출하는 방식을 개선하기 위해 ViewModel을 도입했다. ViewModel은 데이터의 흐름을 관리하고, ViewController와의 바인딩을 담당하도록 설계했다.
리팩토링 전 (MVC 패턴의 ViewController)
기존 코드에서는 ViewController가 직접 API 호출을 수행하고 데이터를 저장하고 있었다.
class ConsumeViewController: UIViewController {
private var items: [Item] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchItems()
}
private func fetchItems() {
APIService.shared.getItems { result in
switch result {
case .success(let data):
self.items = data
self.tableView.reloadData()
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
이 코드의 문제점은 ViewController가 API 호출과 UI 업데이트를 동시에 담당하고 있어, 역할이 모호해진다는 점이다. 이를 해결하기 위해 MVVM을 적용해 ViewModel을 생성했다.
리팩토링 후 (MVVM 패턴 적용)
ViewModel을 생성하고 데이터를 처리하는 역할을 ViewModel로 이동시켰다.
ViewModel은 @Published 속성을 활용하여 ViewController와 데이터 바인딩을 수행하도록 했다.
import Combine
class ConsumeViewModel {
@Published var items: [Item] = []
private var cancellables = Set<AnyCancellable>()
func fetchItems() {
APIService.shared.getItems()
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in }, receiveValue: { [weak self] data in
self?.items = data
})
.store(in: &cancellables)
}
}
이제 ViewModel이 데이터 로직을 담당하고, ViewController는 UI 업데이트만 수행하게 된다.
🛠️ SwiftLint 적용 및 코드 스타일 정리
MVVM 패턴을 적용한 후, SwiftLint를 통해 코드 스타일을 일관되게 유지하는 작업도 수행했다.
1️⃣ SwiftLint 적용 후 주요 수정 사항
✅ 타입 이름 대문자로 시작 (type_name)
변수명과 타입명이 네이밍 컨벤션을 따르도록 수정했다.
struct monthlyRepeatType {} // ❌ 잘못된 예시
struct MonthlyRepeatType {} // ✅ 올바른 예시
✅ 변수명 소문자로 시작 (identifier_name)
var UserName: String // ❌ 잘못된 예시
var userName: String // ✅ 올바른 예시
✅ .count == 0 대신 .isEmpty 사용 (empty_count)
if array.count == 0 { } // ❌ 잘못된 예시
if array.isEmpty { } // ✅ 올바른 예시
2️⃣ SwiftLint 설정 파일 추가 (.swiftlint.yml)
SwiftLint 적용 후 일부 코드에서 발생하는 경고를 완화하기 위해 .swiftlint.yml 파일을 추가했다. 경고로 인해 빌드가 되지 않아, 추후에 아래의 룰을 다시 적용하여 모두 수정할 예정이다.
disabled_rules:
- file_length
- cyclomatic_complexity
- identifier_name
- type_name
🎯 리팩토링 후 기대 효과
🔹 ViewController가 가벼워지고 유지보수가 쉬워짐
🔹 비즈니스 로직과 UI 로직이 분리되어 가독성 향상
🔹 Combine을 활용한 데이터 바인딩으로 코드 간결화
🔹 SwiftLint 적용으로 코드 스타일이 통일됨
🔹 테스트 가능한 구조로 개선되어 품질 향상 가능
🚀 다음 목표
MVVM으로의 전환이 완료되면, 이제 UseCase 적용을 통해 코드의 모듈성을 더욱 강화할 예정이다. 또한, 단위 테스트를 추가하여 코드 품질을 높이는 작업도 진행할 계획이다.
'iOS > iOS Developer Documentation' 카테고리의 다른 글
iOS Developr Documentation : UIViewController (UIKit) (3) | 2024.10.06 |
---|