리팩터링 2판

octofox·2022년 3월 3일
0

리팩터링 2판

목록 보기
1/1

리팩터링 2판을 잠시 읽어 보았다.
처음 단계에서 거의 모든 내용이 나온다.

새롭게 하나 배운 점은 리팩토링과 성능 중 어느 것이 더 중요하나?의 관점이었다.
나는 이제 것 자연스럽게 하나의 루프문에 처리 할 수 있는 모든 로직을 넣었었다.
하지만 이 책에서는 리팩터링을 위해 몇번의 루프문을 나누어서 처리한다.
루프문의 성능이 매우 좋아 체감되는 것이 없다는 설명이었다.
모든 개발자가 이 이야기에 찬성하는지는 모르겠다.
아마 이터러블의 길이가 너무 길다면 그런식의 리팩토링은 힘들겠지.

또 하나는 과하다 싶을 정도로 함수 추출을 많이 한다.
함수의 이름을 잘 짓는 것이 중요하다.
내포 함수도 많이 사용한다. 그리고 임시 변수를 싫어한다.
인라인 함수를 좋아한다.
데이터 준비단계와 출력단계를 나누어 한번의 데이터 준비 로직으로 두가지의 아웃풋을 낼 수 있도록 작업한다.

50줄이었던 코드가 75줄로 늘어나는데 보기에는 75줄의 코드가 더 잘 읽히는 신기한 경험이다.
컴퓨터가 읽기 좋은 코드가 아닌 사람이 읽기에 좋은 코드를 작성해야 한다.

비교를 위해 두개의 코드를 올려본다.

// 공연 정보
{
	"hamlet": {
		"name": "hamlet",
		"type": "tragedy"
	},
	"as-like": {
		"name": "As You Like It",
		"type": "comedy"
	},
	"othello": {
		"name": "Othello",
		"type": "tragedy"
	}
}
[
  {
    "customer": "BigCo",
    "performances": [
      {
        "playID": "hamlet",
        "audience": 55
      },
      {
        "playID": "as-like",
        "audience": 35
      },
      {
        "playID": "othello",
        "audience": 40
      }
    ]
  }
]

정리되지 않은 코드

import invoices from './invoices.json' assert { type: 'json' };
import plays from './plays.json' assert { type: 'json' };

console.log(statement(invoices[0], plays));

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', maximumFractionDigits: 2 })
        .format;

    for (let perf of invoice.performances) {
        const play = plays[perf.playID];
        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 += 10000 + 500 * (perf.audience - 20);
                }
                thisAmount += 300 * perf.audience;
                break;

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

        // 포인트를 적립한다.
        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;
}

정리된 코드

import invoices from './invoices.json' assert { type: 'json' };
import plays from './plays.json' assert { type: 'json' };

console.log(statement(invoices[0], plays));

function statement(invoice, plays) {
    function createStatementData(invoice, plays) {
        const result = {};
        result.customer = invoice.customer;
        result.performances = invoice.performances.map(enrichPerformance); // 함수 호출문이 생략됬지만 인자가 넘어감 
        result.totalAmount = totalAmount(result);
        result.totalVolumeCredits = totalVolumeCredits(result);
        return result;
    }
    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 > 20) {
                    result += 10000 + 500 * (aPerformance.audience - 20);
                }
                result += 300 * aPerformance.audience;
                break;

            default:
                throw new Error(`알 수 없는 장르: ${playFor(aPerformance).type}`);
        }
        return result;
    }
    function volumeCreditsFor(aPerformance) {
        let result = 0;

        result += Math.max(aPerformance.audience - 30, 0);

        if ('comedy' === aPerformance.play.type) {
            result += Math.floor(aPerformance.audience / 5);
        }

        return result;
    }
    function totalAmount(data) {
        let result = 0;
        for (let perf of data.performances) {
            result += perf.amount;
        }
        return result;
    }
    function totalVolumeCredits(data) {
        let result = 0;
        for (let perf of data.performances) {
            result += perf.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(data.totalAmount)}\n`;
        result += `적립 포인트 ${data.totalVolumeCredits}점\n`;

        return result;

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

    }
    return renderPlainText(createStatementData(invoice, plays));
}
profile
개발자라고 우기는 노답 소년

0개의 댓글