views:

634

answers:

14

During a code review, a senior dev commented on some nesting I had going on in my code. He suggested I set a bool value so that I never have more than one level of nesting. I think my code is more readable but want to get other devs' opinion on this. Which is better style? Is his knee-jerk aversion to nesting founded?

Below are some simplified code examples.

Nested:

If(condition1)
{
    If(condition2)
    {
     if(condition3)
     {
      return true;
     }
     else
     {
      log("condition3 failed");
     }
    else
    {
     log("condition2 failed")
    }
}
else
{
    log("condition1 failed")
}

return false;

or

Bool Driven:

bool bRC = false;

bRC = (condition1);
if(brc)
{
    bRC = (condition2);
}
else
{
    log("condition1 failed");
    return false;
}

if(bRC)
{
    bRC = (condition3);
}
else
{
    log("condition2 failed");
    return false;
}

if(bRC)
{
    return true;
}
else
{
    log("condition3 failed");
    return false;
}
+6  A: 

I generally prefer nested for my conditions; of course, if my nested conditions are getting indented too far to the right, I have to start wondering if there's a better way to go about whatever I'm trying to do (refactoring, redesigning, etc..)

Adam Maras
+17  A: 

I personally find the nested code significantly easier to read.

Michael E
Much easier to visualize the program flow IMO.
Gab Royer
And I would argue MUCH easier to maintain.
Matt Davis
The nested one would have be less error-prone. And you reduce the need of one additional boolean!
thephpdeveloper
And if the nesting starts becoming too much, you can always break down the code into multiple functions. A moderate amount of nesting is fine.
Ates Goral
You'll need a wide screen, though. This is especially true if you also want to keep a sidebar visible, and if you like long function and variable names.
Dimitri C.
+3  A: 

The second style is absurdly verbose: did he really suggest exactly this? You don't need most of those if statements, because there is a return in the else part.

With the nested example you are relying on not forgetting to include any possible else clauses.

Neither of these seems satisfactory to me.

Kinopiko
I agree - fugly++
OMG Ponies
I cringe every time I see that "bResult" anti-pattern.
Ates Goral
+30  A: 

I like yours better, but I'd probably do something like:

if (condition1 && condition2 && condition3)
{
    return true;
}
else if (!condition1)
{
    log("condition1 failed");
}
else if (!condition2)
{
    log("condition2 failed");
}
else
{
    log("condition3 failed");
}
return false;

If the conditions are complex expressions then I might assign the expressions to appropriately named variables before evaluating the if statements to avoid having to recompute the values in each if.

This is assuming that the normal mode is that all conditions are true and thus you want to have that check first. If the normal mode is that one or more conditions are false, then I'd reorder it and check each negation in turn and simply return true if all the checks failed. That would also remove the need for temporary variables to take the place of complex expressions.

tvanfosson
I'm in favor of this as well.
nullArray
i like this, but if you have functions in the condition, you MIGHT call the functions several time (unless you take advantage of short circuit eval)
thephpdeveloper
@Mauris -- true, but that's probably a case where I would evaluate the condition and assign to a local variable to avoid having to recompute it.
tvanfosson
+1 - this is clarifies the normal path of execution, making it much easier to see and maintain the execution flow than either of the intial proposals (http://www.refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html).
Jeff Sternal
This is NOT equivilent, and would not compile, as it does not return false if any of the conditions are false. It eoulf have a compile error of "Not all code paths have a return" (or however that is worded..)
Charles Bretana
@Charles - Whether or not it would compile depends entirely on what language/implementation you're talking about. In Java, no, it wouldn't. In C or C++, sure it would.
Chris Lutz
@Charles -- it was only a code snippet, not the entire function so it wouldn't have compiled in any event. I've added the return anyway just to avoid confusion.
tvanfosson
@Mauris - Makes a good point of storing in local variables makes it easier to debug.
AndrewB
@Chris... ahh yes, in C / C++ function return types are entirely optional, right? I'd forgotten that - I've been coding in C# for a while now...
Charles Bretana
The bigger problem with this is that if there are side-effects while the conditions are evaluated (they're function calls, for example), then calling things the second time is a bug.
Jonathan Leffler
@Jonathon -- my answer addresses this by noting that I'd pre-evaluate the conditions and store the values in variables if the conditions were complex (and I would consider function evaluation a complex condition). Of course, this would also introduce another potential issue in that the original code may have relied on the lazy evaluation brought on by the nesting. You'd have to consider the actual code in question when refactoring to know if this code flow would be equivalent and/or appropriate.
tvanfosson
No one mentioned this but what is really clear this way, and it's good, is that the first if contains the only way to return true. Other ifs return false indicating an error and log that error. I think that this is why it's easier to maintain. Now that I think of it, going a step forward, if appropiate, would be to convert this into IEvaluator classes. Each Evalute method would be each if i think, though I think i would'n log errors in that Evalute method.
+2  A: 

The bool driven way is confusing. Nesting is fine if required, but you could remove some of the nesting depth by combining the conditions into one statement, or calling a method where some of the further evaluation is done.

CRice
A: 

Code shall restate the problem in a language given. Therefore I maintain that either snippets can be "better". It depends on the problem being modeled. Although my guess is that neither of the solutions will parallel the actual problem. If you put real terms instead of condition1,2,3 it might completely change the "best" code.
I suspect there is a better (3d) way to write that all together.

+2  A: 
JRoppert
Redundantly checking the conditions multiple times may not be favorable or possible; may have performance issues or side effects, respectively.
Ates Goral
+1  A: 

I'd probably go with

   if (!condition1)      log("condition 1 failed");
   else if (!condition2) log("condition 2 failed");
   else if (!condition3) log("condition 3 failed");
   // -------------------------------------------
   return condition1 && condition2 && condition3;

which I believe is equivilent and much cleaner...

Also, once the client decides that all conditions should be evaluated and logged if they fail, not just the first one that fails, this is much easier to modify to do that:

   if (!condition1) log("condition 1 failed");
   if (!condition2) log("condition 2 failed");
   if (!condition3) log("condition 3 failed");
   // -------------------------------------------
   return condition1 && condition2 && condition3;
Charles Bretana
+1  A: 

If the language supports exception handling, I'd go with the following:

try {
    if (!condition1) {
        throw "condition1 failed";
    }

    if (!condition2) {
        throw "condition2 failed";
    }

    if (!condition3) {
        throw "condition3 failed";
    }

    return true;

} catch (e) {
    log(e);
    return false;
}

EDIT From charles bretana: Please see Using Exceptions for control flow

Ates Goral
-1 : stylistically cute, but a bad idea for many languages. For example, in Java it is expensive to throw (actually create) an exception.
Stephen C
-1; if it's expensive in Java, it's *exorbitant* in Objective-C
Justin Searls
Why do you assume that every bit of code should care about performance? This could easily have been some initialization logic that only runs once at startup. Complex initializations with multiple points of failure are typical of such initialization functions. You wouldn't typically have three points of failure in a time-critical vector handling function -- unless your vectors are wrong! :)
Ates Goral
I honestly like yours the best from the given answers.
treznik
Thanks skidding, but we seem to have some trigger-happy people who seem to be downvoting the answer without honestly considering the context in which this style would be used.
Ates Goral
+1 Stylistically I like this because it moves logging to the catch block. But as you said, its usage depends on circumstance; wouldn't want to use it for form validation where the conditions are routinely false.
DancesWithBamboo
I dV'd it because Throwing exceptions as an alternative to control flow is an accepted anti-pattern, to be avoided. Even if the code only runs ince a year...
Charles Bretana
@Charles Bretana, I'd be interested in seeing some citations for that anti-pattern. Google didn't readily yield anything, but I'll continue looking.
Ates Goral
Charles Bretana
+17  A: 

If you don't have any silly rules about multiple return points, I think this is quite nice (and so does someone else, but they deleted their answer for unknown reasons):

if(!condition1)
{
    log("condition1 failed");
    return false;
}

if(!condition2)
{
    log("condition2 failed");
    return false;
}

if(!condition3)
{
    log("condition3 failed");
    return false;
}

return true;

Maybe this is an equal knee-jerk aversion to super-nesting, but it's certainly cleaner than his crap storing the boolean conditions in certain values. However, it may be less readable in context: consider if one of the conditions was isreadable(). It's clearer to say if(isreadable()) because we want to know if something is readable. if(!isreadable()) suggests if we want to know whether it's not readable, which isn't our intention. It's certainly debatable that there may be situations where one is more readable/intuitive than the other, but I'm a fan of this way myself. If someone gets hung up on the returns, you could do this:

if(!condition1)
    log("condition1 failed");

else if(!condition2)
    log("condition2 failed");

else if(!condition3)
    log("condition3 failed");

else
    return true;

return false;

But that's rather underhanded, and less "clear" in my opinion.

Chris Lutz
A minor variant on your last alternative: int rc = false; if's as before; else rc = true; end-of-function returns rc. Nothing underhand; the function fails until everything is OK.
Jonathan Leffler
This is much better than the boolean approach, which I would call cargo-cult programming **..** reacting to the superficial evidence of complexity but implementing an equally complex but more obscure alternative that replaces the nesting with something worse.
DigitalRoss
@Jonathan - I'd almost consider it more underhanded to use an unnecessary variable, but it's probably not important.
Chris Lutz
+1  A: 

This is how I would implement it, provided your implementations actually reflect the desired behavior.

if (!condition1) {
    log("condition1 failed");
    return false;
}
if (!condition2) {
    log("condition2 failed");
    return false;
}
if (!condition3) {
    log("condition3 failed");
    return false;
}
return true;

However, I think it's more likely that every failed condition should be logged.

result = true;
if (!condition1) {
    log("condition1 failed");
    result = false;
}
if (!condition2) {
    log("condition2 failed");
    result = false;
}
if (!condition3) {
    log("condition3 failed");
    result = false;
}
return result;
Joshua Swink
+5  A: 

Similar to the nested version, but much cleaner for me:

if not condition1:
    log("condition 1 failed")
else if not condition2:
    log("condition 2 failed")
else if not condition3:
    log("condition 3 failed")
else
    return true;
return false;

Beware that each condition is evaluated once.

GogaRieger
+1 this is equivilent to origiaal logic and substantially terser
Charles Bretana
I just typed this in! I guess I don't need to post it. +1
DigitalRoss
+2  A: 

I don't like either way. When you have so much nests something is wrong. In the case of a form validation or something that does indeed require something like this try to figure something out that's more modular or compact.

An example would be an array holding the conditions, through which you'll iterate with a while, and print/break as needed.

There are too many implementations depending on your needs so creating an example code would be pointless.

As a rule of thumb, when your code looks too complicated, it sucks :). Try rethinking it. Following coding practices most of the times makes the code turn out a lot more aesthetic and short; and obviously, also smarter.

treznik
A: 
if( condition1 && condition2 && condition3 )
    return true;

log(String.Format("{0} failed", !condition1 ? "condition1" : (!condition2 ? "condition2" : "condition3")));
return false;

That way, you don't have to see many lines of code just for logging. And if all your conditions are true, you don't waste time evaluating them in case you have to log.

devMomentum