Santos의 개발블로그

Refactoring (4) 본문

Refactoring (4)

Santos 2020. 12. 15. 01:13

* 마틴 파울러님의 Refactoring, Chapter 별로 내용을 다룹니다. Chapter1 리팩터링: 첫 번째 예시 의 관한 글입니다.  


* Refactoring (3)에서 다루지 못한 내용을 계속해서 다룹니다. Refactoring (3) 의 내용이 궁금하시다면 여기를 클릭해주세요.

* 관련 소스는 github.com/SangchoKim/refactoring/tree/refactoringForth에 있습니다.

 

Refactoring(3)에서는 다음과 같은 내용(빨간색)을 다루었습니다. 

 

1. format 변수 제거하기 

2. volumeCredits 변수 제거하기 

3. 계산 단계와 포맷팅(html로 표현) 분리하기 

4. 다형성을 활용해 계산 코드 재구성하기

#8 다형성을 활용해 계산 코드 재구성하기

 

"만약 연극 장르가 추가된다면?"

"만약 연극 공연료와 적립 포인트 계산법을 다르게 지정해야 한다면?" 

 

 

 수정 또는 추가가 되는 부분은 모든 프로그램에서 빈번하게 일어나며, 어떻게 코드를 보안하느냐에 따라 골칫거리로 전락할지, 모범적으로 변할지가 결정이 납니다. 

 

현재 상태에서 코드를 변경하려면 이 계산을 수행하는 함수에서 조건문을 수정해야합니다. 이런 상황에서는 객체지향의 핵심 특성인 "다형성"을 활용하는 것이 가장 자연스럽습니다. 

이번 작업의 목표는 "상속 계층"을 구성해서 "희극 서브클래스""비극 서브클래스"가 각자의 구체적인 계산 로직을 정의하는 것입니다. 호출을 하는 쪽에서 다형성 버전의 공연료 계산 함수를 호출하고, 연극 장르에 따른 계산 로직을 연결하는 작업은 "다형성"을 통해 구현할 것입니다. 이를 필자는 "조건부 로직을 다형성으로 바꾸기"로 명명하였습니다.

 

작업 순서는 다음과 같습니다. 

 

1. 공연료 계산기 만들기 

2. 함수들을 계산기로 옮기기 

3. 공연료 계산기를 다형성 버전으로 만들기


 

(1) 공연료 계산기 만들기 

 

 계산 처리를 하는 createStatementData() 함수 내 조건부 로직을 포함한 함수인 amountFor() 과 volumCreditsFor()를 호출하여 공연료와 적립포인트를 계산하는 enrichPerformance() 함수를 주목해야 합니다. amountFor()과 volumCreditsFor() 함수를 공연 관련 데이터를 계산하는 함수들로 구성된 PerformanceCalculator 클래스로 만드는 것부터 시작합니다. 

 

함수 amountFor(), 함수 volumCreditsFor()  >  클래스 PerformanceCalculator

 

먼저 class PerformanceCalculator를 만들고 이를 객체로 만드는 것부터 시작합니다. 

 

클래스 PerformanceCalculator 만들기

(2) 함수들을 계산기로 옮기기 

 

그리고 난 후 기존 코드에서 몇 가지 동작을 클래스로 하나씩 옮겨봅니다. 순서는 다음과 같습니다.

 

1. playFor() > 공연 정보를 만드는 함수

2. amountFor() > 공연료 계산 함수

3. volumeCreditsFor() > 적립 포인트 계산 함수

 

  1. playFor() 클래스로 옮기기

공연료 계산 함수 amountFor() 은 Class의 get으로 수정하며 이동합니다.

 

2. amountFor() 클래스로 옮기기

적립포인트 계산 함수 volumeCreditsFor() 도 amountFor() 과 같은 방법으로 옮깁니다. 

 

3. volumeCreditsFor() 클래스로 이동

