Знаете это чувство, когда открываешь файл, который писал год назад? Сначала непонимание. Потом стыд. Потом хочется всё удалить и написать заново. Знакомо? Это нормально. Код устаревает, требования меняются, а мы учимся.
Рефакторинг javascript — это не про переписывание ради переписывания. Это про то, чтобы код работал лучше, читался легче, а баги находились до того, как их увидят пользователи. Давайте разберёмся, как это делать так, чтобы не создать новых проблем.
Рефакторинг кода — это простыми словами
Если совсем просто: вы меняете внутреннее устройство кода, не меняя его поведение. Как переставить мебель в комнате — стены те же, но жить стало удобнее.
Функция, которая раньше вычисляла скидку в 20 строк, теперь делает то же самое в 5. Багов не прибавилось, тесты проходят. Но читать её стало можно без аспирина.
На одном проекте мы унаследовали легаси-монстра. 800 строк в одном файле, функции по 200 строк, копипаст везде. Первые две недели я боялся трогать вообще что-либо. Потом понял: если не рефакторить, развитие остановится совсем. Начали с малого — выделили утилиты, покрыли тестами критичные места. Через полгода код был уже не страшно открывать.
Ключевое отличие рефакторинга от переписывания — маленькие шаги. Вы не сносите дом, вы меняете окна по одному. Каждый шаг — рабочий код. Это важно.
Когда код кричит о помощи: code smells
Прежде чем хвататься за клавиатуру, надо понять, что именно не так. Есть классические признаки — их называют code smells, «запахи кода». Не баги, но симптомы проблем.
Длинные функции. Если функция не помещается на один экран — что-то не так. Она делает слишком много. Разбивайте.
// До: функция на 80 строк, делает всё сразу
function processOrder(order) {
// валидация (15 строк)
// расчёт скидки (20 строк)
// работа с базой (15 строк)
// отправка email (10 строк)
// логирование (20 строк)
}
// После: каждая ответственность — своя функция
function processOrder(order) {
validateOrder(order);
const total = calculateTotal(order);
saveOrder(order, total);
sendConfirmation(order);
logOrder(order);
}
Дублирование. Скопировали кусок кода? Вы создали будущий баг. Измените одно место — забудете про второе. На моём опыте 80% багов в легаси — это когда поправили тут, а там забыли.
// До: скидка считается в трёх местах по-разному
function getRegularPrice(item) {
return item.price * 0.9; // скидка 10%
}
function getVipPrice(item) {
return item.price * 0.85; // скидка 15%
}
function getPromoPrice(item) {
return item.price * 0.8; // скидка 20%
}
// После: одна функция, понятная логика
function getPrice(item, discountRate) {
return item.price * (1 - discountRate);
}
Магические числа. Что такое 86400? Правильно, секунды в сутках. Но через месяц вы это забудете.
// До
if (user.lastLogin > Date.now() - 86400000) {
// ...
}
// После
const DAY_IN_MS = 24 * 60 * 60 * 1000;
if (user.lastLogin > Date.now() - DAY_IN_MS) {
// ...
}
Глубокая вложенность. Четыре уровня if — это уже сложно. Пять — мозг взрывается.
// До: пирамида смерти
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.hasPermission) {
if (user.subscription === 'premium') {
return grantAccess(user);
}
}
}
}
return null;
}
// После: ранний выход
function processUser(user) {
if (!user) return null;
if (!user.isActive) return null;
if (!user.hasPermission) return null;
if (user.subscription !== 'premium') return null;
return grantAccess(user);
}
Честно? Большинство команд живут с этими проблемами годами. Код работает, бизнес не жалуется. Но цена — замедление разработки. Новые фичи внедряются всё медленнее, баги находятся всё труднее.
Методы рефакторинга: что реально работает
Техник много. Назову те, которые использую чаще всего.
Извлечение функции. Самый частый приём. Видите кусок кода с комментарием? Это готовая функция. Комментарий станет названием.
// До: комментарий объясняет, что делает код
function renderPage(data) {
// фильтруем неактивные элементы
const active = data.filter(item => item.status === 'active');
// сортируем по дате
active.sort((a, b) => b.createdAt - a.createdAt);
// показываем только первые 10
return active.slice(0, 10);
}
// После: код сам объясняет себя
function renderPage(data) {
const recentActiveItems = getRecentActiveItems(data);
return recentActiveItems;
}
function getRecentActiveItems(data) {
return data
.filter(item => item.status === 'active')
.sort((a, b) => b.createdAt - a.createdAt)
.slice(0, 10);
}
Переименование. Звучит тривиально. Но правильное имя переменной или функции — это полдела. data — плохо. activeUsers — хорошо. x — ужасно. currentUserIndex — отлично.
Замена условного оператора полиморфизмом. Если switch или куча if проверяют тип объекта — это кандидат на полиморфизм.
// До: switch растёт с каждым новым типом
function getShapeArea(shape) {
switch (shape.type) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return 0.5 * shape.base * shape.height;
default:
return 0;
}
}
// После: каждый объект сам знает свою площадь
const shapes = {
circle: (r) => Math.PI * r ** 2,
rectangle: (w, h) => w * h,
triangle: (b, h) => 0.5 * b * h,
};
function getShapeArea(shape) {
const calculator = shapes[shape.type];
return calculator ? calculator(shape) : 0;
}
Ну вот смотрите — стало чище, да? И добавлять новые типы теперь проще.
Как не сломать всё при рефакторинге
Главная ошибка — рефакторить без тестов. Это как менять двигатель на ходу. Может получится. Скорее нет.
План такой:
Во-первых, тесты. Хотя бы на критичный функционал. Если тестов нет — напишите их перед рефакторингом. Да, это скучно. Да, это обязательно.
// Пример теста перед рефакторингом
describe('calculateDiscount', () => {
it('returns 10% for regular users', () => {
expect(calculateDiscount('regular')).toBe(0.1);
});
it('returns 20% for VIP users', () => {
expect(calculateDiscount('vip')).toBe(0.2);
});
});
Во-вторых, маленькие изменения. Один тип рефакторинга за раз. Переименовали — закоммитили. Вынесли функцию — закоммитили. Каждое изменение должно быть атомарным.
В-третьих, не смешивать. Рефакторинг отдельно, новые фичи отдельно. Если начали рефакторить — не добавляйте новый функционал. Это соблазн, который ведёт к боли.
Короче, правило простое: после каждого шага код должен работать. Если сломалось — вы сделали слишком большой шаг.
Инструменты помогают. ESLint найдет неиспользуемые переменные. Prettier приведёт код к единому стилю. Но главное — автоматический code review. Я сейчас использую Distiq для этих задач — бот проверяет каждый MR и подсвечивает проблемы до того, как они попадут в основную ветку. Экономит время на ревью и ловит типичные ошибки.
Что в итоге
Рефакторинг кода javascript — это не разовая акция. Это привычка. Регулярная гигиена, как чистка зубов. Пропустили пару раз — запахло. Запустили — уже больно.
Начните с малого. Самый неприятный файл. Самая запутанная функция. Один шаг за раз. Через месяц код станет другим. Через полгода — вы его узнаете.
А если хотите, чтобы кто-то подсказывал, что именно стоит поправить — попробуйте Distiq. Это российский сервис AI code review, который интегрируется в GitLab, GitHub и GitVerse. Бот смотрит каждый MR и оставляет комментарии — где дублирование, где можно упростить, где потенциальный баг. Бесплатно работает с открытыми репозиториями, для приватных есть тарифы. Настраивается за пару минут, а польза — постоянная.
