Chapter 1) 리팩터링: 첫번째 예시 1.1~1.5
예전에 스터디하면서 책을 읽었던 기억은 있는데.. 많이 까먹기도 했고,
최근 진행한 프로젝트를 리팩터링 하고 싶다는 생각이 들었고, 리팩터링 책을 읽으며 적용해 보는 건 어떨까 싶어 책을 다시 읽으며 정리해보려고 한다! 😄
책에서는 간단한 공연료 청구서를 출력하는 코드를 예로 들어준다.
function statement(invoice,plays){
let totalAmount = 0;
let volumeCredits = 0;
let result = `청구내역 ( 고객명: ${invoide.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 += 10000 * (perf.audience - 30);
}
break;
case "comedy": //희극
thisAmount = 30000;
if(perf.audience > 20){
thisAmount += 10000 + 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;
}
그러면서, 저자는 프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 형태로 리팩터링 하고 원하는 기능을 추가한다고 한다.
위 코드에서 사용자의 필요에 맞게 수정할 부분은
1) 청구내역을 HTML 로 출력하는 기능추가
-> 해당 기능을 추가할 때 영향을 미치는 부분은 청구 결과에 문자열을 추가하는 문장을 각각 조건문으로 감싸야함
-> 이때 문제점은 statement 함수의 복잡도가 증가한다.
2) 연극 type 이 다양하게 추가
-> 공연료와 적립 포인트 계산법에 영향을 주게 됨
리팩터링
1) 리팩터링의 첫 단계는 리팩터링 할 코드 영역을 꼼꼼하게 검사해 줄 테스트 코드 작성 (4장에서 테스트 코드에 대해 좀 더 다루는 것 같다)
2) statement() 함수 쪼개기
statement() 함수 쪼개기
긴 함수를 리팩터링 할 때는 먼저 전체 동작을 각각의 부분으로 나눌 수 있는 지점을 찾는다.
책에서는 switch 에 주목한다.
switch 문이 하는 역할은 한 번의 공연에 대한 요금을 계산하고 있기 때문에 이 부분을 별도의 함수로 추출한다.
amountFor이라고 switch 문이 하는 역할을 함수명으로 적고 추출한다.
별도 함수로 빼냈을 때
유효범위를 벗어나는 변수, 즉 새 함수에서 곧바로 사용할 수 없는 변수가 있는지 확인한다.
(문맥상 유효범위를 벗어나는 변수를 함수를 추출했을 때 추출한 부분에서 선언된 함수가 아니라면 유효범위를 벗어나는 함수라고 이해했다.)
switch 문에서는 perf,play,thisAmount 가 속한다.
perf & play 는 amountFor 함수에서도 필요하지만 값을 변경하지 않기 때문에 매개변수로 전달하고, thisAmount는 함수 안에서 값이 변한다. 해당 변수는 값을 반환해 주도록 한다.
(*) 코드는 책을 참고하자 (책 30p)
함수를 쪼개고 테스트코드를 돌려본다.
간단한 수정이라도 리팩터링 후 항상 테스트하는 습관을 들이자.
커밋시점
하나의 리팩터링이 끝나고 나면 커밋 + 해당 커밋들이 쌓여 의미 있는 단위가 된다면 푸시한다.
함수 추출하기 후
추출된 함수 코드를 들여다보면서 지금보다 명확하게 표현 할 수 있는 방법은 없는지 검토한다.
변수제거하기
함수를 쪼갤 때 해당 함수 안에 있는 임시변수를 최대한 제거한다
왜냐? 임시 변수들로 인해 로컬 범위에 존재하는 이름이 늘어나, 추출작업이 복잡해지기 때문에
(임시변수 : 임시적으로 사용하는 변수)
statement 함수에서는 play 가 임시변수에 속한다.
이런 임시변수를 제거하는 방법으로는 임시변수를 질의 함수로 바꾸기가 있다,
(질의함수: 연산을 통해 값을 계산하여 반환하는 함수)
임시변수를 사용하는 대신 질의함수로 임시변수 사용하는 곳을 대체한다.
리팩터링의 1.1~1.5를 읽으며 느낀 것은 보통 코드를 작성할 때 임시변수를 사용해서 함수 호출하는 부분을 줄이는 것이 좋다고 생각했는데,
임시변수를 줄일수록 함수를 쪼개기 더 쉬워진다는 것을 알게 되었다.
그렇지만, 무조건적으로 임시변수 사용을 줄이고 임시변수대신 함수를 호출하는 것이 좋은 것인가? 에 대한 궁금증은 남은 챕터였다.