A function that mixes two clearly different concerns — pricing AND rendering, parsing AND validating, fetching AND transforming — interleaved through shared locals.
Each phase reads and writes its own well-defined inputs and outputs; the seam between them is data, not control flow.
Before the refactoring
function priceAndRender(input) {let total = 0;for (const item of input.items) total += item.qty * item.price;if (input.member) total *= 0.95;return `<p>Total: ${(total / 100).toFixed(2)} for ${input.items.length} items</p>`;}
After the refactoring
function pricing(input) {let total = 0;for (const item of input.items) total += item.qty * item.price;if (input.member) total *= 0.95;return { items: input.items, totalCents: total };}function render({ items, totalCents }) {return `<p>Total: ${(totalCents / 100).toFixed(2)} for ${items.length} items</p>`;}
Each concern can't be tested or evolved independently; changes to one concern risk breaking the other; the function's intent gets buried under coordination work.
An intermediate data structure between the phases is overhead — earn it by separating two clearly different concerns.
Phases evolve independently; tests target each phase in isolation; the intermediate shape becomes a documented contract.
Splitting phases that genuinely belong together — work where the second phase needs to mutate state the first phase set up in subtle ways — fragments coherent logic across an artificial seam.