mcpField
T
top-down-typescript

top-down-typescript

github.com/paleo開発155 回インストール2 日前に更新
リポジトリ

top-down-typescript について

開発者やコーディングエージェント向けに、コード構成、型、インポート、保守性の指針を定めたTypeScriptスキルです。エントリーポイントから依存する関数や型へ読み進めるトップダウン構成を重視します。

主な機能

  1. 01トップダウンのファイル構成
  2. 02TypeScriptの型規約
  3. 03ESMインポート規則
  4. 04関数とクラスの設計指針
  5. 05コードと変更メッセージの規則

活用シーン

  1. 01TypeScriptプロジェクトの規約統一
  2. 02コード構成と型選択のレビュー
  3. 03実装時のコーディングエージェント支援

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/paleo/skills --skill top-down-typescript

インストール後、Claude Code で/top-down-typescriptを入力して読み込まれたか確認します。

詳細

トップダウン型 TypeScript コーディングスタイル

一般ルール

  • 使用されていないデッドコードは残さないでください(YAGNI の原則)。
  • 空行を連続して複数入れないでください。
  • リンターのルールを変更する場合は、実施前に必ず話し合ってください。
  • コメントで理由を説明する場合を除き、コメントアウトしたコードを含めないでください。

コードの構成

使用箇所を先に、実装を後に記述します。例外として、継承の場合は、インターフェースが別のインターフェースを extends するとき、親を先に記述します。

  • コードはトップダウンに並べます。各ファイルはエントリーポイントから末端へと、物語を読むように理解できる構成にします。読み手はまず最上位の要素に触れ、次にその依存関係をたどります。

    // 1. Imports
    import { ... } from "...";
    
    // 2. Module-level constants and variables (exported first, then internal)
    export const PUBLIC_CONST = ...;
    const INTERNAL_CONST = ...;
    
    // 3. Shared types — main type first, then types it references
    export interface MainType {
      detail: DetailType;
    }
    export interface DetailType { ... }
    
    // 4. Entry-point (exported) function
    export function doThing() {
      stepOne();
      stepTwo();
    }
    
    // 5. Internal functions called by the entry point, in call order
    function stepOne() {
      stepOneHelper();
    }
    function stepOneHelper() { ... }
    
    function stepTwo() { ... }
    
  • モジュールレベルの定数と変数(ファイルの先頭にある const、let、var による値の宣言。エクスポートされるものと内部のものの両方)は、インポートの直後に配置し、型定義、関数、クラスより前に置いてください。読み手が最初にそれらを確認し、そのファイルの設定項目として捉えられるようにします。

  • 関数は、呼び出し元を先に書き、次にその関数が呼び出す関数を再帰的に記述します。ヘルパー関数はファイル末尾にまとめず、呼び出し元の 直下 に配置します。複数の兄弟関数から呼び出されるヘルパーは、最初に呼び出す関数の後に配置します。

  • 型は、主要な(最上位の)型を先に書き、次にその型が参照する型を再帰的に記述します。関数と同じトップダウンのルールを適用します。

  • 単一の関数(またはクラス、その他の宣言)に付随する型、つまり MyComponent だけで使用される MyComponentProps インターフェースのように、単一のシグネチャでのみ使われる型は、その宣言の直前に配置し、トップレベルの型ブロックには置かないでください。

  • エクスポートの有無だけを基準に並べ替えてはいけません。export される function だからといって先頭に移動するのではなく、呼び出す側との関係によって配置を決めます。ファイルのエントリーポイントは通常エクスポートされるため先頭に現れることが多いですが、それはトップダウンのルールに従った結果であり、ルールそのものではありません。

