ウェブエンジニア問題集

詳細設計書の書き方 — 何をどこまで書くべきか

「詳細設計書を書いて」と言われたとき、何をどこまで書けばいいのか迷うことは多いはずです。 書きすぎると実装と乖離し、書かなすぎると設計の意味がなくなる。 この章では、詳細設計書に含めるべき項目、粒度の判断基準、そして実際のテンプレートを示します。

学習者学習者

詳細設計書って、どこまで細かく書けばいいの…?結局コードに書くことと被らない?

詳細設計書の目的

詳細設計書は「実装者がコードを書ける状態にするためのドキュメント」です。 言い換えれば、基本設計で決めた仕様をクラス・メソッド・関数のレベルまで落とし込むことが詳細設計の仕事です。

詳細設計書作成のステップまでの流れ、設計ワークの全体像

詳細設計は単独で存在するものではなく、モデリングや基本設計の成果物を受けて進めます。最終的なゴールはクラス図の完成——つまり「どのクラスにどの責務を持たせ、クラス同士がどう関係するか」を決めることです。

ステップやること成果物
ロバストネス分析基本設計の仕様を分析し、責務を細分化して適切なクラスに割り当てるバウンダリ・コントロール・エンティティの分類
シーケンス図オブジェクト間のメッセージのやり取りを時系列で整理し、各クラスのメソッドを決めるメソッド一覧・呼び出し順序
クラス図クラスの属性・メソッド・依存関係をまとめる実装の設計図
先生先生

いきなりクラス図を書こうとすると手が止まりがちです。ロバストネス分析 → シーケンス図 → クラス図の順に進めると、自然と責務やメソッドが見えてきますよ。

クラス図で決めること — What と How の分離

クラス図が完成すると、各クラスが何を担当するかが見えてきます。ここで意識したいのは、処理の「割り振り」と「実装」を分けることです。

  • What(管理担当) — 「何をしたいか」を受け取り、どの処理に振り分けるかを決める
  • How(処理担当) — 実際の処理を行うメソッドを持つ。データの保存や外部APIの呼び出しなど
レイヤー役割
コントローラリクエストを受け取り、どの処理を呼ぶか振り分けるThreadController
サービス処理の塊。複数の How クラスを組み合わせて業務を完結させるThreadService, NotificationService
ファクトリドメインオブジェクトの生成を担当するThreadFactory
リポジトリデータの永続化(保存・取得)を担当するThreadRepository
APIクライアント外部サービスへのリクエスト送信を担当するSlackApiClient
学習者学習者

コントローラに全部書いちゃダメなの?1ファイルで済むなら楽そうだけど…

先生先生

小さな機能なら1ファイルでも動きます。 でもWhatとHowを分けておくと、別サービスに変えるときもAPIクライアントだけ差し替えれば済みます。

詳細設計書に含める項目

プロジェクトの規模やチームの慣習で変わりますが、以下が典型的な構成です。

1. 機能概要

対象の機能が何をするかを1〜3文で書きます。要件定義の該当箇所へのリンクがあるとなお良いです。

【機能概要】
ユーザーが商品をカートに追加し、数量を変更・削除できる機能。
未ログインユーザーはローカルストレージ、ログインユーザーはDB上のカートを使用する。

2. テーブル定義

基本設計のER図をもとに、カラムレベルまで落とし込みます。

【テーブル: cart_items】
| カラム名       | 型            | NULL | デフォルト    | 説明                  |
|---------------|--------------|------|-------------|----------------------|
| id            | BIGINT       | NO   | AUTO_INCREMENT | 主キー              |
| user_id       | BIGINT       | NO   | -           | users.id への外部キー |
| product_id    | BIGINT       | NO   | -           | products.id への外部キー |
| quantity      | INT          | NO   | 1           | 数量(1以上)         |
| created_at    | TIMESTAMP    | NO   | CURRENT_TIMESTAMP | 作成日時         |
| updated_at    | TIMESTAMP    | NO   | CURRENT_TIMESTAMP | 更新日時         |

