73

From the Code Complete book comes the following quote:

"Put the normal case after the if rather than after the else"

Which means that exceptions/deviations from the standard path should be put in the else case.

But The Pragmatic Programmer teaches us to "crash early" (p. 120).

Which rule should I follow?

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
jao
  • 1,123
  • 2
  • 10
  • 17
  • possible duplicate of [Should I return from a function early or use an if statement?](http://programmers.stackexchange.com/questions/18454/should-i-return-from-a-function-early-or-use-an-if-statement) – gnat May 20 '14 at 08:19
  • 15
    @gnat not duplicate – BЈовић May 20 '14 at 09:36
  • 16
    the two are not mutually exclusive, there is no ambiguity. – jwenting May 20 '14 at 10:14
  • 6
    I'm not so sure the code-complete quote is such great advice. Presumably it's an attempt to enhance readability, but there are certainly situations where you'd want uncommon cases to be textually first. Note that this isn't an answer; the existing answers already explain the confusion your question stems from well. – Eamon Nerbonne May 20 '14 at 14:15
  • @gnat:This appears to be a philosophical statement, not a coding statement. – Tom Au May 20 '14 at 14:26
  • 1
    I wouldn't trust the _Code Complete_ advice at all, being too generic. Error-handling advice varies quite a bit by language. For example, Java has exceptions and garbage collection, and doesn't need to clean up memory allocations the way C code needs to. – 200_success May 20 '14 at 20:45
  • 14
    **Crash early, crash hard!** If one of your `if` branches returns, use it first. And avoid the `else` for the rest of the code, you've already returned if pre-conditions failed. *Code is easier to read, less indentation...* – CodeAngry May 21 '14 at 16:23
  • 5
    Is it just me who thinks this has nothing to do with readability, but instead was probably just a misguided attempt at optimizing for static branch prediction? – user541686 May 22 '14 at 08:06
  • @Mehrdad Branch prediction depends on the CPU anyway; you can only really do this in asm. (For example, on most recent CPUs, backward branches (think loops) are predicted as taken, forward branches (think if/else) are predicted as not taken. But this varies.) GCC has `__builtin_expect()`… use these instead, but only if the compiler doesn't do a good job on its own already (most do; the clutter becomes a maintenance burden, and sometimes, the compiler does a better job without such hints, especially after refactoring). – mirabilos May 24 '14 at 12:17
  • @mirabilos: not sure what you're trying to say. I am aware how branch prediction works, and there is nothing that restricts it to assembly. When you have an `if` statement there is necessarily going to be a jump there, and not-taken implies executing the body of the 'if', so hopefully it's the common code. – user541686 May 24 '14 at 17:32
  • @Mehrdad wrong, the compiler can shuffle the code around, so that either the `then`-part or the `else`-part is the not-taken branch. And all modern ones do. – mirabilos May 24 '14 at 19:17
  • @mirabilos: Er, I never said it's *good* advice, nor that it's always correct. Like many other things, it's a reasonable first-order approximation, and I said I'm wondering if that's what the attempt was. – user541686 May 24 '14 at 20:31
  • @CodeAngry: It really depends: online shop catalog -> crash early and crash hard OK. Avionics software -> hmm. – Giorgio Oct 16 '15 at 18:25

7 Answers7

188

"Crash early" is not about which line of code comes earlier textually. It tells you to detect errors in the earliest possible step of processing, so that you don't inadvertently make decisions and computations based on already faulty state.

In an if/else construct, only one of the blocks is executed, so neither can be said to constitute an "earlier" or "later" step. How to order them is therefore a question of readability, and "fail early" doesn't enter into the decision.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 2
    Your general point is great but I see one issue. If you have (if / else if / else) then the expression evaluated in the "else if" is evaluated after the expression in the "if" statement. The important bit, as you point out, is readability vs conceptual unit of processing. Only one code block is executed but multiple conditions can be evaluated. – Encaitar May 20 '14 at 15:08
  • 7
    @Encaitar, that level of granularity is far smaller than what is generally intended when the phrase "fail early" is used. – riwalk May 20 '14 at 19:49
  • 2
    @Encaitar That is the art of programming. When faced with multiple checks, the idea is to try the one most likely to be true first. However, that information may not be known at design time but at the optimization stage, but beware the premature optimization. – BPugh May 20 '14 at 19:49
  • Fair comments. This was a good answer and I only wanted to try to help make it better for future reference – Encaitar May 20 '14 at 21:58
  • Scripting languages like JavaScript, Python, perl, PHP, bash, etc. are exceptions because they're linearly interpreted. In small `if/else` constructs it probably doesn't matter. But ones called in a loop or with many statements in each block could run faster with the most common condition first. – DocSalvager Oct 02 '14 at 21:29
115

If your else statement contains only failure code then, most likely, it should not be there.

Instead of doing this:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

do this

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

You don't want to deeply nest your code simply to include error checking.

And, as everyone else has already stated, the two pieces of advice are not contradictory. One is about execution order, the other is about code order.

Jack Aidley
  • 2,954
  • 1
  • 15
  • 18
  • 4
    Worth being explicit that advice to put normal flow in the block after `if` and exceptional flow in the block after `else` does not apply if you do not have an `else`! Guard statements like this are the preferred form for handling error conditions in most coding styles. – Jules May 24 '14 at 05:44
  • +1 this is a good point and actually gives something of an answer to the real question of how to order stuff with error conditions. – ashes999 May 26 '14 at 03:17
  • Definitely a lot clearer and easier to maintain. This is how I like to do it. – John Sep 29 '14 at 14:20
27

You should follow both of them.

The "Crash early"/fail early advice means that you should test your inputs for possible errors as soon as possible.
For example, if your method accepts a size or count that is supposed to be positive (> 0), then the fail early advice means that you test for that condition right at the beginning of your method rather than waiting for the algorithm to produce nonsense results.

The advice for putting the normal case first means that if you test for a condition, then the most likely path should come first. This helps in performance (as the branch prediction of the processor will be right more often) and readability, because you don't have to skip over blocks of code when trying to figure out what the function is doing in the normal case.
This advice doesn't really apply when you test for a precondition and immediately bail out (by using asserts or if (!precondition) throw constructs), because there is no error handling to skip while reading the code.

yoozer8
  • 693
  • 1
  • 8
  • 20
Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
  • 1
    Can you elaborate on the branch prediction part? I wouldn't expect code which is more likely to go to the if case to run faster than code which is more likely to go to the else case. I mean, that's the whole point of branch prediction, isn't it? – Roman Reiner Jun 15 '14 at 19:55
  • @user136712: In modern (fast) processors, instructions are fetched before the previous instruction has finished processing. Branch prediction is used to increase the likelihood that the instructions fetched while executing a conditional branch are also the right instructions to execute. – Bart van Ingen Schenau Jun 16 '14 at 06:49
  • 2
    I know what branch prediction is. If I read your post correctly you say that `if(cond){/*more likely code*/}else{/*less likely code*/}` runs faster than `if(!cond){/*less likely code*/}else{/*more likely code*/}` because of branch prediction. I would think that branch prediction is not biased to either the `if` or the `else` statement and only takes history into account. So if `else` is more likely to happen it should be able to predict that just as fine. Is this assumption false? – Roman Reiner Jun 16 '14 at 07:01
18

I think @JackAidley said the gist of it already, but let me formulate it like this:

without Exceptions (e.g. C)

In regular code flow, you have:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

In the “error out early” case, your code suddenly reads:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

If you spot this pattern – a return in an else (or even if) block, rework it immediately so the code in question does not have an else block:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

In the real world…

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

This avoids nesting too deep and fulfils the “break out early” case (helps to keep the mind – and the code flow – clean) and doesn’t violate the “put the more likely thing into the if part” because there simply is no else part.

C and cleanup

Inspired by an answer on a similar question (which got this wrong), here's how you do cleanup with C. You can use one or two exit points there, here's one for two exit points:

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

You can collapse them into one exit point if there is less cleanup to do:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

This use of goto is perfectly fine, if you can deal with it; the advice to stay off using goto is directed at people who cannot yet decide by themselves whether a use is good, acceptable, bad, spaghetti code, or something else.

Exceptions

The above talks about languages without exceptions, which I highly prefer myself (I can use explicit error handling much better, and with much less surprise). To quote igli:

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

But here's a suggestion how you do it well in a language with exceptions, and when you want to use them well:

error return in the face of exceptions

You can replace most of the early returns with throwing an exception. However, your normal program flow, i.e. any code flow in which the program did not encounter, well, an exception… an error condition or somesuch, shall not raise any exceptions.

This means that…

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

… is okay, but…

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

… is not. Basically, an exception is not a control flow element. This also makes Operations look weird at you (“those Java™ programmers always tell us that these exceptions are normal”) and can hinder debugging (e.g. tell the IDE to just break on any exception). Exceptions often require the run-time environment to unwind the stack to produce tracebacks, etc. There are probably more reasons against it.

This boils down to: in a language supporting exceptions, use whatever matches the existing logic and style and feels natural. If writing something from scratch, get this agreed on early. If writing a library from scratch, think of your consumers. (Do not, ever, use abort() in a library either…) But whatever you do, do not, as a rule of thumb, have an exception thrown if operation continues (more or less) normally after it.

general advice wrt. Exceptions

Try to get all in-program use of Exceptions agreed on by the whole dev team first. Basically, plan them. Do not use them in abundance. Sometimes, even in C++, Java™, Python, an error return is better. Sometimes it is not; use them with thought.

mirabilos
  • 391
  • 2
  • 10
  • In general, I view early returns as code smell. I'd throw an exception instead, if the following code would fail because a precondition wasn't met. Just sayin' – DanMan May 24 '14 at 10:40
  • 1
    @DanMan: my article was written with C in mind… I normally do not use Exceptions. But I've extended the article with a (oops, it got rather long) suggestion wrt. Exceptions; incidentally, we had the very same question on the company's internal dev mailing list yesterday… – mirabilos May 24 '14 at 12:14
  • Also, use braces even on one-line ifs and fors. You don't want another `goto fail;` hidden in identation. – Bruno Kim May 25 '14 at 04:23
  • 1
    @BrunoKim: this fully depends on the style / coding convention of the project you work with. I work with BSD, where this is frowned upon (more optic clutter and loss of vertical space); at $dayjob however I place them as we agreed on (less difficult for newbies, less chance of errors, etc. as you said). – mirabilos May 25 '14 at 14:32
3

In my opinion 'Guard Condition' is one of the best and easiest ways to make code readable. I really hate when I see if at the beginning of the method and don't see the else code because it is off the screen. I have to scroll down just to see throw new Exception.

Put the checks at the beginning so that the person reading code doesn't have to jump all over the method to read it but instead always scan it from top to bottom.

Piotr Perak
  • 399
  • 1
  • 7
2

(@mirabilos' answer is excellent, but here's how I think about the question to reach the same conclusion:)

I'm thinking about myself (or someone else) reading the code of my function later on. When I read the first line, I can't make any assumptions about my input (except those which I won't be checking anyway). So my thought is "Ok, I know I'm going to be doing things with my arguments. but first let's 'clean them up" - i.e. kill control paths in which they're not to my liking." But at the same time, I don't see the normal case as something that's conditioned; I want to emphasize its being normal. Thus,

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}
einpoklum
  • 2,478
  • 1
  • 13
  • 30
-3

This kind of condition ordering depends on the criticallity of the code section under question and whether there are defaults that can be used.

In other words:

A. critical section and no defaults => Fail Early

B. non-critical section and defaults => Use defaults in else part

C. in-between cases => decide per case as needed

Nikos M.
  • 131
  • 5
  • is this merely your opinion or you can explain / back it up somehow? – gnat May 23 '14 at 11:27
  • how exactly is this not backed-up, as each option explains (without many words indeed) why it is used? – Nikos M. May 23 '14 at 13:04
  • i wouldnt like to say this, but the downvotes in (my) this answer are out-of-context :). this is the question OP asked, whether you have alternative answer is another matter altogether – Nikos M. May 23 '14 at 13:16
  • I honestly fail to see the explanation here. Say, if someone writes another opinion, like _"critical section and no defaults => don't Fail Early"_, how would this answer help reader to pick of two opposing opinions? Consider [edit]ing it into a better shape, to fit [answer] guidelines. – gnat May 23 '14 at 13:29
  • ok i see, this indeed could be another explanation, but at least you do understand the word "critical section" and "no defaults" which **can** imply a strategy to *fail early* and this is indeed an expanation albeit a minimalistic one – Nikos M. May 23 '14 at 13:31
  • if you have an example i can hear it, no problem – Nikos M. May 23 '14 at 13:35