Найти в Дзене
Сделай игру

SOLID: Как писать код, который не превратится в лапшу

SOLID — это не просто модное слово из мира программирования. Это набор принципов, которые помогают писать код, который: Если игнорировать SOLID, код быстро превращается в "лапшу", где всё зависит от всего, а любое изменение требует танцев с бубном. Давайте разберём каждый принцип на реальных примерах. Класс должен делать что-то одно. Если у него несколько задач — рано или поздно они начнут мешать друг другу. Изначально класс Basic даёт доступ к страницам 1, 2, 3 и для Visitor, и для Admin. Потом выясняется, что Visitor не должен заходить на страницу 3. Если просто изменить Basic, то и Admin потеряет доступ. Решение: вынести логику в отдельные классы. Что было бы без (S): Суть: код должен быть открыт для расширения, но закрыт для изменений. Пример: есть класс Drawer, который рисует квадрат: Плохой подход: переписать render() под новую задачу (например, маленький красный квадрат). Старый функционал сломается. Хороший подход: добавить новый метод, не трогая старый. Что было бы без (O): Су
Оглавление

SOLID — это не просто модное слово из мира программирования. Это набор принципов, которые помогают писать код, который:

  • Легко поддерживать (чтобы через месяц вы не проклинали себя за "гениальные" решения).
  • Гибко изменять (чтобы правка в одном месте не ломала пять других).
  • Проще тестировать (чтобы не приходилось молиться перед каждым запуском).

Если игнорировать SOLID, код быстро превращается в "лапшу", где всё зависит от всего, а любое изменение требует танцев с бубном. Давайте разберём каждый принцип на реальных примерах.

Кто не использует SOLID, к тому придёт это стренное трёхногое нечто! И укусит за бочок
Кто не использует SOLID, к тому придёт это стренное трёхногое нечто! И укусит за бочок

(S) Принцип единственной ответственности (Single Responsibility)

Класс должен делать что-то одно. Если у него несколько задач — рано или поздно они начнут мешать друг другу.

Изначально класс Basic даёт доступ к страницам 1, 2, 3 и для Visitor, и для Admin.

Начальное решение
Начальное решение

Потом выясняется, что Visitor не должен заходить на страницу 3. Если просто изменить Basic, то и Admin потеряет доступ.

Когда лекарство хуже болезни
Когда лекарство хуже болезни

Решение: вынести логику в отдельные классы.

Теперь всё работает как надо
Теперь всё работает как надо

Что было бы без (S):

  • Одна правка ломает логику для разных ролей.
  • При добавлении новых ролей придётся городить if-else внутри getAccess.

(O) Принцип открытости/закрытости (Open/Closed)

Суть: код должен быть открыт для расширения, но закрыт для изменений. Пример: есть класс Drawer, который рисует квадрат:

Да, без изысков
Да, без изысков

Плохой подход: переписать render() под новую задачу (например, маленький красный квадрат). Старый функционал сломается.

Красный квадрат заменил то, что есть
Красный квадрат заменил то, что есть

Хороший подход: добавить новый метод, не трогая старый.

Новый метод - новое решение, без слома изначального
Новый метод - новое решение, без слома изначального

Что было бы без (O):

  • Постоянные правки в одном и том же классе.
  • Риск сломать уже работающую логику.

(L) Принцип подстановки Барбары Лисков (Liskov Substitution)

Суть: наследник класса должен работать так же, как и родитель. Если NewClass наследует Base, то всё, что работало с Base, должно работать и с NewClass.

Изначальный рабочий вариант
Изначальный рабочий вариант

Проблема: если наследник (NewClass) начнёт возвращать строку, код сломается.

Изменили функцию первый раз - всё работает
Изменили функцию первый раз - всё работает
А вот этот вариант уже не будет работать
А вот этот вариант уже не будет работать

Что было бы без (L):

  • Неожиданные ошибки в местах, где "раньше работало".
  • Нарушение контракта между родителем и наследником.

(I) Принцип разделения интерфейсов (Interface Segregation)

Суть: клиенты не должны зависеть от методов, которые они не используют.

Не особо хорошее решение: независимые клиенты могут зависеть от общих данных
Не особо хорошее решение: независимые клиенты могут зависеть от общих данных

Решение: разделить логику так, чтобы каждый клиент работал со своим интерфейсом.

Функция простая, но это только для примера
Функция простая, но это только для примера

Что было бы без (I):

  • Изменения для user1 могут случайно задеть user0.
  • Сложно масштабировать (например, добавить user3).

(D) Принцип инверсии зависимостей (Dependency Inversion)

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

Пример. Прямая зависимость — Handler знает про конкретную реализацию DB.

Решение рабочее, но зависимости грозят трудностями
Решение рабочее, но зависимости грозят трудностями

Решение: зависимость от абстракции (AbstractDB). Тут, конечно, лучше использовать интерфейсы, но коль скоро в JS их нет - мы воспользуемся абстрактным классом. Не то же самое, но сгодится.

А вот это решение позволяет относительно быстро менять зависимости
А вот это решение позволяет относительно быстро менять зависимости

Что было бы без (D):

  • Невозможно подменить DB на другую СУБД без правок в Handler.
  • Тестирование требует реальной базы данных.

Заключение

SOLID — это не панацея, но вполне себе рабочий инструмент. Его преимущества:

✅ Уменьшает связанность кода (меньше "эффекта бабочки").

✅ Повышает читаемость (каждый класс делает что-то одно).

✅ Облегчает тестирование (можно "мокать" зависимости).

Но! Не стоит фанатично применять SOLID к каждой строчке кода. Иногда простой скрипт из 100 строк не стоит разбивать на 10 классов. Главное — понимать принципы и использовать их там, где они действительно упростят жизнь вам и вашей команде.

Как говорил дядя Боб (тот самый, который SOLID и предложил): "Хороший код — это не тот, который нельзя улучшить, а тот, который не нужно улучшать".