앞으로 나열할 리팩터링 기법들은 가장 기본적이고 많이 사용하는 리팩터링들이다. 그중에서도 가장 많이 사용하는 리팩터링인 경우에는 제목 옆에 별기호(⭐)를 추가해놨다.

코드 조각을 찾아 무슨 일을 하는지 파악한 다음, 독립된 함수로 추출하고 목적에 맞는 이름을 붙인다.
📜 절차
📚 유효범위를 벗어나는 변수가 없을 때
해당 코드를 잘라내 새 함수에 붙이고, 원래 자리에 새 함수 호출문을 넣는다.

📚 지역 변수를 사용할 때
지역 변수들을 매개변수로 넘긴다.

📚 지역 변수의 값을 변경할 때
그 지역 변수를 쪼개서 임시 변수를 새로 하나 만들어 그 변수에 대입하게 한다.


함수 본문이 이름만큼 명확한 경우일 때는 그 함수를 제거한다. 쓸데없는 간접 호출은 하지 않는다.
📜 절차

표현식이 너무 복잡한 경우 지역 변수를 활용하여 표현식을 쪼개, 관리하기 쉽도록 한다.
📜 절차
함수 안에서만 의미가 있다면 변수로 추출하고, 함수를 벗어난 넒은 문맥에서까지 의미가 된다면 변수가 아닌 함수로 추출한다.

변수가 원래 표현식과 다를 바가 없을 경우 해당 변수를 인라인하도록 한다.
📜 절차

함수의 이름이 좋으면 함수의 구현 코드를 살펴볼 필요 없이 호출문만 보고도 무슨 일을 하는지 파악할 수 있다.

데이터로의 접근을 독점하는 함수를 만들어, 그 함수를 통해서만 접근할 수 있도록 한다.
데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다. 데이터의 유효 범위가 넓어질수록 다루기 어렵기 때문에 데이터의 유효범위가 넓을수록 반드시 캡슐화해야 한다.
📜 절차
레코드 캡슐화하기를 적용할지 고려해본다.게터의 경우 데이터의 복제본을 반환하도록 수정하여, 원본을 변경해서 발생할 사고를 미연에 방지하도록 한다.

명확한 프로그래밍의 핵심은 이름짓기다.
📜 절차
변수 캡슐화하기를 고려한다.
데이터 뭉치를 데이터 구조로 묶으면 데이터 사이의 관계가 명확해지고, 같은 데이터 구조를 사용하는 모든 함수가 원소를 참조할 때 항상 똑같은 이름을 사용하기 때문에 일관성도 높아진다.
📜 절차
함수 선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다.다음의 예제는 온도 측정값 증 정상 작동 범위를 벗어난 것이 있는지 검사하는 코드다. 코드를 살펴봤을 때 min, max 매개변수는 범위라는 하나의 객체로 묶어서 리팩토링할 수 있다.

NumberRange 클래스를 선언해 데이터를 묶는다. 객체가 아닌 클래스로 선언한 이유는 해당 객체로부터 관련 동작까지 클래스 안에서 작성할 수 있기 때문이다.

이렇게 온도가 허용 범위 안에 있는지 검사하는 메서드를 클래스에 추가할 수 있다.


공통 데이터를 중심으로 긴밀하게 엮어 작동하는 함수 무리가 있다면, 클래스로 묶는다.
📜 절차
다음의 예제를 보자. rawReading 데이터를 가지고 비슷한 연산을 수행하는 부분은 다음과 같다.
const rawReading = { customer: "ivan", quantity: 10, month: 5, year: 2017 };
const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity;
const taxableCharge = Math.max(0, baseCharge - taxThreshold(reading.year));
먼저, rawReading 객체를 클래스로 변환하는 리팩터링을 거친다.

공통 데이터 객체를 사용하는 함수 각각을 새 클래스로 옮긴다.

이렇게 클래스로 묶으면 이 함수들이 공유하는 공통 환경을 더 명확하게 표현할 수 있고, 각 함수에 전달되는 인수를 줄여서 객체 안에서의 함수 호출을 간결하게 만들 수 있다.

한 데이터를 입력받아 도출된 여러 도출 로직들은 한군데로 묶는다.
📜 절차
여러 함수를 클래스로 묶기와의 차이는, 원본 데이터가 코드 안에서 갱신되는 경우에는 클래스로 묶는 것이 낫다. 변환 함수로 묶으면 가공한 데이터를 새로운 레코드에 저장하기 때문에 원본 데이터가 수정되면 일관성이 깨질 수 있기 때문이다. 여러 함수를 변환 함수로 묶기의 경우는 그래서 읽기 전용 데이터를 다룰 때 특히 좋다.

서로 다른 두 대상을 한꺼번에 다루는 코드를 발견한다면, 각각을 별개 모듈로 나누도록 한다.
📜 절차
다음의 예제는 상품의 결제 금액을 계산하는 예제다.
코드를 살펴봤을 때, 상품 가격을 계산하는 단계와 배송비를 계산하는 단계로 나눌 수 있다. 나중에 상품 가격과 배송비 계산을 더 복잡하게 만드는 변경이 생긴다면, 이처럼 두 단계로 나누는 것이 좋다.
export const priceOrder = (product, quantity, shippingMethod) => {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
const shippingPerCase = basePrice > shippingMethod.discountThreshold
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = quantity * shippingPerCase;
const price = basePrice - discount + shippingCost;
return price;
};
먼저 배송비 계산 부분을 추출해 applyingShippingCost 함수를 생성하고, 첫 번째 단계와 두 번째 단계가 주고받을 중간 데이터 priceData를 만든다. 이때 첫 번째 단계에서 사용되는 데이터 basePrice와 discount, quantity를 priceData로 옮긴다.
const applyingShippingCost = (priceData, shippingMethod) => {
const shippingPerCase = priceData.basePrice > shippingMethod.discountThreshold
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = priceData.quantity * shippingPerCase;
return priceData.basePrice - priceData.discount + shippingCost;
};
export const priceOrder = (product, quantity, shippingMethod) => {
// ...
const priceData = {basePrice, quantity, discount};
const price = applyingShippingCost(priceData, shippingMethod);
return price;
};
이제 첫 번째 단계 코드를 함수로 추출하고 priceData로 반환한다.
const calculatePriceData = (product, quantity) => {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
return { basePrice, quantity, discount };
};
// ...
export const priceOrder = (product, quantity, shippingMethod) => {
const priceData = calculatePriceData(product, quantity);
return applyingShippingCost(priceData, shippingMethod);
};