Categories
Refactoring

JavaScript Refactoring — Conditionals

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")

We write:

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

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;

We write:

const x = 5;
const inRange = x > 5 && x < 6;

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();
}

We write:

if (price > 100) {
  //...
} else {
  //...
}
complete();

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;
  }
  //...
}

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;
  }
  //...
}

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) {
        //...
      }
    }
  }
}

We write:

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

  if (!bar) {
    return;
  }

  if (baz) {
    //...
  }
}

In the code above, the guard clauses are:

if (!foo) {
  return;
}

and:

if (!bar) {
  return;
}

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()
      }
    }
  }
}

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;
  }
}

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 {
  //...
}

We write:

class Person {
  //...
}

class NullPerson extends Person {
  //...
}

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.

Categories
Refactoring

JavaScript Refactoring — Objects and Values

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 classes and objects.

Replace Magic Number with Symbolic Constant

If we have lots of repeating values that have the same meaning but not explicitly stated, then we should turn them to a constant so everyone knows what they mean and we only have to change one place if a change is needed.

For instance, instead of writing:

const getWeight = (mass) => mass * 9.81

const potentialEnergy = (mass, height) => mass * height * 9.81

We write:

const GRAVITATIONAL_CONSTANT = 9.81;

const getWeight = (mass) => mass * GRAVITATIONAL_CONSTANT

const potentialEnergy = (mass, height) => mass * height * GRAVITATIONAL_CONSTANT

Now we know that 9.81 actually means GRAVITATIONAL_CONSTANT and we don’t have to repeat ourselves.

Encapsulate Field

We can add getters and setters to a field to access a class field instead of manipulating it directly.

For instance, instead of writing:

class Person {
  constructor(name) {
    this.name = name;
  }
}

We write:

class Person {
  constructor(name) {
    this._name = _name;
  }

  get name() {
    return this._name;
  }

  set name(name) {
    this._name = name;
  }
}

This way, we can control how values are set since we can put code in our setter to set the name .

We can also control who can get the name since it’s returned in the getter.

Encapsulate Collection

We can also encapsulate arrays that are part of the class instance.

For instance, instead of writing:

class Person {
  constructor(friends) {
    this._friends = _friends;
  }

  get friends() {
    return this._friends;
  }

  set friends(friends) {
    this._friends = friends;
  }
}

const person = new Person(['joe', 'jane'])

In the code above, we have an array instead of another type of object as we have above.

But the idea is the same.

Replace Record With Data Class

We can replace a field with its own data class so that we can have more flexibility in the data we record.

For instance, instead of writing:

class Person {
  constructor(name, bloodGroup) {
    this.name = name;
    this.bloodGroup = bloodGroup;
  }
}

const person = new Person('joe', 'a')

We write:

class BloodGroup {
  constructor(name) {
    this.bloodGroup = name;
  }
}

class Person {
  constructor(name, bloodGroup) {
    this.name = name;
    this.bloodGroup = bloodGroup;
  }
}

const bloodGroup = new BloodGroup('a');
const person = new Person('joe', bloodGroup)

This way, we can store more varieties of data in the bloodGroup field.

Replace Type Code With State/Strategy

Instead of having a type field in the class, we create subclasses based on the type of object.

This way, we can have more members which both classes don’t share in their own subclass.

For instance, instead of writing:

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

const cat = new Animal('cat');
const dog = new Animal('dog');

We write:

class Animal {
  //...
}

class Cat extends Animal {
  //...
}

class Dog extends Animal {
  //...
}

const cat = new Cat();
const dog = new Dog();

In the examples above, instead of writing one Animal class, we have Cat and Dog classes that are subclasses of the Animal class.

This way, we can have members that they don’t share in their own class and the members that they share remain in the Animal class.

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")

We write:

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

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

Conclusion

If we have magic numbers, we should replace them with named constants.

We should add getters and setter methods for class fields so that we can control how they’re returned or set.

If we have type fields, we can replace them with their own subclasses.

Finally, we can break up long conditional expressions into smaller ones so that it’s easier to read and understand.

Categories
Refactoring

JavaScript Refactoring —More Class Refactoring

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.

Replace Inheritance with Delegation

If we have subclasses that don’t need to inherit anything from a superclass, then the subclass doesn’t have to be a subclass of the superclass.

For instance, if we have a subclass that doesn’t need anything from the superclass other than calling some methods from it:

class Animal {
  //...
  speak() {
    //...
  }
}

class Cat extends Animal {
  //...
  talk() {
    super.speak()
  }
}

We can remove the extends and just call the Animal class as follows:

class Animal {
  //...
  speak() {
    //...
  }
}

class Cat {
  constructor() {
    this.animal = new Animal();
  }

  talk() {
    this.animal.speak()
  }
}

