SOLID – шляпа чи нє?

Важко уявити собі хоча б одне інтервью розробника чи архітектора, де б не запитали про принципи SOLID. SOLID здається одним із стовпів сучасного ІТ. Навіть я, будучи великим критиком майже усього, часто посилаюся на принципи SOLID (правда, я їх для себе дещо перевизначив).

Ця публікація присвячена критиці SOLID, а ще більше критиці застосування SOLID. Ідея її написання виникла через те, що принципи SOLID із досить розмитих рекомендацій перетворилися на культ вантажу та стали самою метою, а не підходами до її досягнення.

SOLID

Акронім SOLID походить від перших букв назв п’яти принципів першопочатково ООП дизайну, які потім були перенесені на розробку ПЗ загалом. П’ять принципів:

  • S – Single Responsibility Principle (SRP)
  • O – Open/Closed Principle (OCP)
  • L – (Barbara) Liskov’s Substitution Principle (LSP)
  • I – Interface Segregation Principle (ISP)
  • D – Dependency Inversion Principle (DIP)

Поєднання цих принципів в акронім SOLID було виконане відомим розробником та консультантом Робертом Мартіном (Robert Martin), також відомим як Uncle Bob. Деякі з принципів були сформовані самим Робертом Мартіном, а деякі запозичені у колег. Також принципи SOLID формують фундамент для таких підходів (?) як Clean Code та Clean Architecture, що також були сформовані Робертом Мартіном та викладені у книгах із відповідними назвами.

Навчання принципам  SOLID – це один із інформаційних продуктів Роберта Мартіна, який заробляє гроші як консультант, а не як розробник. SOLID – це маркетингове слово, єдине призначення якого – це привернення уваги і продаж інформаційного продукту.

Деякі акроніми виникають самі собою, наприклад NASA чи DARPA, а деякі створюються спеціально, наприклад SOLID (solid – пер. з англ. грунтовний, фундаментальний, солідний). У випадках з останнім типом акронімів не рідко жертвують сенсом в угоду формі. SOLID виглядає крутіше за SOLIK чи SDLOI.

Single Responsibility Principle – принцип єдиної відповідальності

Принцип єдиної відповідальності Роберт Мартін також називає принципом єдиної причини для зміни, що має значно більше сенсу. А у своїй публікації про SRP Роберт Мартін натякає, що причиною для змін є та чи інша роль користувача (той чи інший steakholder):

However, as you think about this principle, remember that the reasons for change are people. It is people who request changes. And you don’t want to confuse those people, or yourself, by mixing together the code that many different people care about for different reasons.

Robert C. Martin (Uncle Bob), The Single Responsibility Principle

Мені дуже подобається приклад про автомобіль із тієї ж публікації і я його постійно цитую:

Imagine you took your car to a mechanic in order to fix a broken electric window. He calls you the next day saying it’s all fixed. When you pick up your car, you find the window works fine; but the car won’t start. It’s not likely you will return to that mechanic because he’s clearly an idiot.

That’s how customers and managers feel when we break things they care about that they did not ask us to change.

Robert C. Martin (Uncle Bob), The Single Responsibility Principle

Проблема тільки у тому, що цей приклад суперечить тому, що причиною для змін є люди, які чимось користуються. Взагалі в мене склалося враження, що Роберт Мартін – словоблуд, який просто бажає продавати свої інформаційні продукти. Детальніше про консультантів – словоблудів я написав у Консультанти брешуть.

Цікавий момент пов’язаний із Single Responsibility Principle трапився в мене нещодавно, коли я мав дискусію з колегами-архітекторами. Один із них сказав мені, що SRP знаходиться рівнем нижче за Bounded context. Мене це дещо здивувало, бо я ніколи не читав про таке і сам вважаю SRP універсальним принципом, що чудово масштабується. Ба більше, SRP та bounded context для мене це дві різні точки зору на одне й те саме. Можна сказати, що bounded context визначається за допомогою SRP.

Такий плюралізм думок щодо принципу єдиної відповідальності свідчить про те, що принцип сформований недостатньо формалізовано, а його межі (те, що мав на увазі Роберт Мартін) не зрозумілі нікому.

Врешті решт я створив власне трактування принципу SRP і відтепер маю намір використовувати власну назву для нього – Принцип застосування в єдиному випадку – Single Use Case Application – SUCA (чудовий акронім!).

За великим рахунком, SRP певним чином обмежує DRY. Лише одна-єдина одиниця обчислень (функція, процедура, об’єкт) має використовувати іншу одиницю обчислень. Інакше кажучи, кожна одиниця самохідної техніки повинна мати спеціально спроєктований під неї двигун, замість надскладного двигуна, який міг би задовільнити усіх. Двигуни можуть мати однаковий інтерфейс, але не зобов’язані бути адаптовані для усіх можливих застосувань.

