ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 명령-쿼리 분리(Command-Query Separation, CQS) 원리
    [옛날 글들] 설계 이야기 2024. 5. 29. 16:44
    728x90

    컴퓨터 프로그래밍이라는 것을 처음 배우기 시작하던 시절에 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

     

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

     

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

    728x90
Designed by Tistory.