DEV Community

Cover image for Why You Should Drop The 'Else' Keyword When Writing Conditionals
Chinoms
Chinoms

Posted on

Why You Should Drop The 'Else' Keyword When Writing Conditionals

Without conditionals, programming would be impossible. From rookie coders to Silicon Valley nerds, we all use conditionals each day. One common conditional is the if-else construct. I greatly reduced my use of the else statement, and this helped me to greatly reduce my code complexity, enhance readability and maintainability. Let me explain.

function canDrinkAlcohol($age)
{
    if ($age !== "") {
        if ($age < 18) {
            echo "Sorry, alcohol is not for you.";
        } else {
            echo "You are free to drink alcohol.";
        }
    } else {
        echo "You must provide your age.";
    }
}
canDrinkAlcohol("");
Enter fullscreen mode Exit fullscreen mode

The snippet above shows how most people would write their code. First, there's a check to see if the an age was supplied at all. The other condition is nested inside of that condition.

The code looks dirty. And if you throw in other conditions to be checked for, it becomes even dirtier and more confusing to read and difficult to maintain.

But why should you seriously consider eliminating the use of 'else' in conditionals in your programming? A look at the code above will help answer this question.

  1. Because the code flow is nonlinear, it is difficult to follow as the conditions are nested.
  2. It is difficult to identify which "else" corresponds to what "if."
  3. Error handling becomes difficult and confusing, and this is especially so when the code blog is big.

Let's rewrite the function and eliminate the nesting and the else statements.

function canDrinkAlcohol($age)
{
    if ($age == "") {
        echo "You must provide your age.";
        return;
    }
    if ($age < 18) {
        echo "Sorry, alcohol is not for you.";
        return;
    }
    echo "You are free to drink alcohol.";
    return;
}
canDrinkAlcohol(17);
Enter fullscreen mode Exit fullscreen mode

You must have noticed a thing or two in the new function. The very first condition was reversed. I checked for the opposite of what was checked for in the first function.

What was implemented in this new function is called the guard clause technique. It is derived from the fail fast method, and the purpose is to validate a condition and immediately terminate code execution by throwing a meaningful error, rather than allowing the program to throw its own errors which are often less meaningful to the average user. To terminate code execution, I introduced the return keyword.

The new function is structured in such a way that the program exits at the earliest point of failure. The function keeps checking for conditions and the last part of the function doesn't have a condition because for the program to get to that point, all conditions have been checked and passed.

Sometimes though, you will still absolutely need to use else statements and even nest if-else statements. In that case, you may wish to extract the nested conditionals into a separate function and chain the function or call it from within another function.

Many people are taught that what I have done above - having multiple exit points in a function is bad programming practice. Well, it's actually bad programming to write unreadable code.

As you can see, we can easily add conditionals to the function above without getting confused. Whenever you're coding, try to eliminate nesting.

Top comments (26)

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

There's one thing I would like to showcase on that example:

function canDrinkAlcohol($age)
{
    if ($age == "") {
        echo "You must provide your age.";
        return;
    }
    if ($age < 18) {
        echo "Sorry, alcohol is not for you.";
        return;
    }
    echo "You are free to drink alcohol.";
    return;
}
Enter fullscreen mode Exit fullscreen mode

What's the intent of the function canDrinkAlcohol?

If you ask me I'd say to discern whether a User can or cannot drink alcohol based on their age.

If you send the age directly to this function (and not a User instance or any other data structure), why ensuring here that age is set?

const canDrinkAlcohol = (age: number) => age > 18 ? true : false;
Enter fullscreen mode Exit fullscreen mode

Note that we are using "if - else" in this example, just that it's in a shape of a ternary, while keeping it much more understandable, the function has single responsibility and I don't think I could stress it more than what I already did :D

The equivalent in PHP would be:

function canDrinkAlcohol($age) {
    return ($age > 18) ? true : false;
}
Enter fullscreen mode Exit fullscreen mode

