mcpField
V
valid-state-types

valid-state-types

github.com/marius-townhouse開発649 回インストール39 週間前に更新
リポジトリ

valid-state-types について

valid-state-typesは、アプリケーション開発者向けのプログラミングスキルです。無効なフィールドの組み合わせを避ける型設計を案内し、判別共用体や、常に一緒に現れるフィールドのグループ化を重視します。

主な機能

  1. 01無効状態を招く設計の検出
  2. 02判別共用体による状態表現
  3. 03同時に必要なフィールドのグループ化
  4. 04単一の信頼できる情報源のモデル化

活用シーン

  1. 01リクエストの読み込み・結果状態の設計
  2. 02依存する複数の真偽値を含む型の改修
  3. 03ショッピングカートなど段階的な状態のモデル化

インストール

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill valid-state-types

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

詳細

常に有効な状態を表現する型を優先する

概要

有効な状態だけを表現できるように型を設計します。

型で無効な値の組み合わせを許してしまうと、コードの作成や読解が難しくなり、バグも発生しやすくなります。

このスキルを使う場面

  • アプリケーションの状態型を設計するとき
  • 関連するフィールドを持つ型を作成するとき
  • 依存関係のある状態をモデル化するとき
  • 存在し得ない状態に関するエラーをデバッグするとき
  • 分かりにくい型定義をリファクタリングするとき

絶対に守るべきルール

NEVER design types that can represent invalid states.

例外はありません:

  • 「このほうが簡単だから」という理由では認められません
  • 「実行時に検証するから」という理由では認められません
  • 「型を増やすのは手間がかかりすぎる」という理由では認められません

検出:「無効な状態」の兆候

本来あってはならない組み合わせを型が許しているなら、設計し直してください。

// ❌ VIOLATION: Allows invalid states
interface RequestState {
  status: 'loading' | 'success' | 'error';
  data?: string;
  error?: string;
}

// These are all valid according to the type, but nonsensical:
const bad1: RequestState = { status: 'success' };           // Where's the data?
const bad2: RequestState = { status: 'error' };             // Where's the error?
const bad3: RequestState = { status: 'loading', data: 'x' }; // Loading but has data?
const bad4: RequestState = { status: 'success', error: 'x' }; // Success with error?

解決策:判別可能なユニオン

// ✅ CORRECT: Only valid states are representable
interface RequestPending {
  status: 'pending';
}
interface RequestLoading {
  status: 'loading';
}
interface RequestSuccess {
  status: 'success';
  data: string;
}
interface RequestError {
  status: 'error';
  error: string;
}

type RequestState = RequestPending | RequestLoading | RequestSuccess | RequestError;

// Now invalid states are impossible:
const bad: RequestState = { status: 'success' };
//    ~~~ Property 'data' is missing in type '{ status: "success"; }'

実例:ページの状態

// ❌ BAD: Implicit relationships, invalid states possible
interface PageState {
  isLoading: boolean;
  error?: string;
  currentPage: string;
  data?: PageData;
}

// What does this mean?
const confusing: PageState = {
  isLoading: true,
  error: 'Network error',
  currentPage: '/home',
  data: someData,  // Loading but has data? Has error but also data?
};

// ✅ GOOD: Explicit states, no invalid combinations
interface PagePending { state: 'pending' }
interface PageLoading { state: 'loading'; currentPage: string }
interface PageLoaded { state: 'loaded'; currentPage: string; data: PageData }
interface PageError { state: 'error'; currentPage: string; error: string }

type PageState = PagePending | PageLoading | PageLoaded | PageError;

// Now the render function is clear:
function renderPage(state: PageState) {
  switch (state.state) {
    case 'pending':
      return renderPending();
    case 'loading':
      return renderSpinner(state.currentPage);
    case 'loaded':
      return renderData(state.data);
    case 'error':
      return renderError(state.error);
  }
}

関連するフィールドは一緒に扱う

// ❌ BAD: Related fields can be independently undefined
interface Person {
  name: string;
  placeOfBirth?: string;  // These should either both
  dateOfBirth?: Date;     // be present or both absent
}

