Мы используем файлы cookie. Чтобы улучшить работу сайта и предоставить вам больше возможностей. Продолжая использовать сайт, высоглашаетесь с условиями использования cookie.

SOLID в реальном мире: SRP без архитектурных космолетов

AGIMA26 марта 2026Артем Валевич·Teamlead PHP

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

Коротко

ОшибкаПрактичный подход
Проектирование на гипотетическое будущееДобавлять абстракции по подтверждённым требованиям
Интерфейс для каждого классаВыделять контракт только при нескольких реализациях или внешней границе
Один сервис для всех сценариевРазделять зоны изменений разных акторов
Слепое следование SRPСопоставлять SRP с KISS, DRY и YAGNI

Проверка решения по SRP

ВопросЕсли ответ «да»
Компонент меняется по запросу разных ролей?Разделите ответственности
Есть несколько реальных реализаций?Рассмотрите интерфейс
Абстракция упрощает тестирование границы?Зафиксируйте контракт
Изменение пока только предполагается?Не усложняйте код заранее

Проблема №1. Нарушение YAGNI

Одна из самых частых проблем, которые я вижу на практике — нарушение принципа YAGNI. Разработчики начинают проектировать систему так, будто заранее знают, как будет развиваться бизнес. Но в реальности этого не знает никто. Даже сам бизнес.

В результате появляются:

  • абстракции «на будущее»;
  • интерфейсы без реальных альтернативных реализаций;
  • архитектурные конструкции, которые никогда не используются.

Код становится сложнее, но не гибче.

Проблема №2. Охота за интерфейсами

Другая распространенная проблема — создание десятков интерфейсов в погоне за принципом Interface Segregation. Сам принцип отличный. Но иногда он превращается в соревнование:

кто создаст больше интерфейсов с одним методом.

Формально всё красиво. Практически — оверхед.

И это только часть проблем, остальные разберем в отдельных статьях.

Что на самом деле имели в виду авторы SOLID

Многие разработчики в целом неправильно понимают, какой смысл закладывали создатели SOLID. Возьмем самый известный принцип — SRP. Чаще всего его формулируют так:

У модуля должна быть одна и только одна причина для изменения.

Но сам Роберт Мартин, создатель методологии ООП, сформулировал его немного точнее:

Модуль должен быть ответственен перед одним и только одним актором.

И это важная деталь. SRP — это не про количество методов в классе и не про то, чтобы каждый класс делал «что-то одно». SRP — это про то, кто инициирует изменения.

Кейс: система отзывов

Рассмотрим простой продакшен-кейс. Есть продукт с несколькими каналами:

  • Сайт.
  • Мобильное приложение.
  • PWA.

Нужно реализовать систему отзывов:

  • Пользователь может оставить отзыв.
  • Отзывы можно посмотреть в админке.
  • Раз в неделю формируется отчет в XLSX и отправляется на почту.

На первый взгляд задача довольно простая. И вот здесь момент, когда многие разработчики достают из кобуры SOLID и начинают стрелять абстракциями во всё, что движется. Иногда даже в то, что не движется.

Как появляется архитектурный космолет

Очень часто разработчик начинает рассуждать примерно так:

  • А вдруг отзывы будут храниться не только в БД?
  • А вдруг их будут получать из внешнего сервиса?
  • А вдруг отчет будут отправлять не только на почту?
  • А вдруг появится еще пять каналов?

И тут начинается инженерная фантастика. В коде появляются примерно такие вещи:

php
interface FeedbackDataSource
{
 public function getFeedback(): array;
}
class DatabaseFeedbackSource implements FeedbackDataSource
{
 public function getFeedback(): array
 {
 // получение из БД
 }
}
interface FeedbackSender
{
 public function send(Report $report): void;
}
class EmailFeedbackSender implements FeedbackSender
{
 public function send(Report $report): void
 {
 // отправка письма
 }
}
interface FeedbackChannel
{
 public function collect(FeedbackDTO $dto): void;
}

Потом добавляются реализации:

  • WebsiteFeedbackChannel;
  • MobileAppFeedbackChannel;
  • PwaFeedbackChannel;