コード品質基準

  • 最初の実装から、洗練された解決策を目指してください。
  • 特に画像変換のようなコストの高い処理では、冗長な操作を避けてください。
  • コードやロジックの重複を避けてください。
  • すでに計算した値は再計算せず、関数間で受け渡してください。
  • 可能な場合は早期リターンを使用して、コードの流れを簡潔にしてください。
  • 現在の処理フローを抜けるコード(throw、return、continue、break)は、1 行に収まる場合は 1 行で記述してください(例:複数行形式ではなく、if (!condition) return false; と記述します)。
  • コメントに頼らなくても意図が明確に伝わる関数名と変数名を使用してください。
  • 関数は単一責任を持つ、小さなものにしてください。
  • any は避け、適切な型を見つけるために時間をかけてください。適切な型が見つからない場合は、必ず any の後に /* FIXME */ を追加してください。例:let myVariable: any /* FIXME */;。
  • 他の場所からインポートされる関数(または変数、クラス)のみをエクスポートしてください。デフォルトではエクスポートしないでください。
  • 公開シグネチャで使用される、ローカルに宣言された型とインターフェースは、まだどのファイルからもインポートされていなくても、必ずエクスポートしてください。
  • 名前付き型が公開される結果を表す場合は、その型を戻り値の型として使用してください(非同期関数では Promise<T>)。

インポート

  • 常に ESM のインポート構文を使用してください(例:require ではなく import { X } from "y.js")。
  • モジュール間の循環インポートを避けてください。

TypeScript、JavaScript

  • enum と namespace は使用しないでください。
  • let より const を優先してください。
  • null より undefined を優先してください。
  • || より ?? を優先してください。
  • i++ と i-- より ++i と --i を優先してください。
  • Error() より new Error() を優先してください。
  • トップレベルでは、定数として作成するより function と class の宣言構文を優先してください。
  • トップレベルの関数、クラス、インターフェースの間には空行を入れてください。
  • ゲッターまたはセッター(ECMAScript 5 の構文)の実装では、例外をスローしてはいけません。
  • type エイリアスより interface 宣言を優先してください。
  • ジェネリックの型パラメーターには、T、K などの大文字一文字を優先してください。
  • プロパティが存在しない場合と、プロパティの値が undefined の場合を区別しないでください。
  • TypeScript のユニオン型で文字列リテラル値を使う場合は、キャメルケースを使用してください(例:"normal" | "gracefulShutdown" | "backupMode"。"normal" | "graceful-shutdown" | "backup-mode" ではありません)。
  • 空文字列を本当に意味する場合を除き、デフォルト値に空文字列を使用しないでください。変数に値がない可能性がある場合は、undefined を使ってください。値がないことが問題を示す場合は、エラーをスローしてください。
  • 文字列、数値、真偽値(および文字列または数値である識別子)の存在を、真偽値への型変換で判定してはいけません。undefined または null との明示的な比較を使用してください。
  • オブジェクトと配列の存在確認には、真偽値への型変換を使用してもかまいません。
  • デフォルト値が undefined の場合、明示的に undefined を代入または返さないでください。return undefined; の代わりに return; を、let myVariable = undefined; の代わりに let myVariable; を使用してください。意図的に値を設定する場合は、undefined を明示的に渡してもかまいません。
  • as any や、あらゆる種類の型アサーションは避けてください。適切な型を見つけるよう努めてください。例外:型が誤っている場合、または本当に不明な場合は、インラインコメントで理由を説明してください。
  • パッケージの index ファイルからの場合を除き、再エクスポートしないでください。
  • import("some-package-or-module").SomeType のようなインラインの import は避け、ファイルの先頭で直接インポートしてください。
  • await import("some-package-or-module") のようなインラインの await import は避け、ファイルの先頭で静的にインポートしてください。例外:正当な理由がある場合は、インラインコメントで理由を説明してください。

OOP

  • クラスよりファクトリー関数を優先してください。
  • クラスを使うより、コンテキストオブジェクトを受け取る関数を記述してください。
  • フレームワークで必要とされる場合を除き、クラス継承は避けてください。

パッケージ依存関係の追加

依存関係または開発依存関係を追加する前に、まずコードベースを検索し、すでに使用されているバージョンを再利用してください。見つからない場合は、デフォルトのインストールコマンドを使って最新バージョンをインストールしてください。

コミット、PR/MR、チェンジセットのメッセージ

コミット、PR、MR、またはチェンジセットのメッセージに、AI のクレジット表記(Co-Authored-By: …、"Generated with …" など)や類似の表記を追加しないでください。

チェンジセットのメッセージ

