DEV Community

Cover image for CS Basics: How to refactor a boolean expression
JosΓ© M. Gilgado
JosΓ© M. Gilgado

Posted on • Originally published at josemdev.com

CS Basics: How to refactor a boolean expression

Intro to series

Computer Science sounds so boring, so academic, that sometimes it's hard to know if there's even any useful knowledge for our jobs building actual software.

Of course, I'm half joking. Computer Science is very important to understand what's going on in a computer but I understand it doesn't sound as attractive as learning a specific technology that's in demand in the industry.

With this series of posts, I'll try to extract from those long boring books a few things we can start applying right away and I hope you'll find useful.

I'll always start with the immediate practical part and then I'll explain a bit more about the underlying reasons and terminology.

Let's start today with some boolean expressions.

Why is it useful to refactor a boolean expression?

It has happened to me many times that, when refactoring a condition, I had to change it to create a method that makes sense in the class. Let's show an simple example.

Imagine we have the following class (in Javascript, but can be applied to almost any language):

class Person {
  constructor(name, active, email = null) {
    this.name = name;
    this.active = active;
    this.email = email;
  }

  hasEmail() {
    return this.email !== null;
  }

  isActive() {
    return this.active;
  }

  sendEmail() {
    if (!this.isActive() || !this.hasEmail()) {
      return false;
    }
    // ... send email ...
  }
}
Enter fullscreen mode Exit fullscreen mode

At some point we realize that it might be easier to have an internal method that can tell us if the user can receive emails. According to the code that'd mean that it's an active user and has an email set.

We could do this:

  canReceiveEmail() {
    return ; // What do we return here? πŸ€”
  }

  sendEmail() {
    if (!this.canReceiveEmail()) {
      return false;
    }
    // ...
  }
Enter fullscreen mode Exit fullscreen mode

As you can see the sendEmail method is more readable now, but what should we put in the canReceiveEmail() method?

I'll let you think for a second...πŸ™‚

πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”

πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€¨πŸΆπŸ€¨πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”

πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”πŸ€”

Did you figure it out?

What we need is the opposite value of the condition we had:

!this.isActive() || !this.hasEmail();
Enter fullscreen mode Exit fullscreen mode

The negation of that expression would be:

this.isActive() && this.hasEmail();
Enter fullscreen mode Exit fullscreen mode

I think intuitively we can understand that if what we were looking for is that:

  • The user is not active OR doesn't have email;

The opposite would be that:

  • The user is active AND has email.

So the final class would look like this:

class Person {
  constructor(name, active, email = null) {
    this.name = name;
    this.active = active;
    this.email = email;
  }

  hasEmail() {
    return this.email !== null;
  }

  isActive() {
    return this.active;
  }

  canReceiveEmail() {
    return this.isActive() && this.hasEmail();
  }

  sendEmail() {
    if (!this.canReceiveEmail()) { // Notice negation of the method with !
      return false;
    }
    // ... send email ...
  }
}
Enter fullscreen mode Exit fullscreen mode

We could also have created the negative version cannotReceiveEmail but I prefer to use "positive" method names since they tend to be more useful in other places of the code. Nevertheless, If we had done that the result would have been:

cannotReceiveEmail() {
  return !(this.isActive() && this.hasEmail());
}

sendEmail() {
  if (this.cannotReceiveEmail()) {
    return false;
  }
  // ... send email ...
}
Enter fullscreen mode Exit fullscreen mode

Computer Science behind solution

What you just saw wasn't discovered recently, the mathematician who formulated this solution, Augustus De Morgan, died in 1871.

This rule, along with another one we'll see in a bit, are called "De Morgan's Laws". They're part of a bigger field in Mathematics and studied in any computer science course, called Boolean Algebra.

The other rule is as follows. If we have this expression:

const willDieSoon = !this.isYoung && !this.worksOut
Enter fullscreen mode Exit fullscreen mode

It's equivalent to:

const willDieSoon = !(this.isYoung || this.worksOut)
Enter fullscreen mode Exit fullscreen mode

If you didn't get this one, think about what would happen in all the potential conditions:

Consider isYoung is true/false and worksOut is true/false. What would be the result of the expression? Wouldn't that be the same that in the first case?

To truly understand this, it's often useful to create a small table of possibilities. The last column represents: willDieSoon.

isYoung worksOut isYoung OR worksOut !(isYoung OR worksOut)
false false false true
true false true false
false true true false
true true true false

The same table can be applied to the first expression:

isYoung worksOut !isYoung AND !worksOut
false false true
true false false
false true false
true true false

The Morgan's Laws

So the two rules, written in a formal manner, would be:

De Morgan's Laws

This image is from Wikipedia since I'm not sure how to add math symbols in dev.to 😞.

So in Javascript, we could say this is:

!(P || Q) == !P && !Q

!(P && Q) == !P || !Q
Enter fullscreen mode Exit fullscreen mode

We've used the second one to refactor our code.

We started with the !P || !Q and ended with !(P && Q). Let's review it now:

// we started with this condition to check before sending the email:
!this.isActive() || !this.hasEmail()

// Then, we moved the logic to canReceiveEmail()
this.isActive() && this.hasEmail()

// And used the negation of that:
!this.isActive() || !this.hasEmail() == !(this.canReceiveEmail())

// Which is:
!this.isActive() || !this.hasEmail() == !(this.isActive() && this.hasEmail());

// So:
!P || !Q == !(P && B)
Enter fullscreen mode Exit fullscreen mode

Other Boolean Algebra Laws

If you didn't know about this boolean algebra laws specifically, don't worry! You're probably applying others like:

Commutativity

Those mean that:

(weAreInChrome() || weAreInFirefox()) === (weAreInFirefox() || weAreInChrome())

(isRaining() && isCloudy()) === (isCloudy() && isRaining())
Enter fullscreen mode Exit fullscreen mode

So, intuitively perhaps, you know that the boolean conditions with two values can be switched in order and the result is the same (πŸ€“πŸ—―οΈ that's commutativity!).

There are many others boolean laws that are useful so I might do a second part of boolean logic later in the series with the ones that are not so intuitive.

Did you find this one useful? Let me know on Twitter please! πŸ‘‹ πŸ™‚

References

Top comments (0)