Live demos are back. See Testim Mobile in action on Feb 21 | Save your spot

JavaScript Refactoring: 5 Plays to Improve Code Quality

No code is perfect, and nobody writes the perfect code. That’s why I’m writing this post. If everyone wrote absolutely…

Testim
By Testim,

No code is perfect, and nobody writes the perfect code. That’s why I’m writing this post. If everyone wrote absolutely perfect code all the time, there wouldn’t be a need for this post.

So how do we improve the quality of our code? One approach is to refactor the code.

But what exactly is JavaScript refactoring about? In short, refactoring means changing the code without changing its behavior.

Refactoring includes many elements, such as:

  • Writing clear, understandable, and maintainable code.
  • Writing meaningful comments and error messages.
  • Sticking to standards like naming conventions and code structure.
  • Modularizing chunks of code and how to structure those modules.

As you can see, refactoring is composed of many different tactics. Additionally, it’s not an exact science. Still, let’s try to improve the quality of your code by diving into five tips for improving code quality.

Before we go there, though, it’s important we’re on the same page regarding the basics of refactoring. So, before walking you through our list of refactoring tips, we’ll cover some important questions regarding refactoring, such as:

  • What does refactoring mean?
  • Why should you care about refactoring?
  • When is refactoring done?

Let’s get started.

JavaScript Refactoring: The Fundamentals

Let’s start by covering the basics of refactoring.

What Is Refactoring in JavaScript?

Refactoring—in JavaScript and other languages as well—is the process of changing a piece of code without altering how it works. In other words: you play around with the internal structure of a given function, class, module, etc., without changing the results it produces.

Expand Your Test Coverage

Fast and flexible authoring of AI-powered end-to-end tests — built for scale.
Start Testing Free

The practice of refactoring was popularized by the rise of the agile methodologies. Refactoring is, for instance, an important—and often overlooked—step in the TDD (test-driven development) cycle. Martin Fowler, who literally wrote the book on Refactoring, was one of the authors of the agile manifesto.

Why Is Refactoring Important?

Having covered the “what” of refactoring, the next obvious question becomes the “why.” Why would you want to refactor at all? If a given piece of code is working, wouldn’t it be advisable to stop touching it?

Refactoring is important because it makes your code better. By refactoring often, you ensure your code is kept maintainable, easy to read and navigate, and free of duplication. Refactoring makes your code simpler and cleaner, which means it’ll be less likely for your or other developers to introduce bugs to it. But even if some bugs do sneak in, it’s going to be easier to spot them and fix them.

As such, refactoring is often seen as an antidote to code smells—i.e. signs that might indicate deeper problems within a codebase.

When Should You Conduct Refactoring?

When exactly should you refactor your code? And how often?

As early as possible. As often as you can.

If you adopt TDD—and if you do it right—you’ll be refactoring dozens of times a day. After all, refactoring is the third step in the TDD cycle, often called “red-green-refactor” because of that.

Also, pull requests are a great opportunity for refactoring. When incorporating the feedback from your peers, you can seize the chance to tidy up the code, making it cleaner, more concise and removing any instances of duplication.

Finally, always track your quality metrics carefully. If they indicate that the quality of your application is going down, that’s probably a sign that a refactoring is long overdue.

JavaScript Refactoring: How To Do It, in Practical Tips

Having covered some fundamentals about refactoring, we’re now ready to walk you through our list of practical tips for JavaScript refactoring.

1. Commenting

Not everyone is gifted at writing clear comments. However, communication (even if it’s textual) is important.

When you write a piece of code, always develop the code from the standpoint of the next person who will work on the code.

Code changes all the time—so will yours. There will always be an occasion where another developer has to make changes to your code to fulfill new business requirements.

Therefore, you must spend time making sure your code is understandable. In my opinion, adding comments to your code always adds extra value. Although the code itself should be easy to read and understand, having extra explanations of a function helps the next person working with the code quickly grasp the purpose of a particular function.

So, how do we comment?

How to Comment Meaningfully

Generally speaking, there are two types of commenters. Some developers prefer to be very verbose, whereas the other type add very few comments.

A snippet of code that’s bloated with comments is hard to read. We want to avoid bombarding fellow developers with too many code comments. However, minimal code commenting often adds little value.

So we need to balance both the number of comments and how we describe things. Sticking to some easy-to-use standards helps you accomplish this goal. Let’s explore the following three comment types: top-level, class-level, and function-level commenting.

Top-Level Commenting

A top-level comment should give the reader an initial explanation of the purpose and responsibilities of a file. A two or three line comment is sufficient to give a fellow developer a clear indication of the purpose of the file.

Class-Level Commenting

In order to understand the need for a certain class, a developer should add a short description to each class they define. A class-level comment should include details about:

  • Goal and scope
  • Usage
  • Location in the bigger architectural picture

Function-Level Commenting

Last but not least, function-level commenting focuses on making the purpose of a function clear. Function-level comments should, for example, describe how the function converts input to output or explain the business logic.

