Files
2nd/10_Wiki/Topics/Architecture/Refactoring (리팩토링).md
T

11 KiB

Refactoring (리팩토링)

📌 Brief Summary

Refactoring(리팩토링)은 소프트웨어의 외부적인 동작(observable behavior)을 변경하지 않으면서 내부 구조를 개선하여 코드를 더 이해하기 쉽고 수정하기 경제적으로 만드는 일련의 과정이다 [1-3]. 이는 소프트웨어 아키텍처의 부패를 방지하고 기술 부채(Technical Debt)를 체계적으로 상환하기 위한 전략적 도구이다 [3, 4]. 마틴 파울러(Martin Fowler)에 의해 대중화되었으며, 지속적인 리팩토링은 유지보수성을 높이고 버그 발견을 용이하게 하며 장기적인 개발 속도를 가속화하는 핵심 규율로 평가받는다 [5, 6].

📖 Core Content

  • 리팩토링의 경제성과 목적: 리팩토링의 근본적인 목적은 코드의 심미적 아름다움이 아닌 '경제성'에 있다 [6, 7]. 클린 코드는 인간의 인지 부하를 줄여 시스템을 디버깅하고 새로운 기능을 추가하는 데 걸리는 시간을 크게 단축시킨다 [6]. 장기적으로 리팩토링이 결핍된 시스템은 기술 부채가 쌓여 생산성이 하락하므로, 리팩토링은 유지보수 비용을 낮추고 비즈니스 가치 전달 속도를 높이는 필수적인 경제 활동이다 [4, 8].

  • 핵심 원칙: 두 개의 모자 (The Two Hats): 개발자는 프로그래밍 과정에서 '기능 추가'와 '리팩토링'이라는 두 가지 작업을 명확히 분리하여 수행해야 한다 [6, 9]. 기능 추가 모자를 썼을 때는 기존 코드를 건드리지 않고 새로운 동작 구현에 집중하며, 리팩토링 모자를 썼을 때는 새로운 기능을 추가하지 않고 오직 코드 구조 개선에만 전념해야 한다 [6, 9]. 이를 섞어서 수행할 경우 디버깅 효율이 급격히 저하된다 [6].

  • 리팩토링의 타이밍: 3의 법칙 (Rule of Three) 및 적기: 마틴 파울러와 돈 로버츠가 제안한 '3의 법칙'에 따르면, 처음 구현할 때는 일단 작성하고, 두 번째 비슷한 작업을 할 때는 중복을 감수하더라도 진행하며, 세 번째 동일한 작업을 하게 될 때 비로소 리팩토링을 수행해야 한다 [10, 11]. 또한, 리팩토링은 별도의 시간을 내서 하는 것이 아니라, 기능 추가 전 준비 과정(Preparatory), 코드를 이해하기 위한 과정(Comprehension), 코드 리뷰 과정 등 일상적인 개발 흐름(Opportunistic) 속에서 지속적으로 수행되어야 한다 [12-14].

  • 코드 스멜 (Code Smells): 시스템에 깊은 설계적 문제가 있음을 암시하는 표면적인 증상들을 '코드 스멜'이라고 부른다 [15, 16]. 주요 스멜로는 동일한 로직이 산재한 '중복 코드(Duplicated Code)', 인지 부하를 높이는 '긴 함수(Long Method)', 다른 클래스의 데이터에 과도하게 의존하는 '기능 욕심(Feature Envy)' 등이 있으며, 이러한 징후를 식별하는 것은 리팩토링을 적용하는 핵심 기준이 된다 [16-18].

  • 안전망으로서의 테스트: 리팩토링 과정에서 외부 동작이 변하지 않았음을 보장하기 위해서는 견고한 자동화 테스트(Automated Tests) 스위트가 필수적이다 [19-21]. 단위 테스트(Unit Tests)를 기반으로 작은 단위의 변환을 적용하고, 매 단계마다 즉시 테스트를 실행하여 결함을 탐지하는 과정을 반복해야 안전한 구조 변경이 가능하다 [19, 21, 22].

