본문 바로가기

실전! 리팩토링

by kicksky 2022. 7. 31.

배경

회사 내부 일정, 개발팀 일정 꼬여서 예상치 못한 공백이 생겼다. 소규모 인력으로 운영하느라 매번 밀려있던 버그를 처리하기로 했다. 우리 서비스를 이용하는 크리에이터는 영상을 업로드 하고 유저가 동작을 따라할 구간을 설정해야 한다. 바로 이 구간 설정 기능을 손봐야 했다.

목표

바뀐 정책을 반영하고, 자잘한 버그를 수정하자 ! (사실 정책은 작업 도중에 확정되긴 했다) 올해 초 이벤트 진행 중에 발생한 버그였다. 유저가 구간 간격을 너무 밭게 설정하면, 순차적으로 이루어져야 하는 서버와의 실시간 소켓 통신이 네트워크 환경에 따라 꼬여버릴 수 있었고, 유저가 서비스를 정상적으로 이용할 수 없었다. 사실 일종의 엣지 케이스여서 흔히 발생하는 경우는 아니었지만 안정성을 위해서는 개선이 필요했다. UI 상에서 유저 인터랙션에 제한을 두기로 했고 앞서 말했듯 엣지 케이스이기 때문에 UX를 크게 해치지는 않을 것으로 예상했다.

작업 과정

우선, 사수는 설계부터 완전히 갈아 엎는 것을 추천했다. 하지만 이미 운영 중인 서비스에 내 실력과 경험으로 판을 새로 짜는 건 도박이라고 판단했다. 정기 배포를 꾸준히 담당했던 프로덕트가 아니었고, 구조와 상호 의존성을 쉽게 파악하기 어려운 코드들이었기 때문이다. 2년 간 대부분의 코드가 개선된 적이 없었고, 운영 중간에 새로운 기능을 위한 코드가 기존 코드에 그대로 덧붙은 부분들이 많았으며, 최적화 되지 않은 채로 재사용되는 컴포넌트들로 구성되어 있었다. 사이드 이펙트가 고구마 줄기처럼 어디까지 딸려올지 예상하기 어려웠다. 그래서 우선 바뀐 정책과 관련된 버그만 확실히 고치는 것을 목표로 작업을 시작했다.

  1. 전체 라이프 사이클 파악하기: 플로우 파악하고 테스트 해보면서 유저 입장에서 직관적이지 않은 UI/UX 체크하고 놓친 버그를 리스트업 했다. 
  2. 다루고 있는 값들 확인하기:  API 통해서 서버로 넘기고 받아오는 데이터, 상태 관리 라이브러리로 관리되는 전역 상태 값, 컴포넌트 내부 state로 관리되는 값, UI 챕터 관련 값(선택, 호버, 해제 등)
  3. 리액트 컴포넌트 구조와 재사용 범위 확인하기: 더불어 콜백 함수 로직의 영향 범위 파악. (최초의 정책 변경은 마우스 드래그 이벤트에 한해서만 적용했는데, 코드 리뷰를 받다가 해당 정책이 키보드 입력 이벤트에 대해서도 적용되어야 한다는 걸 알게 되었다.)

이렇게 시작을 했고, 함수 내부를 고칠 때는

  1. 하나의 함수가 한 페이지를 넘어가면 코드들의 역할과 관련성에 따라 코드를 한 줄씩 옮겨가며 블럭화했다.
  2. 그 다음 각 블럭의 실행 순서를 따져보고 블럭 간 배치와 순서를 조정했다.
  3. early termination을 위해서 리턴의 우선 순위를 정했다.
  4. 각 블럭 내부의 코드를 쪼개거나 합치면서 가독성을 높였다.
  5. 마지막으로 다시 관련 있는 함수나 식들을 재배치를 했다. 

배운 점

타입 정의를 처음부터 잘하고 중간에도 잘하자

any나 union type( | )으로 실제 사용하지 않는 타입을 포함하여 정의한 부분이 있었다. 이렇게 타입 정의를 하면 해당 값을 사용할 때 if( typeof value === ‘string’) 처럼 불필요한 타입 체크 코드가 추가된다. 실제로 string 말고 다른 값을 쓰는 케이스가 없는데도! 프로젝트를 시작하기 전에 어떻게 청사진을 그리냐는 트윗에 누가 타입 정의를 한다고 답글을 단 것을 본 적이 있다. 타입 정의는 귀찮더라도 뼈대를 잡는 일이니 필요할 때마다 수정하고 정의해야겠다는 생각이 들었다. 

바퀴를 다시 발명하지 말자

같은 값을 얻는 방법과 장소가 이원화 되어 있었다. Draggable 라이브러리를 쓰고 있는데 Draggable의 bounds 설정과 드래그 이벤트 로직 둘 모두에서 드래그 범위를 제한하고 있었다. 당장 내가 필요한 기능이 아니더라도 우선 라이브러리가 제공하는 옵션을 잘 파악해두자. 내가 한 고민은 대부분 누가 먼저 해서 해결까지 해놓은 경우가 다반사니까. 그리고 커뮤니케이션과 협업의 중요성도 느꼈다. 

분기 처리 (if/else)

