Re: Job Interview, Did I Mess Up?
"Daniel T." <daniel_t@earthlink.net>
You skipped over the my mentioning exceptions. As exceptions can
happen, it is no longer just a style, and if one thinks some layout
*has* the actual SESE property just by restricting to a single return
-- is quite mislead. Thus imposing danger.
Here too, I will make another attempt at explaining myself. You have
brought up several points and I will attempt to cover them all, but the
last item is IMHO the most importnat so I will cover it first.
_Exceptions to the Rule_
Of course there are exceptions to every rule (including this one. :-) As
programmers we have lots of different design goals and many of them call
for mutually opposing solutions, coding is often about trade-offs. It is
not wrong of me to avoid multiple exits when there are no important
design or performance constraints requiring them. To put it another way,
there is no design rule that says "prefer multiple exits from blocks of
code," yet that is what you seem to be advocating. (If I am wrong here,
then we are probably not that far apart in our positions.)
:-( I definitely mentioned 'exceptions' as in the C++ thingy related to
throw/try/catch and in that meaning. And after the second iteration it is
unclear how it can be not an obvious and serious problem to a C++
programmer, looking at the discussed situation and reasoning.
I can see where this issue might have stemmed from the use of the word
"dogmatic." I said "pretty dogmatic" and you might have thought that I
meant that I insist on single returns no matter what the situation.
Well, I guess that is the vocabulary meaning of dogmatic ;-)
That
is not exactly what I meant, rather I meant that I am more willing to
expend the extra effort to find a single return solution than most other
programmers.
That sounds much better. Yet still not at the optimum. Why it is good to
put extra effort to shape a piece of code against its natural way? Really.
(As when single exit is natural, no effort is needed, and when it is not,
then you fight to reach a suboptimal shape, do you?)
I am certainly not advocating to create extra exits, or multiple ones just
for fun, but advocating to use exactly the amount that is good. Let it be
one, two or 42. Not even thinking to go into statistics which is more
frequent, as it is irrelevant, all that counts is the actual case you deal
with.
Hopefully the above clears up this issue, but if not then continue
reading...
_Classic Example, Finding Something in a Collection_
Your "classic example" is IMHO a very good one. When we search for a
thing in a collection, should we prefer multiple exits? I think not.
No, we should not prefer anything up front before facing the code.
This means approaching the problem in a different way, but it doesn't
necessarily entail what you called a "framework of flags or other
obfuscation measures."
Yes, it is possible to have special situations.
If we approach the problem from the assumption
that multiple exits are to be preferred, then we might write it like
this:
template< typename InIt, typename Tp>
InIt find(InIt first, InIt last, const Tp val) {
for (InIt it = first ; it != last; ++it)
if (*it == val)
return it;
return last;
}
If we try to avoid multiple returns we might end up with something like
this (cribbed from GCC's library):
template< typename InIt, typename Tp>
InIt find(InIt first, InIt last, const Tp val) {
while (first != last && !(*first == val))
++first;
return first;
}
Wow, I see you carefully selected those special situations. Now how about
picking some general example, that does not have such special property with
the return value.
Say you search in a vector and supposed to return the index (0 to size()-1)
or some special value npos when not found. (similar to string::find()).
Or search a list and return the address of the found value -- or NULL if not
there.
The important point being that the special not-found value does not have any
relation with anything you naturally use as the iterator.
Now you probably would not write the multiple return example exactly as
I did, but however you write it, I don't think that the multiple return
example should be preferred as a design rule.
What was never stated -- as we know the implication is not invertable that
way.
The design rule is to create correct code that is also easy to read, review,
modify, etc. Using either a SESE or SEME shape is no goal but a means to
reach it. You chose the solution fitting the probem instead chasing
application of a stock "solution".
I am even willing to add a
"result" variable to avoid multiple returns, but I'm not going to throw
in a bunch of flags, that would be silly.
Yes, addig a "result" variable and assigning it at several places is the
common BAD practice that follows from chasing SESE where it is not at all
necessary.
(As a side note, the GCC library has a separate 'find' for use with
random access iterators that does use multiple returns and looks
remarkably like a somewhat cleaner version of Duff's Device. This is
obviously a performance optimization, which is the one thing that trumps
design near the end of a product's construction.)
Looking at Dinkum's basic_string::find implementation, it has 3 returns,
having a pretty natural natural layout, like
if( needle is empty && startpos is inside)
return startpos;
iteration( ... )
{
... return pos where match found
}
return npos; // no match
Can it be forced to SESE? Sure. Would it be better? i keep my reservation
until shown a really elegant alternative.
_Postconditions and Where to Check For Them_
Again we come back to not repeating ourselves. If the post condition of
a function can be checked by code, then it should be. I think we can
agree on that, but where should we do the checking? Your suggestion was
that this checking should be done "at a caller site (as normally done in
unit tests)."
Most functions are complex enough that they require multiple tests, so
they are generally called from multiple sites. If we want to put the
postcondition checking code in only one place (so we don't repeat
ourselves,) then the obvious place to put it is just before the return
within the function.
You mean you want to mix the testing code right inside the production code?
And keep the production code textually instrumented like an intermediate
file of Coverity or a similar tool?
Hopefully others read this topinc and can comment whether they do it or
not...
Also, "before return" is not a realistic place for (I'd guess the majority)
of the non-void functions, as return will have some expression that is yet
to execute as part of it.
So you'd have to significantly cripple the code (about to the level of
making the best tool: reviews infeasible). Bad trade.
Where quality works, people write literary code banning any clutter, and run
non-intrusive tests. (Or if anything intrusive is needed do it through some
generator.)
If there is more than one return, then we still
must repeat ourselves; which is the one thing that we have both agreed
we should avoid.
That is not true, as the separate returns actually match a different portion
of the postcondition, so there is nothing to repeat. Like in the previous
find() example, we have separate cases for disjunct parts of the
requirement. If you want to check them inside, you need only part of the
condition. While checking outside is just a long list of input/expected
output pairs written in black-box manner.
Hopefully this post will clear up my thinking about these issues and
cover any concerns or confusion you may have.
Well, it did cover some points, reveled one miss and some gaps.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]