DEV Community

Cover image for Write better code and be a better programmer by NEVER USING ELSE statements
Douglas Parsons
Douglas Parsons

Posted on • Originally published at dgls.dev on

Write better code and be a better programmer by NEVER USING ELSE statements

I've been a professional programmer for the last several years. During this time I've risen rapidly through the ranks. I started as an intern, but I'm now the Lead Engineer responsible for a suite of products that serve over 160,000 people in over 140 different countries.

Recently, I took a look back across all the code I've written during these years (that I still have access to). I've written production code in a huge variety of languages, through Haskell, Scala, Go, Python, Java or Javascript. Across all these, I noticed one significant trend: I pretty much never use the ELSE statement.

I realised there's a clear rationale behind my aversion to else statements though. I believe that they shouldn't be used, and should be treated as a code smell instead. There are two reasons I think this: else statements break the line-of-sight rule, and they always lack context. I'll explain these two points in detail before showing how you can avoid using else statements.

Line of Sight rule #

I'm a firm believer that code should be optimised to be read by people in the future, rather than being optimised for being executed by machines. In this, I echo Donald Knuth's sentiment:

“Programs are meant to be read by humans and only incidentally for computers to execute.” - Donald Knuth, The Art of Computer Programming.

The problem is the ability to read code is subjective: it's hard to define exactly what makes code readable. One rule that seeks to clarify this though, is the line-of-sight rule. This is a popular rule in the Go community. Mat Ryer defines it concisely in his talk and article. Simply stated, this is the idea that the ‘happy path' in code should be indented as little as possible.

Happy path

Contrastingly, any error handling or special case code should be indented further.

Special case

Any code that follows this has a unique property: scanning the least indented code is sufficient to understand what any piece of code is doing. Scanning the more indented code shows all the special cases and errors that can occur. This makes it super easy to understand at just a glance.

So how do else statements relate to this?

Else statements are problematic as they force code down a level of indentation. It suddenly becomes unclear what code relates to a ‘happy path', and what a special case really is.

Unclear

This lack of clarity makes the code harder to scan through, and hurts the readability.

Lack of Context #

The ability to quickly and efficiently scan code is super important. Digesting small sections of code in isolation is a key part of this. We don't want to always have to read every line of code to understand a small part of a codebase.

Else statements make this harder as they space out the if condition and the code that is affected by it. This is best explained through two examples. First, can you tell what happens when these three lines of code are run?

if myVariable == nil { 
    return “”
}
Enter fullscreen mode Exit fullscreen mode

Hopefully, this is fairly obvious. Let's take a contrasting example though:

} else { 
    return “”
}
Enter fullscreen mode Exit fullscreen mode

We can see that without the if statement, we can't determine what this is meant to be doing. Why would it return an empty string? Is this an error, or the ‘normal' behaviour? This code instead relies on us remembering, and having read, the earlier context. This doesn't matter much when the statements are small, but if there's complicated logic within the if { … } block or we are scanning quickly, then the separation of context from code can hurt readability massively. It hurts even more when if/else statements are nested, or there are multiple of them in one function (which if statement is this else for?).

How to remove else statements? #

Now we've agreed that else statements are rubbish. But that's not much help by itself. The real trick is how to avoid them. Thankfully, there are two simple ways to do this:

  • Inverting the if condition and returning early, and,
  • Creating helper functions.

Inverting the condition #

This is the most common instance I come across. It can take two forms too - one where the else is implicit, one where it is explicit. The explicit version looks like the following:

func doSomething() error {
  if something.OK() {
    err := something.Do()
    if err != nil {
      return err
    }
  } else {
    return nil, errors.New("something isn't ok")
  }
}
Enter fullscreen mode Exit fullscreen mode

The implicit is similar, but without containing an else statement per se. Instead, the else is implied by simply dropping off the end of the function (this one is more common in Python or JavaScript, where None or undefined are returned if nothing is explicitly stated).

function doSomething() {
  if (something.OK()) {
    return something.Do()
  }
}
Enter fullscreen mode Exit fullscreen mode

Again, this isn't super clear what the full extent of the behaviour is. Without reading the whole function, the return values aren't clear.

By simply inverting the if condition, we can solve all these problems though.

function doSomething() {
  if (!something.OK()) {
    // return or throw error
  }
  return something.Do()
}
Enter fullscreen mode Exit fullscreen mode

We can now scan this function and clearly see the indented error condition and normal flow, satisfying the line-of-sight rule. The behaviour is fully explicit, and we have no separation of context. This is much better.

Helper Functions #

