Рефакторинги, которыми вы действительно воспользуетесь

Что это такое

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

Почему это важно

Названные ходы превращают смутное намерение («здесь беспорядок») в последовательность конкретных шагов, которые можно выполнить в любой кодовой базе, на любом языке и без сеанса проектирования. Это ещё и словарь, которым люди пользуются на ревью: «выдели валидацию» точно говорит коллеге, что менять и насколько крупным будет изменение. Знание горстки приёмов, несущих основную тяжесть, позволяет улучшать код в десятиминутные промежутки, а не ждать тихой недели, которая так и не наступает.

Ключевые идеи

Выделяйте и встраивайте функции

Выделение берёт фрагмент, которому нужно имя, и превращает его в функцию с именем, говорящим, что она делает. Это самый полезный ход, и к нему тянутся, когда функцию трудно пересказать; обратный ход, встраивание, столь же законен — когда функция больше не оправдывает своё имя, возврат её внутрь убирает лишний переход без смысла.

// до
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);

// после
const total = calculateTotal(items);

Переименовывайте и переносите

Переименование ничего не стоит, когда инструменты честны, и окупается при каждом чтении. Перенос кода в модуль, которому принадлежит понятие, — та же идея в большем масштабе, и обычно это и есть лечение, когда зависимость смотрит не туда: код лежит не там, поэтому импорт, который должен быть разрешён, выглядит запрещённым.

invoice/pricing.ts      <- правило живёт вместе с понятием
orders/order.ts         <- импортирует его, а не владеет им

Заменяйте цепочки условий диспетчеризацией

Длинная цепочка условий, проверяющих одно и то же значение, — сигнал, что у вариации есть имя. Замена её поиском по таблице, небольшим отображением из случая в обработчик или полиморфным типом делает добавление следующего случая однострочным изменением в одном месте вместо правки посреди цепочки.

const rates = { retail: 1.2, wholesale: 1.07 };
const price = base * (rates[channel] ?? 1);

Введите объект-значение для связанных значений

Когда несколько параметров путешествуют вместе — сумма и её валюта, начало и конец, хост и порт, — они обычно хотят быть одной вещью. Объект-значение даёт группе имя, даёт валидации один дом и убирает риск того, что две части передадут в неверном порядке.

class DateRange {
  constructor(readonly start: Date, readonly end: Date) {
    if (end < start) throw new Error("end before start");
  }
}

Удаляйте и опирайтесь на тесты

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

grep по вызывающим -> никого -> удалить -> тесты всё ещё зелёные

Практика

Сделайте изменение локальным

Найдите функцию с цепочкой условий по одному значению. Замените цепочку таблицей или небольшой диспетчеризацией, затем добавьте новый случай и убедитесь, что он занял одну строку.

Готово, когда: новый случай требует одного добавления и правки существующих веток не нужны.

Удалите что-нибудь

Найдите параметр, ветку или экспортируемую функцию без вызывающих. Удалите её и прогоните весь набор тестов и проверку типов.

Готово, когда: ничего не проваливается, никто не импортирует удалённое имя, а файл стал короче.

Частые ошибки

  • Выделение фрагмента, используемого один раз и без имени — лишний переход без смысла, от которого вызывающий код труднее читать; исправить: выделяйте, когда у фрагмента есть имя, которое стоит произнести.
  • Переименование без обновления строковых ссылок — рефлексия, конфигурация и тесты хранят старое имя и падают во время выполнения; исправить: ищите по всему репозиторию, а не только по коду.
  • Замена условий таблицей диспетчеризации, скрывающей валидацию — неверный ввод молча проваливается в значение по умолчанию; исправить: сохраните явный случай отказа или возбуждайте ошибку на неизвестном ключе.
  • Перенос кода без исправления направления зависимостей — слоистость выглядит правильной, а циклы остаются; исправить: решите, какой модуль владеет понятием, и переносите вызовы.
  • Рефакторинг и добавление функции одним шагом — прогон тестов больше не различает их; исправить: сначала завершите структурное изменение, потом добавляйте поведение.

Что читать дальше