노트

특이 케이스와 널 객체

Special Case and Null Object

설계#pattern · 연결된 개념 4개

쉽게 말하면

널 객체는 이름을 모르는 손님이 올 때마다 직원이 따로 처리하는 대신, '고객님'이라고 적힌 기본 명찰을 하나 만들어 두는 거예요. 그러면 어디서든 명찰을 읽기만 하면 돼요.

비유가 깨지는 곳 기본 명찰만 있으면 진짜 손님과 구분이 안 될 수 있어서 isUnknown 같은 표시를 남겨요. 옵셔널 체이닝은 검사를 짧게 쓸 뿐 흩어진 처리를 한곳에 모아 주진 않아요.

특정 값(가장 흔하게는 null이나 "미확인" 같은 표시값)을 만날 때마다 같은 처리를 하는 코드가 곳곳에 흩어져 있을 때, 그 경우를 대표하는 객체를 하나 만들어 공통 동작을 담는 패턴. null을 대표하면 널 객체(Null Object)라 부르며, 널 객체는 특이 케이스(Special Case)의 한 예다.

// before: 곳곳에 같은 검사
const name = customer === 'unknown' ? '거주자' : customer.name
 
// after
const unknownCustomer = Object.freeze({ name: '거주자', plan: basicPlan, isUnknown: true })
const name = customer.name
  • 검사 코드 대부분이 평범한 속성 접근이나 메서드 호출로 바뀐다
  • 읽기만 한다면 위처럼 불변 리터럴 객체로 충분하고, 동작이 필요하면 메서드를 가진 클래스로 만든다. 데이터를 받아올 때 변환 함수에서 특이 케이스 객체를 끼워 넣는 방식도 있다
  • 특이 케이스 여부를 꼭 알아야 하는 곳을 위해 isUnknown 같은 속성을 남긴다
  • 임시 필드(Temporary Field)가 비어 있을 때를 위한 대안 객체로도 쓴다

언어 차원의 대안으로 옵셔널 체이닝과 null 병합이 있지만, 그건 검사를 짧게 쓸 뿐 흩어진 처리를 모아 주지는 않는다. 다형성으로 분기를 없앤다는 점에서 조건부 로직을 다형성으로 바꾸기과 같은 계열이다.

출처: 『리팩터링 2판』 마틴 파울러 (원서 Refactoring, 2nd Edition) · refactoring.com: Introduce Special Case · Refactoring.Guru: Introduce Null Object · Martin Fowler: Special Case

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 매개변수 객체 만들기

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

  • 변수·컬렉션 캡슐화

    넓은 범위에서 쓰이는 데이터에 직접 접근하지 못하게 하고, 읽고 쓰는 함수를 통해서만 다루게 하는 리팩터링. 데이터를 옮기거나 바꿀 때 고쳐야 할 곳이 접근 함수 한 곳으로 좁아진다.

  • 값 객체

    식별자 없이 담고 있는 값 자체로 같음을 판단하는 불변 객체. 금액, 기간, 주소, 이메일처럼 "어떤 것"이 아니라 "얼마·무엇"을 나타내는 개념에 쓴다. 식별자로 같음을 판단하고 상태가 바뀌는 엔티티(Entity)와 대비된다(ddd).

  • 코드 냄새

    당장 버그는 아니지만 이해나 변경 비용을 높이는 구조적 신호. 리팩터링을 언제 시작하고 멈출지에 정확한 공식은 없어서, 냄새라는 어휘로 직관을 공유한다.

  • 기본형 집착

    전화번호·금액·우선순위 같은 도메인 개념을 끝까지 문자열이나 숫자로만 다루는 냄새. 같은 검증과 포맷 코드가 여기저기 반복되고, 문자열로 모든 걸 표현하는 "stringly typed" 코드가 된다.

보기 옵션