[Clean Code] 23장. 리팩토링과 레거시 코드 개선

테스트 없는 코드를 레거시로 정의하고, 특성화 테스트로 안전망을 세운 뒤 보이스카우트 규칙에 따라 점진적으로 개선하는 전략을 시리즈 전체를 통합하는 관점에서 다룬다. 전면 재작성의 위험과 스트랭글러 무화과 패턴도 함께 살펴본다.

이 장을 읽기 전에

이 장은 이 시리즈 전체에서 다룬 원칙(네이밍, 함수, 클래스, 테스트, 시스템 설계)을 실제 레거시 코드에 종합적으로 적용하는 마지막 장이다. 16~17장에서 다룬 테스트 작성 경험이 있으면 특성화 테스트 개념을 이해하기 쉽다.

수준읽을 부분핵심 목표
입문자“레거시 코드란 무엇인가"부터 “보이스카우트 규칙"까지안전망 없이 리팩토링하면 왜 위험한지 이해한다
실무자“판단 기준”, “비판적 시각”전면 재작성과 점진적 개선 중 어느 쪽이 프로젝트에 맞는지 판단한다

레거시 코드란 무엇인가

Michael Feathers는 『Working Effectively with Legacy Code』(2004)에서 레거시 코드를 “테스트가 없는 코드"로 정의했다. 오래됐거나 기술이 낡았다는 것이 레거시의 본질이 아니라, 안전하게 변경할 수 있다는 확신을 주는 자동화된 검증이 없다는 것이 본질이라는 뜻이다. 이 정의를 받아들이면, 어제 작성된 코드도 테스트가 없다면 이미 레거시이고, 10년 된 코드도 탄탄한 테스트 스위트가 있다면 안전하게 다룰 수 있는 자산이다.

안전망 먼저: 특성화 테스트

테스트가 없는 코드를 리팩토링하려면, 먼저 그 코드가 현재 실제로 어떻게 동작하는지를 기록하는 테스트부터 작성해야 한다. 이를 **특성화 테스트(Characterization Test)**라 부른다. 특성화 테스트는 “이 코드가 올바르게 동작하는가"를 검증하는 것이 아니라 “이 코드가 지금 무엇을 하는가"를 있는 그대로 기록하는 것이 목적이라는 점에서 일반적인 단위 테스트와 다르다.

1
2
3
4
5
6
7
8
9
// 특성화 테스트: 레거시 계산 로직의 현재 동작을 그대로 기록한다
// (버그처럼 보이는 동작이라도, 리팩토링 전까지는 "현재 동작"으로 우선 고정한다)
@Test
void legacyDiscountCalculation_currentBehavior() {
    LegacyPricing pricing = new LegacyPricing();
    // 음수 수량을 넣으면 할인이 두 배로 계산되는 버그성 동작이 있지만,
    // 리팩토링 전에는 이 동작 자체를 먼저 고정해 회귀를 막는다
    assertEquals(-20.0, pricing.calculateDiscount(-10, 0.1));
}

이렇게 현재 동작을 특성화 테스트로 고정한 뒤에야, 안전하게 내부 구조를 리팩토링할 수 있는 최소한의 안전망이 생긴다. 버그로 보이는 동작을 고칠지 말지는 별도의 의사결정이며, 리팩토링(구조 변경)과 동작 변경(버그 수정, 기능 추가)은 항상 분리된 단계로 진행해야 한다 — 이는 02장에서 다룬 “리팩토링과 기능 추가를 한 커밋에 섞지 말라"는 원칙의 대규모 버전이다.

점진적 개선과 보이스카우트 규칙

안전망이 갖춰지면, 코드를 한 번에 완벽하게 뜯어고치려 하지 말고 작은 단계로 나눠 개선한다. 각 단계마다 테스트를 실행해 시스템이 여전히 이전과 같은 동작을 유지하는지 확인한 뒤 다음 단계로 넘어간다. Robert C. Martin이 자주 인용하는 보이스카우트 규칙(Boy Scout Rule)—“캠핑장은 처음 왔을 때보다 더 깨끗하게 정리하고 떠난다"는 미국 보이스카우트의 규율—을 코드에 적용하면, “체크아웃한 코드는 항상 처음보다 조금 더 깨끗하게 만들어 커밋한다"는 습관이 된다. 큰 개선을 한 번에 밀어붙이는 대신, 코드를 만질 때마다 변수 이름 하나, 함수 하나씩 조금씩 정리하는 이 접근은 대규모 “리팩토링 프로젝트"를 별도로 계획하지 않고도 코드베이스 품질을 꾸준히 끌어올린다.

Martin Fowler의 『Refactoring』(2018)이 카탈로그화한 코드 냄새(Code Smell)—긴 함수, 큰 클래스, 기능의 시기심(Feature Envy), 산탄총 수술(Shotgun Surgery) 등—는 “지금 리팩토링이 필요한 지점"을 식별하는 실무적인 체크리스트 역할을 한다. 이 시리즈에서 다룬 대부분의 원칙 위반(긴 함수, God Class, 기차 충돌)은 사실 코드 냄새 카탈로그의 항목들과 정확히 대응된다.

대규모 시스템의 점진적 교체: 스트랭글러 무화과 패턴

