Santos의 개발블로그

Refactoring (3) 본문

Refactoring (3)

Santos 2020. 12. 12. 18:25

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


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

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

 

Refactoring(2)에서는 다음과 같은 내용을 다루었습니다. 

 

1. format 변수 제거하기 

2. volumeCredits 변수 제거하기 

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

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

 

#6 계산 단계와 포맷팅 단계 분리하기

Refactoring(3)에서는 계산 단계와 포맷팅(html로 표현)분리하기 부분을 다루도록 하겠습니다.

statement()함수의 HTML 버전을 만드는 작업을 수행하기 위해서는 이미 함수 안에 분리된 중첩 함수들(계산작업)과의 구분이 필요합니다.

 

이 책에서는 구분을 하는 것을 강조하는데 이를 "단계 쪼개기"라 합니다. 첫 단계에서는 영수증 출력을 위한 데이터를 처리하고, 두 번째 단계에서는 처리한 결과를 HTML로 표현하는 것입니다. 

두 함수로 쪼갠 후에는 이어주는 브릿지 함수를 만들어주는 것이 이번 장에서 할 부분입니다. 다시 한번 간략하게 정리해보면 다음과 같습니다. 

 

1. 영수증 출력을 위한 데이터 처리 함수 - statement()

2. 처리한 결과를 HTML로 표현해주는 함수 - renderPlainText()

3. 1번과 2번을 이어주는 브릿지 객체

 

(1) 브릿지 객체 만들기 

 

1. 함수 추출하기 

2. 두 단계를 이어주는 브릿지 객체 만들기

 

먼저 "데이터를 처리하는 함수""HTML로 출력하는 함수""함수 추출하기" 방법을 통해 나눕니다.

 

함수 추출하기 

그리고 난 후에는 두 단계 사이의 중간 데이터 구조 역할을 할 객체를 만들어 renderPlainText() 인수로 전달합니다. 

두 단계를 이어주는 브릿지 객체 만들기

(2) 영수증 출력을 위한 데이터 처리 함수 - statement()

 

두 단계로 나누었으면 renderPlainText() 함수내에 있는 모든 계산 로직 함수를 다시 statement() 함수로 옮기는 작업이 필요합니다. 

순서는 다음과 같이 진행됩니다. 

 

1. 고객 정보 데이터 옮기기 

2. 공연 정보 데이터 옮기기

3. 연극 제목 데이터 옮기기

4. 연극 공연료 데이터 옮기기

5. 적립 포인트 데이터 옮기기 

6. 총합 데이터 옮기기

 

renderPlainText() 함수 invoice 인자를 통해 얻었던 고객 정보를 statement의 중간 데이터를 통해 얻을 수 있도록 바꾸었습니다.

고객 정보 데이터 옮기기 

고객 정보를 옮긴 것처럼 공연 정보 데이터도 중간 데이터를 통해 전달하는 방식으로 수정합니다. 

공연 정보 데이터 옮기기

연극 제목도 중간 데이터에서 가져올 수 있도록 수정합니다. 이를 위해 공연 정보 레코드에 연극 데이터를 추가해야합니다. 

JS에서는 immutable 데이터를 권장하고 있기 때문에 객체를 복사하는 함수를 추가합니다. 

 

얕은 복사를 수행하여 연극 정보를 담을 객체를 만들었으니, 연극 정보에 대한 데이터를 담아야합니다. 먼저 연극 제목을 담겠습니다. 이를 위해 "함수 옮기기"를 적용하여 playFor() 함수를 statement()로 옮깁니다. 

 

연극 제목 데이터 옮기기

 다음은 연극 제목과도 같은 방법으로 연극 공연료에 대한 데이터를 연극 정보 데이터 객체에 담는 작업을 진행합니다.

연극 공연료 데이터 옮기기

그 다음으로 적립 포인트 계산 함수를 이동시킵니다. 

적립 포인트 데이터 옮기기 

마지막으로 총합을 구하는 부분을 옮깁니다. 

총합 데이터 옮기기

마지막으로 "반복문을 파이프라인으로 바꾸기"를 통해 코드 양을 줄입니다. 

총합 데이터 옮기기

(3) 처리한 결과를 HTML로 표현해주는 함수 - renderPlainText()

 

statement() 함수가 정리가 끝났으면, 처리 결과에 따른 값을 HTML로 표현해주는 함수 rederPlainText() 함수 내부는 다음과 같이 수정됩니다. 

renderPlainText()

 그리고 난 후 statement(), 즉 영수증을 만드는 함수에 필요한 데이터 처리에 대한 코드를 모두 별도 함수로 추출합니다.

영수증을 만드는 함수 추출

이렇게 총 3개의 함수로 쪼개어 사용해도 괜찮지만, 필자는 rederPlainText()함수를 더 쪼개어 두개의 함수를 더 추가했습니다. 

 

