Avoid Boolean Types

In source code or databases, prefer enumerations over boolean types. Use boolean types only for expressions that compute temporary values fed to conditional expressions like if, while, or SQL’s WHERE.

Using enumerations

For class variables and function parameters, prefer explicit enumerations, even if you expect them to never have more than two values.

Instead of:

SetStyle(red, true);

A customer will see something like:

SetStyle(red, Layer::Foreground)

For database fields, you can always capture more metadata rather than just the boolean. For example, if you’re modeling whether a message has been processed, instead of a boolean processed column, you can add an optional timestamp that changes from null when the message is processed.

This applies to protocol buffers, XML or JSON formats. In JSON, instead of:

{ ... "enabled": true }

Do something like:

{ ... "status": "enabled" }

Separate functions

If you have a function that receives, possibly among other parameters, one boolean controlling some behavior, a common approach is to split the function into two.

In our example, you could replace SetStyle(Color, Layer) with SetForegroundStyle(Color) and SetBackgroundStyle(Color) (and, in general, implement both with a common internal function). Of course, combinatorial explosion makes this unfeasible for functions that receive many boolean inputs.

Rationale

This has a few advantages.

The first advantage is increased type safety. With enumerations, you end up using very specific types for each particular type of predicate. If you accidentally swap two boolean values (corresponding to different semantic properties), the compiler chugs along. Not so with different enums —the type system catches your mistake.

Of course, we could do something stupid like enum MyBoolean { True, False } to negate this advantage. Who would be so stupid as to do this? Well, every time we use a boolean type directly we are, roughly speaking, doing this.

The second advantage, related, is that we make call sites more readable (as the SetStyle example shows).

A third advantage, albeit smaller, is that we future proof our code. We makes it easier to support more values in the future. Perhaps a new status appears —beyond “enabled” and “disabled”, maybe your model unexpectedly gains support for “unavailable”, “lightweight”, or “preview_only”.

You could partially gain some of these advantages thorugh ghost types (e.g., MessageStatus = NewType('MessageStatus', bool)), but if you’re going to do this, you might as well just use an enum. In other words, enums are already the perfect ghost-type-like mechanism for these situations.

Boolean for temporary expressions and predicate checks

Of course, boolean types are necessary: they are required by control mechanisms (if, while, etc., and also high-order functions like any, …).

Adding a function that “checks” if the object has the predicate (i.e., returns a boolean) is okay. If we’re evaluating some complex logical expression —likely with some binary boolean operators— in order to decide which branch to take, it might be okay to temporarily store the result in a boolean variable. Don’t create single-use enums for temporary values constrained to a small scope. Boolean is the right type for such values.

The fundamental principle is: use boolean types for the result of expressions that check whether something (e.g., an instance, a data row, the current function call) matches a logical predicate; don’t store such values in your data (or, at least, don’t store them using boolean types).

“Everything is ephemeral, both what remembers and what is remembered.”

Of course, everything is, ultimately, temporary. In this context, “temporary” refers to values unlikely to survive for long: typically the evaluation of a statement (in a programming language) or a function call (in which the expression that produces them occurs).

Up: Essays on programming