Разработка

github/refactor

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

Оцените навык первым

SKILL.md

Перевод инструкции, которую получает агент при подключении навыка. Агент всегда использует оригинал.

Рефакторинг

Обзор

Улучшение структуры и читаемости кода без изменения внешнего поведения. Рефакторинг — это постепенная эволюция, а не революция. Используйте этот навык для улучшения существующего кода, а не для переписывания с нуля.

Когда использовать

Используйте этот навык, когда:

  • Код трудно понять или поддерживать
  • Функции/классы слишком большие
  • Нужно устранить запахи кода
  • Добавлять функции сложно из-за структуры кода
  • Пользователь просит «почистить этот код», «сделать рефакторинг», «улучшить это»

Принципы рефакторинга

Золотые правила

  1. Поведение сохраняется — Рефакторинг не меняет, что делает код, а только как он это делает
  2. Маленькие шаги — Делайте крошечные изменения, тестируйте после каждого
  3. Система контроля версий — ваш друг — Коммитьте до и после каждого безопасного состояния
  4. Тесты обязательны — Без тестов вы не рефакторите, а просто редактируете
  5. По одному изменению за раз — Не смешивайте рефакторинг с добавлением новых функций

Когда НЕ стоит рефакторить

- Код, который работает и больше не будет меняться (если не сломано — не чинить)
- Критичный продакшен-код без тестов (сначала добавьте тесты)
- Когда очень сжатые сроки
- «Просто так» — нужен чёткий повод

Распространённые запахи кода и их исправления

1. Длинный метод/функция

# ПЛОХО: функция на 200 строк, которая делает всё подряд
- async function processOrder(orderId) {
-   // 50 строк: получение заказа
-   // 30 строк: валидация заказа
-   // 40 строк: расчёт цены
-   // 30 строк: обновление инвентаря
-   // 20 строк: создание отгрузки
-   // 30 строк: отправка уведомлений
- }

# ХОРОШО: разбито на узкоспециализированные функции
+ async function processOrder(orderId) {
+   const order = await fetchOrder(orderId);
+   validateOrder(order);
+   const pricing = calculatePricing(order);
+   await updateInventory(order);
+   const shipment = await createShipment(order);
+   await sendNotifications(order, pricing, shipment);
+   return { order, pricing, shipment };
+ }

2. Дублирование кода

# ПЛОХО: одинаковая логика в нескольких местах
- function calculateUserDiscount(user) {
-   if (user.membership === 'gold') return user.total * 0.2;
-   if (user.membership === 'silver') return user.total * 0.1;
-   return 0;
- }
-
- function calculateOrderDiscount(order) {
-   if (order.user.membership === 'gold') return order.total * 0.2;
-   if (order.user.membership === 'silver') return order.total * 0.1;
-   return 0;
- }

# ХОРОШО: выделение общей логики
+ function getMembershipDiscountRate(membership) {
+   const rates = { gold: 0.2, silver: 0.1 };
+   return rates[membership] || 0;
+ }
+
+ function calculateUserDiscount(user) {
+   return user.total * getMembershipDiscountRate(user.membership);
+ }
+
+ function calculateOrderDiscount(order) {
+   return order.total * getMembershipDiscountRate(order.user.membership);
+ }

3. Большой класс/модуль

# ПЛОХО: god-объект, который знает слишком много
- class UserManager {
-   createUser() { /* ... */ }
-   updateUser() { /* ... */ }
-   deleteUser() { /* ... */ }
-   sendEmail() { /* ... */ }
-   generateReport() { /* ... */ }
-   handlePayment() { /* ... */ }
-   validateAddress() { /* ... */ }
-   // ещё 50 методов...
- }

# ХОРОШО: один класс — одна ответственность
+ class UserService {
+   create(data) { /* ... */ }
+   update(id, data) { /* ... */ }
+   delete(id) { /* ... */ }
+ }
+
+ class EmailService {
+   send(to, subject, body) { /* ... */ }
+ }
+
+ class ReportService {
+   generate(type, params) { /* ... */ }
+ }
+
+ class PaymentService {
+   process(amount, method) { /* ... */ }
+ }

4. Длинный список параметров

# ПЛОХО: слишком много параметров
- function createUser(email, password, name, age, address, city, country, phone) {
-   /* ... */
- }

