4 ms·
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:
by Luyt 15y ago
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.html http://www.cs.virginia.edu/~cs655/readings/bwk-on-pascal.htm...
- rmcclellan 15y agoNeither 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.
- Luyt 15y agoHmmm, 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).
- anthonyb 15y agoThe 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.
- Luyt 15y ago"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 ;-)
- anthonyb 15y agoSometimes 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.
- nostrademons 15y ago
- buddydvd 15y agoAn 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!
- Xion 15y agoFunctions 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.
- buddydvd 15y agoTake 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?
- gte910h 15y agoYou 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.