Reduce Inheritance Hierarchy

We can reduce the inheritance hierarchy to only be what’s necessary.

We don’t want a complex inheritance tree that’s hard to navigate.

For example, if we have the following code:

class Animal {
  //...
}

class Cat extends Animal {

}

class Dog extends Animal {

}

class BlackCat extends Cat {

}

class Labrador extends Dog {

}

Then we may have to rethink if we want to create subclasses for each type of cat and dog.

We can just reduce the tree size by eliminating those classes. For example, we can remove the last 2 classes in the code above by writing:

class Animal {
  //...
}

class Cat extends Animal {

}

class Dog extends Animal {

}

Then the inheritance tree is much less complex.

Convert Procedural Design to Function

If we have lots of procedures doing similar things, we can turn the procedures into functions so we can use them in multiple places.

For instance, if we have the following code:

const subtotal = 100
const provincialSalesTax = subtotal * 0.05;
const federalSalesTax = subtotal * 0.1;
const total = subtotal + provincialSalesTax + federalSalesTax;

We can move the tax calculation code into their own function as follows:

const calculateTax = (amount, taxRate) => amount * taxRate;
const subtotal = 100
const provincialSalesTax = calculateTax(subtotal, 0.05);
const federalSalesTax = calculateTax(subtotal, 0.1);
const total = subtotal + provincialSalesTax + federalSalesTax;

Now if we have to calculate tax amounts again, we can use the calculateTax function instead of writing out the code every time to calculate those amounts.

Extract Hierarchy

If we have class that’s doing too much because of lots of conditional statements in the methods, we can extract those pieces of code into subclasses.

For instance, instead of writing the following:

class Invoice {
  getInvoice(type) {
    if (type === 'residental') {
      //...
    } else if (type === 'commercial') {
      //...
    } else if (type === 'concession') {
      //...
    }
  }
}

We can break the getInvoice logic into their own subclasses as follows:

class Invoice {

}

class ResidentialInvoice extends Invoice {
  getInvoice() {
    // residential logic
  }
}

class CommercialInvoice extends Invoice {
  getInvoice() {
    // commercial logic
  }
}

class ConcessionInvoice extends Invoice {
  getInvoice() {
    // concession logic
  }
}

Assuming the getInvoice method bodies are different in each subclass, the getInvoice method can stay there since they’re all different except for the name.

Conclusion

We can make our code clearer by breaking up complex logic into subclasses.

Also, inherit might not be needed if a class doesn’t need to inherit it.

Reducing the inheritance hierarchy is also a good idea since complexity is bad.

If we have similar logic in different places, we should extract them a function.

Categories
Refactoring

JavaScript Refactoring — Classes and Objects

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 classes and objects.

Replace Data Value with Object

If we want to store things that can’t be stored in any primitive type, then we need to store them in an object.

For instance, instead of writing:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

const purchase = new Purchase('joe');

We write:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  constructor(name) {
    this.name = name;
  }
}

const customer = new Customer('joe');
const purchase = new Purchase(customer);

This way, in addition to the name, we can add other properties to the Customer instance as we desire.

Whereas in the first example, customer isn’t encapsulated in its own object.

Change Value to Reference

We can turn an object with many equal instances into a many to one relationship.

For instance, if we have many customers with the same order, then instead of having many purchase objects that only reference one customer object, we can reference many purchases in one customer object.

For instance, instead of writing the following:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  //...
}

const customer = new Customer('joe');
const purchase = new Purchase(customer);

And repeat the same with all the other purchases, we write:

class Purchase {
  constructor(customesr) {
    this.customers = customers;
  }
}

class Customer {
  //...
}

const joe = new Customer('joe');
const jane = new Customer('jane');
const purchase = new Purchase([joe, jane]);

This way, we don’t need a new customer entry for each new purchase object.

Change Reference to Value

We can also turn a many to one relationship between classes to one. For instance, if we only have one object that always references one other class, then we can turn it into a one to one relationship between them.

For instance, instead of writing:

class Purchase {
  constructor(currencies) {
    this.currencies = currencies;
  }
}

class Currency {
  //...
}

const usd = new Currency('usd');
const purchase = new Purchase([usd]);

We write:

class Purchase {
  constructor(currency) {
    this.currency = currency;
  }
}

class Currency {
  //...
}

const usd = new Currency('usd');
const purchase = new Purchase(usd);

Replace Array with Object

We should change an array to an object if each entry has different types of data that should be grouped together.

For instance, instead of writing:

const joe = ['joe', 12];

We write:

const joe = {
  name: 'joe',
  age: 12
}

Instead of putting the name and age into an array, we move them into an object with their own property names to make everyone clear what they are.

Change Unidirectional Association to Bidirectional

