画面詳細設計書の位置づけ

基本設計の画面設計書は「どんなUI要素があるか」を定義するが、画面詳細設計書は「各UI要素がどう動くか」を定義する。 フロントエンドとバックエンドの両方の実装者が読んで実装に迷わないレベルの記述が求められる。

画面詳細設計書の作成が甘いと、フロントとバックで異なるバリデーション実装が行われ、テスト工程で大量の不整合が発見される。 特に「相関バリデーション」と「表示制御ロジック」は後から追いにくいため、詳細設計段階で必ず定義する。

💡 基本設計書との分担

基本設計:画面レイアウト・画面遷移図・主な入出力項目の洗い出し。詳細設計:各項目の型・桁数・必須/任意・バリデーション式・イベント処理・表示条件。詳細設計書は基本設計書の画面IDと1対1で対応させる。

入力項目定義

画面に存在するすべての入力要素(テキスト・セレクト・チェックボックス・ラジオ・ファイルなど)を以下の属性で定義する。

属性名説明記述例
項目IDHTML の name 属性に対応する識別子user_name
論理名日本語での項目名ユーザー名
データ型文字列・数値・日付・ファイルなど文字列(VARCHAR)
桁数最大文字数・最小文字数1〜50文字
必須/任意必須(◎)・任意(○)・条件付き必須(△)◎ 必須
初期値初期表示時の値空欄 / 現在日付 / ログインユーザーID
入力形式許容する文字種・フォーマット英数字・記号(半角のみ)
UI種別テキスト/パスワード/セレクト/チェックボックス等type="text"
活性/非活性条件項目が入力可/不可になる条件権限が管理者ロールの場合のみ入力可
表示/非表示条件項目が表示される条件区分コード=01 の場合のみ表示
マスク表示パスワード等のマスク要否入力後に ******* でマスク
DB連携保存先のテーブル名・カラム名users.username

バリデーションルール定義

バリデーションは「単項目チェック」と「相関チェック」に分けて定義する。

バリデーション定義例(会員登録画面)
■ 単項目バリデーション

  [user_name] ユーザー名
    VAL-001: 必須チェック → "ユーザー名は必須です"
    VAL-002: 文字数 1〜50文字 → "ユーザー名は1〜50文字で入力してください"
    VAL-003: 使用可文字 半角英数字・アンダースコア・ハイフン
             正規表現: ^[a-zA-Z0-9_\-]+$
             エラー: "ユーザー名は英数字・アンダースコア・ハイフンのみ使用できます"
    VAL-004: 重複チェック(DB照合)users.username と重複禁止
             エラー: "このユーザー名はすでに使用されています"

  [email] メールアドレス
    VAL-010: 必須チェック → "メールアドレスは必須です"
    VAL-011: フォーマットチェック
             正規表現: ^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$
             エラー: "メールアドレスの形式が正しくありません"
    VAL-012: 重複チェック users.email と重複禁止
             エラー: "このメールアドレスはすでに登録されています"

  [password] パスワード
    VAL-020: 必須チェック
    VAL-021: 文字数 8〜72文字
             エラー: "パスワードは8〜72文字で入力してください"
    VAL-022: 複雑性 大文字・小文字・数字・記号を各1文字以上含む
             エラー: "パスワードは大文字・小文字・数字・記号を含む必要があります"

  [birth_date] 生年月日
    VAL-030: 日付形式チェック YYYY-MM-DD
    VAL-031: 範囲チェック 1900-01-01 以降・今日以前
             エラー: "正しい生年月日を入力してください"

■ 相関バリデーション

  COR-001: [password] と [password_confirm] が一致すること
           エラー: "パスワードが一致しません"

  COR-002: [start_date] ≤ [end_date] であること(期間指定の場合)
           エラー: "終了日は開始日以降を指定してください"

  COR-003: [payment_method]=クレジットカードのとき [card_number] は必須
           エラー: "クレジットカード番号を入力してください"