중간에 기능이 추가되면서 조건이 복잡하게 중첩되거나 이율배반 되는 코드가 많았다. (앞선 조건에서 어떤 값이 false인 경우의 블록 내부 에서 다시 해당 값이 true인 케이스를 처리하는 식) 코드의 가독성이 매우 떨어져서 어떤 조건으로 각 케이스를 처리하려는건지 파악하는 데 오랜 시간이 걸렸다. 그리고 분기 처리를 명확하게 해두지 않으면 early termination도 쉽지 않기 때문에 극단적인 경우에는 성능상의 이슈가 있을 수도 있다. 이런 경우에는 여건이 되는 한 리팩토링을 먼저 잠깐이라도 하고 새 기능과 코드를 추가하는 게 낫지 않을까 싶다.

순수 함수 작성하기

리팩토링 하면서 제일 재밌었던 부분은 여러 군데에서 같은 기능을 하는 서로 다른 함수를 아예 추출하여 util로 옮기는 작업이었다. 넣어준 값이 같으면 반환되는 값도 항상 같은 순수 함수로 작성했는데 불필요한 dependency를 덜어내고 코드가 간결해지는 게 좋았다. 추출한 이후에는 필요할 때마다 갖다 쓰기만 하면 되니까 가독성과 효율성 둘 다 향상 되었다. (변수명만 잘 지으면 된다) 다만 함수의 역할 범위를 정하는 건 좀 까다로웠다. 우선은 최소 단위로 쪼개고, 필요하면 새로운 함수를 작성해서 최소 단위의 함수를 가져다 쓰는 방식을 취했다. 다만 이렇게 추출했을 때 나머지 코드 베이스에서 관련된 부분을 어떻게 추적해서 변경해야 하는지는 아직 명확한 답을 찾지 못했다.

복잡한 식으로 작성된 매개변수를 함수 인라인하기

리팩토링 2판에서 항상 리팩토링 말미에 이렇게 앞서 추출한 함수를 인라인 하여 매개변수로 넘겨주곤 하는 게 인상적이었다. 이렇게 작성된 코드들 수정할 때 엄청 시원했다. 

// before
player.currentTime((hoverPosX / timelineDimensionX) * duration);

// after
player.currentTime(offsetXToTime(hoverPosX));

그 외

정책과 직접 관련된 값들은 constant으로 할당하고 MIN_DURATION_SEC 이런 식으로 이름을 지어 변수를 사용했다. 리팩토링 책에서도 강조되던 이름 짓기의 중요성을 절실히 느꼈다. 그리고 setState의 콜백 매개변수 안에 또 다른 setState가 호출되던 부분이 있었는데 굳이 state 업데이트의 순서를 복잡하게 만들 필요가 없었기 때문에 두 setState를 같은 deps에서 실행하도록 했다.  

느낀 점

사실 처음에는 다른 사람이 2년 전에 작성한 코드를 개선해야 한다는 점이 탐탁치 않았다. 배울 점이 많지 않아 보이는, 생판 초면의 코드였다. 근데 때마침 리팩터링 2판을 읽고 있었고 실습이나 해보자는 생각으로 시작했다. 회사에서 코드 뜯어보다가 저녁에 집 가서는 리팩터링 읽고 다음 날에는 전날에 읽은 부분 적용해보면서 저절로 흥미가 생겼다. 정말 오래 묵은 집을 청소하는 느낌이었다. 하기 싫어서 계속 미루다가 언젠가 한번 날 잡고 싹 갈아 엎으면 속 시원하고, 점차 깨끗해지는 거 보면 기분도 좋고 그렇지 않은가. 물론 이번 기회로 청소는 확실히 조금씩 꾸준히 해야한다는 것을 배웠다(…)

그럼에도 중간중간 ‘왜 코드를 이렇게 짰지?’ 이런 생각도 들었던 건 사실이다. 코드가 마음에 안 들어서 저런 생각이 들 때도 있었고, 정말 이해할 수 없어서 저런 생각이 들 때도 있었다. 하지만 다르게 생각하면 리팩토링 해야하는 코드 베이스는 좋은 코드로 가는 초석이다. 처음부터 아름답고 깨끗한 코드를 작성할 수 있는 사람은 드물다. 좋은 코드는 단계적으로 발전하고, 누군가 나 대신 2년 전에 그 초석을 닦아 놓았다는 생각이 들었다. 개발바닥에서도 비슷한 이야기가 있었다. 아무리 레거시, 레거시 하고 욕해도 현재의 서비스와 제품의 기반이 된 것이 저 레거시라고. 당시의 조건과 환경에서 내린 최선의 선택이었을 것이고, 그 선택을 토대로 현재까지 올 수 있었던 것이라고. 그래서 나중에는 진짜 고마운 마음도 들었다. 

마무리

체감상 Line 300은 줄인 것 같았는데(...)

코드를 배포까지 하고 나니 정말 기승전결이 있는 글을 쓴 기분이 들었다. 여력만 된다면 손 대지 못한 부분들도 마저 최적화를 하고 싶다. 사실 이번 리팩토링은 앞서 말했듯 도박을 하기는 싫어서 기존의 틀 안에서 작업을 했는데 이제 어느 정도 코드가 파악 되었으니 설계나 구조에 대한 고민을 해봐도 될 것 같다. 되게 오랜만에 몰입해서 일할 수 있었던 즐거운 작업이었다. 

'' 카테고리의 다른 글

[220521] 개발자, 프로덕트(디자인) 팀과 협업  (0) 2022.05.21
[220520] 문제 해결  (0) 2022.05.20
[211216] 단상  (0) 2021.12.16
우아한형제들 경력 개발자 인터뷰 리뷰  (0) 2021.10.04
킨더구리 v2 프로젝트 후기  (0) 2021.04.13

댓글