1.1 자, 시작해보자!
극단은 공연할 연극 정보를 다음과 같이 간단한 JSON 파일에 저장한다.
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
},
]
}
]
공연료 청구서를 출력하는 코드는 다음과 같이 간단히 함수로 구현했다.
let plays = {
hamlet: { name: "Hamlet", type: "tragedy" },
"as-like": { name: "As You Like It", type: "comedy" },
othello: { name: "Othello", type: "tragedy" },
};
let invoices = {
customer: "BigCo",
performances: [
{
playID: "hamlet",
audience: 55,
},
{
playID: "as-like",
audience: 35,
},
{
playID: "othello",
audience: 55,
},
],
};
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",
minimumFractionDigits: 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 += 1000 + 500 * (perf.audience - 20);
}
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}점`;
return result;
}
console.log(statement(invoices, plays));
이 코드에 앞의 두 테스트 데이터 파일(plays.json과 Invocies.json)을 입력해 실행한 결과는 다음과 같다.
청구 내역(고객명: BigCo)
Hamlet: $650.00 (55석)
}As You Like It: $385.00 (35석)
}Othello: $650.00 (55석)
}총액: $1,685.00
}적립 포인트: 62점
1.2 예시 프로그램을 본 소감
수백 줄짜리 코드를 수정할 때면 먼저 프로그램의 작동 방식을 더 쉽게 파악할 수 있도록 코드를 여러 함수와 프로그램 요소로 재구성한다.
프로그램의 구조가 빈약하다면 대체로 구조부터 바로잡은 뒤에 기능을 수정하는 편이 작업하기가 훨씬 수월하다.
프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 형태로 리팩터링하고 나서 원하는 기능을 추가한다.
수정해야 할 부분
1. 청구 내역을 HTML로 출력하는 기능이 필요
2. 연극 장르와 공연료 정책이 달라질 때마다 statement() 함수를 수정해야 한다. 만약 statement()를 복사해서 별도의 htmlStatement()를 만든다면 모든 수정이 두 함수에 일관되게 반영되도록 보장해야 한다.
1.4 statement() 함수 쪼개기
statement()처럼 긴 함수를 리팩터링할 때는 먼저 전체 동작을 각각의 부분으로 나눌 수 있는 지점을 찾는다. 그러면 중간 즈음의 switch문이 가장 먼저 눈에 띌 것이다.
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);
}
break;
default:
throw new Error(`알 수 없는 장르: ${play.type}`);
}
return result; // <-- 함수 안에서 값이 바뀌는 변수 반환
}
statement()에서는 thisAmount 값을 채울 때 방금 추출한 amountFor() 함수를 호출한다.
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",
minimumFractionDigits: 2,
}).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = amountFor(perf, play); // <-- 추출한 함수를 사용
// 포인트를 적립한다.
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}점`;
return result;
}
리팩터링은 프로그램 수정을 작은 단계로 나눠 진행한다. 그래서 중간에 실수하더라도 버그를 쉽게 찾을 수 있다.
1.4.1 변수이름 바꾸기
함수를 추출하고 나면 추출된 함수 코드를 자세히 들여다보면서 지금보다 명확하게 표현할 수 있는 간단한 방법은 없는지 검토한다. 가장 먼저 변수의 이름을 더 명확하게 바꿔보자. 가령 thisAmount를 result로 변경할 수 있다. 다음은 첫 번째 인수인 perf를 aPerformance로 리팩터링해보자.
function amountFor(aPerformance, play) {
// <-- 명확한 이름으로 변경
let result = 0; // <-- 명확한 이름을 변경
switch (play.type) {
case "tragedy": // 비극
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy": // 희극
thisAmount = 30000;
if (aPerformance.audience > 20) {
result += 1000 + 500 * (aPerformance.audience - 20);
}
break;
default:
throw new Error(`알 수 없는 장르: ${play.type}`);
}
return result;
}
컴퓨터가 이해하는 코드는 바보도 작성할 수 있다. 사람이 이해하도록 작성하는 프로그래머가 진정한 실력자다.
1.4.2 play 변수 제거하기
임시 변수를 질의 함수로 바꾸기
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
변수 인라인하기를 적용한다.
function statement(invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `청구 내역(고객명: ${invoice.customer})\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
}).format;
for (let perf of invoice.performances) {
let thisAmount = amountFor(perf, playFor(perf)); // <-- 변수 인라인
// 포인트를 적립한다.
volumeCredits += Math.max(perf.audience - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy" === playFor(perf).type) // <-- 변수 인라인
volumeCredits += Math.floor(perf.audience / 5);
//청구 내역을 출력한다.
result += `${playFor(perf).name}: ${format(thisAmount / 100)} (${
// <-- 변수 인라인
perf.audience
}석)\n}`;
totalAmount += thisAmount;
}
result += `총액: ${format(totalAmount / 100)}\n}`;
result += `적립 포인트: ${volumeCredits}점`;
return result;
}
변수를 인라인한 덕분에 amountFor()에 함수 선언 바꾸기를 적용해서 play 매개변수를 제거할 수 있게 되었다. 이 작업은 두 단계로 진행한다. 먼저 새로만든 playFor()를 사용하도록 amountFor()를 수정한다. 그리고 play 매개변수를 삭제한다.
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": // 희극
thisAmount = 30000;
if (aPerformance.audience > 20) {
result += 1000 + 500 * (aPerformance.audience - 20);
}
break;
default:
throw new Error(`알 수 없는 장르: ${playFor(aPerformance).type}`);
}
return result;
}
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",
minimumFractionDigits: 2,
}).format;
for (let perf of invoice.performances) {
let thisAmount = amountFor(perf); // <-- 필요 없어진 매개변수 제거
// 포인트를 적립한다.
volumeCredits += Math.max(perf.audience - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy" === playFor(perf).type)
volumeCredits += Math.floor(perf.audience / 5);
//청구 내역을 출력한다.
result += `${playFor(perf).name}: ${format(thisAmount / 100)} (${perf.audience}석)\n}`;
totalAmount += thisAmount;
}
result += `총액: ${format(totalAmount / 100)}\n}`;
result += `적립 포인트: ${volumeCredits}점`;
return result;
}
1.4.3 지역 변수 제거
amountFor()에 전달할 인수를 모두 처리했으니, 이 함수를 호출하는 코드로 돌아가보자, 여기서 amountFor()는 임시 변수인 thisAmount에 값을 설정하는 데 사용되는데, 그 값이 다시 바뀌지는 않는다. 따라서 변수 인라인하기를 적용한다.
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",
minimumFractionDigits: 2,
}).format;
for (let perf of invoice.performances) {
// 포인트를 적립한다.
volumeCredits += Math.max(perf.audience - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy" === playFor(perf).type)
volumeCredits += Math.floor(perf.audience / 5);
// 청구 내역을 출력한다.
// thisAmount 변수를 인라인
result += `${playFor(perf).name}: ${format(amountFor(perf) / 100)} (${perf.audience}석)\n}`;
totalAmount += amountFor(perf);
}
result += `총액: ${format(totalAmount / 100)}\n}`;
result += `적립 포인트: ${volumeCredits}점`;
return result;
}
'Programming > JavaScript+CSS' 카테고리의 다른 글
[JavaScript] 자식 창에서 부모 창 새로고침 (0) | 2024.09.20 |
---|---|
리팩터링 원칙 (2) | 2024.09.11 |
자바스크립트 map() 함수 (0) | 2024.08.31 |
자바스크립트 concat() 함수 배열 합치기 (0) | 2024.08.29 |
자바스크립트 forEach() 함수 (0) | 2024.08.29 |