mcpField
D
distinct-special-values

distinct-special-values

github.com/marius-townhouse開発643 回インストール38 週間前に更新
リポジトリ

distinct-special-values について

distinct-special-valuesは、有効な値を特別扱いする代わりに、専用の型で例外や欠損状態を表すTypeScriptスキルです。null、undefined、判別可能なユニオン型で例外を明示し、TypeScriptに処理を促します。

主な機能

  1. 01センチネル値を専用型に置き換え
  2. 02nullやundefinedで欠損を表現
  3. 03判別可能なユニオン型で状態をモデル化
  4. 04TypeScriptで例外状態を明示

活用シーン

  1. 01検索結果がない状態を表現
  2. 02価格不明の商品をモデル化
  3. 03成功・エラー・処理中の結果を定義

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill distinct-special-values

インストール後、Claude Code で/distinct-special-valuesを入力して読み込まれたか確認します。

詳細

特殊な値には専用の型を使う

概要

特殊な値として -1、0、または "" を使わないでください。null または専用の型を使ってください。

関数が失敗する場合や特殊なケースがある場合は、通常の数値にすぎない -1 のようなドメイン内の値ではなく、TypeScript が区別できる型で表現してください。

このスキルを使う場面

  • 失敗する可能性がある関数(見つからない、エラーなど)
  • 「存在しない」状態や「不明」状態を持つ値
  • -1 のようなセンチネル値を使う API のラップ
  • 独自の戻り値の型の設計

絶対に守るルール

Special cases deserve distinct types.
Use null, undefined, or tagged unions - not -1 or "".

覚えておいてください:

  • -1 は単なる数値で、ほかの数値と区別できません
  • TypeScript は特殊な値から保護してくれません
  • null と undefined は追跡できます
  • 明示的なエラー状態はマジックナンバーより明確です

検出:-1 の落とし穴

function splitAround<T>(vals: readonly T[], val: T): [T[], T[]] {
  const index = vals.indexOf(val);
  return [vals.slice(0, index), vals.slice(index + 1)];
}

splitAround([1, 2, 3, 4, 5], 6);
// Expected: error or [[1,2,3,4,5], []]
// Actual: [[1,2,3,4], [1,2,3,4,5]] (!)

なぜでしょうか? indexOf は「見つからない」場合に -1 を返しますが、-1 は有効な配列インデックスです(末尾から数えます)。

解決策:専用の型でラップする

function safeIndexOf<T>(vals: readonly T[], val: T): number | null {
  const index = vals.indexOf(val);
  return index === -1 ? null : index;
}

これで、TypeScript は両方のケースを処理するよう強制します:

function splitAround<T>(vals: readonly T[], val: T): [T[], T[]] {
  const index = safeIndexOf(vals, val);
  return [vals.slice(0, index), vals.slice(index + 1)];
  //                   ~~~~~             ~~~~~ 
  // 'index' is possibly 'null'
}

修正版:

function splitAround<T>(vals: readonly T[], val: T): [T[], T[]] {
  const index = safeIndexOf(vals, val);
  if (index === null) {
    return [[...vals], []];
  }
  return [vals.slice(0, index), vals.slice(index + 1)];
}

実例:商品価格

// Bad: -1 means "unknown price"
interface Product {
  title: string;
  /** Price in dollars, or -1 if price is unknown */
  priceDollars: number;
}

// Disaster waiting to happen:
function getTotal(products: Product[]) {
  return products.reduce((sum, p) => sum + p.priceDollars, 0);
  // Whoops: products with unknown price make total negative!
}

より良い方法:

interface Product {
  title: string;
  priceDollars: number | null;
}

function getTotal(products: Product[]) {
  return products.reduce((sum, p) => {
    if (p.priceDollars === null) {
      throw new Error(`Unknown price for ${p.title}`);
    }
    return sum + p.priceDollars;
  }, 0);
}

strictNullChecks が重要な理由

特殊な値として -1 を使うのは、strictNullChecks を無効にするようなものです:

// @strictNullChecks: false
const truck: Product = {
  title: 'Tesla Cybertruck',
  priceDollars: null,  // ok with strictNullChecks off
};

strictNullChecks が有効な場合、TypeScript は number と number | null を区別します。「不明」を表すのに -1 を使うと、この安全性が回避されます。

判別可能なユニオン型を使う場面

null/undefined では十分に明確でない場合は、判別可能なユニオン型を使ってください:

type RequestResult<T> = 
  | { status: 'success'; data: T }
  | { status: 'error'; error: string }
  | { status: 'pending' };

function fetchUser(id: string): RequestResult<User> {
  // ...
}

const result = fetchUser('123');
if (result.status === 'success') {
  console.log(result.data.name);  // TypeScript knows data exists
}

避けるべき一般的なセンチネル値

センチネル値問題代替
-1 (indexOf)有効な配列インデックスnull
0有効な数値null
""有効な文字列null
[]有効な配列null
{}有効なオブジェクトnull
NaNnumber 型null または throw

プレッシャーに負けないためのプロトコル

1. 「JavaScript では -1 が使われる」

プレッシャー:「indexOf は -1 を返すので、そのパターンに合わせるべきです」

**回答:**JavaScript の -1 は歴史的な過ちです。ラップしてください。

対応:T | null を返すラッパーを作成します。

2. 「単なるプレースホルダーです」

プレッシャー:「その値を実際に使うことはありません」

**回答:**誰かが忘れるでしょう。TypeScript は保護してくれません。

**対応:**TypeScript が追跡できる専用の型を使います。

危険信号 — 中断して再検討してください

  • -1、0、-999 のようなマジックナンバー
  • 「値なし」を意味する空文字列
  • 特殊な値について説明するコメント
  • 特殊な値の確認忘れによるバグ

よくある正当化(すべて誤り)

言い訳実情
「よくあるパターンです」よくあることが良いとは限りません
「パフォーマンスが向上します」せいぜいわずかな差です。安全性のほうが重要です
「TypeScript は null を追跡できません」追跡できます。それが要点です!

クイックリファレンス

// DON'T: Sentinel values
function indexOf(arr, val): number { ... }  // -1 means not found
function getPrice(): number { ... }  // -1 means unknown

// DO: Distinct types
function indexOf(arr, val): number | null { ... }
function getPrice(): number | null { ... }

// DO: Tagged unions for complex states
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

結論

特殊なケースには、専用の型を使ってください。

特殊な値として -1 や "" を使うと、TypeScript の型システムを回避することになります。特殊なケースは null、undefined、または判別可能なユニオン型で表現してください。そうすれば、TypeScript によって正しく処理するよう強制されます。

参考資料

Dan Vanderkam 著「Effective TypeScript」の項目 36「特殊な値には専用の型を使う」に基づいています。

コメント