클래스 하나, 함수 하나 단위의 리팩토링을 넘어, 오래된 시스템 전체를 새 아키텍처로 옮겨야 할 때도 있다. 이때 전체를 멈추고 처음부터 다시 만드는 대신, Martin Fowler가 이름 붙인 **스트랭글러 무화과 패턴(Strangler Fig Pattern)**을 적용할 수 있다. 이 패턴은 무화과 나무를 감아 자라다가 결국 숙주 나무를 대체하는 스트랭글러 무화과의 생태에서 이름을 따왔다 — 새 시스템이 기존 시스템의 기능을 하나씩 감싸며 대체하고, 결국 기존 시스템이 완전히 사라질 때까지 두 시스템이 공존하며 트래픽을 점진적으로 이전한다.

흔한 오개념

**“리팩토링은 새 기능 개발이 다 끝난 뒤 별도 일정으로 진행하는 것이다”**는 오해가 흔하다. 실제로 리팩토링을 “나중에 한꺼번에” 미루는 접근은 정확히 01장에서 다룬 르블랑의 법칙(“나중은 결코 오지 않는다”)의 함정에 빠진다. 보이스카우트 규칙이 강조하듯, 지속 가능한 방식은 기능 개발과 함께 조금씩, 지속적으로 정리하는 것이다.

**“오래된 코드는 전면 재작성하는 것이 항상 더 낫다”**는 오해도 위험하다. Joel Spolsky는 2000년 에세이 “Things You Should Never Do, Part I"에서, 겉보기에 지저분한 레거시 코드에는 수년간의 버그 수정과 예외 처리 경험이 암묵적으로 녹아 있으며, 전면 재작성은 이 축적된 지식을 모두 버리고 이미 해결됐던 엣지 케이스들을 처음부터 다시 발견해야 하는 위험한 선택이라고 지적했다. 그가 예로 든 Netscape의 전면 재작성 시도는 수년간 신규 기능 출시를 멈추게 만들었고, 그 사이 경쟁자가 시장을 잠식하는 결과로 이어졌다.

판단 기준: 점진적 개선 vs 전면 재작성

코드베이스의 핵심 로직이 여전히 유효한 비즈니스 규칙을 담고 있고, 문제가 “구조가 지저분하다"는 수준이라면 특성화 테스트와 점진적 리팩토링으로 충분하다. 반면 기반 기술 자체가 더 이상 유지보수되지 않거나(단종된 프레임워크, 지원 종료된 언어 버전), 핵심 아키텍처 가정 자체가 현재 요구사항과 근본적으로 맞지 않는다면(예: 단일 테넌트로 설계된 시스템을 멀티 테넌트로 바꿔야 하는 경우) 스트랭글러 패턴을 통한 계획적 교체를 고려할 시점이다. 두 경우 모두 “한 번에 전부 바꾼다"는 선택지는 이 시리즈 전체에서 반복해서 강조한 원칙—작게, 검증 가능하게, 단계적으로—과 정면으로 배치된다.

비판적 시각

“리팩토링"이라는 이름은 때로 실질적인 효용 없이 코드 스타일을 재작성하는 작업을 정당화하는 데 남용되기도 한다. 비즈니스 관점에서 리팩토링은 사용자에게 보이는 새로운 가치를 만들어내지 않으므로, 리팩토링에 들이는 시간과 신규 기능 개발에 들이는 시간 사이의 균형은 항상 논쟁적이다. 이 시리즈가 강조한 “보이스카우트 규칙” 방식—작은 단위로 꾸준히 정리하는 방식—이 이 논쟁에 대한 실무적 절충안으로 널리 받아들여지는 이유가 여기에 있다. 별도의 “리팩토링 스프린트"를 요청해 별도 승인을 받아야 하는 큰 단위 대신, 매번 코드를 만질 때 아주 조금씩 개선하면 이 트레이드오프 자체가 거의 드러나지 않을 정도로 작아진다.

시리즈를 마치며

이 시리즈는 01장의 “왜 나쁜 코드가 비싼가"라는 질문에서 시작해, 이름 하나(03장)부터 함수(05장), 클래스(18장), 시스템(20장), 그리고 이 장의 레거시 코드 개선까지 점점 더 큰 단위로 같은 질문—“이 코드가 다음 사람에게 얼마나 적은 인지 부하를 요구하는가”—을 반복해서 던졌다. 각 장에서 다룬 원칙은 서로 독립적인 규칙의 목록이 아니라, 하나의 관점을 여러 크기의 코드 단위에 반복 적용한 결과다. 완주한 지금, 다음 실전 코드 리뷰에서 “이 코드는 어떤 원칙을 어느 정도로 적용해야 하는가"라는 질문에 이 시리즈에서 다룬 판단 기준으로 답할 수 있다면, 이 시리즈의 목표는 달성된 것이다.

평가 기준

  • 레거시 코드를 “오래된 코드"가 아니라 “테스트 없는 코드"로 정의할 수 있다.
  • 특성화 테스트로 기존 동작을 고정한 뒤 리팩토링과 동작 변경을 분리하는 절차를 설명할 수 있다.
  • 보이스카우트 규칙에 따라 점진적 개선을 일상 습관으로 적용할 수 있다.
  • 전면 재작성이 위험한 이유를 실제 사례(Netscape)로 설명하고, 스트랭글러 패턴과의 차이를 논증할 수 있다.

참고 및 출처

  • Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall. 14, 17장.
  • Feathers, M. (2004). Working Effectively with Legacy Code. Prentice Hall.
  • Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd ed.). Addison-Wesley.
  • Spolsky, J. Things You Should Never Do, Part I. https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/