変更内容を知りたい、すでにプロジェクトを利用している人向けに、数語で書いてください。利用者にとって実行可能な内容だけを記載し、理由や内部の詳細は省いてください。短い一段落のみで記述してください。

  • 新機能:数語で機能名を記載してください。
  • 機能の拡張:明確な場合は機能名をタイトルにし、そうでない場合は「{X} 機能を改善しました。」としてください。
  • 実行可能な変更がない場合(ドキュメントやリファクタリングなど):簡潔に「{topic} に関するドキュメントを改善しました。」のように記載してください。

実行可能な変更と実行可能でない変更が混在する場合(たとえば、大規模なリファクタリングと小規模な機能追加)、実行可能な変更のみを記載してください。ほかに記載することがない場合に限り、実行可能でない変更を記載してください。

バージョンの更新

明示的に指示されない限り、パッケージのバージョンを 0.x.x から 1.0.0 に更新しないでください。メジャーバージョンが 0 の間は、破壊的変更を許容します。

コード品質の改善

SRP - 単一責任の原則

ナラティブ分解として考えてください。呼び出し元は、何が起こるかを示す段落のように読み進められ、各ヘルパーはその段落の一文を展開します。

たとえば、次のコードは:

export function myFunction() {
  // Check something
  // ... 20 lines ...

  // Do something
  // ... 20 lines ...
}

…次のようにリファクタリングしてください。

export function myFunction() {
  checkSomething();
  doSomething();
}

function checkSomething() {
  // ... 20 lines ...
}

function doSomething() {
  // ... 20 lines ...
}

ガイドライン:

  • サブ関数は必ず呼び出し元の関数の_後に_記述してください。
  • ソースファイルの外部からインポートされる関数以外は、エクスポートしないでください。
  • コードを分割するときは、_単一責任の原則_を適用してください。1つの関数は1つの関心事を担当します。
  • 関数の実装が画面1枚分(約50行)を超えないようにしてください。
  • 説明的なコメントを追加するのではなく、コードをクリーンで自己説明的なものにしてください。
DRY - 同じことを繰り返さない

重複したロジックを見つけたら、その都度、再利用可能な関数にリファクタリングしてください。

YAGNI - 必要になるまで作らない

変数、関数、実装などの未使用コードを残さないでください。

注意すべき兆候
  • 空文字列やゼロへのフォールバックに正当な理由があることはまれです。すべての ?? "" と ?? 0 を確認し、意図したものかどうか確かめてください。そうでない場合は、型を理解したうえで、適切な修正方法を見つけてください。
    • 注:UIで空文字列が必要な場合は、?? "" の有効な使用例です。
  • 型アサーション(as SomeType)は、型の理解不足を示していることがよくあります。
  • any は避け、適切な型を見つけてください。どうしても any が必要な場合は、その理由を説明するコメントを追加してください。
  • 実際の型をインポートできる場合は、ReturnType<T> や Parameters<T> を使用しないでください。
  • 実際の型をインポートできる場合は、SomeType["someMemberName"] を使用しないでください。
不要なコメントを削除する
  • コメントについては、少ないほどよいです。コメントは熟練した開発者が読みます。それぞれのコメントは、明確で簡潔、かつ要点を押さえている必要があります。各単語を慎重に吟味して選んでください。
  • コード自体と重複するコメントは削除してください。
  • インラインコメントは、ハック、TODO、例外的な状況を記録する場合、またはコードの構造から目的が明らかでない場合にのみ残してください。
  • 現在取り組んでいるタスクを正当化するための注釈として、コメントを使用しないでください。

次のコメントは削除してください。

// Create a new task
createANewTask();

明白であり、削除すべきインラインコメントの例をほかにも示します。

// Validate that there is a file
if (!file) throw new ApiError(400);

// Validate and parse the request body according to the expected schema
const validated = UploadBodyAT.assert(body);

JSDocコメントは、意味のある情報を加える場合にのみ使用してください。すべてが明白であるため、完全に削除すべきコメントの例です。

/**
 * This function adds two numbers
 * @param a - The first number
 * @param b - The second number
 * @returns The sum of the two numbers
 */
function addTwoNumbers(a: number, b: number) {
  return a + b;
}

コメント