0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【リファクタリング】第16回 発散的変更(Divergent Change)

Posted at

はじめに

発散的変更(Divergent Change) とは、
1つのクラスを修正するたびに、異なる理由で何度も変更が必要になる」状態を指します。

例:

  • 新しいビジネスルールの導入で 計算ロジック を変更
  • 表示要件の追加で フォーマット処理 を変更
  • 永続化の仕様変更で DB アクセス を変更

1つのクラスが多すぎる責務を抱えているサイン


16.1 特徴

  • クラスが 複数の理由で頻繁に変更 される
  • 異なる関心事(UI, ビジネスロジック, DB)が同居
  • 修正のたびに テスト範囲が広くなり、リスクが増大
  • 単一責任原則(SRP)違反 の典型例

16.2 解決手法

  • クラス抽出(Extract Class)
    → 責務ごとにクラスを分ける
  • レイヤー化アーキテクチャ
    → UI / ビジネスロジック / データアクセス を分離
  • 委譲(Delegation)
    → 特定の処理を別オブジェクトに任せる
  • Clean Architecture / DDD の導入
    → 関心ごとごとにレイヤーやドメインオブジェクトを整理

16.3 Kotlin 例

Before:発散的変更を抱えるクラス

class OrderProcessor {
    fun process(order: Order) {
        // ビジネスロジック
        val total = order.items.sumOf { it.price }
        val discount = if (order.customer.level > 3) total * 0.9 else total

        // フォーマット処理
        val message = "Order for ${order.customer.name}, total=$discount"

        // 永続化
        saveToDatabase(order, discount)

        // 通知
        println("Sending email: $message")
    }

    private fun saveToDatabase(order: Order, total: Double) {
        println("Saving order with total=$total to DB")
    }
}
  • 1クラスに 計算 / フォーマット / DB保存 / 通知 が混在
  • どの要件変更でも OrderProcessor を修正する必要がある

After:責務ごとに分離

class PricingService {
    fun calculate(order: Order): Double {
        val total = order.items.sumOf { it.price }
        return if (order.customer.level > 3) total * 0.9 else total
    }
}

class OrderRepository {
    fun save(order: Order, total: Double) {
        println("Saving order with total=$total to DB")
    }
}

class NotificationService {
    fun notifyCustomer(order: Order, total: Double) {
        val message = "Order for ${order.customer.name}, total=$total"
        println("Sending email: $message")
    }
}

class OrderProcessor(
    private val pricing: PricingService,
    private val repository: OrderRepository,
    private val notifier: NotificationService
) {
    fun process(order: Order) {
        val total = pricing.calculate(order)
        repository.save(order, total)
        notifier.notifyCustomer(order, total)
    }
}

→ 各関心事を独立クラスに切り出し、変更理由を分離
→ どの要件変更でも 修正範囲が局所化


16.4 実務での指針

  • クラスが 複数の理由で変更されるなら SRP 違反 を疑う
  • 「このクラスを変更する理由は何か?」を問い直す
  • 1クラス = 1責務 を目指す
  • アプリ全体の構造としては レイヤー分離(UI / Domain / Data) を徹底
  • Kotlin では 委譲 / data class / interface を活用し、関心ごとを明確化

まとめ

  • 発散的変更(Divergent Change) は「1クラスが複数の変更理由を抱えている」悪臭
  • 解決策は Extract Class / レイヤー分離 / 委譲
  • 基本思想:クラスは 1つの変更理由だけ を持つべき

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?