We also get else statements that don't directly result in a return. This is usually through some special-case logic that isn't isolated properly. For example

  let charities
  if (country != "") {
    if (tier != "") {
      charities = getCharitiesByCampaignCountryAndTier(campaign, country, tier)
    } else {
      charities = getCharitiesByCampaignAndCountry(campaign, country)
    }
  } else {
    charities = getCharitiesByCampaign(campaign)
  }

  // do something with charities
Enter fullscreen mode Exit fullscreen mode

The readability of this can be improved by pulling the charity-getting logic into its own function. This then lets the special cases be handled appropriately, and return early. By inverting some of the if statements, this can be improved further.

For example:

function getCharities(campaign, country, tier) {
  if (country == "") {
    return getCharitiesByCampaign(campaign)
  }

  if (tier == "") {
    return getCharitiesByCampaignAndCountry(campaign, country)
  }

  return getCharitiesByCampaignCountryAndTier(campaign, country, tier)
}
Enter fullscreen mode Exit fullscreen mode

This helper function neatly encapsulates all the logic we'd need, removes the need for any else statements, and does a much better job of keeping the happy-path code to the left. This is much easier to scan through, and much more readable as a result.

Conclusion #

Else statements are a weird code smell. They harm the readability of any code by forcing equal levels of indents for error handling and for happy paths. They also have the unique ability to separate code from the logic that affects it. They are easy to avoid through the two techniques of returning early and splitting logic into helper functions. As a result, they are unnecessary. You can write better code and be a better programmer by never using them.

Some caveats (to stop the pedants).

  • In SQL CASE WHEN … ELSE … isn't really avoidable.
  • In Scala, implicit returns (avoiding return statements for referential transparency) means you have to use them - you don't really have the ability to 'return early'.
  • Ternary operators are fine.
  • In python, the ternary operator uses else. This is also fine.

Latest comments (63)

Collapse
 
jimbo99 profile image
James

I love this. Was anyone else told or taught (many years ago maybe) that every function should only have a single return? That old rule contributed greatly to the requirement for multiple nested levels of if statements and the need for lots of else's. With that rule dumped, it's much easier to write more readable code as in this article. BTW not heard it called a 'happy path' before but that's a great way to describe it.

Collapse
 
matthewdean profile image
Matthew Dean

"Hey Siri, how do I un-publish someone else's blog post from the Internet?"

Collapse
 
brandiware profile image
brandiware

OK folks. Readability IS a sign of code quality - for sure. HOWEVER, that does not mean it is necessary to drop useful elements of any language. You can't assert "never use a knive!" simply because misuse of knives is dangerous and can be BLOODY.
It is interesting to note, how little the examples in this post are commented !

ALTERNATIVELY, consider focussing on more and proper commenting:

// Process the passed argument, ... the result of the ... calculation ... , ... if it is useful
if myVariable != nil {
//DoSomeUsefulStuff()
}

Now judge by yourself how well the else part reads if commented (of course with some context of the preceeding IF logic)

// Drop out of the function / procedure if the passed argument, ... the result of the ... calculation ... , ... is NOT useful
} else {
return “”
}

My personal summary on code readability: Do comment;

Collapse
 
sctgcs profile image
Simon Tillson • Edited

I'm sorry, but this is wrong, and I'm not going to be kind about it.

I remember years ago when people started saying 'never use goto in C'.
The real reason for that was the provability of source code. With GOTOs littered all over, it's impossible to prove the output of a program. Without them, it is possible.
For safety-critical software, like flight controllers and medical devices, that's damned important.

Is this the reason people gave when shouting at the top of their lungs 'if anyone in this team uses GOTO, they're out the door!' ? No. It is not.

At the time, coders put up all sorts of other arguments about style, readability, maintainability and even occasionally, simple personal preference. These are all fair reasons, and there was never any justification for littering code with GOTOs, especially conditional ones! In many systems, it was even essential to use GOTO in some places. But I digress...

Your argument for avoiding ELSE is hollow. Source code style and layout is not hindered by ELSE at all. If used correctly, with decent coding style, it looks rather nice and obvious. Avoiding it often makes functions longer or hides their logic, when a clearly stated ELSE case is very straightforward. Of course we don't want 10 levels of indented IF..ELSE..IF.. but that's obvious, isn't it? No reason to ban the ELSE word.

In the real world, we've got more important things to concentrate on, and I don't think you will find the ELSE statement disappearing from any language anytime soon!

If anyone wants to try writing all their code without ELSE, go for it.
Just don't make a religion out of it, ok?

Collapse
 
dezfowler profile image
Derek Fowler

Completely agree with this. I also prefer to write code this way and always suggest it to others as I find it produces a far simpler and more readable result.

