1. Принципы SOLID на примерах рефакторинга реального кода
Принципы SOLID на примерах рефакторинга реального кода
Почему одни модули в легаси-проекте переписываются за час, а на другой уходит неделя? Ответ почти всегда один: нарушены принципы SOLID — пять базовых принципов объектно-ориентированного проектирования, сформулированных Робертом Мартином. Они не догма, а практический инструмент: если код им следует, рефакторинг превращается в локальную операцию. Если нет — любое изменение тянет за собой цепочку зависимостей.
Разберём каждый принцип на реальном коде из Django-проекта, который обрабатывает заказы в интернет-магазине.
Single Responsibility Principle: один класс — одна причина для изменения
Вот типичный «богоподобный» класс из легаси-кода:
Класс OrderProcessor меняется по четырём причинам: бизнес-логика скидок, формат email-сообщений, структура БД и формат PDF. Одно изменение в шаблоне письма ломает расчёт скидок. Решение — декомпозиция по ответственности:
Теперь изменение шаблона письма затрагивает только EmailNotifier. Каждый класс имеет одну причину для изменения — и это именно SRP.
Open/Closed Principle: открыты для расширения, закрыты для изменения
Допустим, в проекте есть функция расчёта доставки:
Каждый новый способ доставки требует модификации существующего кода. Нарушение OCP — принципа открытости/закрытости. Решение через полиморфизм:
Новый способ доставки — добавляем класс и запись в словарь. Существующий код не трогаем.
Liskov Substitution Principle: подклассы должны быть взаимозаменяемы
В Django-проекте часто встречается такой код:
Клиентский код, ожидающий JSON от любого экспортера, сломается при подстановке CsvExporter. Нарушение LSP — принципа подстановки Лисков. Контракт базового класса гласит: «возвращает строку с данными в формате JSON». Подкласс обязан соблюдать этот контракт.
Правильный подход — убрать жёсткий контракт формата из базового класса:
Теперь контракт базового класса — «возвращает строку», и оба подкласса его соблюдают. Клиентский код, работающий с BaseExporter, корректно обрабатывает любой формат.
Interface Segregation Principle: не заставляй клиента зависеть от того, что он не использует
Распространённая ошибка — один «толстый» интерфейс для всех:
Класс OneTimePaymentProcessor зависит от абстракции подписок, хотя никогда их не использует. Нарушение ISP — принципа разделения интерфейсов. Решение — разбить интерфейс на узкие:
Каждый класс реализует только те интерфейсы, которые ему действительно нужны.
Dependency Inversion Principle: зависеть от абстракций, а не от деталей
Финальный принцип — DIP. Вот типичный анти-паттерн в Django-вьюхах:
Вьюха жёстко привязана к SMTP и Stripe. Замена платёжного шлюза или способа отправки писем — переписывание вьюхи. Решение — инверсия зависимостей:
Теперь вьюха работает с абстракциями. Замена Stripe на ЮKassa — пишем новый класс YooKassaGateway(PaymentGateway) и подставляем его. Вьюха не меняется.
Как SOLID работает вместе
Эти пять принципов не изолированы — они усиливают друг друга. SRP даёт маленькие классы с чёткой зоной ответственности. OCP позволяет расширять поведение без правки существующего кода. LSP гарантирует, что полиморфизм работает predictably. ISP не даёт интерфейсам разрастаться. DIP обеспечивает слабую связность между модулями.
В легаси-коде SOLID нарушения почти всегда идут парами: богоподобный класс (нарушение SRP) содержит цепочку if/elif (нарушение OCP) и зависит от конкретных библиотек (нарушение DIP). Рефакторинг начинается с SRP — разбиваем класс на ответственности, затем применяем OCP через стратегии, затем выстраиваем зависимости через DIP. Этот порядок работает в 90% случаев.