typescript-best-practices について
`.ts` や `.tsx` を変更する開発者向けに、TypeScript の基本的な実践指針を示すコーディングスキルです。型付きの境界、小さく合成可能な関数、再利用、重複コードの確認を重視します。
主な機能
- 01境界の JSON を Zod で検証
- 02小さく合成可能な関数
- 03共有ヘルパーと不変条件の再利用
- 04境界データとドメイン型の分離
- 05jscpd による重複チェック
活用シーン
- 01TypeScript または TSX ファイルの変更
- 02コード変更のレビューや修正
- 03重複するデータ変換のリファクタリング
- 04型と境界処理のチーム規約の統一
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ justbecauselabs/ agent-skills --skill typescript-best-practicesインストール後、Claude Code で/typescript-best-practicesを入力して読み込まれたか確認します。
同梱ファイルreferences/review-and-fix-changes.md
詳細
TypeScript のベストプラクティス
.ts または .tsx の変更には、このスキルをデフォルトの指針として使用してください。
このファイルは実用的な内容にしてください。リポジトリで実際に適用されている TypeScript の標準を記録してください。
参考資料
- 作業ツリー、ブランチ、コミット、またはコミット範囲のレビューやレビューと修正を依頼された場合は、
references/review-and-fix-changes.mdを読み、このファイルをレビュー基準として維持しながら、そこに記載された手順に従ってください。
目的
- TypeScript の記述とリファクタリングに関する単一の基準を示します。
- 型安全で読みやすく、変更しやすいコードを保ちます。
- 過剰な防御的クリーンアップ、曖昧な抽象化、重複した変換、弱いフォールバックなど、AI 由来の粗雑なコードを減らします。
すぐに適用する指針
- 型付けされていない JSON は境界で Zod を使って検証します。
- 関数は小さく、組み合わせ可能にします。
- 境界のデータ形状と内部ドメインのデータ形状を分けます。
- 外部データは一度だけ変換し、以降の内部コードは厳密かつシンプルに保ちます。
- 実際の境界上または製品上の理由がない限り、値を正規化しません。
- 元の値を保持し、必要に応じて派生した内部キーを追加します。
- 安定したドメインデータで意味のある不在を表す場合は、
nullを使用します。 undefinedは省略可能な入力と、生の境界ペイロードのフィールドに限定します。- 新しいヘルパーを追加する前に、既存のヘルパーを再利用します。
- 文字列、数値、URL、配列、辞書、重複排除、フィルタリングなどの基礎的なロジックについては、まずリポジトリ内の既存の共有ヘルパーの配置場所を探し、機能コードに埋め込まず、そこに共有ロジックを追加します。
- 共有される不変条件と境界アダプターを抽出し、汎用的なクリーンアップユーティリティを新たに作らないでください。
- リファクタリング後、レビュー前に
jscpdで重複コードを簡易チェックします。 - ユーザーからコード内のコメントを明示的に求められない限り、コードコメントを追加したり増やしたりしません。
優先順位
- まず、プロジェクト固有の規約に従います。
- 境界の型を明示します。
- 新しい抽象化を追加する前に、既存のヘルパーと不変条件を再利用します。
- 繰り返しが実際に発生している場合に限り、重複した境界アダプターや不変条件を抽出します。
- 凝った書き方よりも明瞭さを優先します。
- 正しさ、境界、再利用の方針を固めてから、スタイルの好みを適用します。
基本ルール
1. 型付けされていない境界データは一度だけ検証・変換し、その後はドメイン型を厳密に保ちます。
悪い例:
type Issue = {
id: string;
attemptCount: number;
};
function parseIssue({ body }: { body: string }): Issue {
return JSON.parse(body) as Issue;
}
良い例:
import { z } from "zod";
const issueSchema = z.object({
id: z.string().min(1),
attemptCount: z.coerce.number().int().nonnegative(),
});
type Issue = z.infer<typeof issueSchema>;
function parseIssue({ body }: { body: string }): Issue {
return issueSchema.parse(JSON.parse(body) as unknown);
}
2. 関数は小さく、組み合わせ可能にします。作成者が指定するパラメーターを複数受け取る関数には、params オブジェクトを使用します。
- 作成する関数は、
const name = () => {}よりもfunction name()宣言を優先します。
悪い例:
function buildWorkspaceLabel(issue: Issue, ownerName?: string): string {
const normalizedState = issue.state.trim().toLowerCase();
const normalizedOwner = ownerName ? ownerName.trim() : "unassigned";
const slug = `${issue.identifier}-${normalizedState}-${normalizedOwner}`
.replaceAll(/[^\w-]+/g, "-")
.replaceAll(/-+/g, "-")
.replace(/^-|-$/g, "");
if (!slug) {
return "workspace";
}
return slug;
}
良い例:
function buildWorkspaceLabel(params: {
issue: Issue;
ownerName?: string;
}): string {
const stateKey = buildStateKey(params.issue.state);
const ownerKey = buildOwnerKey(params.ownerName);
return buildWorkspaceSlug({
identifier: params.issue.identifier,
stateKey,
ownerKey,
});
}
function buildOwnerKey(ownerName?: string): string {
return ownerName ?? "unassigned";
}
3. 文字列、数値、URL、配列、辞書、重複排除、フィルタリングの基礎的なロジックは、共有ヘルパーに配置します。
悪い例:
function listUniqueIssueIds(issues: Issue[]): string[] {
return [...new Set(issues.map((issue) => issue.id))];
}
良い例:
import { unique } from "../shared/collections";
function listUniqueIssueIds(issues: Issue[]): string[] {
return unique(issues.map((issue) => issue.id));
}
共有ヘルパーがまだない場合は、まず既存の共有ヘルパーの配置場所を探してください。文字列、数値、URL、配列、辞書、重複排除、フィルタリングなどの純粋に基礎的なロジックについては、機能モジュールに埋め込まず、再利用可能なヘルパーをそこに追加します。これらについては、必ず utils/helpers などのフォルダーを探してください。
4. ユーザーから明示的に求められない限り、コードコメントを書きません。
- 行コメント、ブロックコメント、JSDoc、TODO、コメントアウトしたコード、ディレクティブコメントも含みます。
- 既存のコメント、周囲のコードのスタイル、lint ルール、ドキュメントは、ユーザーからの明示的な指示には当たりません。
- 意図は、正確な名前、型、小さな関数、明確な構造で表現します。
- 既存の正確なコメントは保持します。変更によって内容が古くなった場合は、コードコメントを明示的に求められていない限り、書き換えずに削除します。
5. 防御的なデフォルトより、境界エラーを明示する方法を優先します。
悪い例:
function parseLimit(value?: string): number {
return Number(value) || 0;
}
良い例:
const limitSchema = z.coerce.number().int().nonnegative();
function parseLimit(value: string): number {
return limitSchema.parse(value);
}
リファクタリングのルール
- 新しいヘルパーを追加する前に、同じ問題をすでに解決しているヘルパー、変換処理、アダプターがリポジトリ内にないか検索してください。
- 文字列、数値、URL、配列、辞書、重複排除、フィルタリングに関する基盤的なロジックを実装する場合は、まず共有ヘルパーのファイルを検索し、機能固有のヘルパーを導入する前に、そのロジックを共有ヘルパーに追加してください。
- 同じ不変条件、変換処理、アダプターが複数箇所にある場合は、別のコピーを追加するのではなく、抽出することを優先してください。
- なんとなく似ている2つのコード片を見たからではなく、実際に重複が生じてから抽出してください。
- 共有ヘルパーには、明確なドメイン上の目的を持たせてください。
- 関連のない懸念事項を混在させる、広範な
sanitize*、normalize*、clean*、format*ユーティリティを導入しないでください。 - 抽象化が汎用的に値を「整理する」ためだけに存在する場合、通常は型付き境界で修正するのが本来の解決策です。
- リファクタリングでは、既存の型をできるだけ維持してください。リファクタリングを容易にするためだけに型を広げないでください。
- 抽象的なユーティリティ層より、小規模な境界アダプターや不変条件ヘルパーを優先してください。
重複コードの簡易チェック
型チェックと同様に、リファクタリング後およびレビュー前に、重複コードの簡易チェックとして jscpd を使用してください。現在のリポジトリのルートから、次を実行します。
cd "$(git rev-parse --show-toplevel)"
bunx --yes jscpd --format typescript --pattern '**/*.ts' --gitignore \
--min-lines 12 --min-tokens 120 \
--max-lines 5000 --max-size 1mb --reporters console --exitCode 0 .
- フィクスチャがノイズになることが多いため、最初はテストを除外してください。必要に応じて、リポジトリに適した除外設定を追加するか、パターンの対象を本番コードに絞ってください。
- より小さなヘルパーの重複を探す場合は、
--min-tokensを60~80に下げてください。 - 閲覧可能なレポートが役立つ場合は、
--reporters html,consoleを使用してください。 - クローン検出に加えて、同じ関数名、コメント、ペイロードのフィールド、エラー文字列など、意味上の重複を
rgで絞り込んでください。正確なクローンを検出するツールでは、表記が異なる同じロジックは検出できません。
コードコメントのルール
- ユーザーから明示的に依頼されない限り、コードコメントを追加したり、既存のコードコメントを増やしたりしないでください。
- このルールでは、JSDoc、TODO、説明用ブロック、コメントアウトされたコード、ディレクティブコメントもコードコメントとして扱ってください。
- 既存のコメント、周囲のスタイル、リンタールール、生成された例、ドキュメントから許可を推測しないでください。
- 正確な既存コメントは維持してください。コードの変更によってコメントが古くなった場合は、ユーザーから明示的にコードコメントを求められていない限り、書き直すのではなく削除してください。
- 正確な名前、強い型、小さなヘルパー、明快な構造によって、コード自体が説明的になるようにしてください。
ワークフロー
- TypeScript に変更を加える前に、このスキルを読んでください。
- レビューまたはレビューと修正のタスクでは、作業範囲を決める前に
references/review-and-fix-changes.mdを読んでください。 - プロジェクト固有の規約が一般的なガイダンスと矛盾する場合は、プロジェクト固有の規約に従ってください。
- JSON としてシステムに渡されるデータには、その境界で Zod スキーマを定義してください。
- 値がすでに信頼でき、型付けされている場合は、不要なクリーンアップや型変換を避けてください。
- 関数は小さく、組み合わせ可能にしてください。
- 複数の引数を取る関数では、通常
fn(params: { ... })を優先してください。 - 引数が1つの関数では、そうしない強い理由がローカルにない限り、
paramsオブジェクトでラップせず、値を直接渡してください。 - ユーザーから明示的に依頼されていない限り、コードコメントを追加したり、増やしたりしないでください。
- 変更が文字列、数値、URL、配列、辞書、重複排除、フィルタリングなどの基盤となる型だけに関する場合は、まずリポジトリ内で既存の共有ヘルパーの場所を探し、そこに共有ロジックを追加してください。
- リファクタリング後、リポジトリやツールが対応できる場合は、
jscpdで重複コードの簡易チェックを実行してください。 - コードで
asが必要に思われる場合は、いったん立ち止まり、より安全な型付けや検証の方法を探してください。 - 新しいヘルパーや抽象化を追加する前に、再利用すべき既存のパターンがリポジトリにないか探してください。
- 複数のモジュールに類似したロジックがある場合は、重複を増やすのではなく、共有の不変条件や境界アダプターを抽出してください。
- サブエージェントを利用できる場合、新しい抽象化を導入する前に、既存ヘルパー、繰り返されている変換、リファクタリングの機会をコードベースから調べるためにサブエージェントを活用してください。
- 書き込み範囲が明確な、対象を限定した複数ファイルのリファクタリングではサブエージェントを活用してください。ただし、実際に重複がない限り、広範囲で再利用可能なユーティリティは避けてください。
- 提案した抽象化が、値を汎用的に「クリーンアップ」することだけを目的としている場合は、いったん立ち止まり、本当の修正が型付けされた境界にあるのではないか確認してください。
危険信号
JSON.parse(...) as FooNumber(value) || 0- 製品上の理由もなく使われている
input.trim().toLowerCase() - ビジネスロジック全体に繰り返し現れる
foo ?? default - 範囲が曖昧な汎用的な
sanitize*またはnormalize*ヘルパー - 小さく組み合わせ可能な関数に分割すべき、大きな機能固有ヘルパー
- 共有ヘルパーファイルに置くべき、新しい機能固有の文字列、数値、URL、配列、辞書のヘルパー
- 複数ファイルで重複している境界変換
- 理由もなく
nullとundefinedの両方を持つドメイン型 - 1つか2つのメソッドしか必要としないレイヤーに渡される、広範なインターフェースやクラス
- ユーザーから明示的に依頼されていない、新たに追加または拡張されたコードコメント
- コメントの説明対象となるコードが変更された後も残っている、古い既存コメント
レビューチェックリスト
- 生の JSON はキャストではなく、Zod で検証されていますか?
- 関数は小さく、合成可能ですか?
- 境界での解析は一度だけ、早い段階で行われていますか?
- 境界 DTO やビュー形式は、一度だけ内部ドメイン型に変換されていますか?
- 外部ペイロードの差異は内部ロジックに漏らさず、境界で処理されていますか?
- ドメインコード全体で両方を混在させず、
nullとundefinedを意図的に使い分けていますか? - 明確な境界上またはプロダクト上の理由なく、値を正規化していませんか?
- 正規化が必要な場合、元の値を保持し、派生キーを追加していますか?
- リポジトリに再利用すべき既存のヘルパー、変換処理、アダプターはありませんか?
- 基盤となる文字列、数値、URL、配列、辞書、重複排除、フィルターのロジックについて、機能固有の重複を追加するのではなく、共通ヘルパーファイルを探して使用しましたか?
- 繰り返される不変条件や境界アダプターは、再度コピーするのではなく抽出すべきではありませんか?
jscpdによる簡易テストや対象を絞ったrg検索で、リファクタリングすべき重複コードが見つかりましたか?- より強いチェックや適切な型を使うべき箇所で、
asを使用していませんか? - ドメインの不変条件は、ユニオン、
Map、Set、as constの値などの厳密な型でモデル化されていますか? - 必要な最小限の機能だけに依存していますか?
- ユーザーが明示的に求めていない限り、コードコメントの追加や拡充を避けましたか?
- コメントを求められていない場合、古くなった既存のコメントは書き換えるのではなく削除しましたか?
- 弱いデフォルト値や暗黙のフォールバックで、壊れた前提を覆い隠していませんか?
コメント
Markdown 対応