리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

 

 

GitHub - rkwhr0010/refactoring: 리팩터링책 내용

리팩터링책 내용. Contribute to rkwhr0010/refactoring development by creating an account on GitHub.

github.com

1.6 계산 단계와 포맷팅 단계 분리하기

지금까진 프로그램의 논리적인 요소 파악을 위해 코드 구조를 보강했다.

리팩터링 초기 단계에서 흔히 수행한다.

 

골격은 충분히 개선 됐으니, 이제 원하는 기능 변경 작업을 차례다.

statement()함수의 HTML 버전 만들기

 

구조를 개선해서 처음 코드보다 작업하기 편해졌다.

계산코드가 모두 분리됐기 때문에 일곱 줄짜리 최상단 코드에 대응하는 HTML 버전만 작성하면 된다.

 

HTML버전 statement()함수를 만드는데 문제점이 있다. 계산 코드가 전부 내부 중첩 함수라 그것을 그대로 복사해서 만들면 코드 중복이 발생한다.

 

문제 해결법으로 가장 추천하는 방식은 단계 쪼개기다.

statement() 로직을 단계로 나누는 것이다.

단계는 statement() 필요한 데이터를 처리하고,

다음 단계는 앞서 처리한 결과를 텍스트나 HTML 표현하는

function statement(invoice, plays) {
    return renderPlainText(invoice, plays) //본문 전체를 별도 함수로 추출
}
function renderPlainText(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(totalAount())}\n`; 
    result += `적립 포인트: ${totalVolumeCredits()}점\n`;
    return result;
    function totalAount(){ 
        let result = 0;
        for(let perf of invoice.performances){
            result += amountFor(perf);
        }
        return result;
    }
…. 이하 추출한 본문




function statement(invoice, plays) {
    const statementData = {};
    return renderPlainText(statementData, invoice, plays) //중간데이터 구조를 인수로 전달
}
function renderPlainText(data, invoice, plays)

 

renderPlainText() 인수 invoice 중간 데이터구조로 옮기기

function statement(invoice, plays) {
    const statementData = {};
    statementData.customer = invoice.customer;
    statementData.performances = invoice.performances;
    return renderPlainText(statementData, /*invoice,*/ plays) //필요없어진 인수 삭제
}
function renderPlainText(data, plays) {
    let result = `청구 내역 (고객명 : ${data.customer})\n`;
    for(let perf of data.performances){
        result += `${playFor(perf).name} : ${usd(amountFor(perf))} (${perf.audience}석)\n`;
    }
    result += `총액: ${usd(totalAount())}\n`; 
    result += `적립 포인트: ${totalVolumeCredits()}점\n`;
    return result;
function totalAount(){

 

데이터 복사

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;
    }
}

이렇게 데이터를 복사해서 사용하는 이유는 데이터를 불변으로 취급하기 위함

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);
        result.play = playFor(result);//중간 데이터에 연극 정보를 저장
        return result;
    }
    function playFor(aPerformance){ //renderPlainText 중첩함수를 옮김
        return plays[aPerformance.playID];
    }
}

옮기면서 renderPlainText()에서 playFor()사용했던 부분 전부 변경

 

 

amountFor() 비슷하기 옮기기

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);
        result.play = playFor(result);//중간 데이터에 연극 정보를 저장
        result.amount = amountFor(result);
        return result;
    }
    function playFor(aPerformance){ //renderPlainText 중첩함수를 옮김
        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 > 30){
                result += 10000 + 500 * (aPerformance.audience - 20);
            }
            result += 300 * aPerformance.audience;
            break;
            default: throw new Error(`알수 없는 장르: ${aPerformance.play.type}`);
        }
        return result; 
    }
}

volumeCreditsFor()옮기기

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);
        result.play = playFor(result);//중간 데이터에 연극 정보를 저장
        result.amount = amountFor(result);
        result.volumeCredits = volumeCreditsFor(result);
        return result;
    }
    function playFor(aPerformance){ //renderPlainText 중첩함수를 옮김
        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 > 30){
                result += 10000 + 500 * (aPerformance.audience - 20);
            }
            result += 300 * aPerformance.audience;
            break;
            default: throw new Error(`알수 없는 장르: ${aPerformance.play.type}`);
        }
        return result; 
    }
    function volumeCreditsFor(aPerformance){
        let volumeCredits = 0;
        volumeCredits += Math.max(aPerformance.audience -30,0);
        if("comedy" === aPerformance.play.type) 
            volumeCredits += Math.floor(aPerformance.audience/5);
        return volumeCredits;
    }
}

마지막 총합을 구하는 부분 옮기기

 

function statement(invoice, plays) {
    const statementData = {};
    statementData.customer = invoice.customer;
    statementData.performances = invoice.performances.map(enrichPerformance);
    statementData.totalAmount = totalAmount(statementData);
    statementData.totalVolumeCredits = totalVolumeCredits(statementData);
    return renderPlainText(statementData,  plays) 
    function enrichPerformance(aPerformance){
        const result = Object.assign({}, aPerformance);
        result.play = playFor(result);//중간 데이터에 연극 정보를 저장
        result.amount = amountFor(result);
        result.volumeCredits = volumeCreditsFor(result);
        return result;
    }
    function playFor(aPerformance){ //renderPlainText 중첩함수를 옮김
        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 > 30){
                result += 10000 + 500 * (aPerformance.audience - 20);
            }
            result += 300 * aPerformance.audience;
            break;
            default: throw new Error(`알수 없는 장르: ${aPerformance.play.type}`);
        }
        return result; 
    }
    function volumeCreditsFor(aPerformance){
        let volumeCredits = 0;
        volumeCredits += Math.max(aPerformance.audience -30,0);
        if("comedy" === aPerformance.play.type) 
            volumeCredits += Math.floor(aPerformance.audience/5);
        return volumeCredits;
    }
    function totalAmount(data){ 
        return data.performances //반복문을 파이프라인으로 바꿈
            .reduce((total, p) => total + p.volumeCredits, 0 );
    }
    function totalVolumeCredits(data){
        return data.performances //반복문을 파이프라인으로 바꿈
            .reduce((total, p)=>total+p.volumeCredits, 0);
    }
}

statemnet() 필요한 데이터 처리 코드 함수로 빼기

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; 
    }

중간 데이터 부분 함수로 빼기

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; 
}

 

분리된 코드를 각 파일에 나눠서 별도 파일에 저장해 기존 코드를 재사용한다.

나는 실습 편의상 한 파일로 진행했다.

 

1.7 중간 점검: 파일( 단계) 분리됨

//statement.js 파일이라 가정
function statement(invoice, plays) {
    return renderPlainText(createStatementData(invoice, plays));
}
function htmlStatement(invoice, plays){
    return renderHtml(createStatementData(invoice,plays));
    //중간데이터 생성 함수를 공유한다.
}
function renderHtml(data, plays) {
    let result = `<h1>청구 내역 (고객명 : ${data.customer})</h1>\n`;
    result += "<table>\n";
    result += "<tr><th>연극</th><th>좌석 수</th><th>금액</th></tr>"
    for(let perf of data.performances){
        result += `<tr><td>${perf.play.name}</td><td>(${perf.audience}석)</td><td>${usd(perf.amount)}</td></tr>\n`;
    }
    result += "</table>\n";
    result += `<p>총액: <em>${usd(data.totalAmount)}</em></p>\n`; 
    result += `<p>적립 포인트: <em>${data.totalVolumeCredits}</em>점</p>\n`;
    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(data.totalAmount)}\n`; 
    result += `적립 포인트: ${data.totalVolumeCredits}점\n`;
    return result;
}
function usd(aNumber){
    return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2
    }).format(aNumber/100); 
}
//statement.js 파일이라 가정 끝