【インデックス】
- UNIQUE (user_id, product_id) — 同一ユーザー×商品は1レコード
- INDEX (user_id) — ユーザー別カート取得用

【制約】
- quantity >= 1(CHECK制約)
- user_id → users(id) ON DELETE CASCADE
- product_id → products(id) ON DELETE RESTRICT

ポイントは、型と制約だけでなくなぜそうしたかを書くことです。 「ON DELETE CASCADE にした理由」「RESTRICT にした理由」が書かれていれば、レビューも保守もしやすくなります。

3. API仕様

エンドポイントごとにリクエスト・レスポンス・エラーケースを定義します。

【POST /api/cart/items】
カートに商品を追加する。既に同じ商品がある場合は数量を加算する。

リクエスト:
  Content-Type: application/json
  認証: 必須(Bearer Token)

  {
    "product_id": 123,
    "quantity": 2
  }

バリデーション:
  - product_id: 必須、整数、存在する商品ID
  - quantity: 必須、整数、1以上100以下

レスポンス(201 Created):
  {
    "id": 456,
    "product_id": 123,
    "quantity": 2,
    "created_at": "2025-01-15T10:30:00Z"
  }

レスポンス(200 OK)— 既存商品の数量加算時:
  {
    "id": 456,
    "product_id": 123,
    "quantity": 5,
    "updated_at": "2025-01-15T10:35:00Z"
  }

エラーレスポンス:
  - 400: バリデーションエラー
  - 401: 未認証
  - 404: 商品が存在しない
  - 409: 在庫上限を超える数量

4. 処理フロー

主要なロジックはシーケンス図やフローチャートで可視化します。 すべての処理を図にする必要はなく、分岐が多い処理や複数のシステムが連携する処理が対象です。

5. 画面項目定義

フロントエンドの入力フォームがある場合、項目ごとの制約を定義します。

【カート追加ダイアログ】
| 項目名   | 入力形式        | 必須 | 制約                  | 初期値 |
|---------|---------------|------|----------------------|-------|
| 数量     | 数値ステッパー   | YES  | 1〜100の整数          | 1     |

【表示項目】
- 商品名、単価、小計(数量×単価)、在庫残数
- 在庫残数を超える数量は選択不可(ステッパー上限を在庫数に制限)

6. エラーハンドリング

API仕様にもエラーを書きますが、詳細設計ではエラー発生時の内部的な振る舞いまで定義します。

【エラーハンドリング方針】
- バリデーションエラー: リクエストのフィールドごとにエラーメッセージを返す
- 在庫不足: 現在の在庫数をレスポンスに含め、フロントで上限を再設定
- DB接続エラー: 500を返し、エラーログを出力(ユーザーにはDB情報を見せない)
- 楽観ロック衝突: 409を返し、フロントで再取得を促す

7. 設計判断の記録(ADR)

設計で迷った点や、他の案を検討して却下した理由を残します。 これは Architecture Decision Record(ADR)と呼ばれる考え方で、詳細設計書の中に簡易的に組み込むのが実用的です。

【設計判断】
■ 未ログインユーザーのカート保存先

  採用: ローカルストレージ
  却下: Cookie
  理由: Cookieは4KB制限があり、商品数が増えると溢れる。
       ローカルストレージなら5MB程度まで保持でき、
       ログイン時にサーバーへマージする方式で統一できる。

■ 同一商品追加時の振る舞い

  採用: 既存レコードの数量を加算(UPSERT)
  却下: エラーを返して手動で数量変更させる
  理由: ECサイトのUXとして「カートに入れる」を繰り返し押すのは一般的な操作。
       エラーにすると体験が悪い。

粒度の判断基準

「どこまで書くか」は永遠のテーマですが、実用的な基準は3つあります。

