Data vs logic
On a big picture (and oversimplified) scale, code can be segregated into two categories: data and logical flow. Many people can add additional ones they think is just as important but they're irrelevant for the answer at hand.
You're right that booleans for logical flow are bad practice. I would slightly alter that interpretation and say that it's bad practice for a public API (what you do internally matters less), but that's maybe a different discussion for a different day.
Booleans as data
By excluding booleans for logical flow, we haven't actually excluded booleans that function as data.
For example, while you might expect an update method to look like this:
public void Update(User u) { ... }
maybe you want to limit which fields of the user can be updated (e.g. not the username, but the user's first/last name can be changed), which leads you to:
public void UpdateUser(string firstName, string lastName)
It's not too farfetched to think of examples where a boolean field can be changed:
public void UpdateUser(string firstName, string lastName, bool isAlive)
And this is a basic example of why boolean parameters do exist. Some data simply has an innate binary nature, and therefore it is best represented by a boolean.
Let's ignore the "dead people can't come back alive" argument and say that we allow for fixing bad data (we thought the person was dead but we were wrong). I picked living/dead because we can all agree that this is a binary value. Other options exist but I picked the one with the most commonly agreed upon consensus.
No passing allowed?
The method should use the boolean value itself and not simply pass it through to some other method.
I find this a bit too restrictive of a requirement. In effect, the above passes the boolean through many layers down to the DAL. It's eventually translated into e.g. a SQL statement, but it has passed through many layers before it gets to that stage.
Your current phrasing excludes this as a valid answer, but I very much disagree that this particular example is invalid for the core of your question.
Booleans should never change logical flow?
I also find the "no logical flow repercussions from booleans" argument to be excessively broad and restrictive. Taken to heart, you're effectively suggesting that the if
statement should have never existed.
I can also think of cases where the boolean causes logical flow variations yet still is relevant, e.g. if the backend uses separate external services for handling living/deceased people.
Consider an application which connects to external services (e.g. government registry: one for the living, one for the deceased), where you do not want to expose to your user that different services are being used. "Unbooleaning" a method leads to something along the lines of
public void DoActionForDeadPerson(...)
public void DoActionForLivingPerson(...)
(Obviously, better names are needed in reality)
But this requires the consumer (i.e. the user of your backend service) to know that there is separate handling for living/deceased people, and this may be considered a leaking abstraction in certain scenarios.
It's perfectly reasonable for your application to specifically hide that fact that there are separate repositories. For example, maybe the living/deceased resource split happened after your application went live (it used to be the same resource) and you're trying to implement it without affecting your public API.
Another example
If you can only think of an example with more than one parameter, that's OK too.
While I think there's no real reason to differentiate between methods with only a boolean parameter and methods with multiple parameters (one of which is a boolean), I'll humor you and give you an additional example of a function with only a boolean parameter:
public void SetUserFieldsReadonly(bool isReadonly)
{
txtFirstName.IsReadonly = isReadonly;
txtLastName.IsReadonly = isReadonly;
txtUserName.IsReadonly = isReadonly;
}
Note that in this case, isReadonly
again acts as a data-boolean as opposed to a logical-flow-boolean. It's the same underlying principle as the previous example.
Whether you consider the boolean "passed" on is very arguable. While passing it to a submethod obviously counts as passing it on, does setting a property count too? I think this is highly subjective and liable to opinionated feedback.
Can you separate this into a SetReadonly()
and SetEditable()
? Sure. But it's unnecessary bloat, in my honest opinion. It's copy/paste and whenever you change one method, you're obviously also going to have to change the other method, so it does not pass the DRY test.
But I still agree with the orignal intention of the "no logical flow" rule
As a basic example of boolean usage where I do agree with you that it should not happen:
public void AddName(string name, bool alsoAddLowerCaseName)
{
_myList.Add(name);
if(alsoAddLowerCaseName)
_myList.Add(name.ToLower());
}
It would be much better by letting the consumer call the Add
method twice.
string name = "Robert";
AddName(name);
AddName(name.ToLower());
The consumer retains the same control (choosing whether to add the lower case name or not) but it does not require you to keep changing the method for new possibilities (e.g. upper case, reversed string, HTML encoded string, ...)
I believe that these cases are the core concern of your "no logical flow booleans" argument and I do agree with you there.