1. 概要(Overview)
Separate Query from Modifier は、
データを取得するメソッド(Query) と オブジェクトの状態を変更するメソッド(Modifier) を分離するリファクタリングです。
1つのメソッドが「値を返す」と同時に「状態を変更する」ことは、副作用が隠れてバグを生みやすくします。
「値を返すメソッドは副作用を持たない」というルールを守ることで、コードの可読性・テスト容易性が向上します。
目的は以下の通りです:
- 副作用をなくして予測可能な設計にする
- 呼び出し側の混乱を防ぐ(値を取るだけなのか、状態も変わるのか?)
- 関数型プログラミング的な「純粋さ」に近づける
2. 適用シーン(When to Use)
- 値を返すメソッドが内部状態を変更している
- 「このメソッドは読んでいるだけ?」と疑問を持たせるようなコードがある
- テストで「値取得しただけなのに、副作用で状態が変わる」ために不具合が起きている
よくある匂い:
- Side Effects(副作用のあるメソッド)
- Inconsistent Method(用途が混在しているメソッド)
3. 手順(Mechanics / Steps)
- 値を返すと同時に副作用を持っているメソッドを特定
- 副作用部分(状態変更)を別のメソッドに切り出す
- 元のメソッドは純粋に「値を返す」だけにする
- 呼び出し側コードを修正し、必要なら両方のメソッドを明示的に呼び出す
4. Kotlin 例(Before → After)
Before:取得メソッドが副作用を持つ
class Counter {
private var count = 0
fun getNext(): Int {
count++ // ← 状態を変更している
return count // 値も返している
}
}
fun main() {
val counter = Counter()
println(counter.getNext()) // 1
println(counter.getNext()) // 2
}
-
getNext()は「次の値を返す」と同時に「内部状態を更新」している - 「取得」と「変更」が混ざっており、呼び出しの意図が不明確
After:照会と変更を分離
class Counter {
private var count = 0
fun getCount(): Int = count // 照会専用(副作用なし)
fun increment() { // 変更専用
count++
}
}
fun main() {
val counter = Counter()
counter.increment()
println(counter.getCount()) // 1
counter.increment()
println(counter.getCount()) // 2
}
-
getCount()は純粋に状態を返すだけ -
increment()は副作用(状態変更)のみを担う - 呼び出しの意図が明確になった
5. 効果(Benefits)
- メソッドの役割が明確になり、可読性が向上
- テストが簡単になる(「照会だけ」「変更だけ」を切り分けられる)
- 呼び出し側の意図が分かりやすくなる(「値を取るのか」「状態を変えるのか」)
- 関数型スタイルに近づき、副作用の予測が容易になる
6. 注意点(Pitfalls)
- 分離により「呼び出し回数」が増えるため、パフォーマンスに影響する可能性がある(通常は無視できる)
- 外部 API とのインタラクション(例:DB の
getAndUpdate)では、分離が難しい場合もある - ドメインによっては「照会と更新が一体」な方が自然なこともある
まとめ
- Separate Query from Modifier は「照会(値を返す)と変更(副作用)を分ける」リファクタリング
- 判断基準:このメソッドは「読む」だけか? 「書く」もしているか?
- 基本思想:呼び出し側に「副作用の有無」を明確に伝え、予測可能なコードにする