1.6 계산 단계와 포맷팅 단계 분리하기
목표
statement()
의 HTML 버전 만드는 작업
단계 쪼개기
statement()
에 필요한 데이터 처리하기 → 두 번째 단계로 전달할 중간 데이터 구조를 생성하는 것앞서 처리한 결과를 텍스트나 HTML로 표현하기
0. 단계를 쪼개려면 먼저 두 번째 단계가 될 코드들을 함수 추출하기로 뽑아내야 한다. 현재 statement()의 본문 전체가 여기에 해당한다.
function statement(invoice, plays) {
return renderPlainText(invoice, plays); // 본문 전체를 별도 함수로 추출
}
function renderPlainText(invoice, plays) {
let result = `청구 내역(고객명): ${invocie.customer})\n`;
for (let perf of invoice.performaces) {
// 청구 내역을 출력한다.
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} ($perf.audience}석)\n`;
result += `총액: ${usd(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
function totalAmount() {...}
function totalVolumeCredits() {...}
function usd(aNumber) {...}
function volumeCreditsFor(aPerformance) {...}
function playFor(aPerformance) {...}
function amountFor(aPerformance) {...}
}
항상 수정한 코드는 컴파일-테스트-커밋한다.
다음으로 두 단계 사이의 중간 데이터 구조 역할을 할 객체를 만들어서 renderPlainText()
에 인수로 전달한다.
function statement(invoice, plays) {
const statementData = {};
return renderPainText(statementData, invoice, plays); // 중간 데이터 구조를 인수로 전달
}
funtion renderPlainText(data, invoice, plays) { // 중간 데이터 구조를 인수로 전달
let result = `청구 내역(고객명): ${invocie.customer})\n`;
for (let perf of invoice.performaces) {
// 청구 내역을 출력한다.
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} ($perf.audience}석)\n`;
result += `총액: ${usd(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
function totalAmount() {...}
function totalVolumeCredits() {...}
function usd(aNumber) {...}
function volumeCreditsFor(aPerformance) {...}
function playFor(aPerformance) {...}
function amountFor(aPerformance) {...}
renderPlainText()
의 다른 두 인수(invoice와 plays)를 살펴보자. 이 인수들을 통해 전달되는 데이터를 중간 데이터 구조로 옮기면, 계산 관련 코드는 statement()
함수로 모으고 renderPlainText()
는 data 매개변수로 전달된 데이터만 처리하게 만들 수 있다.
가장 먼저 고객 정보부터 중간 데이터 구조로 옮긴다.
function statement(invoice, plays) {
const statementData = {};
statementData.customer = invoice.customer; // 고객 데이터를 중간 데이터로 옮김
return renderPainText(statementData, invoice, plays);
}
funtion renderPlainText(data, invoice, plays) {
let result = `청구 내역(고객명): ${data.customer})\n`; // 고객 데이터를 중간 데이터로부터 얻음
for (let perf of invoice.performaces) {
// 청구 내역을 출력한다.
result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} ($perf.audience}석)\n`;
result += `총액: ${usd(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
같은 방식으로 공연 정보까지 중간 데이터 구조로 옮기고 나면 renderPlainText()
의 invoice
매개변수를 삭제해도 된다.
function statement(invoice, plays) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances; // 공연 정보를 중간 데이터로 옮김
return renderPainText(statementData, ~~invoice~~, plays); // 필요 없어진 인수 삭제
}
funtion 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(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
function totalAmount() {
let result = 0;
for (let perf of data.performances) {
totalAmount += amountFor(perf);
}
return result;
}
function totalVolumeCredits() {
let voluemCredits = 0;
for (let perf of data.performances) {
volumeCredits += volumeCreditsFor(perf);
}
return volulmeCredits;
}
연극 제목도 중간 데이터 구조에서 가져오도록 한다. 이를 위해 공연 정보 레코드에 연극 데이터를 추가해야 한다.
function statement(invoice, plays) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances;
return renderPainText(statementData, plays);
}
function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance); // 얕은 복사 수행
return result;
}
공연 객체를 복사만 했지만(함수로 건넨 데이터를 수정하기 싫어서) 잠시 후 새로 만든 레코드에 데이터를 채울 것이다. 가변 데이터는 금방 상하기 때문에 데이터를 최대한 불변(immutable)처럼 취급한다.
실제로 데이터를 담아보자
함수 옮기기를 적용하여 playFor()
함수를 statement()
로 옮긴다
playFor()
함수를 statement()
로 옮긴다function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance);
result.play = playFor(result); // 중간 데이터에 연극 정보를 저장
return result;
}
function playFor(aPerformance) { // renderPlainText()의 중첩함수였던 playFor()를 statement()로 옮김
return plays[aPerformance.playID];
}
renderPlainText()
안에서 playFor()
를 호출하던 부분을 중간 데이터를 사용하도록 바꾼다.
let result = `청구 내역(고객명): ${data.customer})\n`;
for (let perf of data.performances) {
result += ` ${perf.play.name} ${usd(amountFor(perf))} ($perf.audience}석)\n`;
// 중간 데이터 사용
result += `총액: ${usd(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
function volumeCreditsFor(aPerformance) {
let result = 0;
volumeCredits += Math.max(aPerformance.audience - 30, 0);
if ("comedy" === playFor(aPerformance).type)
volumeCredits += Math.Floor(aPerformance.audience / 5 );
return result;
}
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerfomance).type) {
case "tragedy":
result = 40000;
if (perf.audience > 30) {
result += 1000 * (perf.audience - 30);
}
break;
case "comedy":
result = 30000;
if (perf.audience > 20) {
result += 1000 + 500 * (perf.audience - 20);
}
result += 300 * perf.audience
break;
default:
throw new Error(`알 수 없는 장르: ${playFor(aPerfomance).type}`);
}
return result;
}
amountFor()
도 비슷한 방법으로 옮긴다.
function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance);
result.play = playFor(result);
result.amount = amountFor(result); // 중간 데이터 사용
return result;
}
function amountFor(aPerformance) {...}
let result = `청구 내역(고객명): ${invocie.customer})\n`;
for (let perf of invoice.performaces) {
result += ` ${playFor(perf).name}: ${usd(perf.amount)} ($perf.audience}석)\n`; // 중간 데이터
result += `총액: ${usd(totalAmount())}\n`;
result += `적립 포인트: ${totalVolumeCredits()}점\n`;
return result;
function totalAmount() {
let result = 0;
for (let perf of data.performances) {
result += perf.amount; // 중간 데이터
}
return result;
}
적립 포인트 계산 부분을 옮긴다.
function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance);
result.play = playFor(result);
result.amount = amountFor(result);
result.volumeCredits = volumeCreidtsFor(result);
return result;
}
function totalVolumeCredits() {
let result = 0;
for (let perf of data.performances) {
result += perf.voluemCredits;
}
return result;
}
총합을 구하는 부분을 옮긴다.
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances.map(enrichPerformance);
statementData.totalAmount = totalAmount(statmentData);
statementData.totalVolumeCredits = totalVolumeCredits(statementData);
return renderPlainText(statementData, plays)
function totalAmount(data) {...}
function totalVolumeCredits(data) {...}
let result = `청구 내역(고객명): ${data.customer})\n`;
for (let perf of data.performaces) {
result += ` ${playFor(perf).name}: ${usd(perf.amount)} ($perf.audience}석)\n`;
result += `총액: ${usd(data.totalAmount)}\n`;
result += `적립 포인트: ${data.totalVolumeCredits}점\n`;
return result;
totalAmount()
와 totalVolumeCredits()
를 직접 statementData 변수를 사용할 수도 있지만 저자는 매개변수로 전달하는 방식을 선호한다.
반복문을 파이프라인으로 바꾸기를 적용해보자
function totalAmount(data) {
return data.performances // for 반복문을 파이프라인으로 바꿈
.reduce((total, p) => total + p.amount, 0);
}
function totalVolumeCredits(data) {
return data.performances
.reduce((total, p) => total + p.volumeCredits, 0); // for 반복문을 파이프라인으로 바꿈
📍 첫 단계인 statement()
에 필요한 데이터 처리에 해당하는 코드를 모두 별도 함수로 빼낸다.
statement()
에 필요한 데이터 처리에 해당하는 코드를 모두 별도 함수로 빼낸다.function statement(invoice, plays) {
return renderPlainText(createStatementData(invoice, plays));
}
function createdStatementData(invoice, plays) { // 중간 데이터를 전달
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances.map(enrichPerformance);
statementData.totalAmount = totalAmount(statmentData);
statementData.totalVolumeCredits = totalVolumeCredits(statementData);
return statementData;
두 단계가 명확히 분리 됐으니 각 코드를 별도 파일에 저장한다. 반환 결과를 저장할 변수의 이름도 바꿔준다.
// statement.js
import createStatementData from './createStatementData.js';
// createStatementData.js
export default 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) {...}
function playFor(aPerformance) {...}
function amountFor(aPerformance) {...}
function volumeCreditsFor(aPerformance) {...}
function totalAmount(data) {...}
function totalVolumeCredits(data) {...}
⭐️ 마지막으로 컴파일-테스트-커밋하면 HTML 버전을 작성할 준비가 끝난다.
// statement.js
function htmlStatment(invoice, plays) {
return renderHtml(createStatementData(invoice, plays)); // 중간 데이터 생성 함수를 공유
}
function renderHtml(data) {
let result = `<h1>청구 내역(고객명): ${data.customer})</h1>\n`;
result += "<table>\n";
...
return result;
Last updated