A good reference for writing function-level comments in JavaScript is JSDoc. JSDoc tries to define a standard for commenting.

JSDoc itself is made up of a markup language that uses annotations to annotate JavaScript code. Using those annotations, programmers can add documentation in a structured way describing their code.

Let’s assume we’re working in a class Point that has an X and Y coordinate. The following fromString function converts two comma-separated numbers into a Point. The comment nicely describes what the function does. Besides that, it describes in a standardized way the expected input with @param and the return value with @return.

/**
* Convert a string containing two comma-separated numbers into a point.
* @param {string} str - The string containing two comma-separated numbers.
* @return {Point} A Point object.
*/
static fromString(str) {
   // business logic
}

JSDoc is definitely worth checking out to bring a more standardized approach towards code commenting to your development team.

2. JavaScript Refactoring According to Standards

There is no doubt that standards help to improve the quality of code. It’s important to check for those standards while refactoring your code.

Of course, every language has its own set of standards you can decide to follow. Let’s explore some commonly-used naming standards for JavaScript.

Naming Arrays

An array often contains a list of items. Therefore, we prefer to append an s to the variable name. If a developer reads code or looks at the interface of a function, they immediately know that a variable that ends in s is most likely an array.

const numbers = [ 1, 2, 3, 4 ];

Naming Booleans

For Booleans, we want to stick to natural language.

For example, we want to know if a car has a license plate. The result is a Boolean, so we name the variable that holds this Boolean hasLicense. To give another example, we want to know if a person is sick so we use isSick.

Both is and has are commonly used prepositions to indicate the variable holds a Boolean.

Looping Arrays

Last, keep things simple when naming variables for looping arrays. Let’s take a look at the below example, which takes a car out of the cars array:

for (const car of cars) {
  // business logic
}

Use the above conventions for refactoring your code. These standards help you to make your code more readable.

Want to spend less time refactoring tests? Take a look at Testim’s self-maintaining tests that use machine learning.

3. Meaningful Variable Names

A name should be descriptive and tell a fellow developer what it holds.

For example, what if we want to store the number of days that have elapsed? We could a simple one-letter variable with a comment. Of course, that’s a bad practice.

const t = 10; // elapsed time in days

To further improve the naming of this variable, let’s try using elapsedTime. Again, this is not a very good naming as it does not reveal the type of the variable.

const elapsedTime = 10;

Finally, let’s try one more time using elapsedTimeInDays. This name is much better as it indicates the variable holds a number and explains what the number represents.

const elapsedTimeInDays = 10;

Next, let’s look at how to handle errors.

4. Returning Errors

While refactoring, it makes sense to read every error message carefully. Does the message describe the exact reason for the error? Let’s take a look at some bad examples.

Ambiguous Message

Assume we try to log in to an application, but our login attempt fails. The application returns “You can’t log in to the application.” This message is not descriptive enough. It can refer to many possible errors: system down, wrong username, or no access to the application.

Try to return a clear error message, like “Password doesn’t match username.”

Avoid Technical Jargon

Finally, as a user might receive an error message, avoid using technical jargon. Try to explain what went wrong in plain language.

Let’s say the application lost connection to the server:

Good: “Network connection is lost.”

Bad: “Error with code N-23 occurred. Operation could not be completed.”

At last, let’s take a look at the power of modularization.

5. Modularization

When refactoring code, you should spend time analyzing the structure or hierarchy of the code. The structure of code can give us valuable information as well.

For example, we often define a utils file that holds all kinds of shared logic. Sometimes, I’ll make a mess by combining non-related functions.

The presence of a utils file is actually a code smell. Try to group functions into meaningful modules.

To give an example, a file full of util functions contains two functions: add and subtract. Both functions perform mathematical operations. We can easily group them into a Maths module which exposes both functions. Now, when screening the architecture, it’s more apparent where to find those functions.

Refactor Safely, Refactor Early, Refactor Often!

Refactoring definitely helps you to improve the code quality. It makes variable names more clear, describes functions, or returns more meaningful error messages. The structure of the code becomes more manageable and with less duplication.

When used together with tools such as linters, refactoring is a powerful force for code quality. All of that means fewer opportunity for bugs to arise and more maintainability.

However, it’s not all a bed of roses when it comes to refactoring; it can be a risky operation. After all, you’re messing with code that currently works fine. In the process of refactoring it, you might cause it to stop working. How to proceed?

The answer lies in automated testing. If you have a comprehensive suite of automated tests covering your application, you’re protected against breaking that already works. With the right test automation tool at your disposal, you’ll be able to test your application thoroughly, protecting it against bugs and allowing you to refactor aggressively.

This post was written by Michiel Mulders. Michiel is a passionate blockchain developer who loves writing technical content. Besides that, he loves learning about marketing, UX psychology, and entrepreneurship. When he’s not writing, he’s probably enjoying a Belgian beer!