Programmers Are Users (Bad Performance Makes Everyone Less Efficient)
On the often-referenced notion of saving “programmer cycles” at the expense of CPU cycles.
I want the act of programming to be easier, more pleasant, and more productive for the vast majority of programmers; and I am willing to sacrifice performance in order to achieve that.
—Robert “Uncle Bob” Martin
There’s a pervasive idea in the software industry that’s often posed to defend abstractions and software layers which are easily (and often) used to generate tremendous amounts of bad code, in favor of programmer efficiency in generating that bad code.
When I say “bad code”, I don’t refer to what the programmer sees in his text editor. I am talking about the actual code—that is, what the user’s CPU executes.
The argument goes: given these abstractions or layers—maybe they’re language features, or programming paradigms—programmers can be more efficient in generating bad code which accomplishes the same meaningful work as the alternative case, where the programmer does not use these abstractions or layers, and perhaps produces good code, but was more inefficient in doing so. The claim—the hypothesis, or as it is often used, the assertion—is that these paradigms, abstractions, languages features, and so on, are more efficient from a business perspective, at the expense of efficiency from a software quality perspective.
I recently saw this argument manifest in practice when I (finally) watched gingerBill’s video breakdown of the written discussion between Casey Muratori and Robert “Uncle Bob” Martin, on the technical merits of Clean Code. It’s important to understand that “Clean Code” doesn’t refer to “clean code”. Capital-C “Clean” describes code, in the same way that “Democratic People’s Republic” describes North Korea. It is a label. It has as much information content as a person’s name. Bill correctly identifies this and says that it’d really more appropriately be called “Mr. Martin Style”.
In the video (which I recommend watching), Bill mostly doesn’t focus on the technical side of the discussion. And with good reason—as Bill demonstrates, Robert Martin didn’t focus on the technical side of the discussion either. Mr. Martin instead opted to debate rhetorically—Bill correctly likens this to being akin to a form of political maneuvering. Throughout the debate, no technical questions are satisfyingly answered by Mr. Martin—if that is what you’re looking for, you’ll be disappointed. It isn’t named “Clean” (rather than Mr. Martin Style) by accident—this convenient word choice is often used by “Clean Code” proponents to mask unfalsifiable assertions, in order to justify certain “Clean Code” guidelines, by subtly shifting between “clean” and “Clean” throughout a discussion.
Casey and Bill both did an excellent job, but in both the written discussion and the video breakdown, I kept having a recurring thought that I didn’t see directly expressed by anyone, and that’s why I’m writing this post.
You could spend several careers researching programmer productivity. The interface of programming is, of course, tightly intertwined with programmer psychology, the programmer’s ability to reason, and the programmer’s ability to easily express computational effects. Not all interfaces are equally efficient for programmers to use. Not all interfaces can have equally efficient implementations. Barely investigating this subject leads to the conclusion that some interfaces are simultaneously better for the programmer, and better for the CPU, than others. Programmer efficiency and CPU efficiency are not always mutually exclusive—one does not necessarily need to always pick one.
Unfortunately, serious investigations of these topics are seemingly never carried out by “Clean Code” proponents. Claims of improved programmer efficiency, at the expense of CPU efficiency, are mere assertions.
But in this post, I want to zoom out from those investigations. I want to give “Clean Coders” the benefit of the doubt. Let’s say that we are operating in a space where we must trade CPU productivity for programmer productivity. Let’s say that we even have tools which we know trade CPU productivity for programmer productivity. Never mind the fact that these premises are never proven to hold—let’s say we find a case where they do hold.
I’d like to explain why, in that context, it still doesn’t make sense to overwhelmingly prefer the comfort and generative productivity of the programmer writing the code. Even when I’ve given “Clean Coders” everything they could possibly ask for, everything they attempt to assert, there is still a strong argument in favor of software performance, at the expense of initial programmer performance. Part of this argument has to do with the overall quality of the computing system, but it is not strictly altruistic—there is also a strong business case for software performance, even with heightened upfront programming cost. As such, this argument does not require that you have business inclinations, nor altruistic inclinations—you could love, or hate, the concept of a free market with private property. In all such cases, this argument still applies.
The fundamental mistake I see as responsible for entirely destabilizing the argument in favor of programmer code-writing efficiency (at the expense of software efficiency) is in an incorrect model of the software ecosystem.
Programmers Run Their Own Code
The implicit model being used to justify the tradeoff assumes a single software producer, which produces the code, and a single customer, which executes the code:
But this model ignores the everyday reality of programming. The reality is that the producer must also execute the code, as they develop, debug, and test it. Only after they’ve done that process innumerable times, they will submit the software to the user to be executed.
Already in this model, bad software execution time has already caused a slowdown in programmer productivity. Some amount of the programmer’s efficiency in reading or writing the code has already been counterbalanced by the software’s inefficiency, because the programmer is blocked on executing his own software. The slow software execution time has thus had an impact on the programmer’s iteration cycle time.
To continue justifying the choice to prefer programming methodologies which promote fast generation of bad code, one must prove that this degradation of programmer iteration cycle time does not outweigh the improvement in programmer efficiency in generating the code. Given that code can often be written once, but tested, debugged, and tweaked hundreds if not thousands of times, this fact is important to demonstrate. But it is never demonstrated.
Customers Are Programmers
But this problem gets worse. Who is the software producer? What are they making? Who is the software consumer? What are they using the software for? It seems like “Clean Code” arguments are often made assuming a very narrow definition of these two entities—for example, as used in the original written discussion, some payroll software, used by a business to pay employees. But what is the common reality in the software world?
Let’s take an example from the real world—from my own life, in fact. I work on a debugger project full-time. As part of my work on that project, I need to use a file explorer. The Microsoft Windows built-in file explorer is an incredibly poor quality experience for a large number of reasons—probably related to “Clean Code”, in fact—which is why I’m excited for my friend Vjekoslav’s file explorer project, File Pilot. Vjekoslav is a programmer too, and so as part of his file explorer project work, he needs to use a debugger.
Instead of some arbitrary payroll software producer, and some arbitrary business, let’s say the entity on the left is me writing the debugger, and the entity on the right is Vjekoslav, writing his file explorer. This is a slight extension of the initial oversimplified model—it demonstrates one simple case of cyclical dependencies within the software ecosystem. This is an overwhelmingly common relationship—it regularly manifests within a single company or organization, but it extends beyond those boundaries as well.
In this context, if my software is unnecessarily slow, not only does it slow myself down before I release it, it also slows Vjekoslav down in his development. This degradation in his programming performance manifests in fewer iteration cycles for his software, which I also depend on. Because I began by making my software worse than it could’ve been, that effect has propagated outward into the computing ecosystem, and because the computing ecosystem is a cyclical graph of dependencies, that effect propagates back to me.
Slowness Propagates, Then Compounds
But this problem is worse, still, than I’ve described. A more detailed graph of the software ecosystem is more complex than the earlier examples. The software ecosystem is a vast cyclic network of independent entities producing software, and relying on other software.
Poor performance, introduced at points in this network, propagate outwards, because “depending on” software means executing that software. Execution time of an entity’s dependency is, one way or another, factored into the production of the dependent itself. This may be literally execution time in the dependent software, if it relies on a poorly constructed library—or it may be in programming resources, if the programmer relies on a poorly constructed tool. Whether it blocks the programmer, or the software, it has a cost.
The longer the dependency chain becomes, the more likely these effects will be multiplied or otherwise duplicated—and the more likely that these effects will back-propagate to the dependent software. Not to mention that, the longer this dependency chain becomes, the less likely that anyone will understand what the problem truly is, or how to fix it.
This reality has explanatory power in describing the state of modern software. Despite vast improvements in computing hardware capabilities, some software often performs dramatically worse than its contemporary equivalent running several decades ago, on hardware that was sometimes a thousand times slower, with a thousand times fewer resources. This is because poor performance in this network does not stop at one edge in the network—it is multiplied several times over.
During the initial discussion, Mr. Martin introduced several time domains. He described some problems as being within the “nanoseconds domain”, where wasting nanoseconds matters. He described the “microseconds domain”, where wasting microseconds matters. He described the “milliseconds domain”, where wasting milliseconds matters.
The problem is, however, that there is only one time domain in reality. And wasted nanoseconds add up to wasted microseconds. And those add up to wasted milliseconds. And those add up to wasted seconds. This can happen before network effects in the software ecosystem are considered, simply within one piece of software.
After network effects are considered, it is perhaps no surprise that programs now accomplish in thirty seconds what they used to accomplish in less than a second on hardware which was thousands of times less capable.
Spending software efficiency to obtain programmer efficiency may seem like an appealing idea. But I hope this post helped clarify why it isn’t that simple. The acceptance of software production methodologies like the one Mr. Martin himself promotes are in part responsible for a vast degradation of software quality, and ironically enough, decreases in programmer efficiency.
I opened this post with a quote from Mr. Martin himself: “I want the act of programming to be easier, more pleasant, and more productive for the vast majority of programmers”.
Unfortunately, the act of programming is harder, less pleasant, and less productive than ever, because of systemic excuse-making for mediocrity. Sacrificing software efficiency for programmer efficiency seems to simply result in losing both.
If you enjoyed this post, please consider subscribing. Thanks for reading.
-Ryan
This looks like reframing software performance as a commons problem - individual companies want to save their time and externalize inefficient software, and collectively everyone ends up using everyone else's inefficient software. At the same time, if a company's programmers end up wasting a lot of time waiting on a specific type of software, there's now a market for a higher end implementation of that type of software, which makes me wonder why no one takes the other end of that bet.
Or, if two companies make each other inefficient enough, they could strike an agreement to improve the relevant pieces of their software, which should be mutually beneficial if the post's point holds (and their software is actually possible to change)
So instead of agreeing that SOLID principles are valuable indeed and clarifying that the definition of clean (or Clean if you will) code should always be perceived in context of software requirements, including speed and efficiency, we're just dismantling the work of Dr. Marting and declaring it untrustworthy, outdated, irrelevant, and so forth?
The fact that some software is slow by design has nothing (or very little) to do with clean code. Some programmers just cant write good (fast, efficient) algorithms l. There is no conflict per se. It's a problem context.
"I need file system explorer, fast" and "I need fast file explorer" are two different problems. Both can be executed with clean code.