On Descriptive Conditionals
OK, what just happened there? This type of conditional can be painful to read and debug. Is there a better way?
Pulling anonymous conditionals out into named variables can make reading and debugging code much easier. You can come back to this code a year later and know exactly what it was attempting to do based on the names of the variables. Walking through with a debugger is a piece of cake; we can see exactly which conditional was true or false at runtime without having to perform the checks manually. The second format also allows us to easily modify the conditional values via the debugger to change the execution path at run-time - something difficult to accomplish otherwise. But as with anything in coding, there are tradeoffs. In this particular case, we have a performance hit, and a pretty large one at that.
I’ve taken the following Java code and examined the bytecode produced to get a better idea of what is happening. First up is the straight conditional.
On compilation, this will produce the following bytecode
Reading this, you can see that the compiler takes advantage of short-circuit evaluation. At first, only the first two arguments are loaded and compared. If the comparison is true, it goes to the return statement. If it’s false, the next two arguments are loaded and evaluated, and so on.
Now let’s compare to the documented version:
Here, we have separated the conditionals out and described what they actually mean. This is a very simple example, and arguably useless in the real world, but the idea is the same. Below is the bytecode produced:
The code is now noticeably longer. Breaking it down, you can see that 0-10 stores the
first boolean’s conditional result. 12-22 store’s
second and 24-35 stores
third. Now to the important conditional, 37-55. This looks almost exactly like
methodOne‘s bytecode but it only loads a single value versus two values in
methodOne. It still provides for short-circuit evaluation of the final conditional.
The issues should be obvious at this point.
methodTwo must perform all of the conditional checks, whereas
methodOne takes advantage of short-circuit evaluation to only perform work as necessary. We must also store an additional value during runtime for each conditional, and with that comes the cost of cleaning it up when it goes out of scope.
To compare the run-time performance of the two approaches, I benchmarked the simplest cases that do not take advantage of short-circuit evaluation since the short-circuit may or may not run.
Benchmarking this simple code shows that the mere act of storing a new boolean slows the method down by 18%. With more conditionals and use of short-circuit evaluation, the performance hit will only go up from there.
With that said, this is still extremely fast code; on my laptop running each of these methods 10 million times each,
methodOne was only faster than
methodTwo by 14 ms. That’s only a 1.4 nanosecond difference between each method.
So the question remains, is descriptively naming your conditionals a worthy practice? The answer will depend on your exact situation. For me, I prefer the more descriptive format that naming conditionals provides, and the debugging aspect really puts it over the top. There have been several times where I was able to effectively identify and resolve buggy code by breaking down large and obscure conditionals into their component parts and labeling them with descriptive variables.
My general approach to writing complex conditionals is to use descriptive naming first, then optimizing later if it is needed. The majority of the time, it’s not, and I’d rather be able to read and debug my code than worry about a few nanoseconds performance hit. Refactoring into the optimized form is a trivial exercise. And with the knowledge gained by actually testing both approaches and looking at the bytecode that is produced, you should be able to make a more informed decision as to which is better for any given situation.⬅ Go back