Categories
Refactoring

Problems That We May Run Into When Refactoring

Refactoring is something that we often have to do when developing software. Therefore, we’ve to know about it.

In this article, we’ll look at the problems that we may run into when refactoring.

Changing Interfaces

When we change an interface, this means that anything that implements the interface and anything that calls something that implements the interface will all have to change.

The whole point of having interfaces is so that we don’t have to worry about the underlying implementation of something.

There’re some changes that we can do for refactoring. Changing method names are OK since they can automatically be changed all at once.

However, if we’re changing an interface that’s already published, then we’ve to keep the new and the old version of the interface to avoid breaking any existing code.

If we change the signature of the function, then we either call the old function or the new function depending on which interface is used.

We should try to reference both interfaces to see if the code still works with both the old and new interfaces.

We can make the old interface deprecated so that we won’t introduce breaking changes but let the users of the old interface know that the old interface will be deprecated and be removed in the future.

Design Changes That Are Difficult to Refactor

There’re design changes that are hard to refactor.

This may mean that we have to design that part of the program from scratch and write clean code to clean up the code.

When Shouldn’t We Refactor?

There’re times that we shouldn’t refactor. We may have to write code from scratch if it’s such a mess that it’s impossible to refactor.

In that case, it might be easier to start from the beginning.

A clear sign that we should rewrite is when the current code isn’t working as expected. We may discover that it’s full of bugs that it’s impossible to stabilize.

The code has to work mostly correctly if we want to refactor.

We can also consider refactoring a large piece of code into components with strong encapsulate.

This way, we can refactor or rebuild one piece of the code at a time. Because of the loose coupling, changing the code would be much easier than if we e kept the existing code.

If we’re close to a deadline, then we shouldn’t refactor. Refactoring always has a chance of breaking code.

Unfinished refactoring is technical debt. Most companies need debt to function properly.

However, we shouldn’t let is go out of hand or the company will go broke.

We got to manage the debt by refactoring our code in the case of software development.

Once we delivered the code, then we can refactor.

Refactoring and Design

Refactoring and design fo together. We should design our code well from the get-go so we won’t have to refactor so much.

Refactoring after we designed our code badly isn’t the most efficient use of our time.

We all run into issues with bad code and after we work with designs that are bad.

Then we either have to do a lot of refactoring after or we’ve to rewrite the bad part from scratch.

Also, we shouldn’t build our software to be too flexible. Flexible solutions are harder to maintain than ones that aren’t flexible.

If we don’t need the flexibility, then we shouldn’t put it in.

Flexibility costs us more time to build as it adds complexity to our code.

This makes everyone frustrated.

Even if we don’t implement flexible solutions now, we can still make our code to add flexibility later easily if needed.

Refactoring can lead to simple designs without sacrificing flexibility.

The design process can be easier and less stressful since we don’t have to design for all the flexibility that we don’t need now.

We can always refactor our code to add the flexibility that we need easily.

Therefore, we should design code that can be refactored easily.

Conclusion

Problems that we run into including changing interfaces and adding flexibility.

Changing interfaces means changing the implementation of whatever that implements the interface and use them.

Therefore, we’ve to keep both the old and new interfaces and deprecate the old one to avoid changes in breaking people’s code.

Also, we’ve to design code to make them easy to refactor so we can add changes to them later.

Categories
Refactoring

What is Refactoring?

Refactoring is something that we often have to do when developing software. Therefore, we’ve to know about it.

In this article, we’ll look at the definition of refactoring what we have to do to refactor our code.

Definition of Refactoring

Refactoring is a change made to the structure of our software so that it makes it easier to understand and modify without changing its observable behavior.

There’re many ways to refactor our code to make them cleaner and easy to change.

To refactor is to change software by applying one or more refactoring without changing the software’s observable behavior.

It’s pretty much just cleaning up code to make our lives easier.

After refactoring, our code should be easier to understand and work with.

We can make changes in the software that make little to no change in behavior while just making them easier to work with.

Wearing Two Hats

We can measure the progress of refactoring by adding tests to see if our refactoring changed anything unintentionally.

We should be both adding tests if they don’t exist yet and changing our production code.

Once we have the tests then we just have to run them to see if our refactoring changed any behavior.

All we have to do to make sure that we didn’t change anything is to run the tests.

