責任分離の基礎
あらゆるスケールで適用される責任分離の基本概念を理解する
責任とは何か
ソフトウェアにおける「責任」の定義
ソフトウェア設計における「責任」とは、あるコード単位が果たすべき役割や目的のことです。これは関数、クラス、モジュール、サービスなど、あらゆるレベルで適用される概念です。
責任を理解する3つの視点
- 機能的視点:何をするコードか?
- 変更の視点:どんな理由で変更されるか?
- 依存の視点:誰が(何が)このコードを使うか?
なぜ責任を分離するのか
責任の分離は、コードの品質と開発効率に直接的な影響を与えます。
混在した責任
理解困難
変更困難
テスト困難
→
分離された責任
理解容易
変更容易
テスト容易
スケールによる責任の違い
同じ原則、異なる適用
責任分離の原則は一貫していますが、適用するスケールによって具体的な方法が異なります。
🔧 関数スケール
// 責任:計算のみ
function calculatePrice(basePrice, taxRate) {
return basePrice * (1 + taxRate);
}
// 責任:フォーマットのみ
function formatCurrency(amount) {
return `¥${amount.toLocaleString()}`;
}
// 使用時に組み合わせる
const price = calculatePrice(1000, 0.1);
const display = formatCurrency(price);
📦 クラススケール
// 責任:商品情報の保持
class Product {
constructor(id, name, basePrice) {
this.id = id;
this.name = name;
this.basePrice = basePrice;
}
}
// 責任:価格計算のロジック
class PriceCalculator {
calculateWithTax(product, taxRate) {
return product.basePrice * (1 + taxRate);
}
}
📁 モジュールスケール
// pricingモジュール - 価格計算に関する全責任
export { PriceCalculator, TaxCalculator, DiscountService } from './pricing';
// inventoryモジュール - 在庫管理に関する全責任
export { StockChecker, InventoryUpdater, StockAlert } from './inventory';
🏗️ システムスケール
// 価格サービス - 価格計算のマイクロサービス
PricingService {
API: /api/pricing/*
責任: 全ての価格計算ロジック
}
// 在庫サービス - 在庫管理のマイクロサービス
InventoryService {
API: /api/inventory/*
責任: 全ての在庫管理機能
}
責任の境界を見つける方法
変更の理由による分析
Robert C. Martinの言葉:「クラスを変更する理由は1つでなければならない」
❌ 複数の変更理由
class Order {
calculateTotal() {
// 価格計算ロジック
}
saveToDatabase() {
// DB保存処理
}
sendEmail() {
// メール送信
}
generatePDF() {
// PDF生成
}
}
// 変更理由:
// 1. 価格計算方法の変更
// 2. DB構造の変更
// 3. メールフォーマットの変更
// 4. PDF様式の変更
✅ 単一の変更理由
class Order {
calculateTotal() {
// 価格計算のみ
}
}
class OrderRepository {
save(order) {
// DB保存のみ
}
}
class OrderNotifier {
sendEmail(order) {
// メール送信のみ
}
}
class OrderReporter {
generatePDF(order) {
// PDF生成のみ
}
}
凝集度と結合度
良い設計の2つの指標
高凝集(High Cohesion)
- 関連する機能が1つの単位にまとまっている
- 内部の要素が協力して1つの目的を達成
- 理解しやすく、再利用しやすい
疎結合(Loose Coupling)
- モジュール間の依存が最小限
- インターフェースを通じた抽象的な関係
- 変更の影響が局所的
// 高凝集の例:関連する機能がまとまっている
class ShoppingCart {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
}
calculateTotal() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
// 疎結合の例:インターフェースを通じた関係
interface PaymentProcessor {
process(amount: number): Promise;
}
class CheckoutService {
constructor(private paymentProcessor: PaymentProcessor) {}
async checkout(cart: ShoppingCart) {
const total = cart.calculateTotal();
return await this.paymentProcessor.process(total);
}
}
責任分離の基本原則
覚えておくべき原則
- 単一責任の原則:1つの単位は1つの責任のみを持つ
- 関心の分離:異なる関心事は異なる場所で扱う
- 抽象化:詳細ではなく抽象に依存する
- モジュール性:独立して理解・変更・テスト可能な単位を作る