Book | 리펙터링 2판 1.4장 마무리

sik2·2022년 8월 6일
0

리뷰

목록 보기
10/12

계속해서 Statement() 함수 쪼개기

이전까지 작업한 코드

function statement(invoice, plays) {
  let totalAmount = 0;
  let volumeCredits = 0;
  let result = `청구 내역(고객명: ${invoice.customer})\n`;
  const format = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
    minimumFractionDigits: 2
  }).format;
  
  function amountFor(perf, play) { // 함수 추출
    let thisAmount = 0;
    switch (play.type) { 
      case "tragedy": //비극
        thisAmount = 40000;
        if (perf.audience > 30) thisAmount += 1000 * (perf.audience - 30);
        break;
      case "comedy": //희극
        thisAmount = 30000;
        if (perf.audience > 20) thisAmount += 1000 + 500 * (perf.audience - 20);
        thisAmount += 300 * perf.audience;
        break;
      default:
        throw new Error(`알 수 없는 장르: ${play.type}`);
    }
    return thisAmount;
  }

  for (let perf of invoice.performances) {
    const play = plays[perf.playID]; // object {name, type}
    let thisAmount = amountFor(perf, play); // 추출 함수 호출
    // 포인트를 적립한다.
    volumeCredits += Math.max(perf.audience - 30, 0);
    // 희극 관객 5명마다 추가 포인트를 제공한다.
    if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);

    // 청구 내역을 출력한다.
    result += `${play.name}: ${format(thisAmount / 100)} (${
      perf.audience
    }석)\n`;
    totalAmount += thisAmount;
  }
  result += `총액: ${format(totalAmount / 100)}\n`;
  result += `적립 포인트: ${volumeCredits}점\n`;
  return result;
}

적립 포인트 계산 코드 추출

  • 지난번 포스팅에서 amountFor 함수를 추출할 때와 마찬가지로 불명확한 변수명을 명확하게 바꾼다.
  • 매개변수는 그 목적에 맞는 이름으로 쓴다. 내부적으로만 쓰이고 return 하는 변수명은 result를 쓴다.
  function volumeCreditsFor(aPerformance) { // perf => aPerformance 로 변수명 변경
    let result = 0; // volumeCredits => result 로 변수명 변경
    result += Math.max(aPerformance.audience - 30, 0);
    if ("comedy" === playFor(aPerformance).type)
      result += Math.floor(aPerformance.audience / 5);
    return result;
  }

format 변수 제거

  • format은 임시 변수에 함수를 대입한 형태인데 함수를 직접 선언해 사용하도록 바꾸었다.
  • format 이라는 이름보다 usd 라는 직관적인 이름으로 바꾸었다.
  function usd(aNumber) {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
      minimumFractionDigits: 2
    }).format(aNumber / 100);
  }

volumeCredits 변수 제거

  • 반복문쪼개기 => 변수 값을 누적시키는 부분을 분리한다.
  • 문장슬라이드하기 (위치 변경) => 변수 초기화 문장을 변수 값 누적 코드 바로 앞으로 옮긴다.
  • 함수 추출하기 => 적립 포인트 계산 부분을 별도 함수로 추출한다.
  • 변수 인라인하기 => volumeCredits 변수를 제거한다.
  function totalVolumeCredits() {
    let volumeCredits = 0;
    for (let perf of invoice.performances) {
      volumeCredits += volumeCreditsFor(perf);
    }
    return volumeCredits;
  }

totalAmount도 같은 원리로 수정

  function totalAmount() {
    let result = 0;
    for (let perf of invoice.performances) {
      result += amountFor(perf);
    }
    return result;
  }

결과물

import INVOICE from "../../invoices.json";
import PLAYS from "../../plays.json";

function statement(invoice, plays) {
  let result = `청구 내역(고객명: ${invoice.customer})\n`;
  for (let perf of invoice.performances) {
    // 청구 내역을 출력한다.
    result += `${playFor(perf).name}: ${usd(amountFor(perf))} (${
      perf.audience
    }석)\n`;
  }
  result += `총액: ${usd(totalAmount())}\n`;
  result += `적립 포인트: ${totalVolumeCredits()}점\n`;
  return result;

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

  function volumeCreditsFor(aPerformance) {
    let result = 0;
    result += Math.max(aPerformance.audience - 30, 0);
    if ("comedy" === playFor(aPerformance).type)
      result += Math.floor(aPerformance.audience / 5);
    return result;
  }

  function totalVolumeCredits() {
    let volumeCredits = 0;
    for (let perf of invoice.performances) {
      volumeCredits += volumeCreditsFor(perf);
    }
    return volumeCredits;
  }
  function totalAmount() {
    let result = 0;
    for (let perf of invoice.performances) {
      result += amountFor(perf);
    }
    return result;
  }

  function playFor(aPerformance) {
    return plays[aPerformance.playID];
  }

  // play는 aPerformance에서 오는 값이라서 제거
  function amountFor(aPerformance) {
    let result = 0;

    switch (playFor(aPerformance).type) {
      case "tragedy": //비극
        result = 40000;
        if (aPerformance.audience > 30)
          result += 1000 * (aPerformance.audience - 30);
        break;
      case "comedy": //희극
        result = 30000;
        if (aPerformance.audience > 20)
          result += 1000 + 500 * (aPerformance.audience - 20);
        result += 300 * aPerformance.audience;
        break;
      default:
        throw new Error(`알 수 없는 장르: ${playFor(aPerformance).type}`);
    }
    return result;
  }
}

리펙터링 관점에서 성능

  • 반복문을 쪼개서 성능이 느려지지 않을까 걱정이 될 수 있다.
  • 하지만 하드웨어 성능이 좋고 최신 컴파일러는 똑똑하므로 실제 성능에 미치는 영향은 미미하다.
  • 그러므로 코드를 보기쉽게 만드는데 더 집중해야한다.
  • 만일 성능에 이슈가 생겼다면 그때 적절한 타협을 통해 이슈를 해결하면 된다.
profile
기록

0개의 댓글