같은 버그를 두 번 고치고 있다

처음에는 v1만 있었다. 신규 기능을 준비하면서 UI/UX도 함께 개편하자는 이야기가 나왔고 그 작업을 v2로 진행하기로 했다.

나는 당시 입사한 지 얼마 되지 않아 제품이 어떻게 운영되는지 충분히 알지 못했다. v2가 나오면 v1을 빠르게 대체할 거라고 생각했다. 그래서 v2를 만들면서 라이브러리 버전을 올리고 코드 구조도 개선했다. 기존 코드와의 차이가 커져도 오래 함께 유지할 일은 많지 않을 거라고 본 것이다.

하지만 v2 일정은 예상보다 많이 미뤄졌다. 그동안 v1은 계속 운영됐고 버그를 고치거나 신규 기능을 추가하는 일도 이어졌다. v1에 변경이 생기면 v2에도 같은 변경이 필요한지 확인해 적용하고 있다.

결국 두 버전을 함께 유지하는 시간이 길어졌다. 한쪽에서 고친 코드를 다른 쪽에 그대로 옮길 수도 없다. 라이브러리와 코드 구조가 달라졌기 때문이다.

두 앱을 함께 관리하면 이 반복 작업을 줄일 수 있을까?

저장소를 합치면 수정할 코드도 하나가 될까?

두 버전 모두 여러 패키지를 함께 관리하는 구조는 이미 갖추고 있다. 두 앱을 한곳에서 관리하더라도 같은 역할을 하는 코드가 각각 남아 있으면 수정할 곳은 여전히 두 군데다.

아래는 차이를 보여주기 위해 단순화한 예시다. 현재 제품의 코드를 그대로 옮긴 것은 아니다.

1
2
3
4
5
// v1
const isBlank = (value) => value.trim().length === 0;

// v2
const isBlank = (value) => value.trim().length === 0;

입력 규칙이 바뀌면 두 함수를 모두 확인해야 한다. 두 버전에서 같은 규칙을 사용한다면 공통 패키지로 옮길 수 있다.

1
2
3
4
// 공통 패키지
export function isBlank(value: string): boolean {
  return value.trim().length === 0;
}

각 앱은 같은 함수를 사용하되 오류를 보여주는 방식은 자신의 흐름에 맞게 처리한다.

1
2
3
4
5
6
7
8
9
// v1: 입력란의 오류 상태를 갱신
if (isBlank(name)) {
  setNameError("이름을 입력하세요.");
}

// v2: 저장 전에 오류를 반환
if (isBlank(name)) {
  return { name: "이름을 입력하세요." };
}

이 예시에서는 입력값을 판단하는 규칙을 공유한다. 실제로 옮길 때는 두 버전의 규칙이 정말 같은지부터 확인해야 한다.

벌어진 기술 차이를 어떻게 줄일까?

v2가 v1을 곧 대체할 거라고 생각했기 때문에 라이브러리도 올리고 코드 구조도 바꿨다. 지금은 두 버전을 함께 유지해야 하므로 그 차이가 수정 작업에 영향을 준다. v1에서 원인을 찾아도 v2의 어느 부분을 고쳐야 하는지 다시 살펴볼 때가 있다.

기존 버전의 라이브러리를 새 버전에 맞추는 방안을 생각하고 있다. 다만 버전만 올리고 끝낼 수 있는 작업은 아니다. UI 라이브러리를 바꾸면 컴포넌트와 스타일을 확인해야 하고 라우팅 라이브러리를 바꾸면 화면 전환과 접근 제어를 살펴봐야 한다. 빌드 도구 변경도 개발 환경과 배포 결과에 영향을 줄 수 있다.

라이브러리 변경과 공통 코드 추출을 한꺼번에 진행하면 동작이 달라졌을 때 원인을 찾기 어렵다. 변경을 나눠 검증하면서 두 앱이 공유할 수 있는 부분을 늘리는 편이 좋겠다. v1의 모든 코드를 v2와 같은 형태로 바꾸는 것까지 목표로 삼을 필요는 없다.

어떤 코드를 함께 사용할 수 있을까?

최근 v1과 v2에 모두 적용했던 변경부터 살펴보려 한다. 코드가 비슷하게 생겼는지보다 요구사항이 바뀔 때 두 버전에서 함께 수정해야 하는지가 더 중요한 기준이다.

입력값 검증은 후보가 될 수 있다. 두 앱이 같은 입력 규칙을 쓴다면 앞의 예시처럼 판단 로직을 공유할 여지가 있다. 한쪽에서만 허용하는 값이 있다면 그 차이가 생긴 이유를 먼저 확인해야 한다.

오류 처리도 나눠 볼 수 있다. 서버 오류 코드가 무엇을 뜻하는지는 함께 해석하더라도 이후 동작은 앱마다 다를 수 있다. 어떤 흐름에서는 메시지를 보여주고 다른 흐름에서는 재인증을 요청해야 한다. 공통 패키지에 각 앱의 화면 처리까지 넣기보다 두 앱에서 함께 바뀌는 규칙을 찾는 게 우선이다.

수정이 한 곳으로 줄면 검증도 줄어들까?

공통 코드를 바꿨다면 v1과 v2에서 사용하는 흐름을 모두 확인해야 한다. 위 예시라면 isBlank의 결과가 맞는지 테스트하는 것과 별개로, 각 앱에서 오류가 적절한 시점에 표시되고 해제되는지 살펴봐야 한다.

두 앱의 관련 검사와 빌드를 실행할 수 있어야 하고 주요 사용자 흐름도 각각 확인해야 한다. 함께 관리하더라도 배포는 각 버전에 필요한 시점에 할 수 있어야 한다.

두 버전을 함께 유지한다는 전제로

처음에는 v2가 v1을 빠르게 대체할 거라고 생각했다. 그 전제에서 라이브러리를 올리고 코드 구조를 바꿨지만 지금은 v1에 들어간 버그 수정과 신규 기능을 v2에도 계속 적용하고 있다. 두 버전이 함께 유지되는 동안의 작업 비용을 고려해 관리 방식을 정해야 하는 상황이다.

현재로서는 두 앱을 모노레포에서 함께 관리하고 반복해서 수정하는 규칙부터 공통으로 사용하는 방향이 적절해 보인다. 라이브러리 통일은 별도의 이관 작업으로 나눠 진행하고 UI/UX가 달라 생기는 차이는 각 앱에 남기는 것이다. 같은 규칙은 함께 고칠 수 있게 하면서 각 버전의 동작과 배포는 따로 관리하려 한다.

아직 전환을 결정한 것은 아니다. 먼저 두 버전에서 반복해 수정했던 기능 하나를 골라 이 구조를 적용해보려 한다. 공통 구현 한 곳을 고친 뒤 두 앱에서 검증할 수 있는지 확인할 생각이다. 그 작업이 실제로 줄어든다면 통합 관리를 이어갈 근거가 된다. 모노레포 전환의 효과도 이렇게 중복 수정을 얼마나 줄였는지로 판단하려 한다.