4 ms·
Tom Duff: Reading Code From Top to Bottom (1999)
- mquander 15y agoCan someone older or wiser than me discuss what the idea is behind "only one return per method" in imperative code? I agree completely that it makes many methods much more difficult to read, and I've never understood why it was taught as a guideline in the past (and why some people still seem to subscribe to it today.) EDIT: It makes a lot more sense to me now that I think about it in the context of a language like C, with no garbage collection and no exceptions. Thanks!
- brnstz 15y agoIt was part of the structured programming dogma. One entry point, one exit point. Multiple return was lumped in with using goto. I never use goto, but I often use multiple returns similar to the examples in this article. I have sadly code reviewed many pieces of code with the nesting problem.
- malkia 15y agoif you have to use C++ without exceptions (console video games), then goto is much needed to handle exceptional situations locally. For example there would be label "fail:;" or "out:;" at the end of the function, where release of resource would happen, and you would "goto" to it, rather than releasing the resources in multiple exceptional places and return from there.
- brnstz 15y agoInteresting. I rarely program in an environment without exceptions, so I haven't had reason to adopt that style. I recall that Linus has advocated the use of goto in the Linux kernel for similar reasons. And the goto version of this code does seem more readable to me: http://kerneltrap.org/node/553/2131 http://kerneltrap.org/node/553/2131
- cpeterso 15y agoIf you are using C++ (even without exceptions), then RAII (Resource Acquisition Is Initialization) with resources freed in local variables' destructors is a better solution.
- __rkaup__ 15y agoWhy don't console games use exceptions?
- to3m 15y agoNo compiler support on one common system. You should be handling every case anyway - by the time the code is done, there are no exceptional situations left ;)
- kabdib 15y agoMakes maintenance simpler; any resource cleanup can go through one point and you can see that it is correct when modifications are made. (C++ gives this to you with destructors, mostly). Also makes it easier to step through code ("I'll just set a breakpoint and see what this function returns..."). I usually advocate using early returns for near boilerplate things like parameter checking, then a single return that does cleanup and exit, with 'goto' encouraged as the proxy for a return-in-the-middle. I think of it as similar to what a C++ compiler does for you. These days I code for maintenance, since someone /is/ going to go in and start adding early returns and subsequent bugs. Plan for code rot.
- ori_b 15y agoFar easier to do stuff like 'assert(postcondition)'
- chadnickbok 15y agoOne, very simple, example: int some_function( some_variable ) { if (some_variable) { b = 1; } else { b = 2; } log("Its important I log that I'm returning %d\n", b); return b; } If its not obvious why this is superior...
- palish 15y agoint some_function( some_variable ) { log("Its important I log that I'm returning %d\n", (some_variable ? 1 : 2)); return (some_variable ? 1 : 2); }
- alisey 15y agoIn dynamic languages you can do this: some_function = function() { if (...) return 1; if (...) return 2; }.log("It's important I log that I'm returning");
- cpeterso 15y agoThe single return is also useful for post-condition assertions of return values and internal state.
- enneff 15y agoGo's defer feature, combined with named return values, lets you do this: func someFunction(b bool) (i int) { defer func() { log.Printf("I'm returning %d", i) }() if b { return 1 } return 0 } For more on defer, see this blog post: http://blog.golang.org/2010/08/defer-panic-and-recover.html http://blog.golang.org/2010/08/defer-panic-and-recover.html
- bumbledraven 15y agoDuff's advice on avoiding if/else is echoed in a functional programming context by CMU's Bob Harper, who writes, "[A]void if-then-else entirely, and instead use only case analysis for branching..." http://existentialtype.wordpress.com/2011/04/17/some-advice-on-teaching-fp/ http://existentialtype.wordpress.com/2011/04/17/some-advice-...
- mahmud 15y agoUhh, not same reasons. Harper is an ML hacker, he wants to avoid IFs to encourage generic programming and pattern matching. Adding a new branch to an if clause requires some nesting, adding a branch to a case/cond is just one entry of many. After that, pattern matching becomes natural an you tend to see a function not as a single wholesome "body", but a family of mappings. Again, switch, duff's device & other computed-goto hacks are "constructs", a chunk of "decision" code that sits in the middle of the page directing traffic. Pattern-matching is far more sublime than that: along with manifesting itself as a visual construct, it can also hide in the head of the function, cozzying up with its parameters, and also in binding/assignment forms. A distant, but weaker cousin of pattern-matching is destructuring, more common in Lisps.
- bumbledraven 15y agoI found a few other interesting little notes on his site: http://iq0.com/notes/trig.html http://iq0.com/notes/trig.html All you need to know about Trignometry http://iq0.com/notes/frobenius.html http://iq0.com/notes/frobenius.html The Frobenius-Burnside Counting Formula http://iq0.com/notes/geometric.html http://iq0.com/notes/geometric.html Sum of a Geometric Series
- barrkel 15y agoOn the topic of loops which end in a break ("Most egregious is the loop whose last statement is break;!"): this can be useful for avoiding contorted control flow when you have a switch statement inside the loop. Instead of coding: cont = 1; while (cont) { foo = bar(); switch (foo) { case 0: cont = 0; break; default: // other work break; } } You can do this instead: for (;;) { foo = bar(); switch (foo) { case 0: break; default: // other work continue; } break; } IMO the idiom, once you're used to it, reads better. (Yes, I know you can reduce the lines further e.g. putting foo=bar() into the for statement, but it's the multi-level break I'm focused on here.)
- ch 15y agoWhy not: while( (foo = bar()) != 0 ) { /* other work */ } Or even simpler (since 'foo' serves no real purpose in the example): while( bar() ) { /* other work */ }
- deleted 15y ago[deleted]