はじめに
発散的変更(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つの変更理由だけ を持つべき