DEV Community

Cover image for Code Smell 24 - Boolean Coercions
Maxi Contieri
Maxi Contieri

Posted on • Updated on • Originally published at maximilianocontieri.com

Code Smell 24 - Boolean Coercions

Booleans should be just True and false

TL;DR: Don't do magic castings to boolean. You will regret on a friday night.

Problems

  • Hiding Errors

  • Accidental complexity coupled to one particular language.

  • Readability

  • Difficulty to hop among languages.

  • IFs

Solutions

  1. Be explicit.

  2. Work with booleans for boolean conditions. Not integers, not nulls, not strings, not lists. Just booleans.

  3. Fail fast

Sample Code

Wrong

virus = ['MERS', 'SARS']
vaccines = []

if vaccines:
    print ("let's get vaccinated")
else:
    print ("We have no vaccines yet. Keep researching")

if virus:
    print ("There are some virus around. Take extra care")
else:
    print ("We are free to get out. Not masks are necessary")

#equivalent     

if not vaccines:
    print ("We have no vaccines yet. Keep researching")
else:
    print ("let's get vaccinated")

if not virus:
    print ("We are free to get out. Not masks are necessary")
else:
    print ("There are some virus around. Take extra care")
Enter fullscreen mode Exit fullscreen mode

Right

if len(vaccines) == 0:
    print ("We have no vaccines yet. Keep researching")
else:
    print ("Let's get vaccinated")


if len(virus) == 0:
    print ("We are free to get out. Not masks are necessary")
else:
    print ("There are some virus around. Take extra care")
Enter fullscreen mode Exit fullscreen mode

Detection

This is a language feature. Some strict languages show warnings with this magic wizardry.

Tags

  • Coercions

  • Primitive

Conclusion

Some languages encourage doing some magic abbreviations and automatic castings. This is a source of errors and a Premature Optimization warning.

We should always be as explicit as possible.

Relations

More Info


It is not the language that makes programs appear simple. It is the programmer that make the language appear simple!

Robert Martin


This article is part of the CodeSmell Series.

Last update: 2021/07/07

Top comments (30)

Collapse
 
yoursunny profile image
Junxiao Shi

Go doesn't allow conversions from and to boolean type altogether.

Meanwhile in C, it's normal to test an integer as boolean, but I'd rather write if (i == 0) although I know the compile will optimize the comparison away.

Collapse
 
mcsee profile image
Maxi Contieri

Good fot both languages

I prefer GoLang aproach.

Booleans are Booleans

Collapse
 
mcsee profile image
Maxi Contieri

Exactly ! Let the compiler do it for you if it is safe.
Go is much stricter than C. I think Go lacks several important features like Exceptions or Full Closures. Once it catches up with older languages it would be an excellent option

Collapse
 
eljayadobe profile image
Eljay-Adobe

Shouldn't that be if (i != 0) ...?

Collapse
 
mcsee profile image
Maxi Contieri

what is 'i' ?

what real world entity does it represent ? the letter 'i' ? Why would you compare a letter with a number ?

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Considering the example talks about C, i is probably a counter variable for a numeric loop.

Thread Thread
 
mcsee profile image
Maxi Contieri

i think we should name it 'counter' or 'index'
We are doing software, not math

Thread Thread
 
yoursunny profile image
Junxiao Shi

My real code uses the variable name res.
github.com/usnistgov/ndn-dpdk/blob...

int res = rte_hash_add_key_with_hash_data(pcct->tokenHt, &token, hash, entry);
if (likely(res == 0)) {
  break;
}
Enter fullscreen mode Exit fullscreen mode

That likely is not "premature optimization". This code is for a high speed router that processes millions of packets per second.

Thread Thread
 
mcsee profile image
Maxi Contieri

I don't know what 'res' stands for.

But if you performed a real benchmark on the code and found out it really needed to be optimized it does not classify as 'premature'.
I'm ok with that since in this very special case speed is more important than readability

Thread Thread
 
yoursunny profile image
Junxiao Shi

res means result. It is common in C code.

Thread Thread
 
mcsee profile image
Maxi Contieri • Edited

ok. that's why i don't like low level languages. They are more cryptic.
I try to specialize on higher level languages. Most code smells apply to them.

