38 comments

[ 2.7 ms ] story [ 51.6 ms ] thread
One argument that I’m missing in the article is that with an enumerated, states are mutually exclusive, while withseveral booleans, there could be some limbo state of several bool columns with value true, e.g. is_guest and is_admin, which is an invalid state.
I’m not a very experienced programmer, but the first example immediately strikes me as weird. The consideration for choosing types is often to communicate intend to others (and your future self). I think that’s also why code is often broken up into functions, even if the logic does not need to be modular / repeatable: the function signature kind of “summarizes” that bit of code.

Making a boolean a datetime, just in case you ever want to use the data, is not the kind of pattern that makes your code clearer in my opinion. The fact that you only save a binary true/false value tells the person looking at the code a ton about what the program currently is meant to do.

What I'm getting out of this is boolean shouldn't be a state that's durably stored, it's ephemeral, an artifact of runtime processing. You wouldn't likely durably store a boolean in an OLTP store, but your ETL into the OLAP store may capture a boolean to simplify logic for all the systems using the OLAP store to drive decision support. That is, it's an optimization. That feels right, but I've never really thought through this before. Interesting!
To summarise: booleans should be derived, not stored
I generally prefer to let one value mean one thing.

Allowing the presence of a dateTime (UserVerificationDate for example) to have a meaning in addition to its raw value seems safe and clean. But over time in any system these double meanings pile up and lose their context.

Having two fields (i.e. UserHasVerified, UserVerificationDate) doesn't waste THAT much more space, and leaves no room for interpretation.

I dont like this pattern.

The author example, checking if "Datetime is null" to check if user is authorized or not, is not clear.

What if there are other field associated with login session like login Location ? Now you dont know exactly what field to check.

Or if you receive Null in Datetime field, is it because the user has not login, or because there is problem when retriving Datetime ?

This is just micro-optimization for no good reason

> But, you're throwing away data

Often it’s intentional for privacy. Record no more data than what’s needed.

replace "should" with "could".

I do think its wise to consider when a boolean could be inferred from some other mechanism, but i also use booleans a lot because they are the best solution for many problems. Sure, sometimes what is now a boolean may need to become something later like an enum, and that's fine too. But I would not suggest jumping to those out the gate.

Booleans are good toggles and representatives of 2 states like on/off, public/private. But sometimes an association, or datetime, or field presence can give you more data and said data is more useful to know than a separate attribute.

The scope of TFA is data modelling, where it advises to use more descriptive data values, such as enums or happenedAtTimestamp.

However, personally I agree with the advice, in another context: Function return types, and if-statements.

Often, some critical major situation or direction is communicated with returned booleans. They will indicate something like 'did-optimizer-pass-succeed-or-run-to-completion-or-finish', stuff like that. And this will determine how the program proceeds next (retry, abort, continue, etc.)

A problem arises when multiple developers (maybe yourself, in 3 months) need to communicate about and understand this correctly.

Sometimes, that returned value will mean 'function-was-successful'. Sometimes it means 'true if there were problems/issues' (the way to this perspective, is when the function is 'checkForProblems'/verify/sanitycheck() ).

Another way to make confusion with this, is when multiple functions are available to plug in or proceed to call - and people assume they all agree on "true is OK, false is problems" or vice versa.

A third and maybe most important variant, is when 'the return value doesn't quite mean what you thought'. - 'I thought it meant "a map has been allocated".' - but it means 'a map exists' (but has not necesarily been allocated, if it was pre-existing).

All this can be attacked with two-value enums, NO_CONVERSION_FAILED=0, YES_CONVERSION_WAS_SUCCESFUL=1 . (and yes, I see the peril in putting 0 and 1 there, but any value will be dangerous..)

I read an article with the same premise here a few years ago.

A Boolean is a special, universal case of an enum (or whatever you prefer to call these choice types...) that is semantically valid for many uses.

I'm also an enum fanboy, and agree with the article's examples. It's conclusion of not using booleans because enums are more appropriate in some cases is wrong.

Some cases are good uses of booleans. If you find a Boolean isn't semantically clear, or you need a third variant, then move to an enum.

I was hoping to read about bitfields or bit flags.
A piece of advice I read somewhere early in my career was "a boolean should almost never be an argument to a function". I didn't understand what the problem was at the time, but then years later I started at a company with a large Lua code-base (mostly written by one-two developers) and there were many lines of code that looked like this:

   serialize(someObject, true, false, nil, true)
What does those extra arguments do? Who knows, it's impossible without looking at the function definition.

