Урок по треку Архитектура фронтенда 7 мин

SOLID и принципы проектирования на фронтенде

SOLID, DRY, KISS, YAGNI, связность и зацепление на примерах фронтенд-кода.

Принципы проектирования нужны не ради красивых слов, а чтобы код было дёшево менять. На собесе их почти всегда спрашивают через пример: «покажи, где в React нарушается SRP» или «как применить DIP во фронтенде». Разберём каждый так, чтобы ты мог привести свой пример.

SOLID

S: Single Responsibility (единственная ответственность). У модуля одна причина для изменения. Формулировка Роберта Мартина: модуль отвечает перед одним «актором», то есть одной группой людей, которые просят изменений.

// Плохо: компонент и грузит данные, и считает скидки, и рисует
function Cart() {
  const [items, setItems] = useState([]);
  useEffect(() => { fetch('/api/cart').then(r => r.json()).then(setItems); }, []);
  const total = items.reduce((s, i) => s + i.price * i.qty * (i.promo ? 0.9 : 1), 0);
  return <div>{/* разметка */}{total}</div>;
}

// Лучше: загрузка в хуке, бизнес-правило в чистой функции, компонент только рисует
const total = calcCartTotal(items);      // меняется, когда меняются правила скидок
const { data: items } = useCart();       // меняется, когда меняется API

O: Open/Closed (открыт для расширения, закрыт для изменения). Новое поведение добавляем новым кодом, а не правкой старого. Во фронтенде это композиция, слоты, children, render props, словари вместо switch.

// Каждый новый тип уведомления = новая запись, функция не меняется
const renderers: Record<NoticeType, (n: Notice) => ReactNode> = {
  info: (n) => <Info {...n} />,
  error: (n) => <Error {...n} />,
};

L: Liskov Substitution (подстановка Барбары Лисков). Наследник или реализация интерфейса должна работать везде, где ожидают базовый тип, без сюрпризов. Пример нарушения: свой <Input> принимает onChange, но вызывает его со строкой, а не с событием, как ожидает код, написанный под нативный input.

I: Interface Segregation (разделение интерфейсов). Не заставляй клиента зависеть от того, чем он не пользуется. Во фронтенде: компонент получает только нужные пропсы, а не весь объект user из стора. Меньше пропсов, меньше лишних ререндеров и проще тесты.

D: Dependency Inversion (инверсия зависимостей). Модули верхнего уровня зависят от абстракций, а не от деталей. Компонент не импортирует axios напрямую, а получает API-клиент или хук. Это упрощает тесты (подменяешь реализацию) и замену транспорта.

interface AnalyticsPort { track(event: string, props?: object): void }
// В приложении передаём реализацию для Яндекс Метрики, в тестах заглушку

Важно: DIP (принцип) и DI (внедрение зависимостей, техника) не одно и то же. DI один из способов соблюдать DIP. В React роль контейнера часто играет Context.

Другие принципы

  • DRY (Don't Repeat Yourself): не дублируй знание. Два похожих куска кода, которые меняются по разным причинам, лучше оставить раздельными. Преждевременная абстракция хуже дубля.
  • KISS: самое простое решение, которое работает.
  • YAGNI: не делай то, что «может понадобиться». Гибкость стоит денег на поддержку.
  • Связность и зацепление (cohesion и coupling): внутри модуля всё про одно (высокая связность), между модулями мало зависимостей (низкое зацепление).
  • Композиция вместо наследования: в React наследование компонентов почти не используют, поведение собирают из хуков и вложенных компонентов.
  • Закон Деметры: «говори только с ближайшими друзьями». order.customer.address.city в компоненте означает, что компонент знает слишком много о структуре данных.

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

  • Применять SOLID механически: класс на каждую строчку, интерфейс с одной реализацией «на всякий случай». Это нарушает KISS и YAGNI.
  • Путать SRP с «функция делает одну вещь». SRP про причины изменений, а не про размер.
  • Выносить в общий хелпер всё, что похоже, и потом обвешивать его флагами isAdmin, isMobile, variant. Это и есть неправильный DRY.

Что спросят на собесе

  • Расшифруй SOLID и приведи пример каждого принципа из фронтенда.
  • Чем DIP отличается от DI и как это выглядит в React?
  • Когда дублирование лучше абстракции?
  • Что такое связность и зацепление, как их оценить в коде?
  • Как бы ты разбил компонент на 600 строк?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.