//createStatementData.js 별도 파일이라 가정
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; 
    function enrichPerformance(aPerformance){
        const result = Object.assign({}, aPerformance);
        result.play = playFor(result);
        result.amount = amountFor(result);
        result.volumeCredits = volumeCreditsFor(result);
        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 > 30){
                result += 10000 + 500 * (aPerformance.audience - 20);
            }
            result += 300 * aPerformance.audience;
            break;
            default: throw new Error(`알수 없는 장르: ${aPerformance.play.type}`);
        }
        return result; 
    }
    function volumeCreditsFor(aPerformance){
        let volumeCredits = 0;
        volumeCredits += Math.max(aPerformance.audience -30,0);
        if("comedy" === aPerformance.play.type) 
            volumeCredits += Math.floor(aPerformance.audience/5);
        return volumeCredits;
    }
    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);
    }
}
//createStatementData.js 별도 파일이라 가정 끝

전체 로직을 구서하는 요소들이 뚜렸하게 분리되면서 파악이 쉬워졌다.

그리고 모듈화하면서 html버전도 재사용으로 쉽게 만들었다.

프로그래밍에선 명료함이 중요하다.

 

 

 

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

amountFor()함수를 보면 연극 장르에 따라 계산 방식이 달라진다.

이런 조건부 로직은 코드 수정 획수가 늘어날수록 골칫거기로 전락한다.

 

