BackEnd/Refactoring

[Refactoring] 데이터 뭉치 (Data Clumps), 기본형 집착 (Primitive Obsession)

샤아이인 2023. 1. 6.

백기선 님의 리팩터링 강의를 들으며 요약한 내용입니다.

 

10. 데이터 뭉치

▶ 항상 뭉쳐 다이는 데이터는 한 곳으로 모아두는 것이 좋다.

1) 여러 클래스에 존재하는 비슷한 필드 목록

2) 여러 함수에 전달하는 매개변수 목록 관련 리팩토링 기술

 

관련된 리팩토링 기술로는,

“클래스 추출하기 (Extract Class)”를 사용해 여러 필드를 하나의 객체나 클래스로 모을 수 있다.

“매개변수 객체 만들기 (Introduce Parameter Object)” 또는 “객체 통째로 넘기기 (Preserve Whole Object)”를 사용해 메소드 매개변수를 개선할 수 있다.

 

11. 기본형 집착

보통 애플리케이션을 개발할 때 도메인에 필요한 기본 타입을 만들기보다는 프로그래밍 언어가 제공하는 기본타입을 사용하는 경우가 많다.

예를 들어 전화번호 같은 경우 단순 String으로 표현이 가능하지만, 하나의 객체로 만들면 좀더 의미있는 메서드와 상태를 추가해줄 수 있다.

 

가령 PhoneNumber 객체를 만든다면, 지역변호를 얻는 메서드를 만들어 줄 수도 있을것이다.

 

11 - 1) 기본형을 객체로 바꾸기

우선 코드로 살펴보자.

 

▶ Order

public class Order {

    private String priority;

    public Order(String priority) {
        this.priority = priority;
    }

    public String getPriority() {
        return priority;
    }
}

지금 우리의 priority 파라미터에는 String이기만 하면, 뭐든 들어올수가 있다.

타입 검증을 못하고 있는 상황이다.

 

▶ OrderProcessor

public class OrderProcessor {

    public long numberOfHighPriorityOrders(List<Order> orders) {
        return orders.stream()
                .filter(o -> o.getPriority() == "high" || o.getPriority() == "rush")
                .count();
    }
}

지금은 위 코드처럼 String형 문자열로 "high", "rush"인 것의 갯수를 직접 count하고 있다.

 

이를 Priority라는 값객체로 만들어보자

▶ Priority

public class Priority {

    private String value;
    private List<String> legalValues = List.of("low", "normal", "high", "rust");

    public Priority(String value) {
        if (!this.legalValues.contains(value)) {
            throw new IllegalArgumentException("Illegal value for priority " + value);
        }

        this.value = value;
    }

    public boolean higherThan(Priority other) {
        return this.index() > other.index();
    }

    private int index() {
        return this.legalValues.indexOf(this.value);
    }

    @Override
    public String toString() {
        return this.value;
    }
}

 

기존의 OrderProcessor의 메서드를 다음과 같이 변경할 수 있다.

public long numberOfHighPriorityOrders(List<Order> orders) {
    return orders.stream()
            .filter(o -> o.getPriority().higherThan(new Priority("normal")))
            .count();
}

 

11 - 2) 타입 코드를 서브클래스로 바꾸기

비슷한 것들을 만들어내야 할 때가 있습니다. 이런경우 서브 클래스를 활용 할 수 있습니다.

 

이번에는 아래 코드를

public class Employee {

    private String name;
    private String type;

    public Employee(String name, String type) {
        this.validate(type);
        this.name = name;
        this.type = type;
    }
    // 일부 생략...
}

 

아래 코드로 바꿉니다

public abstract class Employee {

    private String name;

    protected Employee(String name) { this.name = name; }

    public static Employee createEmployee(String name, String type) {
        return switch (type) {
            case "engineer" -> new Engineer(name);
            case "manager" -> new Manager(name);
            case "salesman" -> new Salesman(name);
            default -> throw new IllegalArgumentException(type);
        };
    }

    protected abstract String getType();

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", type='" + getType() + '\'' +
                '}';
    }
}

위의 코드처럼 타입을 넣으면 되긴 합니다.

 

하지만 이렇게 서브 클래스를 사용하면 2가지가 가능합니다

첫번째는 조건문에서 다형성을 활용할 수 있게 됩니다 - Replace Conditional with Polymorphism

두번째는 서브 클래스로 만들어서 필드를 이동시킬 수 있습니다. - Push down fields 를 사용 할 수 있습니다.

 

11 - 3) 조건부 로직을 다형성으로 바꾸기

복잡한 조건식이나 switch문으로 로직을 분기하는 코드를, 상속과 다형성을 사용해 코드를 보다 명확하게 분리할 수 있다.

 

기본 동작과 (타입에 따른) 특수한 기능이 섞여있는 경우에 상속 구조를 만들어서 기본 동작을 상위클래스에 두고 특수한 기능을 하위클래스로 옮겨서 각 타입에 따른 차이점을 강조할 수 있다.

 

모든 조건문을 다형성으로 옮겨야 하는가? 단순한 조건문은 그대로 두어도 좋다.

오직 복 잡한 조건문을 다형성을 활용해 좀 더 나은 코드로 만들 수 있는 경우에만 적용한다. (과용 을 조심하자.)

댓글