왜 TBD 쓰세요?

우현민·2023년 4월 24일
8

dev

목록 보기
2/9
post-custom-banner

Git을 이용해 개발할 때는 모두들 브랜칭 전략을 택하여 팀의 컨벤션으로 가져갑니다. 저는 git flowgitlab flow, 그리고 TBD를 이용해 봤는데요, 지금은 TBD로 정착하여 회사와 사이드 프로젝트 모두에서 TBD를 이용하고 있습니다.

이번 글에서는 제가 TBD를 좋아하는 이유와 TBD의 장점에 대해 소개해보려 합니다.

TBD에 대한 자세한 내용은 공식문서 에서 확인할 수 있습니다.





깃 브랜칭 전략의 문제점

먼저, gitbranch 는 매우 강력한 기능입니다. 커밋을 통해 코드의 버전을 만들고, 그 버전을 지속적으로 업데이트하고 병합하여 배포하는 과정에서 브랜치가 매우 핵심적인 역할을 합니다. 브랜치가 이렇게 훌륭한 도구가 될 수 있는 이유는, 특정 커밋을 가리키고 commit 명령어를 통해 버전을 업데이트할 수 있게 함으로서 코드가 여러 버전으로 동시에 존재할 수 있도록 도와주기 때문입니다.

하지만 아이러니하게도, 코드가 여러 버전으로 동시에 존재하기에 우리는 종종 비극을 겪습니다. 특히 코드가 여러 버전으로 동시에 오랫동안 존재할 경우가 문제입니다. 오랫동안 서로 다른 버전으로 존재하며 성장한 코드는 메인 브랜치와는 너무 달라져서 몇천 줄짜리 커밋이 되곤 합니다.

다시 강조합니다. 코드가 여러 버전으로 동시에 오랫동안 존재할 경우가 문제입니다.

💥 컨플릭

개발자들은 이런 브랜치들을 모으며 충돌을 해결하고 주기적으로 디폴트 브랜치를 리베이스해줘야 합니다. 컨플릭을 해결하는 건 언제나 고통스러운 일입니다. 나 혼자 해결할 수 있는 컨플릭도 있지만, 해당 커밋을 한 사람과 함께 해결해야 하는 컨플릭도 있습니다. 그리고 컨플릭을 해결하고 나서도 내 코드가 잘 동작하는지 다시 테스트해줘야 합니다.

👥 코드 리뷰

코드 리뷰에도 그리 좋지 않습니다. 100줄 ~ 300줄 정도의 PR은 리뷰하기 쉽지만, 1000줄이 넘어가면 코드 리뷰가 굉장히 어려워집니다. 그렇다고 PR을 잘게 쪼개 리뷰하려 해도, 결국 "진짜 메인에 머지되는" PR은 리뷰할 수 없습니다.

🍾 개발 병목

가령 A 기능을 개발중인 feature/A 브랜치에서 전체 코드베이스에 대한 개선이 이루어졌다면 (그리고 해당 변경사항이 feature/A 브랜치의 특정 커밋에 의존성을 가진다면), 다른 브랜치들은 feature/A 브랜치가 develop에 머지되기 전까지 해당 개선사항을 이용할 수 없습니다. 그 기간 동안 다른 피쳐 브랜치들과 feature/A 브랜치 사이의 차이는 점점 더 벌어져서, 나중에는 코드 개선 작업을 다시 진행해야 하는 수준의 상황까지 가게 됩니다.

🍶 배포 병목

진정한 비극은 이렇게 서로 다른 브랜치에서 오랜 시간 성숙하여 개발된 "덜 검증된" 코드들이 production 으로 배포되기 전에 (일반적으로 develop 이라고 부르는) 하나의 브랜치에 모여서 기다린다는 점입니다. 이 과정에서 특정 한 개의 커밋에서 버그가 발견되었다면? 그 버그 때문에, 고쳐지기 전까지 developmaster 에 머지할 수 없다면? 다른 모든 기능들은 그 커밋 하나가 해결되기만을 오매불망 기다려야 합니다.