Logic on this is that this function should not validate that the user provided the age, this function returns "can drink alcohol if age is X?", and the answer is "if you're over 18 then sure! in any other case (including the case in which you don't tell me your age) NOPE".

It is now easily testable and reusable and can be properly used like that:

function main() {
    $age = $_POST['age']; // or whatever source you wish to use
    if (!$age) {
        echo "You must provide your age.";
        return;
    }

    echo (canDrinkAlcohol($age)) ? "You are free to drink alcohol." : "Sorry, alcohol is not for you.";
     /* 
        other stuff 
    */
}
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
 
j471n profile image
Jatin Sharma • Edited
const canDrinkAlcohol = (age: number) => age > 18 ? true : false;

Why bother,

// typescript

const canDrinkAlcohol = (age: number) => age > 18;
// or
const canDrinkAlcohol = (age: number): boolean => age > 18;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
joelbonetr profile image
JoelBonetR 🥇

That's neat too, even better! 😁

Collapse
 
fjones profile image
FJones

Okay, but just to pick nits: a return foo ? true : false ternary is itself a rather bad example... return foo does the trick.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

Sure! Even better 😁

Collapse
 
nicolus profile image
Nicolas Bailly • Edited
function canDrinkAlcohol($age) {
    return ($age > 18) ? true : false;
}
Enter fullscreen mode Exit fullscreen mode

In "proper" PHP it would be :

function canDrinkAlcohol(int $age): bool
{
    return $age > 18;
}
Enter fullscreen mode Exit fullscreen mode

And 18 would probably be a constant like self::LEGAL_DRINKING_AGE or something
😉

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

I am outdated in regards of PHP for sure 😂

Collapse
 
chinoms profile image
Chinoms

Your example provided context which was lacking in mine. Besides, I kept the examples as basic as possible for the sole purpose of demonstrating the point I'm trying to make. However, I understand what you've explained.

Collapse
 
gilfewster profile image
Gil Fewster

Returning early from functions via guard clauses is definitely a valuable technique, but I don’t think the problem with your example here is the ‘else’ clause. The example in your post illustrates a more general concern about readability of nested conditionals.

There’s nothing inherently wrong or bad about an else clause, and it could well be argued that being explicit with else aids readability by making the developer’s intent clear.

A well placed switch statement, by the way, can also be a thing of beauty.

Collapse
 
chinoms profile image
Chinoms • Edited

Thanks for you insight. Else staments are certainly not inherently bad. However, my example illustrates that when used unnecessarily, code can become dirty and difficult to read. I'm all about simplifying code where realistically and logically possible and necessary.
Thanks, @gilfewster

Collapse
 
oakleaf profile image
oakleaf • Edited
function canDrinkAlcohol(mixed $_age): string
{
    return match(true)
    {
        $_age == "" => "You must provide your age.",
        $_age < 18 => "Sorry, alcohol is not for you.",
        default => "You are free to drink alcohol."
    };
}

echo canDrinkAlcohol(17);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
chinoms profile image
Chinoms

Thanks, @oakleaf
This is concise.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

The problem with early returns is that they create implicit else-branches that aren't easily spotted:

local function doathing()
  doanotherthing()
   if condition then
      return "Early Return"
   end
   return domorethings()
end
Enter fullscreen mode Exit fullscreen mode

In this example, domorethings() looks like an unconditional part of the function that runs after some conditional code. You have to look at the code above to spot the early return, which takes more effort.

Meanwhile, an example using else:

local function doathing()
  doanotherthing()
   if condition then
      return "Early Return"
   else
      return domorethings()
   end
end
Enter fullscreen mode Exit fullscreen mode

This code does the same thing, but it's immediately apparent that domorethings() is inside a conditional block, and may not run. Just from looking at the indentation we can tell the structure of this function: It starts doing some stuff, then splits into two branches.