Collapse
 
linaran profile image
Deni

Imagine someone rejecting a PR over an else statement.

Collapse
 
lofibytes profile image
Jillian S. Estrella (she/her)

I fully agree.

This is part of our style guide; which all of our devs are expected to adhere to.

Collapse
 
rhyous profile image
Jared A Barneck

I kicked 'else' to the curb over ten years ago. Best thing I ever did. As a dev lead, I teach those I mentor that it is similar to 'goto' and just doesn't have a place in modern object-oriented languages (C#, Java, python, typescript, javascript). I tried for a long time to use find a valid use of 'else' and 'goto' and every time I thought I had one, I realized there was a better way. Using either is always a code smell.

The author missed discussing one of the best reasons for not having an 'else' statement. Cyclomatic Complexity decreases. Give Cyclomatic Complexity a google or bing search and read up on it. Using 'else' always increases cyclomatic complexity and always makes unit testing and code coverage harder to obtain.

Collapse
 
rivantsov profile image
Roman Ivantsov

the quote - is not by D.Knuth, it is Abelman & Sussman, SICP, preface. look it up

Collapse
 
oleksiirybin profile image
oleksii-rybin

Usually, when I read the code, I don't read it like a book - all the lines one after another, instead, I read it following the logic path I'm currently interested in, from one logical block to another logical block, and it doesn't matter whether it's a "happy path" or not (it's up to me to decide, depending on the task at hand). So, reading through if/else statements don't create any issues for me, I just look at the condition and immediately proceed to "if" or "else" body. But what frustrates me - is complex "if" conditions and early returns. They both make it more difficult to only read through the code you need while skipping through the parts which are irrelevant to your current goal. First, I have to use the extra time to process the "inverted condition" and then go through the whole function making sure there are no early returns that will unexpectedly interrupt the flow I'm currently interested in, so as a result - I have to read much more lines (basically, all of them) and put more effort to get through the code.

Long story short: I'm not convinced. I still think using if/else statements and a single return at the end of a function is better for readability and stability of the code. Besides, if you're still lacking the context, I assume, any modern IDE lets you just click on the closing parentheses of any statement to quickly remind you what was the "if" statement at the beginning of it.

Collapse
 
jcoelho profile image
José Coelho

Interesting point! I do try to avoid else statements but only to keep my code short.
I think the word NEVER is too strong, sometimes ‘else’ does help readability.

Inverting the if statements is a good technique but most of the time it hurts readability, you need to pause and adjust your thought to the negative statement.

Anyways good article, great spark for discussion

Collapse
 
chadj2 profile image
Chad Juliano

Congrats on publicizing this idea! I led integration teams for years and I tried to explain to them why nested if/else statements negatively impact the ability for other people to read and maintain code.

Code is always easier to deal with when its flow is kept as linear as possible and the happy path is clear. I think most people I spoke with never got this concept and it is good to see this.

Collapse
 
hasii2011 profile image
Humberto A Sanchez II

I am not sure I would recommend this technique when it creates negative logic.

Collapse
 
dglsparsons profile image
Douglas Parsons

How do you mean 'negative logic' ?

Collapse
 
sctgcs profile image
Simon Tillson

He means inverted logic, not negative logic. In boolean terms, they're equivalent.

Collapse
 
hasii2011 profile image
Humberto A Sanchez II

The code showed this:

function doSomething() {
if (something.OK()) {
return something.Do()
}
}

It was changed to:

function doSomething() {
if (!something.OK()) {
// return or throw error
}
return something.Do()
}

My experience has been that using a "not" in front of a conditional requires a mental adjustment to process the "negative" condition. I think I also read this on many books that talk about "code smells"

Thread Thread
 
dglsparsons profile image
Douglas Parsons

I think it depends on the properties/methods you have. I agree with your point that putting 'not' in front of a conditional can require a mental adjustment, but that's usually when you're properties are negative or poorly named.

For example - if I had a something.notOK() method, I wouldn't want to negate this - as !something.notOK() doesn't read very nicely.

That's an extreme example, but it's why naming is super important (especially for booleans).

Another example could be flagging users as inactive - the logical thing to do is to add a boolean property isInactive. This isn't as obviously terrible, but inverting this can be confusing - not isInactive... so active.

I'd argue the code smell there is the names / properties, rather than inverting the if / else statements though.

Great point though <3

Collapse
 
gnsp profile image
Ganesh Prasad

I have been avoiding else blocks for quite some time now. But I have never been able to clearly explain the 'why'. For some reason the code felt cleaner without the else blocks. This article explains the 'why' part beautifully.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.