🤯 브랜치 관리의 피로함

문제가 된 커밋을 revert 하거나, 배포해야 할 커밋을 cherry-pick 하는 방법도 있습니다. 하지만 그러려면 다같이 모여서 커밋이 어느 브랜치까지 나가 있고 무슨 커밋이 배포되어도 되는지 추적하고 골라내야 합니다. 더해서 cherry-pick 이나 revert 같은 고-급 기술들은, 특히 깃이 익숙하지 않은 주니어 개발자들에게 고급 기술을 이용한 죄로 깃을 주머니 속 이어폰 줄처럼 꼬아버리는 벌을 내리곤 합니다.





TBD 는 어떻게 이 문제를 해결하는가

앞에서 branch 라는 강력하고 좋은 기능 덕분에 코드의 버전을 만들고 업데이트하고 병합하여 배포할 수 있다고 말했습니다. 하지만 반대로 branch가 있기에 코드가 오랫동안 다른 버전으로 살아있을 수 있기도 합니다.

그렇다면 아쉽지만 코드가 오랫동안 다른 버전으로 살아있지 못하게 하기 위해 이 branch 라는 좋은 기능을 못 쓰게 막아버리면 어떨까요? 이게 TBD의 기본 아이디어입니다.

TBD 전략에서 개발자들은 trunk 라고 불리는 하나의 브랜치만을 이용합니다. 그리고 진행해야 하는 작업을 잘게 쪼개서 이 브랜치에 바로 푸시합니다.

혹시 이전 릴리즈에서 문제가 발생하여 해당 브랜치에서 핫픽스가 필요하다면, 작업하고 나서 해당 작업을 바로 trunk 에 머지해야 합니다. 이를 통해 모든 개발자들이 하나의 코드 버전에서 크게 벗어나지 않은 코드베이스에서 작업하도록 강제합니다.

컨플릭, 코드리뷰, 브랜치 관리

혹시 컨플릭이 발생하더라도, 충돌이 발생한 지 얼마 지나지 않은 시점에 소규모 작업에 대한 충돌을 해결하는 일은 아주 쉽습니다.

코드 리뷰에도 이점이 있습니다. 큰 PR은 리뷰하기 어렵지만, 작은 PR은 리뷰하기 쉽습니다.

브랜치 관리 역시 용이합니다. 우리가 해야 할 작업은 pull, push, commit, 그리고 간혹 사용해야 하는 rebase 이렇게 네 개 뿐입니다. 이 정도는 깃에 익숙하지 않은 초심자들도 쉽게 할 수 있는 작업입니다.

배포 병목

TBD의 또 하나의 중요한 규칙은, trunk 는 항상 릴리즈 가능한 상태여야 한다는 점입니다. 이를 위해 내가 개발한 코드가 production 에 배포는 되었지만 아직 유저에게 영향이 가진 않는 dark launching과 같은 기법을 사용할 줄 알아야 합니다만, 아무튼 저는 어느 상황에서도 main 에 머지한 제 코드를 배포할 수 있습니다. 다른 사람이 trunk에 최근에 개발한 기능에 문제가 있더라도, 해당 커밋은 작거나 / 작게 쪼개졌을 것이므로 revert하기 쉽습니다.





단점은 없나요?

물론 모든 전략이 그렇듯 TBD 역시 명확한 단점이 존재합니다.

feature flag 와 dark launching 은 어렵습니다.

TBD는 git 에서 편리함을 얻은 대신, 코드에서 편리함을 잃었습니다. "피쳐 플래그"를 사용하는 어플리케이션을 개발한다는 건, 하나의 코드베이스만으로 플래그를 어떻게 꽂아줬냐에 따라 여러 프로젝트가 될 수 있는 복잡한 코드를 구현해야 한다는 점입니다.

가령, git flow 에서는 아래와 같이 처리하면 되었던 단순한 문구 변경조차도

