Введение: когда код начинает «пахнуть»
Каждый разработчик знает это чувство. Вы открываете файл, который писали три месяца назад, и ваше лицо вытягивается. Где-то в глубине души вы слышите шепот: «Это писал не я, это был какой-то новичок». Но зеркало заднего вида безжалостно: это были вы. Код работает, тесты проходят, но при каждом добавлении новой фичи вы тратите всё больше времени. Знакомо?
Это момент, когда пора задуматься о рефакторинге TypeScript кода. Не о переписывании всего с нуля (это путь в ад), а о хирургически точных улучшениях, которые делают код чище, понятнее и безопаснее. В этой статье мы разберем, когда стоит начинать рефакторинг, как это делать без боли и почему TypeScript дает вам суперсилы для этого процесса.
Когда нужно проводить рефакторинг TypeScript кода?
Существует миф, что рефакторинг — это отдельная задача в спринте. На практике лучший рефакторинг — это непрерывный процесс. Но есть четкие сигналы, которые кричат: «Стоп! Здесь нужен порядок!».
Правило трех (и еще двух)
Если вы скопировали один и тот же фрагмент кода три раза — вынесите его в функцию или класс. Если вы видите, что коллега скопировал ваш код — это четвертый раз. TypeScript с его строгой типизацией делает это правило особенно важным: дублирование типов ведет к рассинхронизации данных.
Симптомы «грязного» кода в TypeScript
- Any-хаос: Если в вашем проекте
anyвстречается чаще, чемstring— это красный флаг. TypeScript теряет смысл. - Функции-монстры: Функция на 200+ строк, которая делает всё: валидирует, парсит, отправляет запрос и рисует интерфейс.
- «Магические» числа и строки:
if (status === 3)— что такое 3? Никто не знает, кроме автора, который уже уволился. - Нарушение принципа DRY: Одинаковые интерфейсы разбросаны по 10 файлам.
- Тесты страшнее кода: Если для тестирования одной функции нужно поднять сервер, базу данных и запустить докер — вы свернули не туда.
Запомните главное правило: рефакторинг TypeScript кода нужно делать тогда, когда добавление новой фичи занимает больше времени, чем её написание с нуля. Это момент истины.
Базовые принципы: как не сломать продакшн
Перед тем как браться за рефакторинг, запомните три столпа, на которых стоит безопасный рефакторинг:
- Тесты — ваша страховка. Без покрытия тестами рефакторинг — это русская рулетка. Напишите хотя бы базовые юнит-тесты перед началом.
- Маленькие шаги. Не пытайтесь отрефакторить весь проект за ночь. Один модуль — один коммит.
- 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;
}
Рефакторим с помощью паттерна "Стратегия