書くべきもの:

  • 複数人が関わる部分(API仕様、テーブル定義)
  • 仕様が複雑で口頭では伝わらない部分(分岐の多いビジネスロジック)
  • 設計判断の理由(なぜこの方式を選んだか)

書かなくていいもの:

  • フレームワークが自動でやること(Railsのルーティング規約に従うだけなら不要)
  • 自明な実装の詳細(「変数 total に小計を代入する」のような記述)
  • 一人で完結する使い捨てのスクリプト

判断に迷ったら「半年後にこの機能を修正する人が、このドキュメントなしで困るか?」を考えてください。困るなら書く、困らないなら書かない。

詳細設計書のアンチパターン

コードの日本語翻訳

❌ 悪い例:
1. product_idを変数に代入する
2. cart_itemsテーブルをSELECTする
3. レコードが存在すればquantityを加算してUPDATEする
4. 存在しなければINSERTする

これはコードを日本語に置き換えただけで、設計の意図が何も伝わりません。 実装が変わるたびにドキュメントの更新が必要になり、すぐに乖離します。

⭕ 良い例:
同一ユーザー×同一商品のカートアイテムはUPSERT方式で処理する。
既存レコードがあれば数量を加算し、なければ新規作成する。
数量の上限(100個)を超える場合は409エラーを返す。

「何をするか」と「なぜそうするか」が分かれば十分です。

実装と乖離した設計書

設計書を書いた時点と実装が異なることは普通に起こります。 問題は「乖離を放置すること」です。

対策としては、設計書をGitリポジトリで管理し、実装PRと一緒に設計書の更新も含めるのが最もシンプルです。 Markdownで書いておけば差分レビューもしやすくなります。

形式主義

「テンプレートのすべての項目を埋めなければならない」という運用は、形式を守ること自体が目的化しています。 テンプレートはガイドラインであって、該当しない項目は「N/A」や省略で構いません。

よくある疑問

ドキュメントツールは何を使うべき?

チームで統一されていればなんでもいいですが、選定基準はこの3つです。

  • 差分管理ができるか: GitHubで管理するならMarkdown、Confluenceならページ履歴
  • 図が描けるか: Mermaid対応のツールなら図もテキストで管理できる
  • 検索できるか: ローカルのWordファイルだと他のメンバーが見つけられない

設計書はいつ書くのか

学習者学習者

実装がもう動いてるんだけど、詳細設計書って後から書いてもいいの?

通常の開発フローは「詳細設計書 → スキーマ定義 → 実装 → テスト」の順です。詳細設計書はそもそも実装の前に書くもので、「何をどう作るか」を定義してから手を動かすのが本来の流れです。

ただし、既に動いているコードに対して後追いでドキュメントを作るのは現実としてよくあります。その場合、先にスキーマやテストを整理してから設計書を書くのは、むしろ合理的な判断です。実装を理解した上で書けるので精度も高くなります。

先生先生

大事なのは順番よりも「書くこと」そのものです。後追いでも、ないよりずっと良い。現場の状況に合わせて臨機応変に判断しましょう。

実装しながら設計を修正するのも正常なプロセスです。設計書は「一度書いたら変えないもの」ではなく「育てるもの」と考えてください。

個人開発でも詳細設計書は必要か

必須ではありませんが、テーブル定義とAPI仕様だけは書いておくと半年後の自分が助かります。 最低限この2つがあれば、コードを読まなくてもシステムの構造が把握できます。

ちゃんと使うためのポイント

  • 詳細設計書の目的は「実装者がコードを書ける状態にすること」と「判断を記録すること」
  • 書くべきは「仕様」「制約」「設計判断の理由」で、コードの翻訳ではない
  • 粒度の基準は「半年後に修正する人が、このドキュメントなしで困るか?」
  • テンプレートは埋めることが目的ではなく、観点の漏れを防ぐためのガイドライン
  • 設計書はGit管理し、実装PRと一緒に更新する運用が最もシンプル

次の章では、詳細設計の中でも最初に取り組むことが多いER図とテーブル設計を解説します。