はじめに
リファクタリングの悪臭の一つに Duplicate Observed Data(重複した観測データ) があります。
これは 同じデータを複数の場所で保持し、更新のたびに同期処理が必要になる状態 を指します。
一見便利そうに見えますが、バグや不整合の温床になりやすく、保守性を大きく下げる要因となります。
31.1 特徴
- ドメインオブジェクトと UI(フォーム、ViewModel)が同じデータを保持
- 変更時に「片方だけ更新される」ケースが発生
- 同期処理が散在し、コードが煩雑化
- 「どちらが正しい値か」分からなくなる
31.2 なぜ問題か?
- 同期忘れによる不整合 が発生
- コードの重複(load / save のようなメソッド)が増加
- データフローが複雑になり、デバッグが困難になる
31.3 解決手法
- Observer パターン:データを1箇所で持ち、変更時に通知
- MVVM アーキテクチャ:Model が唯一の真実のソース(Single Source of Truth)
- 重複削除:不要なフィールドは潔く削除
31.4 Kotlin 例
Before:重複した観測データ
class Customer(var name: String)
class CustomerForm {
var name: String = ""
fun loadFromCustomer(customer: Customer) {
this.name = customer.name
}
fun saveToCustomer(customer: Customer) {
customer.name = this.name
}
}
-
CustomerとCustomerFormが両方nameを保持 -
loadFromCustomer/saveToCustomerで同期が必要 → 更新忘れのリスク
After①:Observer パターンで同期
class Customer(var name: String) {
private val listeners = mutableListOf<(String) -> Unit>()
fun addNameListener(listener: (String) -> Unit) {
listeners.add(listener)
}
fun updateName(newName: String) {
name = newName
listeners.forEach { it(newName) }
}
}
class CustomerForm(customer: Customer) {
var displayedName: String = customer.name
init {
customer.addNameListener { newName ->
displayedName = newName
println("Form updated: $displayedName")
}
}
}
→ データは Customer が唯一保持。Form は変更を監視するだけ。
After②:MVVM 的アプローチ
data class Customer(val name: String)
class CustomerViewModel {
var customer: Customer = Customer("Taro")
private set
fun updateName(newName: String) {
customer = customer.copy(name = newName)
}
}
→ ViewModel が「Single Source of Truth」となり、UI はこれを監視。
31.5 実務での指針
- データは 一箇所に集約(Single Source of Truth)
- UI とドメインで同じフィールドを持たないようにする
- Kotlin / Android では StateFlow / LiveData / Compose State を活用
- 同期処理は極力自動化し、手動の load/save を排除する
まとめ
- Duplicate Observed Data は「同じデータを複数箇所に持ち、同期が必要になる」悪臭
- 解決策は Observer パターン / MVVM / 重複削除
- 基本思想:「データは1箇所に。UIや他の層はそれを監視するだけ」