クラスの責務を聞かれて一言で答えられない――そんな設計に何度も遭遇してきた。モジュール構成図とクラス設計書は、実装が始まる前にその「一言」を明文化しておくための資料だと思っている。
クラス設計書の位置づけ
図1: 詳細設計その他ドキュメント(クラス/モジュール設計・シーケンス設計・単体テスト設計)
クラス設計書はシーケンス図・API設計書と連携して「どのクラスがどのメソッドを持ち何を担うか」を定義する。実装前にこれをレビューすることで設計上の問題を早期に発見できる。
💡 単一責任の原則(SRP)
各クラスは「1つの責任」だけを持つように設計する。クラスの責務を1文で表現できない場合は分割を検討する。
定義すべき項目一覧
| 分類 | 定義項目 | 必須度 |
|---|---|---|
| モジュール | パッケージ/モジュール構成・階層 | ◎ |
| クラス基本 | クラス名・スーパークラス・実装インターフェース | ◎ |
| クラス基本 | クラスの責務(1文で表現) | ◎ |
| 属性 | 属性名・型・可視性・説明 | ◎ |
| メソッド | メソッド名・引数・戻り値・可視性・処理概要 | ◎ |
| 依存関係 | 依存するクラス・関係種別(集約/合成/使用) | ◎ |
| 設計パターン | 適用するデザインパターン | ○ |
モジュール構成図
com.example
├── api/ コントローラー層
│ ├── OrderController
│ └── UserController
├── service/ ビジネスロジック層
│ ├── OrderService
│ └── StockService
├── repository/ データアクセス層
│ └── OrderRepository
├── domain/ ドメインモデル
│ ├── Order
│ └── Product
└── exception/ カスタム例外
└── BusinessException
依存方向の原則:
api -> service -> repository -> domain
クラス定義
| クラス名 | 責務 | スーパークラス | 実装IF |
|---|---|---|---|
| OrderService | 注文の確定・キャンセル・履歴照会のビジネスロジックを担う | — | IOrderService |
| StockService | 在庫の照会・引当・補充のビジネスロジックを担う | — | IStockService |
| OrderRepository | orders テーブルのCRUD操作を担う | BaseRepository | IOrderRepository |
メソッド定義
class OrderService:
+ confirmOrder(request: OrderRequest): OrderResponse
引数: OrderRequest(userId, items)
戻値: OrderResponse(orderId, totalAmount)
処理: 1.在庫確認 2.注文レコード作成 3.在庫引当 4.通知送信
例外: BizException(BIZ_001) 在庫不足時
+ cancelOrder(orderId: long, userId: long): void
処理: 1.注文存在確認 2.権限確認 3.ステータス確認 4.引当解除
例外: ResourceNotFoundException / BizException(BIZ_002)
依存関係定義
| クラス | 依存先 | 関係 | 説明 |
|---|---|---|---|
| OrderController | OrderService | 使用(DI) | コントローラーがサービスを呼び出す |
| OrderService | OrderRepository | 使用(DI) | サービスがリポジトリを通じてDB操作 |
| OrderService | StockService | 使用(DI) | 注文時に在庫確認・引当を委譲 |
| Order | OrderItem | 合成 | Orderは1以上のOrderItemを保有 |
設計パターンの適用
| パターン | 適用箇所 | 目的 |
|---|---|---|
| Repository パターン | *Repository クラス | データアクセス実装をビジネスロジックから分離 |
| Dependency Injection | 全サービスクラス | テスタビリティ向上・疎結合の実現 |
| Factory パターン | OrderFactory | 注文オブジェクトの生成ロジックを集約 |
Python Tips — import 依存関係の自動解析
import ast
from pathlib import Path
from collections import defaultdict
def extract_imports(filepath):
try:
source = Path(filepath).read_text(encoding="utf-8")
tree = ast.parse(source)
except Exception:
return []
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
imports.append(alias.name.split(".")[0])
elif isinstance(node, ast.ImportFrom):
if node.module:
imports.append(node.module.split(".")[0])
return imports
def analyze_project(project_dir):
deps = defaultdict(set)
py_files = list(Path(project_dir).rglob("*.py"))
project_modules = {f.stem for f in py_files}
for py_file in py_files:
module_name = py_file.stem
for imp in extract_imports(str(py_file)):
if imp in project_modules and imp != module_name:
deps[module_name].add(imp)
return dict(deps)
# 実行例
deps = analyze_project(".")
print("# モジュール依存関係")
for module, depends_on in sorted(deps.items()):
if depends_on:
print(f" {module:30s} -> {', '.join(sorted(depends_on))}")
レビューチェックリスト
| # | チェック項目 |
|---|---|
| 1 | 各クラスの責務が1文で表現できるか(単一責任) |
| 2 | 循環依存が発生していないか |
| 3 | 依存の方向が上位層→下位層に統一されているか |
| 4 | 全メソッドに引数・戻り値・処理概要が定義されているか |
| 5 | テストしやすい設計(DI・インターフェース活用)か |