Live data from Hacker News

Avoid Else, Return Early (2013)

blog.timoxley.com

61–70 of 601 posts

Re: Avoid Else, Return Early (2013)

#61
Two uses for tests are (1) detecting a corner case or other non-standard or exceptional case, and (2) performing case analysis on a type that can have two values.

(1) often raises an exception or returns an error, failure type, or zero type. The clearest implementation is generally as a guard clause, for the reasons in the neighboring comments and their links.

(2) might most explicitly be represented by changing the boolean to a two-valued enum or (parameter-free) union type or algebraic type; or at least by using a switch statement with cases for true and false. For example, an `ascending` boolean variable could be replaced by a `sortOrder` variable, whose type has values Ascending and Descending. These more explicit alternatives are also more verbose, though. Using a boolean to represent a two-valued type is such a common and readable idiom that introducing an extra type is often a readability and maintenance hit, even if it's more explicit. Similar, using a case statement instead of an if statement might match the underlying math or design, at the expense of verbosity. I generally go with an if statement with two arms, instead of an early return, in to make clear that the branches represent case analysis rather than one normal and one exceptional path.

Re: Avoid Else, Return Early (2013)

#62
post #41

It depends! But it depends on the actual meaning of the code, so examples which use meaningless identifiers like `doStuff()` miss the distinction. If the conditions are semantically symmetrical, if/else is the right approach: fun max(a, b) { if a > b { a } else { b } } But is it a precondition which causes you to skip the primary logic of the function, then get it out of the way early: fun max(a, b) { // special case…

Yeah no hard rules, agreed on this point, especially when the if/else body is short.

Re: Avoid Else, Return Early (2013)

#63
This also works well with a C++ `defer` helper.

    #define CONCAT_LITERAL(x, y) x ## y
    #define CONCAT(x, y) CONCAT_LITERAL(x, y)

    template 
        struct DeferWrapper {
            F f;
            DeferWrapper(F f) : f(f) {}
            ~DeferWrapper() { f(); }
        };

    template 
        DeferWrapper deferWrapper(F f) {
            return DeferWrapper(f);
        }

    #define defer(code) auto CONCAT(_defer_, __COUNTER__) = deferWrapper([&]() code)
Example of usage:

    {
        Foo *foo;
        if (initializeFoo()) {
            warn("Foo could not be initialized");
            return;
        }
        defer({
            destroyFoo(foo);
        });

        Bar *bar;
        if (initializeBar()) {
            warn("Bar could not be initialized");
            return;
        }
        defer({
            destroyBar(bar);
        });
    }
This way, all initialization and destruction happens in one block of the code on the same level of indentation as everything else. Useful for socket code, file handling, encoding/decoding states, etc.

Re: Avoid Else, Return Early (2013)

#64
I had a coworker who was fanatical about removing else blocks, and would refactor entire files so that else wouldn't be necessary.

This blog post is about guard clauses and it's good advice. But if you find yourself refactoring huge chunks of code to avoid a keyword, even making code more complex as a result, stop and consider that you may be making things worse, and maybe look at your other habits to see if you're doing the same thing elsewhere.

Re: Avoid Else, Return Early (2013)

#65
Programmers with lots of hours of maintaining code eventually evolve to return early, sorting exit conditions at top and meat of the methods at the bottom.

Same way you evolve out of one liners.

Same way comments are extra weight that should only be in public or algorithm/need to know areas.

Same way braces go on the end of the method/class name to reduce LOC.

Same way you move on from heavy OO to dicts/lists.

Same way you go more composition instead of inheritance.

Same way while/do/while usually fades away, and if needed exit conditions.

Same way you move on from single condition bracket-less ifs. (debatable but more merge friendly and OP hasn't yet)

Same way you get joy deleting large swaths of code.

and many others on and on.

Usually these come from hours of writing/maintaining code and styles that lead to bugs.

Re: Avoid Else, Return Early (2013)

#66
Writing Go it is good practice dealing with the errors first and, when possible, using if/return; and I agree. Less indentation is good IMHO, and conditional nesting is usually best if avoided.

Working on someone's else code I've found this:

    if a, err := func(); err != nil {
        return err
    } else {
        // a exists here
        ...
    }
    // a is gone
I would have wrote it differently:

    a, err := func()
    if err != nil {
        return err
    }
    // work with a
I guess it depends on the amount of code to put in the "else" branch, but I thought this was interesting because it looks like someone was avoiding to write the error checking in a different line.

Re: Avoid Else, Return Early (2013)

#67
post #41

It depends! But it depends on the actual meaning of the code, so examples which use meaningless identifiers like `doStuff()` miss the distinction. If the conditions are semantically symmetrical, if/else is the right approach: fun max(a, b) { if a > b { a } else { b } } But is it a precondition which causes you to skip the primary logic of the function, then get it out of the way early: fun max(a, b) { // special case…

Exactly this. Anyone who put their shoe in functional programming can make such a distinction. Early returns are not a panacea. If/Else-everything is not a silver bullet either.

How about "there are no silver bullets, there is no substitute for clear thinking."
Post reply on HN