# ХОРОШО: группировка связанных параметров
+ interface UserData {
+   email: string;
+   password: string;
+   name: string;
+   age?: number;
+   address?: Address;
+   phone?: string;
+ }
+
+ function createUser(data: UserData) {
+   /* ... */
+ }

# ЕЩЁ ЛУЧШЕ: использовать паттерн строителя для сложной инициализации
+ const user = UserBuilder
+   .email('test@example.com')
+   .password('secure123')
+   .name('Test User')
+   .address(address)
+   .build();

5. Зависть к функциям другого объекта (Feature Envy)

# ПЛОХО: метод использует данные другого объекта больше, чем свои
- class Order {
-   calculateDiscount(user) {
-     if (user.membershipLevel === 'gold') {
+       return this.total * 0.2;
+     }
+     if (user.accountAge > 365) {
+       return this.total * 0.1;
+     }
+     return 0;
+   }
+ }

# ХОРОШО: перенести логику в объект, владеющий данными
+ class User {
+   getDiscountRate(orderTotal) {
+     if (this.membershipLevel === 'gold') return 0.2;
+     if (this.accountAge > 365) return 0.1;
+     return 0;
+   }
+ }
+
+ class Order {
+   calculateDiscount(user) {
+     return this.total * user.getDiscountRate(this.total);
+   }
+ }

6. Примитивная одержимость

# ПЛОХО: использование примитивов для доменных концепций
- function sendEmail(to, subject, body) { /* ... */ }
- sendEmail('user@example.com', 'Hello', '...');

- function createPhone(country, number) {
-   return `${country}-${number}`;
- }

# ХОРОШО: использовать доменные типы
+ class Email {
+   private constructor(public readonly value: string) {
+     if (!Email.isValid(value)) throw new Error('Неверный email');
+   }
+   static create(value: string) { return new Email(value); }
+   static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }
+ }
+
+ class PhoneNumber {
+   constructor(
+     public readonly country: string,
+     public readonly number: string
+   ) {
+     if (!PhoneNumber.isValid(country, number)) throw new Error('Неверный телефон');
+   }
+   toString() { return `${this.country}-${this.number}`; }
+   static isValid(country: string, number: string) { /* ... */ }
+ }
+
+ // Использование
+ const email = Email.create('user@example.com');
+ const phone = new PhoneNumber('1', '555-1234');

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

# ПЛОХО: необъяснённые значения
- if (user.status === 2) { /* ... */ }
- const discount = total * 0.15;
- setTimeout(callback, 86400000);

# ХОРОШО: именованные константы
+ const UserStatus = {
+   ACTIVE: 1,
+   INACTIVE: 2,
+   SUSPENDED: 3
+ } as const;
+
+ const DISCOUNT_RATES = {
+   STANDARD: 0.1,
+   PREMIUM: 0.15,
+   VIP: 0.2
+ } as const;
+
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
+
+ if (user.status === UserStatus.INACTIVE) { /* ... */ }
+ const discount = total * DISCOUNT_RATES.PREMIUM;
+ setTimeout(callback, ONE_DAY_MS);

8. Вложенные условные конструкции

# ПЛОХО: код-стрела
- function process(order) {
-   if (order) {
-     if (order.user) {
-       if (order.user.isActive) {
-         if (order.total > 0) {
-           return processOrder(order);
+         } else {
+           return { error: 'Неверная сумма' };
+         }
+       } else {
+         return { error: 'Пользователь неактивен' };
+       }
+     } else {
+       return { error: 'Нет пользователя' };
+     }
+   } else {
+     return { error: 'Нет заказа' };
+   }
+ }

# ХОРОШО: защитные проверки / ранние выходы
+ function process(order) {
+   if (!order) return { error: 'Нет заказа' };
+   if (!order.user) return { error: 'Нет пользователя' };
+   if (!order.user.isActive) return { error: 'Пользователь неактивен' };
+   if (order.total <= 0) return { error: 'Неверная сумма' };
+   return processOrder(order);
+ }

# ЕЩЁ ЛУЧШЕ: использование типа Result
+ function process(order): Result<ProcessedOrder, Error> {
+   return Result.combine([
+     validateOrderExists(order),
+     validateUserExists(order),
+     validateUserActive(order.user),
+     validateOrderTotal(order)
+   ]).flatMap(() => processOrder(order));
+ }

9. Мёртвый код

# ПЛОХО: неиспользуемый код остаётся
- function oldImplementation() { /* ... */ }
- const DEPRECATED_VALUE = 5;
- import { unusedThing } from './somewhere';
- // Закомментированный код
- // function oldCode() { /* ... */ }