AS-ISTO-BE
return <div>내 손 안의 백화점</div>;
return <div>오늘의 특가를 만나보세요!</div>;

TBD에서는 이렇게 처리해야 합니다.

const isFlag = process.env.SOME_FLAG; // 환경변수일 수도 있고, 다른 방식일 수도 있습니다.

return <div>{isFlag ? '오늘의 특가를 만나보세요!' : '내 손 안의 백화점'}</div>;

이런 간단한 것도 복잡해지는데, 큰 변경사항이 여러 피쳐 플래그를 꼽아서 구현되어야 한다면 많이 복잡해지겠죠? 이런 복잡한 작업을 좀더 편하게 처리하기 위해서는 꽤 많은 경험과 노력이 필요한 것 같습니다.

안정성이 비교적 보장되지 않습니다.

TBD에서는 아직 충분히 검증하지 않은 코드가 아무튼 trunk에 들어가야 합니다. 피쳐플래그를 이용해서 숨겨뒀으니 괜찮다고 하기엔, 그 피쳐플래그마저도 검증되지는 않았습니다. 단순히 기분의 문제가 아니라, 위험과 불안요소가 실제로 존재합니다.

물론, 잘 생각해보면 git flow 를 사용할 때도 조금 덜하다 뿐이지 이 문제가 없지는 않았습니다.

코드 리뷰어가 맥락을 파악하기 어렵습니다.

앞서 코드 리뷰가 쉬워진다고 했지만, 단점도 있습니다. 이전에는 코끼리를 보며 코끼리를 리뷰할 수 있었다면, TBD에서는 코끼리의 코만 보고 코끼리를 리뷰해야 합니다. 때문에 더 좋은 코드 리뷰 문화를 위해 PR description 을 친절하게 작성하고, 이전 related PR들을 링크해두는 게 중요합니다. github 이 제공하는 milestone 기능이 종종 도움이 되곤 합니다.





그래서 TBD를 어떻게 이용하고 있나요?

🪵 브랜치

우선, 앞에서 열심히 떠든 것과 반대되는 의견이지만 - 저는 브랜치를 여러 개 이용합니다. 단, 아래와 같은 규칙을 적용하여 TBD의 본 목적을 해치지 않았습니다.

  1. 브랜치는 가능한 빨리 merge하여 없애버린다
  2. 모든 브랜치는 trunk 로만 merge한다
  3. PR이 300줄을 넘기지 않게 한다
  4. 머지는 merge commit 말고 squash merge 를 이용한다

이렇게 하면, TBD가 "브랜치를 하나만 써야 한다"고 했던 본 목적을 보존하면서도 github 이 제공하는 PR description, code review, 그리고 ci/cd 등 편리한 기능들을 충분히 활용할 수 있었습니다. 사실상 squash merge 는 좀더 주석이 잘 달린 커밋이나 마찬가지니까요!

🔖 태그 푸시 기반 CI/CD

고정적인 브랜치가 하나이기 때문에 branch merge 를 기준으로 CI/CD를 구성하면 dev/prod 두 환경에 배포하는 게 힘들어졌습니다. 따라서 tag push 를 기준으로 CI/CD를 구성하여, 아무 브랜치에서나 dev 형태의 태그를 푸시하여 dev 환경에 배포하고, trunk 에서는 prod 형태의 태그를 푸시하여 prod 환경에 배포할 수 있도록 했습니다.

이는 또 하나의 장점이 있는데, trunk 에 머지하기 전에 dev 환경에 현재 브랜치를 배포해보면 내 로컬보다 조금 더 믿음직한 dev 환경에서 내 변경사항이 잘 반영되었는지 / 잘 무시되었는지 (다크런칭이라면) 확인할 수 있었습니다.

⛳️ 피쳐 플래그