// This is valid but probably wrong:
const person: Person = { name: 'Alice', placeOfBirth: 'NYC' };  // No date?

// ✅ GOOD: Group related fields
interface Person {
  name: string;
  birth?: {
    place: string;
    date: Date;
  };
}

// Now they travel together:
function printBirth(person: Person) {
  if (person.birth) {
    // Both place AND date are guaranteed to exist
    console.log(`Born in ${person.birth.place} on ${person.birth.date}`);
  }
}

エールフランス447便のアンチパターン

不適切な状態設計が招いた悲劇的な例です:

// ❌ DANGEROUS: Independent controls with conflicting states
interface CockpitControls {
  leftSideStick: number;   // Pilot's stick position
  rightSideStick: number;  // Copilot's stick position
}

// What if they conflict? The code has to decide somehow.
function getStickSetting(controls: CockpitControls): number {
  // Average them? Return left? Return right? No good answer!
  return (controls.leftSideStick + controls.rightSideStick) / 2;
}

// ✅ SAFE: Single source of truth
interface CockpitControls {
  stickAngle: number;  // One stick, one truth
}

圧力に負けないための手順

1. 「型が増えると作業も増える」

圧力:「これらすべてのインターフェースを作るのは面倒です」

**回答:**無効な状態はバグの原因になります。型を作るのは簡単な部分です。

**対策:**最初に時間をかけてください。後でデバッグにかかる時間を節約できます。

2. 「実行時に検証する」

圧力:「コード内で無効な組み合わせをチェックしています」

**回答:**利用側はすべて、検証する必要があることを覚えていなければなりません。実際には覚えていないでしょう。

**対策:**無効な状態を表現できないようにしてください。検証の必要性をなくしましょう。

3. 「内部状態にすぎない」

圧力:「この型を使う外部コードはありません」

**回答:**内部コードもコードです。やはりバグは発生します。

**対策:**どこでも適切な型を設計してください。

危険信号 — 作業を止めて再検討してください

  • 依存関係のある複数のブール型フラグ
  • 常に一緒に現れるべきオプショナルフィールド
  • オプショナルなデータフィールドを持つステータス列挙型
  • 「X の場合は Y を設定する必要がある」と説明するコメント
  • あり得ない組み合わせをチェックする検証コード
  • 「通常は起こらない」デフォルトケースを含む switch 文

よくある正当化(いずれも不適切)

言い訳実際のところ
「そのほうが簡単」バグが発生するまでは簡単です。
「慎重に扱っています」不注意は起こります。型は忘れません。
「ドキュメントに記載しています」ドキュメントは古くなります。型は古くなりません。
「インターフェースが多すぎる」バグが多すぎるよりはましです。

クイックリファレンス

悪いパターン良いパターン
関連する複数のオプショナルフィールドオプショナルなネストオブジェクト
ステータス文字列とオプショナルなデータ/エラー判別可能なユニオン
依存関係のあるブール型フラグ判別可能なユニオン
独立した複数の信頼できる情報源信頼できる情報源を1つにする

有効な状態型を設計する

  1. システムが取り得るすべての状態を列挙します
  2. 各状態について、必要なデータを判断します
  3. 各状態のインターフェースを作成します
  4. 判別可能なユニオンを使ってそれらを組み合わせます
  5. 確認します: 無効な状態を構築できてしまいませんか?
// Example: Shopping Cart
interface EmptyCart { state: 'empty' }
interface ActiveCart { state: 'active'; items: CartItem[] }
interface CheckoutCart { state: 'checkout'; items: CartItem[]; payment: PaymentInfo }
interface CompletedCart { state: 'completed'; orderId: string }

type ShoppingCart = EmptyCart | ActiveCart | CheckoutCart | CompletedCart;

要点

無効な状態を表現できるなら、無効な状態が発生します。

有効な状態だけを表現できる型を設計してください。判別可能なユニオンを使い、関連するフィールドをまとめましょう。そうすれば、コードの作成や理解が容易になり、壊れにくくなります。

参考資料

Dan Vanderkam 著『Effective TypeScript』の項目29「常に有効な状態を表現する型を優先する」に基づいています。

コメント