There is also a conflicting statement in economics that says,
Never account for sunk costs
Sunk costs, according to Wikipedia (https://en.wikipedia.org/wiki/Sunk_cost):
In economics and business decision-making, a sunk cost is a cost that has already been incurred and cannot be recovered.
When sunk costs are coupled with political pressure or personal ego (what manager wants to be the one to admit that they made a poor decision or didn't properly monitor results, even if it was unavoidable or out of their immediate control?), it leads to a situation called escalation of commitment (https://en.wikipedia.org/wiki/Escalation_of_commitment), which is defined as:
a pattern of behavior in which an individual or group, when faced with increasingly negative outcomes from some decision, action, and investment, will continue rather than alter their course—something which is irrational, but in alignment with decisions and actions previously made.
How does this apply to code?
Having a rather long career as a software developer now, one common thread I've found is that, when faced with a challenging or ugly codebase (even if it is our own from two years ago), our first instinct is to want to throw out the old, ugly code and rewrite it from scratch. If it is a familiar codebase, then this is usually born from the fact that we are now much more familiar with the pitfalls of the project and business requirements than we were when we started the project, so we (perhaps subconsciously) yearn for the opportunity to fix our past sins by erasing them with perfection. If it is an unfamiliar codebase, we often tend to over-simplify the challenges faced by the original developers, glossing over "minor details" in favor of "big-picture" architectural-level thinking, and often blowing budgets and timeframes due to a lack of understanding of the complex minutia of the business cases that the code was originally meant to solve.
Then there is the whole concept of technical debt, which, just like financial debt, CAN and WILL accrue to the point that a codebase becomes technically insolvent. More and more time and resources are invested into troubleshooting bugs, extinguishing fires, and overly-challenging improvements to an extent that forward progress becomes expensive, difficult, and perilous. Projects take longer and longer due to defects and being pulled off of project work to fix production issues. After hours "incidents" start becoming expected operation instead of some rare blip. Instead of stepping back and starting to do things right to increase our future productivity (and quality of life), we find ourselves in a position where we are forced to add more and more technical debt in order to meet deadlines - the technical equivalent to taking cash advances on a credit card to make a minimum payment on another card.
That all being said, it neither means that we should rewrite whenever possible, nor should we avoid rewriting working code at all costs. Both extremes are potentially wasteful, and the latter does tend to lead to escalation of commitment (because at all costs means with total disregard to costs, even if those costs completely outstrip the benefits). What needs to occur is an objective assessment of the costs and benefits of rewriting code versus making incremental improvements. The challenge is finding someone with both the expertise and objectivity to make that decision properly. For us developers, we are generally biased towards rewriting because it tends to be a lot more interesting and engaging than working on some crappy legacy codebase. Business managers tend to be biased the other direction because a rewrite imposes some unknowns with little perceivable immediate benefit. The result is generally the absence of a real decision, which then defaults to continuing to dump hours into existing code until some circumstance necessitates a directional shift (or the developer covertly rewrites the code, and usually gets a spanking for it).
I've worked on codebases that were somewhat salvageable, albeit ugly. They didn't follow established practices or standards, didn't use patterns, weren't pretty, but they performed their intended functions reasonably well and were flexible enough that they could be modified to meet anticipated future needs for the expected life of the application. While not glamorous, it was perfectly acceptable to keep this code alive while making incremental improvements when the opportunity arose. Doing otherwise would have produced little benefit other than looking pretty. I would say that most code about which the should I rewrite this? question arises falls under this category, and I find myself explaining to the junior developers on the team that, while it would be great fun to rewrite YetAnotherLineOfBusinessApp in {insert whizzbang framework here}, it is neither necessary or desirable, and here are some ways we can improve it...
I've also worked on codebases that were hopeless. These were applications that barely launched in the first place, usually way behind schedule and in a reduced-functionality state. They were written in a way that no one but the original developer would have any chance of understanding what the code ultimately does. I refer to this as "read-only" code. Once it is written, any attempted change potentially results in systemic indecipherable failure of unknown origin, leading to panicked wholesale rewrites of massive monolithic code constructs that serve no purpose other than to educate the current developer on what is actually happening to a variable cleverly named obj_85
by the time execution reaches line 1,209 nested 7 levels deep in if... else...
, switch
, and foreach...
statements somewhere in the DoEverythingAndMakeCoffee(...)
method. Attempts to refactor this code results in failure. Every path you follow leads to another challenge, and more paths, and then paths that branch, and then circle back to a previous path, and after two weeks of heads-down refactoring of a single class you realize that, while maybe better encapsulated, the new code is nearly as whacky and obfuscated as the old code, probably contains even more bugs because the original intent of what you refactored was totally unclear, and, not knowing what exact business cases led to the original disaster in the first place, you can't be sure you've fully replicated the functionality. Progress is almost non-existent because translation of the codebase is nearly impossible and something so innocent is renaming a variable or using the proper type produces an exponential amount of unintended side effects.
Attempting to improve codebases like the above is an exercise in futility. Refactoring usually results in a 80% rewrite anyways, and the end result is nowhere near an 80% improvement. You end up with something that is very inconsistent, and the new code has a lot of compromises that had to be implemented in the interest of interoperability with legacy code (half of which was unnecessary because the legacy code that the new code needed to interoperate with later gets refactored out anyways). There are only two paths that can be followed... continue to accrue technical debt by hacking in "fixes" and modifications while hoping that the application is deprecated (or you get transferred to another project) before it collapses under its own weight, or someone makes the business decision and takes the risk of doing a complete rewrite. I hate both of these options, because it usually means waiting until something critical has failed or a project is way behind schedule, and you then spend the next three months of evenings and weekends trying to get something breathing that probably never should have been alive in the first place.
So, how do you decide?
- How well does the existing code work? Is it reliable and relatively defect free?
- Are the people on my team capable of understanding what this code does with a reasonable degree of effort? If I bring in an experienced developer, will he/she be able to make enough sense of this to become productive in a reasonable timeframe?
- Do what-should-be-simple defects take geologic time measurements to fix; so much so that we are unable to make real improvements or meet project deadlines?
- Is the codebase so fragile and the expected lifetime such that the ability of the application to adapt to future anticipated business needs is highly questionable?
- Did the existing code actually meet the original functionality requirements?
- Is your organization even receptive to investing in the application, or is someone (especially someone at a higher level on the org chart) going to be handed their own *ss for the problem?
- Can you provide financial or risk-based justification, backed up by hard facts, to make a business case for a rewrite?
- If, after a FULL accounting of the time and costs of a rewrite (including developing proper specifications, quality assurance testing, post-production stabilization, and training, does it still make sense to start rewriting code (us developers tend to only think in terms of coding time)?
- Do you have a choice? Is it even possible for the existing code to meet requirements (because if not, rewriting huge swaths is going to be part of the project and considered an "enhancement" instead of a rewrite)?