반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 디자인패턴
- 테스트코드책
- formik submitting not working
- 전략패턴
- git squash
- 가상면접3장
- 시스템설계면접예시
- 시스템설계면접
- 시스템설계면접팁
- cypress React
- Git commit 합치기
- 리액트구글애널리틱스
- react-ga
- cypressBDD
- 리팩토링2판4장
- 시스템설계방법
- formik react-query submitting not working
- awss3
- file not found Error
- FirebaseAnalytics
- git commit 협업
- git commit merge
- 리팩터링2판테스트
- 가상면접2장
- react
- s3이미지다운로드됨
- 시스템설계
- 가상면접으로대규모시스템
- gitsquash
- 헤드퍼스트전략패턴
Archives
- Today
- Total
mingg IT
[리팩토링] 리팩토링 2판 1장 리뷰 본문
리팩토링 2판
- 예시 1번
- 프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 형태로 리팩토링하고 나서 원하는 기능을 추가한다.리팩토링 하기 전에 제대로 된 테스트부터 마련해야 한다. 테스트는 반드시 자가진단하도록 만들어야함.컴퓨터가 이해하는 코드는 바보도 작성할 수 있다. 사람이 이해하도록 작성하는 프로그래머가 진정한 실력자다.
- 1단계 함수 쪼개기.
switch 문안을 새로운 함수를 사용한다.function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; 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); //포인트를 적립한다. volumnCredits += Math.max(perf.audience - 30, 0); if ("comedy" === play.type) volumnCredits += Math.floor(perf.audience / 5); //청구 내역을 출력한다. result += `${play.name} : ${format(thisAmount / 100)} (${perf.audience}석)`; totalAmount += thisAmount; } result += `총액 : ${format(totalAmount / 100)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } 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); } thisAmount += 300 * perf.audience; break; default: throw new Error(`알수 없는 장르 : ${play.type}`); } return thisAmount; } console.log(statement(invoice, plays));
- 변수 인라인, playFor 함수를 사용함.
let invoice = { customer: "BigCo", performances: [ { playID: "hamlet", audience: 55, }, { playID: "as-like", audience: 35, }, { playID: "othello", audience: 40, }, ], }; let plays = { hamlet: { name: "Hamlet", type: "tragedy" }, "as-like": { name: "As ou Like It", type: "comedy" }, othello: { name: "othello", type: "tragedy" }, }; function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; const format = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format; for (let perf of invoice.performances) { //포인트를 적립한다. volumnCredits += Math.max(perf.audience - 30, 0); if ("comedy" === playFor(perf).type) volumnCredits += Math.floor(perf.audience / 5); //청구 내역을 출력한다. result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${ perf.audience }석)`; totalAmount += amountFor(perf); } result += `총액 : ${format(totalAmount / 100)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } 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": result = 30000; if (aPerformance.audience > 20) { result += 1000 + 500 * (aPerformance.audience - 20); } result += 300 * aPerformance.audience; break; default: throw new Error(`알수 없는 장르 : ${playFor(aPerformance).type}`); } return result; } console.log(statement(invoice, plays));
- volumnCreditsFor 함수 만들어서 volumnCredits 계산하도록
let invoice = { customer: "BigCo", performances: [ { playID: "hamlet", audience: 55, }, { playID: "as-like", audience: 35, }, { playID: "othello", audience: 40, }, ], }; let plays = { hamlet: { name: "Hamlet", type: "tragedy" }, "as-like": { name: "As ou Like It", type: "comedy" }, othello: { name: "othello", type: "tragedy" }, }; function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; const format = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format; for (let perf of invoice.performances) { volumnCredits = volumnCreditsFor(perf); //청구 내역을 출력한다. result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${ perf.audience }석)`; totalAmount += amountFor(perf); } result += `총액 : ${format(totalAmount / 100)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } function volumnCreditsFor(aPerformance) { let result = 0; result += Math.max(aPerformance.audience - 30, 0); if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5); return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } 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": result = 30000; if (aPerformance.audience > 20) { result += 1000 + 500 * (aPerformance.audience - 20); } result += 300 * aPerformance.audience; break; default: throw new Error(`알수 없는 장르 : ${playFor(aPerformance).type}`); } return result; } console.log(statement(invoice, plays));
- format 부분 함수화 시시킴
// const format = new Intl.NumberFormat("en-US", { // style: "currency", // currency: "USD", // minimumFractionDigits: 2, // }).format;let invoice = { customer: "BigCo", performances: [ { playID: "hamlet", audience: 55, }, { playID: "as-like", audience: 35, }, { playID: "othello", audience: 40, }, ], }; let plays = { hamlet: { name: "Hamlet", type: "tragedy" }, "as-like": { name: "As ou Like It", type: "comedy" }, othello: { name: "othello", type: "tragedy" }, }; function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; for (let perf of invoice.performances) { volumnCredits = volumnCreditsFor(perf); //청구 내역을 출력한다. result += `${playFor(perf).name} : ${format(amountFor(perf) / 100)} (${ perf.audience }석)`; totalAmount += amountFor(perf); } result += `총액 : ${format(totalAmount / 100)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } function format(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber); } function volumnCreditsFor(aPerformance) { let result = 0; result += Math.max(aPerformance.audience - 30, 0); if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5); return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } 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": result = 30000; if (aPerformance.audience > 20) { result += 1000 + 500 * (aPerformance.audience - 20); } result += 300 * aPerformance.audience; break; default: throw new Error(`알수 없는 장르 : ${playFor(aPerformance).type}`); } return result; } console.log(statement(invoice, plays));
- /100 해주는 부분을 함수안으로 이동시킴.
// 변경 전 function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; for (let perf of invoice.performances) { volumnCredits = volumnCreditsFor(perf); //청구 내역을 출력한다. result += `${playFor(perf).name} : ${usd(amountFor(perf) / 100)} (${ perf.audience }석)`; totalAmount += amountFor(perf); } result += `총액 : ${usd(totalAmount / 100)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber); } //변경 후 function statement(invoice, plays) { let totalAmount = 0; let volumnCredits = 0; let result = `청구 내역(고객명 : ${invoice.customer})`; for (let perf of invoice.performances) { volumnCredits = volumnCreditsFor(perf); //청구 내역을 출력한다. result += `${playFor(perf).name} : ${usd(amountFor(perf))} (${ perf.audience }석)`; totalAmount += amountFor(perf); } result += `총액 : ${usd(totalAmount)}`; result += `적립포인트 : ${volumnCredits}점`; return result; } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber / 100); }
- volumeCredits 변수 제거하기
function statement(invoice, plays) { let totalAmount = 0; let result = `청구 내역(고객명 : ${invoice.customer})\\n`; for (let perf of invoice.performances) { //청구 내역을 출력한다. result += `${playFor(perf).name} : ${usd(amountFor(perf))} (${ perf.audience }석)\\n`; totalAmount += amountFor(perf); } result += `총액 : ${usd(totalAmount)}`; result += `적립포인트 : ${totalVolumeCredits()}점`; return result; } function totalVolumeCredits() { let volumnCredits = 0; for (let perf of invoice.performances) { volumnCredits += volumnCreditsFor(perf); } return volumnCredits; }
- 반복문 쪼개기로 변수 값을 누적시키는 부분을 분리한다.
- 문장 슬라이드하기로 변수 초기화 문장을 변수 값 누적 코드 바로 앞으로 옮긴다.
- 함수 추출하기로 적립 포인트 계산 부분을 별도 함수로 추출한다.
- 변수 인라인하기로 volumeCredits변수를 제거한다.
- 중간 점검 코드
let invoice = { customer: "BigCo", performances: [ { playID: "hamlet", audience: 55, }, { playID: "as-like", audience: 35, }, { playID: "othello", audience: 40, }, ], }; let plays = { hamlet: { name: "Hamlet", type: "tragedy" }, "as-like": { name: "As ou Like It", type: "comedy" }, othello: { name: "othello", type: "tragedy" }, }; function statement(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(totalAmount())}`; result += `적립포인트 : ${totalVolumeCredits()}점`; return result; } function totalAmount() { let result = 0; for (let perf of invoice.performances) { result += amountFor(perf); } return result; } function totalVolumeCredits() { let result = 0; for (let perf of invoice.performances) { result += volumnCreditsFor(perf); } return result; } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber / 100); } function volumnCreditsFor(aPerformance) { let result = 0; result += Math.max(aPerformance.audience - 30, 0); if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5); return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } 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": result = 30000; if (aPerformance.audience > 20) { result += 1000 + 500 * (aPerformance.audience - 20); } result += 300 * aPerformance.audience; break; default: throw new Error(`알수 없는 장르 : ${playFor(aPerformance).type}`); } return result; } console.log(statement(invoice, plays));
- 불변(immutable) 로 취급하기 위해서 복사해서 사용함.
얕은 복사함.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; }
- reduce를 사용. 반복문을 파이프라인으로 바꾸기
// 리팩토링 하기 전 function totalAmount(data) { let result = 0; for (let perf of data.performances) { console.log(perf); result += perf.amount; } return result; } // 리팩토링 후 function totalAmount(data) { return data.performances.reduce((total, p) => total + p.amount, 0); }
- 중간 단계로 바꾸기
// 리팩토링 전 function statement(invoice, plays) { const statementData = {}; statementData.customer = invoice.customer; statementData.performances = invoice.performances.map(enrichPerformance); return renderPlainText(statementData, plays); } function createStatementData(invoice, plays) { const statementData = {}; statementData.customer = invoice.customer; statementData.performances = invoice.performances.map(enrichPerformance); } // 리팩토링 후 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; }
- 중간 점검 코드
let invoice = { customer: "BigCo", performances: [ { playID: "hamlet", audience: 55, }, { playID: "as-like", audience: 35, }, { playID: "othello", audience: 40, }, ], }; let plays = { hamlet: { name: "Hamlet", type: "tragedy" }, "as-like": { name: "As ou Like It", type: "comedy" }, othello: { name: "othello", type: "tragedy" }, }; function statement(invoice, plays) { return renderPlainText(createStatementData(invoice, plays)); } function createStatementData(invoice, plays) { const result = {}; result.customer = invoice.customer; result.performances = invoice.performances.map(enrichPerformance); result.totalAmount = totalAmount(invoice); result.totalVolumeCredits = totalVolumeCredits(invoice); return result; } function enrichPerformance(aPerformance) { const result = Object.assign({}, aPerformance); result.play = playFor(result); result.amount = amountFor(result); result.volumeCredits = volumnCreditsFor(result); 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(totalAmount(data))}`; result += `적립포인트 : ${totalVolumeCredits(data)}점`; return result; } 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); } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber / 100); } function volumnCreditsFor(aPerformance) { let result = 0; result += Math.max(aPerformance.audience - 30, 0); if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5); 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 += 1000 + 500 * (aPerformance.audience - 20); } result += 300 * aPerformance.audience; break; default: throw new Error(`알수 없는 장르 : ${aPerformance.play.type}`); } return result; } console.log(statement(invoice, plays));
- amountFor 함수에 다형성 적용하기
class PerformanceCalculator { constructor(aPerformance, aPlay) { this.performances = aPerformance; this.play = aPlay; } get amount() { let result = 0; switch (this.play.type) { case "tragedy": result = 40000; if (this.performances.audience > 30) { result += 1000 * (this.performances.audience - 30); } break; case "comedy": result = 30000; if (this.performances.audience > 20) { result += 1000 + 500 * (this.performances.audience - 20); } result += 300 * this.performances.audience; break; default: throw new Error(`알수 없는 장르 : ${this.play.type}`); } return result; } get volumeCredits() { let result = 0; result += Math.max(this.performances.audience - 30, 0); if ("comedy" === playFor(this.performances).type) result += Math.floor(this.performances.audience / 5); return result; } } function statement(invoice, plays) { return renderPlainText(createStatementData(invoice, plays)); } function createStatementData(invoice, plays) { const result = {}; result.customer = invoice.customer; result.performances = invoice.performances.map(enrichPerformance); result.totalAmount = totalAmount(invoice); result.totalVolumeCredits = totalVolumeCredits(invoice); return result; } 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 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(totalAmount(data))}`; result += `적립포인트 : ${totalVolumeCredits(data)}점`; return result; } 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); } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2, }).format(aNumber / 100); } function volumnCreditsFor(aPerformance) { let result = 0; result += Math.max(aPerformance.audience - 30, 0); if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5); return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } function amountFor(aPerformance) { return new PerformanceCalculator(aPerformance, playFor(aPerformance)).amount; } console.log(statement(invoice, plays));
- 다형성 적용하기 (매우 중요하다잉.)
createPerformanceCalculator 를 만들어서 종류에 맞게 다른 Calculator를 적용 시킨다.//적용 전 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; } //적용 후 class TragedyCalculator extends PerformanceCalculator {} class ComedyCalculator extends PerformanceCalculator {} function statement(invoice, plays) { return renderPlainText(createStatementData(invoice, plays)); } function createPerformanceCalculator(aPerformance, aPlay) { switch (aPlay.type) { case "tragedy": return new TragedyCalculator(aPerformance, aPlay); case "comedy": return new ComedyCalculator(aPerformance, aPlay); default: throw new Error(`알 수 없는 장르: ${aPlay.type}`); } } 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; }
- 조건부 로직을 다형성으로 바꾸기 (중요)
// 수정 전 class PerformanceCalculator { constructor(aPerformance, aPlay) { this.performances = aPerformance; this.play = aPlay; } get amount() { let result = 0; switch (this.play.type) { case "tragedy": result = 40000; if (this.performances.audience > 30) { result += 1000 * (this.performances.audience - 30); } break; case "comedy": result = 30000; if (this.performances.audience > 20) { result += 1000 + 500 * (this.performances.audience - 20); } result += 300 * this.performances.audience; break; default: throw new Error(`알수 없는 장르 : ${this.play.type}`); } return result; } } // 수정 후 class TragedyCalculator extends PerformanceCalculator { get amount() { let result = 40000; if (this.performances.audience > 30) { result += 1000 * (this.performances.audience - 30); } return result; } } class ComedyCalculator extends PerformanceCalculator { get amount() { let result = 30000; if (this.performances.audience > 20) { result += 1000 + 500 * (this.performances.audience - 20); } result += 300 * this.performances.audience; return result; } }
- 조건부 다형성 로직으로 분리2
volumeCredits 함수도 다형성 이용해서 분리했음. 이런식으로 해야한다.class PerformanceCalculator { constructor(aPerformance, aPlay) { this.performances = aPerformance; this.play = aPlay; } get amount() { throw new Error("서브클래스에서 처리하도록 설계되었습니다."); } get volumeCredits() { return Math.max(this.performances.audience - 30, 0); } } class TragedyCalculator extends PerformanceCalculator { get amount() { let result = 40000; if (this.performances.audience > 30) { result += 1000 * (this.performances.audience - 30); } return result; } } class ComedyCalculator extends PerformanceCalculator { get amount() { let result = 30000; if (this.performances.audience > 20) { result += 1000 + 500 * (this.performances.audience - 20); } result += 300 * this.performances.audience; return result; } get volumeCredits() { return super.volumeCredits + Math.floor(this.performances.audience / 5); } }
- 함수 추출하기, 변수 인라인 하기, 함수 옮기기, 조건부 로직을 다형성으로 바꾸기
- 리팩토링 과정에서 성능이 크게 떨어졌다면 리팩토링 후 시간을 내어 성능을 계선한다.
- 똑똑한 컴파일러들은 최신 캐싱 기법 등으로 무장하고 있어서 우리의 직관을 초월하는 결과를 내어준다. 또한 소프트웨어 성능은 대체로 코드의 몇몇 작은 부분에 의해 결정됨로 그 외의 부분은 수정한다고 해도 성능 차이를 체감할 수 없다.
- 해당부분을 function으로 변경.
- 리팩토링은 프로그램 수정을 작은 단계로 나눠 진행한다. 그래서 중간에 실수하더라도 버그를 쉽게 찾을 수 있다.
- 리팩토링의 첫단계는 테스트 코드부터 마련해야함.
'기타' 카테고리의 다른 글
[DDD] 1장 도메인 모델 시작하기 (0) | 2022.07.13 |
---|---|
[VsCode] 사용자 코드 조각 만들기 (개발 시간 단축하는 꿀팁) (0) | 2022.04.06 |
[MacOS] arch -arm64 brew install 에러 해결하기 (0) | 2022.03.26 |
[MongoDB] mongo 에러 [js] Error: couldn't connect to server 127.0.0.1:27017 (0) | 2022.02.05 |
[Hadoop] Ubuntu 20.4 Hadoop 3.3.0 설치하기 (0) | 2022.01.22 |
Comments