리팩토링 15. 플래그 인수 제거하기
- etc/리팩토링
- 2023. 4. 30.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 15. 플래그 인수 제거하기 (Remove Flag Argument)
- 플래그는 보통 함수에 매개변수로 전달해서, 함수 내부의 로직을 분기하는데 사용한다.
- 플래그를 사용하는 함수는 차이를 파악하기 어렵다. (플래그는 좋지 않다)
- bookConcert(customer, false), bookConcert(customer, true)
- 플래그가 무슨 역할을 하는지 알 수 없다.
- bookConcert(customer), premiumBookConcert(Customer)
- 플래그가 없어지니, 더욱 더 읽기 편해졌다.
- bookConcert(customer, false), bookConcert(customer, true)
- 조건문 분해하기 (Decompose Condition)를 활용해서 플래그를 제거하고, 의미를 더욱 명확히 할 수 있다.
플래그의 사용은 나쁠까?
플래그성 매개변수는 보통 조건문 (If, Switch)에 사용된다. 플래그는 많이 사용하면 좋지 않지만, 플래그는 하나만 존재하더라도 좋지 않다. 그 이유는 다음과 같다.
- 한 메서드에 플래그가 많은 경우 → 이 메서드는 너무 많은 일을 하고 있다. 따라서 메서드 분리가 필요하다.
- 한 메서드에 플래그가 한 개 있다. → 메서드가 하는 일의 의미를 메서드를 직접 살펴보기 전까지 이해하기 어렵다.
예를 들어서 아래 코드가 있다고 가정해보자. 이 코드만 보았을 때, True / False가 무슨 역할을 하는지 알 수 없다. True / False가 무슨 역할을 하는지 알아보기 위해서 결국은 bookConcert()를 읽어봐야한다. 가독성의 심각한 저해를 가져온다.
bookConcert(customer, false);
bookConcert(customer, true);
이런 코드에서 Flag를 제거하고, 메서드로 분리해주면 어떻게 될까? 오히려 더욱 읽기 편해진다. 이제서야 false / true는 각각 Premium이었는지 아닌지를 결정하는 것인지를 알게 된다.
bookConcert(customer);
premiumBookConcert(Customer);
이처럼 플래그를 사용하면 코드의 가독성에 나쁜 영향을 준다. 따라서 플래그는 사용하지 않도록 하고, 내부에서 조건문을 사용하는 형식이라면 조건문 분리하기를 통해 메서드를 각각 추출해주는 것이 더욱 가독성에 좋다.
Before
아래 코드를 살펴보자. 먼저 deliveryDate의 True / False를 보면 된다. 이건 isRush라는 플래그를 의미한다. 그런데 인텔리제이는 isRush를 보여주지만, IDE를 쓰지 않으면 True / False의 문맥을 개발자는 전혀 알 수 없게 된다. deliveryDate()라는 메서드를 호출했는데, 어떤 동작이 있는지를 알기 위해서 결국은 deliveryDate()를 확인해야 하기 때문에 문제가 발생한다.
이것은 플래그 매개변수가 전달되어 메서드의 전체적인 가독성을 떨어뜨렸기 때문에 발생하는 문제다. 이 문제는 플래그를 전달하는 대신, 플래그를 사용하는 조건문을 메서드로 분리해서 각각을 '의미'로 표현해주는 것이 좋다. 따라서 다음과 같이 수정할 것이다.
- isRush가 true인 경우에 대응되는 rushDeliveryDate() 메서드 생성
- isRush가 false인 경우에 대응되는 regularDeliveryDate() 메서드 생성
public class Shipment {
// isRush 플래그가 전달된다.
// isRush 플래그가 무슨 역할을 하는지 deliveryDate()만을 했을 때 전혀 알 수 없다.
// 심지어 인텔리제이가 아니면 isRush는 보이지도 않는다. 단순히 개발자에게는 true, false만 보일 것이다.
public LocalDate deliveryDate(Order order, boolean isRush) {
if (isRush) {
int deliveryTime = switch (order.getDeliveryState()) {
case "WA", "CA", "OR" -> 1;
case "TX", "NY", "FL" -> 2;
default -> 3;
};
return order.getPlacedOn().plusDays(deliveryTime);
} else {
int deliveryTime = switch (order.getDeliveryState()) {
case "WA", "CA" -> 2;
case "OR", "TX", "NY" -> 3;
default -> 4;
};
return order.getPlacedOn().plusDays(deliveryTime);
}
}
}
After
이전에 deliveryDate를 두 개의 메서드로 분리했다. 코드 복잡도 자체는 올라갔을 수도 있지만, 오히려 플래그를 제거함으로써 각 메서드가 가지는 의미 자체를 더욱 명확하게 이해할 수 있게 되었다.
public class Shipment {
public LocalDate regularDeliveryDate(Order order) {
int deliveryTime = switch (order.getDeliveryState()) {
case "WA", "CA" -> 2;
case "OR", "TX", "NY" -> 3;
default -> 4;
};
return order.getPlacedOn().plusDays(deliveryTime);
}
public LocalDate rushDeliveryDate(Order order) {
int deliveryTime = switch (order.getDeliveryState()) {
case "WA", "CA", "OR" -> 1;
case "TX", "NY", "FL" -> 2;
default -> 3;
};
return order.getPlacedOn().plusDays(deliveryTime);
}
public static void main(String[] args) {
Shipment shipment = new Shipment();
// shipment.regularDeliveryDate();
// shipment.regularDeliveryDate();
}
}
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 18. 변수 쪼개기 (0) | 2023.05.01 |
---|---|
리팩토링 16. 여러 함수를 클래스로 묶기 (0) | 2023.04.30 |
리팩토링 14. 매개변수를 질의 함수로 바꾸기 (0) | 2023.04.30 |
냄새 4. 긴 매개변수 목록 (0) | 2023.04.30 |
리팩토링 13. 조건문을 다형성으로 바꾸기 (0) | 2023.04.29 |