avoid-inferable-annotations について
TypeScript 開発者向けのコーディングスキルです。コンパイラーが推論できる型注釈を省き、API の明示や値の制約に必要な注釈は残します。
主な機能
- 01冗長な変数の型注釈を避ける
- 02文脈からコールバック型を推論する
- 03公開 API のシグネチャを注釈する
- 04必要に応じてオブジェクトリテラルを型注釈する
- 05型推論に関するコードの兆候を見つける
活用シーン
- 01型注釈の多い TypeScript コードのレビュー
- 02型付き変数宣言のリファクタリング
- 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「推論可能な型でコードを煩雑にしない」に基づいています。