mcpField
A
avoid-inferable-annotations

avoid-inferable-annotations

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

关于 avoid-inferable-annotations

面向 TypeScript 开发者的编码技能,指导移除编译器可推断的冗余类型标注,同时保留用于 API 表达或约束值的标注。

主要功能

  1. 01避免冗余变量类型标注
  2. 02利用上下文推断回调类型
  3. 03为公开 API 标注签名
  4. 04必要时为对象字面量标注类型
  5. 05识别类型推断相关异味

适用场景

  1. 01审查类型标注冗长的代码
  2. 02重构带类型的变量声明
  3. 03判断返回值是否需要标注

安装

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill avoid-inferable-annotations

安装后在 Claude Code 中输入/avoid-inferable-annotations确认已加载。

详细介绍

避免在代码中添加可推断类型的注解

概述

TypeScript 能推断出相同类型时,不要编写类型注解。

多余的类型注解会增加冗余,提高维护成本,甚至可能掩盖错误。让 TypeScript 做好它该做的事。

何时使用此技能

  • 声明类型一目了然的变量
  • 解构赋值
  • 编写函数实现
  • 检查注解冗长的代码
  • 重构类型繁多的代码

铁律

NEVER annotate types that TypeScript can correctly infer.

以下情况除外:

  • 对象字面量(用于启用多余属性检查)
  • 公共 API 函数签名
  • 推断结果不正确时

如何识别:“多余注解”的迹象

将鼠标悬停在变量上。如果推断出的类型与注解相同,就删除注解。

// ❌ VIOLATION: Redundant annotations
let x: number = 12;
const name: string = 'Alice';
const numbers: number[] = [1, 2, 3];

// ✅ CORRECT: Let TypeScript infer
let x = 12;
const name = 'Alice';
const numbers = [1, 2, 3];

复杂对象:仍可推断

// ❌ VERBOSE: Don't do this
const person: {
  name: string;
  born: {
    where: string;
    when: string;
  };
} = {
  name: 'Sojourner Truth',
  born: {
    where: 'Swartekill, NY',
    when: 'c.1797',
  },
};

// ✅ CONCISE: Just write the value
const person = {
  name: 'Sojourner Truth',
  born: {
    where: 'Swartekill, NY',
    when: 'c.1797',
  },
};
// TypeScript infers the exact same type!

函数参数:需要注解

// Parameters need types (TypeScript can't infer them)
function greet(name: string) {
  return `Hello, ${name}`;
}

// Return type is inferred - no need to annotate
function square(x: number) {
  return x * x;  // TypeScript infers number
}

回调参数:通常可以推断

// ❌ VERBOSE: Unnecessary parameter types
app.get('/health', (request: express.Request, response: express.Response) => {
  response.send('OK');
});

// ✅ CONCISE: Let context provide the types
app.get('/health', (request, response) => {
  response.send('OK');  // Types are inferred from context
});

何时需要添加注解:对象字面量

interface Product {
  id: string;
  name: string;
  price: number;
}

// ✅ ANNOTATE: Enables excess property checking
const elmo: Product = {
  id: '123',
  name: 'Tickle Me Elmo',
  price: 28.99,
  inStock: true,  // Error! 'inStock' does not exist in type 'Product'
};

// ❌ NO ANNOTATION: Error appears far from definition
const furby = {
  id: 456,        // Wrong type! But no error here...
  name: 'Furby',
  price: 35,
};
logProduct(furby);  // Error appears here, far from the mistake
//         ~~~~~ Types of property 'id' are incompatible

何时需要添加注解:函数返回类型

请考虑为以下情况显式声明返回类型:

1. 有多个返回值的函数
// Without annotation, easy to miss inconsistencies
function getQuote(ticker: string) {
  if (cache[ticker]) {
    return cache[ticker];  // Returns number
  }
  return fetch(`/api/${ticker}`).then(r => r.json());  // Returns Promise<any>
}
// Inferred: number | Promise<any> - probably not what you wanted!

// With annotation, TypeScript catches the bug
function getQuote(ticker: string): Promise<number> {
  if (cache[ticker]) {
    return cache[ticker];  // Error: Type 'number' is not assignable to 'Promise<number>'
  }
  return fetch(`/api/${ticker}`).then(r => r.json());
}
2. 公共 API 函数
// For library functions, explicit types are documentation
export function calculateTotal(items: CartItem[]): number {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
3. 命名返回类型
interface Vector2D { x: number; y: number; }

// Without annotation: { x: number; y: number; }
function add(a: Vector2D, b: Vector2D) {
  return { x: a.x + b.x, y: a.y + b.y };
}

// With annotation: Vector2D (clearer, documented)
function add(a: Vector2D, b: Vector2D): Vector2D {
  return { x: a.x + b.x, y: a.y + b.y };
}

解构:尤其不应添加注解

// ❌ TERRIBLE: Destructuring with types is ugly
function logProduct(product: Product) {
  const {id, name, price}: {id: string; name: string; price: number} = product;
  console.log(id, name, price);
}

// ✅ CLEAN: Just destructure
function logProduct(product: Product) {
  const {id, name, price} = product;
  console.log(id, name, price);
}

抵制压力的准则

1. “显式优于隐式”

压力:“我希望代码中能看到所有类型”

**回应:**多余的注解会增加冗余。使用编辑器的悬停功能查看类型。

**行动:**删除多余的注解。相信类型推断。

2. “如果推断结果变了怎么办?”

压力:“如果我修改值,类型可能也会改变”

**回应:**这通常正是你想要的!类型会随代码一起演变。

**行动:**只在需要限制类型时添加注解。

3. “新开发者会看不懂”

压力:“初级开发者需要看到类型”

**回应:**现代编辑器会显示推断出的类型。多余的注解并不能帮助他们学习。

**行动:**依靠编辑器工具。使用有意义的类型名称,而不是冗长的注解。

警示信号——停下来重新考虑

  • 用数字字面量初始化的变量却标注了 : number
  • 用字符串字面量初始化的变量却标注了 : string
  • 为解构出的变量添加类型
  • 回调的上下文已提供参数类型,却仍显式标注
  • 注解和赋值的值中出现相同类型

常见的自我辩解(都不成立)

借口实际情况
“这样更易读”额外的冗余会降低可读性。
“如果推断错了怎么办?”那么只针对这种情况添加注解。
“代码审查需要类型”审查者可以在现代工具中悬停查看类型。
“保持一致”始终避免冗余更好。

快速参考

情况是否添加注解?
值为字面量的变量否
用于已知类型的对象字面量是(多余属性检查)
解构出的变量否
函数参数是
函数返回值(简单情况)通常不需要
函数返回值(复杂情况/公共函数)是
回调参数通常不需要

总结

TypeScript 擅长推断类型。就让它来做。

为函数参数和公共 API 添加注解。为对象字面量添加注解,以启用多余属性检查。其他情况下,让 TypeScript 推断类型,保持代码简洁。

参考资料

基于 Dan Vanderkam 的《Effective TypeScript》第 18 条:避免在代码中添加可推断类型的注解。

评论

支持 Markdown