(snip on multiple returns)
In long complex functions there may be return statements buried here and
there in the middle, off the screen when either the top or bottom of the
function is examined. These I really don't like because it is just too
easy to not find all the buried return statements, and that leads to
wasted effort when debugging. The person who wrote the code often put
those buried returns in because it was seen as just too much work to
clean things up to get nicely to the bottom of the function.
OK, one case is when there are some tests at the beginning.
In that case, you can put in some if statements, where one branch
or the other covers the whole rest (maybe 99%) of the function.
Not so bad if it is only one, but gets ugly if it is nested two or
three levels. If commented right, ones at the beginning shouldn't
be hard to see.
Another reason concerns tracing code using print statements.
(Sometimes it comes down to that, especially with parallel code,
or event driven GUI code). I use this technique a lot when I need
to do two runs under different conditions and compare what the
program did. (The log of such a run might be 10K lines long.)
In the case of nested loops, well, often enough you don't want
to return, but do still need to get out of nested loops.
My usual one, at least in the case of a for() loop, is to break
and then just after the loop test the (hopefully simple) loop
condition.
for(i=0;i<10;i++) {
(various statements) break;
}
if(i<10) break;
(and inside at least one other loop)
First, unlike just about everyone else, I indent the loop closing
brace along with the loop statement. Note, then, as you look down
the page the next non-indented statement is the if. I might do this
extra if() test for two or three loops deep, and, again much of the
time return doesn't help much.
Two key pieces of information are "entering FunctionA" and
"leaving FunctionA". Marking up the code to allow this is
trivial when there is only one exit point, but it can be a
pain when there are multiple returns.
Reasonably often, though, you want to print out something different
in the two cases.
In most situations there is nothing very wrong with the structure of a
function like this:
int function(void){
if(test1)return(1);
if(test2)return(2);
/* etc */
return(0);
}
For instance, it is easy to see how it works. However in this specific
debugging scenario, it is the worst possible case, since every return
needs to be individually instrumented with its own print statement.
Variants like this:
int function(void){
int status=0;
if(test1){ status = 1; goto end;}
if(test2) {status = 2; goto end;}
/* etc */
end:
return(status);
}
Usually I would do:
status=1;
if(test1) goto end;
status=2;
if(test2) goto end;
You can also do:
if(test1) {
status=1;
}
else if(test2) {
status=2;
else {
(most of the rest of the function)
status=0;
}
return;
or even, with a little luck:
if(test1) {
status=1;
}
else if(test2) {
status=2;
else for(i=0;i<10;i++) {
(most of the rest of the function)
status=0;
}
return;
Such that there is no additional nesting level for the rest
of the function, as it is already inside a loop.
(I assume that there is at least one more statement in the
status=1 and status=2 cases.)
do exactly the same thing, but are no more difficult to
instrument than any other function.
I have done timing tests, where I need some statements at the
beginning and end, which have the same problem.
-- glen