Boolean parameters to API functions considered harmful (2011)
jlebar.com
jlebar.com
Alternate (better) solution to the author's problem: Use enums
document addObserver:observer forNotificationNamed:'load' isWeak:false.
xhr openURL:options url type:options type async:options sync negated[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.
Given that more time is spent reading than writing code, autocomplete is only a partial solution to this problem.
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.
The disease is far worse than the cure. I frequently (at least once a day) find myself wishing code was written in the smalltalk style but I've only found the smalltalk style inconvenient on a small number of occasions.
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 xhr.OpenUrl(url, async: true); - (void)doMysteriousThingsWithParameters:(int)x :(BOOL)y :(NSString*)z;
I've come across this recently when creating an HTML5 <canvas> context object to be called from within JavaScriptCore. This needs to expose JS-friendly methods like: - (void)strokeRect:(CGFloat)x :(CGFloat)y :(CGFloat)w :(CGFloat)h;
- (void)arc:(CGFloat)x :(CGFloat)y :(CGFloat)radius :(CGFloat)startAngle :(CGFloat)endAngle :(BOOL)antiClock;Ah, or do you mean the name is just empty? How does that work on the implementation side? I.e. how do you refer to the passed arguments?
I prefer python approach, where you can set "optionally named" and "kwarg only" parameters" on a per function basis.
For a description, see https://www.python.org/dev/peps/pep-3102/
For internal but not external APIs. Enums create tight coupling and are not extensible. Booleans and Enums should be avoided for external APIs.
if (someCondition) {
showFoo();
} else {
hideFoo();
}
Instead of: setFooVisible(someCondition);
The point being, creating lots of different methods for each variant reduces one's ability to use other abstraction tools, like variables, to control parameters. (someCondition? showFoo : hideFoo)();
You can also pass in arguments if they both accept the same parameters, eg. (someCondition? show : hide)(foo, duration); (someCondition ? foo.show : foo.hide)();
except the language does not auto-bind so you have to write (someCondition ? foo.show : foo.hide).call(foo);
or maybe (someCondition ? foo.show.bind(foo) : foo.hide.bind(foo))(); foo[someCondition? 'show' : 'hide']();
Although looking up functions using raw strings is less than elegant. I'd be tempted to abstract the pattern using a function, but at that point I'd be doing functional programming anyway and wouldn't be using methods ;)A bit odd given jQuery often provides both options e.g. show[0]/hide[1] and toggle(bool)[2].
[0] http://api.jquery.com/show/
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.)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.
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.
(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.)
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. /* 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. document.addObserver(observer, "load", Yes.isWeak);
or xhr.OpenUrl(url, options, No.sync)P.S. I don't use D
PS: I don't use D.
Flag!"async"
And then the possible values are Yes.async and No.async
This exploits D dispatch operator: 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!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.
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?
(async ? dispatch_async : dispatch_sync)(queue, ^{
/* stuff */
});
The reader does a double-take, but it's so damn pretty!https://existentialtype.wordpress.com/2011/03/15/boolean-bli...
Xhr.open(type, url, Xhr.ASYNC); function addObserver(observer, func, { weak = false } = {}) {
if (weak) {
// ...
}
}
addObserver(observer, 'load', { weak: true }); enableOutput(bool)
is pretty easy to understand. If the function name explains the parameter then its ok.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
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).
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.
TFA talks about passing false instead of true or vice versa.