We can change a unidirectional relationship between 2 classes to a bidirectional one.

For instance, instead of writing:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}
class Customer {
  //...
}

We write:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  constructor(order) {
    this.order = order;
  }
}

There’re situations where both classes want to access each other’s data, so we reference each other in each class.

Change Bidirectional Association to Unidirectional

We can also reverse the action we did above and change the bidirectional association to unidirectional.

So we can turn the following:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  constructor(order) {
    this.order = order;
  }
}

back to:

class Purchase {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  //...
}

It the Customer class doesn’t need anything from Purchase , then we can take it out.

Conclusion

We can change the relationships of classes from unidirectional and bidirectional and vice versa depending on if we need to access the data from each class or not.

If we need to store more data than a primitive than store in a class field, then we can turn it into an object.

We can also change class relationships to one to many and vice versa depending on how many of a thing we need in a field.

Categories
Refactoring

JavaScript Refactoring — Classes

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 classes.

Inline Class

We start with 2 classes and roll them into one. It’s the reverse of the previous refactoring.

For instance, instead of writing:

class PhoneNumber {
  constructor(phoneNumber) {
    this.phoneNumber = phoneNumber;
  }
}

class Person {
  constructor(name, phoneNumber) {
    this.name = name;
    this.phoneNumber = new PhoneNumber(phoneNumber);
  }
}

We roll the code for the PhoneNumber class back into the Person class as follows:

class Person {
  constructor(name, phoneNumber) {
    this.name = name;
    this.phoneNumber = phoneNumber;
  }
}

We may want to do this if the class that we separated out isn’t that complex.

In the code above, we have the PhoneNumber class that has no methods, so we can just roll it into the Person class since phoneNumber is a property that a Person instance can have.

Hide Delegate

We can create methods on the client class to hide the underlying implementation of the code by filling the method class with code that gets the value we want directly.

This reduces the coupling of our code for getting the item that we want.

For instance, instead of writing the following:

class Department {
  constructor(name, deptHead) {
    this.name = name;
    this.deptHead = deptHead;
  }

  getDeptHead() {
    return deptHead;
  }
}

class Person {
  constructor(name, dept) {
    this.name = name;
    this.dept = dept;
  }

  getDept() {
    return this.dept;
  }
}

const deptHead = new Person('jane');
const dept = new Department('customer service', deptHead);
const employee = new Person('joe', dept);
const manager = employee.getDept().getDeptHead();

which requires us to get the department first before getting the department head as we did on the last line.

We can write the following:

class Department {
  constructor(name, deptHead) {
    this.name = name;
    this.deptHead = deptHead;
  }
}

class Person {
  constructor(name, dept) {
    this.name = name;
    this.dept = dept;
  }

  getDept() {
    return this.dept;
  }

  getDeptHead() {
    return this.dept.deptHead;
  }
}

const deptHead = new Person('jane');
const dept = new Department('customer service', deptHead);
const employee = new Person('joe', dept);
const manager = employee.getDeptHead();

All we did was to add the getDeptHead method to person to get the department head from the department directly.

Then we don’t have to get the department first to get the department head, which reduces coupling between the Person and Department class.

Introduce Foreign Method

We can add a foreign method to a class that we can’t modify directly by adding methods to it with Object.assign .

For instance, we can write the following:

class Foo {
  //...
}

const mixin = {
  bar() {
    //...
  }
}

Object.assign(Foo.prototype, mixin)

In the code above, we added the bar instance method to Foo by calling Object.assign , given the Foo is a class that we can’t modify.

Introduce Local Extension

In addition to adding new methods to a class directly, we can create new class with extra methods that we want to call by making a class with the methods a subclass of the class that we can’t modify directly.

For instance, we can write the following:

class Foo {
  //...
}

class Bar extends Foo {
  bar() {
    //...
  }
}

In the code above, we created a subclass of Foo that has the methods that we want to call, where Foo is a class that we can’t change.

Now we don’t have to modify Foo by adding things to its prototype.

Self Encapsulate Field

We can add getters and setters methods to a field that we access directly so that we can encapsulate it.

For example, instead of writing the following:

class Counter {
  inRange(arg) {
    return arg >= this._low && arg <= this._high;
  }
}

We write:

class Counter {
  inRange(arg) {
    return arg >= low() && arg <= high();
  }

  get low() {
    return this._low;
  }

  get high() {
    return this._high;
  }
}

This lets us encapsulate fields and we can also apply other operations to it without access to the high and low fields and applying operations to them directly.

Conclusion

We can move members in classes that don’t do much into another class so that we can remove the class that doesn’t do much.

To reduce coupling between classes, we can methods to directly get what we want instead of having to do that in a roundabout manner.

We can encapsulate fields so that we can get a value and do something before it before the value we returned.