기본형 집착하는 코드에 대한 냄새이다.
애플리케이션이 다루고 있는 도메인에 필요한 기본 타입을 만들지 않고 프로그래밍 언어가 제공하는 기본 타입을 사용하는 경우가 많다.
예를 들어 int, double, float, String, boolean..으로 표현하는 데이터이다.
기본형으로는 단위 또는 표기법을 표현하기 어렵다.
전화번호, 돈의 단위들도 마찬가지로 표현하기 위해 코드를 지저분하게 만든 경우에 확인해볼 냄새이다.
관련 리팩터링 기술을 하나씩 알아보자.
기본형을 객체로 바꾸기
개발 초기에는 기본형으로 표현한 데이터가 나중에는 해당 데이터와 관련 있는 다양한 기능을 필요로 하는 경우 발생한다.
화씨, 섭씨와 같이 각종 단위를 표현해야 한다거나, 전화번호의 양식, 주민등록번호 등등 이러한 포맷을 지원해야 한다면 기본형으로는 한계에 봉착에 코드가 두꺼워진다.
따라서 기본형을 사용한 데이터를 감싸 줄 클래스를 만들면 손쉽게 이러한 기능을 제공해줄 수 있다.
public class Order {
private String priority;
public Order(String priority) {
this.priority = priority;
}
public String getPriority() {
return priority;
}
}
public class OrderProcessor {
public long numberOfHighPriorityOrders(List<Order> orders) {
return orders.stream()
.filter(o -> o.getPriority() == "high" || o.getPriority() == "rush")
.count();
}
}
기본형의 priority(우선 순위)에 대해 더 많은 기능이 추가될 수 있습니다. 예를 들어 특정 우선순위보다 더 높은지 확인하는 기능이 필요할 때 위와 같이 필터링을 통해 처리할 수 있습니다. 즉 별도의 계산 로직이 필요합니다.
이를 개선해봅시다.
이를 클래스로 감싸봅니다.
public class Priority {
private String value;
public Priority(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
추가적으로 type safe가 보장되기 위한 로직을 추가해서 의도하지 않은 문자열을 막는 기능도 추가할 수 있습니다.
private List<String> legalValues = List.of("low", "normal", "high", "rush");
public Priority(String value) {
if (legalValues.contains(value))
this.value = value;
else
throw new IllegalArgumentException("illegal value :" + value);
}
이제 인덱스를 활용해 특정 우선순위가 더 큰지 확인하는 코드를 작성할 수 있습니다.
public boolean higherThan(Priority other) {
return this.index() > other.index();
}
private int index() {
return this.legalValues.indexOf(this.value);
}
클래스로 기본형을 감싸는 것만으로도 더 많은 기능을 손쉽게 추가할 수 있다는 점입니다.
@Test
void numberOfHighPriorityOrders() {
OrderProcessor orderProcessor = new OrderProcessor();
long highPriorityOrders = orderProcessor.numberOfHighPriorityOrders(
List.of(new Order("low"),
new Order("normal"),
new Order("high"),
new Order("rush")));
assertEquals(2, highPriorityOrders);
}
기존 테스트 코드를 그대로 가져가고 priority 클래스만 사용하도록 변경하고 싶다면 해당 생성자를 위임하는 쪽으로 방향을 잡아보자.
public Order(String priority) {
this.priority = new Priority(priority);
}
기본형 타입을 클래스로 승격시킴으로 써 더 많은 요구사항에 대해 손쉽게 추가, 관리가 가능해졌다.
이를 적용하면서 궁금했던 것은 엔티티이다.
엔티티는 보통 기본형 타입을 많이 사용해서 구성한다. 그런데 @MappedSuperclass는 엔티티가 아니다. 속성 값을 상속하기 위해 사용하는 클래스이다. 공통적으로 사용해야 하는 속성들에는 중복될 수 있는 메서드가 존재할 수 있다. 이를 부모에서 미리 정의하여 제공하면 중복을 제거하고 기능을 쉽게 추가할 수 있을 것 같다.
이를 활용하면 기본형에 대한 추가 기능을 별도의 클래스로 분리하여 깔끔하게 관리할 수 있지 않을까?
혹은 임베디드 타입도 고려해볼 수 있다. 그렇지만 값 타입을 사용하는 순간. 이를 불변 객체로 만드는 것을 고려하기 때문에 변경에 대한 제약이 생길 것이다. 다만 이를 통해 값을 얻어와서 가공하는 메서드를 임베디드 클래스에서 제공하여 책임을 분가시키면 더욱 깔끔한 엔티티가 될 것이다.
'독서에서 한걸음' 카테고리의 다른 글
Replace Conditional with Polymorphism (0) | 2022.12.21 |
---|---|
타입 코드를 서브클래스로 변경하기 (0) | 2022.12.19 |
Shotgun Surgery (0) | 2022.12.18 |
Divergent Change (0) | 2022.12.14 |
Change Reference to Value (0) | 2022.12.11 |
댓글