Files
2nd/10_Wiki/Topics/Architecture/Introduce Null Object (널 객체 도입하기).md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

6.5 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-introduce-null-object-널-객체-도입하기 Introduce Null Object (널 객체 도입하기) 10_Wiki/Topics verified self
Null Object Pattern
Null Object Refactoring
none A 0.9 applied
refactoring
design-pattern
null-safety
2026-05-10 pending
language framework
typescript none

Introduce Null Object (널 객체 도입하기)

매 한 줄

"매 null check 의 polymorphism 으로 대체". Fowler 의 Refactoring 카탈로그 entry — if (x == null) scattered checks 를 default-behavior object 로 collapse. 2026 modern 에서는 Optional/Maybe monad, sealed class hierarchy, TypeScript discriminated union 으로 evolve 했지만 core idea 는 동일.

매 핵심

매 문제

  • Repeated null guards: customer == null ? "unknown" : customer.name() 의 매 caller 마다 반복.
  • Null-aware logic 의 매 client code 에 leak — encapsulation violation.
  • NullPointerException / undefined error 의 매 production crash 의 top cause.

매 solution

  • Null state 를 dedicated subclass 로 represent: NullCustomer extends Customer.
  • Default behavior 를 polymorphic method 로 push down.
  • Caller 는 customer.name() 의 null check 없이 호출.

매 응용

  1. Fowler refactoring step-by-step (Replace Conditional with Polymorphism 의 sibling).
  2. Domain-driven design 의 missing aggregate handling.
  3. Strategy pattern 의 default no-op variant.

💻 패턴

1. Before (null check scattered)

class Site {
  constructor(public customer: Customer | null) {}
}

const customer = site.customer;
const name = customer === null ? "occupant" : customer.name;
const plan = customer === null ? BillingPlan.basic() : customer.plan;
const history = customer === null ? new PaymentHistory([]) : customer.history;

2. After (Null Object)

abstract class Customer {
  abstract get name(): string;
  abstract get plan(): BillingPlan;
  abstract get history(): PaymentHistory;
  isNull(): boolean { return false; }
}

class RealCustomer extends Customer {
  constructor(
    public name: string,
    public plan: BillingPlan,
    public history: PaymentHistory,
  ) { super(); }
}

class NullCustomer extends Customer {
  get name() { return "occupant"; }
  get plan() { return BillingPlan.basic(); }
  get history() { return new PaymentHistory([]); }
  isNull() { return true; }
}

class Site {
  constructor(public customer: Customer = new NullCustomer()) {}
}

// Caller — 매 null check 없이
const name = site.customer.name;

3. Special Case (richer variant)

class UnknownCustomer extends Customer {
  get name() { return "unknown"; }
  // ... default
}

class DeceasedCustomer extends Customer {
  get name() { return "[deceased]"; }
  // ... distinct semantics
}

// Factory dispatch
function loadCustomer(id: string): Customer {
  const row = db.find(id);
  if (!row) return new UnknownCustomer();
  if (row.deceased) return new DeceasedCustomer(row);
  return new RealCustomer(row.name, row.plan, row.history);
}

4. Optional / Maybe (functional alternative)

type Maybe<T> = { kind: "some"; value: T } | { kind: "none" };

const customer: Maybe<Customer> = loadCustomer(id);

const name = customer.kind === "some"
  ? customer.value.name
  : "occupant";

// Or with helper
function fold<T, R>(m: Maybe<T>, onSome: (v: T) => R, onNone: () => R): R {
  return m.kind === "some" ? onSome(m.value) : onNone();
}

5. Sealed class (Kotlin / TS discriminated union)

type CustomerState =
  | { tag: "real"; data: RealCustomer }
  | { tag: "null"; reason: "missing" | "deceased" };

function displayName(s: CustomerState): string {
  switch (s.tag) {
    case "real": return s.data.name;
    case "null": return s.reason === "deceased" ? "[deceased]" : "occupant";
  }
}

6. Logger Null Object (classic example)

interface Logger {
  log(msg: string): void;
}

class ConsoleLogger implements Logger {
  log(msg: string) { console.log(msg); }
}

class NullLogger implements Logger {
  log(_msg: string) { /* no-op */ }
}

class Service {
  constructor(private logger: Logger = new NullLogger()) {}
  // 매 logger null check 없이 항상 호출 가능
}

7. React/UI default component

function UserBadge({ user }: { user: User | null }) {
  // 매 null branch 없이 — NullUser component 로 dispatch
  const u = user ?? NULL_USER;
  return <span>{u.displayName}</span>;
}

const NULL_USER: User = {
  id: "anonymous",
  displayName: "Anonymous",
  avatar: "/default.png",
};

매 결정 기준

상황 Approach
OO codebase, 매 null check 의 3+ 위치 반복 Null Object class.
Functional / TS strict, 매 type-level safety 필요 Maybe / Optional / discriminated union.
Side-effect 의 default no-op (logger, audit) NullLogger 의 dependency injection.
매 distinct missing-state semantics (unknown vs deceased) Special Case (multiple null subtypes).
매 single-use null check 매 그냥 inline ?? default.

기본값: TypeScript / modern stack 에서는 ?? default 또는 discriminated union. OO heavy refactor target 에서는 Null Object class.

🔗 Graph

🤖 LLM 활용

언제: legacy codebase 의 null-check noise 제거 refactor, library API 의 default behavior design. 언제 X: 매 single null check, performance-critical hot path (allocation overhead), exception 이 의도된 signal 인 경우.

안티패턴

  • Hidden bugs: NullCustomer 의 silent default 가 매 real bug 를 mask. Logging 또는 isNull() flag 로 trace 가능하게 유지.
  • God Null Object: 매 모든 method 에 default 의 over-design. 매 진짜 polymorphic 사용처만 cover.
  • Equality confusion: nullCustomer === null false 의 매 caller 혼란. 매 명확한 documentation + isNull() helper.
  • Mutation on Null Object: 매 shared instance 의 mutation 이 cross-contamination 의 cause. 매 immutable 유지.

🧪 검증 / 중복

  • Verified (Fowler, Refactoring 2nd ed., "Introduce Special Case" — Null Object 의 modern rename).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — Null Object refactoring + modern Maybe/sealed union variants