이 글에서 얻는 것

  • 레거시를 “갈아엎기”가 아니라 리스크를 통제하며 개선하는 작업으로 바라보는 관점을 얻습니다.
  • 우선순위를 비즈니스 임팩트/위험/빈도 기준으로 잡고, 작은 배포 단위로 쪼개는 전략을 설계할 수 있습니다.
  • Strangler, Branch by Abstraction, Characterization Test 같은 실무 패턴을 어떤 상황에 쓰는지 감각이 생깁니다.

0) 레거시란 ‘나쁜 코드’가 아니라 ‘바꾸기 어려운 코드’다

레거시는 보통 이렇게 정의하는 편이 실무적입니다.

  • 테스트가 부족하고
  • 의존성이 얽혀 있고
  • 변경하면 어디가 깨질지 예측이 어렵다

즉 “품질”보다 “변경 비용”이 문제입니다. 그래서 레거시 개선의 핵심은 항상 안전망 → 점진 변경입니다.

1) 우선순위 잡는 법(현실적인 기준)

모든 걸 고칠 수는 없으니, 다음 기준으로 고르면 실패 확률이 줄어듭니다.

  • 사용자/비즈니스 임팩트가 큰가(매출/핵심 플로우)
  • 장애/온콜을 자주 유발하는가(운영 비용)
  • 변경 빈도가 높은가(앞으로도 자주 건드리는 곳)
  • 성능 병목인가(스케일 비용과 직결)

“핫 패스 + 자주 깨지는 곳”이 1순위입니다.

2) 안전망 만들기: 테스트만이 전부는 아니다

레거시에서 가장 먼저 해야 할 건 “안전망”입니다.

  • Characterization test: 현재 동작을 그대로 고정(좋든 나쁘든)해서 회귀를 막음
  • Golden master: 입력/출력 스냅샷으로 동작을 고정(특히 배치/리포트)
  • 관측성: 로그/메트릭/트레이스를 개선해 “어디서 깨지는지”를 빨리 찾게 함

테스트가 당장 어렵다면, 관측성을 먼저 강화하는 것도 좋은 첫 단계입니다.

3) 점진 개선 패턴(자주 쓰는 것들)

3-1) Strangler(점진 대체)

새 코드를 옆에 붙이고, 트래픽/기능을 조금씩 새 코드로 옮깁니다.

  • 장점: 롤백이 쉽고, 리스크를 작게 유지 가능
  • 단점: 한동안 두 시스템을 함께 운영해야 함

3-2) Branch by Abstraction

추상화(인터페이스)를 먼저 만들고, 구현을 교체합니다.

  • 기존 코드 호출 지점을 한 번에 바꾸기 어렵거나,
  • 큰 구현체를 안전하게 교체해야 할 때 유용합니다.

3-3) Seams(틈 만들기)

테스트/교체가 가능하도록 경계를 만들고(의존성 주입, 어댑터), 그 안쪽을 점진적으로 정리합니다.

4) 큰 변경을 작은 배포로 쪼개는 기술

레거시 개선이 실패하는 가장 흔한 이유는 “너무 큰 PR/너무 긴 브랜치”입니다.

  • 피처 플래그로 기능을 꺼둔 채로 코드를 먼저 배포(“배포”와 “릴리스” 분리)
  • DB 변경은 expand/contract로 쪼개기
  • 리팩터링은 ‘동작 변경’과 분리해 작은 단위로 합치기

5) 흔한 함정

  • “전면 재작성”을 선택해, 기존 도메인 지식/엣지 케이스를 잃는다
  • 테스트 없이 리팩터링을 시작해, 회귀를 감지하지 못한다
  • 운영 지표 없이 개선해서 “좋아진 건지” 판단이 불가능하다

연습(추천)

  • 레거시 코드에서 “가장 자주 장애가 나는 경로”를 하나 골라, Characterization test 5개로 동작을 고정해보기
  • Branch by Abstraction을 적용해, 큰 의존성(외부 API/스토리지)을 인터페이스 뒤로 숨긴 뒤 구현을 교체해보기
  • 피처 플래그를 도입해 “코드는 배포됐지만 기능은 꺼진 상태”로 배포해보고, 안전한 릴리스 루프를 경험해보기