1. htmlStatement() 함수 > 화면에 DOM을 그려주는 실행기 

2. rederHtml() 함수 > 데이터를 html 코드로 만들어주는 함수

 

htmlStatement() 함수 >  화면에 DOM을 그려주는 실행기 
rederHtml() 함수 > 데이터를 html 코드로 만들어주는 함수

#7 중간 점검: 두 파일(두 단계)로 분리됨

이번 장까지 리팩터링한 전체코드는 다음과 같습니다. 

import playData from "./plays";
import invoiceData from "./invoices";

// 영수증을 만드는 함수
const statement = (invoice, plays) => {
  return renderPlainText(createStatementData(invoice, plays));
};

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

  // 공연 정보 데이터 셋팅함수
  const enrichPerformance = (aPerformance) => {
    const result = Object.assign({}, aPerformance); // 얕은 복사 수행
    result.play = playFor(result);
    result.amount = amountFor(result);
    result.volumeCredits = volumeCreditsFor(result); // 적립 포인트 계산 함수 추가
    return result;
  };

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

  // 각각의 연극 공연료 계산 함수
  const amountFor = (aPerformance) => {
    let result = 0;
    switch (aPerformance.play.type) {
      case "tragedy": // 비극
        result = 40000;
        if (aPerformance.audience > 30) {
          result += 1000 * (aPerformance.audience - 30);
        }
        break;
      case "comedy": // 희극
        result = 30000;
        if (aPerformance.audience > 20) {
          result += 10000 + 500 * (aPerformance.audience - 20);
        }
        break;

      default:
        throw new Error(`알 수 없는 장르: ${aPerformance.play.type}`);
    }
    return result;
  };

  // 포인트 적립 함수
  const volumeCreditsFor = (aPerformance) => {
    let result = 0;
    result += Math.max(aPerformance.audience - 30, 0);

    // 희극 관객 5명마다 추가 포인트 제공
    if ("comedy" === aPerformance.play.type) {
      result += Math.floor(aPerformance.audience / 5);
    }

    return result;
  };

  // 값 계산 로직 함수
  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;
};

// format 함수
const usd = (aNumber) => {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
    minimumFractionDigits: 2,
  }).format(aNumber / 100);
};

// HTML로 출력하는 함수
const renderPlainText = (data) => {
  let result = `청구 내역 (고객명: ${data.customer})\n`;

  for (const perf of data.performances) {
    // 청구 내역 출력
    result += ` ${perf.play.name}: ${usd(perf.amount)} (${perf.audience}석)\n`;
  }

  result += `총액: ${usd(data.totalAmount)}\n`;
  result += `적립 포인트: ${data.totalVolumeCredist}점\n`;
  return result;
};

// 영수증을 출력하여 dom에 올려주는 실행 함수
const htmlStatement = (invoice, plays) => {
  return renderHtml(createStatementData(invoice, plays));
};

// html로 출력할 수 있도록 만드는 함수
const renderHtml = (data) => {
  let result = `<h1>청구내역 (고객명: ${data.customer})</h1>\n`;
  result += `<table>\n`;
  result += `<tr><th>연극</th><th>좌석 수</th><th>금액</th></tr>`;
  for (const perf of data.performances) {
    result += `<tr><td>${perf.play.name}</td>${perf.audience}석</tr>`;
    result += `<td>${usd(perf.amount)}</td></tr>\n`;
  }
  result += `</table>\n`;
  result += `<p>총액: <em>${usd(data.totalAmount)}</em></p>\n`;
  result += `<p>적립 포인트: <em>${data.totalVolumeCredist}</em>점</p>\n`;
  return result;
};

console.log(statement(invoiceData, playData));
console.log(htmlStatement(invoiceData, playData));

 

출력 결과 값

위 코드는 한 파일에 작성되었습니다. 한 파일에서 5가지의 함수를 선언하는 것보다는 기능과 목적에 따라서 모듈화를 하여 사용하는 것이 더 효율적입니다. 각 역할 (그려주는 함수, 계산하는 함수)에 맞춰 총 2개의 파일로 나누어 구성하였으니, 코드가 궁금하시면 아래의 주소로 들어가 확인부탁드립니다. 

 

github.com/SangchoKim/refactoring/tree/refactoringThird

 

SangchoKim/refactoring

Contribute to SangchoKim/refactoring development by creating an account on GitHub.

github.com

이번 글에서 진행한 리팩터링은 다음과 같습니다. 

 

1. format 변수 제거하기 

2. volumeCredits 변수 제거하기 

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

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

 

다음 글에서는 4번째 다형성을 활용해 계산 코드 재구성하기 부분을 진행해보겠습니다. 


< 참고자료 >

 

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

www.yes24.com/Product/Goods/89649360

 

<기타> Refactoring (3) end

'' 카테고리의 다른 글

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