I suggest not to use 'res' 'i' ir 'result' as variable names since they have no business meaning.

more examples here:

maximilianocontieri.com/what-exact...

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

certain things like res and i are so common that I'd consider them expressive variable names, but there's not many. For res, I really like the pascal feature of assigning to the subroutines name, because presumably that's already a descriptive name for the result value.

re not liking low level languages, it seems to me like high- and low-level languages have completely different appeals (I happen to like both of them), so one might like one but not the other. Thinking in systems and poking at bits are as much different areas as back-end and front-end.

Thread Thread
 
mcsee profile image
Maxi Contieri

you are absolutely right. They are different tools for different purposes

Collapse
 
yoursunny profile image
Junxiao Shi

Depending on whether you want true or false. I usually test for error condition and return early.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I hate languages that treat "empty" values as falsey. It's dumb and leads to unexpected errors. An empty array is still an array, an empty string is still a string, zero is still a number. Neither of them has anything "false" about them and languages that treat them as such are just poorly designed (low-level languages like C being the exception, for obvious reasons)

Collapse
 
netch80 profile image
Valentin Nechayev

I hate languages that treat "empty" values as falsey. It's dumb and leads to unexpected errors.

For what do you check them for booleanness?

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

What do you mean? Numbers aren't booleans, neither are arrays. What is there to check?

Thread Thread
 
netch80 profile image
Valentin Nechayev

So is your vote to avoid any implicit check of them as booleans?

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Depends on the language and how it handles typing. In a language like JavaScript, any value that is not explicitly falsey or void should be considered truthy, kind of how it is in Lua. Empty data structures are empty, but that doesn't mean they don't exist.

Thread Thread
 
netch80 profile image
Valentin Nechayev

So you still provide some border between values which are implicitly treated as false, and those as true, but your border is so only "void" and "false" are false. What about "undef", "null", etc., whatever they are named in different languages?

Empty data structures are empty, but that doesn't mean they don't exist.

But they are empty. Just no data. If this is, for example, a list, you have no values in it => nothing to process.
Why don't you consider boolean checking here as a handy manner to check its state?

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I guess what it comes down to is that an empty array holds no data, while a nil-value itself represents no data. When a value appears in an if statement, only the value itself should be considered, not its actual contents. Otherwise, what about an array like [[], nil, {key: undefined}]? This doesn't hold any value either, as all of its values are, themselves, "empty". But there's still some structure that holds information.

There's also the question of identity; does [] == [] return true? In JavaScript it doesn't, so the language clearly considers two "empty" arrays two distinct values, so it makes sense to me that they'd be considered something rather than nothing in an if statement as well.

But most importantly, I think both if (nil) { unreachable } and if (0) { unreachable } continue, in some way, the idea from languages like C that a null-pointer, representing a pointer to nothing, when put in an if statement, evaluate to false so the if statement doubles as an if exists construct; but in modern languages, nil-values and numeric zero values are two distinct things, so it is possible to continue to treat nil-values as falsey without continuing to do so for numeric zero values too.

Another example would be linked lists: when your variable stores a pointer to the head of the list, and the last element points to null as its "next" pointer, then it makes sense that an empty linked list is a null-poiner, and indistinguishable from a non-value other than by its type. In a functional language with a proper type system, sure, but not in C, that would have been a really bad idea. But again, in more modern languages, we can more easily distinguish an empty collection from no collection at all, so it makes less sense to treat the former as a falsey value.


Sorry for the slightly disorganised thoughts, but I barely just had my morning coffee and can't really be bothered to write a full article on a topic that, at the end of the day, still comes down to what seems more reasonable on a subjective level.

Thread Thread
 
netch80 profile image
Valentin Nechayev

Sorry for the slightly disorganised thoughts

Really nothing to ask sorry, because all this raises tons of basic problems. You slightly touched here, for example, meaning of "null": is it "asserted nothing" or "unknown"? In Javascript, it is first. In SQL and floating (as NaN), it is second. But in SQL it is, in practice, typically used as first, and this discrepancy causes tons of subtle bugs. Your comparison of [] == [] is from the same terrain: comparison by sense and comparison by reference - they shall be clearly different. Lots of other similar cases.

