본문 바로가기

Command-Query Separation(CQS) 원리

이터너티|2008.12.11 16:16

-작게 +크게
신고

컴퓨터 프로그래밍이라는 것을 처음 배우기 시작하던 시절에 x = x + 1이라는 문장을 보고 의아하게 생각했던 기억이 있다. 어떻게 x 1을 더한 값이 x와 같을 수 있지? 더 당황스러웠던 것은 프로그램 내의 함수 f에 대해 f(x) = y이고 f(x) = z일 경우 y z가 다른 값일 수 있다는 사실이었다. 별다른 배경지식이 없었던 당시에는 그저 프로그래밍과 수학에서 말하는 함수가 이름은 같지만 정의에 있어서는 미묘한 차이가 있는 것이라고 막연하게 추측했던 것 같다.

 

그리고 몇 년 후 x = x + 1이라는 문장이 에러라고 생떼를 쓰는 프로그래밍 언어를 보고 다시한 번 당혹감에 빠져 들었다. x 1을 더한 값을 x에 대입하는데 에러가 나는거지? 더 짜증이 났던 것은 f(x) = y이고 f(x) = z일 때 무조건 y z가 같도록 프로그래밍을 짜야 한다는 사실이었다.

 

여기에서 예로 든 두가지 방식의 가장 큰 차이점은 부수 효과(side effect)의 존재 유무라고 할 수 있다. 부수 효과를 기반으로 하는 언어에서 f(x) = y이고 f(x) = z라고 해도 y z가 같지 않을 수 있다. 함수 f에서 발생하는 부수 효과에 의해 결과값이 변경될 수 있기 때문이다. 반면에 부수 효과를 금지하는 언어에서는 f(x) = y이고 f(x) = z일 때 항상 y z가 동일하다. 전자를 명령형 언어(imperative language)라고 부르고 후자를 함수형 언어(functional language)라고 부른다. 현재 주류 언어의 위치를 차지하고 있는 Algol 계열의 언어들은 명령형 언어에 속하며 지금까지 우리가 책이나 학교에서 배웠던 프로그래밍 방식은 거의 대부분 부수 효과를 이용하는 명형형 프로그래밍 방식이라고 봐도 무관하다.

 

다음 Ruby 프로그램을 살펴보자. 메소드 n은 전역변수 value를 변경시키는 부수 효과를 포함하고 있다.

 

$value = 0

def n

  $value = $value + 1

end

 

함수가 부수 효과를 가질 경우 참조 투명성(referential transparency)을 만족할 수 없다. 참조 투명성이란 어떤 표현식 e가 있을 때, e의 값을 바꾸지 않고 다른 표현식으로 대체할 수 있음을 의미한다. 참조 투명성을 만족시키는 함수 f에 대해 f(x) = y이고 f(x) = z인 경우 y z는 항상 동일한 값을 가진다.

 

수학에서 표현식 n + n 2*n은 동일하다. , n + n = 2*n이다. 그러나 부수 효과를 가지는 경우 n + n 2*n은 서로 다른 값을 가진다.

 

class ReferentialTransparencyTest<Test::Unit::TestCase

  def test_n

    assert_equal(2*n,n+n)

  end 

end

 

2*n의 결과가 2인 반면 n + n의 값은 5이다. 참조 투명성이 유지되지 않는 경우 함수의 결과는 함수의 호출 순서에 의존한다. 다음 테스트 케이스에서 n + n과 2*n의 순서를 바꾸어 호출 할 경우 n + n 3, 2*n 6을 반환한다.

 

class ReferentialTransparencyTest<Test::Unit::TestCase

  def test_n

    assert_equal(n+n,2*n)

  end 

end

 

부수 효과는 프로그램의 버그를 발생시키는 온상이다. n + n = 2*n이라는 수식에 문제가 있다는 것을 발견하기는 쉽지 않다. 따라서 부수 효과를 없애면 디버깅이 용이해 진다. 부수 효과를 제거하고 참조 투명성을 유지함으로써 프로그램의 수행 결과를 예측 가능한 상태로 유지할 수 있다. 부수 효과를 가진 프로그램은 함수 호출 순서에 따라 다른 결과를 얻게 되므로 프로그래밍 동안 함수 호출 순서에 주의를 기울여야 한다. 부수 효과의 존재 유무를 판단하기 위해서는 추상화 뿐만 아니라 구현 세부 사항까지 인지하고 있어야 한다. 이것은 프로그래가 짊어져야 할 개념적 무게를 증가시킨다.

 

그러나 명령형 언어를 사용하는 경우 부수 효과를 피할 수는 없다. 대신 부수 효과의 영향을 최소화하기 위해 부수 효과를 가진 함수(일반적으로 프로시져라고 부른다)와 부수 효과를 가지지 않는 순수한 함수를 분리시키는 방법을 생각해 볼 수 있다. Command-Query Separation 원리의 기본 개념은 부수 효과의 발생 여부에 따라 객체의 메소드를 Command Query로 분리하자는 것이다.

 

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

-- Betrand Meyer, Object-Oriented Software Construction 2nd Edition

Command-Query Separation 원리를 한 문장으로 줄여 표현하면질문이 답변을 수정해서는 안 된다"는 것이다. Command는 상태를 변경하는 대신 객체의 상태를 반환해서는 안된다. Query는 객체의 상태를 반환하는 대신 값을 변경해서는 안 된다. Command Query를 분리함으로써 명령형 언어의 틀 안에서 함수형 언어의 장점을 제한적이나마 누릴 수 있게 된다. , 버그를 줄일 수 있고, 디버깅이 용이하며, Query의 순서에 따라 실행 결과가 변하지 않는다.

 

Command-Query Separation 원리에서 말하는 부수 효과의 범위는 외부에서 인식 가능한 부수 효과만으로 제한된다. Meyer는 이를 '추상적인 부수 효과(abstract side effect)'라고 표현했으며, Martin Folwer'관찰가능한 상태(Observable State)'의 변경이라고 표현했다. Command에서 객체의 내부 상태를 변경하더라도 변경 내용이 클라이언트에게 노출되지 않는다면 추상적인 부수 효과라고 볼 수 없다. 객체의 모든 상태 변경을 '구체적인 부수 효과(concrete side effect)'라고 할 때, 추상적인 부수 효과는 구체적인 부수 효과의 부분 집합이다. Meyer가 두 가지 부수 효과를 구분한 이유는 프로그래밍 언어의 부수 효과를 자동적으로 검출 할 수 없다는 사실을 강조하기 위해서이다. 따라서 Command에 의해 발생하는 추상적인 부수 효과를 인식하고 부수 효과에 의한 버그를 방지하는 것은 전적으로 프로그래머의 몫이다.

 

Command-Query Separation 원리에 따라 Command Query를 분리했다면 복잡한 변경 로직을 VALUE OBJECT 내부로 이동시켜 부수 효과를 제어하는 방법을 고려해 보는 것이 좋다. 이에 대한 자세한 논의는 Eric Evans의 저서인 “Domain-Driven Design”에서 다루고 있다. 


덧글 0 트랙백 0

아래 URL을 전체 선택하면 복사할 수 있습니다.

닫기

Concept & Principle 카테고리의 다른 글

이전 다음
TOP