The Paradox of Pointless Code: Moving Faster by Doing Nothing

The Paradox of Pointless Code: Moving Faster by Doing Nothing

It’s easy to think of software development as a baldly utilitarian task. A process needs to be formalized or some data needs to be transformed, and so we write code that automates the task. Inputs are received, outputs are produced, and you might assume that what lies in between is streamlined logic that travels the most efficient route from point A to point B with few, if any, detours. If you do then you are going to be surprised to learn how little resemblance this bears to code that actually gets written. Modern applications tend to contain a lot of code that does little if anything for their intended function. Some of this is just part of the luggage that comes with using standard tools and components, but when the possibly pointless code is our own doing the question of what to do about it becomes more salient.

The Spectrum of Non-Essential Code

The range of things that we might consider non-essential code is broad, and a fair definition would have to include many things that are so standard that the distinction is merely semantic. The purest form is undoubtedly the Easter egg, but these are both rare and intentionally added. However, anything that could be removed entirely without impact on how the application works is fair game. Unit tests and abstract interfaces both meet the strict definition of non-essential, even though it would be hard to make the case that these things aren’t important. All the code that gets bundled in by frameworks and libraries but which is never employed in your specific application also technically counts. Again, the application would work just the same without the unused library code, but it’s hard to fault the developer for its presence and it is at least possible that it might one day find a purpose. And since third-party library code isn’t generally kept inside an app repository, its size and scope is not usually a cause for concern.

Leaving aside standard items like tests and library bloat, it’s still a pretty safe bet that you won’t have to look very far to find parts of your codebase that uncontroversially fail to meet even a generous definition of essential. To be clear, I do not mean to suggest that we developers are intentionally padding out our repositories with needlessly circuitous logic. On the contrary, we are prone to spend more time optimizing an operation to save a few milliseconds than would have ever been wasted by a less efficient version over the lifetime of the application. Nevertheless, like most developers, the code I produce often includes things that are not strictly necessary. Here are a few recent examples:

  • A dead handler method that is never actually called. It was written to process a file format that is no longer in active use by the organization, but if it provides a nice template for how future handlers could be implemented is that reason enough for me to leave it in place?
  • A base class that only has a single extender. I wouldn’t do this because I have any love for needless abstraction, but I might if I strongly suspect there are likely to be more extensions needed in the near future. The additional complexity required to support the base/extender pattern is technically extraneous when a single class would suffice, but is it a good idea anyway if it makes the design intention clear and future implementations simpler?
  • Handling for value formats that no caller of a method currently provides. There may be no requirement to convert a string value to upper case if you always receive upper case strings.  But if I cannot guarantee that some future code will not call the method with a lower case but otherwise acceptable value, would it be easier for me to add the pointless conversion now than it will be for the future developer to track down why their method call is not working as expected?

Why Keep Non-Essential Code?

I would argue that the answer in all these cases is easily “Yes.” Every single one of them involves violating a general best practice: do not leave dead code, do not prematurely optimize, do not over-engineer. To make matters worse, there is nothing about the code in question that impacts the operation of the application. But crucially, what all these examples have in common is that there is a reasonable expectation that adding this “pointless” code will provide tangible future value, and that value is greater than whatever amorphous benefits would accrue from staying within the methodological lines. Developer time is valuable, and reducing the time and effort required to implement a new feature in the future is still time saved; it just hasn’t been saved yet. Obviously not all pointless code is worth keeping, but spotting the difference is a simple matter of asking yourself whether it provides something useful to the future developer that encounters it. Dead code branches that would not function if called because the API has drifted too far only makes the code harder to understand.  But on the other hand, extracting related functionality into a dedicated package is likely to make the code easier to follow and reuse, even if doing so doesn’t provide the application with any new features today.

Balancing Efficiency and Future Value

Tolerance for this kind of inefficiency has led directly to many of the core aspects of modern software development. Early high level languages were criticized for their relative inefficiency compared to what could be achieved with bespoke assembly language code; that cycle repeated itself when interpreted languages came to prominence.  Web frameworks, any number of DSLs, and client side Javascript are all things that started out as relatively pointless excursions whose primary benefits were speculative ones for future development. As it turned out these were all good bets, and they eventually became powerful tools in their own right.

Ultimately, producing efficient and highly targeted code is certainly something worth striving for and the general wisdom that over-engineered solutions are to be avoided still holds true. However, when taken too literally these precepts can result in overly narrow solutions that do not respect the time of the future developer (often still you) who needs to maintain or extend your work. So if you have code that is not actively participating in your application, and you almost certainly do, don’t be too quick to judge it as useless. It may just need a little more time to realize its potential.

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All