I was recently working on a javascript project. My intent was to add a logging service to our code, in addition to those already existing. This brought an interesting discussing around falsy checks with a distinguished colleague.

The code, as I found it, was:

if (process.env.LOGSERVICE1_KEY != null) {
	loggerService.addLogger(new LogService1(process.env.LOGSERVICE1_KEY));
}

This works for the wrong reasons. I assume that the intent there is the following:

  • if the environment variable LOGSERVICE1_KEY is not null
  • then add logger with key contained in LOGSERVICE1_KEY

This works only because null evaluates to false, and process.env.LOGSERVICE1_KEY if the string is empty, or contains a “falsy” value: "", false, 0, null, undefined. This is a problem, because what’s happening is actually different from how it reads. It corresponds more to:

if (process.env.LOGSERVICE1_KEY != false)

Also corresponding to

if (process.env.LOGSERVICE1_KEY)

The hidden thing at work there is that if environment variable LOGSERVICE1_KEY is undefined then we’ll not branch.

One of the most important quality of code is that it conveys intent12.

  • if (process.env.LOGSERVICE1_KEY != null) conveys the false sense that we’re checking for null. It requires some mental exercise to realize that we are actually checking for falsiness. Someone could tell themselves “Oh wait, but this won’t work if it’s actually undefined”3 on a first read. In all three notations, this is the worst, because it’s misguiding the maintainer regarding the actual intent.
  • if (process.env.LOGSERVICE1_KEY != false) is slightly better, because it conveys that we’re doing a boolean comparison. But a first read could have a new maintainer expect to find a boolean in process.env.LOGSERVICE1_KEY, which is not the case. For that reason, it is also confusing.
  • if (process.env.LOGSERVICE1_KEY) is directly using truthiness, which is probably the best, but is also possibly misguiding as per what values to expect in process.env.LOGSERVICE1_KEY. .

In addition, this doesn’t tell us straight forward how to deactivate logging. Do we need to leave the value empty? To put a magic value like “deactivated”, “off”, or “null”? A person configuring the service without looking at the code might even not realize that the service can be shut-down.

The main issue behind using truthiness in such a context, is that it makes a given variable convey two meanings. Is the service active, and what the service key is. As such, it is breaking the Single Responsibility Principle.

The actual intent in the example is:

  • it LOGSERVICE1 is active
  • then add logger with key contained in LOGSERVICE1_KEY

SRP: A better way to convey intent in this case is to use two variables:

if (process.env.LOGSERVICE1_ACTIVE) {
	loggerService.addLogger(new LogService1(process.env.LOGSERVICE1_KEY));
}

This way, each variable has a single, clear responsibility, and we clear out the confusion. Single Responsibility Principle at work

But errors can still be made, for example, someone could set the environment variable LOGSERVICE1_ACTIVE to false, which… is truthy! Javascript sucks.

Your true salvation is to work without it, something such as:

const isActive = (val) => /^\s*(yes|true|on|1|active)\s*$/i.test(val);

This way, code can be refactored thusly:

if (isActive(process.env.LOGSERVICE1_ACTIVE)) {
	loggerService.addLogger(new LogService1(process.env.LOGSERVICE1_KEY));
}

And the corresponding configuration would look like:

LOGSERVICE1_ACTIVE=true
LOGSERVICE1_KEY=3420948205789177471402420374587

This gives no ambiguity as per what this does, and how it works. Someone reading the code knows that this will go in if LOGSERVICE1_ACTIVE contains a value specifying that the service is, indeed, active. Someone configuring the application will assume correctly that setting LOGSERVICE1_ACTIVE to false will indeed deactivate the service.

Truthiness is too complicated to be safe

This example seems trivial. One of the main issues is that truthiness is not bijective:

"0"     == false    // > true
""      == false    // > true
"0"     == ""       // > false...

!false              // > true
!null               // > true
null    == false    // > false...

!!undefined == undefined // > false...

It reads wrong, and is very confusing:

"false" == false    // > false
"true"  == true     // > false
"false" == true     // > false

""      == false    // > true
"null"  == null     // > false
0       == false    // > true

The fact that, even when knowing about these, you need to think to read a conditional is a good indicator that this is an impediment to clarity.

As such, truthiness is too complicated to be safe. Even if you understand it, others may not, and it’s adding complexity for no value. Therefor, it should not be used.

Notes

  1. Martin, Uncle Bob, The - Clean Code. 

  2. Beck, Kent - (outlined by M. Fowler) 

  3. And indeed that was my first reaction when reading that.