노트

명령-질의 분리

Command-Query Separation

설계#refactoring · 연결된 개념 12개

쉽게 말하면

명령-질의 분리는 '물어보는 말'과 '시키는 말'을 섞지 말자는 원칙이에요. 잔액을 물어봤을 뿐인데 몰래 돈이 빠져나가면 곤란하니까, 질문은 몇 번을 해도 아무것도 안 바뀌게 해요.

비유가 깨지는 곳 현실에선 묻기만 하면 아무 일도 없지만, 코드의 질의는 가변 전역 변수를 읽기만 해도 같은 입력에 다른 결과가 나올 수 있어요. 그 값을 매개변수로 받으면 참조 투명성을 되찾아요.

함수를 값을 돌려주는 질의(query)와 상태를 바꾸는 명령(command) 중 하나로만 만들라는 원칙. 질의 함수는 겉으로 관찰되는 부수 효과(Side Effect)가 없어야 한다.

// before: 이름에 And가 들어간 함수
function getTotalOutstandingAndSendBill() { ... }
 
// after
function totalOutstanding() { return invoices.reduce((t, i) => t + i.amount, 0) }
function sendBill() { emailGateway.send(formatBill(customer)) }
  • 부수 효과 없는 질의는 몇 번을 불러도, 어디로 옮겨도 안전하다. 이용할 때 신경 쓸 것이 거의 없다
  • 함수 이름에 "And"가 들어가면 두 가지 일을 하고 있다는 신호다
  • 심플 디자인 강의는 같은 이야기를 "Mutator와 Accessor를 구분하라"로 한다. 함수를 추출할 때도 부수 효과를 섞지 말라는 것이다

참조 투명성(Referential Transparency)

같은 입력에 항상 같은 결과를 내는 성질. 질의 함수가 가변 전역 변수를 읽으면 이 성질을 잃는다. 이럴 땐 그 값을 매개변수로 받게 하면 되찾을 수 있다(질의 함수를 매개변수로 바꾸기, Replace Query with Parameter). 반대로 함수가 스스로 쉽게 구할 수 있는 값을 매개변수로 받는 것은 일종의 중복이다(매개변수를 질의 함수로 바꾸기, Replace Parameter with Query). 둘 사이의 균형점은 프로젝트를 진행하며 옮겨 다닌다.

React 렌더 함수에 요구하는 순수성(React의 규칙과 렌더 순수성)과 부수 효과를 Effect로 분리하는 방식(부수 효과와 Effect)도 같은 원칙의 응용이다. 질의를 반복 호출해도 안전하다는 점은 서버 쪽 멱등성(Idempotency)과도 닮았다.

출처: 『리팩터링 2판』 마틴 파울러 (원서 Refactoring, 2nd Edition) · refactoring.com: Separate Query from Modifier · Refactoring.Guru: Separate Query from Modifier · Martin Fowler: Command Query Separation · 인프런 『심플 디자인』 박영록

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 매개변수 객체 만들기

    여러 함수에 늘 함께 넘겨지는 값 묶음(데이터 뭉치, Data Clumps)을 하나의 객체로 묶어 넘기는 리팩터링.

  • 보호 구문

    정상 흐름이 아닌 경우를 함수 앞부분에서 검사하고 바로 빠져나오는 조건문. 중첩된 if-else를 평평하게 펴서 핵심 흐름을 드러낸다.

  • 선언형과 명령형 프로그래밍

    명령형(Imperative)·절차적(Procedural) 프로그래밍은 결과에 도달하는 방법(how)을 단계별로 지시하고, 선언형 프로그래밍(Declarative Programming)은 원하는 결과(what)만 기술하고 과정은 도구에 맡긴다. 길 안내로 치면 "직진하다 두 번째 신호에서 우회전"이 명령형, "강남역 가 주세요"가 선언형이다.

  • 구성요소 줄이기

    셀 수 있는 모든 구성요소(파일, 폴더 깊이, 클래스, 함수, 분기, 변수, 테이블, 필드, 테스트 케이스…)는 비용이라는 관점. 심플 디자인의 네 번째 규칙을 실무 기준으로 풀어낸 것이다.

  • 반복문을 파이프라인으로 바꾸기

    for 반복문을 filter·map·reduce 같은 컬렉션 연산의 연쇄, 즉 컬렉션 파이프라인(Collection Pipeline)으로 바꾸는 리팩터링. 각 원소가 어떤 단계를 거치는지가 위에서 아래로 읽힌다.

보기 옵션