views:

201

answers:

9

In using ReSharper recently, it is suggesting I reduce nesting in certain places by inverting if conditions and using the continue statements.

nested conditionals:

foreach(....)
{
    if(SomeCondition)
    {
        //do some things

        if(SomeOtherNestedCondition)
        {
            //do some further things
        }
    }
}

continue statements:

foreach(....)
{
    if(!SomeCondition) continue;

    //do some things

    if(!SomeOtherNestedCondition) continue;

    //do some further things
}

I understand some of the logic of why you'd want to reduce nesting for performance and memory issues as well as how the two snippets equate to each other, however from my development background, the before example is easier to follow when reading the code.

Which approach do you prefer and why? Do you use continue over nested ifs in your everyday code?

+5  A: 

There should be no significant performance different, this is all about readability. Personally I think the latter is easier to read. Less nesting, easier to read.

Matti Virkkunen
+2  A: 

Simple answer. Which ever one is easier to read and understand.

Jordan S. Jones
+3  A: 

With the continue style code; how would you handle a third operation that isn't dependent on SomeOtherNestedCondition, this makes the order of the code important which IMO makes it less maintainable.

For example:

foreach(....) 
{ 
    if(!SomeCondition) continue; 

    //do some things 

    if(!SomeOtherNestedCondition) continue; 

    //do some further things 

    if(!SomeSecondNonNestedCondition) continue;

    // do more stuff
}

What happens when SomeOtherNestedCondition causes the continue; to happen, but SomeSecondNonNestedCondition should still execute?

I would refactor out each bit of "code" and use nested if()s to call each refactored method, and I'd keep the nested structure.

Nate Bross
The order of code is always important in procedural languages like these. You can't get around that.
Matti Virkkunen
I'd just add another third continue after the SomeOtherNestedCondition. If the the order of the code is important, then you darn well better know that, and know the correct order tp put the code in... using nested `if()`s neither obviates the need to do that nor does it make it easier to "see" it in reading the code... In fact it can easily be argued that using the continue approach make it "easier" to see the order of the code snippets since they are not hidden in all the nesting...
Charles Bretana
+1 good point. Yeah I realize you have to use it where appropriate. Sometimes the logic is simply too complex for the `continue` design.
KP
Maybe I articulated my point poorly, but I think KP got the main point, that sometimes theres too much going on to use the `continue` design.
Nate Bross
Clearly the order is important, but in my example, if SomeSecondNonNestedCondition should execute, in the event that SomeOtherNestedCondition is false it will not. This may or may not be intended behavior, since SomeSecondNonNestedCondition isn't dependent on SomeOtherNestedCondition.
Nate Bross
@Nate: In that case whoever put it at the end of the loop is an idiot. They could have at least read through the loop before doing something like that (even if the policy is "no continue or return statements", you better read through the code to make sure...) If your loop is huge enough to make that unfeasible, then you're doing something else wrong.
Matti Virkkunen
supercat
+18  A: 

As a rule I have found it best to always start statement blocks with any conditions that will except out as it reduces complexity, but more importantly throws out non-compatible circumstances before they are stepped any further which can increase code and memory performance. This also ensures safety of your conditions over a duration through maintenance, that it's less likely to have invalid scenarios passed into code they don't belong in.

Plus I think the second of the two is more readable personally because you don't have the scope layers confusing what's available, it's easy to create a variable in one layer later down the road and not realize it's unavailable in another layer, or having to manage them to be modified appropriately etc.

This isn't just continue in loops, but this also refers to conditions of methods should return; instead of having a method start

if (valid)
{
    do stuff;
}

it should always start

