关于 soundness-traps
soundness-traps 是一项帮助 TypeScript 开发者识别类型与运行时值不一致风险的技能。内容涵盖常见陷阱、防御性编码方法及相关编译器选项。
主要功能
- 01说明数组越界访问风险
- 02讲解类型断言和 any 陷阱
- 03提示回调与参数变更风险
- 04建议验证外部数据
- 05介绍严格编译器选项
适用场景
- 01排查看似不可能的运行时错误
- 02审查 TypeScript 类型安全风险
- 03编写防御性代码
- 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 结构类型系统带来的一种隐蔽的不健全性。
提高类型健全性的编译器选项
| 选项 | 可捕获的问题 |
|---|---|
strictNullChecks | null/undefined 赋值 |
noUncheckedIndexedAccess | 数组/对象访问可能返回 undefined |
strictFunctionTypes | 函数参数逆变 |
启用这些选项可以提高安全性(但会降低便利性)。
通用策略
- 验证外部数据(API、JSON、用户输入)
- 对不会修改的参数使用 readonly
- 优先使用 unknown,而不是 any
- 避免类型断言;改用类型收窄
- 启用严格的编译器选项
- 测试边界情况(空数组、缺失属性)
抗压指南
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 条:避免类型健全性陷阱。