Why Should We Refactor?

Refactoring makes our lives easier after the refactoring id done.

It improves the design of our software. Changes may be made without thinking fully about the design.

Therefore, we can clean up the code so that the code is designed better.

Poorly designed code takes more code to do the same things. The code might be doing the same thing in several places.

We can eliminate these issues by moving the common code to one place and referencing that.

This reduces duplication and code bloat. The less code is there, the less code that we’ve to modify.

The more code there is, the harder it is to modify. There’s more to understand and we may run into issues like the changes we made didn’t do what we expect because they’re places that we didn’t change but we’re supposed to.

If we remove the duplicates, then there’s only one place to change to get what we want.

Refactoring Makes Software Easier to Understand

Many people have to read code. We probably read more code than we write, so we should make our lives easier by refactoring our code so that we can understand them easier.

More often than not, people may just get something to work and then forget about.

Refactoring can make code that’s done quickly to just make things work easier to understand.

A bit of time spent on refactoring can communicate what the code is doing better.

Programming is all about saying what we’re doing.

It also helps us work faster since we can understand clean code easier than messy code.

As the code gets clearer, we can find things that we didn’t see before.

If we didn’t change the code, we may miss those things.

Refactoring Helps Find Bugs

Refactoring may make bugs surface. The new understanding that we get by refactoring may find flaws in logic that we may have missed before.

Photo by Harley-Davidson on Unsplash

Refactoring Helps Us Program Faster

This is the main purpose of refactoring. If the code is cleaner, then it’ll help us work faster.

If there’s less code bloat and duplicated bloat, then we can drill down to where we need to make our changes easier than if we left them messy.

Good design is critical for fast software development. The point of having a good design is to make development faster.

Without good design, the code deteriorates and making changes will get much harder fast.

Refactoring helps us develop software more quickly since it stops the design from decaying.

When Should We Refactor?

We should refactor when we see something duplicated 3 times. Once we have it 3 times, we refactor.

When we add functionality, we should also refactor to make adding the functionality easier.

Also, when we fix bugs, we should aim to clean up the code as we fix bugs to make everyone’s lives easier later on.

Code reviews are also a great chance to suggest refactoring before they get merged into the main branch.

Conclusion

Refactoring is cleaning up our code. It makes it easier to understand and change so that we can develop faster.

We should refactor when we’re changing code. Pretty much any situation when we’ve to change code is a good time to refactor.

Categories
Refactoring

JavaScript Refactoring — 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.

Pull Up Constructor Body

If we have multiple constructor bodies in our subclasses with overlapping code, we can move them to the superclass.

For instance, if we have the following code:

class Employee {

}

class Cook extends Employee {
  constructor(name, level) {
    this.name = name;
    this.level = level;
  }
}

class Manager extends Employee {
  constructor(name) {
    this.name = name;
  }
}

We can move the common code in both constructors to the superclass as follows:

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

class Cook extends Employee {
  constructor(name, level) {
    super(name)
    this.level = level;
  }
}

class Manager extends Employee {

}

In the code above, we move the code for setting the name field to the Employee class to avoiding duplicating that in the Cook and Manager classes.

Push Down Method

If we have superclass methods that are only used by an instance of one subclass, then we should move it to the subclass.

For instance, if we have the following code:

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

class Cook extends Employee {

}

class Manager extends Employee {

}

const cook = new Cook();
cook.speak();

Since speak is only called by a Cook instance, we can move it to the Cook class:

class Employee {

}

class Cook extends Employee {
  speak() {
    //...
  }
}

class Manager extends Employee {

}

const cook = new Cook();
cook.speak();

This way, we can reduce the clutter of our superclass.

Push Down Field

We push down a field that’s only used in one or a few subclasses to the subclass itself.

For instance, instead of writing the following code:

class Employee {
  constructor(subordinates) {
    this.subordinates = subordinates;
  }
}

class Cook extends Employee {

}

class Manager extends Employee {

}

We can put the subordinates field to the Manager class as follows:

class Employee {
  //...
}

class Cook extends Employee {
  //...
}

class Manager extends Employee {
  constructor(subordinates) {
    this.subordinates = subordinates;
  }
}

The subordinates field only applies to Manager so it should be there.

Extract Subclass

If we have a class that has features that are only used by some instances, then it should be extracted to its own subclass.

