신입 개발자의 레거시 코드를 향한 관점 전환기

Taekhwan Han
playkeyboard
Published in
8 min readJun 7, 2024

안녕하세요, 플레이키보드 iOS 개발자 한택환입니다.

이번 글에서는 iOS 신입 개발자로 서비스를 개발하면서 마주했던 고정관념과 그 고정관념을 깨기 위해 진행했던 기술부채 해결 과정을 공유하려고 합니다.

플레이키보드에 합류했을 때, 가장 큰 고민은 iOS 키보드의 메모리 제한이었습니다. iOS 커스텀 키보드는 약 80MB를 초과하면 기본 키보드로 강제 전환되며 충돌이 발생하는 문제가 있었습니다. 이런 메모리의 제한 안에서 기존 코드를 따라가려니, 레거시 코드에 대한 의문점이 많았습니다. 여러 개발자의 코드가 섞여 있어 그 의도를 파악하기 어려웠고, 확실한 기술 스택도 정립되어 있지 않았습니다. 저도 모르게 점차 ‘오래된 프로덕트’, ‘경력 개발자 코드’라는 인식 때문에 의문을 지워나가고 한정된 메모리 안에서 레거시 코드를 만들어내고 있었습니다.

⓵ 기술부채 해결의 시간

플레이키보드는 작년 11월, 모두의 위기의식 속에 약 한 달 반 동안의 기술부채를 해결하는 시간을 가졌습니다.

처음 기술부채 해결 기회가 주어졌을 때 제 머릿속은 딱 아래의 사진과 같았습니다.

신입개발자가 레거시 코드를 마주하였을 때

아슬아슬하게 메모리 한계 안에서 돌아가고 있는 코드를 건드려야 하고, 이 과정에서 경력 개발자 코드를 걷어내고 내 코드를 채운다는 것이 처음에는 엄두가 나지 않았습니다. 그러던 중 ‘플레이키보드는 본래 사용자의 입력 경험 개선을 위한 서비스인데, 계속해서 메모리 제약에 발목을 잡힌다면 아무리 개선해도 한계가 있겠구나’라는 생각이 들었습니다.

만약 이대로 개발이 이뤄진다면 iOS 플레이키보드는 밑바닥이 빠진 젠가처럼 위태로워질 수 있겠다는 우려도 동시에 가지게 되었죠.

따라서 이번 한 달 반의 시간이 정말 중요하다는 생각을 갖고 기술부채 해결을 위한 계획을 짜기 시작했습니다.

가장 중요하게 해결해야 했던 문제는 아키텍처의 부재와 기술 스택의 다양성이었습니다. 기술 스택이 정립되어 있지 않은 상황에서 다양한 코드 구조와 서드파티 라이브러리가 뒤엉켜 있었기 때문에 문제가 발생하면 여러 코드를 살펴봐야 했고, 원인을 파악하는 데에도 많은 시간이 소요되었습니다. 따라서 이 문제를 반드시 해결해야 한다고 판단했습니다. 해결 후 어떤 긍정적인 변화가 있을지, 반대로 어떤 부작용이 발생할 수 있을지 면밀히 검토하고 예측하며 계획을 수립했습니다.

최종적으로 약 한 달 반에 걸쳐 ‘아키텍처 도입, 코드 구조 개선, 불필요한 라이브러리 제거’ 등의 기술부채 해결 작업을 진행하기로 결정했습니다.

⓶ MVVM 아키텍처의 도입

플레이키보드 iOS 팀에서는 MVVM 아키텍처를 도입하였습니다.

MVVM과 Combine을 결합한 기술스택

‘서비스를 분리하고 의존성을 줄이기 위해서는 RIBs, Clean과 같은 보다 고도화된 아키텍처가 나을 수 있겠다’라는 생각도 했지만 키보드의 기능과 View들이 비슷한 구조로 되어 있는 점, 그리고 서드파티 라이브러리를 활용한 아키텍처는 KeyboardExtension에서 제한된다는 점에서 최대한 퍼스트파티 라이브러리만을 활용한 기술 스택으로 해결하고자 노력했습니다.

아래는 MVVM 구조를 도입한 AIKeyboard 예시입니다.

처음 AIKeyboard의 코드 패턴은 State 패턴을 활용하면서 각각의 상태 변화에 따라 유동적으로 View의 DataBinding을 하려고 했습니다.

Swift State 패턴이 들어갔던 AIKeyboard

그러나 여기서 문제는 State가 다양한 종류의 변수를 갖게 되고, 한 번에 State와 View들이 생성되면서 구독하고 있는 상태들이 꼬여 사용 횟수에 문제가 가거나, 프리미엄 기능에 문제가 가는 등, 안정성이 떨어지는 문제가 있어 개선하기 가장 어려웠던 기능입니다.

저희는 이 기능을 개선하기 위해서 우선 필요한 요구 사항을 모두 적고 이를 다이어그램으로 구조화하여 진행했습니다.

MVVM 구조 구상을 위한 다이어그램

