8 ms·
Don't use booleans (2019)
- pavel_lishin 2y agoFolks, the headline is real bad and clickbaity, but I think the blog post is actually very, very correct in its basic premise.
- kromenak 2y agoTotally agree - I appreciate and understand (and agree with) the enum idea. But stating “don’t use booleans” is a terrible absolutist ideas that will lead to terrible code. Imagine a student reads it, takes it to heart, and starts creating dedicated enums for every logic branch in their code.
- compressedgas 2y agoReminds me of https://blog.ometer.com/2011/01/20/boolean-parameters-are-wrong/ https://blog.ometer.com/2011/01/20/boolean-parameters-are-wr...
- 0x63_Problems 2y agoI'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.
- ervine 2y agoYes, 100%. Although you end up with some super gnarly types sometimes, so much better than marking a bunch of properties as optional but not handling certain combinations of them being passed in correctly. Lord knows I still do that most of the time though...
- relaxing 2y agoIn Python you can use keyword arguments to the same benefit.
- kierenj 2y agoSolution in C#: use named parameters: RaiseInvoice(taxInvoice: false, sendEmail: true)
- gary_0 2y agoWhich is far more realistic than defining single-use enums all over the place. I don't know why more languages don't have named parameters...
- kerkeslager 2y agoWhy is defining an enum "unrealistic"? It's at most 4 lines of very simple code to define an enum that replaces a boolean.
- edflsafoiewq 2y agoThink about cases more complicated than passing a bare literal. // Booleans + Named Parameters foo(should_scrub=not options.scrubbing_disabled) // Single-Use Enum import ScrubbingOption, ShouldScrub; foo( if options.scrubbing == ScrubbingOption::Disabled { ShouldScrub::No } else { ShouldScrub::Yes } )
- oneeyedpigeon 2y agoIf you format the second example more sensibly: foo(options.scrubbing == ScrubbingOption::Disabled ? ShouldScrub::No : ShouldScrub::Yes) it doesn't look much worse at all
- kerkeslager 2y agoI'd actually go further and say that there's no reason I can think of that foo() shouldn't have access to the ScrubbingOption, so just pass that in. If you have two-option enums and for some reason you transform them into two-option enums that represent essentially the same thing, of course that's going to be unnecessarily complex. As a general rule, "it's more verbose" isn't much of a concern to me: verbosity is a very poor indication of complexity, and entering code with your keyboard simply isn't the bottleneck for writing code (if it is, you aren't thinking about what you're writing enough).
- deleted 2y ago[deleted]
- _fat_santa 2y agoTangential 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.
- ervine 2y agoBig time.
- presentation 2y agoYou can actually set up VSCode and probably other editors to show the parameter names inline with the values.
- nine_k 2y agoSame for JetBrains IDEs.
- someoneontenet 2y agoHow is this done for js?
- thamer 2y agoAs far as I know it is not a native feature, but inline hints for parameter names can be added with a VSCode extension called "Inline Parameters for VSCode": https://marketplace.visualstudio.com/items?itemName=liamhammett.inline-parameters https://marketplace.visualstudio.com/items?itemName=liamhamm... The only languages it supports are JavaScript, TypeScript, PHP, and Lua. I can confirm that it works for JavaScript and TypeScript, I haven't tried the other two. A similar extension named "Inline Parameters Extended for VSCode" supports a different set of languages: Golang, Java, Lua, PHP, and Python. It seems to be a fork of the aforementioned.
- whalesalad 2y agoKeyword 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.
- the__alchemist 2y agoYou 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?
- thomasingalls 2y agoUnfortunately, yes
- replyifuagree 2y agoOh gawd yes they do!
- pavel_lishin 2y agoMy friend, I'd love to give you a tour of our codebase.
- makeitdouble 2y agoIt usually starts with one boolean. Then people avoid touching it when expending a bit, so they just add another boolean for the corner case they're dealing with (potentially as an optional last param). And so on, until someone refactors the whole function bearing the burden to retouch all its legacy that accumulated up to that point.
- asddubs 2y agoIt usually starts with "is_car", a simple, clear, yes or no. and then at some point someone needs to also check if it's a sedan and instead of turning it into an enum called vehicle_type an "is_sedan" is added, etc.
- PhilipRoman 2y agoThe other side of the coin is making an enum and eventually realizing that some of the types can overlap. I prefer the "set of tags" approach due to its flexibility.
- 2y ago
- ayhanfuat 2y agoAlso called the Boolean trap: https://ariya.io/2011/08/hall-of-api-shame-boolean-trap https://ariya.io/2011/08/hall-of-api-shame-boolean-trap
- deleted 2y ago[deleted]
- happytoexplain 2y agoThe 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).
- bluefirebrand 2y agoNamed parameters are easy enough to mimic in JS using objects and the spread operation function foo({bar, baz}) { ... } foo({bar: 1, baz: false})
- ervine 2y agoLemme be an absolute pedant and say that's destructuring, not spreading. And correct me if I'm wrong, nothing worse than an incorrect pedant.
- asddubs 2y agothat's its own kind of horrible though. now either all your functions just take single object parameters or you have to remember which ones. and I get javascript is light on checking what was passed is actually what was expected but this seems to somehow make that situation even worse still
- edflsafoiewq 2y agoDownsides: 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.).
- kerkeslager 2y agoIn most languages with strong types, the IDE will handle imports for you pretty easily. Selecting an import with a hotkey isn't as hard as debugging a mysterious boolean. Not being able to use logical operations on them is a feature, not a bug: logical operations on unnamed values here are going to be even less readable than passing them into functions.
- ruszki 2y agoThe IDE, like IntelliJ, can tell you which boolean has what name without named parameters.
- itotallydont 2y agofetch( 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})
- kerkeslager 2y agoThat doesn't really solve any but the first problem mentioned in the article.
- slivanes 2y agoGot it. use_boolean = false;
- wgjordan 2y agothing_to_use = enums; // fixed
- andrewstuart 2y agoThe beauty of booleans is the clarity and simplicity.
- throwaway81523 2y agoThe antipattern in the article is also called "boolean blindness" sometimes. https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ https://existentialtype.wordpress.com/2011/03/15/boolean-bli... https://duckduckgo.com/?q=boolean+blindness https://duckduckgo.com/?q=boolean+blindness
- HumblyTossed 2y agoYes, use booleans. Why would I want one more layer of abstraction staring at me mocking my memory?
- mrkeen 2y agoYour brain's memory, or your computer's? If your compiler isn't handling enums efficiently it may be time to get a new compiler.
- sebastianconcpt 2y agoTell me you're almost ready for Rust without telling me you're almost ready for Rust.
- Quothling 2y agoYou're not wrong, but you're probably getting downvoted because you didn't elaborate on what you mean. Possibly also because the way Rust handles structs isn't exactly unique to rust. Go does it, Typescript does it, and so on.
- sebastianconcpt 2y agoHappy to elaborate. Although I was having enums in mind https://www.youtube.com/watch?v=Epwlk4B90vk https://www.youtube.com/watch?v=Epwlk4B90vk
- bionhoward 2y agoLove it. Simple, straightforward advice. Thank you.
- hardwaresofton 2y agoTangentially -- 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 https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html [1]: https://wiki.haskell.org/Type https://wiki.haskell.org/Type
- teaearlgraycold 2y agoTypescript is great for this. function operation(user: User, state: “active” | “inactive”): void Boom. Done. Enum defined. You want people to use enums? Remove all context switching from the definition process.
- leobg 2y agoSame in Python, no? def foo(bar: Literal["active", "inactive"])
- hardwaresofton 2y agoWhat's the best Python type checker these days? Seems like there are at least 4: Mypy, Pytype, Pyright, and Pyre
- Terretta 2y agobasedpyright: https://github.com/DetachHead/basedpyright https://github.com/DetachHead/basedpyright (assuming you can't/don't use MSFT's pylance)
- hardwaresofton 2y ago
- cassepipe 2y agoIt 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
- Gibbon1 2y agoIf a giant meteor took out WG14 we might get named arguments in C modify_string(.str=str, .options = { .toggle_case = true, .strip_whitespace = false });
- CoastalCoder 2y agoI've wanted a similar thing for C++. Anyone know what the reasons have been for the language not adopting this?
- wavemode 2y agoIt would cause argument names to become part of the function signature, which would be a significant change to the language, and its ABI.
- wk_end 2y agoWouldn’t you be able to treat this as syntactic sugar - effectively you rearrange the arguments as needed and strip the labels. Doing that wouldn’t necessitate any kind of change to the ABI, I think.
- kazinator 2y agoThis 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.
- CoastalCoder 2y agoIs this an example from some well-known compiled language? I work mostly in C++, and this is a feature I've wished for for a long time.
- ijlx 2y agoSwift and C# appear to have this feature, though they use the syntax fetch(accountId, history: true, details: false);
- yen223 2y agoKotlin does this https://kotlinlang.org/docs/functions.html#named-arguments https://kotlinlang.org/docs/functions.html#named-arguments
- kazinator 2y agoNope! > "When you use named arguments in a function call, you can freely change the order that they are listed in." That violates a key requirement in my specification, which means that I regard it as broken. Positional parameters should take arguments only in their designated argument position. Only non-positional parameters (keyword parameters) should be allowed to be specified in any order.
- JamesonNetworks 2y ago
- bdjsiqoocwk 2y agoOne 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?
- mrkeen 2y agoPattern-match over them and don't use a catch-all clause. Let your compiler emit warnings for unhandled cases.
- scosman 2y agoI’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
- cesaref 2y agoI 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.
- tialaramex 2y agoSure, but booleans are the lowest hanging fruit, most programmers won't write many functions which take 3 floating point numbers and are then modified to add a fourth but they will write functions which have three flag booleans and then add a fourth. The generalization of the author's lesson is the New Type idiom. Don't use basic types like "integer" when you can invent types which correspond to the actual meaning of your values. A Duration, a FileDescriptor, a DatabaseHandle, a KeyboardScanCode. Once we do this it's obvious that if the function takes a FileDescriptor and a Duration we should not give it this KeyboardScanCode we have twice, that's clearly nonsense. Rust piles on extra value for its own New Types (as yet you cannot directly grant this to your own types although if they're enumerations they get it for free) - niches. Result<(), OwnedFd> is the same size (a single 32-bit integer) as the C int you'd have used for this purpose in a much less capable language, but since it's a Result type it has all the same ergonomics as if it was much more complicated and heavyweight than that.
- dwattttt 2y agoI 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)
- cassepipe 2y agoElixir's approach is great imho : https://hexdocs.pm/elixir/keywords-and-maps.html https://hexdocs.pm/elixir/keywords-and-maps.html
- 1899-12-30 2y agouse enums instead type boolean = (true, false, EFileNotFound)
- fifticon 2y agoOne 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.
- fifticon 2y agocaveat: In languages where enums silently accept random ints, they break this somewhat, sadly.
- beyondCritics 2y agoBasic style guide stuff.
- Night_Thastus 2y agoI 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.
- camgunz 2y agoI 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.
- yellowapple 2y ago> What if you have like a SetActive(bool active) method? Does this also get an enum? I'd be inclined to do away with the argument entirely, instead going with separate SetActive() and SetInactive() methods.
- camgunz 2y ago>> I'd be inclined to do away with the argument entirely > Should this be two methods? Blah blah blah.