Ever look at a piece of code and think,
“Hey, waitaminute, I’ve seen this before. It was cargo-culted from this module…”
“And the way this class inherits from this other class, well, that doesn’t make any sense…”
“Alright, time to roll up ’em sleeves and put in some preverbal elbow grease.”
So begins another tale of the weekend of mad refactoring.
Refactors are triggered by change. Now, that sounds wrong, as refactors are the act of modifying the organization and infrastructure of software without changing functionality (and ideally, ensured by a suite of tests). The internals of the black box may have shifted, but the same inputs garner the same outputs, maybe with an added bonus of reducing bugs at the edges.
Rather, it’s the change around the code that drives the need for refactoring. The conditions that caused the original code to be written have changed in some way: maybe you explicitly allocated this time post-launch to go back and undo all the hacks you made the first time; maybe there’s an updated best practice you want to implement; maybe there are new use cases that will extend this code, but in its current form would turn it into a giant hack. The old cruddy code, despite its offense to sensible software taste, was perfectly capable of executing its job; rebuilding it is the equivalent to getting a makeover, but we’re performing the task mainly because it needs to go onstage to accept an award.
When code is rewritten for intrinsic reasons – applying a new technique, fixing up TODO tech debt that was necessary to get to a timely launch – it feels good to clean up messes and apply newfound knowledge, even if the end result produces the exact same input and output. In production software, these refactors are also less common, as the “tech debt” bucket is a wary enemy of the eager project manager and the immediate business or product value is murky at best, even if it makes the engineers really happy.
Extrinsic motivation for rewriting code – where it’s necessary to modify existing infrastructure to accommodate additional product – is more amicable to the eyes of the non-engineer, and much easier to justify time and resources. The flip side, though, is that it’s hard to internalize the how evolution of the code is bound to changing product requirements; the stuff that looks terrible and non-extensible and rigid for this feature was perfect for the last one, and it’s easy to make light of the (lack of) quality in legacy codebases when viewed out of context.
Something to think about the next time I decide whether a refactor or even a rewrite is worth the effort.