본문 바로가기
독서에서 한걸음

Separate Query from Modifier && Remove Setting Method

by oncerun 2022. 12. 10.
반응형

 

가변 데이터에 대한 내용 중 하나 인 질의 함수와 변경 함수 분리하기 리팩터링과 세터 제거하기에 대해 알아보자.

Separate Query from Modifier

 

Modifier라는 것은 어떤 변경을 발생 시킬 수 있는 함수를 말합니다.

 

이 주제에 대해 이해를 하는 쉬운 예시가 하나 있는데 바로 getter/setter입니다.

 

조회를 하는 쿼리와 변경을 가하는 쿼리를 구분해서 함수를 만드는 것에 대한 이야기입니다.

 

이를 command-query separation 규칙이라고 한다. 

 

어떤 값을 리턴하는 함수는 사이드 이펙트가 없어야 한다라는 규칙인데요.

 

근데 사이드 이펙트를 구분할 때 가령, 캐시는 중요한 객체 상태 변화는 아니라고 이야기하고 있다. 

 

어떤 메서드 호출로 인해 캐시 데이터를 변경하더라도 분리할 필요는 없다.

 

즉 눈에 띌만한 사이드 이펙트에 해당하는 경우 분리해보자라는 말이다.

 

public double getTotalOutstandingAndSendBill() {
    double result = customer.getInvoices().stream()
            .map(Invoice::getAmount)
            .reduce((double) 0, Double::sum);
    sendBill();
    return result;
}

 

해당 메서드는 함수명에서도 암시할 수 있듯이 미납금액의 총계를 내서 실제 청구서를 보내는 메서드라는 것을 알 수 있습니다. 

 

총계를 가져오고만 싶은데, 이메일까지 보내는 것입니다. 이를 분리해보죠.

 

public double getTotalOutstanding() {
    return customer.getInvoices().stream()
            .map(Invoice::getAmount)
            .reduce((double) 0, Double::sum);
}

public void sendBill() {
    emailGateway.send(formatBill(customer));
}

 

 

기존 하나의 함수로 존재했지만 이 둘을 분리함으로 조회하는 쿼리와 커맨드 쿼리를 분리하는 것을 볼 수 있습니다.

 

public String alertForMiscreant(List<Person> people) {
    for (Person p : people) {
        if (p.getName().equals("Don")) {
            setOffAlarms();
            return "Don";
        }

        if (p.getName().equals("John")) {
            setOffAlarms();
            return "John";
        }
    }

    return "";
}

 

해당 코드를 조회하는 것과 커맨드를 분리해보겠습니다.

 

public void alertForMiscreant(List<Person> people) {
    if (!findMiscreant(people).isBlank()) {
        setOffAlarms();
    }
}

public String findMiscreant(List<Person> people) {
    for (Person p : people) {
        if (p.getName().equals("Don")) {
            return "Don";
        }
        if (p.getName().equals("John")) {
            return "John";
        }
    }
    return "";
}

이제 클라이언트에서는 조회와 커맨드를 분리해서 사용하게 됩니다.

 

@Test
public void findMiscreants() throws Exception {

    Criminal criminal = new Criminal();

    String found = criminal.findMiscreant(List.of(new Person("Keesun"), new Person("Don")));

    assertEquals("Don", found);

    criminal.alertForMiscreant(List.of(new Person("Keesun"), new Person("Don")));
}

 

 

Remove Setting Method

 

 

세터를 제공한다는 것은 해당 필드가 변경될 수 있다는 것을 뜻한다.

 

객체 생성 시 처음 설정된 값이 변경될 필요가 없다면 해당 값을 설정할 수 있는 생성자를 만들고 세터를 제거해서 변경될 수 있는 가능성을 제거해야 한다.

 

public class Person {

    private String name;

    private int id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

 

예를 들어 id 값이 설정되고 다음부터 변경되지 않는다면 다음과 같은 방법으로 처리할 수 있습니다.

 

1. 생성자를 사용해 생성 시 값을 넣고 setter 메서드를 제거한다.

 

2. 빌더 패턴을 적용하고 기본 생성자 대신 private 생성자에 적용하여 처리한다. 


반응형

'독서에서 한걸음' 카테고리의 다른 글

Change Reference to Value  (0) 2022.12.11
Replace Derived Variable with Query, Combine Functions into Transform  (0) 2022.12.11
Split Variable  (0) 2022.12.05
변수 캡슐화  (0) 2022.12.04
긴 매개변수 목록  (0) 2022.12.04

댓글