iOS 개발 공부
2025년 1분기 회고 본문
드디어 리팩토링을 시작하다
지난 분기에 다짐했던 대로 이번 분기는 앱 전체 리팩토링에 본격적으로 뛰어들었다. 세 개의 앱이 대부분 비슷하면서도 조금씩 다르다 보니, 자연스럽게 공통되는 부분을 모듈화하는 방향을 먼저 떠올렸다. 그리고 스토리보드, UIKit, SwiftUI가 뒤섞여 있는 것도 하나로 통합하고 싶었다.
스토리보드는 미련 없이 걷어낼 생각이었다. 문제는 UIKit과 SwiftUI 중 무엇으로 통일하느냐였다. SwiftUI는 스터디를 계속 진행하고 있었지만 아직 UIKit만큼 대중화되지 않았고, 우리가 사용하는 SDK들 중에서 SwiftUI를 지원하지 않는 것들이 꽤 많았다. 혼자서 세 개의 앱을 책임져야 하는 상황에서 가장 확실한 선택은 내가 제일 잘 아는 UIKit이었다. 그래서 UIKit으로 통일하기로 했다.
아키텍처는 RIBs, 모듈화는 static library
통합할 기술을 정하고 나서는 모듈화를 염두에 두고 아키텍처를 다시 설계하기로 했다. 먼저 모듈로 나눌 화면들을 분리하고, 각 서비스에 맞게 커스텀되어 있는 부분들을 하나하나 파악해서 정리했다.
아키텍처는 처음에 MVVM을 고민했다. 하지만 계층이 명확하게 나뉘어 있었고, 화면 안에서 재사용해야 하는 화면들이 많았으며, 공통 로직과 각 서비스에 특화된 로직이 함께 들어가야 하는 경우가 잦았다. 여기에 모듈화까지 감안하니, 부스트캠프에서 가장 많이 다뤄봤던 RIBs가 가장 잘 맞겠다고 판단해 RIBs를 선택했다.
물론 혼자 개발하는 규모에 RIBs가 과하다는 것은 나도 잘 알고 있었다. 그럼에도 RIBs를 택한 데에는 이유가 있었다. 참고할 문서도, 함께 논의할 동료도 없는 환경에서 일관된 규칙 없이 개발하다 보면 결국 지금의 레거시와 똑같은 코드를 만들게 될 것 같았다. 차라리 RIBs의 엄격한 구조가 나 스스로에게 강한 컨벤션을 강제해주는 안전장치가 되어줄 거라 생각했다. 무엇보다 이미 다뤄본 아키텍처였기에, 큰 리팩토링을 진행하면서 새로운 아키텍처까지 학습하는 리스크를 지지 않아도 된다는 점이 컸다.
아키텍처 구조를 확정한 뒤에는 모듈화를 어떻게 할지 정해야 했다. 처음 생각한 것은 Tuist였는데, 우리가 쓰는 SDK 중 몇 개가 CocoaPods로만 설정을 제공하고 있어서 결국 서브 프로젝트를 static library로 나누는 방식으로 결정했다. Swift Package로 할까도 고민했지만, 혼자 개발하는 데다 별도의 버전 관리가 필요하지 않았기에 static library가 더 편리할 것 같았다.
리팩토링에서 진짜 힘든 건 따로 있었다
리팩토링은 운영 중인 앱을 직접 건드리는 대신, 완전히 별개의 프로젝트를 새로 파서 진행했다. 여기서 리팩토링과 테스트가 모두 끝나면, 그때 기존 레거시 앱을 이 새 프로젝트로 교체해 업데이트할 계획이었다.
그럼에도 리팩토링 과정은 결코 쉽지 않았다. 애초에 리팩토링은 내가 하고 싶어서 벌인 일이었고, 그 와중에도 레거시 프로젝트로 들어오는 실제 운영 업무는 계속됐기 때문이다.
리팩토링에서 가장 중요한 것은 기존 기능들이 전부 그대로 동작하느냐인데, 어떤 코드가 실제로 살아 있는 코드인지 아닌지를 파악하는 것부터가 어려웠다. 딥링크가 대표적이었다. 먼저 안드로이드 개발자와 확인하고, 실제로 마케팅 부서에서 사용하고 있는 딥링크가 맞는지 다시 확인한 뒤에야 죽은 코드를 걷어낼 수 있었는데, 이 과정 하나하나에 시간이 많이 들었다.
무엇보다 QA팀이 없다는 것이 컸다. 기존에 구현되어 있던 기능들을 리스트업해 둔 곳과 대조하며 빠진 것은 없는지 체크하는 일이 가장 힘들었다. 게다가 private으로 선언된 것이 하나도 없다 보니, 어디서 무엇이 영향을 받는지 확신하기 어려워 이 허들이 유독 높았다.
그래서 이번에는 리팩토링을 진행하면서 테스트 코드도 최대한 많이 작성했다. QA팀이 없는 만큼 테스트 코드를 두텁게 깔아 스스로를 방어하고 싶었기 때문이다. 기획서에 명시된 스펙과 내가 따로 알고 있던 스펙들을 테스트로 옮겼다. 그런데 정작 더 힘들었던 것은, 기획서가 제대로 남아 있는 경우가 드물어서 기존 코드와 그 동작을 일일이 따라가며 스펙 자체를 새로 확정해야 했다는 점이다.
레거시를 무조건 갈아엎는 게 답은 아니었다
리팩토링을 하다 보니 신기한 일이 벌어졌다. 그동안 도무지 이해가 가지 않던, 운영 코드가 왜 이렇게 짜여 있는지가 조금씩 이해되기 시작한 것이다. 로직의 흐름을 따라가다 보면 정말 다른 방법이 없어서 그렇게 짤 수밖에 없었던 경우가 많았다.
예전에는 레거시 코드를 왜 리팩토링하지 않고 그냥 두는지 이해할 수 없었다. 그런데 이번 경험을 통해, 레거시 코드를 무조건 리팩토링하는 것이 항상 정답은 아니라는 것을 깨닫게 되었다. 나름의 이유가 있어서 그 자리에 그렇게 있는 코드도 있었던 것이다.
무너진 설계, 그리고 자괴감
개발을 진행하다 보니 처음 설계대로 돌아가지 않는 것들이 많았다. 어느 정도 예상은 했지만, 막상 마주하고 나니 그동안 작성했던 코드들이 한순간에 무용해지는 것에 허탈감이 상당했다. 이걸 왜 설계 과정에서 미리 발견하지 못했을까 하는 자괴감도 들었다.
잘못 설계했다는 것을 왜 알아차리지 못했는지 곰곰이 생각해봤다. 결국 내가 리팩토링에 너무 매몰되어 있었고, 기존 코드는 전부 뜯어고쳐야 한다는 생각에 사로잡혀 있었던 것이 문제였다. 경험이 부족했던 것이다. 기존 코드에서 살릴 것은 살리고, 왜 이렇게 작성되었는지에 대해 충분히 고민하지 않은 채 달려든 결과였다.
그래서 설계를 처음부터 다시 점검했다. 기존 운영 프로젝트가 왜 지금과 같은 모습이 되었는지를 다시 되짚어보고, 거기에 맞춰 설계를 변경했다. 가장 큰 변경은 모듈을 나누는 방식이었다. 레거시 코드에는 상당히 거대한 Massive View Controller가 있었는데, 나는 이걸 최대한 잘게 쪼개려고 7개의 모듈로 나눴었다.
하지만 막상 모듈 경계를 그으려 하니 문제가 드러났다. 코드상으로는 서로 영향 범위가 없어 보였지만, 앞서 말했듯 private이 아니다 보니 하위 뷰에서 부모를 타고 타고 올라가 값을 변경하는 부분이 있었다. 이렇게 모듈 간 영향 범위가 얽혀 있으니 애초에 깔끔하게 7개로 분리하는 것이 불가능했다. 나도 미처 생각하지 못하고 놓쳤던 부분이었고, 그 탓에 설계가 틀어졌던 것이다. 결국 이런 부분들을 다시 찾아내 하나씩 정리하면서, 모듈을 최소화해 3개로 다시 합쳤다.
1분기를 돌아보며
1분기는 거의 모든 시간을 리팩토링에 쏟았다. 전체 리팩토링은 5개월 정도를 예상 기간으로 잡았는데, 1분기에 그중 60% 정도를 완료했다. 여기서 60%란, 처음 계획했던 공통 로직들을 전부 분리해내고 이제 각 서비스에 특화된 화면들만 남은 상태를 뜻한다. 내가 예상한 일정대로 흘러가고 있어서 꽤 만족스러웠다.
무엇보다 이번 분기는 코드를 고치는 것 이상으로, 레거시를 대하는 태도와 설계에 대한 관점을 다시 배운 시간이었다. 남은 리팩토링을 어떻게 마무리했는지는 다음 분기 회고에서 이어서 이야기해보려 한다.
'회고 > 일상' 카테고리의 다른 글
| 2024년 4분기 회고 (1) | 2026.08.10 |
|---|---|
| 2024년 3분기 회고 (0) | 2026.07.29 |
| 2024년 2분기 회고 (0) | 2026.07.29 |
| 2024년 1분기 회고 (0) | 2026.07.29 |