Null Object パターン

null チェックの代わりに、何もしないオブジェクトを使って条件分岐を排除するデザインパターン

設計パターン型安全

Null Object パターンとは

Null Object パターンは、null を返す代わりに「何もしない」振る舞いを持つオブジェクトを返すことで、呼び出し側の null チェックを不要にするデザインパターンである。Bobby Woolf が Pattern Languages of Program Design 3 (1998 年・Addison-Wesley) に寄稿した「Null Object」で定式化したパターンである。

Tony Hoare は 2009 年の講演で、自身が ALGOL W に null 参照を導入したことを「10 億ドルの過ち」と振り返っている。Null Object パターンは、null が引き起こす問題のうち「呼び出し側に分岐を強いる」部分を、設計レベルで消しに行く手段だ。

Before / After

取得関数が null を返し得る設計では、依存を 1 つ増やすたびに呼び出し側の分岐が 1 つ増える。

// ❌ null チェックがコード全体に散在
function processOrder(order: Order) {
  const logger = getLogger();
  if (logger !== null) logger.info('Processing started');

  const metrics = getMetrics();
  if (metrics !== null) metrics.increment('orders.processed');

  const notifier = getNotifier();
  if (notifier !== null) notifier.send('Order processed');
}

// ✅ Null Object パターン: null チェック不要
function processOrder(order: Order) {
  const logger = getLogger();     // NullLogger or ConsoleLogger
  const metrics = getMetrics();   // NullMetrics or DatadogMetrics
  const notifier = getNotifier(); // NullNotifier or SlackNotifier

  logger.info('Processing started');
  metrics.increment('orders.processed');
  notifier.send('Order processed');
}

実装

要点は、何もしない実装を本物と同じインターフェースで用意し、切り替えを生成箇所 1 か所に閉じ込めることだ。

interface Logger {
  info(message: string): void;
  error(message: string, error?: Error): void;
}

class ConsoleLogger implements Logger {
  info(message: string) { console.log(`[INFO] ${message}`); }
  error(message: string, err?: Error) { console.error(`[ERROR] ${message}`, err); }
}

class NullLogger implements Logger {
  info(_message: string) { /* 何もしない */ }
  error(_message: string, _err?: Error) { /* 何もしない */ }
}

// ファクトリ関数で切り替え
function createLogger(enabled: boolean): Logger {
  return enabled ? new ConsoleLogger() : new NullLogger();
}

NullLogger はインターフェースを完全に実装するが、すべてのメソッドが何もしない。呼び出し側は Logger インターフェースだけを知っていればよく、実体が ConsoleLogger か NullLogger かを意識しない。

実務での活用パターン

テスト時のモック代替

テストで外部サービスへの通知を抑制する。

// テスト: 実際のメール送信を抑制
const service = new OrderService(
  new InMemoryOrderRepo(),
  new NullEmailSender(),   // メールを送らない
  new NullLogger(),        // ログを出さない
);

Optional Chaining との違い

TypeScript?. (Optional Chaining) は null チェックを簡潔に書く構文だが、Null Object パターンとは目的が異なる。

手法目的適するケース
Optional Chaining (?.)null アクセスの安全化プロパティの参照
Null Object パターン振る舞いのデフォルト化メソッド呼び出しの分岐排除

user?.address?.city は値の参照に適しているが、logger?.info('...') のように振る舞いを持つオブジェクトには Null Object パターンの方が適切だ。

注意点

デバッグが難しくなる

NullLogger を使うと、エラーが発生してもログが出ない。本番環境で NullLogger が使われていないか確認する仕組みが必要だ。

過度な適用

すべての null を Null Object に置き換える必要はない。「値が存在しない」ことに意味がある場合 (検索結果が 0 件など) は、null や空配列を返す方が適切だ。

判断の目安は、そのメソッドが戻り値を持つかどうかにある。副作用だけのメソッド (ログ出力・通知・計測) は「何もしない」が自然な既定値になる。一方、戻り値のあるメソッドで空文字や 0 を返す Null Object を作ると、呼び出し側が本物の値と区別できず、集計や表示に無言で混ざり込む。

Null Object vs Optional vs null チェック

3 つの方式を、書き忘れが実行時エラーになるか (安全性) と、分岐がどこに置かれるか (可読性) で比べる。

方式安全性可読性
null チェック (if)書き忘れた箇所がそのまま実行時のエラーになる分岐が呼び出し側に散り、ネストが深くなる
Optional/Maybe中身を取り出す前に、有無の扱いを型が要求する取り出しの記述は挟まるが、分岐は 1 箇所に収まる
Null Object何もしない実装を返すため、呼び出し側に null が渡らない呼び出し側は分岐を書かずに済む

実践的な知識は関連書籍でも得られる。

この記事は役に立ちましたか?

関連用語

関連する記事