Gather by the warm clean-code fire and let me tell you the story about a scary little monster who lives in the dark corners of the quick-n-dirty forest - I’m talking about the function which receives a boolean argument.
function doThis(shouldDoThat) {
if (shouldDoThat) {
// Do that...
} else {
// Do this...
}
return 'done';
}
“What’s wrong with that?” you may ask “I use it all the time”. Well, let me explain and you can then decide for yourself if you wanna keep on using them or try to avoid them as much as possible.
Function’s responsibility
A good function is a function which does a single thing, like adding 2 numbers or getting some value. You will find many resources out there explaining the benefits of a function which is simple and has a single responsibility. To try and TL;DR it:
- It’s easy to reason about
- It’s easy to test
- It’s easy to refactor and modify
- Has a better potential of being pure (no side effects, the same input will also result with the same output, etc.)
It’s no surprise that the basic methodologies of code refactoring deals with extracting complex functions, which do many things, into small units that do a single thing and can be later composed to achieve the big goal the feature requires.
The boolean argument meaning
What does it mean when we declare a function that can receive a boolean argument?
Before even beginning to read the first line of the function’s code we know that our function does at least 2 slightly (and sometimes not that slightly) different things.
If the boolean is true we enter the “true” branch and do something, and if it is false, we enter the “false” branch and do something else.
“Ok” you say “but what’s wrong with that?”
First of all, you might have calls to that function which go like this:
doThis(false);
I think you can agree that when reading such a line you don’t understand right away what this call does. The “false” has an ambiguous meaning, and now you need to go to the function declaration and see what that “false” means
What if you have several booleans as arguments? Now it starts to be really confusing. Zero readability ("but TypeScript" you say... hold on, I'm not done).
“Ok then, you can have the function receive an object as an argument and then you will understand right away what the booleans stand for” you say. Something like this:
doThis({shouldDoThat: true});
Yeah that can work, but this brings us to another pitfall the boolean arguments have and that’s the “boolean drilling”.
The Boolean drilling
Say you have well designed code, and you have many functions, each with a single responsibility, that are composed nicely to achieve your feature needs. One function calls to another in a perfect design.
One day your PM arrives and asks for a “small” modification which requires you to change the way the composition behaves. You say “no problem, I’ll just pass a boolean argument to the entry point and then I can decide which branch to take”, but you know that some functions still need to behave the same regardless of the boolean, since the new requirement does not affect their behavior.
This means that you have to pass the boolean to them even though they don’t "care" about it, but they need to pass it onward to the next function which does "care" about it. This will make your code hard to understand and add redundant complexity. You will find that this boolean is drilling down through all your implementation's layers.
So what can we do about it?
It is preferred to branch as early as possible and from there have a clear path. Yes, it means that now you will have different functions with a slight change between them on each branch, but you can of course extract common logic to be shared among them.
You can also use the object argument mentioned above if you must have a boolean arg (and let's be real, sometimes there is no other way due to different constraints). This will help future code-readers understand your code a lot faster and better.
If you find that your function has too many boolean arguments to it (I think 1 is too many, but let’s say 2 and above) - consider extracting your function into smaller units. It is better to give a function a name which declares exactly what it does than to have a generic name that given a boolean can be this or that.
If you find yourself drilling with a boolean argument down many code layers, stop and rethink :) Your code should probably go through some refactoring, perhaps injecting different implementations altogether when dealing with case A versus case B, instead of insisting that all relevant code should support both A and B.
Do you have other ideas on how to avoid the “function with the boolean argument”? Do you agree it’s an issue or you still don’t see anything wrong with it. Share your thoughts on the comments below :)
Hey! If you liked what you've just read check out @mattibarzeev on Twitter 🍻
Photo by Mariusz Słoński on Unsplash
Top comments (0)