In my previous installments, I showed you how a little bit of cosmetic change in the layout of your source code can dramatically improve readability and how tiny changes in your habits can truly affect overall code safety. In that vein, let’s continue and take this a little bit further by visiting Ternary Operators.
One, or the other
Ternary operators exist in almost every programming language in one form or another. They are essentially an expression that says “If it’s this, do the following, otherwise, to something else.” It is a logic construct that we encounter constantly in computer programming daily, in all shapes and forms, and because we encounter it so frequently, ternary operators offer a shorthand solution to this very particular situation.
In C-style languages, a ternary operator looks like this
int deviation = (xOffset > 20) ? 50 : 100;
whereas in Python, the same expression would look something like this
deviation = (50, 100)[xOffset > 20]
Now, at first glance you’d say, hey, this is neat and compact. Not that you’re wrong, but with this kind of terseness comes a set of entirely different problems because ternary operations are inherently
- hard to read
- hard to debug
- easy to mess up
Particularly for inexperienced programmers, ternary terms can be a nightmare to decipher. As defensive programmers, we do not write code that is meant for our eyes only but is inherently easy to ingest and maintain by others. With that in mind, ternaries represent an unnecessary obfuscation of the code’s meaning. I mean, even if you’ve programmed in Python before, were you aware that this kind of ternary operator even existed? Now imagine, you’re looking over someone else’s code and you see an expression like the one above. It would represent an unnecessary stumbling block.
But even experienced programmers can easily struggle with ternaries because of their inherent terseness. Programmers who rely on ternary operators tend to try and “minimize” their source code footprint and often you will also find them creating nested ternaries, which can very quickly become a Jeopardy-sized challenge for anyone to decipher. Not only for other programmers but even for the original coder.
Nothing wrong with if-else
Considering that at its most basic, a ternary operator is nothing but a glorified “if…else” statement, why take all these risks? Why would you deliberately keep your code obtuse? The expression above is no different from the following code
if (xOffset > 20)
deviation = 50;
deviation = 100;
Or, if you want to clean it up slightly, this snippet
int deviation = 100;
if (xOffset > 20)
deviation = 50;
When you look at these different versions, it instantly becomes clear that while the ternary is very compact, the if-else variations are much clearer by comparison. They convey structure and flow, along with a concise understanding of what is going on. Any programmer can read such a basic if-statement—after all, it’s virtually in plain English—and there is no ambiguity at all.
No performance benefits
Moreover, you will be pleased to hear that the compiler creates the same code for both versions. To prove my point, here is the compiler output for all three code snippets.
While the third version generates slightly different assembly code, it is the result of the inherent default value assignment. Regardlessly, however, it results in the same number of instructions with the same processor timings. (As an aside, if you are not familiar with it, Compiler Explorer is an incredibly useful, free online tool that you should be aware of. It allows you to quickly analyze code generation for countless languages, compilers, and compiler settings.)
Because there is no inherent processing benefit, clarity, readability, and maintainability should take the fore and you should essentially banish ternary operators from your vocabulary—or at the very least, limit their usage severely, perhaps restricting them to the most atomic ternary assignments.
But we’re not done yet. The explicit if-statement offers yet another benefit. It is more debuggable. Ternary operators are inherently hard to debug because you can’t trace what is going on on the inside. You can place a breakpoint on them but you will not see what the condition evaluates to and why a, perhaps incorrect, choice is selected. If you place a breakpoint at the beginning of the if-statement, you can step through all evaluations one line at a time and observe the flow of data up close.
I got another one
And just in case you still need a reason to drop the use of ternaries, take a look at this line of code. (I know, it’s not the most sensible example, and the evaluation makes little sense, but that is not the point here. We’re looking at the principle of things.)
pos = 480 + ((xOffset > 20) ? 50 : 100) + (yOffset * (((yDelta < 0.5) ? yDelta : -yDelta)) * 0.5);
It may be compact, but it is also entirely undecipherable. Try to figure out what it does and what the output should be, given a certain input. Sure, you can do it, but it takes time. Chances are you may have to try a few times to get the right result. At the very least you probably have to run it in your head two or three times, just to double-check your logic and the result.
Is this really what you want your code to be like? Undecipherable and hard to follow? Make no mistake, you’re not going to impress anyone with it.
Now compare it to the exploded version using if-statements, instead.
float yScalar = -yDelta;
if ( yDelta < 0.5 )
yScalar = yDelta;
yScalar *= 0.5;
float finalYOffset = yOffset * yScalar;
int xDelta = 100;
if ( xOffset > 20 )
xDelta = 50;
float finalXOffset = 480 + xDelta;
float pos = finalXOffset + finalYOffset;
Once again, while longer, the exploded version is clearer because it illustrates the flow and its dependencies. It makes it easy to read, understand, maintain and debug. Incidentally, the generated code is, while not identical to the ternary, every bit as efficient. There is no need for you to try and generate convoluted statements that you think will generate faster code. In almost every case, you will find that you will fail unless you are most intimately familiar with the inner workings of your compiler and its optimization strategies.
Know your place
Let the compiler worry about building the most efficient code. You just concentrate on writing the best implementation you can. And since we have plenty of screen real estate available to us these days, don’t be afraid to use longer, expanded statements when they will help provide more clarity and safety.
Thanks for stopping by. Preparing content such as the one you have just read takes time and effort to prepare. If you enjoyed it and are using a Brave browser, please feel free to leave a small tip as a sign of your support by clicking on the small BAT icon at the top of your browser window. Your tip is much appreciated and it encourages me to continue providing more content such as this.