AIと育てる型定義 Part 3:責務の分離でコードをクリーンにするのサムネイル

はじめに

一つの型がページネーション・フィルター・UI表示の役割を兼ねると、フィルター条件を変えただけで無関係の箇所で型エラーが出ます。
Intersection Types(type A = B & C)で責務を分解すると、変更の影響範囲が予測できるようになります。

型定義に「何でも屋」が潜んでいませんか?

  • 開発が進むにつれ、1つの型が複数の役割(ページネーション、フィルター等)を兼ねる「巨大な型」になってしまった。
  • UI層とサーバー層の両方で使いたい型が、特定のファイルに閉じ込められていて再利用が難しい。
  • フィルター条件を少し変えただけで、全く関係のない箇所の型エラーに悩まされている。

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

  • 型定義は動けばそれで十分で、責務の分離という設計原則には関心がない人。
  • TypeScriptの型システムを駆使した「柔軟で堅牢なコード」よりも、単純な定義を好む人。
  • AIを単なるコーディングマシンとして使い、設計の壁打ち相手として活用したくない人。

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

責務が混ざったままだと

  • 1つの型に複数の責務が混ざると、フィルターを変えたらUIの型が壊れるといった予測不能な連鎖が起きる。
  • 「UI層でも使いたいが、サーバー固有のフィールドが入っている」という理由で型が再利用できなくなる。
  • AIに修正を頼むと既存の巨大な型をさらに拡張する提案が返ってきて、問題が大きくなる。

責務の分離という明るい未来

  • この記事を読めば、TypeScriptのIntersection Typesを活用し、型の再利用性と見通しを向上させる手法が手に入る。
  • 具体的には、混在した責務を独立した型に分解し、必要に応じて結合するクリーンな型システムへの設計図を手に入れられる。
  • この方法は、本ブログのフィルター機能のリファクタリングを通じて、その有用性を実証済みである。

私も同じでした

このブログの記事フィルター型が、ページネーション・カテゴリフィルター・UI表示フラグを一つに抱えていました。
type PostFilter = PaginationParams & CategoryFilter のように Intersection Types で責務を分解したことで、フィルター条件だけを変更してもUIの型定義に影響しなくなりました。このシリーズの Part 3 です。

📝 概要

これまでのシリーズでは、AIとの対話を通じて型定義を整理してきました。

今回は、さらに一歩進んで 「関心の分離 (Separation of Concerns)」 という設計原則を型定義に適用します。具体的には、ブログのフィルター機能に関連する型が抱えていた「責務の混在」と「配置場所の問題」を、AIと協力して解決していきます。

⚠️ 新たな課題:混在する責務と不適切な配置

これまでのリファクタリングで主要な型は整理されましたが、機能横断的な型にはまだ改善の余地がありました。

  1. 責務の混在 : データ取得オプションの型が、 ページネーション (表示件数、オフセット)と フィルター (条件検索)という、2つの異なる責務を併せ持っていました

  2. 不適切な配置 : UI層とサーバー層の両方で必要とされる型が、データアクセス層に定義されていたため、UI層から参照しにくい状態でした

🔍 私が設計した『関心の分離』アプローチ

この問題を解決するため、私は 「関心の分離 (Separation of Concerns)」 という設計原則を適用しました。

具体的には、以下の戦略で型の責務を分離し、再配置します:

  1. 責務の明確化 : 混在していた責務(ページネーションとフィルター)を独立した型に分離する
  2. 型の結合 : TypeScriptの型システムを使い、分離した型を必要に応じて結合する
  3. 配置の最適化 : UI層とサーバー層で共有する型を、プロジェクト共通の場所に配置する

このアプローチにより、単に型を移動するのではなく、 型の責務を明確にし、適切な配置で再利用性を高める という、より高度なリファクタリングを実現しました。

達成した成果

改善項目 Before After
型の責務 ページネーションとフィルターが混在 独立した型に分離
再利用性 フィルター条件だけを使いたい場合に困難 フィルター型を独立して再利用可能
配置の適切性 UI層で必要な型がサーバー層に配置 共通の場所に配置し、両層から参照可能

その結果、 「責務が混在した型」を「明確に分離された型階層」へ再構築する ことに成功しました。

AIが陥る責務混在の罠

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

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

  • 「型をまとめて1つのファイルに移動しましょう」
  • 「とりあえず共通の場所に配置しましょう」
  • 「型の名前を統一しましょう」

しかし、これらは 対症療法 です。一時的に整理されますが、「どの型がどの責務を持つか」という設計が欠けているため、すぐに再び混在が発生します。

根本原因は、 型の責務が設計されていない ことにありました。AIは「今ある型の配置」しか見ませんが、人間は「型の持つ責務の分離」や「層をまたぐ型の配置戦略」という、より高次の制約を満たす必要があります。

ここから先は、AIが絶対に提案しない 「責務の分離」という設計パターン の全貌と、TypeScriptの型システムを使った型の結合手法、層横断的な型の配置戦略、そして実際の型定義ファイル構造を、すべて公開します。

このパターンをコピーすれば、型の責務混在ループを回避し、 初回から責務が明確な型システム を実現できます。私が実践で確立した型の責務分離設計と、実装済みの型配置パターンを、ここで全て公開します。

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

では、実際に私が設計した責務分離の具体的な型定義、TypeScriptの型システムを使った型の結合手法、層横断的な型の配置ルール、そして各レイヤーでの型のインポート構造を公開します。

このコードと構造をそのままコピーすれば、「どう責務を分離するか」「どうやって結合するか」「どこに配置するか」を毎回悩むことなく、 再現可能な型の責務分離 を実現できます。また、なぜ責務の分離が重要なのか、型の結合によって柔軟性を保つ仕組み、層横断的な型の管理方法も解説します。

まとめ

今回のリファクタリングにより、機能横断的な型は「関心の分離」の原則に従って整理されました。

  • 責務の明確化 : データ取得オプションが「ページネーション」と「フィルター」という明確な責務に分離され、コードの意図が読み取りやすくなりました
  • 再利用性の向上 : フィルター型が独立したことで、将来的に他の機能でフィルター条件だけを再利用することが容易になりました
  • 見通しの改善 : UIとサーバーで共有する型が共通の場所に配置されたことで、データフローが明確になり、プロジェクト全体の整合性が向上しました

AIとの協調を通じて、単にコードを動かすだけでなく、よりクリーンでメンテナンスしやすい設計へと導かれる。これもまた、AIとの協調開発の大きな価値の一つです。

このシリーズの記事

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

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

SEPARATION|責務を分離

フィルター機能の型でページネーション・フィルター・UI表示の責務が分離されているか確認する

調査:

  1. 現在のフィルター関連の型定義がどのファイルに分散しているか確認せよ
  2. Intersection Typesで責務分離した型が、新しい機能追加後も正しく分離された状態を維持しているか確認せよ
  3. フィルター条件変更時にUI表示側の型に影響が出ていないか確認せよ

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

SIMPLICITY|シンプルに管理

Intersection Typesの使用が適切で過剰な複雑化が起きていないか確認する

調査:

  1. Intersection Typesが3つ以上重なっているケースを特定せよ
  2. 型定義を見て「この型は何の責務を持つか」が即座に理解できるか確認せよ
  3. 型エラーが発生した際にデバッグしやすい構造になっているか確認せよ

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

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