AIと育てる型定義 Part 2:『唯一の真実の源』の確立のサムネイル

はじめに

YAML の設定値と TypeScript の型定義を別々に管理していると、変更のたびに同期漏れが起きます。
keyof typeof を使って設定から型を導出すれば、一箇所の変更だけで整合性が保たれます。

設定値の変更で、コードのあちこちを修正していませんか?

  • カテゴリを一つ追加しただけで、YAML と TypeScript の型定義を両方手作業で更新している。
  • どちらかを変え忘れて、本番環境で実行時エラーが出たことがある。
  • 「YAML と型のどちらが正しいのか」が曖昧で、確認する気力が削がれる。

この記事をお勧めしない人

  • 設定値と型定義が二重管理になっていても、手間さえかければ問題ないと考えている人。
  • TypeScriptの keyof typeof 等を活用した自動同期よりも、手動での定義を好む人。
  • プロジェクトの整合性を自動で担保する「仕組み化」に関心がない人。

もし一つでも当てはまらないなら、読み進める価値があるかもしれません。

二重管理を放置すると

  • 設定と型を別々に管理し続けると、「どちらが最新か」の確認コストが毎回の変更に乗っかる。
  • 仕様変更のたびに修正漏れのリスクがあり、AIに修正を頼んでも片方しか直さないことがある。
  • 実行時エラーはコンパイルを通り抜けるため、本番デプロイ後に初めて発覚する。

唯一の真実の源(SSOT)という明るい未来

  • この記事を読めば、設定ファイルと型定義を完全に同期させ、情報の整合性を自動で担保する手法が手に入る。
  • 具体的には、spec.yaml を起点として型を生成し、一箇所の変更を全体に波及させる設計図を手に入れられる。
  • この方法は、本ブログのカテゴリ管理システムにおいて、一貫性を維持するために実証済みである。

私も同じでした

このブログのカテゴリ定義が YAML と TypeScript の両方に存在していました。カテゴリを追加するたびに両方を更新する必要があり、片方の修正漏れがランタイムエラーになりました。
keyof typeof で YAML から型を導出し、二重管理を廃止しました。このシリーズの Part 2 です。

📝 概要

前回の記事では、AIと協力して完全に重複していた型を集約し、コードベースの健全性を取り戻す第一歩を踏み出しました。

今回は、より複雑な課題に挑戦します。それは、 関連しているが、少しずつ内容が違う 型定義の整理です。

このリファクタリングを通じて、 「唯一の真実の源 (Single Source of Truth)」 という重要な設計原則を導入し、TypeScriptの型システムを活用して、保守性と再利用性の高い構造を構築していきます。

⚠️ 新たな課題:関連するが同一ではない型

前回の単純な重複は、一つのファイルにまとめるだけで解決しました。しかし、関連する型は、それぞれ異なる目的とプロパティを持っています。

例えば、同じドメインの型でも:

  • ビルド時に生成される 元データ (すべての情報を持つ)
  • 詳細ページで必要とされる フルセット
  • 一覧ページで使われる 要約版
  • コンポーネントが表示に使う 部分セット

これらの型は密接に関連していますが、それぞれが独立して定義されているため、以下のような問題が発生します。

  • 変更の追従が困難 : 元データに新しいプロパティを追加した場合、他のすべての関連型を手動で更新する必要があり、修正漏れのリスクが高い
  • 信頼性の欠如 : どの型が「最新」で「正しい」情報源なのかが不明確になる

🔍 私が設計した『唯一の真実の源』アプローチ

この問題を解決するため、私は 「唯一の真実の源 (Single Source of Truth)」 という設計原則を導入しました。

具体的には、以下の戦略で型の依存関係を再構築します:

  1. 基底型の確立 : すべての情報を持つ 基底となる型 を一つ定義する
  2. 派生型の自動生成 : TypeScriptの型システムを使い、基底型から各用途(一覧、詳細など)に特化した型を自動的に派生させる
  3. 依存関係の一方向化 : 基底型 → 派生型という一方向の依存にし、基底型の変更が自動的に派生型に反映される仕組みを作る

このアプローチにより、単にファイルをまとめるのではなく、 型同士の関係性を設計し、依存関係を明確にする という、より高度なリファクタリングを実現しました。

達成した成果

