Separate Query from Modifier

Removes smells
Compare
Symptom
Human

A function that both returns a value AND mutates state — `findMiscreant(people)` that returns the miscreant AND alerts them.

Agent

A function the agent calls for a query also mutates state; the agent reasoning about safety must trace the mutation across consumers.

Goal
Human

Functions either return a value or mutate state, never both — callers can compose them without surprise.

Agent

Functions either return or mutate, never both; the agent composes queries without surprise side effects.

Before the refactoring

function findMiscreant(people) {
for (const p of people) {
if (p.isMiscreant) { alert(p); return p; }
}
}

After the refactoring

function findMiscreant(people) { return people.find(p => p.isMiscreant); }
function alertMiscreant(people) {
const m = findMiscreant(people);
if (m) alert(m);
}
Example source: Illustrative example written for this site, not a quotation from any source.
Pressure
Human

Callers can't query without triggering the side effect; tests must work around the dual contract; reasoning about side effects becomes non-local.

Agent

Every call to the function pays for both contracts; the agent can't query without triggering mutation, which complicates testing and composition.

Tradeoff
Human

If the modification and the query truly cannot be separated (e.g. find-and-remove on a queue), the constraint is fundamental — leave the combined operation but document it.

Agent

If the modification and query are genuinely atomic (find-and-remove, compare-and-swap), splitting them introduces a race window the agent must close at every call site.

Relief
Human

Reasoning about side effects is local; tests target each shape independently.

Agent

Queries return values without mutating; the agent predicts each function's effect from its name alone, and code generated against a query never accidentally writes the state the query reads.

Trap
Human

Splitting a function whose query and modification truly cannot be separated — find-and-remove on a queue, atomic compare-and-swap — fragments operations that need atomicity.

Agent

Splitting atomic query-and-modify operations introduces race windows the agent must reason about at every call site — the cure becomes worse than the smell.