가변 데이터에 대한 내용 중 하나 인 질의 함수와 변경 함수 분리하기 리팩터링과 세터 제거하기에 대해 알아보자.
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 |
댓글