News for hackers. Stuff that matters.
Re:Why Python is Important for You by Luyt on Saturday February 11, 2012 @03:12PM (#3580035)
I find a great way to avoid deep nesting is to move code to functions, and bail out when some conditions are not met.

As an example, this deeply nested code:

    def frob(someargs):
        if something > 2:
            ... # do some processing
            if that == "CONNECT":
                ... # some more processing
                if verdict in blessedsolutions:
                    ... # even more processing
                    print "Happy birthday!"
I tend to write like this:

    def frob(someargs):
        if something <= 2: return # Not our concern.
        ... # do some processing
        if that != "CONNECT": return # Not handled here.
        ... # some more processing
        if verdict not in blessedsolutions: return
        ... # even more processing
        print "Happy birthday!"

I do the same thing in loops, using 'continue'. The above second example looks rather flat, but in practice the 'do some processing' statements often need identations due to conditions and loops.

I added the processing to demonstrate that it's not always possible to combine conditions. And, of course this way of 'bailing out early' vs. 'nested ifs and processing' is not Python specific. The dogma that a function should have only one exit point (as proposed by 'structured programming' adepts and Pascal afficionados) often leads to deeply nested control structures and convoluted testing. Brian Kernighan has written a paper on this[1].

[1] http://www.cs.virginia.edu/~cs655/readings/bwk-on-pascal.htm...

Re:Why Python is Important for You by rmcclellan on Saturday February 11, 2012 @03:24PM (#3580055)
Neither solution is great. Excessive use of return and continue is an incredibly fast way of making code completely incomprehensible.

Such complicated control flows should be treated as a major, fundamental problem. Keeping them in place will cause bugs and prevent future contributors from having confidence that their changes are correct. The problem of complicated control flow is too deep to be solved with a band-aid like this, although perhaps syntactically it looks a bit nicer. In fact, this is exactly what people did before the so-called "structured programming adepts" came around, except using gotos instead of continues. It didn't work so well back then, and it doesn't work so well now.

Re:Why Python is Important for You by nostrademons on Saturday February 11, 2012 @05:37PM (#3580388)
It works fine as long as the early returns are for error-checking or default values. Generally, you want each function to do one thing and do it well; one very effective way to structure this is to bail out early if that one thing can't be accomplished, and then have the body of the function actually do it.

If the actual logic requires many branching paths, then usually I'd reach for a more structured solution like a chain-of-responsibility pattern, fanout, or FSM. Also, you can get pretty far with a bunch of predicate functions composed together with 'and'.

Re:Why Python is Important for You by Luyt on Saturday February 11, 2012 @03:40PM (#3580101)
Hmmm, you have a point there, but what is your proposal to solve it in a correct way, then?

Some problems need a deeply nested control flow and arcane business rules, you have to implement that somehow, and in such a case the method I use looks like the lesser of two evils. I'd rather have some flat ecosystem of meaningful functions than one monolithic deeply nested control structure.

From time to time I like defensive programming, and when I write a function 'frob' that promises to frob something, that function first makes sure that the thing can indeed be 'frobbed', and if not, bails out. This also works well for programs that should be idempotent (although that is a whole other issue).

Re:Why Python is Important for You by anthonyb on Saturday February 11, 2012 @04:29PM (#3580230)
The way I would do it is to push the processing out to functions, too. Something like:

  verdict = get_verdict(the_thing)
  if verdict not in blessedsolutions:
      return
I'd advise against running returns onto the same line - I find it makes it harder to read, particularly when you have a lot of checks like this.
Re:Why Python is Important for You by Luyt on Saturday February 11, 2012 @04:37PM (#3580251)
"The way I would do it is to push the processing out to functions, too."

This is indeed a good idea to isolate code away, but wouldn't alleviate a deeply nested control structure. It would make it somewhat more easier to look at, though.

"I'd advise against running returns onto the same line "

I usually follow PEP8 and place returns and continues on the next line, but the code examples I posted earlier had to be a bit condensed to conserve screen space, that's why ;-)

Re:Why Python is Important for You by anthonyb on Saturday February 11, 2012 @09:58PM (#3580965)
Sometimes your problem space is really just that complex, in which case there's not much you can do, short of writing some sort of business rules engine.
Re:Why Python is Important for You by gte910h on Saturday February 11, 2012 @07:45PM (#3580657)
You should try inverting that code with guard statements:

    def frob(someargs):
        if something <= 2: return

        # do some processing
        if that != "CONNECT": return
        
        if verdict not in blessedsolutions: return
                    
        print "Happy birthday!"
Code Complete 2 suggests this for many types of logic when overly deep nesting bites you.
Re:Why Python is Important for You by buddydvd on Saturday February 11, 2012 @07:29PM (#3580616)
An less-known technique to dealing with nested-ifs is through the use of an one-time loop. Here's an example (in JavaScript):

    function oneTimeLoopMethod(x) {
      var result, temp = -1, a, b;
      do {
        ... extra processing code ...
        a = func_a(x);
        if (!a) break;
        ... extra processing code ...
        b = func_b(a);
        if (!b) break;
        ... extra processing code ...
        temp = b;
      } while (false);
      result = postProcess1(postProcess2(postProcess3(temp))); // This step can be more complicated.
      return result;
    }
It's useful when you want do additional processing before returning the final result to the caller. Because, otherwise, you'd have to duplicate the processing code in multiple places. When I first read about this technique, its description cautioned about the lack of clarity. So, use it at your own risk!
Re:Why Python is Important for You by Xion on Sunday February 12, 2012 @10:43AM (#3582047)
Functions that need to employ this trick are often overly complicated and could use splitting into more manageable ones. For one, you could relieve the need of do-while-false itself by moving its content into separate function and replacing break with return.
Re:Why Python is Important for You by buddydvd on Sunday February 12, 2012 @08:37PM (#3583786)
Take this code snippet for example:

    function func(x) {
      var a, b, c, result = -1;
      a = getA(x);
      if (a) {
        b = getB(a);
        if (b) {
          c = getC(b);
          if (c) {
            result = calc(c);
            release(c);
          }
          release(b);
        }
        release(a);
      }
      return result;
    }
What would you do? Would you create a function for each of the nested block?

    function func(x) {
      return _calc1(x, -1);
    }

    function _calc1(x, default) {
      var a = getA(x);
      if (!a)
        return default;
      var result = _calc2(a, x, default);
      release(a);
      return result;
    }

    function _calc2(a, default) {
      var b = getB(a);
      if (!b)
        return default;
      var result = _calc3(b, x, default);
      release(b);
      return result;
    }

    function _calc3(b, default) {
      var c = getC(b);
      if (!c)
        return default;
      var result = calc(c);
      release(c);
      return result;
    }
Or, use guard clauses and write your code like this:

    function func(x) {
      var a, b, c, result = -1;
      a = getA(x);
      if (!a)
        return result;
      b = getB(a);
      if (b) {
        release(a);
        return result;
      }
      c = getC(b);
      if (!c) {
        release(b);
        release(a);
        return result;
      }
      result = calc(c);
      release(c);
      release(b);
      release(a);
      return result;
    }
Instead of doing that, with a do-while-false loop, you can write your code like this:

    function func(x) {
      var a, b, c, result = -1;
      do {
        a = get(x);
        if (!a) break;
        b = get(a);
        if (!b) break;
        c = get(b);
        if (!c) break;
        result = calc(c);
      } while(false);

      if (c) release(c);
      if (b) release(b);
      if (a) release(a);

      return result;
    }
Note that this type of deep nesting are pretty common with Window-based COM programming. They usually go much deeper. With the do-while-false loop technique, you 1.) avoid creating one-time-use helper functions, 2.) consolidate post-processing/clean-up code, 3.) have only one exit point.

Can you think of a better way to tackle this problem?