⚠️ バリデーションIDは必ず振る

VAL-001 のようにバリデーションIDを振っておくことで、テスト仕様書・バグ票との紐付けが容易になる。「エラーが出た」ではなく「VAL-022 が発火した」と言えると問題特定が格段に速い。

イベント・ボタン処理定義

ボタンクリック・フォーカスアウト・選択変更など、ユーザー操作に紐づくイベントをすべて定義する。

イベントIDトリガー処理内容遷移先
EVT-001「登録」ボタンクリック① 単項目バリデーション実行
② 相関バリデーション実行
③ 確認ダイアログ表示
④ 会員登録API(POST /api/members)呼出
⑤ 成功時:完了画面へ遷移
⑥ 失敗時:エラーメッセージ表示(画面保持)
完了画面(screen-complete)/ エラー表示
EVT-002「戻る」ボタンクリック入力内容を破棄して前画面へ戻る(確認なし)前画面(ブラウザバック)
EVT-003[prefecture] 選択変更選択した都道府県に紐づく市区町村をAPI(GET /api/cities?pref={code})で取得し [city] セレクトボックスを再描画画面保持(動的更新)
EVT-004[email] フォーカスアウトメール形式の即時チェック(VAL-011)を実行し、不正な場合はフィールド下にエラーメッセージをインライン表示画面保持

表示制御ロジック

条件によって表示・非表示・活性・非活性が変化する要素は、条件を明文化する。 実装者任せにすると表示漏れ・制御ミスが頻発する。

表示制御ロジック定義例
■ 表示/非表示制御

  [corporate_section](法人情報欄)
    表示条件: [member_type] = "法人"(member_type=02)
    非表示時: DOM から削除(display:none)ではなく hidden 属性を使用

  [admin_note](管理者備考欄)
    表示条件: ログインユーザーのロールが ADMIN or SUPER_ADMIN
    非表示時: レンダリング自体しない(サーバーサイドで制御)

■ 活性/非活性制御

  [submit_button](登録ボタン)
    非活性条件: 必須項目(VAL-001, VAL-010, VAL-020)のいずれかが未入力
    活性化タイミング: 必須項目の全入力確認時(onBlur ごとにチェック)

  [city](市区町村セレクト)
    初期状態: 非活性(disabled)
    活性化タイミング: [prefecture] が選択されたとき(EVT-003 完了後)

■ 初期表示データ取得

  [user_id]         : URLパラメータから取得(/screen/edit?id={user_id})
  [prefecture]      : マスターAPI(GET /api/prefectures)で取得・キャッシュ(1時間)
  [created_at]      : 編集モード時のみ表示。API(GET /api/members/{id})から取得

出力項目定義

画面に表示する出力項目も入力項目と同様に定義する。 特に「どのAPIから取得するか」「取得できない場合の表示」を明記することが重要だ。

項目ID論理名取得元フォーマットnull時の表示
order_date注文日時orders.created_atYYYY年MM月DD日 HH:mm「−」表示
total_amount合計金額計算値(明細合計)¥#,###,###(3桁区切り)¥0
status_labelステータス名称orders.status → コード変換STATUS=10→「確認待ち」(黄色バッジ)「不明」(グレー)

Python Tips — HTMLからフォーム要素を自動抽出

既存のHTMLファイルやプロトタイプHTMLから、入力項目の属性を自動抽出して設計書の草稿を作成できる。 BeautifulSoup を使った実装例を示す。

Python — HTMLフォームから入力項目定義を自動抽出
"""
HTMLファイルまたはURLからフォーム要素を抽出し、
画面詳細設計書の「入力項目定義」草稿をCSVで出力するスクリプト
"""
from bs4 import BeautifulSoup
import requests
import csv
import sys

