A piece of advice I read somewhere early in my career was "a boolean should almost never be an argument to a function". I didn't understand what the problem was at the time, but then years later I started at a company with a large Lua code-base (mostly written by one-two developers) and there were many lines of code that looked like this:
serialize(someObject, true, false, nil, true)
What does those extra arguments do? Who knows, it's impossible without looking at the function definition.Basically, what had happened was that the developer had written a function ("serialize()", in this example) and then later discovered that they wanted slightly different behaviour in some cases (maybe pretty printed or something). Since Lua allows you to change arity of a function without changing call-sites (missing arguments are just nil), they had just added a flag as an argument. And then another flag. And then another.
I now believe very strongly that you should virtually never have a boolean as an argument to a function. There are exceptions, but not many.
But this isn't really a boolean problem - even in your example there is another mistery argument: nil
And you can get the same problem with any argument type. What do the arguments in
mean?In general, you're going to need some kind of way to communicate the purpose - named parameters, IDE autocomplete, whatever - and once you have that then booleans are not worse than any other type.
You're correct in principle, but I'm saying that "in practice", boolean arguments are usually feature flag that changes the behavior of the function in some way instead of being some pure value. And that can be really problematic, not least for testing where you now aren't testing a single function, you're testing a combinatorial explosions worth of functions with different feature flags.
Basically, if you have a function takes a boolean in your API, just have two functions instead with descriptive names.
> Basically, if you have a function takes a boolean in your API, just have two functions instead with descriptive names.
Yeah right like I’m going to expand this function that takes 10 booleans into 1024 functions. I’m sticking with it. /s
If your function has a McCabe complexity higher than 1024, then boolean arguments are the least of your problems...
Not really.
Tons of well-written functions have many more potential code paths than that. And they're easy to reason about because the parameters don't interact much.
Just think of plotting libraries with a ton of optional parameters for showing/hiding axes, ticks, labels, gridlines, legend, etc.
Yes but this is about the difference between:
and: The latter is how you should use such a function if you can't change it (and if your language allows it).If this was my function I would probably make the parameters atrributes of an TurboEncabulator class and add some setter methods that can be chained, e.g. Rust-style:
Did you mean to reply to a different comment?
I absolutely agree named arguments are the way to go. But my comment wasn't in the thread about that.
(follow-up) BTW thank you for introducing me to turbo encabulators -- I did not know about them and they seem exceptionally useful! TIL...
https://en.wikipedia.org/wiki/Turbo_encabulator
Hopefully you could refactor it automatically into 1024 functions and then find out that 1009 of them are never called in the project, so you can remove them.
I think you might have missed the “/s”
True, but I think its worth noting that inferring what a parameter could be is much easier if its something other than a boolean.
You could of course store the boolean in a variable and have the variable name speak for its meaning but at that point might as well just use an enum and do it proper.
For things like strings you either have a variable name - ideally a well describing one - or a string literal which still contains much more information than simply a true or false.
If you language doesn't support named arguments, you can always name the value, with the usual mechanism:
Well, that just means the function might be named wrong?
Or, much better, you use named parameters, if your language supports it: Or you could make it part of object by declaring a method that could be used like this:Named arguments are a solution to precisely this issue. With optional arguments with default value, you get to do precisely what was being done in your Lua code but with self documenting code.
I personally believe very strongly that people shouldn’t use programming languages lacking basic functionalities.
Or not use them without tooling?
I believe IDE's had the feature of showing me the function header with a mouse hover 20+ years ago.
It amazes me the contortions that Golang devs (me included) go through to get something approaching the keyword arguments from python.
It's honestly what I miss the most about python: keyword args, keyword-only args, positional-only args.
What I don't miss is the unholy abomination of *kwargs...
You can also document the argument name inline for languages with block comments but no named args.
You can but it’s not the same because the names of named arguments are also present at the call site.
Named arguments don't stop the deeper problem, which is that N booleans have 2^N possible states. As N increases it's rare for all those combinations to be valid. Just figuring out the truth table might be challenging enough, then there's the question of whether the caller or callee is responsible for enforcing it. And either way you have to document and test it.
Enums are better because you can carve out precisely the state space you want and no more.
That's not a problem per se. It may very well be that you're configuring the behavior of something with a bunch of totally independent on/off switches. Replacing n booleans with an enum with 2^n values is just as wrong as replacing a 5-valued enum with 3 booleans that cannot be validly set independently.
If you use keyword arguments then something like that doesn't look too bad:
serialize(someObject, prettyPrint:true)
NB I have no idea whether Lua has keyword arguments but if your language does then that would seem to address your particular issue?
Lua doesn't directly support keyword arguments, but you can simulate it using tables:
And indeed that is a big improvement (and commonly done), but it doesn't solve all problems. Say you have X flags, then there's 2^X different configurations you have to check and test and so forth. In reality, all 2^X configurations will not be used, only a tiny fraction will be. In addition, some configurations will simply not be legal (i.e. if flag A is true, then flag B must be as well), and then you have a "make illegal states unrepresentable" situation.If the tiny fraction is small enough, just write different functions for it ("serialize()" and "prettyPrint()"). If it's not feasible to do it, have a good long think about the API design and if you can refactor it nicely. If the number of combinations is enormous, something like the "builder pattern" is probably a good idea.
It's a hard problem to solve, because there's all sorts of programming principles in tension here ("don't repeat yourself", "make illegal states unrepresentable", "feature flags are bad") and in your way of solving a practical problem. It's interesting to study how popular libraries do this. libcurl is a good example, which has a GAZILLION options for how to do a request, and you do it "statefully" by setting options [1]. libcairo for drawing vector graphics is another interesting example, where you really do have a combinatorial explosion of different shapes, strokes, caps, paths and fills [2]. They also do it statefully.
[1]: https://curl.se/libcurl/c/curl_easy_setopt.html
[2]: https://cairographics.org/manual/cairo-cairo-t.html
It's a failing of many type systems of older languages (except Pascal).
The best way in many languages for flags is using unsigned integers that are botwise-ORed together.
In pseudocode:
Whatever language you are using, it probably has some namespaced way to define flags as `(1 << 0)` and `(1 << 1)` etc.If you really need all of that I think I'd go with a separate object holding all of the options:
options = new SerializeOptions();
options.PrettyPrint = true;
options.Flag2 = "red"
options.Flag3 = 27;
serialize(someObject, options)
So 1 line of C/C++ becomes 5 lines of Java/C#? That sounds about right! :-) Though I'm sure we can get to 30 if we squeeze in an abstract factory or two!
You can do the above in C#, I haven't written Java in a decade so can't comment on that. I don't really understand your argument though - the options approach is extremely readable. You can also do the options approach in C or C++. The amount of stuff that you can slap into one line is an interesting benchmark to use for languages.
It's always crazy to see languages like C being able to beat high-level languages at some ergonomics (which is usually their #1 point of pride) just because C has bitfields and they often don't.
> The best way in many languages for flags is using unsigned integers that are botwise-ORed together.
Why is that the "best" way?
> Why is that the "best" way?
"Best way" is often contextual and subjective. In this context (boolean flags to a function), this way is short, readable and scoped, even in C which doesn't even have scoped namespaces.
Maybe there are better ways, and maybe you have a different "best way", but then someone can legitimately ask you about your "best way": `Why is that the "best" way?`
The only objective truth that one can say about a particular way to do something is "This is not the worst way".
It's simple, efficient, and saves space in memory. While not as big a deal these days where most systems have plentiful RAM, it's still useful on things like embedded devices.
Why waste a whole byte on a bool that has one bit of data, when you can pack the equivalent of eight bools into the same space as an uint8_t for free?
Sure, that works when trying to conserve memory to the degree that a few bytes matter, but the downside is that it's more complex, less obvious.
I've done exactly what you propose on different projects but I would never call it the "best" method, merely one that conserves memory but with typical trade-offs like all solutions.
I'm surprised nobody has suggested this yet. Just use a different name for the function. In your example, the new function should be prettyPrint(). No booleans required. No extra structures required.
Something I really love in Elixir is that functions can be named identically and are considered different with different arity.
https://elixirschool.com/en/lessons/basics/functions#functio...
I'm not sure I understand how this is different from function overloading
I think the answer to this (specific to lua) is passing a table as an argument that gets unpacked.
I don't know if this is where you read it, but this advice is also given in Clean Code.
I don’t remember exactly where I read this, but I think it was some internet forum of some kind. It makes sense that whoever wrote it got it from there. Never read it myself.
> I now believe very strongly that you should virtually never have a boolean as an argument to a function. There are exceptions, but not many.
Really? That sounds unjustified outside of some specific context. As a general rule I just can't see it.
I don't see whats fundamentally wrong with it. Whats the alternative? Multiple static functions with different names corresponding to the flags and code duplication, plus switch statements to select the right function?
Or maybe you're making some other point?
I die a little inside every time I write:
(So yes, but it goes beyond booleans. All optional parameters should be named parameters.)