What is a short-circuit statement? In this case, I'm not talking about the language feature related to boolean comparisons, but instead I'm talking about statements which cause a method to return as soon as some conclusion has definitely been reached. Here's an example from a very simple
compareTo
method in Java:public int compareTo(Foobar that) {
if (that == null) return -1;
if (this._value < that._value) return -1;
if (this._value > that._value) return +1;
return 0;
}
In this example, each line (except the last one) would qualify as a short circuit statement; that is, they all return as soon as a definite answer is determined. If we weren't using short circuit statements, then the code may look like this:
public int compareTo(Foobar that) {
int result = 0;
if (that == null) {
if (this._value == that._value) {
if (this._value < that._value) {
result = -1;
} else {
result = +1;
}
}
}
return result;
}
For something this simple, there isn't a huge difference in the complexity between the two functions, but it still demonstrates the point. Many people ardently recommend always having a single
return
statement in any function, and would strongly advocate using the second example over the first. However, I would argue that the first is superior because it better respects the Unit Economy of the reader.Short circuit statements support Unit Economy because they allow a reader to take certain facts for granted for the remainder of a method. In the first example after reading the first line of the method the reader knows that they will never have to worry about the
that
variable having a null
value for the rest of the method. In the second example, the reader will have to carry the context of whether he is still reading code within the first if
statement. Every short circuit statement removes one item from the set of things one must consider while reading the remainder of the method.Naturally, this example is pretty simplistic, and it's a stretch to claim that either method is more complicated than the other. However, consider if this weren't a simple example. If this were a formula to compute an amortization table for a home mortgage, then the first few lines may look like this:
public AmortizationSchedule computeSchedule(
int principle, float rate, int term) {
if (principle <= 0) throw new IllegalArgumentException(
"Principle must be greater than zero");
if (rate < 0.0) throw new IllegalArgumentException(
"Rate cannot be less than zero");
if (term <= 0) throw new IllegalArgumentException(
"Term must be greater than zero");
// Here is the 20 or so lines to compute the schedule...
}
In this case, there may be a substantial amount of code following this brief preamble, and none of it has to consider what may happen if these invariants are broken. This greatly simplifies the act of writing the code, the logic of the code itself, and the process of maintaining the code later on.
Thoughts? Comments? I'd love to hear them.
Single return values may have been somewhat reasonable in languages such as C where there are no exceptions. In languages such as C++ (with exceptions turned on) and Java, you have many, many hidden return paths anyway, and thus the argument that a single return statement makes the flow more readable doesn't hold at all. Even more reason to setup your preamble and do all the error checking up front, thus asserting what you believe are truth values later on in the function.
ReplyDeleteHaha, I was just going to comment that radoshi made his exception point when I first started at Pelago and I thought it was a great argument against the single return statement paradigm (at least in Java).
ReplyDeleteAlso, I think Andrew's point holds even for languages like C. I prefer checking the conditions up front to layering 'if' statements. It's probably not a coincidence that layered 'if' statements contribute to function points when calculating code complexity.
While I agree with this post in general, there are counter examples that show that short circuites are not always a good idea.
ReplyDeleteConsider Andrew's last example. If the first precondition is not met, there's some value in continuing execution past that point. You can, for instance, verify the other preconditions. An IllegalArgumentException is more valuable if it lists all the arguments that are invalid, not just the first.
More broadly, this issue comes up all the time in software testing. A given test may pass or it may fail for any number of faults. If a test is short circuted to stop on the first fault it finds, it leaves other faults undiscovered. If a test can uncover more than the first fault, that's a good thing, and short circuiting is not preferable.
One may argue that the validation of arguments could be abstracted into another method and that a complex test could be split up into simpler tests that verify fewer conditions. This idea, though, just pushes the same issue of short circuiting into a different level of abstraction.
Still, these cases are exceptions, not the rule. Most software is not written like tests.
Thanks for the feedback! I think the point you're making applies when there's some utility in knowing exactly which conditions were violated (e.g. validating user input from a form, or in testing a variety of use cases). However, in the vast majority of programming, one doesn't need to verify each of the preconditions, one simply needs to know that any *one* of them has been violated. Depending upon which context the last example is used, it may fall into one or the other of those two categories.
ReplyDelete