HooneyLog
© 2026 Seunghoon Shin. All rights reserved.
모든 게시글
refactoring
2022. 5. 7.•
3

마틴 파울러의 리팩터링: 단계 쪼개기와 다형성을 이용한 계산 로직 개선

Seunghoon Shin
작성자 Seunghoon Shin풀스택 개발자

마틴 파울러의 리팩터링: 단계 쪼개기와 다형성을 이용한 계산 로직 개선

1. 문제의 배경

지난 1편에서는 스파게티처럼 얽혀있던 코드를 작은 함수들로 추출하여 가독성을 높이는 작업을 진행했습니다. 하지만 여전히 하나의 함수(statement) 안에 데이터 계산 로직과 텍스트 출력 로직이 공존하고 있었습니다.

이러한 구조는 다음과 같은 한계를 가집니다:

  • 낮은 재사용성: 만약 HTML 형식의 출력이 추가된다면 계산 로직을 복사하거나 중복 구현해야 합니다.
  • 높은 변경 리스크: 출력 형식을 수정하다가 실수로 계산 로직을 건드릴 위험이 있습니다.
  • 유연하지 못한 구조: 새로운 장르(연극)가 추가될 때마다 거대한 switch 문을 매번 수정해야 합니다.

2. 해결 방안 탐색

마틴 파울러가 제안하는 리팩터링의 핵심 전략 중 하나는 "단계 쪼개기(Split Phase)"입니다.

  1. 데이터 계산 단계: 원본 데이터를 가공하여 화면에 출력하기 적합한 중간 데이터 구조(StatementData)를 만듭니다.
  2. 포맷팅 및 출력 단계: 가공된 중간 데이터를 받아 사용자에게 보여줄 텍스트나 HTML로 변환합니다.

여기에 더해, 장르별로 상이한 요금 계산 로직은 "조건부 로직을 다형성으로 바꾸기(Replace Conditional with Polymorphism)" 기법을 적용하여 클래스 구조로 재편합니다.

3. 핵심 개념 및 아키텍처

리팩터링된 시스템은 데이터가 여러 층을 거치며 정제되는 파이프라인 구조를 가집니다.

  • createStatementData: 도메인 로직을 담당하며, 다형성 기반의 계산기를 사용하여 중간 데이터를 생성합니다.
  • PerformanceCalculator: 각 공연의 요금과 포인트를 계산하는 베이스 클래스입니다.

4. 구현 및 트러블슈팅

1단계: 중간 데이터 구조 구축

먼저 계산 로직을 분리하기 위해 enrichPerformance 함수를 통해 기존 데이터에 play, amount, volumeCredits 정보를 추가합니다.

export interface EnrichPerformance extends Performance { play: Play; amount: number; volumeCredits: number; } const createStatementData = (invoice: Invocie, plays: Plays): StatementData => { const result: StatementData = { customer: invoice.customer, performances: invoice.performances.map(enrichPerformance), }; result.totalAmount = totalAmount(result); result.totalVolumeCredits = totalVolumeCredit(result); return result; function enrichPerformance(perf: Performance): EnrichPerformance { // 계산 로직이 여기서 실행됨 const calculator = createPerformanceCalculator(perf, playFor(perf)); return { ...perf, play: calculator.play, amount: calculator.amount, volumeCredits: calculator.volumeCredits, }; } };

2단계: 다형성을 활용한 계산기 구현

장르별로 다른 요금 체계를 switch 문 대신 클래스의 상속 구조로 풀어냅니다.

class PerformanceCalculator { constructor(protected perf: Performance, public play: Play) {} get volumeCredits() { return Math.max(this.perf.audience - 30, 0); } } class TragedyCalculator extends PerformanceCalculator { get amount() { let result = 40000; if (this.perf.audience > 30) { result += 1000 * (this.perf.audience - 30); } return result; } } class ComedyCalculator extends PerformanceCalculator { get amount() { let result = 30000; if (this.perf.audience > 20) { result += 10000 + 500 * (this.perf.audience - 20); } result += 300 * this.perf.audience; return result; } get volumeCredits() { return super.volumeCredits + Math.floor(this.perf.audience / 5); } }

팩토리 함수를 통해 적절한 계산기 객체를 생성함으로써, 호출하는 쪽에서는 구체적인 클래스 타입을 알 필요 없이 공통된 인터페이스(amount, volumeCredits)를 사용할 수 있게 됩니다.

5. 결과 및 Trade-off

결과

  • 가독성 향상: 최상위 statement 함수는 단 한 줄의 코드로 로직의 흐름을 보여줍니다.
  • 유지보수 용이성: 새로운 출력 형식(HTML)이 필요하다면 renderHtml 함수만 새로 만들면 됩니다.
  • 확장성: 새로운 장르가 추가되면 새로운 서브클래스 하나만 정의하면 되며, 기존 코드를 수정할 필요가 없습니다(OCP 준수).

Trade-off

  • 코드량 증가: 클래스와 인터페이스 정의로 인해 전체적인 코드 라인 수는 늘어났습니다.
  • 복잡도: 단순한 로직의 경우 오히려 과한 설계(Over-engineering)가 될 수 있으므로, 프로젝트의 규모와 변경 빈도를 고려해야 합니다.

6. 마치며

리팩터링은 단순히 코드를 예쁘게 정리하는 것이 아닙니다. "어떻게 하면 다음 변경 사항이 왔을 때 가장 적은 비용으로 안전하게 수정할 수 있을까?"라는 질문에 답하는 과정입니다.

단계 쪼개기와 다형성은 이러한 질문에 대한 강력한 해결책을 제공합니다. 이제 여러분의 프로젝트에서도 거대한 switch 문이나 거대한 단일 함수가 보인다면, 오늘 다룬 기법들을 적용해보시길 바랍니다.

참고 자료:

  • 리팩터링 2판 (마틴 파울러 저)
  • Refactoring.guru: Replace Conditional with Polymorphism
← 이전 글마틴파울러의 리팩터링 개정 2판과 함께 클린코드 잘짜는법 배워보자!!(1/2)
다음 글 →display:grid 사용시 text가 nowrap이 안되는 이슈 해결 방법