(3) 공연료 계산기를 다형성 버전으로 만들기 

 

클래스에 계산에 관련된 로직을 모두 옮겼으니 이제 다형성을 지원하도록 해봅니다. 가장 먼저 할 일은 "타입 코드" 대신 "서브 클래스"를 사용하도록 변경하는 것입니다. 이를 "타입 코드를 서브클래스로 바꾸기"라고 이 책에서는 말합니다. 

이를 시행하기 위해서는 두가지가 필요합니다. 

 

1. performanceCalculator의 서브 클래스 

2. 생성자 대신 함수를 호출하도록 수정 (생성자 > 팩터리 함수)

* JS에서는 생성자가 서브클래스의 인스턴스를 반환할 수 없기 때문입니다.

 

하나씩 차근차근 만들어 봅시다. 먼저 "생성자를 팩터리 함수"로 바꿔봅시다. 

생성자를 팩터리 함수로 바꾸기

 

함수를 이용하게 되면 PerformanceCalculator의 서브클래스 중에서 선택해서 생성 후 반환을 할 수 있습니다. 

createPerformanceCalculator() 함수의 내부를 Switch 함수를 써서 조건식으로 나누면 됩니다. 

 

createPerformanceCalculator() 함수 분기처리

분기 처리된 곳에서는 각 조건에 맞는 서브클래스를 호출합니다. 각각의 서브 클래스는 PerformanceCalculator를 상속받아 알맞는 기능을 처리합니다. 

 

PerformanceCalculator를 상속받는 서브 클래스 

PerformanceCalculator내에서 공연료 계산은 모든 처리는 서브 클래스에서 이행하도록 처리합니다. 

 

PerformanceCalculator 클래스 

포인트 적립 함수도 연극 공연료 계산 함수와 동일하게 작업해줍니다.  한가지 다른점은 희극 장르에서만 계산하는 방법이 달라지므로, 일반적인 경우를 기본으로 슈퍼클래스에 남겨두고, "희극 클래스"에서만 "오버라이드"하게 만드는 것이 좋습니다. 

 

volumeCredits() 함수 각각의 서브 클래스 적용

#9 상태 점검: 다형성을 활용하여 데이터 생성하기

지금까지 작업한 코드는 다음과 같습니다. 

// 공연료, 적립포인트 계산기 함수 
class PerformanceCalculator {
  constructor (aPerformance, aplay){
    this.performance = aPerformance;
    this.play = aplay; // 공연 정보 
  }

   // 포인트 적립 함수
   get volumeCredits(){
     // 일반적인 계산 방법
    return Math.max(this.performance.audience - 30, 0);
  }

   // 각각의 연극 공연료 계산 함수
  get amount(){ 
    throw '서브클래스에서 처리하도록 설계되었습니다.' // 공연료는 서브클래스에서 계산하도록 유도
  }
}

// 희극 공연료 계산 클래스
class ComedyCalculator extends PerformanceCalculator{

   // 포인트 적립 함수
   get volumeCredits(){
     // 일반적인 계산 방법에서 오버라이드를 함.
    return super.volumeCredits +  Math.floor(this.performance.audience / 5);
  }
  // 각각의 연극 공연료 계산 함수
  get amount(){ 
     let result = 30000;
      if (this.performance.audience > 20) {
        result += 10000 + 500 * (this.performance.audience - 20);
      }
      result += 300 * this.performance.audience;
      return result;
 }
}

// 비극 공연료 계산 클래스
class TragedyCalculator extends PerformanceCalculator{
  // 각각의 연극 공연료 계산 함수
   get amount(){ 
      let result = 40000;
        if (this.performance.audience > 30) {
          result += 1000 * (this.performance.audience - 30);
        }
       return result;
  }
}

