From: BobF on
On Wed, 01 Jun 2005 13:50:11 +0200, Robert Marquardt wrote:

> BobF wrote:
>
>> I've seen this - and seen it debated - a lot. One could argue that the
>> four successive if blocks make the code easier to follow. Everything you
>> need to understand is in front of you. 'return oops()' forces the reader
>> to some other place in the code, or even another file to figure out the
>> functionality.
>
> You are wrong.

Wow ... lots of room for differing perspectives here, I see :-)

> If function oops() or a macro OOPS() are equivalent in
> understanding the functionality.
> What i dislike is the 4 lines needed (plus the empty line). With 5
> blocks you get a staggering 25 lines where about 5 would be sufficient.

I'll agree in the case where the author is the one reading the code. When
its some poor person trying to acquaint themselves at some point later, I
don't necessarily agree.

Now, if oops() (or OOPS()) are a widely used convention throughout a large
project, then I agree completely.
From: Wan-Hi on
i thank everyone sharing his / her thoughts. it's clear to me now that proper
device io code should expect all failures, even the 'exotic' ones. the
discussion about everyone's favourite failure checking loop was quite
informative. not knowing some of the expressions i read, e.g. k&r (?), i
decided to go with 'while', empowering the user to decide when to quit and
when to retry:

// Retrieving the device info set of currently installed devices exposing
// the volume interface.
while(true)
{
hVolInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_VOLUME,
NULL,
NULL,
DIGCF_DEVICEINTERFACE |
DIGCF_PRESENT);

if (hVolInfoSet != INVALID_HANDLE_VALUE)
{
break;
}
else
{
switch(ShowErrorMessage(GetLastError(), MB_RETRYCANCEL))
{
case IDRETRY:
continue;
default:
return false;
}
}
}

although my first problem of nested blocks didn't vanish, i find this way of
failure checking easier to read. so i think i'll favourize code robustness
and easier readibility over code line 'packing'.
From: Maxim S. Shatskih on
> depth to which you must nest 'if' statements. Version 1.0 of most programs
> and drivers can and should be discarded for a better design later.

Not affordable in general due to PITAs to debug the new code - the old one was
stable. Large timeframes.

> Commercial pressures make most of us keep the old code, so then you just
> have to improve it block at a time as you have the time or you need to
> modify or fix a particular area.

That's why "refactoring" was invented. Minor remakes, each standartized a lot,
which change no functionality and no performance, but just make the code
clearer. This is especially necessary for OO languages (bad OO code is much
better then bad C code).

The usual refactoring steps are like - splitting the function to 2, moving
functions from class to class ("envious" function - defined in class B and uses
lots of internal details of class A - move it to class A), and so on.

Refactoring can be done step-by-step without rewriting from scratch. Each minor
step will hardly introduce a bug, but, as a cumulative effect of lots of such
steps the code starts to be much better.

Surely all of this is not an issue if you need extreme performance. In these
cases, manual loop unrolling is good, using if() goto... instead of if() {...}
is usually good, "the most popular execution path must be jump-free", and so
on.

Coding for extreme performance is a talent. BTW - Linux code is good in this
aspect, though it is poor in being structured - lots of gotos etc.

--
Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
maxim(a)storagecraft.com
http://www.storagecraft.com


From: Maxim S. Shatskih on
With the high-performance requirements, the usual way is

if( !condition ) goto Error;

with the Error: label being long below in the very end of the function. In
this case, you will avoid 1 jump for successful condition (which is nearly 100%
so - the condition is just a guard of malformed input data and exists for being
defensive only).

--
Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
maxim(a)storagecraft.com
http://www.storagecraft.com

"Wan-Hi" <WanHi(a)discussions.microsoft.com> wrote in message
news:1F0C555F-F0DA-44FB-A3FD-2F791F54F2CE(a)microsoft.com...
> the fact that excessive if block 'nesting' causes maintainance problems was
> the very reason why i reconsidered my way of failure checks and asked you.
>
> i don't really believe that it's generally caused by a bad algorithm.
> logically
>
> if (failure)
> {
> do clean up;
> show error;
> return;
> }
> else
> {
> if (nextFailure)
> {
> do clean up;
> show error;
> return;
> }
> else
> {
> ...
> }
> }
>
> is logically more correct than a code avoiding 'else' blocks. in my opinion,
> functioning code avoiding 'else' blocks is a result and benefit of the
> language specific 'code execution path'. so the algo is not that bad, but the
> design is. this does not mean that i hold up the opinion to use 'else' if
> unnecessary. i also second you that i should reconsider my design when i
> encounter excessive if block 'nesting'.
>
> "David Craig" wrote:
>
> > If you are coding if blocks within if blocks more than three or so levels
> > deep, you need to reconsider your algorithms and design. It is not
> > necessary. It leads to bad results since the code becomes far more
> > difficult to debug, understand, and maintain. Most bad code can be fixed
> > with better algorithms and a redesign. That 'do once' block helps limit
the
> > depth to which you must nest 'if' statements. Version 1.0 of most programs
> > and drivers can and should be discarded for a better design later.
> > Commercial pressures make most of us keep the old code, so then you just
> > have to improve it block at a time as you have the time or you need to
> > modify or fix a particular area.


From: Pavel A. on
"Wan-Hi" wrote:
> i thank everyone sharing his / her thoughts. it's clear to me now that proper
> device io code should expect all failures, even the 'exotic' ones. the
> discussion about everyone's favourite failure checking loop was quite
> informative. not knowing some of the expressions i read, e.g. k&r (?), i
> decided to go with 'while', empowering the user to decide when to quit and
> when to retry:
>
> // Retrieving the device info set of currently installed devices exposing
> // the volume interface.
> while(true)
> {
> hVolInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_VOLUME,
> NULL,
> NULL,
> DIGCF_DEVICEINTERFACE |
> DIGCF_PRESENT);
>
> if (hVolInfoSet != INVALID_HANDLE_VALUE)
> {
> break;
> }
> else
> {
> switch(ShowErrorMessage(GetLastError(), MB_RETRYCANCEL))
> {
> case IDRETRY:
> continue;
> default:
> return false;
> }
> }
> }
>
> although my first problem of nested blocks didn't vanish, i find this way of
> failure checking easier to read. so i think i'll favourize code robustness
> and easier readibility over code line 'packing'.

Pardon me but now this code fragment looks
ever more sloppy and less readable.

A sequence of actions that can (unlikely) fail, IMHO should look
exactly like what it is: a sequence - wrapped in try-except or
try-finally, or a function that returns success or failure.

Each element of the sequence can be wrappend into a checker macro
like in my previous post.

If you want to display a messagebox, fine.
Then the checker macro becomes a bit more complex;

#define CHECK( expr ) \
while(1) { \
if( check_helper(expr) ) break; /*success*/ \
if( ID_CANCEL != Msgbox( #expr ) ) { /*user wants continue*/ \
continue; \
} \
return false; /* or throw */ \
}

Now the sequence becomes:

try {

CHECK( hVolInfoSet = SetupDiGetClassDevs(...) );
CHECK( b = SetupDiSomething(...) );
CHECK( b = SetupDi...(...) );
CHECK( b = SetupDi...(...) );
// etc
} catch (...) ...

Of course tastes vary. Maybe there still are developers
payed for lines of code or number of operators...

--PA