해당 글은 이전 글로부터 이어집니다.
레거시를 어떻게 안전하게 변경할 수 있을까?
Router를 제거하기로 결정한 뒤, 안전하게 레거시를 변경하기 위해서 다음의 두 가지를 염두에 두고 고민해보았습니다.
1. 기존 기능의 손상 없이 변경을 진행합니다.
2. 새로운 기능 개발에 영향이 없어야 합니다.
이를 위해서, 우선 변경하고자 하는 코드의 영향이 어디까지 미쳐지지는지에 대한 이해가 필요했습니다.
따라서, 위와 같이 화면 전환 흐름 중 가장 간단한 스플래시 화면(SplashFlow)의 Coordinating에 관여하는 객체들을 다이어그램으로 나타내보았습니다. 도식화해보니 Router를 제거했을 때 영향이 가는 클래스들을 한눈에 확인할 수 있었고, Router가 제거됨에 따라서 함께 제거할 수 있는 추상화 계층들 또한 수월하게 이해할 수 있었습니다.
또한, 리팩토링을 시작하기 전, 원활한 진행에 혼란을 줄 수 있는 미사용 코드들을 먼저 제거하기로 했습니다. 위 노션 캡처본은 함께 논의한 챕터원 분께서 정리해주신 문서인데요, 기존의 Router에는 사용하지 않는데 미리 정의된 수많은 유틸들이 있어, 해당 함수들이 현재 프로젝트에서 어떻게 활용되고 있는지, 앞으로도 사용 가능성이 있는지 등을 면밀히 따져보는 시간을 가졌습니다.
기존 코드와 병렬로 작업하기: Strangler Fig pattern
레거시를 안전하게 제거하기 위한 기법에는 여러가지가 있지만, 가장 직관적인 방법은 새로 파일을 생성해 기존 파일을 대체하는 것이라고 생각했습니다. 특히 해당 리팩토링에서 Router와 여러 객체들이 강한 결합도를 가지고 있고, 이를 떼어내어 최종적으로 Router를 제거하기 위해서는, 완전히 새로운 파일로 대체하는 것이 좋다고 판단했습니다.
해당 방법을 정식적으로 Strangler Fig Pattern이라고 부른다는 것을 알게 되었습니다. 참고
📍 Strangler Fig Pattern의 이름은
기존 나무를 지지대 삼아 새로운 무화과가 자라며, 최종적으로 기존 나무가 새 무화과로 대체되는 것에서 영감을 받았다 합니다.
병렬 작업을 하기로 정한 뒤, Legacy와 Refactor 코드의 명칭 구분에 대해 두 가지 접근 방식을 고민했습니다.
- Legacy 파일에 구분자 추가 (e.g., LegacySplashCoordiantor)
- Refactor 파일에 구분자 추가 (e.g., SplashCoordinator_Refactor)
이에 대해 논의한 결과, 1번 방식을 선택했습니다. 그 이유는 이후 최종적으로 레거시 코드를 제거해야 할 때, 한 번에 식별하고 제거할 수 있으며 (Refactor는 리팩토링 파일에서 구분자를 제거하는 작업이 추가적으로 필요합니다.) 작업 범위 또한 명확하게 파악할 수 있기 때문입니다.
작업하는 순서를 정리하면 다음과 같습니다.
- 기존 코드(파일) 명칭의 prefix에 Legacy 키워드를 붙입니다. (e.g., SplashCoordinator -> LegacySplashCoordinator)
- 새로운 파일을 생성하고, 기존 코드와 병렬로 구현합니다.
- 점진적으로 기존 코드를 새로운 코드로 변경합니다.
- 최종적으로 Legacy 코드들을 제거합니다.
Flag 활용하기
다시 처음에 고민했던 두 가지 조건을 떠올려보았습니다.
1. 기존 기능의 손상 없이 변경을 진행합니다.
2. 새로운 기능 개발에 영향이 없어야 합니다.
1번을 위해, 병렬 구현을 통해 파일을 분리해서 리팩을 진행하기로 결정했습니다. 그렇다면 리팩을 진행하면서 새로운 기능을 개발해야 할 경우에는 이를 어떻게 스위치 껐다 키듯, 레거시와 리팩 코드를 바꿔가며 테스트 할 수 있을까요?
이와 관련해서, 이전 글에서도 참고했던 29cm의 글에서 발췌했습니다.
물론 위 글에서는 원격으로 레거시와 리팩 코드를 오갈 수 있는 Remote Flag로 소개되었지만, 분기 로직을 두고 스킴에 따라 이를 바꿀 수 있도록 구현해야겠다는 아이디어를 얻을 수 있었습니다.
그래서 간단하게 CoordinatorFlag라는 이름의 enum을 두고, 이를 활용해 배포용 스킴으로 빌드하면 레거시 화면 전환 코드로, 디버그용 스킴에서는 리팩토링 중인 코드로 실행될 수 있도록 구현했습니다. 이렇게 분기점을 활용해 레거시와 리팩 코드를 분리하니, 리팩 중에도 기능을 안전하게 배포할 수 있었습니다.
위와 같이 활용할 수 있습니다.
+ Cursor와 함께 작업 속도 높이기
방법이 결정되고 나니, Router를 제거하는 일은 단순 반복 작업이라 시간을 단축하고 싶었습니다. 최근 Cursor를 처음 사용하게 되면서, 기술의 발전이 참 놀랍다는 생각을 많이 했는데 이전에 입력한 리팩토링 방향성을 이해하고, 이후 명령하는 작업들을 아주 빠르고 편리하게 해주어서 신기했습니다.
결론
이렇게 부족함 많지만, Router를 안전하게 제거하고자 챕터원분들과 고민했던 내용들을 기록해보았습니다.
이외에도, 아직 챕터원 분들과 논의만 했던 이후 리팩토링의 방향성에 관해서는 Router를 모두 제거하고 적어보려 합니다!
이후에 프로젝트에 합류할 챕터원 분들께서 화면 전환으로 같은 어려움을 겪지 않았으면 좋겠습니다.
읽어주셔서 감사합니다. :D
(해당 내용에 대한 PR입니다.)
https://github.com/sopt-makers/SOPT-iOS/pull/559
[Refactor] #516 - SplashCoor에서의 Router 의존성 제거 by dlwogus0128 · Pull Request #559 · sopt-makers/SOPT-iOS
🌴 PR 요약 SplashCoor에서의 Router를 제거했습니다. Router를 제거함에 따라, 불필요해진 추상화 관련 코드를 Legacy로 변경하고 (prefix에 Legacy를 붙이고) 새 파일을 생성했습니다. 🌱 작업한 브랜치 [Re
github.com
https://github.com/sopt-makers/SOPT-iOS/pull/562
[Refactor] #560 - TabBarCoor, HomeCoor, SoptlogCoor에서의 Router 의존성 제거 by dlwogus0128 · Pull Request #562 · sopt
🌴 PR 요약 TabBarCoor, HomeCoor, SoptlogCoor에서의 Router를 제거했습니다. TabBarCoor만 작게 작업하려고 했으나... 탭바와 강한 의존성을 갖고 있는 자식 탭 Coor들도 한 번에 제거하다보니 PR이 너무 비대해
github.com
'Project' 카테고리의 다른 글
[iOS/Tuist 4] Firebase Crashlytics 설정 (1) | 2025.06.15 |
---|---|
[SOPT makers] Coordinator, Router 리팩토링 (1) 왜 화면이 움직이지 않나요? (2) | 2025.05.15 |
[SOPT makers] 솝탬프 이슈 해결하기 (2) 메모리 최적화하기 (0) | 2025.03.08 |
[SOPT makers] 솝탬프 이슈 해결하기 (1) 왜 크래시가 발생하나요? (0) | 2025.03.08 |
[SOPT makers] 35기 회고 (0) | 2025.02.24 |