А затем появляются:

  • FeedbackRepositoryInterface;
  • FeedbackExporterInterface;
  • FeedbackProviderFactory;
  • AbstractFeedbackExporter.

И где-то на этом этапе простая фича превращается в архитектурный симулятор NASA. Самое интересное — ни одной из этих абстракций бизнес не просил. Мы просто начали защищаться от гипотетического будущего.

Схематично это может выглядеть вот так:

Избыточная архитектура системы отзывов с множеством интерфейсов, адаптеров и фабрик

В чем проблема такого подхода

На архитектурной диаграмме всё выглядит красиво: гибко и расширяемо. И соответствует SOLID. Но в реальности мы получаем:

  • десятки классов;
  • сложную навигацию по коду;
  • абстракции без реальных реализаций;
  • лишнюю когнитивную нагрузку для команды.

И самое главное: мы оптимизировали архитектуру под будущее, которое может никогда не наступить. Это прямое нарушение YAGNI.

Но и игнорировать SRP нельзя

Есть и другая крайность. Можно сказать: «Да ну этот SOLID. Сделаем один FeedbackService на 800 строк и поедем дальше». Это тоже плохая идея.

Чтобы принять разумное решение, нужно задать главный вопрос SRP:

кто является актором изменений?Точно не интерфейсы. Не абстракции. И не «вдруг когда-нибудь».

Кто акторы в нашем кейсе

В реальном продукте ситуация может выглядеть примерно так. Продакт сайта хочет показывать форму после покупки и собирать имейлы. Продакт мобильного приложения хочет показывать NPS после третьего запуска, а имейлы ему не нужны. Продакт PWA хочет максимально короткую форму Здесь важно заметить одну вещь. Каналы могут иметь разные правила сбора отзывов. А значит, изменения могут происходить независимо:

  • правила сайта могут поменяться;
  • правила мобильного приложения — нет;
  • PWA может вообще отключить сбор отзывов.

Вот здесь появляются разные акторы, а значит, и разные причины для изменений. И это уже сигнал для применения SRP.

Как может выглядеть разумная архитектура

Без космолета, но и без God Object.

php
class FeedbackService
{
 // ответственность: сохранение отзывов
 public function __construct(private FeedbackRepository $repository) {}
 public function submit(Feedback $feedback): void
 {
 $this->repository->save($feedback);
 }
}
class FeedbackRepository
{
 // ответственность: хранение отзывов
 // источник данных один — база данных
 public function save(Feedback $feedback): void
 {
 // запись в БД
 }
 public function getAll(): array
 {
 // получение отзывов
 }
}
class FeedbackReportService
{
 // ответственность: формирование и отправка отчёта
 public function __construct(private FeedbackRepository $repository,
 private ReportMailer $mailer) {}
 public function sendWeeklyReport(): void
 {
 $feedback = $this->repository->getAll();
 $report = FeedbackXlsxReport::generate($feedback);
 $this->mailer->send($report);
 }
}

Что мы сознательно не делаем

Мы не добавляем:

  • FeedbackDataSourceInterface;
  • FeedbackSenderInterface;
  • FeedbackStorageAdapter;
  • ExternalFeedbackProvider.

Мы не делаем этого, потому что отзывы хранятся в БД, отчет отправляется на имейл, а других требований нет (и скорее всего, не будет). Если когда-нибудь появится внешний сервис, добавить адаптер будет не сложно.

А пока код остается простым, понятным и поддерживаемым. И кроме того, закрывает все потребности бизнеса. Куда бизнес свернет через месяц, неизвестно никому. Поэтому предусмотреть всё заранее в любом случае не получится.

Схематично это может выглядеть так:

Упрощённая архитектура системы отзывов с отдельными зонами сбора, хранения и отчётности
Акторы системы отзывов и независимые причины изменения её компонентов

Вывод

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

  • есть разные причины для изменений;
  • есть разные акторы;
  • части системы могут меняться независимо.

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

А поддержка ненужного кода — занятие примерно такое же увлекательное, как чтение логов в пятницу вечером.

SOLIDSRPархитектура ПОPHPрефакторинг
Загружаем чат

Напишите в поддержку

Опишите проблему — команда разберётся.