다양한 연극을 외주로 받아 공연하는 극단이 있다.
공연요청이 들어오면 연극의 장르와 관객 규모를 기초로 비용을 책정한다.
공연료와 별개로 포인트를 지급해 다음번 의뢰 시 공연료를 할인받을 수도 있다.
plays.json
{
"hamlet": {"name": "Hamlet", "type": "tragedy"}
"as-like": {"name": "As You Like It", "type": "comedy"}
"othello": {"name": "Othello", "type": "tragedy"}
}
invoices.json
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
},
]
}
]
export 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 = 40_000;
if (perf.audience > 30) {
thisAmount += 1_000 * (perf.audience - 30);
}
break;
case 'comedy':
thisAmount = 30_000;
if (perf.audience > 20) {
thisAmount += 10_000 + 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;
}
volumeCredits 변수를 제거하는 작업 단계
1. 반복문 쪼개기로 변수 값을 누적시키는 부분을 분리한다.
2. 문장 슬라이드하기로 변수 초기화 문장을 변수 값 누적 코드 바로 앞으로 옮기고
3. 함수 추출하기로 적립 포인트 계산 부분을 별도 함수로 추출한다.
4. 변수 인라인하기로 volumeCredits 변수를 제거한다.
export 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 = 40_000;
if (aPerformance.audience > 30) {
result += 1_000 * (aPerformance.audience - 30);
}
break;
case 'comedy':
result = 30_000;
if (aPerformance.audience > 20) {
result += 10_000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`알 수 없는 장르: ${aPerformance.play.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) {
return data.performances.reduce((acc, cur) => acc + cur.amount, 0);
}
function totalVolumeCredits(data) {
return data.performances.reduce((acc, cur) => acc + cur.volumeCredits, 0);
}
}
import { createStatementData } from './createStatementData.js';
export function statement(invoice, plays) {
return renderPlainText(createStatementData(invoice, plays));
}
function renderPlainText(data) {
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 htmlStatement(invoice, plays) {
return renderHtml(createStatementData(invoice, plays));
}
function renderHtml(data) {
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>`;
result += `<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 usd(aNumber) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 2 }).format(
aNumber / 100
);
}
dumplicate code