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.
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'.
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).
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.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 ;-)
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. 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! 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?
As an example, this deeply nested code:
I tend to write like this: 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...