# ХОРОШО: удаляйте
+ // Удаляйте неиспользуемые функции, импорты и закомментированный код
+ // Если понадобится — история git сохранит

10. Неподходящая близость (Inappropriate Intimacy)

# ПЛОХО: один класс глубоко залезает в другой
- class OrderProcessor {
-   process(order) {
-     order.user.profile.address.street;  // Слишком интимно
-     order.repository.connection.config;  // Нарушение инкапсуляции
+   }
+ }

# ХОРОШО: спрашивайте, а не лезьте внутрь
+ class OrderProcessor {
+   process(order) {
+     order.getShippingAddress();  // Order знает, как получить адрес
+     order.save();  // Order знает, как сохранить себя
+   }
+ }

Рефакторинг с выделением метода

До и после

# До: одна длинная функция
- function printReport(users) {
-   console.log('ОТЧЁТ ПО ПОЛЬЗОВАТЕЛЯМ');
-   console.log('=====================');
-   console.log('');
-   console.log(`Всего пользователей: ${users.length}`);
-   console.log('');
-   console.log('АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ');
-   console.log('---------------------');
-   const active = users.filter(u => u.isActive);
-   active.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Активных: ${active.length}`);
-   console.log('');
-   console.log('НЕАКТИВНЫЕ ПОЛЬЗОВАТЕЛИ');
-   console.log('-----------------------');
-   const inactive = users.filter(u => !u.isActive);
-   inactive.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Неактивных: ${inactive.length}`);
- }

# После: выделены методы
+ function printReport(users) {
+   printHeader('ОТЧЁТ ПО ПОЛЬЗОВАТЕЛЯМ');
+   console.log(`Всего пользователей: ${users.length}\n`);
+   printUserSection('АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ', users.filter(u => u.isActive));
+   printUserSection('НЕАКТИВНЫЕ ПОЛЬЗОВАТЕЛИ', users.filter(u => !u.isActive));
+ }
+
+ function printHeader(title) {
+   const line = '='.repeat(title.length);
+   console.log(title);
+   console.log(line);
+   console.log('');
+ }
+
+ function printUserSection(title, users) {
+   console.log(title);
+   console.log('-'.repeat(title.length));
+   users.forEach(u => console.log(`- ${u.name} (${u.email})`));
+   console.log('');
+   console.log(`${title.split(' ')[0]}: ${users.length}`);
+   console.log('');
+ }

Введение типобезопасности

От не типизированного к типизированному

# До: без типов
- function calculateDiscount(user, total, membership, date) {
-   if (membership === 'gold' && date.getDay() === 5) {
-     return total * 0.25;
-   }
-   if (membership === 'gold') return total * 0.2;
-   return total * 0.1;
- }

# После: полная типобезопасность
+ type Membership = 'bronze' | 'silver' | 'gold';
+
+ interface User {
+   id: string;
+   name: string;
+   membership: Membership;
+ }
+
+ interface DiscountResult {
+   original: number;
+   discount: number;
+   final: number;
+   rate: number;
+ }
+
+ function calculateDiscount(
+   user: User,
+   total: number,
+   date: Date = new Date()
+ ): DiscountResult {
+   if (total < 0) throw new Error('Сумма не может быть отрицательной');
+
+   let rate = 0.1; // По умолчанию бронза
+
+   if (user.membership === 'gold' && date.getDay() === 5) {
+     rate = 0.25; // Пятничный бонус для золота
+   } else if (user.membership === 'gold') {
+     rate = 0.2;
+   } else if (user.membership === 'silver') {
+     rate = 0.15;
+   }
+
+   const discount = total * rate;
+
+   return {
+     original: total,
+     discount,
+     final: total - discount,
+     rate
+   };
+ }

Паттерны проектирования для рефакторинга

Паттерн Стратегия

# До: условная логика
- function calculateShipping(order, method) {
-   if (method === 'standard') {
-     return order.total > 50 ? 0 : 5.99;
-   } else if (method === 'express') {
-     return order.total > 100 ? 9.99 : 14.99;
+   } else if (method === 'overnight') {
+     return 29.99;
+   }
+ }

