리팩토링 24. 단계 쪼개기
- etc/리팩토링
- 2023. 5. 10.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 24. 단계 쪼개기 (Split Phase)
- 서로 다른 일을 하는 코드를 각기 다른 모듈(클래스)로 분리한다.
- 그래야 어떤 것을 변경할 때, 그것과 관련있는 것만 신경쓸 수 있다.
- 응집도가 잘 되어있도록 코드 작성을 하는 것인데, 변경이 필요할 때 특정 모듈만 변경하면 되도록 할 수 있음.
- 여러 일을 하는 함수라면 처리 과정을 각기 다른 단계로 구분할 수 있다.
- 예) 전처리 → 주요 작업 → 후처리
- 예) 컴파일러: 텍스트 읽어오기 → 실행 가능한 형태로 변경
- 서로 다른 데이터를 사용한다면 단계를 나누는데 있어 중요한 단서가 될 수 있다.
- 중간 데이터를 만들어 단계를 구분하고 매개변수를 줄이는데 활용할 수 있다.
- 목표 : 단계를 분리해서, 각 단계가 적절한 클래스에 위치할 수 있도록 하는 것이다.
단계 분리하기는 '메서드 안에서 각기 다른 일을 하는 코드들을 분리'하는 리팩토링이다. 하나의 메서드는 하나의 일만 할 때 이해하기 좋으며, 변경 시에도 손쉽게 변경할 수 있다.
'단계 분리하기'를 적용하기 좋은 경우는 '긴 메서드'일 때다. 긴 메서드에서는 구체적인 흐름이 있을텐데, 그 흐름을 나눠서 별도의 메서드로 분리를 한다. 이 때 메서드의 위치가 적절하지 않다면 다른 모듈로 메서드를 옮길 수 있다. 단계 분리하기는 다른 리팩토링의 전단계 리팩토링으로 사용할 수 있다.
긴 메서드는 여러 단계의 프로세스로 나눌 수 있다. 예를 들면 전처리 / 주요 작업 / 후처리 등이다. 이렇게 나눈 작업들은 별도의 클래스 옮기거나, 관련있는 작업들끼리 묶어주는 형태의 리팩토링이 가능하다.
단계를 나누는 기준은?
그렇다면 어떤 경우에 단계를 나누는 것이 좋을까? 사용할 데이터가 다르다면, 그 둘은 서로 다른 프로세스에 있을 가능성이 있다는 것이다.
단계 나누기의 장점은?
- 복잡한 함수를 문맥별로 나누는 작업을 한다. 이를 통해서 코드의 응집도를 높일 수 있다.
- 쪼갠 프로세스 간에 데이터를 전달해야 할 필요가 있을 때, 중간 데이터를 만들어서 사용할 수 있다. 이 방법으로 메서드의 매개변수를 줄일 수 있게 됨.
코드 (쪼개기 + 중간 데이터 만들기)
아래에서 priceOrder() 메서드를 살펴보자. 이 메서드에서는 다음 두 가지 작업을 한다
- basePrice + Discount를 계산한다.
- ShippingCost를 적용한 최종 Price를 구함.
이렇게 두 단계로 나누어 볼 수 있고, 각 단계를 메서드로 추출할 수 있다.
public class PriceOrder {
// 코드가 너무 복잡하다.
// 1. basePrice 구함.
// 2. shipping 비용을 구함.
// 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = quantity * shippingPerCase;
final double price = basePrice - discount + shippingCost;
return price;
}
}
가장 먼저 배송비를 구하는 단계를 메서드로 추출해 분리해 볼 수 있다. 적용하면 아래와 같이 메서드로 분리된다. 그런데 한 가지 문제점이 있다.
분리된 메서드에서 너무 많은 매개변수를 필요로 한다는 것이다. 이 부분을 해결 하기 위해서 중간 데이터를 위한 새로운 레코드를 하나 만들어서, 이 부분을 해결할 수 있게 된다.
public class PriceOrder {
// 코드가 너무 복잡하다.
// 1. basePrice 구함.
// 2. shipping 비용을 구함.
// 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
return applyShipping(quantity, shippingMethod, basePrice, discount);
}
private double applyShipping(int quantity, ShippingMethod shippingMethod, double basePrice, double discount) {
final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = quantity * shippingPerCase;
final double price = basePrice - discount + shippingCost;
return price;
}
}
중간 단계 변수를 도입하면서 다음과 같이 코드를 한번 수정할 수 있게 되었다. 이제 applyShipping() 메서드는 매개변수를 적게 가지게 된다. 그 다음으로 처리해야 하는 부분은 basePrice, Discount를 계산하는 단계를 메서드로 추출하는 것이다.
public class PriceOrder {
// 코드가 너무 복잡하다.
// 1. basePrice 구함.
// 2. shipping 비용을 구함.
// 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
PriceRealData priceRealData = new PriceRealData(basePrice, discount, quantity);
return applyShipping(priceRealData, shippingMethod);
}
private double applyShipping(PriceRealData priceRealData, ShippingMethod shippingMethod) {
final double shippingPerCase = (priceRealData.basePrice() > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = priceRealData.quantity() * shippingPerCase;
final double price = priceRealData.basePrice() - priceRealData.discount() + shippingCost;
return price;
}
}
마지막 단계를 분리하면 다음과 같이 코드가 리팩토링 된다.
public class PriceOrder {
// 코드가 너무 복잡하다.
// 1. basePrice 구함.
// 2. shipping 비용을 구함.
// 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
PriceRealData priceRealData = calculatePriceData(product, quantity);
return applyShipping(priceRealData, shippingMethod);
}
private PriceRealData calculatePriceData(Product product, int quantity) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
PriceRealData priceRealData = new PriceRealData(basePrice, discount, quantity);
return priceRealData;
}
private double applyShipping(PriceRealData priceRealData, ShippingMethod shippingMethod) {
final double shippingPerCase = (priceRealData.basePrice() > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = priceRealData.quantity() * shippingPerCase;
final double price = priceRealData.basePrice() - priceRealData.discount() + shippingCost;
return price;
}
}
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 26. 함수 옮기기 (0) | 2023.05.10 |
---|---|
리팩토링 25. 함수 옮기기 (0) | 2023.05.10 |
냄새 7. 뒤엉킨 변경 (0) | 2023.05.10 |
리팩토링 23. 참조를 값으로 바꾸기 (0) | 2023.05.02 |
리팩토링 22. 여러 함수를 변환 함수로 묶기 (0) | 2023.05.02 |