6장에서는 리팩터링의 기본이자 가장 자주 사용되는 기법들을 다룬다. 그만큼 중요하고, 먼저 익혀야 할 리팩터링이라는 의미다.
마틴 파울러가 실무에서 가장 자주 사용하는 리팩터링은 함수 추출하기(Extract Function)와 변수 추출하기(Extract Variable).
반대로, 함수 인라인하기(Inline Function)와 변수 인라인하기(Inline Variable)도 자주 등장한다.
기능을 쪼개고 다시 단순하게 합치는 것. 리팩터링은 결국 이 반복이다.
함수 추출하기 (Extract Function)
배경
코드를 언제 함수로 분리해야 할지는 논쟁이 많다. 파울러는 “목적과 구현을 분리하는 기준”을 가장 합리적으로 본다.
예를 들어, 코드의 목적을 파악하는 데 시간이 걸린다면, 그 부분을 함수로 뽑아내고 그 목적에 맞는 이름을 붙이는 게 좋다.
코드의 목적이 잘 드러나게 하는 것, 이게 핵심이다.
켄트 백의 예시도 흥미롭다. 오리지널 스몰토크 시스템에는 highlight()라는 메서드가 있었는데, 내부 구현은 reverse() 한 줄뿐이었다.
메서드 이름이 실제 코드보다 길었지만, 이름이 담고 있는 의미가 훨씬 컸기 때문에 정당했다. 구현보다 목적이 더 중요하다는 것을 보여주는 좋은 사례다.
절차
- 새 함수를 만들고, 목적이 잘 드러나는 이름을 붙인다.
- 추출할 코드를 복사해서 새 함수에 붙여넣는다.
- 추출한 코드가 원래 함수의 지역 변수를 참조하고 있다면, 그 변수들을 매개변수로 전달한다.
- 변수가 다 처리됐으면 컴파일한다.
- 원래 코드에서 해당 부분을 새로 만든 함수 호출로 대체한다.
- 테스트한다.
- 다른 코드에도 비슷한 부분이 있는지 찾아본다. 있다면 같은 방식으로 정리한다.
예시
💥 Before: 지역 변수와 함께 있는 함수
function printOwing(invoice) {
let outstanding = 0;
printBanner();
// 미지급 금액 계산
for (const o of invoice.orders) {
outstanding += o.amount;
}
// 납기일 설정
const today = Clock.today();
invoice.dueDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 30
);
// 상세 정보 출력
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
✅ After: recordDueDate, printDetails 함수 추출
function printOwing(invoice) {
let outstanding = 0;
printBanner();
// 미지급 금액 계산
for (const o of invoice.orders) {
outstanding += o.amount;
}
recordDueDate(invoice); // 🎯 데이터 갱신 로직 분리
printDetails(invoice, outstanding); // 🎯 출력 로직 분리
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
function recordDueDate(invoice) {
const today = Clock.today();
invoice.dueDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 30
);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
함수 인라인하기 (Inline Function)
배경
짤막하지만 목적이 분명한 함수는 코드를 이해하기 쉽게 만든다. 이런 함수는 무조건 환영이다.
하지만 어떤 경우에는 함수 이름이 굳이 필요 없을 정도로 본문이 명확한 경우도 있다.
또는 원래는 복잡했지만 리팩터링을 통해 간결해져서, 이제는 더 이상 별도의 함수로 분리해둘 필요가 없어진 경우도 있다.
이럴 땐 굳이 간접 호출을 유지할 이유가 없다.
간접 호출은 구조를 단순화하거나 캡슐화를 위한 수단으로 쓰는 게 좋지, 의미 없이 남겨두면 오히려 가독성을 떨어뜨린다.
실제로 이런 불필요한 간접 호출이 반복되는 코드는 함수 인라인하기의 주요 대상이다.
절차
- 해당 함수가 다형 메서드(서브클래스에서 오버라이드되는 메서드)는 아닌지 확인한다.
- 인라인하려는 함수를 호출하는 모든 지점을 찾는다.
- 각 호출문을 함수 본문으로 교체한다.
- 교체할 때마다 테스트한다.
- 모든 호출을 교체했으면 원래 함수 정의를 삭제한다.
단, 함수 인라인하기가 항상 좋은 선택은 아니다.
예를 들어, 재귀 호출이 있거나 반환문이 여러 개인 복잡한 함수는 오히려 코드 흐름을 망가뜨릴 수 있다. 이런 경우엔 과감하게 패스하는 게 맞다.
예시
💥 Before: 별 의미 없는 헬퍼 함수
function reportLines(aCustomer) {
const lines = [];
gatherCustomerData(lines, aCustomer);
return lines;
}
function gatherCustomerData(out, aCustomer) {
out.push(["name", aCustomer.name]);
out.push(["location", aCustomer.location]);
}
✅ After: 인라인으로 단순화
function reportLines(aCustomer) {
const lines = [];
lines.push(["name", aCustomer.name]);
lines.push(["location", aCustomer.location]);
return lines;
}
변수 추출하기 (Extract Variable)
배경
표현식이 너무 복잡해서 한눈에 이해하기 어려울 때, 지역 변수를 활용해 표현식을 쪼개는 것만으로도 코드가 훨씬 읽기 쉬워진다.
이렇게 쪼개진 변수는 디버깅할 때도 도움이 되고, 의도를 코드에 명확히 드러내는 역할을 한다.
사실 변수 추출을 고려하고 있다는 건, 그 표현식에 이름을 붙이고 싶다는 신호다.
이름을 붙이기로 마음먹었다면, 그 이름이 어떤 문맥 안에서 의미를 가지는지 먼저 살펴봐야 한다.
해당 표현식이 현재 함수 안에서만 의미가 있다면, 메서드로 뽑는 것보다 변수로 추출하는 편이 더 적절하다.
반대로, 클래스 전체에 걸친 의미가 있다면 메서드 추출을 고려하는 게 맞다.
절차
- 추출하려는 표현식에 부작용이 없는지 확인한다.
- 불변 변수를 하나 선언하고, 추출할 표현식의 복사본을 대입한다.
- 원래 표현식을 새로 만든 변수로 교체한다.
- 테스트한다.
- 같은 표현식이 여러 군데 있다면, 각각 새 변수로 교체하고 그때마다 테스트한다.
예시
💡 1단계: 초안 (Before)
function price(order) {
// price is base price – quantity discount + shipping
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
}
🔧 2단계: 기본 가격 추출
function price(order) {
// price is base price – quantity discount + shipping
const basePrice = order.quantity * order.itemPrice;
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
}
🔧 3단계: basePrice로 본문 대체
function price(order) {
// price is base price – quantity discount + shipping
const basePrice = order.quantity * order.itemPrice;
return basePrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(basePrice * 0.1, 100);
}
✅ 이후: 모든 표현식 이름 추출 + 주석 제거
function price(order) {
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
}
변수 인라인하기 (Inline Variable)
배경
어떤 변수의 이름이 원래 표현식과 별 차이 없을 때, 굳이 그 변수를 유지할 필요는 없다.
오히려 코드만 늘어나서 흐름을 방해할 수 있다. 특히 주변 코드를 리팩터링하려고 할 때, 불필요한 변수가 오히려 걸림돌이 되는 경우도 있다.
이럴 땐 변수를 없애고 표현식을 직접 사용하는 쪽이 더 깔끔하다.
절차
- 해당 표현식에 부작용이 있는지 확인한다.
- 변수가 불변으로 선언되지 않았다면, 먼저 불변으로 바꾸고 테스트한다.
- 변수를 가장 처음 사용하는 코드를 찾아서, 표현식으로 바로 교체한다.
- 하나씩 바꿀 때마다 테스트한다.
- 모든 사용처를 바꿨다면, 변수 선언문과 대입문을 삭제한다.
'Programming > Refactoring' 카테고리의 다른 글
기본적인 리팩터링 (2) (0) | 2025.04.10 |
---|---|
테스트 구축하기 (0) | 2025.03.20 |
코드에서 나는 악취 (1) | 2025.03.13 |
리팩터링: 첫 번째 예시 (0) | 2025.02.26 |
리팩터링 2판 공부를 시작하며 (0) | 2025.02.24 |