Advertisement

Membership

Login

ACCU Buttons

Single Exit

In CVu 15.4 Francis makes a case that some functions are less
complex if they use multiple return statements. In Overload 55 I
stated my preference for single exit via a single return. I'd like
to explore the examples Francis presented to try and explain my
preference more explicitly.

"if you are a believer that functions should never have more
than a single return you have a problem because however you
reorganise your code the requirement is for two distinct exit
conditions".

I'm a firm believer, but even if I wasn't I'd have to agree that
some multiple returns somehow feel more acceptable than others. And
as Francis says "perceived complexity is a
function of many things". However I don't think it's quite
accurate to say the requirement is for two distinct exit
conditions. To try and explain what I mean, consider the following
implementation of contains[1]:

This is an unusual implementation but it does show that the
requirement is always to return a single value to the function
caller (in this case either true or
false). Exactly how you do so depends on
your choice of implementation which is a different matter. Another
approach would be to design an iterator adapter class that
"flattens" the iteration through a container of containers.

Francis continues "The only ways these can
be combined in a single return statement require either continuing
processing after you know the answer or increasing the perceived
complexity of the code." Here is the heart of the issue -
the complexity of the code. Is a single-return version of contains
necessarily more complex?

Example 1 -
Comparison

What are the differences between these single/multiple return
versions?

Line count. No difference. (I
haven't counted lines containing a single left/right brace).

Maximum indent. The deepest
control in the multiple-return version is 3 - the return in an
if in a for in a
for whereas the deepest control in the
single-return version is 1 - the increment in the while. This is the reason the single-return version
needs fewer lines containing a single left/right brace.

Function count. The
multiple-return version is a single function whereas the
single-return version uses a helper function. The helper function
is useful in its own right and could quite conceivably have already
existed. Small helper functions are significant because they can
help to make other functions smaller and clearer.

Loop scaffolding complexity. By
using the helper function the single-return version has lost a
whole level of iteration scaffolding.

Return expression complexity.
The multiple-return version uses two literals, true and false. One of
these returns occurs at indentation level 3. In contrast the
single-return version uses a single boolean expression at
indentation level 1.

Loop condition complexity. The
multiple-return version has two very simple (and very similar)
boolean expressions as its two for
statement continuation conditions. The single-return version has
one (more complex) boolean expression in its while statement continuation condition. How
comfortable you are with this more complex boolean expression
(using the && short-circuiting
operator) is largely a matter of how familiar you are with this
style.

Style. If you are looking for
an element in a C++ vector you could argue that it's reasonable to
expect to use the C++ find algorithm, as the multiple-return
version does. In contrast the single-return version uses a more
C-like explicit subscript iteration. The difference is quite subtle
in this case but it does serve to highlight an important point
Francis made - "perceived complexity is a
function of many things (one of them being the individual
reader)". I think its fair to say the more experience you
have of "mature" C++/STL style the more readable you'd find the
single-return version.

Example 2 -
Multiple Returns

The second code fragment Francis presented is as follows (some
code elided, I assume the int <->
bool conversions are deliberate):

The difference is the explicit coding of the control-flow
surrounding the return statements. Do you think making the
control-flow explicit is a good thing? If you're not that bothered
I invite you to consider the following:

if (live_neighbours == 3)
return true;

I hope you're more concerned by this lack of indentation. These
days indenting your code to reflect logical grouping is taken as an
article of faith that people forget to question or recall exactly
why it is used. Indentation visibly groups similar actions and
decisions occurring at the same level. If you believe that
indentation is a Good Thing there is a strong case for clearly and explicitly emphasising that
all three return statements exist at the same level. In contrast,
and significantly, the multiple-returns in the first example are
not at the same level.

Example 2 -
Single Return

Francis also presented example 2 using a single return involving
nested ternary operators:

I agree with Francis that this adds nothing in terms of clarity.
In fact I think it's a big minus. This is the kind of code that
gives the ternary operator a bad name. But inside this long and
inelegant statement there is a shorter and more elegant one trying
to get out. To help it escape consider a small progression. We
start with this (not uncommon) pattern:

This is exactly the kind of code that gives single-exit a bad
name. It is overly verbose; it isn't a simple, clear, and direct
expression of its hidden logic. It is better as:

if (expression)
return true;
else
return false;

But this is still overly verbose. So we take a short step
to:

return (expression) ? true : false;

And removing the last bit of duplication we finally arrive
at:

return expression;

This is not better merely because it is shorter. It is better
because it is a more direct expression of the problem. It has been
stripped of its solution focused temporary variable, its if-else, and its assignments; all that remains is
the problem focused expression of the answer. It has less code and
more software. Applying the same process to the chained if-else containing three return statements we arrive
not at a nested ternary operator but at this:

return live_neighbours == 3 ||
live_neighbours == 2 &&
data[i][j];

This is focused on and is a direct expression of the problem in
exactly the same way.

Conclusion

My rules of thumb are as follows:

Almost all functions are better with a single return. The issue
is separation of concerns. Do you separate out the statements that
determine the answer from the statement/s that return the answer?
Multiple-return versions don't whereas single-return versions
do.

Multiple return statements become less acceptable the further
apart they become (both in terms of logical indentation and
physical line number). Large functions have greater scope for abuse
simply because they allow multiple returns to live farther
apart.

Multiple return statements are more acceptable when they are all
at the same level of a mutually-exclusive selection. In most cases
these multiple returns can be refactored into a more expressive
single return.

But remember, dogmatically following rules is not a recipe for
good software. The best software flows from programmers who think
about what they do and who follow principles and practices that
naturally generate quality.

Many thanks to Kevlin for an insightful review of a first draft
of this article.