責任とは何か

ソフトウェアにおける「責任」の定義

ソフトウェア設計における「責任」とは、あるコード単位が果たすべき役割や目的のことです。これは関数、クラス、モジュール、サービスなど、あらゆるレベルで適用される概念です。

責任を理解する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つの責任のみを持つ
  • 関心の分離:異なる関心事は異なる場所で扱う
  • 抽象化:詳細ではなく抽象に依存する
  • モジュール性:独立して理解・変更・テスト可能な単位を作る