In a perfect world, development schedules would be based on realistic estimates, with plenty of buffer time factored in for the unexpected contingencies that always arise. But the real world is never ideal. Shit happens. Development cycles end up being too short and inflexible. Even worse, deadlines are often times determined by a completely arbitrary release date.
A fun thing to do on an old code base is to search for the word “hack” and read all the hilarious comments that crop up. These comments typically start out with a warning along the lines of: “This is a horrible hack!”, “UGH hack hack bad bad”, or “DANGER! Hack!” This is followed up by an explanation of why the following code is, to put it nicely, less than optimal. I don’t think anybody who has ever checked in code such as that ever expected their changes to be permanent, but rather a temporary band-aid fix/implementation to meet a deadline. Unfortunately, many of these comments that I see are dated back from many years ago. Oops.
Its not hard to see why. You check in the code, promising yourself you’ll come back and revisit this at some later point in time and do some badly needed cleanup, refactoring, or in a worst case scenario, a complete rewrite. But what happens inevitably is that there are new features to work on, bug fixes to make, meetings to attend, and pretty soon you are completely sidetracked. After all, if the current feature you’re cutting corners on is part of an unforgiving development cycle, why would future development cycles be planned out any differently?
Worse, each passing day makes a refactor that much more riskier. Think about it – the easiest bug fixes to make are ones that you do a few days after you’ve completed a feature. This is because your brain doesn’t have to make a “context switch”; everything is still fresh in your memory. However, as time goes on, you grow less familiar with the code in question and it is more likely you’ll introduce new bugs into the system anytime you modify it. This problem is compounded over time by the fact that new functionality will invariably be built upon this previous code. In the worst case, a temporary hack becomes an integral cornerstone of the system architecture. You’ll then need to be cognizant of a whole set of complex dependencies that simply wouldn’t have existed back when the code was first written. After a certain point it becomes more expensive and risky to refactor/rewrite a broken piece of code than it is to simply leave it as is.
Its also worth mentioning the political barrier to rewrites and refactors: Upper management, sales, and marketing don’t really care about clean and elegant code, especially if it is at the expense of more tangible things, such as a shiny new feature. Its difficult to make a compelling power point slide to customers explaining that the new version of a product uses 30% more design patterns. This problem is mitigated if you work for a good tech company, but not everyone is so fortunate.
The bottom line here is that it is wishful thinking to hope that a temporary hack is going to be anything other than permanent. So what’s a developer to do? As the cliche goes, anything worth doing is worth doing right. Ok great, but even if you work weekends, the fact of the matter is that there’s only so many hours in a day. Given an inflexible deadline, you’ve got to figure out what truly matters. Luckily, tough decisions such as these are why upper management exists. Transparency and honesty go a long way. All you can do is give them enough information so that they can make an informed decision. The importance here is to emphasize the tradeoff between high quality code and being 100% feature complete by a given date.
Obviously, everyone would be happy if bug free code shipped on the feature complete date. But that’s not possible in most cases. Management may not be happy to hear that kind of news, but I think its a safe bet to make that they’d be far more upset to find out later on down the line that you were unable to deliver on what you promised. The choices would involve some combination of pushing back the release date, slipping certain features (or slipping/modifying certain requirements), and of course, scheduling time for the inevitable bug fix patches.
If shoddy code is knowingly shipped, the key thing is to stress the importance of a refactor. As I mentioned before, non techies typically don’t grasp the benefits of a well written piece of code, so it is your duty to make sure to make sure that they do understand. A well architected solution not only has fewer bugs, it will also be flexible enough to accommodate future requirements, making subsequent dev work that much easier. Of course, the downside is that it obviously takes much longer to come up with a good solution. However, this is only a one time price that you pay upfront. In contrast, bad code is bug ridden and inflexible. New functionality built on such a shoddy foundation will take longer to write and be buggier as well. This is a steep price that will be paid repeatedly in the future.
This is exactly why anytime code of dubious quality is shipped, it is imperative that you convince the powers that be to create some bug/task/feature in whatever bug tracking / project planning system your company uses to make sure this code is improved upon. Simply promising yourself that you will do it at some later unspecified date and time won’t be enough; chances are its not gonna happen.
*“Magic numbers” are numeric values that appear in code which are unclear in meaning. Ideally, these should be replaced with a descriptive variable name instead. For example, instead of:
weight = mass * 9.80665;
the following would be better:
weight = mass * EARTH_STANDARD_GRAVITY;