Programming

SOLID

SOLID — акроним из пяти принципов объектно-ориентированного проектирования: SRP, OCP, LSP, ISP и DIP, помогающих держать код пригодным к изменениям.

Что такое SOLID

SOLID (по-русски часто пишут «солид») — акроним из пяти принципов объектно-ориентированного проектирования. Принципы сформулировал Роберт Мартин (Uncle Bob) на рубеже 2000-х, а в запоминающийся акроним их сложил Майкл Фэзерс.

Задача у всех пяти одна: сделать код пригодным к изменениям. Не «правильным», не «красивым» — именно дешёвым в правке, когда через год придёт новое требование.

SOLID: расшифровка

Буква Принцип По-русски Суть в одной строке
S Single Responsibility Principle Единственная ответственность У класса одна причина для изменения
O Open/Closed Principle Открытость/закрытость Открыт для расширения, закрыт для изменения
L Liskov Substitution Principle Подстановка Лисков Наследник должен подходить вместо родителя
I Interface Segregation Principle Разделение интерфейсов Много узких интерфейсов лучше одного широкого
D Dependency Inversion Principle Инверсия зависимостей Зависеть от абстракций, а не от реализаций

S — Single Responsibility

Классическая формулировка «класс делает одну вещь» неточна. Точнее: у класса должна быть одна причина для изменения — он отвечает перед одной группой заказчиков.

# нарушение: три причины меняться — правила отчёта, формат вывода, способ доставки
class Report:
    def calculate(self) -> dict: ...
    def render_html(self) -> str: ...
    def send_email(self) -> None: ...

Правки бухгалтерии, вёрстки и почтовиков сталкиваются в одном файле. Разделите на ReportCalculator, HtmlRenderer, EmailSender — каждый меняется независимо.

O — Open/Closed

Новое поведение добавляется новым кодом, а не правкой работающего. Признак нарушения — растущий if/elif по типу:

# нарушение: каждый новый формат — правка функции
def export(data, fmt):
    if fmt == "csv": ...
    elif fmt == "json": ...
    elif fmt == "xlsx": ...   # и так до бесконечности

Лечится паттерном Strategy: интерфейс Exporter и по классу на формат. Подробнее — в паттернах проектирования.

L — Liskov Substitution

Если код работает с базовым типом, он обязан работать и с любым наследником — без проверок isinstance и сюрпризов. Хрестоматийный контрпример — Square extends Rectangle: у прямоугольника ширина и высота независимы, у квадрата нет, и код вида «поставь ширину 5, высоту 4, ожидай площадь 20» ломается.

Практические признаки нарушения: наследник бросает исключение там, где родитель работал; ужесточает предусловия; возвращает None вместо списка.

I — Interface Segregation

Клиента нельзя заставлять зависеть от методов, которыми он не пользуется. Интерфейс Worker с методами work(), eat() и sleep() заставит класс робота реализовывать eat() заглушкой. Разбейте на Workable, Feedable, Sleepable.

D — Dependency Inversion

Модули верхнего уровня не должны зависеть от нижнего — оба зависят от абстракций.

# нарушение: сервис намертво привязан к конкретной СУБД
class UserService:
    def __init__(self) -> None:
        self.db = PostgresClient(host="10.0.0.5")   # попробуйте это протестировать

# инверсия: зависимость приходит снаружи
class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self.repo = repo

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

Критика и здравый смысл

SOLID — эвристики, а не законы. Реальные претензии к ним стоит знать:

  • Формулировки размыты. «Одна ответственность» — понятие резиновое, два инженера проведут границу по-разному.
  • Легко переусердствовать. Буквальное следование SOLID рождает пятнадцать интерфейсов с одной реализацией каждый. Абстракция ради абстракции — это стоимость без выгоды.
  • Принципы про ООП. В функциональном коде часть из них тривиальна или бессмысленна: OCP решается передачей функции аргументом.

Разумное применение: заметили, что класс тяжело менять или невозможно протестировать, — ищите, какой принцип нарушен. Не наоборот.

Связанные термины

Готовы запустить GPU-задачу?

Запустить GPU-сервер