⚖️ Trade-offs & Caveats

  • 테스트 부재 시의 높은 위험성: 견고한 테스트 코드가 없는 환경에서의 리팩토링은 매우 위험한 코드 수정에 불과하며, 예상치 못한 회귀 버그(Regression Bug)를 발생시킬 확률이 높다 [21, 23]. 거대한 레거시 시스템의 경우 의존성을 끊고 접점(Seam)을 만들어 테스트를 끼워넣기 전까지는 코드를 섣불리 재구조화할 수 없다 [24].
  • 성능과의 상충 관계 (Performance Trade-off): 리팩토링은 코드를 분리하고 이해하기 쉽게 만드는 과정에서 간접 호출(Indirection)을 늘리거나 반복문을 여러 번 실행하게 만들어 단기적으로 소프트웨어의 실행 속도를 늦출 수 있다 [25, 26]. 하지만 리팩토링으로 잘 정돈된 코드는 성능 병목(Hot spot)을 파악하고 최적화(Performance Tuning)하기 훨씬 용이해지므로 장기적으로는 더 빠른 소프트웨어 구축에 유리하다 [26-28].
  • 단기 일정 및 자원 제약: 빡빡한 마감일(Tight deadlines)이 임박한 경우, 리팩토링으로 얻는 생산성 향상 이점보다 일정 지연으로 인한 손실이 크므로 리팩토링을 유보해야 한다 [29, 30]. 또한 기능 구현에만 집중하는 경영진에게 리팩토링은 부가적인 오버헤드 활동으로 인식될 수 있어 이를 설득해야 하는 조직적 과제가 따른다 [8, 31].
  • 데이터베이스 및 공개된 인터페이스의 제약: 비즈니스 애플리케이션이 데이터베이스 스키마와 강하게 결합된 경우, 데이터 마이그레이션의 어려움 때문에 리팩토링 진행이 매우 까다로울 수 있다 [32]. 또한 이미 발행된 인터페이스(Published Interfaces)를 변경하는 리팩토링은 해당 인터페이스를 사용하는 클라이언트 코드의 동시 수정을 강제하거나, 전환 기간 동안 구형 인터페이스를 병행 유지해야 하는 부담을 발생시킨다 [23, 33, 34].

🔗 Knowledge Connections

[코드 품질 및 검증 (Code Quality & Verification)]

  • Code Smells
    • 연결 이유: 리팩토링이 필요한 시점과 부위를 식별하는 핵심 진단 도구이기 때문이다.
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 긴 함수, 거대 클래스, 기능 욕심 등 구체적인 코드의 악취를 통해 어떤 리팩토링 기법(예: 함수 추출, 메서드 이동)을 적용해야 할지 결정하는 원리를 이해할 수 있다.
  • Test-Driven Development (TDD)
    • 연결 이유: 리팩토링의 안전성을 보장하는 핵심 실천법인 Red-Green-Refactor 사이클의 기반이 되기 때문이다.
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능을 먼저 통과시킨 후(Green) 구조를 개선(Refactor)하는 반복적인 흐름과, 자가 테스트 코드(Self-Testing Code)가 어떻게 리팩토링의 심리적 안정감을 제공하는지 파악할 수 있다.

[설계 및 실행 원칙 (Design & Execution Principles)]

  • The Two Hats
    • 연결 이유: 리팩토링을 기능 개발과 혼동하지 않고 훈련된 방식으로 수행하기 위한 필수 심리/절차적 원칙이기 때문이다.
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능 추가와 구조 개선을 명확히 분리함으로써 디버깅 효율을 높이고 버그 추적을 명확히 하는 리팩토링의 체계적인 작업 흐름을 이해할 수 있다.
  • Rule of Three
    • 연결 이유: 실무에서 지나치게 성급한 추상화를 방지하고 리팩토링을 수행해야 할 적기를 판단하는 경험 법칙이기 때문이다.
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 중복 코드가 3번 발생했을 때 리팩토링을 수행함으로써 오버 엔지니어링을 피하고 적절한 시점에 유지보수성을 높이는 판단 기준을 학습할 수 있다.
  • Technical Debt
    • 연결 이유: 리팩토링을 수행해야 하는 비즈니스 및 경제적 정당성을 제공하는 근본 개념이기 때문이다.
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 단기적인 개발 속도를 위해 타협한 더러운 코드(Dirty Code)가 어떻게 유지보수 비용을 기하급수적으로 증가시키며, 리팩토링이 이를 상환하는 과정인지 이해할 수 있다.

