첫 번째 예시
import plays from "./plays.json";
import invoices from "./invoices.json";
function getInvoiceCustomer(customer) {
return `청구내역 (고객명: ${customer})\\n`;
}
function statement(invoice, plays) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `청구내역 (고객명: ${invoice.customer})\\n`;
// let result = getInvoiceCustomer(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 = 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 += 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;
}
invoices.map((invoice) => console.log(statement(invoice, plays)));
- 프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 혀애로 리팩터링하고 나서 원하는 기능을 추가한다.
- 중복 코드는 골칫거리가 된다. 수정은 한 곳에서만 일어나야 한다. 버그의 위험성이 많다.
- 별도의 htmlStatement()를 만든다면? 리팩터링이 필요한 이유는 바로 이러한 변경때문이다.
- htmlStatement()를 만들 때 그때 수정하는게 좋다고 생각한다. 굳이 미리 분리할 필요가 있나?
- 리팩토링의 첫 단계는 테스트 코드를 마련하는 것.
- 현실적으로는 프론트엔드에서 테스트 코드가 없는 경우가 더 많다고 생각한다.
- 그치만 불가능한 경우가 많기 때문에 수동 테스트를 많이 하는게 좋은 것 같다.
- 전략 세우기!
- statement() 함수 쪼개는 것 자체는 좋은데 꼭 지금?
- 코드를 분석해서 얻은 정보 → STM(단기 기억에 저장된다) → 선언적으로 표현할 수 있도록 변경한다.
- 아무리 간단한 수정이라고 리팩터링 후에는 항상 테스트하는 습관을 들이는 것이 바람직하다.
- 함수 반환값에는 Result?
- 자바스크립트와 같은 동적 타입 언어를 사용할 때는 타입이 드러나게 작성하면 도움된다.
- 매개변수의 역할이 뚜렷하지 않을때는 부정 관사(a/an)를 붙인다.
- 컴퓨터가 이해하는 코드는 바보도 작성할 수 있다. 사람이 이해하도록 작성하는 프로그래머가 진정한 실력자다.
- 좋은 코드라면 하는 일이 명확히 드러나야 하며, 변수 이름은 커다란 역할을 한다.
임시 변수를 질의 함수로 바꾸기
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function statement(invoice, plays){
...
for (let perf of invoice.performances) {
const play = playFor(perf);
...
if("comedy" === play.type) {
}
}
}
변수 인라인 하기
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function statement(invoice, plays){
...
for (let perf of invoice.performances) {
...
if("comedy" === playFor(perf).type) {
}
}
}
- 리팩터링된 코드베이스는 그렇지 않은 코드보다 성능을 개선하기가 훨씬 수월하다.
함수 선언 바꾸기
// Before
function format(aNumber) {
return new Intl.NumberFormat('en-US', ...);
}
// After
function usd() {
return new Intl.NumberFormat('en-US', ...);
}
반복문을 파이프라인으로 바꾸기
- 선언적으로 표현이 된다
- map, filter, reduce
문장 슬라이드하기
- 문장의 위치를 바꾸는 것
- 스코프를 최대한 좁힌다.
- 가독성을 위하여 선언, 실행, 반환 등으로 모아놓는다.
리팩토링은 성능이 낮아지더라도 의미가 있다.
function statement() {
return renderPlainText(invoice, plays);
}
function renderPlainText(invoice, plays) {
}
계산하는 부분과 출력 형식을 다루는 부분이 분리했다.
보이스카우트 원칙
- 도착했을 때보다 깔끔하게 정돈하고 떠난다.
- 코드베이스를 작업 시작 전보다 건강하게 만들어놓고 떠냐아 한다.
조건부 로직을 다형성으로 바꾸기
function 영화표_발급기(type, count) {
const 영화_계산기 = createMovieCalculatorFactory(type, count);
const 영화가격 = 영화_계산기.영화가격;
const 마일리지 = 영화_계산기.마일리지;
return {
영화가격,
마일리지,
};
}
class MovieCalculator {
constructor(type, count) {
this.type = type;
this.count = count;
}
get 영화가격() {
throw new Error('자식 클래스에서 오버라이딩해서 사용해야합니다.');
}
get 마일리지() {
throw new Error('자식 클래스에서 오버라이딩해서 사용해야합니다.');
}
}
class 스릴러_계산기 extends MovieCalculator {
get 영화가격() {
return 10_000 * this.count;
}
get 마일리지() {
return 100 * this.count;
}
}
class 코믹_계산기 extends MovieCalculator {
get 영화가격() {
return 8_000 * count;
}
get 마일리지() {
return 80 * this.count;
}
}
class 로맨스_계산기 extends MovieCalculator {
get 영화가격() {
return 12_000 * count;
}
get 마일리지() {
return 120 * this.count;
}
}
function createMovieCalculatorFactory(type, count) {
if (type === '스릴러') {
return new 스릴러_계산기(type, count);
}
if (type === '코믹') {
return new 코믹_계산기(type, count);
}
if (type === '로맨스') {
return new 로맨스_계산기(type, count);
}
throw new Error(`해당 type:${type}에 해당하는 클래스가 존재하지 않습니다.`);
}
좋은 코드를 가늠하는 확실한 방법은 얼마나 수정하기 쉬운가다.
리팩토링은 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
- 리팩토링의 목적은 코드를 이해하고 수정하기 쉽게 만드는 것이다.
개발할 때 목적이 ‘기능 추가’냐, ‘리팩터링’이냐를 명확하게 구분해서 작업한다.
리팩터링하는 이유
- 리팩터링하면 소프트웨어 설계가 좋아진다.
- 리팩터링하면 소프트웨어를 이해하기 쉬워진다.
- 리팩터링하면 버그를 쉽게 찾을 수 있다.
- 리팩터링하면 프로그래밍 속도를 높일 수 있다.
언제 리팩터링해야 할까?
- 기능을 쉽게 추가하게 만들기
- 코드를 이해하기 쉽게 만들기
- 쓰레기 줍기 리팩터링
- 간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음, 하던 일을 끝내고 나서 처리한다.
- 계획된 리팩터링과 수시로 하는 리팩터링
- 짝 프로그래밍
프로 개발자의 역할은 효과적으로 소프트웨어를 최대한 빨리 만드는 것이다.
리팩터링하지 말아야 할 때
- 굳이 수정할 필요가 없다면 리팩터링하지 않는다.
리팩터링 시 고려할 문제
- 새 기능 개발 속도 저하
- 리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
- 리팩터링의 본질은 개발 기간을 단축하고자 하는 것, 경제적인 이유ek.
- 코드 소유권
- 코드 소유권이 특정 팀에 나눠져있으면 수정하기가 어렵다.
테스팅
- 리팩터링의 특징은 프로그램의 겉보기 동작은 똑같이 유지된다는 것
- 따라서 겉보기 동작이 유지되는 것을 보증하기 위해 테스트가 필요하다.
리팩터링, 아키텍처, YAGNI
리팩터링 ,TDD
리팩터링과 성능
리팩터링 자동화
코드에서 나는 악취
- 기이한 이름
- 중복 코드
- 변경이 여러곳에서 발생해야 하면 유지보수가 너무 어렵다.
- 긴 함수
- 긴 매개변수 목록
- 전역 데이터
- 가변 데이터
- 어디서 데이터가 변경될지 모른다.
- Flux? 2-way-binding?
- 뒤엉킨 변경
- 소프트웨어는 소프트해야한다.
- SRP가 지켜지지 않을 때
- 산탄총 수술
- 반복되는 switch
- 데이터 뭉치
- 반복문
- 추측성 일반화
- YAGNI, 나중에 필요할거야라는 생각으로 코드를 미리 추가하지 말자.
- 거대한 클래스
- 주석
- 주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링한다.
테스트 구축하기
- 모든 테스트를 완전히 자동화하고 그 결과까지 스스로 검사하게 만들자, 테스트 자동화를 하자.
- 테스트 스위트는 강력한 버그 검출 도구로, 버그를 찾는 데 걸리는 시간을 대폭 줄여준다.
- TDD
- 테스트는 위험 요인을 중심으로 작성한다, 잘못될까봐 가장 걱정되는 영역
- 경계 조건, 엣지 케이스를 검사하자.
- 단위 테스트
리팩터링 카탈로그 보는 법