Split Variable
Intent
Section titled “Intent”A variable should stand for one thing. When a single name is reassigned partway through so that it first holds a perimeter and later holds an area, it is really two variables wearing one coat. Split Variable gives each distinct meaning its own name, assigned exactly once. The reader can then trust that a name means the same thing everywhere it appears, and the second assignment that used to mutate it disappears.
The smell
Section titled “The smell”The smell is a variable assigned more than once for unrelated purposes — not a true accumulator like a loop counter or a running total, but a name reused as a convenient scratch slot. The two clues: the variable is not a collecting variable (it is not summing or building something across iterations), and its meaning shifts between assignments. Whenever you have to scroll up and ask “which value does this hold right now?”, the variable is overloaded and wants splitting.
Before → After
Section titled “Before → After”A physics calculation reuses one temp variable, first for perimeter then for area. After, each result gets its own clearly named, single-assignment variable.
// Beforefunction describe(height: number, width: number): string { let temp = 2 * (height + width); const out: string[] = [`Perimeter: ${temp}`]; temp = height * width; out.push(`Area: ${temp}`); return out.join('\n');}
// Afterfunction describe(height: number, width: number): string { const perimeter = 2 * (height + width); const area = height * width; return [`Perimeter: ${perimeter}`, `Area: ${area}`].join('\n');}# Beforedef describe(height, width): temp = 2 * (height + width) out = [f"Perimeter: {temp}"] temp = height * width out.append(f"Area: {temp}") return "\n".join(out)
# Afterdef describe(height, width): perimeter = 2 * (height + width) area = height * width return "\n".join([f"Perimeter: {perimeter}", f"Area: {area}"])// Beforefunc Describe(height, width float64) string { temp := 2 * (height + width) out := fmt.Sprintf("Perimeter: %g", temp) temp = height * width out += fmt.Sprintf("\nArea: %g", temp) return out}
// Afterfunc Describe(height, width float64) string { perimeter := 2 * (height + width) area := height * width return fmt.Sprintf("Perimeter: %g\nArea: %g", perimeter, area)}// Beforefn describe(height: f64, width: f64) -> String { let mut temp = 2.0 * (height + width); let mut out = format!("Perimeter: {temp}"); temp = height * width; out.push_str(&format!("\nArea: {temp}")); out}
// Afterfn describe(height: f64, width: f64) -> String { let perimeter = 2.0 * (height + width); let area = height * width; format!("Perimeter: {perimeter}\nArea: {area}")}Mechanics
Section titled “Mechanics”- Find the first assignment to the variable and rename it to a name that describes only that first meaning. Where the language allows, make it a constant (
const, an immutablelet, a:=you never reassign). - Change every reference between the first and second assignment to use the new name.
- Run your tests.
- Repeat at the second assignment: declare a fresh variable with a name for the second meaning, and point the references after it at the new name.
- Continue until each distinct purpose owns a separate, single-assignment variable.
- Once the original mutable variable has no readers left, delete it. In Rust, you can usually drop the
mutentirely.
When to use / trade-offs
Section titled “When to use / trade-offs”Apply Split Variable whenever a name is reassigned to mean something new — it removes a whole class of “which value is this now?” bugs and makes each line independently understandable. It is a frequent precursor to Extract Function: once each value lives in its own single-assignment variable, the fragment that computes it lifts out cleanly.
Be careful not to split a genuine accumulator. A loop counter, a running sum, or a builder that grows across iterations is meant to be reassigned, and giving each step its own name would be nonsense. Split only when the successive values mean different things. The cost of splitting is one extra named variable, which is almost always cheaper than the confusion of an overloaded one.