본문 바로가기

Java

Command Query Separation 원칙

우테코에서 로또 미션을 구현할 때 다음과 같은 메소드를 작성한 적이 있다.

public boolean hasNext() {
    return this.count-- > 0;
}

작성할 당시엔 위 메소드에 아무런 문제가 없다고 생각했다.

하지만 위 사진처럼 리뷰를 받았고 Command Query Separation(CQS) 원칙에 대해 공부를 하게 되었다.


Query

결과값을 반환하고, 시스템의 관찰가능한 상태를 변화시키지 않는다.
따라서 부작용에서 자유롭다.(free of side effects)

Command

결과를 반환하지 않고, 대신 시스템의 상태를 변화시킨다.

CQS 원칙은 말 그대로 결과값을 반환하는, 조회의 기능을 하는 Query와
상태를 변경 시키는 Command를 분리하는 것이다.

public boolean hasNext() {
    return this.count-- > 0;
}

이 메소드를

public boolean hasNext() {
    return this.count > 0;
}

public void next() {
    this.count--;
}

위 코드처럼 2개의 메소드로 분리하면 CQS 원칙을 지킨 것이다.


CQS 원칙 왜 지켜야 할까?

Command와 Query를 분리함으로써, 각 메소드의 의미를 매우 명확하게 해준다.

  • 명확한 메소드는 코드를 더 읽기 쉽게하고 유지 보수하기 쉽게한다.

side effect가 발생할 수 있는 코드와 그렇지 않은 코드를 분리해준다.

부수 효과를 발생시키지 않는 것만을 함수로 제한함으로써 소프트웨어에서 말하는 “함수”의 개념이 일반 수학에서의 개념과 상충되지 않도록 한다. 우리는 오브젝트를 변경하지만 직접적으로 값을 반환하지 않는 Command와 오브젝트에 대한 정보를 반환하지만 변경하지는 않는 Query 간의 명확한 구분을 유지할 것이다.
Betrand Meyer, Object-Oriented Software Construction 2nd Edition

Query는 조회의 기능만을 수행하므로 side effect로 부터 안전하다.
반면에 Command는 상태를 변경시키기 때문에 side effect가 발생할 확률이 있다.

side effect의 영향을 최소화하기 위해 side effect를 가진 메소드와
side effect으로 부터 안전한 메소드를 분리함으로써 얻는 효과는 다음과 같다.

  1. 디버깅이 용이하다.
  2. 버그를 줄일 수 있다.
  3. Query의 순서에 따라 실행 결과가 변하지 않는다.
return this.count-- > 0;

side effect를 가진 이런 코드가 분리되지 않았다면
side effect가 발생했을 때 return this.count-- > 0; 에서 문제가 있다는 것을 발견하기는 쉽지 않을 것이다.

HTTP에서 GET은 대표적인 안전한 메소드로서 자원의 상태만을 요청하기 때문에 서버측에서 별다른 주의없이 대응해도 된다.
반면 POST 등의 안전하지 않은 메소드는 자원의 상태를 변경하기 때문에 서버 입장에서 신중하게 다루어야 한다.
이때 GET은 Query에 대응되고, POST는 Command에 대응된다.

Query와 Command를 분리하지 않았다면 별다른 주의없이 대응하는 GET메소드에서
side effect가 발생할 확률이 있는 것 이다.
GET에서 side effect가 발생한 것을 찾기도 어려울 것이다.

이러한 이유들로 우리는 Command와 Query를 분리해야 한다.

Command Query Separation 원칙에 따라 Command와 Query를 분리했다면
복잡한 변경 로직을 VALUE OBJECT 내부로 이동시켜 side effect를 제어하는 방법을 고려해 보는 것이 좋다.


예외로 CQS 원칙을 위반하는 몇 가지 경우가 있다.
이들은 일반적으로 성능 또는 스레드 안전에 관한 것이다.

쉬운 예로 스택의 pop이 있다.
pop은 스택의 맨 위의 값을 빼서(command) 반환(query)하는 일까지 병행한다.
이 일반적인 액션은 명확히 command와 query를 겸임하고 있는 상태다.

가능하다면 CQS를 지키되, 예외가 필요할 때는 허용하는 것이 좋을 수 있다.
다만 CQS를 지키는 것이 유용하기 때문에 최대한 지키고
그럴 수 없는 경우에는 기능에 대한 문서화를 좀더 충실히 해놓는 것이 바람직하겠다.

[참고문서]


https://qastack.kr/software/67707/when-and-why-you-should-use-void-instead-of-e-g-bool-int
http://egloos.zum.com/aeternum/v/1125381
https://shoark7.github.io/programming/knowledge/command-and-query-method

'Java' 카테고리의 다른 글

Map 인터페이스의 유용한 메서드들  (0) 2020.04.18
null 반환보다는 Optional을 활용하자.  (5) 2020.04.10
Exception 정리  (0) 2020.03.04
Immutable Object(불변 객체)  (0) 2020.03.03
Factory Pattern(팩토리 패턴)  (0) 2020.02.24