
Boolean parameters to API functions considered harmful (2011) - luu
http://jlebar.com/2011/12/16/Boolean_parameters_to_API_functions_considered_harmful..html
======
fyolnish
Alternate title: "Unnamed parameters considered harmful"

Alternate (better) solution to the author's problem: Use enums

~~~
mpweiher
Alternate (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

~~~
BurningFrog
So wordy and repetitive and wordy.

~~~
jjoonathan
Not a problem if autocomplete is working.

~~~
exDM69
The 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.

~~~
mpweiher
This comment makes sense for the original problem, but not for the message
you're replying to, as keyword parameters help _tremendously_ with
readability.

~~~
BurningFrog
I've never worked in those languages, but I suspect I'd end up with a lot code
like this:

 _foo(author: author, title: title, price: price)_

The problem in the article is real, but it is the exception. If it's hard to
see what 5% of your parameters mean, forcing 100% of them is probably a cure
worse than the disease.

When I worked in Java with IntelliJ, the solution was to simply hover over the
call, and the declaration would appear.

~~~
mpweiher
While I can't speak to what you would end up with, my experience (~quarter of
a century) is that this is not the case. To verify, I just did

    
    
       ack '\[.*:' | less
    

to search my code-base for message sends with parameters. Scanning the first
hundred or so results shows the pattern you mention to be rare.

Dan Ingalls explains the syntax really nicely in this video:

    
    
      https://youtu.be/P2mh92d-T3Y?t=600
    

With this type of syntax, most APIs turn into EDSLs all by themselves, and
often read like fairly natural sentences.

I ramble on a bit about the relationship between keyword syntax and (lack of)
operator overloading:

    
    
      http://blog.metaobject.com/2015/03/why-overload-operators.html

------
awalton
Boolean 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_
Somewhat 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.

------
ygra
This 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.

~~~
philh
Something 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
One 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.

------
notacoward
I'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.

------
paulirish
_hall 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-t...](http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-
trap.html)

------
lultimouomo
I 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
Wow that looks really good. Can you use any String after the dot?

P.S. I don't use D

~~~
barrkel
You could look at the linked documentation. I think it makes it clear.

PS: I don't use D.

~~~
barrkel
I see the lazy people are out in force :)

------
golergka
I 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
"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
That'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.

------
ahoge
Not 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
It'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
Which isn't to say named parameters don't help a bunch over boolean positional
parameters!

------
ridiculous_fish
A 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!

------
sanxiyn
Boolean is harmful, period.

[https://existentialtype.wordpress.com/2011/03/15/boolean-
bli...](https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/)

------
Liron
I'll agree that Boolean _positional_ arguments are bad. But nothing wrong with
keyword args like myFunc(async=true).

~~~
dnlserrano
My exact thought. Ruby ftw! :D

------
tantalor
An enum/symbol makes more sense,

    
    
      Xhr.open(type, url, Xhr.ASYNC);

------
synesso
It 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)

------
ChrisGaudreau
An alternative using ES6 object parameters:

    
    
      function addObserver(observer, func, { weak = false } = {}) {
        if (weak) {
          // ...
        }
      }
    
      addObserver(observer, 'load', { weak: true });

------
dllthomas
To 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.

------
ape4
Usually 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
Why not enableOutput() and disableOutput()? enableOutput(false) is also not
very clear.

------
FeepingCreature
Alternate solution: use a language with shortcut syntax for named parameters.
foo(sync => true) or foo(=>sync).

------
trendnet
I like Objective-C for that.

------
makeitsuckless
This is programming 101. Kind of odd that this hits the front page of HN.

~~~
zimpenfish
Might be programming 101 but you'd be amazed how much code out there is at the
programming 100 level.

------
yllow
It's always a freakout moment when dealing with flags.

------
michaelochurch
Boolean 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).

------
67726e
Languages 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
Commenting without RTFA considered harmful.

TFA talks about passing false instead of true or vice versa.

~~~
67726e
Commenting 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
The 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
So 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
I 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.

~~~
67726e
I can understand that line of thinking, but in given the OP is talking in JS,
and and that I've only ever written such languages at Uni or for practice, I
don't find them applicable to my day to day. I deal with Java, Scala, JS, and
a few other C-style languages. These are how I think and work, these are, and
I'm going to assume (make an ass of you and me ;)) that these are what the
majority of HNers deal in.

------
kwhitefoot
Good to see that someone agrees with me for a change.

~~~
justinpombrio
Good to see that you agree with someone for a change.