기능이 새로 나왔을 때, 먼저 피쳐플래그와 다크 런칭 기법을 이용할지 말지 판단합니다. 300줄 이하로 끝날 작업이라고 생각된다면 피쳐 플래그를 적용하지 않고 하나의 PR과 3~4개의 커밋으로 개발하고, 그보다 사이즈가 커질 것 같고 여러 명이 동시에 붙어야 하는 작업이라고 생각된다면 피쳐 플래그를 적용하여 여러 PR을 통해 기능을 구현합니다.

피쳐 플래그를 구현할 때는 대부분의 상황에서 환경 변수를 이용하고 있습니다. 이를 통해 아래와 같은 프로세스를 활용합니다.

  1. 환경 변수 생성 및 코드에 적용, .env.local 에 환경 변수를 설정하여 피쳐플래그 on
  2. QA 기간에 dev 환경에서 사용하는 .env.dev 에 환경 변수를 설정하여 피쳐플래그 on
  3. PROD 배포 시 prod 환경에서 사용하는 .env.prod 에 환경 변수를 설정하여 피쳐플래그 on
  4. 피쳐 플래그를 하드코딩된 true 값으로 변경
  5. 신나는 코드 제거 시간 🔥

피쳐 플래그는 기본적으로 어렵고 복잡하기 때문에, 가능하면 한 프로젝트에 여러 개의 플래그가 붙어있는 상황은 지양하는 게 좋습니다. 이는 기획자와의 적절한 핑퐁을 통해 해결 가능합니다. (가령 몇몇 기능은 출시일보다 조금 일찍, 개발되자마자 릴리즈하는 등)

🧪 테스트 코드

TBD에서는 변경이 더 자주, 더 많이 일어나기 때문에 테스트코드가 더욱 중요합니다. 테스트코드를 통해 제 변경사항이 prod 에 영향을 미치지 않는다는 것을 확인할 수 있고, 동료가 실수로 제 코드를 잘못 건드렸을 때 빠르게 인지할 수 있습니다.

특히 피쳐 플래그가 적용된 어플리케이션이라면 테스트코드가 더욱 중요하면서도 어려워집니다. 피쳐 플래그가 적용되었다면, 제가 테스트해야 하는 대상은 "피쳐 플래그가 켜진 어플리케이션" 과 "피쳐 플래그가 꺼진 어플리케이션" 이렇게 두 개입니다.

이 경우, 제가 가진 시나리오는 크게 세 가지 타입이 있습니다.

  • (A) 피쳐 플래그가 켜졌을 때만 테스트할 시나리오
  • (B) 피쳐 플래그가 꺼졌을 때만 테스트할 시나리오
  • (C) 피쳐 플래그와 무관한 시나리오

따라서 이 경우에는 어플리케이션을 피쳐플래그가 꺼진 버전과 켜진 버전 두 가지로 빌드해서, 켜진 버전에서 (A) 케이스를 돌리고, 꺼진 버전에서 BC 케이스를 돌리도록 CI/CD를 구성합니다. 이를 위해 테스트 코드의 각 시나리오에도 피쳐 플래그에 종속된 시나리오인지 아닌지에 대한 어노테이션이 필요합니다.





결론

TBD는 최대한 많은 개발자들이 거의 같은 버전을 보고 개발하도록 하는 기능입니다. 이를 통해 컨플릭을 해결하고 커밋을 관리하는 복잡하고 소모적인 시간을 최대한 줄여 주지만, 반대로 피쳐 플래그나 다크 런칭과 같은 어려운 과제들이 새로이 생기게 됩니다.

저의 경우 TBD가 주는 장점 -모두가 최신화된 코드 베이스에서 작업할 수 있다- 이 매우 크다고 생각하여 TBD를 선호하지만, TBD가 모든 상황에서 정답이진 않습니다.

항상 그렇듯 모든 전략에는 장단점이 있기에, 우리 조직의 문화나 의사결정 구조, 컨벤션, 그간의 경험, 비용 등을 종합적으로 고려하여 브랜칭 전략을 선택하는 게 중요하겠습니다.

profile
프론트엔드 개발자입니다
post-custom-banner

0개의 댓글