Навіщо та як проводити рефакторинг коду

Навіщо та як проводити рефакторинг коду

  • 11 лютого, 2022
  • читати 5 хв
Микола Барда
Микола Барда Senior Front-end Developer у GlobalLogic

Привіт, мене звуть Микола Барда, у цій статті ви дізнаєтесь про мій погляд на рефакторинг та підходи, які я використовую під час роботи з реальними проектами.

Починаючи писати цю статтю, я не розраховував на таку кількість інформації, яку знайшов потрібним викласти на цю тему. Тому дана стаття висвітлюватиме лише введення у рефакторинг. Можливо, надалі ця стаття стане першою у циклі статей про рефакторинг.

Для початку давайте визначимо, що таке рефакторинг коду:

У книзі «Рефакторинг. Поліпшення проекту існуючого коду», Мартін Фаулер дає таке пояснення терміну:

Refactoring is the process of changing a software system in a way that does not alter the external behavior of the code yet improves its internal structure.
Мартін Фаулер, Рефакторинг. Поліпшення проекту існуючого коду

Рефакторинг — це зміна зовнішнього вигляду коду без зміни поведінки.

Перефразовуючи, коли ми проводимо рефакторинг коду, ми не змінюємо поведінку коду, ми торкаємося лише його внутрішньої складової.

Рефакторинг необхідний, якщо ваш код:

  • важко читати (це найголовніше!)
  • незрозумілий
  • дублюється
  • метод або функція містить занадто багато рядків
  • нечистий (є закоментований код, присутні другорядні коментарі)
  • тяжко дебагувати
  • важко вносити зміни
  • важко масштабувати програму
  • не відповідає прийнятому в команді коду стайлу

А тепер давайте подивимося на рефакторинг реального коду та розберемо зміни рядок за рядком.

Приклад до рефакторингу:

export class UsersModule {
  init() {
    fetch("https://example-url.com/api/users")      
    .then((r) => r.json())
      .then((resp) => {
        const cont = document.querySelector("#root");
        resp.forEach((element) => {
          if (element.isActive) {
            const elem =
            `<div class="user-container">
               <div class="user-name">${element.id}</div>
               <div class="user-name">${element.title}</div>
            </div>`;
            cont.insertAdjacentHTML("beforeend", elem);
          }
        });
      });
  }
}

Після рефакторингу:

export class UsersModule {
  users = null;
  usersContEl = null;
  constructor() {
    this.initUserModule();
  }
  async initUserModule() {
    this.usersContEl = document.querySelector("#root");    
    this.users = await this.getUsers(enpoints.getUsers);
    const usersForRender = this.createUsersToRender(this.users);
    this.renderUsers(this.usersContEl, usersForRender); 
  }
 
  getUsers(endpoint) {
    return fetch(endpoint).then((r) => r.json());
  }
  renderUsers(containerEl, users) {
    users.forEach((user) => {
      const userEl = this.createUserElement(user);
      containerEl.insertAdjacentHTML("beforeend", userEl);
    });
  }
  createUsersToRender(users) {
    return users.filter((user) => user.isActive);
  }
  createUserElement(user) {
    return `<div  id="${user.id}" class="user-container">
                <div class="user-name">${user.firstName}</div>
                <div class="user-name">${user.role}</div>
            </div>`;
  }
}

Навіщо та як проводити рефакторинг коду

Як видно з прикладу, поведінка коду не змінилася, але його стало набагато більше! Навіщо взагалі так збільшувати кількість коду, навіщо нам рефакторинг?

Давайте розберемо результат детально та подивимося, які з пунктів вище ми змогли покращити:

Рекомендуємо публікацію по темі

Приклад вище відповідає майже всім наведеним пунктам, тому ми провели його рефакторинг.

Що і як ми зробили:

  1. Ми створили властивості класу users, usersContEl, у яких зберігатимемо дані.
  2. Перейменували метод init на initUsersModule, що говорить про те, що цей метод ініціює модуль користувачів. Сам модуль тепер складається з 4 рядків і став більш легшим для читання. Ми розуміємо, з чого ми починаємо і чим закінчуємо.
  3. Ми створили нові способи класу.

Ми окремо винесли метод отримання даних, getUsers, і він приймає параметр endpoint, до якого ми будемо звертатися за даними. Таким чином, наш метод можна використовувати для будь-яких GET запитів з будь-якими endpoint.

async getUsers(endpoint) {
    const data = await fetch(endpoint).then((r) => r.json());
    return data;
  }

У нас є окремий метод обробки отриманих даних createUsersToRender(), який як параметр приймає будь-яких користувачів. Цей метод дозволяє коду гнучкіше реагувати не внесення змін — показувати тільки активних користувачів, або тих, хто активний і тих, роллю яких є admin.

createUsersToRender(users) {
    return users.filter((user) => user.isActive);
  }

Метод створення елемента UI createUserElement приймає параметром сутність для відображення, а також додає гнучкість до відображення користувача. Ми завжди знаємо, де ми можемо додати нові теги, класи, ID.

createUserElement(user) {
    return `<div  id="${user.id}" class="user-container">
                <div class="user-name">${user.firstName}</div>
                <div class="user-name">${user.role}</div>
            </div>`;
  }

Загальний метод для відображення списку користувачів renderUsers:

renderUsers(containerEl, users) {
    users.forEach((user) => {
      const userEl = this.createUserElement(user);
      containerEl.insertAdjacentHTML("beforeend", userEl);
    });
  }

Отже, ми розбили код на незалежні методи.

Логіка та поведінка коду не змінилися, ми як і раніше отримуємо дані, обробляємо їх та відмальовуємо, тільки тепер усі ці дії незалежні одна від одної.

Надалі, якщо нам прийдуть редагування від замовника, або буде знайдено баг, нам буде набагато простіше вносити зміни в окремі частини коду, а не в один метод.

Також, окремі методи краще піддаються тестуванню.

Говорячи про рефакторинг, не можна не згадати про тестування коду (unit testing).

Тести — це наша гарантія, що поведінка коду в процесі рефакторингу не зміниться.

Тема рефакторингу велика і не поміститься у рамки невеликої статті. Але сподіваюся, що ви змогли познайомитися з поняттям рефакторингу, дізналися, на що варто звертати увагу, і що можна постаратися не робити в процесі проектування та написання коду.