一键导入
distinct-special-values
Use when tempted to use -1 or "" as special values. Use when indexOf returns -1. Use when special cases need representation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when tempted to use -1 or "" as special values. Use when indexOf returns -1. Use when special cases need representation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when defining global types. Use when augmenting window. Use when typing environment variables. Use when working with build-time constants. Use when configuring type definitions.
Use when migrating JavaScript to TypeScript. Use when gradually adopting TypeScript. Use when working with mixed codebases. Use when converting large projects. Use when teams are learning TypeScript.
Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations.
Use when creating types from example data. Use when types don't match all cases. Use when API responses vary.
Use when writing type annotations on variables. Use when TypeScript can infer the type. Use when code feels cluttered with types.
Use when defining array-like types. Use when tempted to use number as index type. Use when understanding array keys.
| name | distinct-special-values |
| description | Use when tempted to use -1 or "" as special values. Use when indexOf returns -1. Use when special cases need representation. |
Don't use -1, 0, or "" as special values. Use null or a distinct type.
When a function can fail or have a special case, represent it with a type that TypeScript can distinguish, not an in-domain value like -1 that's just a regular number.
Special cases deserve distinct types.
Use null, undefined, or tagged unions - not -1 or "".
Remember:
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]] (!)
Why? indexOf returns -1 for "not found", but -1 is a valid array index (counts from end).
function safeIndexOf<T>(vals: readonly T[], val: T): number | null {
const index = vals.indexOf(val);
return index === -1 ? null : index;
}
Now TypeScript forces you to handle both cases:
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'
}
Fixed version:
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!
}
Better:
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);
}
Using -1 as a special value is like disabling strictNullChecks:
// @strictNullChecks: false
const truck: Product = {
title: 'Tesla Cybertruck',
priceDollars: null, // ok with strictNullChecks off
};
When strictNullChecks is on, TypeScript distinguishes number from number | null. Using -1 as "unknown" bypasses this safety.
If null/undefined isn't clear enough, use a tagged union:
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
}
| Sentinel | Problem | Alternative |
|---|---|---|
| -1 (indexOf) | Valid array index | null |
| 0 | Valid number | null |
| "" | Valid string | null |
| [] | Valid array | null |
| {} | Valid object | null |
| NaN | number type | null or throw |
Pressure: "indexOf returns -1, I should match that pattern"
Response: JavaScript's -1 is a historical mistake. Wrap it.
Action: Create wrapper returning T | null.
Pressure: "We'll never actually use that value"
Response: Someone will forget. TypeScript won't protect you.
Action: Use a distinct type that TypeScript can track.
| Excuse | Reality |
|---|---|
| "It's a common pattern" | Common doesn't mean good |
| "Performance is better" | Marginal at best; safety matters more |
| "TypeScript can't track null" | Yes it can, that's the point! |
// 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 };
Special cases deserve special types.
Using -1 or "" as special values bypasses TypeScript's type system. Use null, undefined, or tagged unions to represent special cases. TypeScript will then force you to handle them correctly.
Based on "Effective TypeScript" by Dan Vanderkam, Item 36: Use a Distinct Type for Special Values.