Hi Steve,
On 6/27/2011 5:33 PM, Steve at fivetrees wrote:
> To expand my earlier vowel-heavy response:
(sigh) I'd have rather watched Vanna twirl some consonants...
>>> The point here is that there are several places where "goto win"
>>> would have cleaned up the code. The do-while hack runs the
>>> risk of not being familiar to novices (and, also gives trouble
>>> if you want to "break break" -- requiring a flag and some
>>> kludge logic). And, the do-while cheats me out of three
>>> characters per line (because it introduces another indent level)
>>
>> I use goto whenever it results in clearer and more readable code. Using
>> "goto error" in a function that ends like this:
>>
>> error:
>> cleanup_stuff();
>> return retval;
>> }
>>
>> is easy to understand. The 'error' label makes it clear what the purpose
>> is, even without reading any further, and it helps to keep the mainline
>> code free of error handling. It may be possible to rewrite the code to
>> avoid the 'goto', but if that doesn't result in any tangible benefits,
>> why bother ?
>
> There are any number of ways of avoiding this. One is to have an error
> flag; set it if you need to do cleanup (not great).
Booo! Then every conditional after that has to include "!error &&"
(assuming you *don't* want to execute the code protected by the
conditional if you've previously detected an error)
> Another is to return
> FALSE early if errors are encountered (see below; preferred).
But, if error means some form of *recovery* is required, that
means you have to wrap this portion of the code in a separate
function -- so that the caller can examine that error return
and unwind things, as appropriate. If doing that requires
accessing something referenced *within* the called function
(the one that "error returned"), then you have to make that
data accessible to the calling function, etc.
> For me, using a goto is an admission of failure (to find a cleaner way),
> and I've not done this in 30+ years. There is *always* a better,
> cleaner, more comprehensible, and more robust way.
I don't think I would agree with that "unconditionally".
I figure Ritchie, et al are pretty smart folks and didn't
leave that feature in the language "casually".
OTOH, when they created Limbo, the noticeably *removed* the
"goto".
OTOOH, in creating Limbo, the gave "continue" and "break"
an optional argument -- an identifier indicating *which*
enclosing "loop" is being referenced (in effect, where
to "go to" as a result of the statement's execution).
[this must give the MISRA folks fits! continue, break
*and* goto all cohorting together!! :> ]
I don't think anyone is questioning the *need* for "goto".
But, rather, that ruling it out can make code clumsier
(e.g., the Limbo break/continue fits my style of wrapping
complex conditionals in do-while()'s and breaking, accordingly)
>> The extra indentation shouldn't be a problem, though. If you need more
>> than 3-4 levels of indentation, you should probably restructure the code
>> and/or use some extra functions anyway.
>
> I have a code-purity fascist at work ;). I love him dearly, as he's even
> more OCD than me. If I need a sanity check on something I'm doing, I run
> it past him. If he were to object to something I'm doing, it would make
> me think hard... but this hasn't happened yet.
>
> So... recently I ran something I do a lot past him. I had validation
> code that (simplified) looked like this:
>
> if ( ! first_bit_of_data_valid() )
> return( FALSE );
> if ( ! second_bit_of_data_valid() )
> return( FALSE );
> ... some processing...
> if ( ! processing_happy )
> return( FALSE );
> return( TRUE );
>
> These "early returns" get objected to (by n00bs, mainly) on the basis
> that some see them as glorified gotos; I don't. I see them as
> structurally sound, since they all wind up in the same place (a return).
This is equivalent to the example I posted.
The problem comes in when what you really want is:
if ( ! first_bit_of_data_valid() ) {
cleanup_mess_from_first_bit(params1);
return( FALSE );
}
if ( ! second_bit_of_data_valid() ) {
cleanup_mess_from_second_bit(params2);
cleanup_mess_from_first_bit(params1);
return( FALSE );
}
etc.
Sure, you can interpose another function to catch the "FALSE"
return value... but, that function needs to be able to access
all the stuff that the "cleanup" routines access -- potentially
in the context of the "false-returning function".
At some point, this just gets clumsy and forces you to
go out of your way *just* to avoid a goto.
> Consider for a second the indentation levels of multiple tests for
> validity without early returns - every time you do a test, you indent
> one more time. (I have worse examples: imagine maintaining time of day,
> then date.)
Exactly. This is the case with the arbitrary precision decimal
arithmetic package I alluded to previously. The code isn't
overly complex -- "tedious" is a better term (lots of care
to be sure you don't have fence-post errors, etc.). Couple
that with my preference for informative (i.e., *long*) identifiers
and every level of indent starts to hurt. Especially as it forces
same-line comments further and further out (making them shorter
and less effective -- hence my preference for comment blocks)
> So anyway, my OCD colleague thoroughly approved ;).
>
>>>> Similarly, I've seen a lot of code that is absolutely *infested* with
>>>> if/then/elses, where my background in hardware (Karnaugh maps, De
>>>> Morgan's theorem) has allowed me to simplify it to a few logical cases.
>>>
>>> <grin> I find I have to leave notes for folks explaining odd
>>> choices of conditionals. E.g.,
>>> if (! ((*ptr != 'e') && (*ptr != 'E')) ) {
>>> though that would be a bad choice, here.
>>
>> I never worry about simplifying the logic, unless it helps readability.
>
> Friend, I lost you right there. *Simplifying the logic can only help
> readability*. I suspect you're fairly new to this game (which I'm not)
> and have a few things yet to learn (as do we all). That's cool; don't be
> offended and try to keep an open mind about better ways. I've spent most
> of my career trying to find ways of keeping out of trouble (and being
> able to sleep nights) - that doesn't make my approach perfect (e.g. I
> avoid C++ at all costs, much to the scorn of my code-purity fascist
> colleague [I prefer good *design* over good *language*), but I do sleep
> nights [1].
I will often rewrite conditionals to exploit things that I
know to be true of the environment in which the code will
operate. E.g., something that is more likely to affect
a conditional one way or the other might get "promoted"
to the front of the expression -- or, pulled into a separate
expression "enclosing" the other expression (to emphasize that
it is more significant to the algorithms performance). It's
a minor efficiency hack but often can have rewards in loops,
etc.
> Steve
> [1] Except tonight. Too bloody hot. Am sitting by the aircon.
Pish! 30C? It's *40*C at 9P, here. High of 45C today. And
tomorrow. (and I plan on being outdoors much of tomorrow :< )
Of course, unlike your 88%RH, we're sitting at something like *seven* %.