機能詳細設計書とは
基本設計が「何を作るか」を定義するのに対し、機能詳細設計書は「どう動くか」を定義するドキュメントだ。 実装者がこのドキュメントだけを見てコードを書けるレベルの精度が求められる。
機能詳細設計書が不十分だと、実装者がロジックを独自解釈して仕様齟齬が発生する。 特に条件分岐・例外処理・排他制御の記述が曖昧なプロジェクトは、後工程のテストで大量のバグが発生しやすい。
💡 基本設計との違い
基本設計の機能一覧・画面一覧が「機能の存在とインターフェース」を定義するのに対し、機能詳細設計書は「その機能の内部処理ロジック」を定義する。基本設計書の機能IDと1対1で対応させるのが原則。
定義すべき項目一覧
機能詳細設計書に必ず記載すべき項目を以下に示す。
| 分類 | 定義項目 | 必須度 | 説明 |
|---|---|---|---|
| 基本情報 | 機能ID / 機能名 | ◎ | 基本設計の機能一覧と紐付ける識別子 |
| 基本情報 | 対象画面 / API | ◎ | この機能が紐づく画面・エンドポイント |
| 基本情報 | 処理概要 | ◎ | 3〜5行で何をする機能かを説明 |
| 基本情報 | 作成者 / 更新日 | ◎ | バージョン管理の起点 |
| 前提条件 | 事前条件(Pre-condition) | ◎ | この処理が実行される前提となる状態 |
| 前提条件 | 事後条件(Post-condition) | ◎ | 処理が正常終了した後の状態 |
| 処理フロー | 主フロー(Happy Path) | ◎ | 正常系の処理手順(番号付き) |
| 処理フロー | 代替フロー(Alternative) | ◎ | 条件分岐で生じる別経路 |
| 処理フロー | 例外フロー(Exception) | ◎ | エラー・異常発生時の処理 |
| 業務ロジック | 計算式・変換ロジック | ◎ | 金額計算・コード変換など数式や変換ルール |
| 業務ロジック | バリデーションルール | ◎ | 入力値の検証条件(型・桁数・必須・相関) |
| 業務ロジック | コード値の意味 | ○ | ステータスコード・区分コードの意味定義 |
| DB操作 | 参照テーブル / 更新テーブル | ◎ | CRUD操作の対象テーブルを全件列挙 |
| DB操作 | WHERE条件 / JOIN条件 | ○ | 複雑なクエリは条件を明示 |
| 排他制御 | 楽観ロック / 悲観ロック | ○ | 同時更新制御の方式 |
| 非機能 | パフォーマンス要件 | △ | 応答時間・件数上限など |
| 非機能 | ログ出力要件 | △ | 出力するログレベル・項目 |
処理フローの記述方法
処理フローは番号付きのステップで記述する。条件分岐が生じる箇所は「ステップ番号.条件」形式でインデントして記述するのが一般的だ。
以下は注文確定機能の処理フロー記述例だ。
【主フロー】
1. リクエストのJWTトークンを検証する
1a. 検証失敗 → 例外フロー[E01]へ
2. 注文IDが存在するかを orders テーブルで確認する
2a. 存在しない → 例外フロー[E02]へ
3. 注文ステータスが「確認待ち(STATUS=10)」であることを確認する
3a. STATUS≠10 → 例外フロー[E03]へ
4. 在庫テーブル(inventories)から各明細の在庫数を取得し、
注文数量と照合する
4a. 在庫不足の明細が1件でも存在する → 例外フロー[E04]へ
5. トランザクション開始
6. inventories.stock を注文数量分デクリメントする
7. orders.status を「確定済み(STATUS=20)」に更新する
8. order_histories に処理ログを INSERT する
9. コミット
10. 確定完了メール送信キューに enqueue する
11. レスポンス(200 OK, 確定済み注文情報)を返す
【例外フロー】
E01: 401 Unauthorized を返す。ログ出力:WARN
E02: 404 Not Found を返す。ログ出力:INFO
E03: 409 Conflict("注文はすでに処理済みです")を返す。ログ出力:WARN
E04: 409 Conflict("在庫不足:商品ID={id} 不足数={n}")を返す。
ロールバック。ログ出力:WARN
✅ フロー記述のポイント
① ステップは動詞で始める(「確認する」「取得する」「更新する」)。② 条件分岐は必ずインデントして例外フローと紐付ける。③ DBのテーブル名・カラム名・コード値は実際の物理名で記述する。実装者が設計書と実装を1:1で対応できる粒度が理想だ。
業務ロジックの定義
計算式・変換ルール・ステータス遷移など、実装者が判断を委ねてはならないロジックはすべて明文化する。 特に金額計算・税率・丸め処理は実装者によって解釈が異なりやすいため、必ず式で記述する。
■ 請求金額計算ロジック
小計 = Σ(単価 × 数量) ※小数点以下は切り捨て(FLOOR)
割引額 = 小計 × 割引率 ※割引率は members.discount_rate(小数:0.00〜0.30)
※小数点以下は切り捨て(FLOOR)
税込前 = 小計 - 割引額
消費税 = 税込前 × 0.10 ※小数点以下は切り捨て(FLOOR)
合計 = 税込前 + 消費税
■ ステータス遷移図
10(確認待ち)
→ [注文確定API] → 20(確定済み)
→ [注文キャンセルAPI] → 90(キャンセル)
20(確定済み)
→ [出荷登録API] → 30(出荷済み)
→ [注文キャンセルAPI] → ※不可(E03)
30(出荷済み)
→ [配達完了Webhook] → 40(完了)
■ コード値定義(orders.status)
10: 確認待ち(初期値。注文作成時に自動設定)
20: 確定済み(在庫確保済み)
30: 出荷済み(倉庫で出荷処理完了)
40: 完了(配達完了確認済み)
90: キャンセル(確認待ち状態でのみ遷移可)
例外・エラー処理の定義
例外処理の記述が甘いと、テスト工程で異常系のバグが大量発生する。以下の観点を漏らさず定義すること。
| 観点 | 定義すべき内容 |
|---|---|
| 入力エラー | バリデーション違反時のレスポンスコード・メッセージID・ログレベル |
| 業務エラー | 在庫不足・残高不足など業務上の制約違反時の処理 |
| システムエラー | DB接続エラー・タイムアウト時のリトライ回数・フォールバック処理 |
| 外部サービスエラー | 外部API障害時の振る舞い(代替処理・キューイング・サーキットブレーカー) |
| ロールバック | トランザクション中断時にロールバックが必要な操作の列挙 |
| 通知 | アラート・メール通知が必要なエラーの条件と宛先 |
⚠️ よくある記述漏れ
「エラー時は適切に処理する」「例外が発生した場合はエラーを返す」のような抽象的な記述では設計書として機能しない。エラーコード・HTTPステータス・ログレベル・リトライ回数を具体的に記述すること。
排他制御・トランザクション
複数ユーザーが同一データを操作する機能では、排他制御の方式を必ず定義する。 方式を選択しないと、実装者がそれぞれ独自の制御を行い不整合が発生する。
| 方式 | 仕組み | 採用場面 | 実装例 |
|---|---|---|---|
| 楽観ロック | 更新時にバージョン番号を照合。不一致なら競合エラー | 競合頻度が低い・ユーザー操作待ちが長い | WHERE id=? AND version=? |
| 悲観ロック | 取得時にDBロックを取得。他からの更新をブロック | 競合頻度が高い・バッチ処理 | SELECT ... FOR UPDATE |
| べき等設計 | 同一リクエストを複数回送っても結果が変わらない設計 | API・非同期処理の重複実行対策 | 冪等キー(idempotency_key)管理 |
Python Tips — ソースから処理フローを抽出
既存システムをリプレイスする場合、旧システムのソースコードから処理フローを逆引きするケースがある。 Pythonのastモジュールを使うと、Pythonコードの関数定義・呼び出し関係を自動で抽出できる。
"""
既存Pythonソースから関数定義・呼び出し関係を抽出して
処理フロー把握の補助資料を生成するスクリプト
"""
import ast
import os
from pathlib import Path
from collections import defaultdict
def extract_function_calls(filepath: str) -> dict:
"""Pythonファイルから関数定義と内部呼び出しを抽出"""
with open(filepath, encoding="utf-8") as f:
source = f.read()
try:
tree = ast.parse(source)
except SyntaxError as e:
print(f"構文エラー: {filepath} — {e}")
return {}
result = {}
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
func_name = node.name
calls = []
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name):
calls.append(child.func.id)
elif isinstance(child.func, ast.Attribute):
calls.append(f"{child.func.attr}")
result[func_name] = {
"line": node.lineno,
"calls": list(set(calls)),
"is_async": isinstance(node, ast.AsyncFunctionDef),
}
return result
def scan_directory(target_dir: str, pattern: str = "*.py") -> dict:
"""ディレクトリ配下の全Pythonファイルをスキャン"""
all_funcs = defaultdict(dict)
for path in Path(target_dir).rglob(pattern):
rel = str(path.relative_to(target_dir))
funcs = extract_function_calls(str(path))
if funcs:
all_funcs[rel] = funcs
return dict(all_funcs)
def print_call_tree(scan_result: dict) -> None:
"""処理フロー一覧を標準出力"""
for filepath, funcs in scan_result.items():
print(f"\n📄 {filepath}")
for fname, info in funcs.items():
prefix = "async " if info["is_async"] else ""
print(f" {'⚡' if info['is_async'] else '🔹'} {prefix}{fname}() [L{info['line']}]")
for call in info["calls"]:
print(f" └─ calls: {call}()")
# ── 使用例 ────────────────────────────────────────────────
if __name__ == "__main__":
TARGET = "./src" # 解析対象ディレクトリ
result = scan_directory(TARGET)
print_call_tree(result)
# CSVに出力して設計書作成の補助資料にする
import csv
with open("function_flow.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["ファイル", "関数名", "非同期", "行番号", "呼び出し先"])
for fpath, funcs in result.items():
for fname, info in funcs.items():
writer.writerow([
fpath, fname,
"YES" if info["is_async"] else "NO",
info["line"],
" / ".join(info["calls"])
])
このスクリプトを実行すると function_flow.csv が生成され、
どの関数がどの関数を呼び出しているかが一覧化される。
ただし動的なメソッド呼び出し(getattr 経由など)は検出できないため、静的解析の補助資料として使うこと。
"""
Javaソースコードから「メソッド名 → SQL操作対象テーブル」の
マッピングを抽出するスクリプト(正規表現ベース)
既存Javaシステムのリプレイス時に処理フローの草稿を作る補助に使う
"""
import re
from pathlib import Path
# SQL操作を検出する正規表現
SQL_PATTERNS = {
"SELECT": re.compile(r"(?i)\bFROM\s+([`\"\[]?[\w]+[`\"\]]?)", re.MULTILINE),
"INSERT": re.compile(r"(?i)\bINSERT\s+INTO\s+([`\"\[]?[\w]+[`\"\]]?)", re.MULTILINE),
"UPDATE": re.compile(r"(?i)\bUPDATE\s+([`\"\[]?[\w]+[`\"\]]?)", re.MULTILINE),
"DELETE": re.compile(r"(?i)\bDELETE\s+FROM\s+([`\"\[]?[\w]+[`\"\]]?)", re.MULTILINE),
}
METHOD_PATTERN = re.compile(
r"(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+([\w]+)\s*\([^)]*\)\s*\{",
re.MULTILINE
)
def extract_java_method_sql(filepath: str) -> dict:
with open(filepath, encoding="utf-8", errors="ignore") as f:
content = f.read()
# メソッドごとに分割(簡易)
method_map = {}
methods = list(METHOD_PATTERN.finditer(content))
for i, m in enumerate(methods):
method_name = m.group(1)
start = m.end()
end = methods[i + 1].start() if i + 1 < len(methods) else len(content)
body = content[start:end]
tables = {}
for op, pat in SQL_PATTERNS.items():
found = [t.group(1).lower() for t in pat.finditer(body)]
if found:
tables[op] = list(set(found))
if tables:
method_map[method_name] = tables
return method_map
# ── 使用例 ────────────────────────────────────────────────
if __name__ == "__main__":
import json
result = {}
for java_file in Path("./java-src").rglob("*.java"):
m = extract_java_method_sql(str(java_file))
if m:
result[str(java_file)] = m
print(json.dumps(result, ensure_ascii=False, indent=2))
✅ 活用シーン
レガシーシステムのリプレイス案件では、旧システムのソースコードから「どの機能がどのテーブルを操作しているか」を把握するだけでも詳細設計の工数が大幅に削減できる。SQLパターンマッチは100%ではないが、見逃しのスクリーニングとして非常に有効だ。
レビューチェックリスト
機能詳細設計書のレビュー前に以下を確認すること。
| # | チェック項目 |
|---|---|
| 1 | 機能IDが基本設計の機能一覧と一致しているか |
| 2 | 主フロー・代替フロー・例外フローの全経路が網羅されているか |
| 3 | 条件分岐の条件式が具体的な値・コード・式で記述されているか |
| 4 | 計算式・丸め方式・税率が明示されているか |
| 5 | DB操作対象テーブルが全件列挙されているか(参照・更新・挿入・削除) |
| 6 | 例外フローにHTTPステータス・メッセージID・ログレベルが記載されているか |
| 7 | 排他制御の方式(楽観/悲観/べき等)が定義されているか |
| 8 | 外部サービス呼び出しのタイムアウト・リトライ方針が定義されているか |
| 9 | 非機能要件(応答時間・上限件数)への言及があるか |
| 10 | テスト担当者が読んでテストケースを設計できる粒度か |