フィーチャーフラグ:設定ではなく負債である

フラグは到達可能なコード経路を倍にし、そして自動では消えません。精巧な仕組みを作るより、各フラグに削除日を決める方がはるかに重要です。

フラグが解く問題は明快です。配備と公開を分離し、段階公開と巻き戻しを可能にします。生む問題も同じくらい明快です。フラグを一つ足すごとに取り得る状態の組み合わせが倍になり、削除する担当者がいません。

三種、寿命はまったく違う

種類 寿命 例
公開フラグ 数日から数週 新しいトップの段階公開
運用フラグ 数か月、永続も 依存先の緊急停止
実験フラグ 実験一回分 A/B テスト

三つを一つの仕組みで管理することが混乱の始まりです。公開フラグは必ず削除します。 公開作業の足場にすぎません。

真偽値ではなく日付を使う

フラグに期限の欄を持たせる方が、どんな文書の取り決めより効きます。

const FLAGS = {
  newCheckout: { enabled: false, expiresAt: '2026-10-31' },
  legacySearch: { enabled: true, expiresAt: '2026-12-01' },
} as const;

テストを一つ添えます。期限切れのフラグはビルドを失敗させます。

for (const [name, f] of Object.entries(FLAGS)) {
  if (Date.parse(f.expiresAt) < Date.now()) {
    throw new Error('flag ' + name + ' is past its removal date');
  }
}

この表明が「後で片付ける」を「今すぐ処理する」に変えます。無ければフラグは永久に残ります。

読み取りには既定値を持たせる

function isEnabled(name: keyof typeof FLAGS): boolean {
  const override = process.env['FLAG_' + name];
  return override ? override === '1' : FLAGS[name].enabled;
}

既定値はコードに置き、環境変数は一時的な上書きに限定します。逆(既定は無効、設定で有効化)にすると、新しい環境で設定を忘れて機能が黙って無効になります。

分岐は一箇所に

フラグはただ一箇所に現れるべきです。散在する if (flag) は削除をほぼ不可能にします。

// 良い:一箇所で実装を選ぶ
export const checkout = isEnabled('newCheckout') ? newCheckout : legacyCheckout;

// 悪い:実装の内部に分岐が散る
export function checkout(cart) {
  if (isEnabled('newCheckout')) { /* ... */ }
  if (isEnabled('newCheckout')) { /* ... */ }
}

前者の削除は一行と一ファイルを消すだけです。後者は数十箇所で統合を戻す必要があります。

フラグは業務規則ではない

三か月有効で、無効化の予定が無いなら、それはもうフラグではありません。設定であり、正式なコード経路に統合すべきです。 フラグと呼び続けると、いつでも消せると誤解されます。

フラグの価値は公開の数日間にあります。その後一日残るごとに、コードを読みにくく、試験しにくくする負債が増します。

← 記事一覧に戻る

コメント

…