改善項目 Before After
型定義の依存関係 各型が独立して定義 基底型から派生型へ一方向の依存
プロパティ追加時の作業 すべての関連型を手動更新 基底型のみ更新で自動反映
信頼できる情報源 不明確(どれが正しいか分からない) 基底型が単一の真実の源

その結果、 「散らばった型定義」を「依存関係が明確な型階層」へ再構築する ことに成功しました。

AIが陥る型定義の依存関係の罠

ここで重要な警告があります。

AIに「関連する型を整理して」と頼むと、高確率で以下のような提案が返ってきます:

  • 「各型を個別に修正しましょう」
  • 「とりあえず同じファイルにまとめましょう」
  • 「どの型を使うか、用途に応じて選びましょう」

しかし、これらは 対症療法 です。一時的に整理されますが、「どの型が情報源か」という依存関係が欠けているため、すぐに修正漏れや不整合が発生します。

根本原因は、 型定義の依存関係が設計されていない ことにありました。AIは「今ある型」しか見ませんが、人間は「将来の変更時の追従性」や「情報源の信頼性」という、より高次の制約を満たす必要があります。

ここから先は、AIが絶対に提案しない 「基底型からの派生」という設計パターン の全貌と、TypeScriptの型システムを使った具体的な実装コード、依存関係を一方向化する技術的手順、そして実際の型定義ファイル構造を、すべて公開します。

このパターンをコピーすれば、型定義の修正漏れループを回避し、 初回から依存関係が明確な型システム を実現できます。私が実践で確立した型階層設計と、実装済みの型定義パターンを、ここで全て公開します。

実装フェーズ:具体的な手順

では、実際に私が設計した基底型の定義、TypeScriptの型システムを使った派生型の自動生成、依存関係を一方向化するインポート構造、そして各レイヤーでの型の使い分けパターンを公開します。

このコードと構造をそのままコピーすれば、「どの型を基底にするか」「どうやって派生させるか」「どこからインポートするか」を毎回悩むことなく、 再現可能な型階層 を実現できます。また、なぜ基底型が単一の真実の源になるのか、派生型の自動追従の仕組み、コンポーネントでの型の受け取り方も解説します。

まとめ

このリファクタリングにより、 「唯一の真実の源」 が確立されました。

主なメリットは以下の通りです:

  • 保守性の向上 : 将来、データに新しいプロパティを追加する場合、基底型を修正するだけで済みます。派生型は、意図的に変更しない限り影響を受けません
  • 信頼性の向上 : 基底型が唯一の信頼できる情報源となり、どのプロパティが利用可能かが明確になりました
  • コードの簡潔化 : 冗長な型定義を削除し、コンポーネントの責務がより明確になりました

AIとの協調を通じて、単なる重複排除に留まらず、より堅牢な型設計へとステップアップできた好例と言えるでしょう。

「型からSSOTを確立する」のと並行して、UIラベル・バリデーション・テストIDを含むすべての設定値をYAMLの階層型マージで一元管理するアーキテクチャも整備しました。型定義のSSOTとYAML定義のSSOTが両輪として機能することで、「コードが仕様を語る」状態が完成します。また、同じSSOT思想をE2Eテストデータに適用したspec.yamlでテストデータを一元管理して記事追加でテストが壊れなくなった記録も参照してください。

このシリーズの記事

あなたのリポジトリへの適用

どちらを採用しますか?(1つ選択)

SIMPLICITY|シンプルに管理

keyof typeofによる型導出がプロジェクト全体で一貫して使用されているか確認する

調査:

  1. YAML設定値から型を導出しているファイルと、独自に型定義しているファイルを特定せよ
  2. keyof typeof を使わずに手動で型定義が書かれている箇所がないか確認せよ
  3. YAMLの変更後に型定義の同期漏れが発生していないか確認せよ

この内容をClaude Codeに貼り付けて実行します

SEPARATION|責務を分離

YAMLの設定値とTypeScriptの型定義の同期状態を確認する

調査:

  1. 現在のYAML設定値とTypeScript型定義の対応関係が正しく維持されているか確認せよ
  2. YAML変更時に型定義が自動更新される仕組みが機能しているか確認せよ
  3. 型とYAMLが乖離しているフィールドを特定せよ

この内容をClaude Codeに貼り付けて実行します

外部コードのローカル実行にはリスクがあります。ブラウザ環境での実行を推奨します。