Separate Query from Modifier
Before you begin: Objects, state changes, and return values.
Separate Query from Modifier splits an operation that both returns information and changes state. A query observes; a modifier performs a change. Separating them lets callers inspect a value without accidentally triggering an update. 1
Original version: looking at the score clears it
public sealed class Score
{
private int value;
public Score(int value) { this.value = value; }
public int ReadAndReset()
{
int result = value;
value = 0;
return result;
}
}class Score:
def __init__(self, value):
self._value = value
def read_and_reset(self):
result = self._value
self._value = 0
return resultThe name admits both effects, but a screen that only wants to display the current score cannot use this operation safely. Reading twice returns different results.
Refactored version: reading and resetting are separate choices
public sealed class Score
{
private int value;
public Score(int value) { this.value = value; }
public int Read() { return value; }
public void Reset() { value = 0; }
}var score = new Score(12);
int result = score.Read();
score.Reset();
Console.WriteLine(result); // 12class Score:
def __init__(self, value):
self._value = value
def read(self):
return self._value
def reset(self):
self._value = 0score = Score(12)
result = score.read()
score.reset()
print(result) # 12A caller that needs the old behavior reads first and resets second. A display-only caller just reads. In this sequential example, the old sequence’s result and final state are preserved.
Do not split an operation that must be atomic
If other threads can update the score between the read and reset, two separate calls may lose an update or return an inconsistent result. An atomic exchange, queue removal, or database change-and-return operation can legitimately combine a mutation with a result. Preserve that atomicity rather than obeying separation mechanically.
The original example has no synchronization, and the revision adds none. Its purpose is to show an API distinction, not a concurrent counter implementation.
For a public API, a compatibility method can call the separate operations while consumers migrate. Tests should check both returned information and final state. A query that returns the expected number but still secretly resets the score has not accomplished the refactoring.