Posted On: 2020-05-18
There are many difficult elements to programming*, but, for me personally, one element causes more stress than any other: compromise. Specifically, this is compromise wherein one sacrifices the future for the sake of the present. When phrased thusly, it might not be clear that this can (sometimes) be the best possible choice (difficult though it may be). If you'll indulge me, I'd like to share with you a bit about what this is, why it is sometimes the right thing to do, and why, for me personally, it is so difficult.
While developing, one needs to consider not just how to resolve problems now, but also what impact one's current choices will have on the future maintainability of the project. Finding a working solution is often just one step in the journey of solving a problem: one still needs to convert that solution into something that is clear, concise, and flexible enough to handle realistic* future needs. Consistently implementing maintainable solutions often pays off in the future, as maintainable systems are both easier and faster to modify.
Whenever one intentionally chooses a solution that is less maintainable*, one is, in effect, borrowing time from the future. By committing to a particular solution now, one saves time in the short-term by avoiding the time costs of making revisions or validating alternatives. Doing so does, however, comes with ongoing maintenance costs - whether that's in the taking extra time making changes to those systems or more formal maintenance activities (such as scheduled system downtime to work around stability problems.)
The simplest, and perhaps most practical, reason for picking a less maintainable approach is urgency: if a solution must be completed as soon as possible, then borrowing time from the future may seem like a viable strategy. When used sparingly, this can work out to everyone's benefit, but, unfortunately, it is quite common for urgency to be overstated*. As such, system maintainability often dwindles over time - so often, in fact, the euphemism Legacy Code is used both to refer to code that is old and code that is painful to maintain.
Another reason to pick a less maintainable approach is that more maintainable approaches have some kind of unacceptable trade-off. In that case, one is borrowing time from the future, and using it to pay for something else. Of these trade-offs, the one I am most familiar with is the trade-off of risk. When comparing the current solution to unvalidated solutions, there is always an element of risk: unvalidated solutions may be flawed in some way that prevents their use, or they may not be as maintainable as first imagined (such as requiring unexpected workarounds.) Thus, when comparing what one has to alternatives, one must always be cognizant of how validated solutions are inherently less risky than unvalidated ones.
One of the quirks of comparing one's current approach to unvalidated approaches is that there will always be more possibilities. I am something of a solution optimist in this regard: I believe that, for any given problem, there always is some solution that works (and likely many such.) Unfortunately, the reality is that actually finding a working solution is not always possible, or at least, not possible in a finite time-frame.
As such, when evaluating seeking alternatives to some known solution, there is something of an insidious risk of always chasing but never catching. Attempted solutions that are flawed in some way can be revised or redefined to become new, unvalidated alternatives. Thus, one alternative that might seem particularly appealing may be attempted not just once, but many different times, each attempt being a slightly different variation.
When one is trying to find an alternative to a solution that is particularly painful to maintain, one may continue to chase solutions well beyond what is justifiable from a time-cost perspective. The risk of such pursuits is one of the hardest things to judge correctly, particularly for those who are impacted by the painful maintenance. Yet, at some point, one has to make the call - otherwise, the project will be stalled forever.
Personally, I have a particularly difficult time accepting painful maintenance. While part of that might be a simple pain-avoidance response, it's also something much deeper: I value my work as a developer through the lens of helping others be more productive. When I write a tool to help someone, that is saving them effort in the future. Likewise, when I write maintainable code, I am saving my colleagues (or myself) future effort. To willingly adopt a solution that require painful maintenance is, for me, to fail* as a programmer.
Hopefully you now better understand why one might want to sacrifice the future for the present, and why it particularly bothers me whenever I do so. At the risk of stating the obvious: when it really is the best course of action, I do, in fact, do this. As a simple example: I still haven't found a good solution the save issues I've been struggling with for the past month. Since I need to keep moving forward, I cut my losses and decided to use the one approach that actually works, even though it makes the save system less clear and more cumbersome to use. Thus, even if it adds some extra bumps to the road ahead, I can continue to keep moving forward.