"조건부 로직을 다형성으로 바꾸기"

리팩터링은 조건부 코드 덩어리를 다형성을 활용하는 방식으로 바꿔준다.

리팩터링을 적용하려면 상속 계층부터 정의해야 한다.

 

공연료 계산기 만들기

여기서 핵심은 각 공연 정보를 중간 데이터 구조에 채워주는 enrichPerformance()함수

이 함수는 조건부 로직을 포함한 함수인 amountFor()와 volumeCreditsFor()를 호출한다.

두 함수를 전용 클래스로 옮기는 작업을 수행한다.

 

createStatementData()함수속
...
    function enrichPerformance(aPerformance){
        const calculator = new PerformanceCalculator(aPerformance); //공연료 계산기 생성
        const result = Object.assign({}, aPerformance);
        result.play = playFor(result);
        result.amount = amountFor(result);
        result.volumeCredits = volumeCreditsFor(result);
        return result;
    }


최상위
class PerformanceCalculator{
    constructor(aPerformance){
        this.performance = aPerformance;
    }
}

함수들을 계산기로 옮기기

//createStatementData.js 별도 파일이라 가정

class PerformanceCalculator{
    constructor(aPerformance, aPlay){
        this.performance = aPerformance;
        this.play = aPlay;
    }
    get amount(){
        let result = 0;
        switch(this.play.type){
            case "tragedy" : 
            result = 40000;
            if(this.performance.audience > 30){
                result += 1000 * (this.performance.audience - 30);
            }
            break;
            case "comedy" : 
            result = 30000;
            if(this.performance.audience > 30){
                result += 10000 + 500 * (this.performance.audience - 20);
            }
            result += 300 * this.performance.audience;
            break;
            default: throw new Error(`알수 없는 장르: ${this.play.type}`);
        }
        return result; 
    }
    get volumeCredits(){
        let result = 0;
        result += Math.max(this.performance.audience -30,0);
        if("comedy" === this.play.type) 
        result += Math.floor(this.performance.audience/5);
        return result;
    }
}
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; 
    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 playFor(aPerformance){ 
        return plays[aPerformance.playID];
    }

    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);
    }
}
//createStatementData.js 별도 파일이라 가정 끝

 

 

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

클래스에 로직을 담았으니 다형성을 지원하도록 만든다.

먼저 타입 코드를 서브클래스로 바꾸기를 진행한다.

 

그러기 위해선 클래스를 직접적으로 생성하는 코드를 팩토리 함수를 호출하게 하여 의존성을 낮춰야 한다.


class PerformanceCalculator{
    constructor(aPerformance, aPlay){
        this.performance = aPerformance;
        this.play = aPlay;
    }
    get amount(){
        return new Error('서브클래스에서 처리하도록 설계되었습니다.');
    }
    get volumeCredits(){
        let result = 0;
        result += Math.max(this.performance.audience -30,0);
        if("comedy" === this.play.type) 
        result += Math.floor(this.performance.audience/5);
        return result;
    }
}
class TragedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 40000;
        if(this.performance.audience > 30){
            result += 1000 * (this.performance.audience - 30);
        }
        return result;
    }
}
class ComedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 30000;
        if(this.performance.audience > 30){
            result += 10000 + 500 * (this.performance.audience - 20);
        }
        result += 300 * this.performance.audience;
        return result;
    }
}
function createPerformanceCalculator(aPerformance, aPlay){
    switch(aPlay.type){
        case "tragedy" : return new TragedyCalcultor(aPerformance,aPlay);
        case "comedy" : return new ComedyCalcultor(aPerformance,aPlay);
        default: throw new Error(`알 수  없는 장르 : ${aPlay.type}`);
    }
}

 

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

//createStatementData.js 별도 파일이라 가정

class PerformanceCalculator{
    constructor(aPerformance, aPlay){
        this.performance = aPerformance;
        this.play = aPlay;
    }
    get amount(){
        return new Error('서브클래스에서 처리하도록 설계되었습니다.');
    }
    get volumeCredits(){
        return Math.max(this.performance.audience -30,0);
    }
}
class TragedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 40000;
        if(this.performance.audience > 30){
            result += 1000 * (this.performance.audience - 30);
        }
        return result;
    }
}
class ComedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 30000;
        if(this.performance.audience > 30){
            result += 10000 + 500 * (this.performance.audience - 20);
        }
        result += 300 * this.performance.audience;
        return result;
    }
    get volumeCredits(){
        return super.volumeCredits + Math.floor(this.performance.audience/5);
    }
}
function createPerformanceCalculator(aPerformance, aPlay){
    switch(aPlay.type){
        case "tragedy" : return new TragedyCalcultor(aPerformance,aPlay);
        case "comedy" : return new ComedyCalcultor(aPerformance,aPlay);
        default: throw new Error(`알 수  없는 장르 : ${aPlay.type}`);
    }
}
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; 
    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;
    }
    function playFor(aPerformance){ 
        return plays[aPerformance.playID];
    }

    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);
    }
}
//createStatementData.js 별도 파일이라 가정 끝

