대규모 코드 마이그레이션 자동화하기

자동화를 통해 마이그레이션 비용 줄이기

앤드류 • Web frontend

  • 테크 인사이트

안녕하세요 채널톡 웹팀의 앤드류입니다. 채널톡에서는 베지어라는 디자인 시스템을 자체 개발하여 채널톡 어플리케이션 개발에 활용하고 있습니다.

최근에 베지어의 메이저 버전을 올리면서 베지어의 css-in-js 라이브러리에 대한 의존성을 제거하고 보다 나은 인터페이스를 제공하기 위해 개선했던 작업이 있었습니다. 디자인 시스템이 사용하는 스타일링 시스템 자체를 변경하다 보니 상당히 많은 변경 사항(breaking change)이 발생했고, 어플리케이션에서는 만 줄 이상이 되는 코드를 바꿔야 했습니다.

이번 글에서는 이러한 변경 사항에 보다 효율적으로 대응하기 위해 마이그레이션의 대부분을 자동화하여 마이그레이션 비용을 감소시킨 경험을 소개하려고 합니다.


도전 과제

베지어에는 어떤 변경 사항이 있었길래 사용처에서 수작업으로 하나씩 대응하지 않고 자동화를 해야만 했을까요? 몇 개만 살펴보자면 아래와 같은 변경사항이 있었습니다.

1. 컴포넌트 네이밍 변경: 기존의 Stack을 레거시 처리하고 실험적으로 제공하고 있던 AlphaStack을 안정화했습니다.

JavaScript

2. 컴포넌트 인터페이스 변경: 라이브러리 전반적으로 크기, 스타일, 위치, 타이포그래피 관련 속성으로 enum을 사용하고 있던 것을 모두 string literal로 변경했습니다.

JavaScript

3. styled, foundation 객체 지원 중단: 베지어에서 styled-componentsstyled객체와 color, round, border-radius 등 디자인 요소를 가지고 있는 foundation객체를 지원하지 않게 되었습니다. 이에 따라 styled-components에서 직접 styled를 가져오고 foundation 대신 css variable을 사용해야 합니다.

JavaScript

위와 같은 변경 사항이 일어난다면 여러분은 어떻게 대응하시겠나요? 첫 번째 경우라면 IDE에서 컴포넌트 이름으로 전체 검색하여 수정을 하면 되겠지만, 두 세 번째 경우라면 import 문이 바뀌고 검색해야 하는 경우의 수가 많아져서 사실상 기계적으로 소스파일을 하나씩 둘러보면서 바꿀 수 밖에 없습니다.

이 과정에서 생기는 문제점들을 살펴보면 다음과 같습니다.

  1. 채널톡 어플리케이션(데스크)의 경우 15명의 프론트엔드 엔지니어가 함께 개발하는 서비스인 만큼, 버튼이나 레이아웃 컴포넌트처럼 자주 쓰이는 컴포넌트 하나의 인터페이스가 바뀌면 적어도 수 백줄에서 수 천 줄 이상의 코드를 바꿔야 합니다. 그에 따라 마이그레이션에 대응하는 비용이 매우 크고 실수의 여지가 많아집니다.

  2. 베지어를 사용하는 레포지토리가 데스크를 포함해서 8가지 정도 있고 모두 담당하는 개발자가 다릅니다. 그들이 모두 베지어에 대한 지식이 있는 것이 아니기 때문에 변경 사항 전체를 팔로우하기 어렵고, 따라서 자동화된 프로세스가 더욱 필요했습니다.

  3. 베지어가 오픈소스로 관리되고 있기 때문에 적절하게 마이그레이션 전략을 지원하는 것이 오픈소스 성격에도 잘 맞았습니다.

이러한 문제점들을 고려하여 저희는 마이그레이션을 자동화해주는 툴을 만들기로 결론짓게 되었습니다.

구현하기