Це занадто радикальний підхід, тому додамо до нашого розуміння цього принципу невеличке прагматичне послаблення: одиницею обчислень/певною деталлю можуть користуватися декілька користувачів, допоки вона їх влаштовує без будь-яких змін. Якщо ж необхідно внести бодай невеличкі зміни, краще скопіювати код деталі, внести зміни у копію та мати у проєкті дві дуже схожі деталі, кожна з яких розроблена під власного користувача.

Згодні – поширюйте цю публікацію з друзями. Не згодні – посперечайтеся зі мною у коментарях. Немає алгоритмів, що просувають мій блог через активність користувачів, тому мене дійсно цікавить Ваша думка.

Open/Closed Principle – Принцип відкритості до модифікації та закритості до змін

OCP, можливо, ще більш заплутаний за SRP. У своїй публікації The Open Closed Principle Роберт Мартін сам зізнається, що першопочатковий принцип з якого бере початок OCP застарів та пропонує більш зрозуміле пояснення (як і у випадку з SRP):

You should be able to extend the behavior of a system without having to modify that system.

Robert C. Martin, The Open Closed Principle

Далі у своїй публікації Роберт Мартін підводить нас до того, що OCP – це про архітектуру плагінів. Справа у тому, що архітектура плагінів досить складна, потребує визначення основної функціональності, що існує без плагінів, потребує більш складного управління та все одно потребує змін всередині себе, бо ніхто не зможе передбачити усі ін’єкції коду через т.з. хуки, усі необхідні в майбутньому події для підписок на них і таке інше. Крім того, OCP не завжди доречний. Є безліч додатків які не потребують плагінів.

Таким чином, OCP має певний масштаб (додаток чи доволі складний сервіс) на відміну від SRP і тому його не можна вважати універсальним принципом розробки. Open Closed Principle насамперед доданий у SOLID аби було SOLID, а не SLID.

Barbara Liskov’s Substitution Principle – принцип підстановки Барбари Ліскоу

LSP один із принципів, щодо яких в багатьох, на жаль, не виникає питань. LSP стосується підтипів та виставляє до них вимогу, власне, завдяки якій підтипи і називаютсья підтипами. Це наступна вимога: підтипи мають зберігати предикати власних надтипів. Наприклад, маємо тип Яблуко, який має підтип ЗеленеЯблоко, отже усі зелені яблука мають бути яблуками.

Якщо говорити про програмування, то розробники повинні мати можливість використовувати екземпляри класів, що спадкують від деяких базових класів в усіх місцях, де можуть використовуватися екземпляри базових класів. LSP не обмежується спадкуванням і класами, а стосується усіх можливих застосувань підтипів, у тому числі і використанням різноманітних обгорток, т.з. качиної типізації і т.п.

Загалом LSP покликаний виправити бідні системи типів, що не виконують необхідних перевірок та покращити ситуацію з використанням неявних підтипів, що, нажаль, ніяк не можливо позначити у коді, аби повідомити інших розробників про їх наявність.

Я вважаю принцип LSP дещо надмірним та недостатньо універсальним, оскільки не можу згадати реальних проблем, пов’язаних з підтипами, навіть у коді новачків. На практиці я бачив багато разів реалізацію пустих методів, аби об’єкти задовільняли інтерфейс об’єктів базового класу, що свідчить про поганий об’єктний дизайн та погану гранулярність, хоча вимогу LSP було задоволено.

Якщо інтерфейс порушено і код компілюється і працює, то все досить непогано, але ми маємо розуміти, що вимоги базового інтерфейсу у тому чи іншому випадку надмірні, тобто що ми маємо поганий інтерфейс/гранулярність. Якщо ми маємо справу з неявними підтипами, то від них ніхто і не очікує реалізації певних інтерфейсів/поведінки. Іншими словами, LSP фокусує увагу не на тому, що дійсно важливо.

Interface Segregation Principle – принцип розділення інтерфейсів

ISP – стосується згаданих вище проблем поганих інтерфейсів/поганої грануляції. Загалом з ISP неможливо посперечатися. Варто лише зазначити, що ISP – це інакше переформульований SRP. Найкраще до задоволення ISP, мені здається, підійшли у Golang, де пропагандують дві наступні практики:

  • Визначення інтерфейсу на стороні користувача
  • Використання інтерфейсів з одним єдиним методом

Dependency Inversion Principle – принцип інверсії залежностей

DIP – це найкорисніший з усіх SOLID принципів. DIP пропагандує низхідний (Top-Down) підхід до розробки та використання ACL – Anti Corruption Layer аби мати можливість перемикатися на висхідний (Bottom-Up) підхід, де це має сенс.

Для мене принцип DIP є основним, а SRP та ISP витікають із нього. Я б сказав, що DIP – це єдиний принцип SOLID, який дійсно вартий уваги.

Висновки

SOLID – це маркетингова лабуда для продажу інформаційних продуктів. Сукупність принципів SOLID існує лише заради красивого акроніму.

Корисно бути знайомим з принципами SOLID, тому що про них обов’язково запитають на будь-якій технічній співбесіді. Реально важливо використовувати лише принцип інверсії залежностей, який заради красивого акроніму було поставлено на останне місце.

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

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *