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

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

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

Начать курс

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

Работа с устаревшим (legacy) кодом — неизбежная часть жизни любого разработчика, особенно в крупных проектах. Часто такой код написан на JavaScript без строгой типизации, изобилует "магическими" строками, непонятными функциями и отсутствием модульности. Переход на TypeScript — это не просто смена синтаксиса, а возможность значительно улучшить архитектуру, читаемость и поддерживаемость проекта. В этой статье мы разберем проверенную стратегию рефакторинга legacy кода на TypeScript, которая минимизирует риски и приносит максимальную пользу.

Почему TypeScript идеален для рефакторинга legacy кода?

TypeScript предоставляет уникальные возможности для безопасного рефакторинга:

  • Статическая типизация: Позволяет выявить ошибки на этапе компиляции, а не в рантайме.
  • Автоматическое документирование: Типы служат живой документацией, объясняющей, какие данные ожидает функция.
  • Улучшенная навигация и автодополнение: IDE (VS Code, WebStorm) понимает структуру кода, что ускоряет разработку.
  • Безопасные рефакторинги: Переименование переменных или изменение сигнатур функций происходит с уверенностью, что ничего не сломается.
  • Постепенное внедрение: TypeScript можно добавлять в проект файл за файлом, не переписывая всё сразу.

Шаг 1: Подготовка и настройка окружения

Прежде чем трогать код, необходимо подготовить инфраструктуру. Без неё рефакторинг превратится в хаос.

  1. Убедитесь, что у вас есть тесты. Это самое важное правило рефакторинга. Если тестов нет — напишите хотя бы интеграционные тесты для ключевых модулей. Без тестов вы рискуете сломать работающую функциональность.
  2. Настройте TypeScript. Установите typescript и создайте tsconfig.json.
  3. Включите строгие проверки. В tsconfig.json обязательно добавьте "strict": true. Это включает strictNullChecks, noImplicitAny и другие полезные опции.

Пример минимального tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}
  1. Настройте линтер и форматтер. ESLint с плагином для TypeScript (@typescript-eslint) и Prettier помогут поддерживать единый стиль кода.

Шаг 2: Постепенная миграция с JavaScript на TypeScript

Не пытайтесь переписать весь проект за один день. Используйте стратегию "файл за файлом".

Алгоритм действий:

  • Переименуйте файл legacyModule.js в legacyModule.ts.
  • Добавьте типы для экспортируемых и импортируемых функций.
  • Используйте any временно, если не уверены в типе. Пометьте такие места комментарием // TODO: fix type.
  • Запустите тесты и проверьте, что всё работает.

Пример: Предположим, у нас есть legacy функция на JavaScript:

// oldModule.js
function calculateDiscount(price, customerType) {
    if (customerType === 'vip') {
        return price * 0.8;
    }
    return price * 0.95;
}

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

// oldModule.ts
type CustomerType = 'regular' | 'vip';

function calculateDiscount(price: number, customerType: CustomerType): number {
    if (customerType === 'vip') {
        return price * 0.8;
    }
    return price * 0.95;
}

Тип CustomerType делает код самодокументируемым. Теперь любой разработчик точно знает, какие значения может принимать параметр customerType.

Шаг 3: Улучшение архитектуры и устранение "запахов" кода

Когда базовые типы расставлены, можно переходить к более глубокому рефакторингу. Обратите внимание на следующие "запахи" кода (code smells):

  • Дублирование кода: Выносите повторяющиеся участки в отдельные функции или классы.
  • Длинные функции: Разбивайте их на несколько маленьких, каждая из которых отвечает за одну задачу.
  • Магические числа и строки: Заменяйте их константами или перечислениями (enum).
  • Сильная связанность (tight coupling): Используйте интерфейсы и внедрение зависимостей (Dependency Injection).

Пример рефакторинга с использованием интерфейса:

До рефакторинга (legacy код):

function sendEmail(userData) {
    const transporter = nodemailer.createTransport({...});
    // ... логика отправки
}

После рефакторинга (с интерфейсом и DI):

interface IMailService {
    send(to: string, subject: string, body: string): Promise<void>;
}

class EmailService implements IMailService {
    async send(to: string, subject: string, body: string): Promise<void> {
        // Реализация с nodemailer
    }
}

class UserNotifier {
    constructor(private mailService: IMailService) {}

    async notifyUser(user: User): Promise<void> {
        await this.mailService.send(user.email, 'Welcome!', '...');
    }
}

Теперь UserNotifier не зависит от конкретной реализации EmailService. Вы можете легко заменить его на другой сервис (например, для тестов) или изменить логику отправки.

Шаг 4: Работа с "любыми" (any) и строгая типизация

После того как архитектура улучшена, приш

Блоги

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