if (notValid)
{
    return;
}
Jimmy Hoffa
+1 Good point. I'm with you on this one. I do generally put all conditions that make the method return/exit at the top. Generally I put exception throwing first, followed by if-return blocks, followed by method logic.
KP
+1 for the "return asap" thing
Diego Pereyra
this is exactly what i do and some of my bosses hate it....
Stan R.
The argument against it is that it's "less structured", in that there's more than one way that the function can end. For instance, you could be in trouble if you forget to do cleanup code before your function terminates.That said, I'm lazy and I don't think it's a big issue, especially in C# where cleanup code is less necessary than in, say, C++. Plus, I prefer my code to be less indented.
Rei Miyasaka
This is almost always my argument so just to play devil's advocate I'm going to mention that occasionally I want to do something (add logging?) just before a method exits and the fact that I often code this way can make it a real PITA. I might start experimenting with the policy that if you are returning a non-error USEFUL value, use a single exit point.
Bill K
@Bill K: in those events, I would write logging methods that return what I would want to return on a bad scope, then you just return LogAndExit(); if a value is necessary you can make a generic return LogAndExit<SomeReturnType>(defaultRetValue);
Jimmy Hoffa
@Jimmy Hoffa I'm not talking about logging exactly, I'm talking about adding a bit of functionality to the end LIKE logging. For example, during debugging spitting out a line of logging that you intend to delete. Other examples might be setting a flag, incrementing a counter or something like that. I'm not disagreeing with your point I use that pattern all the time, I'm just mentioning that it's been a problem occasionally.
Bill K
A: 

however from my development background, the before example is easier to follow when reading the code.

Well - I think, that's your personal opinion. I - for example - try to avoid such nesting as it makes the code harder to read in my opinion.

If you like the "before" version more than the "after" one, use it.
Just configure ReSharper so it suggests what you really want it to.

Always keep in mind: ReSharper is a quite "dumb" refactoring tool - it cannot replace the developer. It can only help him by doing some otherwise stupid copy&paste work.
Some refactorings done by ReSharper result even in situations in whcih ReSharper suggests the opposite refactoring.

Therefore, don't see ReSharper's suggestions as best practices but as possibilities you could do.

BTW: You should be driven by performance considerations here - I would be surprised if there are really notable differences in performance.

winSharp93
Yeah I agree ReSharper doesn't always make the right call. In this case I know the conversion works and is logical. My question is in part because our dev team is small, with varied views on this. I'm interested to know what the c# community at large leans toward.
KP
A: 

There is no difference in terms of memory or performance. The underlying IL is just a selection of branch/jump instructions so it doesn't really matter whether they jump back to the top of the loop or to the "else" statement.

You should pick whichever is easier to read. I personally prefer to avoid 'continue', but if you have many levels of nesting then your second example can be easier to follow.

Paolo
+1  A: 

Using continues makes the bulk of the code on the level of regular procedural code. Otherwise, if there are five checks, you'll indent the "meat" of the method 10 or 20 chars, depending on the indent size, and those are 10/20 chars that you'll have to scroll to see the longer lines.

SWeko
+1  A: 

Use a combination of the two. I use if(condition) continue; and if(condition) return; at the top of the loop to validate the current state, and nested if statements further down to control the flow of whatever I'm trying to accomplish.

adam0101
+2  A: 

Short answer:

I tend to use indentation such that it implies that some real decision logic takes place, ie. logic where more than one possible execution path is expected.

Longer answer:

I usually prefer indented blocks to preceding "breaking" statements (continue, break, return, or throw).

Reason: In my opinion, breaking statements generally make code harder to read. If you indent code, it's easy to find the location where some decision logic happens: that'll be the first line further up the code that is indented less. If you use breaking statements with an inverted condition instead, you have to do more work to understand how code branches and under which circumstances certain code is skipped.

There is one notable exception for me, namely validating arguments at the beginning of a method. I format this code as follows:

if (thisArgument == null) throw new NullArgumentException("thisArgument");
if (thatArgument < 0) throw new ArgumentOutOfRangeException("thatArgument");
...

Reason: Since I don't expect these exceptions to be actually thrown (that is, I expect the method to be invoked correctly; the calling function is responsible to detect invalid input, IMO), I don't want to indent all the rest of the code for something that shouldn't happen.

stakx