This makes the code a lot easier to scan and to reason about, which is why else and else-if constructs are a thing in almost every programming language. Trying to simulate else with early returns is like simulating if with goto and claiming it helps with readability.

Collapse
 
chinoms profile image
Chinoms

I understand your perspective. But it may not hold strongly if you have to deal with deep nesting of conditionals.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Deeply nested conditionals are usually a good indicator that either a subroutine is doing too much, or that there needs to be some more abstraction. Flattening the code tree by turning nested ifs into sequential ifs with early returns will only make the problem less obvious and get in the way of future refactoring.

Collapse
 
chrisgreening profile image
Chris Greening

Thanks so much for sharing!

I really like your comment on how unnecessary else blocking introduces nonlinearity into your code. I thought I was writing clean code prior but leveraging better if placement has been a huge improvement for me over the last year or so

On top of limiting my use of else blocks I've also found (mostly) eliminating loops and opting for filter, map, and reduce operations instead has also been a huge improvement - I didn't realize how much of an implicit spaghetti-mess loops can be compared to their more explicit functional counterparts

Small improvements like this build up into some really elegant code - thanks again for sharing! :D

Collapse
 
chinoms profile image
Chinoms

Thank you very much, @chrisgreening. I'm glad you took the time to read my little article and even found something valuable in it.
You mentioned replacing loops with filter and map. Do you have any pointers? I'd be glad to read up.

Collapse
 
chrisgreening profile image
Chris Greening

I'm glad you took the time to write and share :D really valuable insights, keep it up!!

Here is a really great article on mapping instead of looping:

Basically I've found using operations like this:

  • introduces immutability (easier to ensure the state of objects, variables, etc. aren't changing in unexpected ways between operations)
  • reduces variable scope and side effects (and thus less cognitive overhead on the developer/maintainer)
  • explicitly states your intentions (instead of having to read through a for loop and understand what it's trying to accomplish, a map, filter, reduce or similar operation immediately tells you why the iteration is taking place and what the expected output will look like, no guessing games)

All these together sum up to some really beautiful code that chains together cleanly and reduces unexpected behaviors significantly improving readability

Thread Thread
 
chinoms profile image
Chinoms

Thanks a lot. I'll definitely read up.

Thread Thread
 
chrisgreening profile image
Chris Greening

Cheers! Keep up the great work, can't wait to read your next blog posts :~)

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Good post and I agree with else usually being unnecessary. The broader concept at play here is simply FLATTENING your logic. Any time you see code blocks that have multiple layers of nesting, there are usually opportunities to "flatten" the logic by removing the nesting.

Another concept that you're hitting here is that the return statement gives us a chance to "short-circuit" the logic in a function - which also has the benefit of flattening the logic.

Collapse
 
chinoms profile image
Chinoms

Perfect summary to my article. I didn't come here to do a smear campaign on conditionals. But some see it that way. I'm glad you saw things clearly.

Collapse
 
ant_f_dev profile image
Anthony Fung

I too started out using lots of elses. Returning instead of using else is something that I learned after using ReSharper (with C#) for a while.

Another benefit is that there is less indentation for the remainder of the code.

I would add one thing though. In my experience, I've found that using multiple return guard statements near the top of a function is ok. I would suggest caution with returning in the middle of more complex functions though. It's very easy to miss a code-path when in the midst of lots of logic, and get an unexpected result.

In short: using guard statements is good - it tidies up the code, and reduces nesting; use caution when returning early in the middle of a complex function.

Collapse
 
chinoms profile image
Chinoms

Thanks, @ant_f_dev
Your caution is in order.

Collapse
 
nfcristea profile image
Florin Cristea

For good practice, a boolean should be returned whenever a function starts with an inquiring verb like can, is, has, etc. The result can then be as simple as a checkand the validation can be handled via type hinting:

function canDrinkAlcohol(int $age): bool
{
    return $age >= 18;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
abutahermuhammad profile image
AbuTaherMuhammad

One thing I noticed after only using "if" is the proper use of "return".