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("");
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.
- Because the code flow is nonlinear, it is difficult to follow as the conditions are nested.
- It is difficult to identify which "else" corresponds to what "if."
- 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);
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)
There's one thing I would like to showcase on that example:
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?
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:
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:
Cheers!
Why bother,
That's neat too, even better! 😁
Okay, but just to pick nits: a
return foo ? true : false
ternary is itself a rather bad example...return foo
does the trick.Sure! Even better 😁
In "proper" PHP it would be :
And 18 would probably be a constant like self::LEGAL_DRINKING_AGE or something
😉
I am outdated in regards of PHP for sure 😂
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.
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.
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
Thanks, @oakleaf
This is concise.
The problem with early returns is that they create implicit
else
-branches that aren't easily spotted: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
: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
andelse-if
constructs are a thing in almost every programming language. Trying to simulateelse
with early returns is like simulatingif
withgoto
and claiming it helps with readability.I understand your perspective. But it may not hold strongly if you have to deal with deep nesting of conditionals.
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
if
s into sequentialif
s with early returns will only make the problem less obvious and get in the way of future refactoring.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 betterif
placement has been a huge improvement for me over the last year or soOn 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 counterpartsSmall improvements like this build up into some really elegant code - thanks again for sharing! :D
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.
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:
map vs. for loop. I almost never use for loops in… | by Andrew Crites | Medium
Andrew Crites ・ ・
Medium
Basically I've found using operations like this:
for
loop and understand what it's trying to accomplish, amap
,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
Thanks a lot. I'll definitely read up.
Cheers! Keep up the great work, can't wait to read your next blog posts :~)
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.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.
I too started out using lots of
else
s. Returning instead of usingelse
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.
Thanks, @ant_f_dev
Your caution is in order.
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:One thing I noticed after only using "if" is the proper use of "return".