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

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

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

Начать курс

Введение: когда код начинает «пахнуть»

Каждый разработчик знает это чувство. Вы открываете файл, который писали три месяца назад, и ваше лицо вытягивается. Где-то в глубине души вы слышите шепот: «Это писал не я, это был какой-то новичок». Но зеркало заднего вида безжалостно: это были вы. Код работает, тесты проходят, но при каждом добавлении новой фичи вы тратите всё больше времени. Знакомо?

Это момент, когда пора задуматься о рефакторинге TypeScript кода. Не о переписывании всего с нуля (это путь в ад), а о хирургически точных улучшениях, которые делают код чище, понятнее и безопаснее. В этой статье мы разберем, когда стоит начинать рефакторинг, как это делать без боли и почему TypeScript дает вам суперсилы для этого процесса.

Когда нужно проводить рефакторинг TypeScript кода?

Существует миф, что рефакторинг — это отдельная задача в спринте. На практике лучший рефакторинг — это непрерывный процесс. Но есть четкие сигналы, которые кричат: «Стоп! Здесь нужен порядок!».

Правило трех (и еще двух)

Если вы скопировали один и тот же фрагмент кода три раза — вынесите его в функцию или класс. Если вы видите, что коллега скопировал ваш код — это четвертый раз. TypeScript с его строгой типизацией делает это правило особенно важным: дублирование типов ведет к рассинхронизации данных.

Симптомы «грязного» кода в TypeScript

  • Any-хаос: Если в вашем проекте any встречается чаще, чем string — это красный флаг. TypeScript теряет смысл.
  • Функции-монстры: Функция на 200+ строк, которая делает всё: валидирует, парсит, отправляет запрос и рисует интерфейс.
  • «Магические» числа и строки: if (status === 3) — что такое 3? Никто не знает, кроме автора, который уже уволился.
  • Нарушение принципа DRY: Одинаковые интерфейсы разбросаны по 10 файлам.
  • Тесты страшнее кода: Если для тестирования одной функции нужно поднять сервер, базу данных и запустить докер — вы свернули не туда.

Запомните главное правило: рефакторинг TypeScript кода нужно делать тогда, когда добавление новой фичи занимает больше времени, чем её написание с нуля. Это момент истины.

Базовые принципы: как не сломать продакшн

Перед тем как браться за рефакторинг, запомните три столпа, на которых стоит безопасный рефакторинг:

  1. Тесты — ваша страховка. Без покрытия тестами рефакторинг — это русская рулетка. Напишите хотя бы базовые юнит-тесты перед началом.
  2. Маленькие шаги. Не пытайтесь отрефакторить весь проект за ночь. Один модуль — один коммит.
  3. TypeScript компилятор — ваш друг. Используйте строгие настройки tsconfig.json: strict: true — это ваш детектор лжи.

Практические техники рефакторинга с примерами на TypeScript

Давайте перейдем к самому интересному — коду. Мы рассмотрим три реальные ситуации и покажем, как их улучшить.

1. Избавляемся от any: сила обобщений

Плохой пример. Функция принимает что угодно и возвращает что угодно. Это не TypeScript, это JavaScript с маской.

// ПЛОХО: any убивает типизацию
function getFirstElement(arr: any[]): any {
  if (arr.length === 0) {
    throw new Error('Array is empty');
  }
  return arr[0];
}

const num = getFirstElement([1, 2, 3]); // num: any
console.log(num.toUpperCase()); // Ошибка в рантайме, но компилятор молчит

Хороший пример. Используем дженерики. Код становится безопаснее, а IDE подсказывает типы.

// ХОРОШО: дженерики спасают
function getFirstElement<T>(arr: T[]): T {
  if (arr.length === 0) {
    throw new Error('Array is empty');
  }
  return arr[0];
}

const num = getFirstElement([1, 2, 3]); // num: number
// console.log(num.toUpperCase()); // Ошибка компиляции — мы в безопасности!

Это база, но именно с таких мелочей начинается рефакторинг кода TypeScript. Пройдитесь по проекту и замените any на дженерики или конкретные типы. Вы удивитесь, сколько багов отвалится само собой.

2. Убираем магические строки: Enums и Union Types

Представьте, что у вас есть функция, которая обрабатывает статусы заказа:

// ПЛОХО: магические строки
function processOrder(status: string): string {
  if (status === 'pending') {
    return 'Ожидает оплаты';
  } else if (status === 'shipped') {
    return 'Отправлен';
  } else if (status === 'delivered') {
    return 'Доставлен';
  }
  return 'Неизвестный статус';
}

Проблема: вы можете передать 'PENDING', 'pending ' или 'котик' — TypeScript не возразит. Рефакторинг:

// ХОРОШО: Union Type + Enum
type OrderStatus = 'pending' | 'shipped' | 'delivered';

const statusLabels: Record<OrderStatus, string> = {
  pending: 'Ожидает оплаты',
  shipped: 'Отправлен',
  delivered: 'Доставлен',
};

function processOrder(status: OrderStatus): string {
  return statusLabels[status] ?? 'Неизвестный статус';
}

// Теперь это вызовет ошибку компиляции:
// processOrder('котик'); // Error!

Чисто, безопасно, читаемо. Рефакторинг TypeScript в действии.

3. Упрощаем сложные условия: стратегия и полиморфизм

Иногда код превращается в простыню из if-else. Вот классический пример расчета скидки:

// ПЛОХО: лестница if-else
function calculateDiscount(userType: string, amount: number): number {
  if (userType === 'vip') {
    if (amount > 1000) {
      return amount * 0.2;
    }
    return amount * 0.1;
  } else if (userType === 'regular') {
    if (amount > 500) {
      return amount * 0.05;
    }
    return 0;
  } else if (userType === 'new') {
    return amount * 0.15;
  }
  return 0;
}

Рефакторим с помощью паттерна "Стратегия

Блоги

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