For instance, if we have the following code:

class Employee {
  //...
  getSubordinates() {
    //...
  }
}

Then since getSubordinates isn’t used by all Employee instances, we can extract it as follows:

class Employee {
  //...
}

class Manager extends Employee {
  //...
  getSubordinates() {
    //...
  }
}

This way, we don’t have to clutter up the Employee class with methods that not all instances need.

Extract Superclass

If we have 2 or more classes with similar features, then we should move the shared members to a superclass.

Then the existing classes can be subclasses of the superclass.

For instance, if we have the following code:

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

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

Then we can create a new class with the speak method and make Cat and Dog subclasses of that class as follows:

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

class Cat extends Animal {

}

class Dog extends Animal {

}

Now we don’t have any more duplication.

Photo by NeONBRAND on Unsplash

Collapse Hierarchy

If the subclass and superclass aren’t too different, then we can combine them into one.

For instance, if we have the following:

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

class Cat extends Animal {
  //...
}

We can combine them as follows:

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

Since the Cat class didn’t have many extra things we need, we can just remove it.

Any overly complex class hierarchy isn’t good.

Conclusion

We can remove duplicate from multiple classes by moving common code to a superclass.

If we don’t need much in a subclass, then we can remove it and combine all the members into one class.

Categories
Refactoring

JavaScript Refactoring — Functions and 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 conditionals.

Introduce Parameter Object

If our function has multiple parameters, we can combine them into one object parameter and use that instead.

For instance, if we have the following code:

const getAmountFromRange = (start, end) => {
  //...
}

We can instead write the following code:

const getAmountFromRange = ({
  start,
  end
}) => {
  //...
}

The fewer parameters we need, the better our function is.

Remove Setting Method

If we have extra setters that we never use, then we can remove them from our class.

For instance, instead of writing the following class:

class Employee {
  setUselessValue() {
    //,..
  }
}

We can remove the useless setter as follows:

class Employee {
  //...
}

Replace Constructor with Factory Method

If we want to do more things than a simple constructor can do, then we can replace it with a factory method.

For instance, instead of just having the following class:

class Employee {
  constructor(type) {
    this.type = type;
  }
  //...
}

We can have a factory function that takes instantiates the class according to the type that’s passed in:

class Employee {
  constructor(type) {
    this.type = type;
  }
  //...
}

const createEmployee = type => new Employee(type);

This lets us create employees of all types with one function.

Replace Error Code with Exception

We can replace error codes with exceptions so that errors are more obvious to us when we occur.

For instance, if we have the following function:

const deposit = (amount) => {
  if (amount < 0) {
    return -1;
  }
  //...
}

We can instead throw an exception as follows:

const deposit = (amount) => {
  if (amount < 0) {
    throw new Error('amount must be bigger than or equal to 0');
  }
  //...
}

Exceptions are more obvious that return codes, so we should use throw exceptions instead.

Error codes like -1 are meaningless without explanation or assigning them to constants.

Replace Exception with Test

We can also replace exceptions with conditionals to check the value so that we don’t have to use try...catch blocks everywhere.

For instance, instead of writing:

const getItemByIndex = (arr, index) => {
  try {
    return arr[index].foo;
  } catch (ex) {
    console.error(ex);
  }
}

We can write the following:

const getItemByIndex = (arr, index) => {
  if (typeof arr[index] === 'undefined') {
    return;
  }
  return arr[index].foo;
}

This way, we don’t have to make our code more complex by introducing try...catch , which makes the workflow less linear.

Pull Up Field

If 2 or more subclasses have the same field, then we can pull them up to the superclass.

For instance, instead of writing the following code:

class Employee {}

class Cook extends Employee {
  constructor(name) {
    this.name = name;
  }
}

class Manager extends Employee {
  constructor(name) {
    this.name = name;
  }
}

We write:

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

class Cook extends Employee {

}

class Manager extends Employee {

}

This is better since name is in the Employee class rather than duplicated in multiple subclasses.

Pull Up Method

Likewise, if we have 2 or methods that are duplicated in multiple subclasses, we can pull them up to the superclass.

For instance, instead of writing the following code:

class Employee {}

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

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

We can instead write the following:

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

class Cook extends Employee {
  //...
}

class Manager extends Employee {
  //...
}

Again, we eliminated duplication, which is good.

