Refactor a Mess (Safely)
The refactoring loop: characterize behavior first, small steps, compile+test after each โ behavior preserved, structure improved.
The loop
- Characterize: write tests (or run existing ones) that pin the current behavior โ including the ugly parts.
- Small step: one structural change โ extract a function, rename, replace a magic number.
- Compile + tests green. If red, undo the step (it was one step โ that is the point).
- Repeat until the mess is structure.
Never mix "refactor" and "add a feature" in the same step โ the commit that does both is the commit you cannot trust.
A mess, and its dissection
// BEFORE: 60 lines in main โ parse, validate, compute, print, all interleaved
int main() { /* read csv line, split, stoi everywhere, if-chains, cout... */ }
Extraction order that always works: constants โ pure functions (parse, compute โ no I/O) โ I/O boundary (read/print) โ data type (struct Record). Pure functions first: they are the tests' favorite food.
// AFTER:
struct Record { std::string name; int qty{}; double price{}; };
std::optional<Record> parse_record(const std::string& line);
double record_total(const Record& r);
void print_report(const std::vector<Record>& rows);
Same behavior (your characterization tests still pass), new shape: every unit named, testable, and single-purpose.
The compiler is your refactor partner
Renaming a function: change the declaration, compile, follow every error to its caller. C++'s strictness โ the thing that made week one painful โ is exactly what makes refactoring safe here: nothing breaks silently.