View가 많았기 때문에 MainView 하나를 잡고 그 다음 각각의 SubView마다 따로 MainView와 Input, Output Stream을 구성해주었습니다. 그 결과 Publisher들의 분리와 필요한 Stream만 호출하여 쓰도록 하면서 각각의 SubView들의 의존성이 줄었고 비동기 과정에서 상태가 겹쳐 발생할 수 있는 오류들을 해결할 수 있었습니다.

Stream 을 활용한 AIKeyboard의 MVVM 예시

결과적으로, 아키텍처를 도입하는 과정에서 많은 라이브러리를 줄일 수 있었고, 다양한 방식으로 구성되어 있던 DataBinding을 통합할 수 있었습니다.

⓷ 여전히 개선되지 않았던 메모리와 의존성 주입

아키텍처 작업을 통해서 코드 구조의 개선과 기존의 다양한 기술 스택을 정리할 수 있었지만 메모리 점유율이 높다는 문제는 여전했습니다. 추후 다른 기능을 도입하기 위해서 그리고 키보드를 확장하기 위해서는 메모리의 감소가 필수적이었기 때문에 다시 놓친 부분을 점검하고 필요한 구조를 검토했습니다.

검토 결과 여전히 하나의 ViewController에서 모든 View들을 인스턴스화해서 들고 있는 구조가 문제였습니다.

특히 플레이키보드는 8개가 넘는 다양한 기능들과 이 기능들마다 각각의 View와 ViewModel을 가지고 있었기 때문에 한 번에 올라오면서 일시적으로 메모리가 초과되는 경우가 많았습니다.

따라서 의존성 주입 도구인 DIContainer를 구성하여 키보드를 제외한 기능들을 모두 필요한 순간에 외부에서 주입하도록 변경하고, 이 과정에서 ViewModel도 같이 주입할 수 있도록 하여 ViewController에 대한 의존성을 낮추고 한 번에 올라가는 메모리 양을 줄일 수 있었습니다.

DIContainer의 도입

뷰 계층을 보시면 이전 플레이키보드는 사용하지 않는 기능과 일회성 기능들까지 모두 실행되고 있는 것을 보실 수 있습니다. 그러나 DIContainer를 도입하고 난 이후에는 키보드를 사용할 때는 오직 키보드만 레이아웃과 메모리에 올라오도록 개선이 되었습니다.

DIContainer를 활용한 이후의 View 계층 구조

이 과정을 거치고 나서야 키보드를 활성화하기 전에 메모리가 초과되어 기본 키보드로 바뀌는 문제가 완화되었고, 주입하는 방식으로 변경되어 추후 다른 기능 도입도 용이해졌습니다.

개선 후 다양한 기능이 도입된 플레이키보드 지금 확인하세요 :)

⓸ 기술부채 개선 결과

이전 약 66MB 제한 선에 있던 iOS 키보드 메모리가 26.6MB라는 굉장히 적은 양을 점유하도록 개선되었습니다. 또한 안정화율의 향상과 이로 인한 DAU 우상향 지표를 얻을 수 있었습니다.

줄어든 메모리 점유율, DAU의 상승

이제는 어디서든 “우리의 기술 스택이 MVVM+Combine이며 의존성 주입을 통해 메모리 관리를 하고 있습니다.”라고 말할 수도 있게 되었습니다.

정말 큰 변화이고 또 앞으로 나아가기 위한 기반이 된 과정이었습니다.

그러나 제게 있어 가장 큰 변화는 레거시 코드를 자신 있게 제거하고, 새로운 관점에서 개발 구조를 설득해 성과를 낼 수 있게 되면서, 스스로 쌓아왔던 고정관념이 깨진 것이었습니다.

이 글의 핵심은 ‘필요한 아키텍처와 기술 스택의 신중한 선택’이 아닙니다. 입사 초기에는 제보다 플레이키보드에 오랜 시간을 함께한 레거시 코드들과 그 코드를 작성한 여러 개발자를 의식하며, 도전할 수 있는 범위를 제한하는 경향이 있었습니다. 그래서 코드를 배움의 대상으로만 바라보며 ‘항상 배워야지’라는 자세로 임했습니다.

그러나 기술부채 해결 과정을 겪으면서 학습은 무조건적인 수용, 그러한 태도가 아니라 능동적인 문제 해결이 중요하고, 그렇기 때문에 의문을 갖고 코드를 해체해서 더 나은 코드를 직접 작성할 수 있도록 노력해야 한다는 것을 깨달았습니다.

의문이 들면 주저 없이 해체하고 파고들어 더 나은 코드를 작성할 수 있도록 끊임없이 노력해야 합니다.

신입 개발자라는 틀안에서 항상 배움의 자세로 코드를 바라보고 계시지는 않으신가요?

제 글을 접한 신입 개발자 모두가 신입이라는 스스로의 고정관념과 한계를 인식하고 깨고, 도전하며 함께 성장하면 좋겠습니다.

감사합니다.

--

--