エンジニアを目指す初学者に向けて、わかりやすく解説したブログです。

フラグを使った状態管理をやってはいけない理由

以下のような is〇〇というフラグが多く設定されたデータを見たことはないだろうか。

■TypeScriptの型定義

type Order = {
  id: string
  price: number
  isPurchased: boolean
  isShipped: boolean
  isDelivered: boolean
  isCancelled: boolean
}

■データベースの設計

order_idproduct_namepriceis_purchasedis_shippedis_deliveredis_cancelled
1マグカップ2000TFFF
2ノート500FTFF
3本1800FFTT

直感的で実装も簡単に見える。

しかしこのアプローチは、システムの複雑性と脆弱性をただただ高める行為のため、やってはいけない。

今回はその理由を解説する。

フラグが生む「ありえない状態」

4つのフラグがあれば、論理的な組み合わせは 2⁴ = 16通りになる。

しかし現実の注文フローで意味を持つのは、たとえば次のような数パターンだけだ。

isPurchasedisShippedisDeliveredisCancelled意味
truefalsefalsefalse購入済み・未発送
truetruefalsefalse発送済み・未配達
truetruetruefalse配達完了
falsefalsefalsetrueキャンセル済み

残り12通りは「ありえない状態」だ。

isDelivered=true(発送済み) かつ isPurchased=false(購入前) のような状態は、現実のビジネスロジックでは存在しえない。
しかし、 is〇〇のようなフラグを多用したデータ構造は、それを禁止することができない。

この「ありえない状態を表現できてしまう」という事実が、あらゆる問題の根源となる。

ありえない状態を許すと何が起きるか

1. 防衛的なコードを書き続けなければならない

ありえない状態を表現できるということは、それに対処するコードを書かなければならないということだ。

function processOrder(order: Order) {
  if (order.isDelivered && order.isCancelled) {
    throw new Error("配達済みかつキャンセル済みの注文は存在しない")
  }
  if (order.isShipped && !order.isPurchased) {
    throw new Error("購入前に発送はできない")
  }
  if (order.isDelivered && !order.isShipped) {
    throw new Error("発送前に配達はできない")
  }
  // ... 他にも無数のガード節
}

この防衛的なコードを書くためには、まず「ありえない組み合わせを全パターン想像する」必要がある。
これは認知コストが高い。
現実にありえないケースを正確に列挙するのは難しく、抜け漏れが生じやすい。

テストケースも同様である。
フラグが1つ増えるたびに組み合わせは倍増し、テストケースも比例して膨張する。
しかしこれを省略すれば致命的なバグにつながるため、省略することもできない。

2. 「ありえない」と「エッジケース」の区別がつかない

ガード節が増えると、次の問題が生じる。

「このガード節は現実にはありえないケースへの対処か、それとも実際に起きうるエッジケースへの対処か」が、
コードを読んだだけではわからなくなる。

// 発送済みかつキャンセル済み
if (order.isShipped && order.isCancelled) {
  // 仕様として商品発送後のキャンセルを許容しているのか?それともこれはありえないケースなのか?分からない
  handleShippedButCancelled(order)
}

これは将来の開発者にとって大きな負債となる。

ガード節を削除してよいのか、仕様変更に伴って修正が必要なのか、判断するための情報がコードから読み取ることができない。

コメントで補うにしても、それは型が本来表現すべき情報をコメントで補っているに過ぎず
コメントの更新漏れはそれ自体が発生しやすいミスなので過信できない。

3. 正しく実装しても破綻しうる

仮にすべてのガード節を正確に書いたとしても、それでシステムが安全になるわけではない。

アプリケーションコードを完璧に書いても、以下のような経路でありえない状態が紛れ込む可能性がある。

  • データ移行スクリプトのバグ
  • 管理画面からの手動修正
  • 外部サービスからの不正なデータ連携
  • 過去のバグの二次的影響が残ったレコード

1件でもありえないフラグの組み合わせが出現した瞬間、システムは未定義の振る舞いに入る。

ガード節で例外を投げるように書いていれば、そのデータに触れた処理が軒並み落ちる。

握りつぶしていれば、サイレントなバグが発生する。

ガード節の記述が漏れていれば、2次被害につながる。

まとめ

フラグで状態を管理する本質的な問題は、「ありえない状態を構造的に禁止できない」ことにある。

ありえない状態を表現できるということは、

  1. それに対処するコードを書き続けるコストが発生し
  2. そのコードの意図が失われ
  3. それでも外部からありえない状態が混入したとき壊れる

という3重の問題を抱えることになる。

安直な設計として多用されがちな、フラグを使った状態管理は行うべきではない。