Conclusion

We can eliminate duplication by moving duplicate fields and methods to the superclass.

Also, we can group multiple parameters into objects so that we can reduce the number of parameters in a function.

We also don’t have to always throw exceptions, we can check for what we’re looking for before proceeding in our code instead.

However, sometimes we may want to throw exceptions instead when we’re returning error codes.

Categories
Refactoring

JavaScript Refactoring — More 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.

Introduce Assertion

We can add assertions to check for the parameters passed in by adding assertions.

This way, assumptions are made explicit.

For instance, we can write the following to add an assertion:

const assert = require('assert');
const foo = (val) => {
  assert.equal(val, 50);
  //...
}

The code above works for Node.js since it uses the Node.js’s assert module.

We check if val is 50 before proceeding. If the assertion is false, the function will end.

Rename Method

If our method name isn’t clear enough, then we should rename it so that it’s clearer.

For instance, instead of writing:

class Person {
  ga() {
    //...
  }
}

We write:

class Person {
  getAge() {
    //...
  }
}

getAge is much clearer than ga , so we should rename it.

Add Parameter

We can also add parameters to methods. For instance, instead of writing:

class Person {
  getContact() {
    //...
  }
}

We write:

class Person {
  getContact(date) {
    //...
  }
}

We added the date parameter to make getting what we want easier.

Separate Query From Modifier

If we have a method that returns and value and changes the state of the object, then we should break them up into separate methods.

This way, they only do one thing each.

For example, instead of writing:

class Purchase {
  setPurchaseDateAndGetTotalPrice() {
    //...
  }
}

We write:

class Purchase {
  setPurchaseDate() {
    //...
  }

  getTotalPrice() {
    //...
  }
}

Not only the names of the methods are shorter, but their bodies also do less and only one thing so it’s better.

Parameterize Method

Instead of having multiple methods doing similar things, we can have one method that takes a parameter that does something that all the methods do.

For instance, instead of writing:

class Purchase {
  fivePercentDiscount() {
    //...
  }

  tenPercentDiscount() {
    //...
  }
}

We write:

class Purchase {
  discount(percent) {
    //...
  }
}

Replace Parameter with Explicit Methods

If we have a method with parameters and different things are done according to those parameters, we can separate them out into their own methods.

For instance, instead of writing:

class Person {
  getDimension(type) {
    if (type === 'height') {
      return this.getHeight();
    } else if (type === 'weight') {
      return this.getWeight();
    }
  }

  getHeight() {
    ///...
  }

  getWeight() {
    ///...
  }
}

We write:

class Person {
  getHeight() {
    ///...
  }

  getWeight() {
    ///...
  }
}

As we can see, the getDimension isn’t all that useful to begin with, so we can remove it and call those getHeight and getWeight directly.

Preserve Whole Object

If we have to get multiple values from an object and pass them all into a function, then we should just pass in the whole object instead.

For instance, instead of writing:

const calculate = (height, weight) => {
  //...
}

const height = getDimensions().height;
const weight = getDimensions().weight;
calculate(height, weight);

We write:

const calculate = ({
  height,
  weight
}) => {
  //...
}

const dimensions = getDimensions();
calculate(dimensions);

We just pass in the whole object instead of passing in individual properties.

To make the calculate function more readable, we used destructuring to get the properties that we want to get from the object.

Replace Parameter with Method

We can replace a parameter with its own method. For instance, if we have:

const getDiscountLevel = () => {
  //...
  return 0.5;
};

const discountedPrice = (subtotal, discountLevel) => {
  return subtotal * discountLevel
}
const subtotal = 100
const discountLevel = getDiscountLevel();
const total = discountedPrice(subtotal, discountLevel);

We can just replace discountLevel with the getDiscountLevel function call itself as follows:

const getDiscountLevel = () => {
  //...
  return 0.5;
};

const discountedPrice = (subtotal) => {
  return subtotal * getDiscountLevel()
}
const subtotal = 100
const total = discountedPrice(subtotal);

Now we eliminated the parameter from discountedPrice and the discountLevel variable.

Conclusion

To add checks to a function before it’s run, we add some assertions with the Node assert module.

We can also rename methods to make them clearer. Also, methods that do multiple things should be broken down.

If we’re passing in multiple properties from the same object into a function, we should just make the function take the whole object.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.