Чому я не викладаю SOLID і «принцип усунення залежностей»

Чому я не викладаю SOLID

Якщо ви розмовляєте з кимось, кому небайдуже якість коду, вже досить скоро в розмові спливе SOLID — абревіатура, що допомагає розробникам запам'ятати п'ять важливих принципів об'єктно-орієнтованого програмування:

SOLID корисний. Його розробили знавці в нашій області. Він допомагає людям міркувати про дизайні. Допомагає створювати системи, стійкі до змін.

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

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

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

Якби мене попросили описати всі ці проблеми одним словом, я б вибрав слово незрозумілий. Розробники, які застосовують SOLID (в тому числі я), часто створюють незрозумілий код. Цей SOLID-код мають низький рівень зв'язності, його нескладно протестувати. Але він незрозумілий. І часто зовсім не настільки адаптується, як хотів би розробник.

Основна проблема в тому, що SOLID концентрується на залежностях. Кожний з принципів Відкритості / Закритості, Поділу інтерфейсу та Інверсії залежностей веде до використання великої кількості залежностей від абстракцій (інтерфейсу або абстрактного класу C#/Java). Принцип відкритості / закритості використовує абстракції для простоти розширення. Принцип поділу інтерфейсу сприяє створенню більш клієнто-орієнтованих абстракцій. Принцип інверсії залежностей каже, що залежності мають бути від абстракції, а не від конкретної реалізації.

В результаті все це призводить до того, що розробники починають створювати інтерфейси де попало. Вони захаращують свій код інтерфейсами типу IFooer, IDoer, IMooer, IPooer.

Навігація перетворюється в кошмар. Під час code review часто незрозуміло, яка саме частина коду запрацює. Але це нормально. Це ж SOLID. Це чудовий дизайн!

Щоб допомогти впоратися з цим божевіллям, ми впроваджуємо IoC-контейнер. А ще Mock-фреймвок для тестів. Якщо раніше все було не дуже зрозуміло, тепер стає незрозуміло остаточно. Тепер ви в самому прямому сенсі не можете знайти виклик конструктора в коді. Намагаєтеся розібратися у всьому цьому? Удачі! Але це нічого. Бо це ж SOLID. Це чудовий дизайн!

Раз це такий чудовий дизайн, чому ж він тоді такий незрозумілий? Хіба це чудовий дизайн, якщо розробники не можуть просто і ясно пояснити його? Мені так не здається. SOLID важливий і корисний. Але я не думаю, що розробники добре з ним справляються. Ось саме тому я більше не вчу використовувати SOLID.

За рамками SOLID: «Принцип усунення залежностей»



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

Ми отримуємо якісь дані через залежність? Передайте самі дані, але приберіть ПолучательДанных.
Ми передаємо що через залежність? Розгляньте модель з подіями і підпискою на них, замість передачі інтерфейсу. Або ж використовуйте Data Transfer Object, що описує стан залежності і реагуйте на його зміну (як, наприклад, у MVVM).

Об'єкти-значення
У більшості проектів виявляється занадто багато залежностей, тому що вони слабо використовують об'єкти-значення для опису своїх концепцій. Наступні кілька простих порад допоможуть створити ясний і зрозумілий код з помірною кількістю залежностей і абстракцій (підглянуті у статті «Take on the 4 Rules of Simple Design» J. B. Rainsberger):

  • Позбавтеся від Одержимості примітивами
  • Придумуючи назви використовуй іменники (і не використовуй віддієслівні іменники, що мають закінчення «er»)
  • Позбавляйтеся від дублювання


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

Тестопригодность в якості еталону
Давайте поглянемо на Принцип усунення залежностей з точки зору тестопригодности. Код з залежностями (навіть якщо ці залежності незначні) складніше протестувати, чим код без них. Ось рівні, які я розташував у порядку зростання від простого до складного (здається, я знайшов це в пості якогось іншого блогу, але, хоч убий, не можу його знайти):

  • Рівень 0: статистична функція без побічних ефектів
  • Рівень 1: Клас з незмінним станом. Наприклад, Об'єкти-Значення, що замінюють примітиви начебто EmailAddress або PhoneNumber
  • Рівень 2: клас із змінним станом, взаємодіє тільки з простими залежностями, такими як Об'єкт-Значення Рівня 1.
  • Рівень 3: клас із змінним станом, який взаємодіє з іншими змінними класами


Рівень 0 тестувати елементарно. Просто надсилайте різні вхідні дані у функцію і перевіряйте правильність результату.

Рівень 1 не занадто відрізняється від Рівня 0, крім того, що у вас є ще кілька методів, якими його можна протестувати і ще кілька конфігурацій, які теж треба перевірити. Рівень 1 хороший, тому що він змушує вас инкапсулировать ваші концепції Об'єкти-Значення.

Рівень 2 складніше, ніж Рівень 1, так як вам доведеться думати про внутрішній стан і тестувати різні випадки, в яких воно змінюється. Але іноді вам потрібен Рівень 2 з-за своїх переваг.

Рівень 3 тестувати складніше всього. Доведеться або використовувати Mock-об'єкти, або тестувати кілька модулів одночасно.

Я хочу зробити тестування як можна більш простим, тому я намагаюся використовувати як можна більш низький рівень, який задовольняє моїм вимогам: в основному я використовую код Рівня 0 і 1-го Рівня. Іноді Рівня 2 і рідко Рівня 3. Мій код стає дуже функціональним, однак користується перевагою об'єктно-орієнтованості при створенні Об'єктів-Значень для групування пов'язаної функціональності.

Повертаючись назад до SOLID
Припустимо, ми застосували Принцип усунення залежностей. Давайте проаналізуємо наскільки SOLID став цей код:

  • Принцип єдиною обов'язки: Ну, так! Масове використання Об'єктів-Значення. Незвичайно високе зчеплення.
  • Принцип відкритості /закритості: Так, але по-іншому. Відкритість в тому, що ми можемо як завгодно комбінувати Об'єкти-Значення, а не в тому, щоб скрізь впроваджувати залежно від абстракцій.
  • Принцип підстановки Барбари Лисков: він нам не важливий. Ми взагалі майже не використовуємо механізм успадкування.
  • Принцип поділу інтерфейсу: знову ж таки, не важливий. Ми майже не використовуємо інтерфейси.
  • Принцип інверсії залежностей: знову ж таки здебільшого не потрібен, так як ми усунули більшу частину залежностей. Залишилися залежності є Об'єктами-Значеннями, які варто розглядати як частину системи типів, а також можливе невелике кількість інтерфейсів, щоб спілкуватися із зовнішнім світом.


вся справа в Принципі єдиної обов'язки
Застосовуючи Принцип усунення залежностей, ви фокусуєтеся виключно на Принципі єдиної обов'язки. І ви отримуєте гнучкість Принципу відкритості / закритості, який веде до простоти адаптації під бізнес-вимоги. А ще ви можете забути про всі складнощі, який несуть з собою принципи підстановки Лисковой, Поділу інтерфейсів та Інверсії залежностей. Ми виграємо по всім статтям!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.