distinct-special-values について
distinct-special-valuesは、有効な値を特別扱いする代わりに、専用の型で例外や欠損状態を表すTypeScriptスキルです。null、undefined、判別可能なユニオン型で例外を明示し、TypeScriptに処理を促します。
主な機能
- 01センチネル値を専用型に置き換え
- 02nullやundefinedで欠損を表現
- 03判別可能なユニオン型で状態をモデル化
- 04TypeScriptで例外状態を明示
活用シーン
- 01検索結果がない状態を表現
- 02価格不明の商品をモデル化
- 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 |
| NaN | number 型 | 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「特殊な値には専用の型を使う」に基づいています。