Привет, меня зовут Николай Барда, в этой статье вы узнаете о моем взгляде на рефакторинг программного кода и подходы, которые я использую при работе с реальными проектами. Начиная писать эту статью, я не рассчитывал на такое количество информации, которое нашел нужным изложить по этой теме. Поэтому данная статья будет освещать только введение в рефакторинг. Возможно, в дальнейшем эта статья станет первой в цикле статей об оптимизации кода.
Для начала давайте определим, что такое рефакторинг кода:
В своей книге «Рефакторинг. Улучшение проекта существующего кода», Мартин Фаулер дает такое объяснение термину:
Рефакторинг кода — это изменение внешнего вида кода, без изменения его поведения.
Перефразируя, когда мы проводим рефакторинг кода, мы НЕ изменяем поведение кода, мы затрагиваем только его внутреннюю составляющую.
Рефакторинг необходим, если ваш код:
Нечитаемый (самое главное!)
Непонятный
Дублируется
Метод или функция содержит слишком много строк
Нечистый (присутствует закомментированный код, присутствуют второстепенные комментарии)
Тяжело дебажить
Тяжело вносить изменения
Тяжело масштабировать приложение
Не соответствует принятому в команде код стайлу
А теперь давайте посмотрим на рефакторинг реального кода и разберём изменения строка за строкой.
Кода слишком много и в нём комментарии, код обычно self documented, с коментариями очень сложно читать. Например можно сделать описание,
код ниже получает данные пользователей с бека, добавляет данные в темплейты хтмл и отрисовывает их.
Пример до рефакторинга:
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>`;
}
}
Зачем и как проводить рефакторинг кода
Как видно из примера, поведение кода не изменилось, но его стало гораздо больше! Зачем вообще так увеличивать количество кода, зачем нам рефакторинг?
Давайте разберём результат детально и посмотрим, какие из пунктов выше мы смогли улучшить:
Рекомендуем публикацию по теме
Пример выше соответствует почти всем приведенным пунктам, поэтому мы провели его рефакторинг.
Что и как мы сделали:
Мы создали свойства класса users, usersContEl, в которых будем хранить данные.
Переименовали метод init в initUsersModule, что говорит о том, что этот метод инициирует модуль пользователей. Сам модуль теперь состоит из 4 строк и стал гораздо более читаемым. Мы понимаем с чего мы начинаем и чем заканчиваем.
- Мы создали новые методы класса.
Мы отдельно вынесли метод получения данных, 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).
Тесты — это наша гарантия того, что поведение кода в процессе рефакторинга не изменится.
Тема рефакторинга обширна и не поместится в рамки маленькой статьи. Но надеюсь, вы смогли познакомится с понятием рефакторинга, узнали, на что стоит обращать внимание, и что можно постараться не делать в процессе проектирования и написания кода.