DEV Community

Mina
Mina

Posted on • Updated on

Refactoring for Beginners: Part 1

Refactoring is a practice aimed at improving existing code without altering its external behavior or functionality. Its purpose is to enhance the overall structure, readability, and maintainability of the codebase. Refactoring encourages clean and reusable code that reveals intent, thereby helping to create well-structured design patterns.

A common misconception is that the design patterns of a project should be determined upfront, and one must strictly abide by them. However, continuous refactoring allows the codebase to evolve more naturally, forming better design patterns that more effectively accommodate the app's requirements.

So where should we begin when refactoring? Detecting 'code smells' can be the first step. 'Code smell' is a term used to describe code that not only contains repetition but also lacks clarity of intent and is overengineered to the point of being overly complex to understand. These involve duplicated code, names that don't clarify their purpose, global data, long methods, parameters, and classes, as well as complex conditional/switch statements, among others.

Although duplicated code is not always a problem, let’s consider the following example.

  for (const lesson of lessons) {
    if (lesson.subject === 'english') {
      total += lesson.price
    }
  }

  for (const lesson of lessons) {
    if (lesson.subject === 'spanish') {
      total += lesson.price
    }
  }
Enter fullscreen mode Exit fullscreen mode

This repeated code could be extracted into a function with a descriptive name that clearly communicates its purpose. If performance isn't a primary concern, higher-order functions like 'filter' and 'reduce' could be used to make the code more readable.

funciton calculateTotalForSubject(lessons, subject) {
  let total = 0;
  for (const lesson of lessons) {
    if (lesson.subject === selectedSubject) {
      total += lesson.price;
    }
  }
  return total;
}
Enter fullscreen mode Exit fullscreen mode

Global data is often viewed as a code smell: it can alarm developers as it can create potential problems. There are situations, especially in frontend development scenarios like logging and configuration settings, where a single source of truth for the entire application's state is necessary. The singleton pattern can be useful, but the global data should be maintained immutable (much like in Redux implementations) or read-only. The singleton pattern can raise concerns because the data is susceptible to modifications from anywhere in the codebase, and if it is mutable, it can cause unexpected changes.

Image description

In situations where global data poses a problem, encapsulation is a useful refactoring strategy as it allows data to be stored locally within specific scopes rather than globally. By doing so, we can better control how and where the changes occur.

This approach can be accomplished through the techniques of extracting and inlining classes. To extract a class, we simply identify the methods to be relocated and extract them into new classes. On the other hand, inlining a class is like taking the methods and properties from one class to another and integrating them directly into the new class.

class CourseSingleton {
    constructor() {
        if (CourseSingleton.instance) {
            return CourseSingleton.instance;
        }

        this.courses = {};
        CourseSingleton.instance = this;
    }

    addCourse(name, content) {
        if (!this.courses[name]) {
            this.courses[name] = content;
        }
    }

    getCourse(name) {
        return this.courses[name];
    }
}

Enter fullscreen mode Exit fullscreen mode

Inlining singleton looks like this.


class Course {
    constructor() {
        this.courseSingleton = new CourseSingleton();
    }

    addCourse(name, content) {
        return this.courseSingleton.addCourse(name, content);
    }

    getCourse(name) {
        return this.courseSingleton.getCourse(name);
    }

    displayCourses() {
        return this.courseSingleton.displayCourses();
    }
}

Enter fullscreen mode Exit fullscreen mode

In the client, you would then reference Course instead of CourseSingleton.

To be continued...

Top comments (0)