But what I try to predicate here is that, despite these problems are eternal (and, definitely, any who studies IT shall eventually get acquainted with them, the earlier the better), there could be come shortcuts, convenient for most typical cases. And, in this sense, all examples could be reconsidered. I'm not familiar in practice with Javascript (only a tiny chunks of glue anything), my fields of experience are Python (closest to the discussion origin), C/C++, Java, slightly - SQL. And, from POV closer to all them:

Otherwise, what about an array like [[], nil, {key: undefined}]? This doesn't hold any value either, as all of its values are, themselves, "empty".

As said above, yes, it holds a value. (Hmm: with Ukrainian/Russian rules I'd have to write "No, it holds a value".) If you have difference between undefined and nil, then undefined is "don't know" and nil is "definitely nothing". The latter is already a piece of data. Well, your example really could be rewritten as e.g. [undefined, undefined] - it's closer to what to consider.

Python currently converts any non-empty list (tuple) to true. If the rules changed to "if any list element is true, the whole list is true; otherwise, false", it could be accepted. Instead, they used the simplest approach.

does [] == [] return true? In JavaScript it doesn't

Well, this is heavy Javascript insanity, along with all other its peculiar like weird result of []+{}. There shall be explicit univocative manners to compare contents and to compare references. In Python, for example, first is "==" (overriddable if needed) and second is "is" (not overriddable). That's clear. In Javascript, both "==" and "===" are specific. It's hard and complely unhandy.

then it makes sense that an empty linked list is a null-poiner, and indistinguishable from a non-value other than by its type.

This is how LISP works: () is the same as nil and treated as false. Well, LISP is the oldest and definitely not strongly typed functional language :)

and can't really be bothered to write a full article on a topic that

For just me, no need. Well, this is public discussion and somebody other could get interested, but, in this case, 1) we have already provided enough examples to start delving into, 2) this should be rather a separate article than just a comment, and properly prepared.
As already said I understand the whole (I hope;)) complexity of the issue. But people still try to make handy cases easier, and provide a policy how boolean check is performed on different data types. No variant is ideal, but some are definitely better than others, some are really clueless. We need to avoid all clueless rules and keep ones that looks generally not bad:)

Thread Thread
 
mcsee profile image
Maxi Contieri

The whole problem here is null

So, avoiding "the billion dollar mistake", we don't need all of the above stuff.

Sounds easy. It is easy

Thread Thread
 
netch80 profile image
Valentin Nechayev

Sounds easy. It is easy

"For every complex problem there is an answer that is clear, simple, and wrong." (c)
This is exactly the case you show here.

Thread Thread
 
mcsee profile image
Maxi Contieri

Ok. I respect your opinion

Thread Thread
 
netch80 profile image
Valentin Nechayev

I respect your opinion

Thanks:))
I'd add that Hoare is too self-blaming. At the moment he devised NULL, it was good solution. As always, remedy renders into poison only in excessive amounts. What we really needed is strict non-NULL guarantee at compiler level - and, well, this is what is added to much part of newer languages and tools; and, where we are sure any NULL is not allowed, this shall be marked, to allow checking. For God's sake, developer tools are now powerful enough.
The more problem I've faced once is that the whole set of "undefined" and "null" is not enough in some cases. This was a monitoring tool where we had to invent multiple cases as "sensor is absent", "sensor is unreachable" and "sensor is failing". This doesn't fit into traditional approaches with a single special value, but a language which allows algebraic types nicely fit here. Over recent ones, nearly any do (with different efficiency).

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I really prefer how this would work in ruby:

if vaccines.empty?
   puts "We have no vaccines yet. Keep researching"
else
   puts "Let's get vaccinated"
Enter fullscreen mode Exit fullscreen mode

It's closer to human thinking.

Collapse
 
mcsee profile image
Maxi Contieri

i agree.

empty is much better than strlen()=0

Collapse
 
alainvanhout profile image
Alain Van Hout

I'm a bit confused here. You say to not use coercion, but your 'right' example treats an array as a boolean.

Collapse
 
mikesamuel profile image
Mike Samuel

dev.to/mikesamuel/boolean-coercion... touches on this topic as well.