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.
Contrastingly, any error handling or special case code should be indented further.
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.
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 “”
}
Hopefully, this is fairly obvious. Let's take a contrasting example though:
} else {
return “”
}
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")
}
}
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()
}
}
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()
}
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
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)
}
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.
Top comments (63)
I don't find the
else
worth targeting for the sake of readability. In fact, I welcomeelse
as an explicit sign of "this or that" logic.Guard clauses are fine. Nested conditionals are another problem entirely.But I don't think breaking a simple function with 3 parameters into at least a combinations of small function with a long chain of
get by this and by that
is anymore readable.It does however make you think about reusability and keeping consistency in code, in the getCharities example there, is something that could be reused elsewhere.
Sorry, but I don't see why reusability is relevant here - when it was suggested to create multiple 'versions' of
getCharities
with different combinations of parameters to avoid branching. One could even argue that it is the exact opposite of reusability: duplication.Unless we have wildly different logic for getting by
country
ortier
- in which case it isn't even about branching as they would be two different functions in the first place, what we would most likely have is this:getCharitiesByCampaignAndCountry -> return getCharities(country, campaign, defaultTier)
Why make the reader jump an extra hoop just to get to the actual logic? And even if we were to force reusability on this, what is more reusable:
getCharities
orgetCharitiesByCampaignAndCountry
?I know the example are meant to be simple, sometimes contrived, but this is precisely why I don't see why eliminating
else
helps in any way with readability.@dglsparsons , excellent points. However, the code examples are a little difficult to read as all of the code is in the same line. Better readability can be achieved if it is something like below.
Hey - thanks for pointing this out. I'm not sure what's happened there.
I've fixed it now, so hopefully its much easier to read!
New line before
return
please :3While I agree with many of these points, and definitely find excessive cases of indentation in more novice-written code, this reads far more like "do as I say because I do it this way". You even start this with gloating that fits right with that motivation. Do you have studies on readability and how this change impacts that? While I suspect it does increase it, the wording of your article goes against your intentions.
You can look at how people talk when explaining something. I apply same technique and find it very useful. My motivation for this technique and some other comes from psychology and some linguistics. My coworkers whom I mentor think this is valuable technique, but maybe they do not know better. I cannot find studies regarding applicability of the technique in the real world even if I look for it deliberately. If you know what entity can do such kind of research I’m more then happy to start search again to prove or disprove statements of an article.
My anecdotal evidence is that code written in such way easier for novices to follow, since this kind of code can be easier to reading out loud and it would appear more natural.
Also there usually when express business logic there two kind of code intermixed, guards and shortcuts which require early exit to simplify logic and actual business logic where this rules less strictly applicable.
Why early exits and shortcut processing important? Try to remember how rare cases handled by business persons(can be engineers from other fields). Most people concentrate on core process and for other conditions usually throw simple shortcuts and suggestions which solve problem from their perspective. Such style of code just reflect how business people usually talk, just in slightly more formal way
Hi. Thanks for the feedback. That's not my intent at all, but I'm sorry it's come across that way. It's more intended to be - this is what works in my experience but I appreciate your perspective.
I'll bear this in mind in future :)
I didn't think it was, hence saying I agree with your point. I just saw an easy way others can discredit your message and I don't think it's a message that should be discredited. I'm glad hostility didn't come through in text, because my intentions were only constructive.
Wow !
Nice article!
I think we should avoid ELSE and also IFS
How to Get Rid of Annoying IFs Forever
Maxi Contieri ⭐⭐⭐ ・ Nov 9 '20 ・ 5 min read
I like the extract you made on your function. I don't know why you call them helper. Who are they helping ?
Thank you for your article
Hi, thanks for reading - I'm glad you enjoyed the article. I'll definitely give yours a read too :).
You're right about calling them helper functions - I think it's a kind of industry standard term (or at least it is here), for separating out pieces of logic into plain functions. As for who they help... I'm not sure either. Probably us developers :D
Yes. I'm aware of that
But I think we are wrong. Calling something a 'Helper' is a code smell of a real world entity we are too lazy to find.
Here are some reasons not to use that name.
Arguing with standards or convention is a difficult line to take though.
e.g. why are most keyboards in a QWERTY layout? - it's an outdated convention from when typewriters would jam, but that doesn't mean keyboard or laptop manufacturers should deviate.
I've not come across a different terminology for a helper function personally. Calling it just a function could suffice but doesn't make a succinct point. As long as it's widely understood, I think it's okay - coming up with a different term could just cause more confusion.
Agreed with the standard part
I'm not suggesting replacing Helper with another standard.
I'm suggesting to change every helper to an intention revealing name according to the domain
in your case I think charitiesByCampaignAndCountry(campaign, country) is a very good name related to domain. But it is not a helper :)
I'm absolutely agreed with the names of the functions. Although I'd argue that as a concept, the function is still a helper 😜 (insofar as it's separating out the logical act of getting charities from the specific invocation of it).
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.
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.
Interesting concepts about code readability, but I respfully agree.
I think that putting the else statement on its own line greatly improves the "line of sight" readability:
if( condition ) {
// statements
}
else {
// statements
}
Also, what about loops? They also break the same "line of sight" readability. What about switch statements? What about continue and break statements inside loops and switch statements? What about raising exceptions?
I think that helper functions also worsen the readability. For a programmer taking over or reviewing somebody else's code, helper functions definitely interupt the readability of the code.
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.
I am not sure I would recommend this technique when it creates negative logic.
How do you mean 'negative logic' ?
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"
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 propertyisInactive
. 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
He means inverted logic, not negative logic. In boolean terms, they're equivalent.
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
Some comments may only be visible to logged-in visitors. Sign in to view all comments.