mcpField
S
soundness-traps

soundness-traps

github.com/marius-townhouse开发646 次安装39 周前更新
代码仓库

关于 soundness-traps

soundness-traps 是一项帮助 TypeScript 开发者识别类型与运行时值不一致风险的技能。内容涵盖常见陷阱、防御性编码方法及相关编译器选项。

主要功能

  1. 01说明数组越界访问风险
  2. 02讲解类型断言和 any 陷阱
  3. 03提示回调与参数变更风险
  4. 04建议验证外部数据
  5. 05介绍严格编译器选项

适用场景

  1. 01排查看似不可能的运行时错误
  2. 02审查 TypeScript 类型安全风险
  3. 03编写防御性代码
  4. 04评估严格编译器选项的取舍

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill soundness-traps

安装后在 Claude Code 中输入/soundness-traps确认已加载。

详细介绍

避免类型健全性陷阱

概述

TypeScript 并不健全——运行时值可能与静态类型不一致。

“类型健全性”意味着静态类型始终与运行时值相符。TypeScript 有意牺牲部分类型健全性,以换取便利。请了解常见陷阱。

何时使用此技能

  • 调试“看似不可能”的运行时错误
  • 了解 TypeScript 的局限性
  • 编写防御性代码
  • 评估严格选项的权衡

铁律

TypeScript types are NOT runtime guarantees.
Know the common soundness traps and avoid them.

请记住:

  • 数组访问不会检查边界
  • 类型断言会绕过检查
  • 函数可能会修改其参数
  • 外部数据可能与声明的类型不符

类型健全性与便利性

TypeScript 在许多情况下选择便利而非类型健全性:

const xs = [1, 2, 3];
const x = xs[10];
//    ^? number (but actually undefined!)

这并不健全,但很方便。每次访问都检查边界会很繁琐。

常见的类型健全性陷阱

1. 未检查的数组访问
const arr = [1, 2, 3];
const item = arr[5];  // undefined at runtime
//    ^? number (wrong!)

item.toFixed(2);  // Crashes!

修复方法: 使用 noUncheckedIndexedAccess 或显式检查:

const item = arr[5];
if (item !== undefined) {
  item.toFixed(2);  // OK
}
2. 类型断言
const hour = (new Date()).getHours() || null;
//    ^? number | null

// Assertion removes null
const definitelyHour = hour as number;
//    ^? number (but might be null!)

修复方法: 使用条件判断,而不是类型断言:

if (hour !== null) {
  hour.toFixed(1);  // TypeScript knows it's number
}
3. any 类型
function log(x: number) {
  console.log(x.toFixed(1));
}

const val: any = 'not a number';
log(val);  // No error, crashes at runtime

修复方法: 避免使用 any。使用 unknown 并通过类型收窄进行处理:

function log(x: unknown) {
  if (typeof x === 'number') {
    console.log(x.toFixed(1));
  }
}
4. 对象索引访问
type Dict = { [key: string]: string };
const dict: Dict = { a: 'apple' };
const val = dict['b'];
//    ^? string (but actually undefined!)

修复方法: 在类型中包含 undefined:

type Dict = { [key: string]: string | undefined };
const val = dict['b'];
//    ^? string | undefined
5. 修改函数参数
function addFox(animals: Animal[]) {
  animals.push(new Fox());
}

const hens: Hen[] = [new Hen()];
addFox(hens);  // Fox in the henhouse!

修复方法: 使用 readonly 防止修改:

function addFox(animals: readonly Animal[]) {
  animals.push(new Fox());
  //      ~~~~ Property 'push' does not exist
}
6. 回调导致类型收窄失效
interface Data {
  value?: string;
}

function process(data: Data, callback: (d: Data) => void) {
  if (data.value) {
    callback(data);
    console.log(data.value.toUpperCase());  // Might crash!
    //          ^? string (but callback might have deleted it!)
  }
}

process({ value: 'hello' }, d => delete d.value);

修复方法: 在回调之前捕获该值:

function process(data: Data, callback: (d: Data) => void) {
  const value = data.value;
  if (value) {
    callback(data);
    console.log(value.toUpperCase());  // Safe!
  }
}
7. 不准确的类型声明
// Library types might be wrong
declare function getUser(): { name: string; email: string };

const user = getUser();
user.email.toLowerCase();  // Might crash if email is undefined!

修复方法: 在运行时验证外部数据:

const user = getUser();
if (user.email) {
  user.email.toLowerCase();
}
8. 可选属性与可赋值性
interface Person { name: string; }
interface AgePerson { name: string; age?: number; }

const p: Person = { name: 'Bob', age: '30' };  // age is string
const ap: AgePerson = p;  // No error!
console.log(ap.age?.toFixed());  // toFixed on string!

这是 TypeScript 结构类型系统带来的一种隐蔽的不健全性。

提高类型健全性的编译器选项

选项可捕获的问题
strictNullChecksnull/undefined 赋值
noUncheckedIndexedAccess数组/对象访问可能返回 undefined
strictFunctionTypes函数参数逆变

启用这些选项可以提高安全性(但会降低便利性)。

通用策略

  1. 验证外部数据(API、JSON、用户输入)
  2. 对不会修改的参数使用 readonly
  3. 优先使用 unknown,而不是 any
  4. 避免类型断言;改用类型收窄
  5. 启用严格的编译器选项
  6. 测试边界情况(空数组、缺失属性)

抗压指南

1. “TypeScript 应该能发现这个问题”

压力:“TypeScript 为什么没发现这个错误?”

回应: TypeScript 在许多情况下有意不保证类型健全性。

行动: 了解这些陷阱,编写防御性代码。

2. “限制太多了”

压力:“noUncheckedIndexedAccess 真让人烦”

回应: 它能捕获真实错误。这点不便是值得的。

行动: 启用严格选项;处理边界情况。

危险信号——停止并重新考虑

  • 访问数组元素时不检查边界
  • 用类型断言来“修复”类型错误
  • 修改函数参数
  • 相信外部数据符合声明的类型

常见的自我开脱(都不成立)

借口事实
“TypeScript 会发现问题”TypeScript 并不健全
“它始终有值”直到它没有值为止
“API 文档写得很清楚”文档可能不准确;请进行验证

快速参考

// TRAP: Array access
const x = arr[10];  // Might be undefined

// TRAP: Type assertion
const y = val as number;  // Might not be number

// TRAP: any type
const z: any = 'string';
z.toFixed();  // Crashes

// TRAP: Index signature
const v = dict['missing'];  // Undefined

// SAFE: Narrowing
if (typeof val === 'number') { val.toFixed(); }

// SAFE: Optional chaining
arr[10]?.toFixed();

// SAFE: undefined in type
type Dict = { [k: string]: string | undefined };

总结

TypeScript 追求便利,而非类型健全性。

了解类型可能与运行时值不一致的情况。启用严格选项。验证外部数据。编写防御性代码。不要盲目信任类型。

参考资料

基于 Dan Vanderkam 的《Effective TypeScript》,第 48 条:避免类型健全性陷阱。

评论

支持 Markdown