DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info

JavaScript Refactoring — Conditionals

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

We can clean up our JavaScript code so that we can work with them more easily.

In this article, we’ll look at some refactoring ideas that are relevant for cleaning up JavaScript conditionals.

Decompose Conditional

We can break up long conditional expressions into smaller conditional expressions that are named.

For instance, instead of writing:

let ieIEMac = navigator.userAgent.toLowerCase().includes("mac") && navigator.userAgent.toLowerCase().includes("ie")
Enter fullscreen mode Exit fullscreen mode

We write:

let userAgent = navigator.userAgent.toLowerCase();
let isMac = userAgent.includes("mac");
let isIE = userAgent.toLowerCase().includes("ie");
let isMacIE = isMac && isIE;
Enter fullscreen mode Exit fullscreen mode

We broke the conditional expressions by assigning smaller expressions into their own variables and then combining them to make everything easier to read.

Consolidate Conditional Expression

If we have multiple short conditional expressions assigned to their own variables, then we can combine them into one.

For example, instead of writing:

const x = 5;
const bigEnough = x > 5;
const smallEnough = x < 6;
const inRange = bigEnough && smallEnough;
Enter fullscreen mode Exit fullscreen mode

We write:

const x = 5;
const inRange = x > 5 && x < 6;
Enter fullscreen mode Exit fullscreen mode

Since the expressions are so short, even combining them doesn’t make the much longer, so we can do that.

Consolidate Duplicate Conditional Fragments

If we have duplicate expressions or statements in a conditional block, then we can move them out.

For instance, instead of writing:

if (price > 100) {
  //...
  complete();
} else {
  //...
  complete();
}
Enter fullscreen mode Exit fullscreen mode

We write:

if (price > 100) {
  //...
} else {
  //...
}
complete();
Enter fullscreen mode Exit fullscreen mode

This way, we don’t have to do repeatedly call the complete function unnecessarily.

Remove Control Flag

If we have used a control flag for a loop, then we often have loops that look like:

let done = false;
while (!done) {
  if (condition) {
    done = true;
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

In the code above, done is the control flag and we set done to true so that we stop the while loop when the condition is true .

Instead, we can use break to stop the loop as follows:

let done = false;
while (!done) {
  if (condition) {
    break;
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

Replace Nested Conditional with Guard Clauses

Nested conditional statements are very hard to read, so instead of using them, we can use guard clauses instead.

For instance, instead of having the following nested conditional statements:

const fn = () => {
  if (foo) {
    if (bar) {
      if (baz) {
        //...
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We write:

const fn = () => {
  if (!foo) {
    return;
  }

  if (!bar) {
    return;
  }

  if (baz) {
    //...
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, the guard clauses are:

if (!foo) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

and:

if (!bar) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

They return the function early if those conditions are false.

With that, we don’t need to have any nesting.

Replace Conditional with Polymorphism

Instead of having a switch statement to that does the same thing to different kinds of data, we can create subclasses for each and then have different methods that are tailored to the type of object that it’s called on.

For instance, instead of writing the following:

class Animal {
  constructor(type) {
    this.type = type;
  }

  getBaseSpeed() {
    return 100;
  }

  getSpeed() {
    switch (this.type) {
      case ('cat'): {
        return getBaseSpeed() * 1.5
      }
      case ('dog'): {
        return getBaseSpeed() * 2
      }
      default: {
        return getBaseSpeed()
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We write the following:

class Animal {
  constructor(type) {
    this.type = type;
  }

  getBaseSpeed() {
    return 100;
  }
}

class Cat extends Animal {
  getSpeed() {
    return super.getBaseSpeed() * 1.5;
  }
}

class Dog extends Animal {
  getSpeed() {
    return super.getBaseSpeed() * 2;
  }
}
Enter fullscreen mode Exit fullscreen mode

Our switch statement was long and we have to tailor the case blocks to different kinds of objects.

Therefore, it’s better that we turn the switch statement into their own subclass so they can have their own method for getting the speed.

Introduce Null Object

If we have repeated checks for null or undefined , then we can define a subclass that represents the null or undefined version of the class and then use that.

For instance, instead of writing:

class Person {
  //...
}
Enter fullscreen mode Exit fullscreen mode

We write:

class Person {
  //...
}

class NullPerson extends Person {
  //...
}
Enter fullscreen mode Exit fullscreen mode

Then instead of setting properties of the object where Person would be null or undefined , we set it to the NullPerson instance instead.

This eliminates the need to check for those values with conditionals.

Conclusion

We can create a null or undefined version of the class to set it instead of null or undefined .

Also, we can decompose long conditional expressions into smaller ones and combine small ones into big ones.

Top comments (0)