Deeper Research Questions

  • 테스트가 전혀 존재하지 않는 레거시 시스템에서 테스트 하네스를 도입하기 위해 접점(Seam)을 찾아내고 의존성을 안전하게 끊는 절차는 무엇인가?
  • 마이크로소프트 윈도우 7 분석 사례에서 나타난 것처럼, 리팩토링이 모듈 간의 결합도(Dependencies)와 출시 후 결함률(Post-release Defects)을 낮추는 구체적인 메커니즘은 무엇인가?
  • AI 기반 코드 생성 및 자동 리팩토링 도구가 도입되었을 때, 숙련된 시니어 개발자들에게 생산성 하락(AI Productivity Paradox)이 발생하는 인지적 원인은 무엇인가?
  • 마틴 파울러가 제시한 70여 가지의 리팩토링 기법 중, 가장 광범위하게 쓰이는 '함수 추출하기(Extract Method)'를 수행할 때 지역 변수(Local Variables)를 안전하게 처리하는 단계적 방법은 무엇인가?
  • '초기 완벽한 설계(Big Design Up Front)'를 지양하고 리팩토링을 통한 '진화적 설계(Evolutionary Design)'를 채택할 때 요구되는 소프트웨어 아키텍처 측면의 준비 사항은 무엇인가?

Practical Application Contexts

  • Implementation: 코딩 중 복잡한 조건문이나 10~20줄이 넘어가는 긴 함수를 발견하면, IDE의 자동화 기능(예: Extract Method)을 즉각적으로 활용하여 메서드를 추출하고 목적을 드러내는 명확한 이름을 부여함.
  • System Design: 프로젝트 초기에 유연성을 위한 복잡한 추상화를 무리하게 구축하기보다, 현재 요구사항에 맞춰 가장 단순한 설계를 구현한 뒤 향후 변경이 필요할 때 리팩토링을 통해 유연한 구조로 점진적으로 진화시킴.
  • Operation / Maintenance: 기능 추가나 버그 수정을 위해 기존 코드를 살펴볼 때, 먼저 이해하기 쉽도록 구조나 이름을 변경하는 이해를 위한 리팩토링(Comprehension Refactoring)을 수행한 후 실제 수정 작업을 진행함.
  • Learning Path: TDD 사이클 및 단위 테스트 작성법을 우선 숙지한 다음, 다양한 코드 스멜(Code Smells)을 인지하는 훈련을 하고, 카탈로그에 있는 구체적인 리팩토링 기법들을 하나씩 실습하며 체화함.
  • My Project Relevance: 유지보수하고 있는 복잡한 레거시 코드베이스에서 기능 추가에 앞서, 의존성을 끊고 보호 구문(Guard Clauses) 등을 추가하는 준비적 리팩토링을 통해 기술 부채를 지속적으로 줄여나감.

Adjacent Topics

  • Legacy Code
    • 확장 방향: 마이클 페더스의 "레거시 코드는 테스트가 없는 코드다"라는 정의를 바탕으로, 스파우트 메서드(Sprout Method)나 접점(Seams) 기법을 활용하여 기존 코드를 깨뜨리지 않고 레거시 시스템을 점진적으로 현대화하는 방법을 탐구함.
  • Performance Optimization
    • 확장 방향: 리팩토링을 통해 코드를 분리하고 가독성을 확보한 후, 프로파일러(Profiler)를 사용하여 발견된 병목 지점(Hot Spots)의 리소스 사용량(시간, 메모리 등)을 중점적으로 개선하는 최적화 프로세스와의 차이점 및 시너지 방안을 조사함.

Last updated: 2026-05-03