잠깐 책 예시 설명
다양한 연극을 외주로 받아서 공연하는 극단이 있다고 생각해보자
공연 요청이 들어오면 연극의 장르와 관객 규모를 기초로 비용을 책정한다.
현재 이 극단은 2가지 장르 , 비극, 희극만 공연한다.
그리고 공연료와 별개로 포인트를 지급해서 다음번 의뢰시 공연료를
할인 받을 수 있다.
{
"halmet": {"name:": "Halmet", "type": "tragedy"},
"as-like": {"name:": "As you like it", "type": "comedy"},
"othello": {"name:": "Othello", "type": "tragedy"}
}
plays.json
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
}
]
Invoices.json
혼자 그냥 고쳐보고 그러기보다 같이 얘기 해보고 싶어서
10분 ~ 15분 정도 statement.js 를 리팩토링 해보는 시간을 가져보면 어떨까 합니다.
+ 이 분이 고치기 전에 내가 먼저 아쉬운 부분을 고쳐보자 !
공연료와 청구서를 출력하는 코드 statemet.js
1. 내가 생각한 부분
1.1
JSDOC or
/* parameter return type 명시 */
1.2 함수는 동사 + 명사 행위나 의도를 나타내면 더 좋을 듯
(책에서는 playFor, volumeCreditFor, statement)
1.3 es6 destructuring 등을 활용
(ex) perf.audience -> audience )
1.4 for of -> Array instance methods 가 가독성 밑 조작이 편리하다
1.5 행위 별로 함수를 분리한다.
ex) totalAmount 계산, 포인트 적립, 청구내역 출력, ....
참고 JS DOC Example js 함수면 써주면 좋을 듯
/**
* Blend two colors together.
* @param {string} color1 - The first color, in hexadecimal format.
* @param {string} color2 - The second color, in hexadecimal format.
* @return {string} The blended color.
*/
export function blend(color1, color2) {}
function formatCurrency(number) {
return format(number)
}
class Play {
amount = 0;
constructor(audience) {
this.audience = audience
}
calculate() {}
getPoint() {
return Math.max(this.audience - 30, 0)
}
}
class Tragedy {
amout = 40000;
constructor(audience) {
super(audience)
}
calculate() {
if (this.audience > 30) {
this.amount += 1000 * (this.audience - 30)
}
return this.amount
}
}
class Codemy {
amount = 30000;
constructor(audience) {
super(audience)
}
calculate() {
if (perf.audience > 20) {
this.amount += 10000 + 500 * (this.audience - 20)
}
this.amount += 300 * this.audience
return this.amount
}
getPoint() {
let value = super.getPoint()
value += Math.floor(this.audience / 5)
return value
}
}
책에서
statement.js
설계가 나쁜 시스템은 수정하기 어렵다
무엇을 수정할지 찾기 어렵다 면 실수를 저질러서 버그가 생길 가능성도 높아진다 -> 비용 + 안전성
프로그램이 새로운 기능을 추가하기에 편한 구조가 아니면, 먼저 기능을 추가하기 쉬운 형태로 리팩토링하고 나서 원하는 기능을 추가한다.
책에서
변경사항 ex)
청구 내역을 HTML로 출력하는 기능이 필요!
복사한다고 가정하면
statement
statementHTML
또 변경사항이 생기면 둘다 고쳐줘야 한다
+
연극 장르와 공연료 정책이 달라진다면 ?
맞아요 변명입니다 ㅜㅜ
Why?
1. 내가 제대로 고치고 있는지 검증
2. 고칠때 마다 수시로 테스트 결과적으로 디버깅 시간을 줄여줌 !
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 += 10000 + 500 * (aPerformance.audience - 20)
}
result += 300 * aPerformance.audience
break
}
default: {
throw new Error(`알 수 없는 장르 ${playFor(aPerformance).type}`)
}
}
return result
}
리팩토링 TIP
1. return 값 result 변수
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 += 10000 + 500 * (aPerformance.audience - 20)
}
result += 300 * aPerformance.audience
break
}
default: {
throw new Error(`알 수 없는 장르 ${playFor(aPerformance).type}`)
}
}
return result
}
리팩토링 TIP
2. aPerformance ???
동적타입 언어 -> 타입이 들어나게 작성 +
매개변수 역할이 뚜렷하지 않을 때 부정관사(a/an)를 붙여준다
function playFor(aPerformance) {
return plays[aPerformance.playID]
}
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': {
result = 30000
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20)
}
result += 300 * aPerformance.audience
break
}
default: {
throw new Error(`알 수 없는 장르 ${play.type}`)
}
}
return result
}
const play = plays[perf.playID]
// 변수 remove 함수 인라인화로 바로 적용
변수 제거 의문점 -> 반복작업 -> 대부분 성능상에 큰 영향 없음
-> 반복작업 두고 똑똑하게 처리
-> 지역변수를 제거하면 유효범위를 신경써야 할 대상이 줄어든다 !
for (let perf of invocie.performances) {
// 청구 내역을 출력한다.
result += `${playFor(perf).name}:
${usd(amountFor(perf))} (${perf.audience} 석) \n`
totalAmount += amountFor(perf)
}
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
}
컴파일 테스트 커밋 아주 작은 변경점 도 변수 이름 바꿀때도
컴파일-> 테스트 -> 커밋
임시 변수 지양 -> 함수 선언으로
const usd = (aNumber) =>
new Intl.NumberFormat('en-Us', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
}).format(aNumber / 100)
const format = new Intl.NumberFormat('en-Us', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
}).format
쓰이는 곳에서
함수 내부로 들어갈 로직이 보이면 또 처리 + 네이밍 변경
반복문 쪼개기
기존에 한 번에 처리했던 부분을 의미 단위별로 나눠서 별도 for문 처리
for (let perf of invocie.performances) {
const play = plays[perf.playID]
let thisAmount = 0
...
...
volumeCredits += Math.max(perf.audience - 30, 0)
if ('comedy' === play.type) {
volumeCredits += Math.floor(perf.audience / 5)
}
result += `${play.name}: ${format(thisAmount / 100)} (${perf.audience} 석) \n`
totalAmount += thisAmount
}
for (let perf of invocie.performances) {
// 청구 내역을 출력한다.
result += `${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} 석) \n`
totalAmount += amountFor(perf)
}
for (let perf of invocie.performances) {
volumeCredits += volumeCreditsFor(perf)
}
문장슬라이드 하기
기존에 앞에 다 선언되어 있던 변수
function volumeCreditsFor(perf) {
let result = 0
result += Math.max(perf.audience - 30, 0)
if ('comedy' === playFor(perf).type) {
result += Math.floor(perf.audience / 5)
}
return result
}
for (let perf of invocie.performances) {
volumeCredits += volumeCreditsFor(perf)
}
->
function totalVolumeCredits() {
let volumeCredits = 0
for (let perf of invocie.performances) {
volumeCredits += volumeCreditsFor(perf)
}
return volumeCredits
}
마찬가지로 volumeCredits가 쓰이는 곳을 -> 인라인화 -> totalVolumeCredits
result += `총액 ${usd(totalAmount())}`
let result = `청구 내역 (고객명: ${inovice.customer})\n`
for (let perf of invocie.performances) {
result += `${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} 석) \n`
}
result += `총액 ${usd(totalAmount())}`
result += `적립포인트 ${totalVolumeCredits()} \n`
return result
function statment(invoice, plays) {
return renderPlainText(invoice, plays)
}
function statment(invoice, plays) {
const statmentData = {}
return renderPlainText(statmentData, invoice, plays)
}
function renderPlainText(data, invoice, plays)
function statment(invoice, plays) {
const statmentData = {}
statmentData.customer = invoice.customer
return renderPlainText(statmentData, invoice, plays)
}
function renderPlainText(data, invocie, plays)
function statment(invoice, plays) {
const statmentData = {}
statmentData.customer = inovice.customer
statmentData.performances = inovice.performances.map(enrichPerformance)
function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance)
return result
}
return renderPlainText(statmentData, invoice, plays)
}
function statment(invoice, plays) {
const statmentData = {}
statmentData.customer = inovice.customer
statmentData.performances = inovice.performances.map(enrichPerformance)
statmentData.performances = inovice.performances.map(enrichPerformance)
statmentData.performances = inovice.performances.map(enrichPerformance)
function enrichPerformance(aPerformance) {
const result = Object.assign({}, aPerformance)
result.play = playFor(result)
return result
}
function playFor(aPerofrmance){
return plays[aPerofrmance.playID]
}
return renderPlainText(statmentData, invoice, plays)
plays[perf.playID] -> perf.play
switch (perf.play.type) {
case 'tragedy': {
result = 40000
if (perf.audience > 30) {
result += 1000 * (perf.audience - 30)
}
break
}
}
function totalAmount(data){
return data.performance.reduce((total, p) => total + p.amount, 0)
}
function totalVolumeCredit(data){
return data.performance.reduce((total, p) => total + p.volumeCredits, 0)
}
function createStatement(invoice, plays){
const statement = {}
statement.customer = invoice.customer
statement.performance = invoice.performance.map(enrichPerformance)
statement.totalAmount = totalAmount(statementData)
statement.customer = totalVolumeCreditCard(statementData)
}
function totalAmount(data){
return data.performance.reduce((total, p) => total + p.amount, 0)
}
function totalVolumeCredit(data){
return data.performance.reduce((total, p) => total + p.volumeCredits, 0)
}
function createStatement(invoice, plays){
const statement = {}
statement.customer = invoice.customer
statement.performance = invoice.performance.map(enrichPerformance)
statement.totalAmount = totalAmount(statementData)
statement.customer = totalVolumeCreditCard(statementData)
}
function totalAmount(data){
return data.performance.reduce((total, p) => total + p.amount, 0)
}
function totalVolumeCredit(data){
return data.performance.reduce((total, p) => total + p.volumeCredits, 0)
}
function createStatement(invoice, plays){
const result = {}
result.customer = invoice.customer
result.performance = invoice.performance.map(enrichPerformance)
result.totalAmount = totalAmount(statementData)
result.customer = totalVolumeCreditCard(statementData)
return result
}
function htmlStatement(invoice, plays){
return renderHtml(createStatement(invoice, plays))
}
TO BE CONTINUED ... ㅜ
다음 발표때 ... 다형성 및 다른 부분 가져오겠습니다.
- 사내 기술 블로그 글 2편 이상 쓰기
- DB 부터 Node Ts로 간단한 TODO or 게시판 Service 정도 구축해보기
- Git, Docker 책 1권 이상 씩 읽고 공부하기
- 2주일에 블로그 글 1편 씩 쓰기
- 개인 프로젝트 서비스 분기 별로 하나씩 내보기
- 운동습관 ->지금 거의 체지방률 20%... -> 13%
- 새로운 취미 활동 하기
- 영어공부 시작하기 -> 일주일에 한 번 meetup 알아보기
- 서울 살이 -> 연말에는 집 알아보기
끝