# После: паттерн стратегия
+ interface ShippingStrategy {
+   calculate(order: Order): number;
+ }
+
+ class StandardShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 50 ? 0 : 5.99;
+   }
+ }
+
+ class ExpressShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 100 ? 9.99 : 14.99;
+   }
+ }
+
+ class OvernightShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return 29.99;
+   }
+ }
+
+ function calculateShipping(order: Order, strategy: ShippingStrategy) {
+   return strategy.calculate(order);
+ }

Цепочка ответственности

# До: вложенная валидация
- function validate(user) {
-   const errors = [];
-   if (!user.email) errors.push('Требуется email');
+   else if (!isValidEmail(user.email)) errors.push('Неверный email');
+   if (!user.name) errors.push('Требуется имя');
+   if (user.age < 18) errors.push('Должно быть 18+');
+   if (user.country === 'blocked') errors.push('Страна не поддерживается');
+   return errors;
+ }

# После: цепочка ответственности
+ abstract class Validator {
+   abstract validate(user: User): string | null;
+   setNext(validator: Validator): Validator {
+     this.next = validator;
+     return validator;
+   }
+   validate(user: User): string | null {
+     const error = this.doValidate(user);
+     if (error) return error;
+     return this.next?.validate(user) ?? null;
+   }
+ }
+
+ class EmailRequiredValidator extends Validator {
+   doValidate(user: User) {
+     return !user.email ? 'Требуется email' : null;
+   }
+ }
+
+ class EmailFormatValidator extends Validator {
+   doValidate(user: User) {
+     return user.email && !isValidEmail(user.email) ? 'Неверный email' : null;
+   }
+ }
+
+ // Построение цепочки
+ const validator = new EmailRequiredValidator()
+   .setNext(new EmailFormatValidator())
+   .setNext(new NameRequiredValidator())
+   .setNext(new AgeValidator())
+   .setNext(new CountryValidator());

Шаги рефакторинга

Безопасный процесс рефакторинга

1. ПОДГОТОВКА
   - Убедитесь, что есть тесты (напишите, если нет)
   - Сделайте коммит текущего состояния
   - Создайте ветку для фичи

2. ИДЕНТИФИКАЦИЯ
   - Найдите запах кода, который нужно исправить
   - Поймите, что делает код
   - Спланируйте рефакторинг

3. РЕФАКТОРИНГ (маленькими шагами)
   - Сделайте одно небольшое изменение
   - Запустите тесты
   - Если тесты прошли — сделайте коммит
   - Повторяйте

4. ПРОВЕРКА
   - Все тесты проходят
   - При необходимости — ручное тестирование
   - Производительность не ухудшилась или улучшилась

5. ЗАВЕРШЕНИЕ
   - Обновите комментарии
   - Обновите документацию
   - Финальный коммит

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

Качество кода

  • [ ] Функции маленькие (< 50 строк)
  • [ ] Функции делают только одно
  • [ ] Нет дублирования кода
  • [ ] Описательные имена (переменные, функции, классы)
  • [ ] Нет магических чисел/строк
  • [ ] Удалён мёртвый код

Структура

  • [ ] Связанный код сгруппирован
  • [ ] Чёткие границы модулей
  • [ ] Зависимости идут в одном направлении
  • [ ] Нет циклических зависимостей

Типобезопасность

  • [ ] Типы определены для всех публичных API
  • [ ] Нет типов any без обоснования
  • [ ] Nullable-типы явно помечены

Тестирование

  • [ ] Рефакторенный код покрыт тестами
  • [ ] Тесты покрывают граничные случаи
  • [ ] Все тесты проходят

Распространённые операции рефакторинга

Операция Описание
Extract Method Выделение фрагмента кода в метод
Extract Class Перенос поведения в новый класс
Extract Interface Создание интерфейса из реализации
Inline Method Перенос тела метода обратно к вызывающему
Inline Class Перенос поведения класса к вызывающему
Pull Up Method Перенос метода в суперкласс
Push Down Method Перенос метода в подкласс
Rename Method/Variable Улучшение читаемости
Introduce Parameter Object Группировка связанных параметров
Replace Conditional with Polymorphism Использование полиморфизма вместо switch/if
Replace Magic Number with Constant Именованные константы
Decompose Conditional Разбиение сложных условий
Consolidate Conditional Объединение дублирующихся условий
Replace Nested Conditional with Guard Clauses Ранние выходы
Introduce Null Object Устранение проверок на null
Replace Type Code with Class/Enum Сильная типизация
Replace Inheritance with Delegation Композиция вместо наследования