Simplifying a JavaScript Function With 12 Automated Refactorings
Tags: javascript, vscode, webdev, node
Published: 2021-12-01 15:33:30

**Executing many automated refactorings in a row is a powerful way to improve your code quickly**. The advantage of this approach over manual refactoring is that it is less likely to introduce bugs and that it can often be faster with the right keyboard shortcuts. However, it is a bit of an art to chain refactorings, as it can involve unintuitive actions to enable further steps.

This blog post shows an example of how to **simplify a small JavaScript function in a series of 12 automated refactorings** without changing its behavior. I'll be using [Visual Studio Code](https://code.visualstudio.com/) and the [P42 JavaScript Assistant refactoring extension](https://marketplace.visualstudio.com/items?itemName=p42ai.refactor).

> 💡&nbsp;&nbsp;This blog post uses P42 JavaScript Assistant v1.55

Initially, the function (from this [blog post](https://dev.to/luigizaccagnini/refactoring-code-in-octo-3397)) looks at follows:
```javascript
const lineChecker = (line, isFirstLine) => {
  let document = ``;

  if (line !== "" && isFirstLine) {
    document += `<h1>${line}</h1>`;
  } else if (line !== "" && !isFirstLine) {
    document += `<p>${line}</p>`;
  } else if (line === "") {
    document += "<br />";
  }

  return document;
};
```

After refactoring, the function is much shorter and easier to comprehend:
```javascript
const lineChecker = (line, isFirstLine) => {
  if (line === "") {
    return `<br />`
  }

  return isFirstLine ? `<h1>${line}</h1>` : `<p>${line}</p>`;
};
```

Here are the steps that I took to refactor the function:

## Simplify Control Flow and Remove Variable

The first refactorings eliminate the document variable and simplify the control flow. This change makes it easier to reason about the function because there is less state (i.e., no document variable) and several execution paths return early.

1. **Pull out the `+` from the `+=` assignments into regular string concatenation**. 
   This step enables the introduction of early return statements in the next step.
1. **Replace re-assigning the `document` variable with early return statements.**
   This step simplifies the control flow and enables inlining the `document` variable.
1. **Inline the `document` variable**. 
   This step removes an unnecessary variable and enables the removal of the empty string literals in the next step. 
1. **Remove empty string literals by merging them into the templates.**

After applying these steps, the function looks as follows:
```javascript
const lineChecker = (line, isFirstLine) => {
  if (line !== "" && isFirstLine) {
    return `<h1>${line}</h1>`;
  } else if (line !== "" && !isFirstLine) {
    return `<p>${line}</p>`;
  } else if (line === "") {
    return `<br />`;
  }

  return ``;
};
```

![Simplify Control Flow and Remove Variable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3j18ahr2e453p70gn70.gif)

## Simplify Conditions and Remove Code

The next goals are to simplify the conditions in the if statements and to remove dead or unnecessary code. This change further reduces the complexity of the function and makes it easier to comprehend because there is less code and the conditions are simpler.

1. **Separate `isFirstLine` condition into nested if statement.**
1. **Pull up negation from `!==`.** These two steps prepare the removal of the redundant else-if condition.
1. **Remove redundant condition on else-if** because it is always true. After removing the redundant else-if condition, it becomes clear that the final `return` statement is unreachable.
1. **Remove unreachable code.** Unreachable code is useless and consumes some of our attention without benefit. It is almost always better to remove it.
1. **Push negation back into `===`.** This refactoring reverts a previous step that was temporarily necessary to enable further refactorings.
1. **Invert `!==` condition and merge nested if.** The resulting `line === ""` condition is easier to understand because there is no negation. Even better, it enables lifting the inner if statement into an else-if sequence and indicates that the empty line handling might be a special case.

After applying these steps, the function looks as follows:
```javascript
const lineChecker = (line, isFirstLine) => {
  if (line === "") {
    return `<br />`;
  } else if (isFirstLine) {
    return `<h1>${line}</h1>`;
  } else {
    return `<p>${line}</p>`;
  }

};
```

![Simplify Conditions and Remove Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2y693ze3z69z71a10nx.gif)

## Improve Readability

The last set of refactorings aims to improve the readability by moving the special case `line === ''` into a guard clause and using a conditional return expression.

1. **Convert `line === ''` condition into guard clause.**
1. **Simplify return with conditional expression.**
1. **Format**, e.g., with [Prettier](https://prettier.io/) on save.

Here is the final result:
```javascript
const lineChecker = (line, isFirstLine) => {
  if (line === "") {
    return `<br />`
  }

  return isFirstLine ? `<h1>${line}</h1>` : `<p>${line}</p>`;
};
```

![Improve Readability](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vssqxeonqaztk7ag2wi4.gif)

### Additional Considerations

This blog post shows how to use automated refactorings to simplify a JavaScript function without changing its behavior. In practice, there are many additional considerations:

- **Automated Test Coverage** 
  Automated testing is essential to have confidence that the refactoring did not inadvertently change the code's behavior. It is particularly crucial when there are error-prone manual refactoring steps. When there is insufficient test coverage, it is critical to add tests before refactoring code.

- **Uncovering Potential Bugs**
  Simpler code can uncover potential bugs that you can investigate after the refactoring is completed. In the example here, a `<br />` is being returned from the function even when `isFirstLine` is `true`, which might not be the intended behavior.

- **Other Refactorings**
  There are many ways to refactor the function from this blog post. I've focussed on simplifying the function, but renaming or even decomposing it are other possibilities. Check out the post "[How would you refactor this JS function?](https://dev.to/p42/how-would-you-refactor-this-js-function-4n71)" for more.

I hope this post gave you an idea of how to sequence automated refactoring steps to achieve a more significant refactoring change.