조건부 로직을 생성 함수하나로 옮겼다.

연극 장르별 계산 코드들을 함께 묶었다.

 

1.10 마치며

리팩터링을 크게 3 단계로 진행했다.

1단계 : 원본 함수를 중첩 함수 여러개로 나누기

2단계 : 쪼개기를 적용, 계산 코드/출력 코드 분리

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

 

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

코드를 수정할 때 고쳐야 할 곳을 쉽게 찾고, 오류 없이 빠르게 수정할 수 있어야 한다.

고객의 요구사항을 빠르게 반영할 수 있어야 한다.

 

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
<script src="plays.json" type="text/javascript"></script>
<script src="invoices.json" type="text/javascript"></script>
<script>



//statement.js 파일이라 가정
function statement(invoice, plays) {
    return renderPlainText(createStatementData(invoice, plays));
}
function htmlStatement(invoice, plays){
    return renderHtml(createStatementData(invoice,plays));
    //중간데이터 생성 함수를 공유한다.
}
function renderHtml(data, plays) {
    let result = `<h1>청구 내역 (고객명 : ${data.customer})</h1>\n`;
    result += "<table>\n";
    result += "<tr><th>연극</th><th>좌석 수</th><th>금액</th></tr>"
    for(let perf of data.performances){
        result += `<tr><td>${perf.play.name}</td><td>(${perf.audience}석)</td><td>${usd(perf.amount)}</td></tr>\n`;
    }
    result += "</table>\n";
    result += `<p>총액: <em>${usd(data.totalAmount)}</em></p>\n`; 
    result += `<p>적립 포인트: <em>${data.totalVolumeCredits}</em>점</p>\n`;
    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(data.totalAmount)}\n`; 
    result += `적립 포인트: ${data.totalVolumeCredits}점\n`;
    return result;
}

function usd(aNumber){
    return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 2
    }).format(aNumber/100); 
}
//statement.js 파일이라 가정 끝


//createStatementData.js 별도 파일이라 가정


class PerformanceCalculator{
    constructor(aPerformance, aPlay){
        this.performance = aPerformance;
        this.play = aPlay;
    }
    get amount(){
        return new Error('서브클래스에서 처리하도록 설계되었습니다.');
    }
    get volumeCredits(){
        return Math.max(this.performance.audience -30,0);
    }
}
class TragedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 40000;
        if(this.performance.audience > 30){
            result += 1000 * (this.performance.audience - 30);
        }
        return result;
    }
}
class ComedyCalcultor extends PerformanceCalculator{
    get amount(){
        let result = 0;
        result = 30000;
        if(this.performance.audience > 30){
            result += 10000 + 500 * (this.performance.audience - 20);
        }
        result += 300 * this.performance.audience;
        return result;
    }
    get volumeCredits(){
        return super.volumeCredits + Math.floor(this.performance.audience/5);
    }
}

function createPerformanceCalculator(aPerformance, aPlay){
    switch(aPlay.type){
        case "tragedy" : return new TragedyCalcultor(aPerformance,aPlay);
        case "comedy" : return new ComedyCalcultor(aPerformance,aPlay);
        default: throw new Error(`알 수  없는 장르 : ${aPlay.type}`);
    }
}

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; 

    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;
    }
    function playFor(aPerformance){ 
        return plays[aPerformance.playID];
    }
    
    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);
    }
}

//createStatementData.js 별도 파일이라 가정 끝

console.log(statement(invoices,plays));
document.write(htmlStatement(invoices,plays));
</script>
</body>
</html>

 

 

 

 

'IT책, 강의 > 리팩터링' 카테고리의 다른 글

03 - 코드에서 나는 악취  (0) 2023.07.10
02 - 리팩터링 원칙 - 02  (0) 2023.07.08
02 - 리팩터링 원칙 - 01  (0) 2023.07.06
01 - 리팩터링: 첫 번째 예시 - 01  (0) 2023.07.02
00 - 들어가며  (0) 2023.07.01

+ Recent posts