July 3rd, 2024

Don't Use Booleans

Using enums over booleans in programming enhances code readability, prevents errors, and improves API extendability. Enumerations reduce mistakes, simplify code maintenance, and elevate software quality efficiently.

Read original articleLink Icon
Don't Use Booleans

The article discusses the advantages of using enums over booleans in programming. Enumerations are suggested as a better choice for improving code readability, ensuring explicit typing to prevent errors, and enhancing behavior-driven design and extendability of APIs. By replacing boolean parameters with enums, developers can enhance code clarity, reduce the risk of passing incorrect flags, and simplify the process of extending APIs without creating compatibility issues. The author emphasizes the benefits of enums in reducing errors, improving code maintainability, and enhancing the overall quality of software development. The use of enums is recommended as a valuable practice that offers low overhead and significant advantages in software engineering.

Link Icon 32 comments
By @hardwaresofton - 3 months
Tangentially -- use languages that have great enum support.

Well you knew what was coming -- Rust enums are excellent[0], and so are Haskell's[1] (try and spot the difference between an enum and a record type!)... But that probably won't help you at $DAYJOB.

A bit more on topic though -- I'd like to see a strong opinion on Option<SomeEnum> versus SomeEnum containing a Missing variant. I usually lean towards Option<SomeEnum> but I wonder if there are any devout zero value proponents.

I don't like how golang does/requires zero values, but in more expressive languages I do waver sometime between Option<T> and T w/ a "missing" indicator variant inside.

[0]: https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html

[1]: https://wiki.haskell.org/Type

By @kazinator - 3 months
This goes away in a language that allows arguments to name the parameters:

  fetch(accountId, history = true, details = false);
However, I would be opposed if this mechanism is permitted to perturb the order of fixed arguments. If history is the third argument, rather than the second, it should error.

I.e. "history = true" means "we are passing true as the second parameter, which we believe to be called 'history', on penalty of error".

Fixed parameters having their names mentioned and checked should not be confused with the language feature of keyword parameters, which passes an unordered dictionary-like structure.

By @the__alchemist - 3 months
You can think of booleans as a built-in, two-value enum that has a common semantic meaning. Use booleans if this is suitable for your purpose; it will be suitable for many.

That said... this article feels like a bit of a strawman. Does anyone by default use three booleans in cases that are arechetypical enums?

By @throwaway81523 - 3 months
The antipattern in the article is also called "boolean blindness" sometimes.

https://existentialtype.wordpress.com/2011/03/15/boolean-bli...

https://duckduckgo.com/?q=boolean+blindness

By @cassepipe - 3 months
It seems it's not so much booleans than using unnamed parameters for option parameters. I find myself using this style in C, it does not prevent a mix-up if someone refuses to use named field designators but it works ok :

    typedef struct options_s {
        bool toggle_case;
        bool strip_whitespace;
    } options_s;
    
    char *modify_string(char *str, options_s options)
    {
            if (options.toggle_case) { /**/ }
            if (options.strip_whitespace) { /**/ }
            return str;
    }
    
    int main(void) {
            char str[] = "Hacker News";
            modify_string(str, (options_s){ .toggle_case = true, .strip_whitespace = false });
    }

Now that I think of it it's probably trivial to forbid the use of struct literals without designated fields in code in a linter

Maybe we get anonymous struct function parameter declaration with C32 ? :D

EDIT: I have been asking around to people fluent in standardese and if you leave out fields in a struct literal you are guaranteed they will be zeroed-out

By @pavel_lishin - 4 months
Folks, the headline is real bad and clickbaity, but I think the blog post is actually very, very correct in its basic premise.
By @_fat_santa - 3 months
Tangential but coding in JS/TS, I often will go for object arguments to make things more readable. If you have a function like:

foo(arg1: boolean, arg2: boolean, arg3: boolean)

Then when you call it it will look like foo(true, false, true) which is not great for readability. Instead I move all the arguments into an object which makes each field explicit. Ie.

foo({ arg1: true, arg2: false, arg3: true })

This also carries the advantage that you can quickly add/remove arguments without going through an arduous refactor.