// 팩터리 함수 
const createPerformanceCalculator = (aPerformance, aplay) => {
  switch (aplay.type) {
    case "tragedy": return new TragedyCalculator(aPerformance, aplay);
    case "comedy": return new ComedyCalculator(aPerformance, aplay);
    default:
      throw new Error(`알 수없는 장르: ${aplay.type}`);
  }
}

// 필요한 데이터 처리하는 함수
export default function createStatementData(invoice, plays) {
  const result = {};

  // 공연 정보 데이터 셋팅함수
  const enrichPerformance = (aPerformance) => {
    const calculator = createPerformanceCalculator(aPerformance, playFor(aPerformance)); // 생성자 대신 팩터리 함수 호출
    const result = Object.assign({}, aPerformance); // 얕은 복사 수행
    result.play = calculator.play;
    result.amount = calculator.amount; 
    result.volumeCredits = calculator.volumeCredits; 
    return result;
  };

  // 연극 데이터 추출 함수
  const playFor = (aPerformance) => {
    return plays[aPerformance.playID];
  };

  // 값 계산 로직 함수
  const totalVolumeCredist = (data) => {
    // for 반목문을 파이프문으로 변경
    return data.performances.reduce((total, p) => total + p.volumeCredits, 0);
  };

  // 토탈 값 계산 로직 함수
  const totalAmount = (data) => {
    // for 반목문을 파이프문으로 변경
    return data.performances.reduce((total, p) => total + p.amount, 0);
  };

  result.customer = invoice.customer;
  result.performances = invoice.performances.map(enrichPerformance);
  result.totalAmount = totalAmount(result);
  result.totalVolumeCredist = totalVolumeCredist(result);

  return result;
}

 

이번 수정으로 나아진 점은 연극 장르별 계산 코드들을 함께 묶음으로써 코드가 명확해졌다는 것입니다.

새로운 장르가 추가 될때 해당 장르의 서브클래스를 생성함수인 createPerformanceCalculator()에 추가하기면 하면되고, 수정을 할 때에도 각각의 장르로 나눠져있기 때문에 해당 클래스에서 수정을 하면 됩니다. 

#10 마치며

지금까지 리팩터링의 맛보기를 진행해보았습니다. 크게 보면 3단계로 진행을 하였는데 단계는 다음과 같습니다.

 

1. 원본 함수 > 중첩 함수 나누기

2. 계산 코드와 출력 코드 분리 

3. 계산 로직을 다형성으로 표현 

 

3단계를 진행하면서 "함수 추출하기", "변수 인라인하기", "함수 옮기기", "조건부 로직을 다형성으로 바꾸기" 등 다양한 리팩터링 기법을 선보였습니다. 세부적인 부분들은 뒤에있는 챕터에서 다룰 예정입니다.

 

"좋은 코드를 가늠하는 확실한 방법은 얼마나 수정하기 쉬운가다"

 

코드는 명확해야 하고, 코드를 수정해야 할 상황이 되면 고쳐야 할 곳을 쉽게 찾을 수 있고 오류 없이 빠르게 수정할 수 있을 때 건강한 코드라 할 수 있습니다. 단계를 잘게 나눠야 더 빠르게 처리할 수 있고, 코드는 절대 깨지지 않으며, 이러한 작은 단계들이 모여서 상당히 큰 변화를 이룰 수 있다는 사실을 리팩터링을 통해 구현할 수 있습니다. 

 

다음 장에서는 리팩터링 전반에 적용되는 원칙 몇가지를 다뤄볼 생각입니다. 긴 글 읽어 주셔서 감사합니다.  


< 참고자료 >

 

[책] Refactoring - 마틴 파울러 지음 

www.yes24.com/Product/Goods/89649360

 

<기타> Refactoring (4) end

'' 카테고리의 다른 글

Refactoring (6)  (0) 2020.12.30
Refactoring (5)  (0) 2020.12.19
Refactoring (3)  (0) 2020.12.12
Refactoring (2)  (0) 2020.12.12
Refactoring (1)  (0) 2020.12.08
Comments