Move Statements to Callers
Intent
Section titled “Intent”A function that started as a single clear unit can grow until it is doing slightly different things for different callers. When part of its body should vary by caller, pull that part out of the function and into each caller. The function keeps the shared core; the variable behaviour moves to where the variation actually lives.
The smell
Section titled “The smell”You notice yourself reaching for a boolean parameter — render(person, includeFooter) — or wishing one particular caller could skip a chunk of the function. That itch means the function is bundling a step that is not universal. The common part and the per-caller part have been welded together. This is the exact inverse of Move Statements into Function: there, identical statements wanted to come in; here, divergent statements want to go out.
Before → After
Section titled “Before → After”A renderPerson that always emits a photo block at the end. A new caller needs the heading but a different footer. Rather than add a flag, we move the photo block out to the callers.
// Before — one caller wants a different ending, tempting a flagfunction renderPerson(person: Person): string { return [ `<p>${person.name}</p>`, emitPhotoData(person.photo), ].join('\n');}
const listing = people.map(renderPerson).join('\n');
// After — shared core stays, the varying tail moves to callersfunction renderPerson(person: Person): string { return `<p>${person.name}</p>`;}
const listing = people .map((p) => [renderPerson(p), emitPhotoData(p.photo)].join('\n')) .join('\n');
// a different caller can now end differentlyconst compact = people.map(renderPerson).join('\n');# Before — one caller wants a different ending, tempting a flagdef render_person(person): return "\n".join([ f"<p>{person.name}</p>", emit_photo_data(person.photo), ])
listing = "\n".join(render_person(p) for p in people)
# After — shared core stays, the varying tail moves to callersdef render_person(person): return f"<p>{person.name}</p>"
listing = "\n".join( "\n".join([render_person(p), emit_photo_data(p.photo)]) for p in people)
# a different caller can now end differentlycompact = "\n".join(render_person(p) for p in people)// Before — one caller wants a different ending, tempting a flagfunc renderPerson(person Person) string { return fmt.Sprintf("<p>%s</p>\n%s", person.Name, emitPhotoData(person.Photo))}
func listing(people []Person) string { parts := make([]string, len(people)) for i, p := range people { parts[i] = renderPerson(p) } return strings.Join(parts, "\n")}
// After — shared core stays, the varying tail moves to callersfunc renderPerson(person Person) string { return fmt.Sprintf("<p>%s</p>", person.Name)}
func listing(people []Person) string { parts := make([]string, len(people)) for i, p := range people { parts[i] = renderPerson(p) + "\n" + emitPhotoData(p.Photo) } return strings.Join(parts, "\n")}
// a different caller can now end differentlyfunc compact(people []Person) string { parts := make([]string, len(people)) for i, p := range people { parts[i] = renderPerson(p) } return strings.Join(parts, "\n")}// Before — one caller wants a different ending, tempting a flagfn render_person(person: &Person) -> String { format!("<p>{}</p>\n{}", person.name, emit_photo_data(&person.photo))}
fn listing(people: &[Person]) -> String { people.iter().map(render_person).collect::<Vec<_>>().join("\n")}
// After — shared core stays, the varying tail moves to callersfn render_person(person: &Person) -> String { format!("<p>{}</p>", person.name)}
fn listing(people: &[Person]) -> String { people .iter() .map(|p| format!("{}\n{}", render_person(p), emit_photo_data(&p.photo))) .collect::<Vec<_>>() .join("\n")}
// a different caller can now end differentlyfn compact(people: &[Person]) -> String { people.iter().map(render_person).collect::<Vec<_>>().join("\n")}Mechanics
Section titled “Mechanics”- Identify the statements that should vary by caller — usually the leading or trailing part of the function, not the middle.
- If there are only one or two callers, copy the statements directly into each caller, right next to the call.
- With many callers, do it safely: extract the remaining (shared) body into a new function, leave the original calling that new function plus the variable statements, then point callers at the new shared function one at a time.
- Run your tests after wiring each caller.
- Once every caller holds its own copy of the moved statements, remove them from the original function.
- Rename the shared function if its narrower responsibility now deserves a clearer name.
When to use / trade-offs
Section titled “When to use / trade-offs”Reach for this when a function is almost right but one part needs to differ per caller, and you would otherwise reach for a flag parameter. Moving the variation out keeps each function focused on a single, consistent job.
The cost is some duplication at the call sites — each caller now repeats the moved statement. That is acceptable when the callers genuinely differ; the duplication is honest. If the statement turns out to be identical everywhere after all, you have gone the wrong way: apply Move Statements into Function to fold it back.