Inefficiently Efficient

I’ve been asked about this twice these last few weeks so it’s easier to blog about it. This blog post will be written within the context of C/C++ where there is usually an (over-)obsession with performance or at least much more performance “awareness” than with higher-level languages where developers accept that they’re pretty slow.

I love clean code. Clean code is both an art and science. One of the most important elements of clean code is naming. If we name our variables well, our code can be so readable that we don’t even need comments! It brings us a step closer to declarative programming. However, it takes more than just good variable naming to write clean code. Sometimes, you might just want to eat up more memory for the sake of readability. This is what I call “inefficiently efficient.” Let’s take a look…

Oftentimes, C/C++ programmers can become so obsessed with performance that they begin to micro-optimize. We’ve all heard the quote, “Premature optimization is the root of all evil.” While it’s all well and good if you aren’t overoptimizing to the point of impacting readability, programmers can become frightened to death with the thought of personally introducing inefficiency into the code. “What?! Inefficiency under my name?! With the VCS to prove it too!” This is an instinctual reaction. However, allow me to discuss why purposefully introducing inefficiencies to write clean code can do a world of good for you (and your code).

This is our if statement:

1
2
3
if (node->operator == "-" && node->argument->is<NumericLiteral>()) {
    // ...
}

What on Earth is that doing? As you’re writing code like this, you might know in this moment exactly what it does. Come back and revisit the code in five years, and let’s see if your memory is so fresh.

I’m a fan of writing code where, upon glancing at it, you can immediately tell what it does. There is no need to inspect the code, and there is no need to jump from file to file trying to figure out how it all interconnects. Following this principle, let’s try and refactor the above code:

1
2
3
4
bool isNegativeNumber = node->operator == "-" && node->argument->is<NumericLiteral>();
if (isNegativeNumber) {
    // ...
}

Now it’s perfectly clear what we were testing for in the if statement: we wanted to know if we had a negative number!

The beauty of variables is that you can use them to give a human readable name to almost any expression. In fact, that’s what variables were designed for! What’s happening is that we consume slightly more memory to greatly enhance readability. Let’s put numbers to that: we are allocating 1 byte (on the stack in this case, not even the heap), but we are getting 2x, 10x, 100x better readability, maintainability, and scalability. Is that a trade-off you’re willing to make? In a more advanced usage, you can actually break down expressions into multiple subexpressions and baptize those subexpressions with human-readable names.

So, yes, we are introducing inefficiency. In return, we gain scalability. I’m not talking about vertical scalability and efficiency where your program is so lean, mean, and efficient it takes almost no memory. I’m talking about scaling the software development process, scaling the project, scaling where – once you add more team members – they will know exactly what your code is doing and how to use/modify it.

In the end, is it all worth 1 byte on the stack? I think it is.