はじめに
「なぜ同じようなクラスがいくつも存在するんだ?共通化すべきじゃないのか?」
コードレビューでこんなコメントを見かけたり、あるいは自分自身が疑問に思ったことはないでしょうか。私たちは長年、「重複を避けること」を美徳として教えられてきました。しかし、現代のWeb開発を振り返ると、この考え方が必ずしも最適でないケースが増えています。
この記事では、Web開発における設計思想の歴史的変遷を振り返り、なぜ今「重複を恐れない設計」が重要なのか、そして凝集度や単一責任の原則をどう捉え直すべきかについて考察します。
※AIに考え方や思想をプロンプトとして与え、アウトプットをそのまま貼ってます。
一昔前の設計思想:データモデル中心の世界
シンプルだった時代の前提
かつてのWeb開発は、明確でシンプルな構造で成り立っていました。
基本的な前提:
- 1テーブル = 1画面 = 1機能
- データベースのテーブル構造とUI画面が1:1で対応
- CRUD操作が中心
- 画面遷移はサーバーサイドで完結
- ウォーターフォール型の開発プロセス
この時代、システム設計は非常に直線的でした。データベース設計が決まれば、それに対応する画面が決まり、その画面に対応するコントローラーとモデルが決まる。すべてが予測可能で、最初に全体設計を固めることが可能でした。
再利用可能性こそが美徳
この構造のもとで最も重視されていたのは**「再利用可能性」**です。
「Userモデルは、ユーザー一覧でも、ユーザー詳細でも、ユーザー編集でも使える汎用的なものにすべき」というのが常識でした。リクエストモデルも同様で、1つのエンティティに対して1つの汎用的なモデルクラスを作り、それをあらゆる場面で使い回すことが推奨されていました。
この思想は理にかなっていました。テーブル構造が変われば全画面に影響が出るため、**「1つの変更 = ほとんどの機能で変更必要」**という前提があったからです。ならば、共通のモデルクラスを使っていれば、1箇所変更するだけで済みます。
DRY原則の絶対視
「Don’t Repeat Yourself(重複を避けよ)」は、この時代の黄金律でした。
同じような構造のクラスが2つ存在することは、ほぼ「設計ミス」とみなされました。共通化できるものは必ず共通化し、コードの重複を最小化することが良い設計の証とされていました。
パラダイムシフト:何が変わったのか
1画面 = n機能の時代へ
現代のWeb開発は、根本的に異なる前提で動いています。
現代の前提:
- 1画面 = n機能
- リッチなUIによる複数の同時操作
- マイクロインタラクション(インライン編集、ドラッグ&ドロップ、リアルタイム更新)
- SPAによるクライアントサイドでの複雑な状態管理
- APIファーストの設計
- アジャイル開発による継続的な機能追加・変更
具体例:ダッシュボード画面の変化
昔のダッシュボード:
- データの表示が中心
- 編集するには専用の編集画面に遷移
- 1つのアクション = 1つの画面遷移
現代のダッシュボード:
- 1つの画面内で以下すべてが可能:
- プロジェクトステータスの変更(ドラッグ&ドロップ)
- プロジェクト名のインライン編集(その場で変更)
- メンバーの追加(モーダルから)
- タグの追加・削除(ドロップダウン)
- 進捗率のクイック更新(スライダー)
- 並び順の変更(ドラッグ&ドロップ)
- フィルタリング(リアルタイム)
それぞれが独立したAPI呼び出しであり、それぞれに適したリクエスト構造が必要です。
テーブルと画面の関係性の崩壊
最も重要な変化は、**「テーブル構造と画面構造の1:1対応が崩壊した」**ことです。
昔:
- Projectsテーブル → プロジェクト管理画面
- 画面での操作 = テーブルへのCRUD
今:
- 1つの画面が複数のテーブルを横断して操作
- 1つのテーブルに対して、10種類以上の異なる操作方法が存在
- 同じデータでも、コンテキストによって必要な情報が全く異なる
たとえば「作業(Work)」というエンティティ1つを取っても:
- プロジェクト作成時の入力:名前だけあればいい
- 一括登録機能:名前、担当者、工数、期限などの詳細が必要
- 並び替え機能:IDの配列だけで十分
- インライン編集:変更された項目だけを送信したい
- 検索機能:部分一致、日付範囲、担当者フィルタなど複雑な条件
これらは全て「作業」に関する操作ですが、必要なデータ構造が完全に異なります。
設計原則の優先順位の転換
DRY原則の問題点が顕在化
かつての「とにかく共通化」というアプローチは、現代では深刻な問題を引き起こします。
1. 責任の混在 1つのクラスが「作成」「更新」「並び替え」「検索」すべてに対応しようとすると、どの項目がどの場面で必須なのか不明確になります。
2. 変更の波及 「並び替え機能の改善」のための変更が、無関係な「作成機能」に予期せぬ影響を与えることがあります。
3. 複雑化の連鎖 新しいユースケースが追加されるたびに、共通クラスに条件分岐やnullableフィールドが増えていきます。
4. 理解の困難 新しく参加した開発者が「このフィールドはいつ使われるのか?」を理解するのに時間がかかります。
SRP(単一責任の原則)の重要性
現代では、「単一責任の原則」が最優先されるべきです。
1つのクラスは1つの責任だけを持つべきであり、その「1つの責任」とは**「1つのユースケース」**を意味します。
「作成」と「更新」と「並び替え」は、たとえ同じエンティティに対する操作でも、異なる責任です。それぞれに専用のモデルクラスを持つべきです。
変更容易性の重視
共通化による「変更時の効率化」という幻想は崩れました。
共通化のメリットとして語られてきたこと: 「1箇所変更すれば全体に反映される」
実際に起きること: 「1箇所の変更が、予期せぬ場所に影響を与える」
現代では、**「1つの機能の変更が他の機能に影響を与えないこと」**の方がはるかに重要です。
重要な認識の転換
昔の認識:変更は波及するもの
前提:
- 「1つの機能での変更 = ほとんどの機能で変更必要」
- テーブル構造の変更は全機能に波及する
- だから共通化して一箇所で管理すべき
結果:
- 変更のコストは高いが、変更箇所は明確
- ウォーターフォール的なアプローチとマッチしていた
現代の認識:変更は局所化すべきもの
前提:
- 「1つの機能での変更 = その機能だけで完結すべき」
- 機能ごとに必要なデータ構造は異なる
- 他の機能に影響を与えない設計が最優先
結果:
- 変更のコストは低く、素早いイテレーションが可能
- アジャイル的なアプローチとマッチしている
波及の判断は人間がする
ここで重要なのは、「他の機能にも変更を波及させるか」は人間が意図的に判断することだという点です。
ある機能でモデルの構造を変えた時、それを他の機能にも反映させるべきかどうかは:
- ビジネス要件
- ユーザー体験
- 影響範囲
- 実装コスト
これらを総合的に考慮して、開発者が意図的に判断します。機械的に「共通だから自動的に反映される」わけではありません。
機能が適切に分離されていれば、この判断と実装が容易になります。
新しい設計指針
1. 「重複」を恐れない
1つのモデルに対してn機能分、同じようなクラスが存在してもいい
それぞれが異なるユースケース、異なる責任を持っているなら、それは重複ではなく適切な分離です。
名前や構造が似ていても、「作成用リクエスト」と「更新用リクエスト」と「並び替え用リクエスト」は別物です。
2. ユースケース単位で凝集させる
凝集度の新しい定義:
かつての「凝集度」は「データ構造の近さ」を意味していました。同じエンティティに関するものは同じ場所に、という考え方です。
現代の「凝集度」は**「ユースケースの完結性」**を意味します。1つの機能を実現するために必要なものは、すべて発見可能な場所にまとまっているべきです。
3. 変更の影響範囲を明示的に
機能ごとにモデルが分離されていれば、変更の影響範囲が明確になります。
「プロジェクト作成リクエスト」を変更しても、「プロジェクト並び替えリクエスト」には影響しません。この保証が、安心して変更できる基盤を作ります。
4. コンテキストを優先する
同じデータでも、コンテキストによって必要な形式は異なります。
「User」という概念は、以下のように異なる表現を持ちます:
- 認証時:IDとパスワードだけ
- プロフィール表示:名前、アイコン、経歴など詳細
- メンバー選択:IDと名前だけ
- ユーザー検索:検索条件(部分一致、権限、所属など)
これらを無理に1つのクラスで表現しようとすると、複雑さが爆発します。
実践的な判断基準
いつ分離すべきか
以下の場合、別々のクラスとして設計すべきです:
- 異なるバリデーションルール: 作成時は必須だが更新時は任意、など
- 異なるデータ範囲: 一部の項目だけ、全項目、集約データ、など
- 異なる変更頻度: ある機能は頻繁に変わるが、別の機能は安定している
- 異なるチーム担当: 機能Aはチーム1、機能Bはチーム2が担当
いつ共通化すべきか
以下の場合は、共通化を検討すべきです:
- 本質的に同じ概念: 住所、電話番号など、どこでも同じ形式で扱われる
- 変更が常に連動: ビジネスルールとして、片方が変われば必ずもう片方も変わる
- 複雑なロジック: 独自のバリデーションロジックや変換ロジックを持ち、それ自体が独立した価値を持つ
フォルダ構造とモデリングの考え方
機能中心のフォルダ構成
現代の設計では、フォルダ構造も機能(ユースケース)中心にするべきです。
エンティティ(テーブル)ごとに分けるのではなく、関連する機能をまとめます。たとえば:
Models/Request/Project/
- ProjectCreateRequest
- ProjectCreateFromTemplateRequest
- ProjectBulkCreateRequest
- ProjectUpdateRequest
- ProjectQuickEditRequest
- ProjectArchiveRequest
- ProjectReorderRequest
このフォルダを見れば、「プロジェクトに対してどんな操作ができるのか」が一目瞭然です。
発見可能性の重要性
**「関連する概念が発見可能な距離にある」**ことが重要です。
同じフォルダ内に複数の作成系リクエストがあれば、1つを変更する際に「他の作成系も確認すべきか?」と自然に気づけます。これが、変更の波及を人間が適切に判断できる基盤になります。
まとめ:設計思想のパラダイムシフト
現代のWeb開発における設計の要点をまとめます。
時代の変化
| 観点 | 一昔前 | 現代 |
|---|---|---|
| 構造 | 1画面1機能 | 1画面n機能 |
| 中心 | データモデル | ユースケース |
| 重視 | 再利用性 | 変更容易性 |
| プロセス | ウォーターフォール | アジャイル的 |
| 関係性 | テーブル=画面(ほぼ) | テーブルと画面は独立 |
設計原則の優先順位
優先順位が下がったもの:
- DRY(重複を避ける)
- 共通化・汎用化
- 網羅的な事前設計
優先順位が上がったもの:
- SRP(単一責任)
- 変更の局所化
- 独立性・疎結合
- 素早いイテレーション
重複への向き合い方
「重複」を恐れる必要はありません。
同じような構造のクラスが複数存在することは、それぞれが異なる責任を持つ証拠です。むしろ、無理に共通化して1つのクラスに複数の責任を持たせることの方が問題です。
大切なのは、「これは本当に重複なのか、それとも異なる責任の分離なのか」を見極めることです。
新しい凝集度の概念
現代における「高い凝集度」とは:
- 責任の明確さ: 各クラスが単一の明確な責任を持つ
- 変更の局所性: 機能の変更が他に波及しない
- 発見可能性: 関連する概念が見つけやすい場所にある
- 理解の容易さ: クラスの目的と使われ方が明確
- 独立性: 他の機能に依存せず、独立してテスト・変更できる
おわりに
Web開発のパラダイムシフトは、私たちの設計思想も変えていきます。
「データベース中心」から「ユースケース中心」へ。「再利用性」から「変更容易性」へ。「DRY」から「SRP」へ。
この変化を理解せずに、昔ながらの「共通化こそ正義」という考え方を続けていると、現代の開発スピードに対応できなくなります。変更のたびに影響範囲の調査に追われ、テストに時間がかかり、新機能の追加が遅くなっていきます。
一方で、新しい設計思想を理解していれば、機能を素早く追加・変更でき、影響範囲を心配する必要も減り、チームの生産性は大きく向上します。
大切なのは、教科書的な原則に盲目的に従うのではなく、「なぜその設計が推奨されるのか」を時代背景とともに理解することです。そうすることで、自分たちのプロジェクトに最適な設計を選択できるようになります。
「重複を恐れず、変更を恐れない設計」。それが現代のWeb開発における新しいスタンダードです。


コメント