mingg IT

[리팩토링] 리팩토링 2판 1장 리뷰 본문

기타

[리팩토링] 리팩토링 2판 1장 리뷰

mingg123 2022. 4. 1. 16:18

리팩토링 2판

  • 예시 1번
  • 프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 형태로 리팩토링하고 나서 원하는 기능을 추가한다.리팩토링 하기 전에 제대로 된 테스트부터 마련해야 한다. 테스트는 반드시 자가진단하도록 만들어야함.컴퓨터가 이해하는 코드는 바보도 작성할 수 있다. 사람이 이해하도록 작성하는 프로그래머가 진정한 실력자다.
    • 1단계 함수 쪼개기.
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
      const format = new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format;
    
      for (let perf of invoice.performances) {
        const play = plays[perf.playID];
        let thisAmount = amountFor(perf, play);
        //포인트를 적립한다.
        volumnCredits += Math.max(perf.audience - 30, 0);
        if ("comedy" === play.type) volumnCredits += Math.floor(perf.audience / 5);
    
        //청구 내역을 출력한다.
        result += `${play.name} : ${format(thisAmount / 100)} (${perf.audience}석)`;
        totalAmount += thisAmount;
      }
      result += `총액 : ${format(totalAmount / 100)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    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;
    }
    console.log(statement(invoice, plays));
    switch 문안을 새로운 함수를 사용한다.
    • 변수 인라인, playFor 함수를 사용함.
    let invoice = {
      customer: "BigCo",
      performances: [
        {
          playID: "hamlet",
          audience: 55,
        },
        {
          playID: "as-like",
          audience: 35,
        },
        {
          playID: "othello",
          audience: 40,
        },
      ],
    };
    
    let plays = {
      hamlet: { name: "Hamlet", type: "tragedy" },
      "as-like": { name: "As ou Like It", type: "comedy" },
      othello: { name: "othello", type: "tragedy" },
    };
    
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
      const format = new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format;
    
      for (let perf of invoice.performances) {
        //포인트를 적립한다.
        volumnCredits += Math.max(perf.audience - 30, 0);
        if ("comedy" === playFor(perf).type)
          volumnCredits += Math.floor(perf.audience / 5);
    
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${
          perf.audience
        }석)`;
        totalAmount += amountFor(perf);
      }
      result += `총액 : ${format(totalAmount / 100)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    function playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    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;
    }
    console.log(statement(invoice, plays));
    
    • volumnCreditsFor 함수 만들어서 volumnCredits 계산하도록
    let invoice = {
      customer: "BigCo",
      performances: [
        {
          playID: "hamlet",
          audience: 55,
        },
        {
          playID: "as-like",
          audience: 35,
        },
        {
          playID: "othello",
          audience: 40,
        },
      ],
    };
    
    let plays = {
      hamlet: { name: "Hamlet", type: "tragedy" },
      "as-like": { name: "As ou Like It", type: "comedy" },
      othello: { name: "othello", type: "tragedy" },
    };
    
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
      const format = new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format;
    
      for (let perf of invoice.performances) {
        volumnCredits = volumnCreditsFor(perf);
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${
          perf.audience
        }석)`;
        totalAmount += amountFor(perf);
      }
      result += `총액 : ${format(totalAmount / 100)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    function volumnCreditsFor(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 playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    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;
    }
    console.log(statement(invoice, plays));
    
    • format 부분 함수화 시시킴
    let invoice = {
      customer: "BigCo",
      performances: [
        {
          playID: "hamlet",
          audience: 55,
        },
        {
          playID: "as-like",
          audience: 35,
        },
        {
          playID: "othello",
          audience: 40,
        },
      ],
    };
    
    let plays = {
      hamlet: { name: "Hamlet", type: "tragedy" },
      "as-like": { name: "As ou Like It", type: "comedy" },
      othello: { name: "othello", type: "tragedy" },
    };
    
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
    
      for (let perf of invoice.performances) {
        volumnCredits = volumnCreditsFor(perf);
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${
          perf.audience
        }석)`;
        totalAmount += amountFor(perf);
      }
      result += `총액 : ${format(totalAmount / 100)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    function format(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber);
    }
    function volumnCreditsFor(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 playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    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;
    }
    console.log(statement(invoice, plays));
    
    // const format = new Intl.NumberFormat("en-US", { // style: "currency", // currency: "USD", // minimumFractionDigits: 2, // }).format;
    • /100 해주는 부분을 함수안으로 이동시킴.
    // 변경 전 
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
    
      for (let perf of invoice.performances) {
        volumnCredits = volumnCreditsFor(perf);
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${usd(amountFor(perf) / 100)} (${
          perf.audience
        }석)`;
        totalAmount += amountFor(perf);
      }
      result += `총액 : ${usd(totalAmount / 100)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    function usd(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber);
    }
    
    //변경 후
    function statement(invoice, plays) {
      let totalAmount = 0;
      let volumnCredits = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})`;
    
      for (let perf of invoice.performances) {
        volumnCredits = volumnCreditsFor(perf);
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${usd(amountFor(perf))} (${
          perf.audience
        }석)`;
        totalAmount += amountFor(perf);
      }
      result += `총액 : ${usd(totalAmount)}`;
      result += `적립포인트 : ${volumnCredits}점`;
      return result;
    }
    
    function usd(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber / 100);
    }
    
    
    • volumeCredits 변수 제거하기
    function statement(invoice, plays) {
      let totalAmount = 0;
      let result = `청구 내역(고객명 : ${invoice.customer})\\n`;
    
      for (let perf of invoice.performances) {
        //청구 내역을 출력한다.
        result += `${playFor(perf).name} : ${usd(amountFor(perf))} (${
          perf.audience
        }석)\\n`;
        totalAmount += amountFor(perf);
      }
    
      result += `총액 : ${usd(totalAmount)}`;
      result += `적립포인트 : ${totalVolumeCredits()}점`;
      return result;
    }
    
    function totalVolumeCredits() {
      let volumnCredits = 0;
      for (let perf of invoice.performances) {
        volumnCredits += volumnCreditsFor(perf);
      }
      return volumnCredits;
    }
    
    1. 반복문 쪼개기로 변수 값을 누적시키는 부분을 분리한다.
    2. 문장 슬라이드하기로 변수 초기화 문장을 변수 값 누적 코드 바로 앞으로 옮긴다.
    3. 함수 추출하기로 적립 포인트 계산 부분을 별도 함수로 추출한다.
    4. 변수 인라인하기로 volumeCredits변수를 제거한다.
    반복문을 쪼개지고 반복문이 중복되는 것을 꺼리는 이들이 많지만, 이정도 중복은 성능에 미치는 영향이 미미할 때가 많다.하지만 ‘대체로 그렇다’ 와 ‘항상 그렇다’ 는 엄연히 다르다. 때로는 리팩토링이 성능에 상당한 영향을 주기도 한다. 그런 경우라도 나는 개의치 않고 리팩토링 한다. 잘 다듬어진 코드라야 성능 개선 작업도 훨씬 수월하기 때문이다.
    • 중간 점검 코드
    let invoice = {
      customer: "BigCo",
      performances: [
        {
          playID: "hamlet",
          audience: 55,
        },
        {
          playID: "as-like",
          audience: 35,
        },
        {
          playID: "othello",
          audience: 40,
        },
      ],
    };
    
    let plays = {
      hamlet: { name: "Hamlet", type: "tragedy" },
      "as-like": { name: "As ou Like It", type: "comedy" },
      othello: { name: "othello", type: "tragedy" },
    };
    
    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())}`;
      result += `적립포인트 : ${totalVolumeCredits()}점`;
      return result;
    }
    
    function totalAmount() {
      let result = 0;
      for (let perf of invoice.performances) {
        result += amountFor(perf);
      }
      return result;
    }
    function totalVolumeCredits() {
      let result = 0;
      for (let perf of invoice.performances) {
        result += volumnCreditsFor(perf);
      }
      return result;
    }
    
    function usd(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber / 100);
    }
    function volumnCreditsFor(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 playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    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;
    }
    console.log(statement(invoice, plays));
    
    • 불변(immutable) 로 취급하기 위해서 복사해서 사용함.
    function statement(invoice, plays) {
      const statementData = {};
      statementData.customer = invoice.customer;
      statementData.performances = invoice.performances.map(enrichPerformance);
    
      return renderPlainText(statementData, plays);
    }
    
    function enrichPerformance(aPerformance) {
      const result = Object.assign({}, aPerformance);
      return result;
    }
    
    얕은 복사함.
    • reduce를 사용. 반복문을 파이프라인으로 바꾸기
    // 리팩토링 하기 전 
    function totalAmount(data) {
      let result = 0;
      for (let perf of data.performances) {
        console.log(perf);
        result += perf.amount;
      }
      return result;
    }
    
    // 리팩토링 후 
    function totalAmount(data) {
      return data.performances.reduce((total, p) => total + p.amount, 0);
    }
    
    • 중간 단계로 바꾸기
    // 리팩토링 전
    function statement(invoice, plays) {
      const statementData = {};
      statementData.customer = invoice.customer;
      statementData.performances = invoice.performances.map(enrichPerformance);
    
      return renderPlainText(statementData, plays);
    }
    
    function createStatementData(invoice, plays) {
      const statementData = {};
      statementData.customer = invoice.customer;
      statementData.performances = invoice.performances.map(enrichPerformance);
    }
    
    // 리팩토링 후 
    function statement(invoice, plays) {
      return renderPlainText(createStatementData(invoice, plays));
    }
    
    function createStatementData(invoice, plays) {
      const statementData = {};
      statementData.customer = invoice.customer;
      statementData.performances = invoice.performances.map(enrichPerformance);
      statementData.totalAmount = totalAmount(statementData);
      statementData.totalVolumeCredits = totalVolumeCredits(statementData);
      return statementData;
    }
    
    • 중간 점검 코드
    let invoice = {
      customer: "BigCo",
      performances: [
        {
          playID: "hamlet",
          audience: 55,
        },
        {
          playID: "as-like",
          audience: 35,
        },
        {
          playID: "othello",
          audience: 40,
        },
      ],
    };
    
    let plays = {
      hamlet: { name: "Hamlet", type: "tragedy" },
      "as-like": { name: "As ou Like It", type: "comedy" },
      othello: { name: "othello", type: "tragedy" },
    };
    
    function statement(invoice, plays) {
      return renderPlainText(createStatementData(invoice, plays));
    }
    
    function createStatementData(invoice, plays) {
      const result = {};
      result.customer = invoice.customer;
      result.performances = invoice.performances.map(enrichPerformance);
      result.totalAmount = totalAmount(invoice);
      result.totalVolumeCredits = totalVolumeCredits(invoice);
      return result;
    }
    function enrichPerformance(aPerformance) {
      const result = Object.assign({}, aPerformance);
      result.play = playFor(result);
      result.amount = amountFor(result);
      result.volumeCredits = volumnCreditsFor(result);
      return result;
    }
    function renderPlainText(data, plays) {
      let result = `청구 내역(고객명 : ${data.customer})\\n`;
    
      for (let perf of data.performances) {
        //청구 내역을 출력한다.
        result += `${perf.play.name} : ${usd(perf.amount)} (${perf.audience}석)\\n`;
      }
    
      result += `총액 : ${usd(totalAmount(data))}`;
      result += `적립포인트 : ${totalVolumeCredits(data)}점`;
      return result;
    }
    function totalAmount(data) {
      return data.performances.reduce((total, p) => total + p.amount, 0);
    }
    function totalVolumeCredits(data) {
      return data.performances.reduce((total, p) => total + p.volumeCredits, 0);
    }
    
    function usd(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber / 100);
    }
    function volumnCreditsFor(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 playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    function 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 += 1000 + 500 * (aPerformance.audience - 20);
          }
          result += 300 * aPerformance.audience;
          break;
        default:
          throw new Error(`알수 없는 장르 : ${aPerformance.play.type}`);
      }
      return result;
    }
    console.log(statement(invoice, plays));
    
    • amountFor 함수에 다형성 적용하기
    class PerformanceCalculator {
      constructor(aPerformance, aPlay) {
        this.performances = aPerformance;
        this.play = aPlay;
      }
    
      get amount() {
        let result = 0;
        switch (this.play.type) {
          case "tragedy":
            result = 40000;
            if (this.performances.audience > 30) {
              result += 1000 * (this.performances.audience - 30);
            }
            break;
          case "comedy":
            result = 30000;
            if (this.performances.audience > 20) {
              result += 1000 + 500 * (this.performances.audience - 20);
            }
            result += 300 * this.performances.audience;
            break;
          default:
            throw new Error(`알수 없는 장르 : ${this.play.type}`);
        }
        return result;
      }
      get volumeCredits() {
        let result = 0;
        result += Math.max(this.performances.audience - 30, 0);
        if ("comedy" === playFor(this.performances).type)
          result += Math.floor(this.performances.audience / 5);
        return result;
      }
    }
    
    function statement(invoice, plays) {
      return renderPlainText(createStatementData(invoice, plays));
    }
    
    function createStatementData(invoice, plays) {
      const result = {};
      result.customer = invoice.customer;
      result.performances = invoice.performances.map(enrichPerformance);
      result.totalAmount = totalAmount(invoice);
      result.totalVolumeCredits = totalVolumeCredits(invoice);
      return result;
    }
    function enrichPerformance(aPerformance) {
      const calculator = new PerformanceCalculator(
        aPerformance,
        playFor(aPerformance)
      );
      const result = Object.assign({}, aPerformance);
      result.play = calculator.play;
      result.amount = calculator.amount;
      result.volumeCredits = calculator.volumeCredits;
      return result;
    }
    function renderPlainText(data, plays) {
      let result = `청구 내역(고객명 : ${data.customer})\\n`;
    
      for (let perf of data.performances) {
        //청구 내역을 출력한다.
        result += `${perf.play.name} : ${usd(perf.amount)} (${perf.audience}석)\\n`;
      }
    
      result += `총액 : ${usd(totalAmount(data))}`;
      result += `적립포인트 : ${totalVolumeCredits(data)}점`;
      return result;
    }
    function totalAmount(data) {
      return data.performances.reduce((total, p) => total + p.amount, 0);
    }
    function totalVolumeCredits(data) {
      return data.performances.reduce((total, p) => total + p.volumeCredits, 0);
    }
    
    function usd(aNumber) {
      return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2,
      }).format(aNumber / 100);
    }
    function volumnCreditsFor(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 playFor(aPerformance) {
      return plays[aPerformance.playID];
    }
    function amountFor(aPerformance) {
      return new PerformanceCalculator(aPerformance, playFor(aPerformance)).amount;
    }
    console.log(statement(invoice, plays));
    
    • 다형성 적용하기 (매우 중요하다잉.)
    //적용 전
    function enrichPerformance(aPerformance) {
       const calculator = new PerformanceCalculator(
         aPerformance,
         playFor(aPerformance)
       );
    
      const result = Object.assign({}, aPerformance);
      result.play = calculator.play;
      result.amount = calculator.amount;
      result.volumeCredits = calculator.volumeCredits;
      return result;
    }
    
    //적용 후 
    class TragedyCalculator extends PerformanceCalculator {}
    
    class ComedyCalculator extends PerformanceCalculator {}
    
    function statement(invoice, plays) {
      return renderPlainText(createStatementData(invoice, plays));
    }
    
    function 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}`);
      }
    }
    
    function 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;
    }
    
    createPerformanceCalculator 를 만들어서 종류에 맞게 다른 Calculator를 적용 시킨다.
    • 조건부 로직을 다형성으로 바꾸기 (중요)
    // 수정 전
    class PerformanceCalculator {
      constructor(aPerformance, aPlay) {
        this.performances = aPerformance;
        this.play = aPlay;
      }
    
      get amount() {
        let result = 0;
        switch (this.play.type) {
          case "tragedy":
            result = 40000;
            if (this.performances.audience > 30) {
              result += 1000 * (this.performances.audience - 30);
            }
            break;
          case "comedy":
            result = 30000;
            if (this.performances.audience > 20) {
              result += 1000 + 500 * (this.performances.audience - 20);
            }
            result += 300 * this.performances.audience;
            break;
          default:
            throw new Error(`알수 없는 장르 : ${this.play.type}`);
        }
        return result;
      }
    }
    
    // 수정 후
    class TragedyCalculator extends PerformanceCalculator {
      get amount() {
        let result = 40000;
        if (this.performances.audience > 30) {
          result += 1000 * (this.performances.audience - 30);
        }
        return result;
      }
    }
    
    class ComedyCalculator extends PerformanceCalculator {
      get amount() {
        let result = 30000;
        if (this.performances.audience > 20) {
          result += 1000 + 500 * (this.performances.audience - 20);
        }
        result += 300 * this.performances.audience;
        return result;
      }
    }
    
    • 조건부 다형성 로직으로 분리2
    class PerformanceCalculator {
      constructor(aPerformance, aPlay) {
        this.performances = aPerformance;
        this.play = aPlay;
      }
    
      get amount() {
        throw new Error("서브클래스에서 처리하도록 설계되었습니다.");
      }
      get volumeCredits() {
        return Math.max(this.performances.audience - 30, 0);
      }
    }
    
    class TragedyCalculator extends PerformanceCalculator {
      get amount() {
        let result = 40000;
        if (this.performances.audience > 30) {
          result += 1000 * (this.performances.audience - 30);
        }
        return result;
      }
    }
    
    class ComedyCalculator extends PerformanceCalculator {
      get amount() {
        let result = 30000;
        if (this.performances.audience > 20) {
          result += 1000 + 500 * (this.performances.audience - 20);
        }
        result += 300 * this.performances.audience;
        return result;
      }
    
      get volumeCredits() {
        return super.volumeCredits + Math.floor(this.performances.audience / 5);
      }
    }
    
    volumeCredits 함수도 다형성 이용해서 분리했음. 이런식으로 해야한다.
  • 함수 추출하기, 변수 인라인 하기, 함수 옮기기, 조건부 로직을 다형성으로 바꾸기
  • 리팩토링 과정에서 성능이 크게 떨어졌다면 리팩토링 후 시간을 내어 성능을 계선한다.
  • 똑똑한 컴파일러들은 최신 캐싱 기법 등으로 무장하고 있어서 우리의 직관을 초월하는 결과를 내어준다. 또한 소프트웨어 성능은 대체로 코드의 몇몇 작은 부분에 의해 결정됨로 그 외의 부분은 수정한다고 해도 성능 차이를 체감할 수 없다.
  • 해당부분을 function으로 변경.
  • 리팩토링은 프로그램 수정을 작은 단계로 나눠 진행한다. 그래서 중간에 실수하더라도 버그를 쉽게 찾을 수 있다.
  • 리팩토링의 첫단계는 테스트 코드부터 마련해야함.
Comments