def extract_form_elements(html: str) -> list[dict]:
    """フォーム要素を抽出して属性情報を辞書リストで返す"""
    soup = BeautifulSoup(html, "lxml")
    rows = []

    # input, select, textarea を対象
    targets = soup.find_all(["input", "select", "textarea"])

    for el in targets:
        tag = el.name
        input_type = el.get("type", "text") if tag == "input" else tag
        name = el.get("name", el.get("id", "(名前なし)"))
        label_el = soup.find("label", attrs={"for": el.get("id", "")})
        label_text = label_el.get_text(strip=True) if label_el else ""

        # バリデーション属性を収集
        validations = []
        if el.get("required") is not None:
            validations.append("必須")
        maxlen = el.get("maxlength") or el.get("max")
        minlen = el.get("minlength") or el.get("min")
        if maxlen:
            validations.append(f"最大{maxlen}")
        if minlen:
            validations.append(f"最小{minlen}")
        pattern = el.get("pattern")
        if pattern:
            validations.append(f"pattern={pattern}")

        # セレクトの選択肢を抽出
        options = ""
        if tag == "select":
            opts = el.find_all("option")
            options = " / ".join(
                f"{o.get('value','')}:{o.get_text(strip=True)}"
                for o in opts if o.get("value")
            )

        rows.append({
            "項目ID":       name,
            "論理名":       label_text,
            "UI種別":       input_type,
            "必須":         "◎" if "必須" in validations else "○",
            "桁数/範囲":   f"{minlen}〜{maxlen}" if (minlen or maxlen) else "",
            "パターン":     pattern or "",
            "選択肢":       options,
            "初期値":       el.get("value", el.get("placeholder", "")),
            "その他属性":   " ".join(validations),
        })

    return rows


def main(source: str) -> None:
    # URLかファイルパスを判定
    if source.startswith("http"):
        resp = requests.get(source, timeout=10)
        resp.encoding = resp.apparent_encoding
        html = resp.text
    else:
        with open(source, encoding="utf-8") as f:
            html = f.read()

    rows = extract_form_elements(html)

    out_path = "screen_input_items.csv"
    fieldnames = ["項目ID","論理名","UI種別","必須","桁数/範囲","パターン","選択肢","初期値","その他属性"]
    with open(out_path, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

    print(f"{len(rows)} 件の入力項目を {out_path} に出力しました。")


# ── 実行 ───────────────────────────────────────────────────
if __name__ == "__main__":
    # 引数なしの場合はサンプルHTMLで動作確認
    if len(sys.argv) < 2:
        sample = """
        <form>
          <label for="email">メールアドレス</label>
          <input id="email" name="email" type="email" required maxlength="100"
                 pattern="[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}" />
          <label for="age">年齢</label>
          <input id="age" name="age" type="number" min="18" max="120" />
          <label for="pref">都道府県</label>
          <select id="pref" name="prefecture" required>
            <option value="">選択してください</option>
            <option value="13">東京都</option>
            <option value="27">大阪府</option>
          </select>
        </form>
        """
        rows = extract_form_elements(sample)
        for r in rows:
            print(r)
    else:
        main(sys.argv[1])

実務での使い方

プロトタイプのHTMLを渡すだけで入力項目一覧のCSV草稿が生成できる。これを Excel に貼り付けてバリデーション列を追記するだけで設計書の80%が完成する。HTMLに requiredmaxlengthpattern をきちんと書いてあれば、精度はさらに高まる。

レビューチェックリスト

#チェック項目
1すべての入力項目に「必須/任意」「型」「桁数」が定義されているか
2相関バリデーション(複数項目間の制約)がすべて記載されているか
3バリデーションIDがメッセージ定義書と紐付いているか
4ボタン押下時の処理(API呼び出し・遷移先・エラー時の振る舞い)が定義されているか
5表示/非表示・活性/非活性の条件が具体的に記述されているか
6セレクトボックス・ラジオボタンの選択肢とそのコード値が定義されているか
7出力項目のnull時・0件時の表示が定義されているか
8入力項目とDBカラムの対応が明記されているか