개발 slecs

결제 송금 알림 메시지 빌더를 분리해 가독성 개선

목차

연락처 송금 메시지 유틸이 어느 순간부터 감당이 안 되기 시작했다. 결제 플랫폼에서 파트너끼리 잔액을 옮길 때 알림 문구를 만드는 부분인데, 처음엔 단순했을 코드가 2년치 요구사항을 흡수하면서 한 메서드 안에 분기가 켜켜이 쌓였다.

구체적으로는 이런 상태였다:

  • 송금 성공/실패/대기/취소 4종 분기
  • 파트너 등급별 호칭 표기 차이
  • 결제대행사 결과코드에 따른 문구 분기
  • 다국어 금액 포맷 분기

새 메시지 한 줄 추가하려면 200라인짜리 메서드를 위에서 아래로 다 읽어야 했다. 후배가 PR 리뷰에서 "이거 어디 끼워야 됨?" 하고 물어보길래 갈아엎기로 했다. 사실 나도 그 메서드 전체를 한 번에 머릿속에 못 담고 있었으니까.

분리 기준 잡기

리팩터링할 때 제일 먼저 해야 할 일이 "무엇을 기준으로 자를 건지" 정하는 거다. 기준 없이 손대면 분리가 또 다른 복잡함을 낳는다.

이번엔 "메시지 종류""포맷팅" 으로 나누는 게 자연스러웠다. 메시지 타입은 도메인 의미를 갖고, 포맷팅은 출력 형태를 다루는 일이라 책임이 다르다. 같은 메서드 안에서 이 둘을 동시에 처리하니까 변경이 생길 때마다 서로 영향을 줬던 거다.

타입별로 빌더를 따로 두고, 공통 컨텍스트만 주입받도록 구조를 바꿨다.

// Before
buildMessage(type, status, partner, result) {
  if (type == "SEND")      { ... 50줄 ... }
  else if (type == "RECV") { ... 50줄 ... }
  else if ...
  // 결제대행사 결과코드 분기, 금액 포맷 분기 다 여기에 섞임
}

// After
MessageBuilder.of(type).render(ctx)
// 각 타입별 빌더가 render() 만 구현
// ctx 에 파트너 정보, 결과코드, 금액 포맷터 주입

결과를 비교하면:

항목 Before After
메서드 길이 200+ 평균 30
신규 타입 추가 분기 추가 빌더 신설만
단위 테스트 거대 fixture 1개 빌더별 분리
가독성 스크롤 무한 한 화면

숫자로 보면 단순한데, 실제로 빌더별로 테스트가 분리되니까 신규 타입을 추가할 때 기존 빌더에 사이드이펙트가 생겼는지 빠르게 잡을 수 있게 됐다. 이게 체감상 제일 크다. 거대한 fixture 하나에 전체 케이스를 때려박아놓으면 어디서 터진 건지 파악하는 것만도 한참 걸렸는데, 빌더별로 쪼개놓으니 실패 지점이 바로 보인다.

삽질한 것들

enum 에 템플릿 박으려다 포기. 처음엔 타입을 enum 으로 두고 각 상수에 메시지 템플릿 메서드를 달아보려 했다. 종종 쓰는 패턴인데, 결제대행사별 결과코드가 런타임에 결정되는 값이라 상수로 표현하기가 어려웠다. 억지로 넣으면 enum 이 외부 의존성을 들고 다니는 구조가 되어서 포기하고 빌더로 우회했다. 이런 경우 enum 은 타입 식별자 역할만 남기고 로직은 별도 클래스로 빼는 게 낫다.

금액 포맷 분리. 파트너 지역에 따라 금액 표기 방식이 달랐다. 이건 빌더 안에서 처리하지 않고 별도 Formatter 로 뺐다. 빌더가 Formatter 를 주입받아서 쓰는 구조. 나중에 지역이 추가되더라도 Formatter 만 건드리면 된다. 포맷팅처럼 "변화 이유가 다른 코드"는 일찍 분리해두는 게 결국 더 편하다.

호출부가 30곳 넘어서 한 번에 못 바꿨다. 기존 시그니처를 어댑터로 남겨두고 호출부를 한 주에 5~6개씩 점진 전환했다. 어댑터가 남아 있는 동안엔 "이 메서드 쓰지 마시오" 주석을 달아뒀는데, 그래도 중간에 한 번 더 쓴 케이스가 나왔다. 주석보다 @Deprecated 같은 언어 레벨 마킹이 확실하다는 걸 다시 확인했다. IDE 가 노란 줄 그어주는 게 주석 몇 줄보다 훨씬 눈에 띈다.


리팩터링 자체보다 "왜 200줄이 됐는지" 추적하는 데 더 시간이 걸렸다. 커밋 로그를 따라가 보니 메시지 한 줄 추가가 2년치 쌓인 결과였다. 처음 짠 사람이 잘못한 게 아니다. 매번 손쉽게 if 한 줄 더 넣을 수 있는 구조가 문제였다. 복잡도가 쌓이는 건 대부분 한 번의 나쁜 결정이 아니라, 작은 결정들의 누적이다. 각자 맥락이 있었고, 당시엔 합리적이었을 선택들이다.

검증은 빌더별 출력 스냅샷을 떠서 비교했고, 스테이징에서 실제 송금 시나리오 4종을 돌렸다. 메시지 문구 자체가 바뀌면 안 되는 작업이라 디프 0 을 목표로 잡고 끝까지 맞췄다. 이런 류의 리팩터링엔 스냅샷 테스트가 특히 유용하다. 기대값을 사람이 직접 쓰지 않아도 되고, 뭔가 바뀌면 테스트가 즉시 잡아준다. 문구가 실수로 달라지는 것도 걸러주고, "나 건드린 거 없는데 왜 다름?" 하는 상황도 방지된다.

다음엔 분기가 5개 넘어가는 시점에 미리 잘라낼 것. 200줄이 되고 나서 손대는 것보다 50줄일 때 자르는 게 훨씬 낫다. 당연한 말이지만, 이번에 직접 증명했다.

댓글 0

첫 댓글 달아줘.