구현을 위해 ts-morph라는 라이브러리를 사용했습니다. ts-morph는 타입스크립트 AST를 손쉽게 순회하고 조작하기 위해 타입스크립트 컴파일러 API를 랩핑한 라이브러리입니다. ts-morph를 사용하여 코드의 원하는 부분을 바꾸는 과정을 간단히 도식화하면 다음과 같습니다. 자세한 구현 내용은 bezier-codemod패키지를 참조하시면 될 것 같습니다.

  1. 소스 코드를 AST로 파싱합니다. AST viewer를 참고하시면 좋습니다.

  2. 만들어진 AST에서 변경하고자 하는 노드를 찾아서 원하는 대로 변경합니다. 이 때 원하는 노드를 제거할 수도 있고 노드의 내용을 변경할 수도 있습니다.

  3. 수정한 AST기반으로 코드를 생성합니다.

이러한 과정을 거쳐서 베지어의 breaking change에 대응하는 변환 코드를 만든 결과, 총 작업량의 90퍼센트 이상에 해당하는 만 줄이상의 코드 변환을 자동화할 수 있었습니다 🎉 

수작업으로 했다면 하루 이틀정도는 꼬박 걸려서 했을 단순 반복 작업을 보다 효율적이고 재밌는 방법으로 끝낼 수 있었습니다. 또한 베지어의 변경사항에 대한 컨텍스트가 없는 개발자도 이렇게 만들어진 변환 코드를 사용하여 관리하고 있는 라이브러리 혹은 어플리케이션에서 손쉽게 코드 변환을 자동화할 수 있었습니다.

주의할 점

1. 테스트 코드 짜기

구현한 변환 코드를 사용처에 적용하기 전에, 변환 코드가 잘 작동하는 지 확인하기 위해 테스트 코드를 작성하는 것이 필요합니다. 이 때, 변환하고자 하는 부분을 잘 변환하는 지 확인하는 테스트 코드 뿐만 아니라, 반대로 변환하지 말아야 하는 곳을 변환하지 않는 지 확인하는 테스트 코드까지 만드는 것을 추천합니다.

워낙 규모가 큰 코드뭉치에 변환 코드를 적용해야 하기 때문에, 변환하고자 하는 부분을 느슨하게 체크하면 원하지 않는 곳까지 변환해버리는 경우가 있었기 때문입니다.

JavaScript

2. 성능 챙기기

변환 코드를 작성하다 보면, 변환이 끝난 후 사용하는 곳이 더이상 없기 때문에 named import를 제거해야 하는 경우가 종종 있었습니다. 이 때 처음에는 ts-morph에서 제공하는 SourceFile.fixUnusedIdentifiers() 메서드를 사용하여 안쓰이는 변수를 제거했었는데요, 사용해보니 웬만해서는 사용하지 않는 것이 좋다고 판단했습니다.

첫 번째로, 안쓰이는 변수를 모두 제거하다보니 원하지 않는 코드까지 제거해버리는 문제가 있었습니다. 예를 들면 파일 자체를 import하는 import 'global_styles.css' 구문 전체를 제거해버리기도 합니다.

두 번째로, 소스 파일 안에 있는 모든 노드를 순회해야 하기 때문에 그만큼 성능면에서 악영향을 미칩니다. 이 메서드를 사용했을 때와 ts-morph에서 제공하는 저수준의 메서드를 사용해서 안쓰이는 named import를 직접 제거했을 때를 비교해보니, 채널톡 코드를 변환코드에 넣었을 때마다 걸리는 시간이 약 20분 정도 차이가 나는 것을 확인했습니다.

마치면서

지금까지 설명한 코드 변환 마이그레이션은 사실 codemod라는 명칭을 가지고 있고, 베지어 뿐만 아니라 여러 오픈 소스에서 breaking change를 지원하기 위한 방법으로 활용되고 있는 일반적인 방법입니다.

아마도 React, Storybook, Chakra-ui 등의 메이저 버전업을 해야할 일이 있다면 높은 확률로 그들이 구현한 codemod를 npx 명령어로 사용하게 될 것 입니다. 베지어의 codemod도 여러 오픈 소스를 참고하면서 개발하였기에 베지어 또한 누군가에게 영감을 주는 오픈소스가 되어 가길 희망합니다.

혹시 이 글을 보고 저희의 여정에 흥미가 생겼다면 채널팀의 채용공고를 봐주시길 부탁드립니다 😄

We Make a Future Classic Product

채널팀과 함께 성장하고 싶은 분을 기다립니다

사이트에 무료로 채널톡을 붙이세요.

써보면서 이해하는게 가장 빠릅니다

회사 이메일을 입력해주세요