We learn if/else on our first programming day. Then we forget the else
TL;DR: Be explicit. Even with Else.
Problems
Readability
Cognitive Load
Unforeseen conditions
Solutions
- Write the explicit else
Context
If we early return on an IF sentence we can omit the else part.
Afterward, we Remove the IF and use polymorphism.
That is when we miss the real cases.
Sample Code
Wrong
function carBrandImplicit(model) {
if (model === 'A4') {
return 'audi';
}
return 'Mercedes-Benz';
}
Right
function carBrandExplicit(model) {
if (model === 'A4') {
return 'audi';
}
if (model === 'AMG') {
return 'Mercedes-Benz';
}
// Fail Fast
throw new Exception('Model not found);
}
Detection
[X] Automatic
We can check syntax trees and parse them and warn for missing else.
We can also rewrite them and perform mutation testing.
Tags
- Conditionals
Conclusion
This kind of smell brings a lot of public debate, and hate.
We must exchange opinions and value each pros and cons.
Relations
Code Smell 102 - Arrow Code
Maxi Contieri γ» Nov 15 '21 γ» 2 min read
Code Smell 36 - Switch/case/elseif/else/if statements
Maxi Contieri γ» Nov 28 '20 γ» 1 min read
More Info
How to Get Rid of Annoying IFsΒ Forever
Maxi Contieri γ» Nov 9 '20 γ» 5 min read
Credits
Photo by Elena Mozhvilo on Unsplash
The biggest issue on software teams is making sure everyone understands what everyone else is doing.
Martin Fowler
Software Engineering Great Quotes
Maxi Contieri γ» Dec 28 '20 γ» 13 min read
This article is part of the CodeSmell Series.
Top comments (4)
You really think throwing is preferable over an actual default case? From my point of view that second snippet is way more smellier than the first one. Having to wrap it on a
try/catchin order to be "safe" makes it usage way less convenient.My suggestion would be to just have something simple like this (added JSDocs to make DX even better):
And then the usage is as simple as:
As you can see, we have several options, none of which will make the app explode for not finding a result. Now with your approach we have less options and we need to use more boilerplate:
But basically less options and more boilerplate just because you took the nuclear option of throwing. Take a look at native methods such as Array.prototype.find: When it finds an
itemthat matches the predicate function, it returns it, but if it doesn't find it, it doesn't throw, instead it just returnsundefined. The developer already knows that if the value received isundefined, it means the item wasn't found, so no need to throw for that. In TypeScript or JS with type check enabled you get the typeTypeOfItem | undefinedfrom it, so you know you need to check forundefinedor use??to be safe. Typing for errors is way more nasty (you need to useassertsto create invariants for every function that throws).We can go simpler than that, with any function and operation that returns a
number, if an operation to get a number is invalid, you get the infamousNaN, which is still not throwing. Developers really don't want to have to wrap every single function call in atry/catchblock, so you really should avoid that as a practice, even if it is "good" in other languages.Cheers!
The functionality of the two code samples is different
Hi
It is not a "safe refactor"
And, as long as I can see. the two possitive cases are dealt in the same way
But that's the bouncer pattern, no? Lot's of devs prefer it over else π