By @cesaref - 3 months
I interpreted this as 'don't write crappy functions taking confusing arguments'. If the function had taken 4 floating point numbers, the argument would be the same - it would be confusing to know what is what.
By @ayhanfuat - 3 months
By @kierenj - 3 months
Solution in C#: use named parameters: RaiseInvoice(taxInvoice: false, sendEmail: true)
By @edflsafoiewq - 3 months
Downsides: you have to name and import all the enums (notice no one ever shows this in code examples). You can't do boolean math on them (and, or, etc.).
By @happytoexplain - 3 months
The assumption being that the language you are using does not have named parameters. Something all modern languages have, except, as usual, the ones the world is built on (JS, Java).
By @HumblyTossed - 3 months
Yes, use booleans. Why would I want one more layer of abstraction staring at me mocking my memory?
By @camgunz - 3 months
I feel like this comes up here and there, and generally what I think is these probably aren't good functions. Enums won't save you from all the twisting and turning you're doing in a function that takes 3 booleans.

Rules like this aren't that useful IMO. What if you have like a SetActive(bool active) method? Does this also get an enum? Should this be two methods? Blah blah blah. You can only cultivate taste with experience and consideration, not with rules. Everyone's seen codebases that followed all the rules but were still total messes. Or as Tool said: think for yourself; question authority.

By @dwattttt - 3 months
I haven't seen it mentioned elsewhere in this thread, but I really appreciated my IDE adding extra information in function calls, annotating create(true, false) into create(initial_state: true, reset: false)
By @fifticon - 3 months
One more benefit to favor enums for booleans here. When passing facts/intent solely through a generic true/false, and possible a single more or less well-named identifier, you have increased risk of multiple and differing interpretations of what the values mean: Did 0 or 1 mean failure or success? did false mean that the file did not exist or that you are not allowed to access it? By explicitly naming what your 'true' and 'false' cases mean, you _can_ lessen this risk. (Of course, you can still communicate badly both with booleans and with badly named enums.) Also for return values for functions, you don't have an identifier name (not all acting functions share name with their "report-back"). Further, enums _may_ upgrade elegantly when you learn your boolean assumption had >2 cases.

I read about half of of submissions, I don't know if others also said all this already.

By @whalesalad - 3 months
Keyword arguments or named parameters solve this. In JS I tend to pass a single object to a function with a large number of parameters like this. But I do agree using named entities like enums or constants is also good. A bit heavier but if you do it everywhere the cost is worth it.
By @compressedgas - 4 months
By @0x63_Problems - 4 months
I'm definitely a fan of making bad data 'unrepresentable' through the type system. The bit flag enum pattern is not so familiar to me in Python, but I recognize it from the CPython internals so maybe it's more common in systems languages. Not sure how I feel about explicitly listing every valid combination of flags, but I'm sure in some cases that's the right move.
By @bdjsiqoocwk - 3 months
One thing that annoys me about enums is that I'm always afraid that I'll write code like

    @enum MyEnum A B
    sometest = x == A ? B
Which is correct but that one that I'll add new elements to the enum

    @enum MyEnum A B C
and forget to check if that code is still correct. Suggestions?
By @cassepipe - 3 months
Elixir's approach is great imho : https://hexdocs.pm/elixir/keywords-and-maps.html
By @itotallydont - 3 months
fetch( accountId, true, // include disabled, true, // fetch history, true, // fetch details );

typescript saves the day again

fetch(accountId: AccountId, options: {includeDisabled?: boolean, history?: boolean, details?: boolean})

By @Night_Thastus - 3 months
I agree...mostly.

Of course, doing this adds overhead to the developer at the time the code is written. Instead of just writing 'bool', you need to go into the header and define a new enum and decide what it and each of its entries will be named, and make sure that it is propagated up through any dependencies that will interact with that function and so on.

Frankly, that can be a lot more work - especially in a large legacy code base.

Sometimes just a simple Bool is easier.

By @andrewstuart - 3 months
The beauty of booleans is the clarity and simplicity.
By @slivanes - 3 months
Got it.

use_boolean = false;

By @beyondCritics - 3 months
Basic style guide stuff.
By @1899-12-30 - 3 months
use enums instead

type boolean = (true, false, EFileNotFound)

By @bionhoward - 3 months
Love it. Simple, straightforward advice. Thank you.
By @scosman - 3 months
I’m reminded of what a beautiful language objective C is. It completely solves this problem. For example, take a look at this iOS method name (real Apple API):

initwithenablefan:enableAirConditioner:enableClimateControl:enableAutoMode:airCirculationMode:fanSpeedIndex:fanSpeedPercentage:relativeFanSpeedSetting:temperature:relativeTemperatureSetting:climateZone:

/s

By @sebastianconcpt - 3 months
Tell me you're almost ready for Rust without telling me you're almost ready for Rust.