기본적인 리팩터링
- 6.1 함수 추출하기
- 6.2 함수 인라인하기
- 6.3 변수 추출하기
- 6.4 변수 인라인하기
- 6.5 함수 선언 바꾸기
- 6.6 변수 캡슐화하기
- 6.7 변수 이름 바꾸기
- 6.8 매개변수 객체 만들기
- 6.9 여러 함수를 클래스로 묶기
- 6.10 여러 함수를 변환 함수로 묶기
- 6.11 단계 쪼개기
6장은 리팩터링 카탈로그의 시작이자, 가장 자주 쓰이는 기본기들을 모아놓은 챕터야. 여기 나오는 11가지 리팩터링은 나머지 챕터의 기법들보다 압도적으로 자주 사용돼. 이것만 제대로 알아도 리팩터링의 80%는 커버할 수 있어.
**함수 추출하기(Extract Function)**는 리팩터링의 뼈대라고 해도 과언이 아니야. 코드 조각을 별도 함수로 빼내는 건데, 핵심 원칙은 **"코드가 무엇을 하는지와 어떻게 하는지를 분리하라"**는 거야. 코드를 읽다가 의도를 파악하는 데 시간이 걸린다면, 그 코드를 함수로 추출하고 '무엇을 하는지'를 함수 이름으로 표현해. 파울러의 기준은 **"목적과 구현을 분리"**하는 거야. 한 줄짜리 코드라도 목적이 구현보다 명확하게 드러나면 추출할 가치가 있어. 길이가 중요한 게 아니거든. 새 함수를 만들고, 추출할 코드를 복사하고, 지역 변수 참조는 매개변수로 전달하고, 원래 코드를 함수 호출로 바꾸고, 테스트해. 추출한 코드에서 원래 함수의 변수를 수정하는 경우가 까다로운데, 변수가 하나면 리턴값으로 처리할 수 있지만 여러 개면 더 복잡한 기법이 필요해.
반대로 **함수 인라인하기(Inline Function)**는 함수 본문이 함수 이름만큼이나 명확할 때, 함수를 제거하고 본문을 직접 쓰는 거야. "간접 호출이 과하면 오히려 읽기 어렵다." 잘못 추출된 함수들을 일단 다 인라인한 다음, 다시 올바르게 추출하는 전략도 있어. 기존 구조가 마음에 안 들면 백지에서 시작하는 거지.
**변수 추출하기(Extract Variable)**는 복잡한 표현식에 이름을 붙이는 거야.
// Before
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
// After
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)**는 변수가 표현식 이상의 정보를 주지 못하거나, 주변 리팩터링을 방해할 때 적용해.
**함수 선언 바꾸기(Change Function Declaration)**는 함수 이름 바꾸기, 매개변수 추가/제거를 모두 포함하는 기법이야. 함수 선언은 소프트웨어의 연결부거든. 연결부가 잘 설계되면 새 기능을 추가하기 쉽고, 잘못 설계되면 모든 게 어려워. "좋은 이름을 떠올리는 방법: 주석을 달아보라." 함수의 목적을 설명하는 주석을 써보고, 그 주석을 함수 이름으로 만들어봐. 간단한 절차는 선언을 바꾸고 호출하는 곳을 모두 수정하는 거고, 더 안전한 마이그레이션 절차는 새 함수를 만들고, 기존 함수가 새 함수를 호출하게 한 다음, 호출자를 하나씩 옮기는 거야.
**변수 캡슐화하기(Encapsulate Variable)**는 데이터에 대한 접근을 함수로 감싸는 거야. "데이터는 함수보다 다루기 까다롭다." 함수는 이름을 바꾸거나 옮겨도 전달 함수 역할을 하면 되지만, 데이터는 그런 게 안 되거든. 유효 범위가 넓은 데이터일수록 캡슐화가 중요해. 불변 데이터는 캡슐화 필요성이 줄어들어 — 수정할 수 없으니까 예상치 못한 변경이 일어날 여지가 없잖아. 변수 이름 바꾸기도 같은 맥락인데, 유효 범위가 넓으면 캡슐화를 먼저 적용한 다음 이름을 바꾸는 게 안전해.
**매개변수 객체 만들기(Introduce Parameter Object)**는 여러 매개변수가 항상 함께 다닐 때 하나의 객체로 묶는 거야.
// Before
function amountInvoiced(startDate, endDate) { ... }
// After
function amountInvoiced(dateRange) { ... }
이게 단순히 매개변수 수를 줄이는 것 이상의 효과가 있어. "데이터 뭉치를 객체로 만들면, 그 객체에 행위를 추가할 기반이 생긴다." DateRange 객체에 contains(), overlaps() 같은 메서드를 넣을 수 있거든. 코드의 개념적 그림을 바꿔놓는 거야.
여러 함수를 클래스로 묶기는 같은 데이터를 반복적으로 매개변수로 받는 함수들이 있으면, 그 데이터와 함수를 클래스로 묶는 거야. 함수 간에 데이터를 공유하기 편해지고, 파생 데이터를 관리하기 좋아. 여러 함수를 변환 함수로 묶기도 비슷한 목적인데, 원본 데이터를 받아서 파생 데이터를 추가한 새로운 레코드를 반환하는 함수를 만드는 거야. 원본 데이터가 변경될 수 있는 상황이면 클래스가 낫고, 불변 데이터에는 변환 함수가 적합해.
마지막으로 **단계 쪼개기(Split Phase)**는 서로 다른 두 가지 일을 하는 코드를 두 단계로 나누는 거야. 1장에서 이미 봤지 — 계산 단계와 포맷팅 단계를 분리하고, 중간 데이터 구조를 통해 소통하게 만든 거. 컴파일러의 동작 방식이 좋은 비유야. 텍스트 → 토큰 → 구문 트리 → 최적화 → 코드 생성. 각 단계가 독립적이라 이해하기 쉽고 변경하기 쉬워.
정리
6장 읽고 기억할 거 세 가지:
- "함수 추출하기는 가장 많이 쓰는 리팩터링이다." 목적과 구현을 분리하는 핵심 기법이야
- "데이터는 함수보다 다루기 까다롭다." 넓은 범위의 데이터는 반드시 캡슐화해
- "매개변수 객체 만들기는 단순한 정리가 아니라 새로운 개념의 발견이다." 데이터 뭉치를 객체로 만들면 행위를 추가할 기반이 생겨