It’s really hard to take an article like this seriously.
The opening paragraph:
This article deals with the use of bool in C++. Should we use it or not? That is the question we will try to answer here. However, this is more of an open discussion than a coding rule.
So we are having an “open discussion” in an article whose title is definitive, denying the very idea of a discussion. Gotcha.
Then, if you continue, the “discussion” is laughably simplistic and narrow. It trades Booleans for types with a design that is most generously described as questionable. The problem at the core is understanding the problem domain. If a function like shouldBuyHouse is taking parameters to describe all the variations, then trading Booleans for types isn’t going to solve much unless your domain is rudimentary, in which case Booleans are probably just fine.
It’s really a question on the use of booleans in function signatures, not in general.
If a function like shouldBuyHouse is taking parameters to describe all the variations, then trading Booleans for types isn’t going to solve much unless your domain is rudimentary, in which case Booleans are probably just fine.
It’s definitely better if calling a function like shouldByHouse requires identifiers named hasPool/noPool versus positional true/false params. That is, shouldBuyHouse(hasPool, badLights) is definitely better than shouldBuyHouse(true, false). Right? No?
I love C++ enum classes, but the drawback is they have no implicit conversions to anything…
Another way to look at the problem described here is that it’s a lack of parameter naming at the call site. Booleans are much clearer in shouldBuyHouse(hasPool: true, badLights: false). Right?
I agree 100% - the lack of named arguments in c++ is mostly why we resort to using enum classes to represent boolean values. Unfortunately this trick only works for booleans and not so much for strings or other types, which is why we often resort to the builder pattern or similar idioms to simulate named arguments.
Named parameters have appeal, but they tend to go hand-in-hand with optionality, which is a huge source of risk, IME far more costly than the benefits delivered by the feature. If you could have named parameters without the option of leaving any out, then I’d agree that’s a good solution, except that it still allows callers to call shouldBuyHouse(true, false) which is exactly the thing we’re trying to prevent, here, by construction. So I dunno. I wouldn’t put them in my language.
Parameter names are encouraged by the syntax and non-optional for the caller, and must be provided in definition order. There’s a syntax to say “caller doesn’t need to provide a name here” but the caller needs to provide the argument names in basic looking signatures like shouldBuyHouse(hasPool: bool, badLights: bool) -> bool.
Swift does have default parameters though, which are as bad or worse than optional parameters - but at least they need to be trailing, and order is guaranteed.
When it’s a function with two parameters backing an uncompelling example, then I argue it still mostly doesn’t matter, and if you want those identifiers could be globals or even macros, if you’re using C.
I’m not advocating for Booleans as arguments in general, I’m just asking for better writing. This is article is not worth anyone’s time.
If the two parameters you mention are both booleans, then for readability it definitely matters whether you use a bool or an enum. The tradeoff is some extra work for the enum, and it’s reasonable to argue whether (or not) the extra work is worthwhile.
One bonus the enum gives you is you can more easily replace it with a policy object.
Some conditions are essential, no doubt, but those can be contained in a small number of places so the rest of the code can read straight-line. Booleans propagate conditions outward, instead of helping contain them
This is similar to why I always used the named parameters feature of ruby methods if it takes more than a single argument. Just helps things stay readable.
x and y (the centre), radius, stroke width. There are many possible interpretations to imagine, of course (such as top left and bottom right of a rectangle, where it’s not necessarily a circle that’s actually drawn).
My point (please excuse that I didn’t try to make it properly before) was just that bool isn’t, in my opinion, any worse than other basic types by nature: I do see plenty of non-obvious function signatures where other types are involved.
Whether bool is abused most in practice, well, I wouldn’t argue against that being the case. I think it’s a bit of an easy scapegoat, however, and that well-designed interfaces are a product of lots of deep consideration and careful design, rather than a monkeys-and-hosepipe rule to avoid bool.
It gets to my nerve too when people drop the canned just use and IDE response. But this post is on the other extreme. Just check the function signature if in doubt?
The provided example with the switched variables is not valid because the code is objectively wrong. Why exactly does a person reading the code need to tell at all times that the parameters are right? Or course you assume the code does what is supposed. If it doesn’t why even bother any other line of code at all? What’s right and what’s wrong of everything is to be questioned?
If you are debugging, then you are already aware of such mistakes and will check such things carefully and manual because you know something wrong.
Making wrong code readable is not, should not and will never be a design goal of any language.
I file this under “Can be nice, but kind of depends due to C++ cumbersomeness.”
Adding a whole new enum class is a more code.
If you want people to be able to forward declare your class/etc, then you can’t have this enum class inside of your class, it has to be in a namespace. This is fine, but, more things in the namespace can be a con.
IDEs now (personally using vscode + clangd) often automatically insert the argument name when reading the code, so you can see it when reading the code.
BUT - often I’m using something like cs.chromium.org which doesn’t show those argument names…
This is basically meant to protect against human errors.
As such its applicability depends on whether this type of screw-up is likely for a particular team or a code base. In that sense this is a not too distant cousin of if (NULL != foo) inversion evangelized by some back in the 90s. It hedged against one particular type of typo at the expense of code readability. Some people needed it, most didn’t.
Same here - you tend to mix-up the order of function arguments? Sure, use enums. But don’t forget to wrap your int args in classes too, so not to mix them up too. Better yet though, try and pay some attention to what you are typing.
Programs are written primarily for humans to understand, and only secondarily for computers to execute. Protecting against human errors is the whole ball game.
I think this post misses the forest for the trees. Yes, you should avoid raw types because positional arguments (without an IDE pointing out the names) suck for at-a-glance reasoning.
You should’ve had a House or Listing type that you passed in, not this selective enumeration idea. There’s little benefit between this approach and bool apart from turning the error’s emergence from runtime to compile time.
Those types aren’t really re-usable, and this small picture of a problem seems like a strawman to beat up positional arguments, a better example could’ve been picked.
It’s really hard to take an article like this seriously.
The opening paragraph:
So we are having an “open discussion” in an article whose title is definitive, denying the very idea of a discussion. Gotcha.
Then, if you continue, the “discussion” is laughably simplistic and narrow. It trades Booleans for types with a design that is most generously described as questionable. The problem at the core is understanding the problem domain. If a function like
shouldBuyHouse
is taking parameters to describe all the variations, then trading Booleans for types isn’t going to solve much unless your domain is rudimentary, in which case Booleans are probably just fine.It’s really a question on the use of booleans in function signatures, not in general.
It’s definitely better if calling a function like
shouldByHouse
requires identifiers namedhasPool
/noPool
versus positionaltrue
/false
params. That is,shouldBuyHouse(hasPool, badLights)
is definitely better thanshouldBuyHouse(true, false)
. Right? No?It’s definitely better if the caller is passing a constant, as in your example.
It gets awkward otherwise:
I love C++ enum classes, but the drawback is they have no implicit conversions to anything…
Another way to look at the problem described here is that it’s a lack of parameter naming at the call site. Booleans are much clearer in
shouldBuyHouse(hasPool: true, badLights: false)
. Right?I agree 100% - the lack of named arguments in c++ is mostly why we resort to using enum classes to represent boolean values. Unfortunately this trick only works for booleans and not so much for strings or other types, which is why we often resort to the builder pattern or similar idioms to simulate named arguments.
Well, you probably wouldn’t express the mapping of buyerCanSwim to hasPool, or buyerIsBlind to whichLights, inline with the calling expression.
Named parameters have appeal, but they tend to go hand-in-hand with optionality, which is a huge source of risk, IME far more costly than the benefits delivered by the feature. If you could have named parameters without the option of leaving any out, then I’d agree that’s a good solution, except that it still allows callers to call
shouldBuyHouse(true, false)
which is exactly the thing we’re trying to prevent, here, by construction. So I dunno. I wouldn’t put them in my language.I think Swift’s approach is quite interesting: https://docs.swift.org/swift-book/LanguageGuide/Functions.html
Parameter names are encouraged by the syntax and non-optional for the caller, and must be provided in definition order. There’s a syntax to say “caller doesn’t need to provide a name here” but the caller needs to provide the argument names in basic looking signatures like
shouldBuyHouse(hasPool: bool, badLights: bool) -> bool
.Swift does have default parameters though, which are as bad or worse than optional parameters - but at least they need to be trailing, and order is guaranteed.
When it’s a function with two parameters backing an uncompelling example, then I argue it still mostly doesn’t matter, and if you want those identifiers could be globals or even macros, if you’re using C.
I’m not advocating for Booleans as arguments in general, I’m just asking for better writing. This is article is not worth anyone’s time.
If the two parameters you mention are both booleans, then for readability it definitely matters whether you use a bool or an enum. The tradeoff is some extra work for the enum, and it’s reasonable to argue whether (or not) the extra work is worthwhile.
One bonus the enum gives you is you can more easily replace it with a policy object.
Or in any language anywhere you possibly can. A Boolean requires a condition, and conditions are a thing to design away from
How do you do, like, any programming without conditions?
Some conditions are essential, no doubt, but those can be contained in a small number of places so the rest of the code can read straight-line. Booleans propagate conditions outward, instead of helping contain them
This is similar to why I always used the named parameters feature of ruby methods if it takes more than a single argument. Just helps things stay readable.
Is this necessarily only a problem with bools?
Bools are the most common culprit. For other types, it’s often possible to tell why they’re there.
I love IDE support for telling me what parameters are.
I said “often”, not “always”.
Is the point the centre or top left or something else?
Sorry, I meant to write
drawCircle(Point2, float)
. Circles have only three degrees of freedom. (Why were there fourfloat
s in your original example)?Anyway, you’re right, this isn’t the best example. I’d assume it’s the center, but I can see a weird library having it as the top-left corner.
x and y (the centre), radius, stroke width. There are many possible interpretations to imagine, of course (such as top left and bottom right of a rectangle, where it’s not necessarily a circle that’s actually drawn).
My point (please excuse that I didn’t try to make it properly before) was just that bool isn’t, in my opinion, any worse than other basic types by nature: I do see plenty of non-obvious function signatures where other types are involved.
Whether bool is abused most in practice, well, I wouldn’t argue against that being the case. I think it’s a bit of an easy scapegoat, however, and that well-designed interfaces are a product of lots of deep consideration and careful design, rather than a monkeys-and-hosepipe rule to avoid bool.
It gets to my nerve too when people drop the canned just use and IDE response. But this post is on the other extreme. Just check the function signature if in doubt?
The provided example with the switched variables is not valid because the code is objectively wrong. Why exactly does a person reading the code need to tell at all times that the parameters are right? Or course you assume the code does what is supposed. If it doesn’t why even bother any other line of code at all? What’s right and what’s wrong of everything is to be questioned? If you are debugging, then you are already aware of such mistakes and will check such things carefully and manual because you know something wrong.
Making wrong code readable is not, should not and will never be a design goal of any language.
Where exactly does the article say that it’s a good idea?
I file this under “Can be nice, but kind of depends due to C++ cumbersomeness.”
enum class
inside of your class, it has to be in a namespace. This is fine, but, more things in the namespace can be a con.:shrug:
This is basically meant to protect against human errors.
As such its applicability depends on whether this type of screw-up is likely for a particular team or a code base. In that sense this is a not too distant cousin of
if (NULL != foo)
inversion evangelized by some back in the 90s. It hedged against one particular type of typo at the expense of code readability. Some people needed it, most didn’t.Same here - you tend to mix-up the order of function arguments? Sure, use enums. But don’t forget to wrap your int args in classes too, so not to mix them up too. Better yet though, try and pay some attention to what you are typing.
Programs are written primarily for humans to understand, and only secondarily for computers to execute. Protecting against human errors is the whole ball game.
Between
and
there is an option which is — I hope — unambiguously superior.
I think this post misses the forest for the trees. Yes, you should avoid raw types because positional arguments (without an IDE pointing out the names) suck for at-a-glance reasoning.
You should’ve had a
House
orListing
type that you passed in, not this selective enumeration idea. There’s little benefit between this approach andbool
apart from turning the error’s emergence from runtime to compile time.Those types aren’t really re-usable, and this small picture of a problem seems like a strawman to beat up positional arguments, a better example could’ve been picked.