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 ...
}
}
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;
}
// ...
}
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();
The negation of that expression would be:
this.isActive() && this.hasEmail();
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 ...
}
}
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 ...
}
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
It's equivalent to:
const willDieSoon = !(this.isYoung || this.worksOut)
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:
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
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)
Other Boolean Algebra Laws
If you didn't know about this boolean algebra laws specifically, don't worry! You're probably applying others like:
Those mean that:
(weAreInChrome() || weAreInFirefox()) === (weAreInFirefox() || weAreInChrome())
(isRaining() && isCloudy()) === (isCloudy() && isRaining())
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! π π
Top comments (0)