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. 02用 null 或 undefined 表示缺失
  3. 03用标记联合类型描述状态
  4. 04让 TypeScript 明确特殊情况

适用场景

  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 或独立类型。

如果函数可能失败或遇到特殊情况,请使用 TypeScript 能够区分的类型来表示,而不是使用 -1 这类域内值,因为它本质上只是一个普通数字。

何时使用此技巧

  • 可能失败的函数(未找到、出错等)
  • 具有“缺失”或“未知”状态的值
  • 封装使用 -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 或抛出异常

抵抗压力协议

1. “JavaScript 使用 -1”

压力:“indexOf 会返回 -1,我应该遵循这种模式”

**回应:**JavaScript 的 -1 是历史遗留错误。请对其进行封装。

**行动:**创建一个返回 T | null 的封装函数。

2. “这只是个占位符”

压力:“我们实际上永远不会用到这个值”

**回应:**总会有人忘记。TypeScript 无法保护你。

**行动:**使用 TypeScript 能够追踪的独立类型。

危险信号——停下来重新考虑

  • -1、0 或 -999 这样的魔法数字
  • 用空字符串表示“无值”
  • 解释特殊值的注释
  • 因忘记检查特殊值而产生的 Bug

常见借口(都不成立)

借口事实
“这是常见模式”常见不代表好
“性能更好”充其量只有微小提升;安全性更重要
“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 条:为特殊值使用独立类型。

评论

支持 Markdown