Don't use boolean evaluation as a readability shortcut
TL;DR: Don't use Boolean comparison for side effect functions.
Problems
Readability
Side Effects
Solutions
- Convert short circuit into IFs
Context
Smart programmers like to write hacky and obscure code even when there is no strong evidence for this improvement.
Premature optimization always hurts readability.
Sample Code
Wrong
userIsValid() && logUserIn();
// this expression is short circuit
// Does not value second statament
// Unless the first one is true
functionDefinedOrNot && functionDefinedOrNot();
// in some languages undefined works as a false
// If functionDefinedOrNot is not defined does
// not raise an erron and neither runs
Right
if (userIsValid()) {
logUserIn();
}
if(typeof functionDefinedOrNot == 'function') {
functionDefinedOrNot();
}
// Checking for a type is another code smell
Detection
[X] Semi-Automatic
We can check if the functions are impure and change the short circuit to an IF.
Some actual linters warn us of this problem
Tags
- Premature Optimizacion
Conclusion
Don't try to look smart.
We are not in the 50s anymore.
Be a team developer.
Relations
Code Smell 140 - Short Circuit Evaluation
Maxi Contieri ・ Jun 13 '22
Code Smell 149 - Optional Chaining
Maxi Contieri ・ Jul 16 '22
Credits
Photo by Michael Dziedzic on Unsplash
A computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are, in short, a perfect match.
Bill Bryson
Software Engineering Great Quotes
Maxi Contieri ・ Dec 28 '20
This article is part of the CodeSmell Series.
Top comments (12)
What about optional chaining? Which of course only works with functions designed to be chained, like
user?.isValid()?.logUserIn();
I'd use that for finding a deep value but not performing an action:
Also, the implication in your example is that
isValid()
returns a user object when that's not obvious - it looks like it should be a boolean.It has to be a boolean, boolean is how the data is first made.
The condition essentially evaluates to a boolean value, determining whether the nested property user.foo.bar exists and is truthy.
I am also engineer. Can we discuss more details?
Null is the worst code smell
And optional chaining, IMHO hids this antipattern.
I've written a smell on optional chaining. I will publish it in the following weeks
hackernoon.com/null-the-billion-do...
And optional chaining is another code smell
Code Smell 149 - Optional Chaining
Maxi Contieri ・ Jul 16 ・ 2 min read
But we are not always writing the data or code with null ourselves.
One example: Auto-generated types made with GraphQL Code Generator.
We still need to be able to handle null.
Null does indeed make sense if you do not know if data will return empty.
Of course
In case null is beyond your own control you need to deal with it
That is why it is the billion dollar mistake
We must be mature enough to avoid creating NEW nulls
Yeah, that makes sense 😄
In my humble opinion
user?.isValid()?.logUserIn()
reads like a human sentence that makes sense, compared to multilinetypeof
blocks.LooksSmells good to me :)My big issue with this is debugging it. Did we not log in because we have no user, because the user isn't valid, or because isValid returned null (which seems like something you'd want to handle in a particular way IMO)? A single line getting the job done is great until you're ripping through 20 lines of chained methods trying to figure out where things went wrong.
I agree with Ben that it's fine to get deep values with though. Especially if you really just care about whether or not you could get to the value you were looking for.
When I've asked about optional chaining in C++, the push back I've gotten has been that it's a code smell because optional chaining means high coupling and intimate knowledge of the private parts.
So, for better or worse, in C++ we'll still have this...
Why is this a problem specifically before a side-effect?
Something like myFunc?.() will force people to pause. On the other hand short-circuits are a common pattern when setting default values or optionally rendering React components. People are used to reading them in other contexts, often with function calls on the right, so why do they become less readable in this one?