✍️ 악취 11 : 기본형 집착
표현하려는 데이터가 심플하다면 언어가 제공하는 기본 타입으로도 충분히 구현할 수 있지만, 표현할 데이터 구조가 복잡해지고 제공할 기능이 복잡해진다면 단순 기본형으로 원활한 기능을 구현하기 어렵다. 가령 단순 화씨온도를 표현하고 싶다면 실수형으로 가능하겠지만 화씨, 섭씨 등 다양한 온도를 표현하고 싶다면 별도의 클래스로 제공하는 것이 더 합리적일 것이다.
여기 기본형 집착 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.
1. "기본형을 객체로 바꾸기"
2. "조건부 로직을 다형성으로 바꾸기"
🍊 기본형을 객체로 바꾸기
개발 초기엔 기본 타입으로 표현한 데이터를 기능이 추가됨에 따라, 구조가 확장됨에 따라 더 다양한 형태로 표현해야 하는 경우가 발생한다. 앞서 언급했던 예시와 동일하게, 화씨로만 표현하던 데이터를 섭씨로도 제공해야 하는 경우가 그러하다.
STEP 1 : 문제 파악
@AllArgsConstructor
@Getter
public class Order {
private String priority;
}
public class OrderProcessor {
public long numberOfHighPriorityOrders(List<Order> orders) {
return orders.stream()
.filter(o -> o.getPriority() == "high")
.count();
}
}
위 구조는 크게 두 가지 문제점을 가지고 있다.
1. priority는 type safety가 보장되지 않는다. 막말로 어떤 String 값이 와도 우선순위로 간주된다.
2. 우선순위 계산을 위한 별도의 메서드가 없고 외부에서 계산하고 있다.
STEP 2 : Order의 책임으로
priority의 type safety 보장과 우선순위 계산을 Order의 책임으로 구현해 보자.
@Getter
public class Order {
private static final List<String> LEGAL_PRIORITY = Arrays.asList("low", "normal", "high");
private String priority;
public Order(String priority) {
validate(priority);
this.priority = priority;
}
public boolean higherThan(String priority) {
validate(priority);
return index(this.priority) > index(priority);
}
private void validate(String priority) {
if (!LEGAL_PRIORITY.contains(priority))
throw new IllegalArgumentException(priority);
}
private int index(String priority) {
return LEGAL_PRIORITY.indexOf(priority);
}
}
public class OrderProcessor {
public long numberOfHighPriorityOrders(List<Order> orders) {
return orders.stream()
.filter(o -> o.higherThan("normal"))
.count();
}
}
위 구조는 여전히 문제점이 있다. 가장 큰 문제로, Order가 너무 많은 정보를 알고 있고 동시에 너무 많은 일을 처리한다. 가령 Order는 우선순위 검증을 위해 LEAGAL_PRIORITY도 알아야 하고, 우선순위 계산을 위한 메서드를 가지고 있다. 이는 너무 많은 기능이 Order에게 집중되었다고 볼 수 있다.
STEP 3 : Priority의 책임으로
기본형이었던 Priority를 클래스로 변경하고 Order에게 부여했던 책임을 Priority로 옮겨보자.
@Getter
public class Priority {
private static final List<String> LEGAL_PRIORITY = Arrays.asList("low", "normal", "high");
private String value;
public Priority(String value) {
if (!LEGAL_PRIORITY.contains(value))
throw new IllegalArgumentException(value);
this.value = value;
}
public boolean higherThan(Priority priority) {
return index(this) > index(priority);
}
private int index(Priority priority) {
return LEGAL_PRIORITY.indexOf(priority.value);
}
}
@Getter
public class Order {
private Priority priority;
public Order(Priority priority) {
this.priority = priority;
}
}
public class OrderProcessor {
public long numberOfHighPriorityOrders(List<Order> orders) {
Priority normal = new Priority("normal");
return orders.stream()
.filter(o -> o.getPriority().higherThan(normal))
.count();
}
}
'리팩토링' 카테고리의 다른 글
[리팩토링] 악취 13 : 반복문 (0) | 2023.01.10 |
---|---|
[리팩토링] 악취 11 : 기본형 집착 (2) (0) | 2023.01.02 |
[리팩토링] 악취 10 : 데이터 뭉치 (0) | 2023.01.02 |
[리팩토링] 악취 9 : 기능 편애 (0) | 2023.01.02 |
[리팩토링] 악취 8 : 산탄총 수술 (0) | 2022.12.22 |