Essential Refactoring Techniques for Clean and Maintainable Code

Suraj Vishwakarma - Aug 2 - - Dev Community

Introduction

Refactoring is a task that needs to be done despite all your functionality running as expected. When we start a project, we have zero technical debt, but as we implement features, it accumulates. We need to refactor the code to reduce the technical debt.

Here are some benefits of refactoring:

  • Improved code quality and reduced complexity
  • Increased development speed and reduced costs over time
  • Better collaboration among teammates due to clearer code

These are quite helpful in writing clean and more readable code. I have previously written an article, 5 Code Refactoring Techniques to Improve Your Code to discuss refactoring techniques. This article focuses on more techniques, and a few are taken from previous comments.

Now, let’s get started.

1. Rename Variables and Methods

This is quite a common refactoring technique to rename variables and methods as per their usage in the code. This helps another developer understand the usage and functionality of the variable and method without looking into the code block. It also involves changing the names of classes, files, and other identifiers to more descriptive and meaningful ones.

Best Practices

  • Be Descriptive but Concise: The variable name should be small but enough to convey the purpose of the variable and methods.
  • Consistent Naming Convention: Follow a naming convention and stick to it for the whole project. You can use camelCase, Pascal Case, etc.
  • Avoid Abbreviations: Rather than using cust use the customer to avoid any confusion.

Example

Before:

    function calc(a, b) {
        let x = a * b;
        return x;
    }

    let result = calc(5, 3);
    console.log(result);
Enter fullscreen mode Exit fullscreen mode

After:

    function calculateArea(width, height) {
        let area = width * height;
        return area;
    }

    let rectangleArea = calculateArea(5, 3);
    console.log(rectangleArea);
Enter fullscreen mode Exit fullscreen mode

2. Parameter Object

This method simply means that when a method or class has multiple parameters then replace it with one object. This object encapsulates all the parameters, making the code cleaner and easier to maintain. It simplifies the method signature making method calls cleaner and easier to understand. The parameter object can be reused across different objects.

Best Practice

  • Logical Grouping: You should identify the parameters that belong together and group them in an object. Separate the parameters that do not belong
  • Meaningful Name: Give the object a meaningful name that indicates its purpose.

Example

Before:

    function calculateTotalPrice(quantity, pricePerUnit, discount, taxRate) {
        let discountAmount = quantity * pricePerUnit * (discount / 100);
        let taxAmount = quantity * pricePerUnit * (taxRate / 100);
        let totalPrice = (quantity * pricePerUnit) - discountAmount + taxAmount;
        return totalPrice;
    }

    let total = calculateTotalPrice(10, 15, 5, 8);
    console.log(total);
Enter fullscreen mode Exit fullscreen mode

After:

    const OrderDetails = {
      quantity,
      pricePerUnit,
      discount,
      taxRate,
    }

    function calculateTotalPrice(orderDetails) {
        let discountAmount = orderDetails.quantity * orderDetails.pricePerUnit * (orderDetails.discount / 100);
        let taxAmount = orderDetails.quantity * orderDetails.pricePerUnit * (orderDetails.taxRate / 100);
        let totalPrice = (orderDetails.quantity * orderDetails.pricePerUnit) - discountAmount + taxAmount;
        return totalPrice;
    }

    let total = calculateTotalPrice(orderDetails);
    console.log(total);
Enter fullscreen mode Exit fullscreen mode

Note: You can restructure the object for more cleaner code.


3. Using Design Patterns

Design patterns are reusable solutions to problems that occur while developing software. They are the best practices that can provide standard solutions to various design issues. Using design patterns we can make code more maintainable and reusable.

It can help easily find areas of issue to refactor. It can also help initially to reduce future technical debt with good design patterns. Otherwise, you can implement a design pattern later to simplify the code for readability and maintainability.

Best Practices

  • Understand the problem: Identify the issues in your code that need refactoring, such as high coupling, poor cohesion, or complex logic.
  • Choose the Right Design Pattern: Select the design pattern that best addresses the specific problem you're trying to solve. Not all design patterns are suitable for every situation. ## Example

Singleton Pattern: Ensure a class has only one instance and provide a global point of access to it.

    class Singleton {
        constructor() {
            if (!Singleton.instance) {
                this.data = {};
                Singleton.instance = this;
            }
            return Singleton.instance;
        }

        set(key, value) {
            this.data[key] = value;
        }

        get(key) {
            return this.data[key];
        }
    }

    const instance1 = new Singleton();
    const instance2 = new Singleton();
    console.log(instance1 === instance2);  // true

Enter fullscreen mode Exit fullscreen mode

4. Refactoring While Implementing New Functionality

Based on the comment by Edwin(@ekeijl). The comment mentioned, Refactoring -- Not on the backlog! article that shows refactoring should be done while implementing a new functionality. In this way, we can save time and refactor the code.

Best Practices

  • Refactor codes that are related to new functionality: Rather than refactoring whole code just refactor the part of the code that are used by the new functionality. We improve the code where we work and ignore the code where we don't have to work.
  • Repeat: With each new feature, repeat the process. Thus reducing the technical debt and improving the code quality.

5. Writing Test Features

Writing test features allow you to refactor the code without hampering its functionality. Writing tests for new functionality ensures that the new code works as expected and integrates smoothly with the existing code.

Best Practices

  • Scope of Refactoring: Determine which parts of the code need refactoring. Understand the current functionality and behavior of the code.
  • Write Unit Tests: Before refactoring, write unit tests that capture the current behavior of the code. These tests act as a baseline to ensure that refactoring does not alter functionality.
  • Refactor the Code: Make incremental changes to the code to improve its structure, readability, or performance without altering its external behavior after running the test.

Conclusion

Refactoring is a crucial task in maintaining and improving the quality of your codebase. Even if your current functionality works as expected, refactoring helps manage and reduce technical debt that accumulates over time. By regularly refactoring your code, you can achieve several benefits, including improved code quality, reduced complexity, increased development speed, and better collaboration among teammates.

Thanks for reading the article. I hope it will help you write better code in the future.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .