Рефакторинг legacy кода на JavaScript: пошаговое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Рефакторинг legacy кода на JavaScript: как превратить хаос в порядок

Работа с legacy кодом — неизбежная часть жизни разработчика. Рано или поздно каждый сталкивается с проектом, написанным на устаревшем JavaScript, где переменные называются a1, temp или data, а логика погребена под сотнями строк в одном файле. Рефакторинг такого кода не просто улучшает читаемость — он снижает стоимость поддержки, ускоряет добавление новых функций и уменьшает количество багов.

В этой статье мы разберем, как подходить к рефакторингу legacy JavaScript кода в среде Node.js, какие инструменты использовать и как не сломать работающую систему.

Почему legacy код опасен и почему его нужно рефакторить

Legacy код (от англ. legacy — наследие) — это код, который уже работает в продакшене, но написан с нарушением современных стандартов, без тестов и с плохой архитектурой. Основные проблемы такого кода:

  • Сложность понимания. Отсутствие модульности, длинные функции, магические числа.
  • Высокая связность. Изменение одной части ломает другую, не связанную на первый взгляд.
  • Отсутствие тестов. Любое изменение — риск.
  • Устаревшие паттерны. Использование var вместо const/let, callback hell, отсутствие async/await.

Рефакторинг — это не переписывание всего с нуля. Это постепенное улучшение кода без изменения его внешнего поведения. Цель — сделать код чище, безопаснее и легче для поддержки.

Стратегия рефакторинга: с чего начать

Перед тем как менять код, нужно разработать план. Хаотичные правки только усугубят ситуацию. Вот проверенная стратегия:

  1. Задокументируйте текущее поведение. Запишите, что делает каждая функция, какие входные данные ожидает и что возвращает.
  2. Напишите тесты. Даже если нет юнит-тестов, напишите интеграционные тесты, которые проверяют ключевые сценарии. Используйте jest или mocha.
  3. Изолируйте изменения. Работайте с одним модулем или функцией за раз. Не пытайтесь рефакторить всё сразу.
  4. Применяйте малые шаги. Одно изменение — один коммит. Если что-то сломалось, легко откатиться.

Пример: у вас есть функция, которая обрабатывает данные из файла и сохраняет результат. Сначала напишите тест, который проверяет, что при входном файле A получается выходной файл B. Затем рефакторьте внутренности.

Типичные проблемы legacy JavaScript и их решения

1. Callback hell → async/await

Одна из самых частых проблем в старом Node.js коде — вложенные callback'и. Они делают код нечитаемым и трудноотлаживаемым.

Плохой код:

function getUserData(userId, callback) {
    db.query('SELECT * FROM users WHERE id = ?', [userId], function(err, user) {
        if (err) return callback(err);
        db.query('SELECT * FROM orders WHERE user_id = ?', [user.id], function(err, orders) {
            if (err) return callback(err);
            callback(null, { user, orders });
        });
    });
}

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

async function getUserData(userId) {
    const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
    const orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [user.id]);
    return { user, orders };
}

Код стал короче, понятнее и легче для отладки. Ошибки обрабатываются через try/catch.

2. Глобальные переменные и мутация

В legacy коде часто встречаются глобальные переменные, которые изменяются в разных частях программы. Это приводит к неожиданным побочным эффектам.

Плохой код:

let config = { port: 3000 };

function setPort(newPort) {
    config.port = newPort;
}

function startServer() {
    console.log('Starting on port', config.port);
}

После рефакторинга — используем замыкания или классы:

function createConfig(initial) {
    let config = { ...initial };
    return {
        get(key) { return config[key]; },
        set(key, value) { config[key] = value; },
        startServer() {
            console.log('Starting on port', config.port);
        }
    };
}

const appConfig = createConfig({ port: 3000 });
appConfig.startServer();

Теперь состояние изолировано, и мутация контролируется через методы.

3. Магические числа и строки

Числа и строки, разбросанные по коду без пояснений, — классика legacy. Их нужно выносить в константы.

Плохой код:

if (status === 3) {
    sendEmail();
}

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

const STATUS = {
    ACTIVE: 1,
    PENDING: 2,
    CANCELLED: 3
};

if (status === STATUS.CANCELLED) {
    sendEmail();
}

Теперь понятно, что за статус проверяется. Если значение изменится, правка будет в одном месте.

4. Длинные функции и дублирование

Функции на 200+ строк, которые делают всё сразу, — верный признак необходимости рефакторинга. Разбивайте их на мелкие, каждая из которых отвечает за одно действие.

Плохой код:

function processOrder(order) {
    // 50 строк валидации
    // 30 строк расчета скидки
    // 40 строк отправки email
    // 20 строк обновления БД
}

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

function validateOrder(order) { /* ... */ }
function calculateDiscount(order) { /* ... */ }
function sendOrderEmail(order) { /* ... */ }
function updateDatabase(order) { /* ... */ }

async function processOrder(order) {
    validateOrder(order);
    const discount = calculateDiscount(order);
    await sendOrderEmail(order);
    await updateDatabase(order);
}

Каждая функция теперь легко тестируется и переиспользуется.

Блоги

Рекомендаций по книгам