Ваш код работает — но это не значит, что он хорош
Вы написали скрипт. Он запускается. Клиенты довольны. Но проходит месяц, и вы с ужасом смотрите на ту самую функцию, которая разрослась до 200 строк. В ней перемешаны запросы к API, манипуляции с DOM и какая-то магия с датами. Знакомо?
Добро пожаловать в мир, где рефакторинг JavaScript кода — это не прихоть перфекциониста, а жизненная необходимость. Если вы хотите, чтобы ваш проект жил долго и не рассыпался при первой же правке, эта статья для вас.
Что такое рефакторинг и чем он не является
Начнем с главного. Рефакторинг JavaScript кода — это процесс изменения внутренней структуры программы без изменения её внешнего поведения. Вы не добавляете новые функции, не чините баги. Вы просто делаете код чище, понятнее и легче для поддержки.
Многие путают рефакторинг с переписыванием. Это разные вещи. Переписывание — это когда вы выбрасываете старый код и пишете новый с нуля. Рефакторинг — это эволюция, а не революция. Вы меняете код маленькими шагами, каждый раз проверяя, что ничего не сломалось.
Когда нужно проводить рефакторинг JavaScript?
Вот несколько верных признаков того, что ваш код кричит о помощи:
- Правило трёх. Если вы копируете один и тот же кусок кода в третий раз — пора выносить его в отдельную функцию.
- Функция-монстр. Функция длиннее 30–40 строк, которую невозможно прочитать за один взгляд.
- «Магические числа». В коде разбросаны числа вроде
86400000или42без комментариев и констант. - Глобальные переменные. Если вы боитесь изменить переменную, потому что не знаете, где ещё она используется.
- Тесты. Если вы боитесь вносить изменения, потому что нет автоматических тестов — это первый звоночек.
Но есть и ситуации, когда рефакторинг не нужен: перед дедлайном, в продакшене без тестов, или когда код написан на коленке для прототипа, который завтра выкинут.
Техники рефакторинга JavaScript: от простого к сложному
1. Извлечение функции (Extract Function)
Самая популярная и безопасная техника. Вы находите фрагмент кода, который можно сгруппировать, и выносите его в отдельную функцию.
// ДО рефакторинга
function calculateInvoice(items) {
let total = 0;
for (let item of items) {
total += item.price * item.quantity;
}
let discount = 0;
if (total > 1000) {
discount = total * 0.1;
}
return total - discount;
}
// ПОСЛЕ рефакторинга
function calculateSubtotal(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
function calculateDiscount(total) {
return total > 1000 ? total * 0.1 : 0;
}
function calculateInvoice(items) {
const subtotal = calculateSubtotal(items);
const discount = calculateDiscount(subtotal);
return subtotal - discount;
}
Что изменилось? Теперь каждая функция отвечает за одно действие. Вы можете переиспользовать calculateSubtotal в другом месте, а calculateDiscount легко протестировать отдельно.
2. Замена магических чисел константами
Никогда не используйте «голые» числа в коде. Через месяц вы сами не вспомните, что значит 86400000.
// ПЛОХО
if (Date.now() - lastLogin > 86400000) {
// запросить повторный вход
}
// ХОРОШО
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
const ONE_DAY_MS = 86400000; // или просто читаемая константа
if (Date.now() - lastLogin > ONE_DAY_MS) {
// запросить повторный вход
}
3. Упрощение условных конструкций
Сложные вложенные if-else — главный враг читаемости. Используйте ранний возврат (early return) и тернарные операторы.
// ДО
function getDiscount(price, isPremium) {
let discount;
if (isPremium) {
if (price > 500) {
discount = 0.2;
} else {
discount = 0.1;
}
} else {
discount = 0;
}
return price * discount;
}
// ПОСЛЕ
function getDiscount(price, isPremium) {
if (!isPremium) return 0;
const discount = price > 500 ? 0.2 : 0.1;
return price * discount;
}
Код стал короче на 40% и гораздо понятнее. Сразу видно: если пользователь не премиум — скидки нет.
4. Замена switch на объект (Replace Switch with Object)
Когда у вас много case, объект с функциями смотрится элегантнее и легче расширяется.
// ДО
function getStatusText(status) {
switch (status) {
case 'pending':
return 'Ожидает';
case 'approved':
return 'Одобрено';
case 'rejected':
return 'Отклонено';
default:
return 'Неизвестно';
}
}
// ПОСЛЕ
const statusMap = {
pending: 'Ожидает',
approved: 'Одобрено',
rejected: 'Отклонено',
};
function getStatusText(status) {
return statusMap[status] || 'Неизвестно';
}
5. Избавление от мутаций (Immutability)
Мутирующие функции — источник багов. Старайтесь возвращать новые объекты, а не изменять старые.
// ПЛОХО
function addItem(cart, item) {
cart.items.push(item);
return cart;
}
// ХОРОШО
function addItem(cart, item) {
return {
...cart,
items: [...cart.items, item],
};
}
Как проводить рефакторинг JavaScript без страха
Рефакторинг без тестов — это как ходить по минному полю с завязанными глазами. Вот пошаговый план безопасного рефакторинга:
- Напишите тесты. Если их нет — покройте критически важные участки кода юнит-тестами.
- Выберите одну маленькую задачу. Не пытайтесь рефакторить весь проект за раз. Начните с одной функции.
- Вносите изменения по одному. Изменили — запустили тесты. Зелёные — идём дальше.
- Используйте IDE. Современные редакторы (VS Code, WebStorm) умеют автоматически извлекать функции, переименовывать переменные с учётом контекста.
- Commit после каждого шага. Если что-то пошло не так — у вас есть точка отката.