Basically, what had happened was that the developer had written a function ("serialize()", in this example) and then later discovered that they wanted slightly different behaviour in some cases (maybe pretty printed or something). Since Lua allows you to change arity of a function without changing call-sites (missing arguments are just nil), they had just added a flag as an argument. And then another flag. And then another.

I now believe very strongly that you should virtually never have a boolean as an argument to a function. There are exceptions, but not many.

I'll expand on the first example, the datetime one.

Many user databases use soft-deletes where fields can change or be deleted, so user's actions can be logged, investigated or rolled back.

When user changes their e-mail (or adds another one), we add a row, and "verifiedAt" is now null. User verifies new email, so its time is recorded to the "verifiedAt" field.

Now, we have many e-mails for the same user with valid "verifiedAt" fields. Which one is the current one? We need another boolean for that (isCurrent). Selecting the last one doesn't make sense all the time, because we might have primary and backup mails, and the oldest one might be the primary one.

If we want to support multiple valid e-mails for a single account, we might need another boolean field "isPrimary". So it makes two additional booleans. isCurrent, isPrimary.

I can merge it into a nice bit field or a comma separated value list, but it defeats the purpose and wanders into code-golf territory.

Booleans are nice. Love them, and don't kick them around because they're small, and sometimes round.

The timestamps instead of boolean thing is something good engineers stumble upon pretty reliably. One gotcha is the database might be weird about indexing nulls. I'm not going to give an example because you should really read the docs for your specific database if this matters.

The ever growing set of boolean flags seems to be an attractor state for database schemas. Unless you take steps to avoid/prohibit it, people will reach for a single boolean flag for their project/task. Fortunately it's pretty easy to explain why it's bad with a counting argument. e.g. There are this many states with booleans, and this fraction are valid vs. this many with the enum and this fraction are valid. There is no verification, so a misunderstanding is more likely to produce an invalid state than a valid state.

This is such a weird advice and it seems to come from a particular experience of software development.

How about using Booleans for binary things? Is the LED on or off, is the button pressed or not, is the microcontroller pin low or high? Using Enums, etc. to represent those values in the embedded world would be a monumental waste of memory, where a single bit would normally suffice.

I would add counters to this list. Start from zero (false), and then you know not just whether an event has occurred, but how many times.
I once, briefly, worked with a developer who believed that you should never use primitive types for fields or parameters...
Maybe for the DB domain author is talking about but the nice thing about a bool is that it's true or false. I don't have to dig around documentation or look through the code what the convention of converting enum, datetime, etc. to true/false is. 1970/1/1 (I was four years old then, just sayin), -6000 or something else?

Nullable helps a lot here but not all languages support that the same way.

> A lot of boolean data is representing a temporal event having happened. For example, websites often have you confirm your email. This may be stored as a boolean column, is_confirmed, in the database. It makes a lot of sense.

> But, you're throwing away data: when the confirmation happened. You can instead store when the user confirmed their email in a nullable column. You can still get the same information by checking whether the column is null. But you also get richer data for other purposes.

So the Boolean should be something else + NULL?

Now we have another problem ...

Booleans don't "remember" what they mean. They're just a `true` or a `false`, the association with the `is_authenticated` variable or whatever has to be maintained by programmer discipline. But when you have an enum variant like `Authenticated`, that's encoded in the value itself, helped by the type system. It can't be confused with some other state or condition.

Booleans beget more booleans. Once you have one or two argument flags, they tend to proliferate, as programmers try to cram more and more modalities into the same function signature. The set of possible inputs grows with 2^N, but usually not all of them are valid combinations. This is a source of bugs. Again, enums / sum-types solve this because you can make the cardinality of the input space precisely equal to the number of valid inputs.

Please if you are in this situation do not take this advice. You just generate massive garbage abstractions upstream. If boolean arguments are out of hand, the problem isn't the boolean.
My favorite Java code I've ever seen is:

    @Nullable Optional<Boolean> foo;
For when 3 values for a boolean just aren't enough.

Here are two rules I learned from data modelling and APIs many years ago:

1. If you don't do arithmetic on it, it's not a number. ID int columns and foreign keys are excluded from this. But a phone number or an SSN or a employee ID (that is visible to people) should never be a number; and

2. It's almost never a boolean. It's almost always an enum.

Enums are just better. You can't accidentally pass a strong enum into the wrong parameter. Enums can be extended. There's nothing more depressing than seeing:

    do_stuff(id, true, true, false, true, false, true);
This goes for returning success from a function too.
Oh this is fantastic! I'm giving a talk in about a month at work on how to use the python type system in useful ways to catch more bugs before runtime, and this seems like a great point to throw in there as an aside at the very least!