14 ms·
Boolean parameters to API functions considered harmful (2011)
- kwhitefoot 12y agoGood to see that someone agrees with me for a change.
- justinpombrio 12y agoGood to see that you agree with someone for a change.
- tantalor 12y agoAn enum/symbol makes more sense, Xhr.open(type, url, Xhr.ASYNC);
- Liron 12y agoI'll agree that Boolean positional arguments are bad. But nothing wrong with keyword args like myFunc(async=true).
- dnlserrano 12y agoMy exact thought. Ruby ftw! :D
- ygra 12y agoThis gets especially horrible with multiple arguments, each of which is a bool. foo.frob(true, false, true); and now try to figure out what each of them means. It can get better with explicitly stating argument names, but that's only a temporary stopgap. The »two methods, distinguished in name« works fine, as long as you only have a single boolean argument and API growth won't ever happen around that point. Other options that often scale better would be enums, or a parameter object encompassing options, if there are many.
- BurningFrog 12y agoWe used to call that "boolean salad" at an old job.
- philh 12y agoSomething I occasionally saw people do in lisp was (frob foo :quick (not :sneaky) :force) which could be done with strings in other languages foo.frob('quick', not 'sneaky', 'force') It's not ideal, because it makes it look like the arguments mean something. But if someone remembers that frob takes three boolean arguments, but doesn't remember what order they come in, this could be handy. Another option would be foo.frob(bool('quick'), not bool('sneaky'), bool('force')) but that's kind of verbose. EDIT: And the D approach posted above inspires this idea in python: class _ConstAttr: def __init__(self, const): self.const = const def __getattr__(self, attr): return self.const Yes = _ConstAttr(True) No = _ConstAttr(False) foo.frob(Yes.quick, No.sneaky, Yes.force) (It wouldn't be much use in Python, because of keyword arguments, but there might be languages without keyword arguments that could implement something like this. E.g. C macros YES(x) -> 1, NO(x) -> 0. It looks like D enforces the attribute name, so you can't get the arguments mixed up when writing and say No.sneaky, Yes.quick. It would be nice to get that as well.)
- ekimekim 12y agoOne solution I've seen is to use two unique string values instead of True/False, and reject all other string values. eg. foo.frob("quick", "loud", "noforce") foo.frob("slow", "sneaky", "force") foo.frob("fast", "sneaky", "force") # this errors EDIT: Naturally I'd prefer a type-safe approach in languages that offer it, but this is a common solution I've seen in the python world.
- fyolnish 12y agoAlternate title: "Unnamed parameters considered harmful" Alternate (better) solution to the author's problem: Use enums
- mpweiher 12y agoAlternate (better) solution to the author's problem: Use a language where all parameters are named, like Smalltalk or Objective-C. document addObserver:observer forNotificationNamed:'load' isWeak:false. xhr openURL:options url type:options type async:options sync negated
- Argorak 12y agoBut even there, I would prefer: [document addWeakObserver: observer forNotificationNamed: 'load'] and [document addObserver: observer forNotificationNamed: 'load'] Assuming for a second, that the non-weak observer should be the normal case.
- BurningFrog 12y agoSo wordy and repetitive and wordy.
- jjoonathan 12y agoNot a problem if autocomplete is working.
- exDM69 12y agoThe issue with autocomplete is that it makes writing a lot of code really easy but offers no help when reading the code. Syntax/semantic highlighting may help a little but the issue is still there. Given that more time is spent reading than writing code, autocomplete is only a partial solution to this problem.
- yoz-y 12y agoI consider verbose code to be more readable in general. However one has to take more time when naming their functions and parameters.
- paulirish 12y agohall of api shame: the boolean trap, by Ariya (of Esprima and PhantomJS) covers the same topic but is probably a more fun read. http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-t...
- awalton 12y agoBoolean parameters aren't the problem. It's that people use booleans when they mean "something that has two choices", they're too lazy to enumerate what those choices are, and most crucially, it cannot be easily inferred from context what the boolean argument means. It's perfectly fine if your function takes a boolean argument where the parameter either means "TRUE" or "FALSE": EnableThing(bool) is self-descriptive (as long as passing TRUE enables the thing... too many APIs fall victim to this total failure as well). The real problem is when you start using booleans where you lose the meaning. Mapping boolean to things that take on similar meanings { 0, 1 }, { NO, YES }, { OFF, ON }, and maybe stretching it a bit, { BLACK, WHITE } is probably okay as long as it is clearly spelled out what the flag is doing, and likely it should be the only parameter to the function (and definitely not when there are multiple flags, and especially definitely not when those flags have some interaction). Mapping boolean to { PURPLE, YELLOW }, { LEFT TO RIGHT, RIGHT TO LEFT }, or { SYNCHRONOUS, ASYNCHRONOUS } is asking for people to misuse your API, and worse - painting yourself into the corner when it comes time to handle BLUE, TOP TO BOTTOM, or ISOSYNCHRONOUS. In many of these cases, you're far better off just throwing in the ten second enum. ("Functions are cheap" is true for applications, but it's categorically false for libraries, especially those that must stand up to the test of time, so I would definitely stick to the enum - types really are cheap.) His given example is definitely an example of doing it wrong, but I have a better one that I come across every day at my job: gtk_box_pack_start (Box, Widget, bool expand, bool fill, int padding); #1. Two boolean flags, so you must remember the ordering, or look it up every time. #2. One boolean changes the behavior of the other. We don't have a complete mapping; only three of the four states are meaningful. #3. The API is not expressive enough to tell you what expand/fill mean without looking at the documentation, so you have to "do the matrix" and start seeing blonde, brunette, redhead by memorizing the order of the flags. An enumeration would tell you immediately, and be roughly as character efficient as "FALSE, FALSE", "TRUE, FALSE" or "TRUE, TRUE": enum GtkBoxPackingOptions { PACK_SHRINK, PACK_EXPAND_PADDING, PACK_EXPAND_WIDGET }; (And the latter enumeration is exactly what the gtkmm C++ bindings do.)
- Terr_ 12y agoSomewhat related to the "use an enum" approach: Value Objects. Not quite an enum since it's not a design-time constraint, but they can let you "wrap" a scalar (or series of scalars) into something much more self-documenting, and prevent mismatches, like where someone substitutes length for mass. For your example it'd be overkill since there are only four states, but if there was some additional related piece of information, like a number for tolerance, you could make an immutable object to group it all together.
- synesso 12y agoIt goes for all primitive parameters. I can't tell you how many days I've burnt on this kind of bug. But I can tell you it stole 3 hours of my time yesterday. https://twitter.com/badgerhunt/status/587787102684758016 https://twitter.com/badgerhunt/status/587787102684758016
- trendnet 12y agoI like Objective-C for that.
- makeitsuckless 12y agoThis is programming 101. Kind of odd that this hits the front page of HN.
- zimpenfish 12y agoMight be programming 101 but you'd be amazed how much code out there is at the programming 100 level.
- ChrisGaudreau 12y agoAn alternative using ES6 object parameters: function addObserver(observer, func, { weak = false } = {}) { if (weak) { // ... } } addObserver(observer, 'load', { weak: true });
- sanxiyn 12y agoBoolean is harmful, period. https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ https://existentialtype.wordpress.com/2011/03/15/boolean-bli...
- deleted 12y ago[deleted]
- golergka 12y agoI don't see how this has any relation to API. The same argument could be true for any function — after all, your code would only benefit if you develop any class or function in your code base like an API endpoint. And also, it is not the parameter type that is harmful here, but rather how the parameter is named and used. Let me just quote 2nd edition of Code Complete, which was written over 10 years ago: "give boolean variables names that imply true or false". Very simple, almost obvious piece of advice that summaries the problem on hand better than all this post.
- juliangregorian 12y ago"API" has a much broader meaning than the HTTP/REST/JSON variant that everyone is familiar with. Any class, 3rd-party lib, C header file, etc. that you include in your code can be considered to have an API - an application programming interface - as opposed to a application binary interface. I find it a great exercise when developing new code to first plan out the external API that other classes or programs will use to talk to it.
- golergka 12y agoThat's exactly what I meant. Ideally, I first write classes public interface (it's especially easy in C/C++, it's just an almost complete header file), then write some code that uses it to ensure that it is easy and intuitive to use, then unit tests, and only after that get to implementation. It's not always possible to follow this flow all the time (I work in the game industry and unit testing is seldom used, unfortunately), but I always find that I write better code when I follow it.
- FeepingCreature 12y agoAlternate solution: use a language with shortcut syntax for named parameters. foo(sync => true) or foo(=>sync).
- ridiculous_fish 12y agoA lovely trick I found with libdispatch: when the async-nature is specified dynamically, invoke via a function pointer determined by the ternary operator: (async ? dispatch_async : dispatch_sync)(queue, ^{ /* stuff */ }); The reader does a double-take, but it's so damn pretty!
- ape4 12y agoUsually this is good advice. But there are exceptions: enableOutput(bool) is pretty easy to understand. If the function name explains the parameter then its ok.
- spdionis 12y agoWhy not enableOutput() and disableOutput()? enableOutput(false) is also not very clear.
- cnvogel 12y ago...one interpretation might be enableOutput(prefixDate): enableOutput(true) -> enable output with the date prefixed on each line enableOutput(false) -> enable output, date is not prefixed on each line disableOutput() -> disables all output
- yllow 12y agoIt's always a freakout moment when dealing with flags.
- lultimouomo 12y agoI like D solution very much: http://dlang.org/phobos/std_typecons.html#.Flag http://dlang.org/phobos/std_typecons.html#.Flag which results in calls like: document.addObserver(observer, "load", Yes.isWeak); or xhr.OpenUrl(url, options, No.sync)
- btzll 12y agoWow that looks really good. Can you use any String after the dot? P.S. I don't use D
- barrkel 12y agoYou could look at the linked documentation. I think it makes it clear. PS: I don't use D.
- barrkel 12y agoI see the lazy people are out in force :)
- lultimouomo 12y agoYes, you declare the parameter as Flag!"async" And then the possible values are Yes.async and No.async This exploits D dispatch operator: http://dlang.org/operatoroverloading.html#dispatch http://dlang.org/operatoroverloading.html#dispatch which in general is great for implementing this sort of dynamic feature in a strongly statically typed language as D - everything is checked as compile time!
- ahoge 12y agoNot a problem with named arguments. Too bad that they really screwed that up with ES6: function foo({a = 1, b = 2} = {}) {...} Dart's syntax is a lot easier to remember: foo({a: 1, b: 2}) {...} Another very important detail: ES6's syntax is not declarative. You aren't restricted to "compile-time" constants and you can do all kinds of bogus stuff there. function foo({a = 1, b = 2, c} = {c: 3}) { console.log(a, b, c); } foo(); // 1 2 3 foo({a: 'a'}); // a 2 undefined foo({c: 'c'}); // 1 2 c But wait! There is more! function foo({length = 2} = 'foobar') { console.log(length); } foo(); // 6 foo({length: 0}); // 0 foo({x: 'x'}); // 2 Just look at that! Those short Hello World examples are already super confusing brain teasers. I really don't understand why they prioritized this kind of excessive flexibility over tooling. Now you can put entire programs into a function's signature. Why would anyone want that?
- tel 12y agoIt's still a problem with named parameters if the name doesn't dramatically break the symmetry between "true" and "false". Even if it does, booleans are still dangerous because they're harder to check statically.
- dllthomas 12y agoWhich isn't to say named parameters don't help a bunch over boolean positional parameters!
- notacoward 12y agoI've seen people suggest enums, strings, and named parameters. I'm surprised that nobody seems to have mentioned binary flags, because sometimes they work really well. /* caller */ doSomething (arg1, arg2, XYZ_SYNC); /* platform 1 */ #define XYZ_SYNC 0 #define XYZ_ASYNC 0x01 /* platform 2 */ #define XYZ_SYNC 0x01 #define XYZ_ASYNC 0 For extra credit, you can define flags so that one or the other must be specified, so both can't be, etc. Then you can still pack multiple flags into a single parameter, to reduce argument-passing time and space. I know it's all old-school and everything, but it has worked well for a lot of people over the years.
- 67726e 12y agoLanguages without type-checking considered harmful. Seriously, it's 2015, I shouldn't have to worry about getting my arguments out of order and slipping into production code. Test your code, pick a better language, or hell, use enums. Instead we get one hell of a click-bait headline and a very specious argument.
- reinhardt 12y agoCommenting without RTFA considered harmful. TFA talks about passing false instead of true or vice versa.
- 67726e 12y agoCommenting without knowing that I read TFA is also considered harmful, but sorry I hit a nerve. I'm sure your Python/Ruby/Node is going to be nice and maintainable for years to come. Let's just hope all of your parameter names are meaningful, because hey, we have no other means to glean what should be passed in.
- cousin_it 12y agoThe problem is that the type checker won't catch you when you accidentally call foo(true, false) instead of foo(false, true). Using enums is a valid solution, though.
- 67726e 12y agoSo you propose a) Having a "boolean" enum which does't solve the problem or having dozens of unique "boolean" enums which is a maintenance nightmare. Instead, I can use my IDE to tell me the names and ordering of parameters, and a type checker to verify everything at compilation. You'll never completely stop the problem of logical errors, and enums are certainly no panacae.
- cousin_it 12y agoI think in languages like ML or Haskell, people are encouraged to be trigger-happy about creating enums with meaningful names for every tiny use case. That's a viable approach because defining a new enum literally takes one line, and working with enums is very natural due to pattern matching. The maintenance nightmare doesn't seem to happen. Though in languages like Java that approach would be much less convenient.
- michaelochurch 12y agoBoolean parameters aren't always bad. It depends on the language. This is a more superficial explanation of why they're bad: they're hard to name. I agree. More deeply: they also scale horribly. Boolean parameters often are used to switch off to a meaningfully different function, and when they accumulate, you have 2^n different cases. At n = 6, you have two anti-patterns: a function with at least 6 parameters and probably more, and quite possibly 64 different use cases. How likely is it that those parameters all play well together? It's quite probable that there are combinations of the parameters that don't make any sense together. They also don't extend well. If you move from 2 cases to 3, the common behavior (by a maintenance programmer who doesn't understand the code well and is working to deadline, so forgive him) is to add another boolean parameter rather than refactor it to an enum or union. Then you have a "case2" and "case3" boolean parameter and get the problem described above: 4 possibilities, 1 of which is meaningless. With named, optional arguments, boolean parameters can be fine-- if you know that there will never be more than 2 cases. Sometimes they're the right thing. That said, they're a code smell (not always bad, but suggestive that you might be doing something wrong) and I'd almost never use them in Haskell, which (although a great language) doesn't have named arguments, because it's so easy to create your own datatype (that is easier to extend than a boolean).
- dllthomas 12y agoTo some extent, this is also true of any bare type. In C, I try to wrap primatives in a single-element struct that means something, particularly at function boundaries. On a modern compiler this doesn't change the emitted code, but it makes it much easier to avoid shooting yourself in the foot in ways like this.