Рефакторинг к паттернам и оверинжиниринг
Паттерн — это не цель, а лекарство. А лекарство без болезни — это яд.
Рефакторинг к паттернам — введение паттерна как ответ на конкретную, уже проявившуюся боль в коде, а не «потому что так положено».
Изучив каталог паттернов, легко впасть в искушение применять их везде — это известный синдром «новенького с молотком, которому всё вокруг кажется гвоздём». Но паттерн — это компромисс: он добавляет гибкость в одной оси ценой лишних классов, косвенности и порога входа для того, кто будет читать код после вас. Хороший инженер вводит паттерн тогда, когда выигрыш от гибкости перевешивает цену сложности, — и ни мгновением раньше. Этот урок собирает воедино всё, о чём шла речь в разделе: SOLID подсказывает какой паттерн, а DRY/KISS/YAGNI — стоит ли вообще.
Зачем это на практике
Две крайности одинаково вредны, но проявляются по-разному. Недодизайн — это спагетти и копипаста, код, который страшно трогать, потому что любое изменение ветвится по всему файлу. Его видно сразу: он болит каждый день. Передизайн (оверинжиниринг) коварнее — это лес абстракций вокруг тривиальной задачи, где, чтобы понять «как добавить кнопку», надо пройти через пять интерфейсов, фабрику и конфиг. Он не болит в момент написания (автору даже приятно — «как солидно»), зато бьёт по каждому, кто придёт следом. Цель урока — научиться двигаться к паттерну от боли, а не от каталога, и так же спокойно откатывать лишнюю абстракцию назад.
Путь к паттерну: от боли к решению
Возьмём типичный сценарий. Сначала есть скромная функция расчёта цены доставки:
def shipping(method, weight):
if method == "standard":
return weight * 5
elif method == "express":
return weight * 5 + 100
return 0
print(shipping("express", 2))
Вывод:
110
Пока вариантов два, это нормальный код, и навешивать сюда паттерн — оверинжиниринг. Но проходит время: добавляются «ночная», «международная», «по подписке», у каждой свои правила и параметры, функция распухает до сотни строк elif, её страшно менять. Вот теперь появилась реальная боль — нарушение OCP, и она диктует лекарство: Strategy. Выносим каждый способ в свой класс с общим интерфейсом.
from abc import ABC, abstractmethod
class Shipping(ABC):
@abstractmethod
def cost(self, weight): ...
class Standard(Shipping):
def cost(self, weight):
return weight * 5
class Express(Shipping):
def cost(self, weight):
return weight * 5 + 100
def checkout(strategy: Shipping, weight):
return strategy.cost(weight)
print(checkout(Express(), 2))
Вывод:
110
Обратите внимание: поведение не изменилось (тот же результат 110), изменилась только структура. Это и есть рефакторинг. Теперь новый способ доставки — новый класс, существующий код не трогаем. Паттерн заработал, потому что его позвала боль, а не мода.
Где паттерн оправдан, а где лишний
| Паттерн оправдан, когда… | Паттерн лишний, когда… |
| точек изменения реально несколько и они растут | вариант один и второго не предвидится |
| ветвление по типу расползлось по коду | две ветки if, которые понятны с первого взгляда |
| нужна замена реализации в рантайме или на тестах | реализация всегда одна |
| гибкость снизит будущую стоимость изменений | гибкость нужна «на всякий случай» (YAGNI) |
Признаки оверинжиниринга
- Интерфейс с единственной реализацией, которую никто не собирается заменять, — абстракция без причины.
- Фабрика, создающая один тип объекта. Если выбора нет, фабрика — это просто длинный способ написать конструктор.
- Слои-проксёры, которые ничего не делают, кроме как передают вызов дальше.
- «Конфигурируемость» того, что никогда не конфигурируют — флаги и стратегии под один-единственный режим.
Хорошая проверка: спросите «какую конкретную боль снимает эта абстракция сегодня?». Если ответ начинается с «а вдруг в будущем…», это сигнал YAGNI.
Как это работает под капотом
За выбором «вводить паттерн или нет» стоит экономика изменений. У любой абстракции есть постоянная цена — её надо понять при чтении (когнитивная нагрузка) и сопровождать. И есть выгода — она снижает стоимость определённого типа будущих правок. Пока изменчивости нет, вы платите цену, не получая выгоды, — это чистый убыток. Когда изменчивость доказана (вы уже третий раз правите один и тот же switch), выгода превышает цену. Поэтому правильный момент для паттерна — не в начале «на чистом листе», а в точке, где боль уже измерима. Рефакторинг к паттернам всегда идёт под защитой тестов: они фиксируют, что внешнее поведение не поменялось, пока вы перекраиваете внутренности.
Частые ошибки
- «Резюме-ориентированное программирование». Внедрять паттерн, чтобы показать, что вы его знаете. Код существует для задачи, а не для демонстрации эрудиции.
- Паттерн вместо понимания проблемы. Накрыть паттерном плохо понятую задачу — значит закрепить непонимание в архитектуре.
- Архитектура «на вырост» с первого дня. Слои, шины, плагины под проект из трёх экранов — классический передизайн, нарушающий KISS и YAGNI.
- Рефакторинг без тестов и мелких шагов. Большая перестройка вслепую почти гарантированно ломает поведение.
- Боязнь упрощать. Оверинжиниринг лечится в обе стороны: лишний паттерн можно (и нужно) убрать, схлопнув абстракцию обратно в простую функцию.
Итоги
- Паттерн — лекарство от конкретной боли (обычно нарушения OCP/SRP), а не самоцель.
- Двигайся к паттерну «от боли»: сначала простой код, паттерн — когда изменчивость доказана.
- Рефакторинг сохраняет поведение и меняет структуру; делай его маленькими шагами под тестами.
- Оверинжиниринг — интерфейсы с одной реализацией, фабрики одного типа, гибкость «на всякий случай».
- Проверочный вопрос: какую боль снимает абстракция сегодня? «А вдруг в будущем» — это YAGNI.
- Упрощать так же ценно, как и обобщать: лишний паттерн не грех убрать.