Ви вивчили SOLID, але архітектура все одно кульгає? Чому GRASP важливіший за GoF (і як їх поєднати)

Привіт. Мене звати Сергій Немчинський, я програміст з досвідом більше 20 років, а ще засновник та власник школи ІТ-професій FoxmindEd.

У 2025 році я написав цикл статей про принципи SOLID, яких має дотримуватись кожен поважаючий себе програміст. Отримав неабиякий фідбек, в тому числі і критику. Мовляв, джуни навчаться того SOLID-у, а на реальних проєктах все одно працювати не вміють. До біса той SOLID.

Не можу не погодитися з першою частиною звинувачень: так, ситуація знайома. Приходить на співбесіду junior або early middle, може без проблем перелічити GoF-патерни і навіть пояснити, чим Factory відрізняється від Abstract Factory. Але впевненість миттєво зникає, коли доходить до реальних задач.

Проблема тут не в SOLID, а у відсутності архітектурного мислення. Що це таке, де його беруть і до чого тут патерни GRASP — ось про це і буде ця стаття.

Чому знання принципів SOLID та GoF-патернів не рятує

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

«Юний падаван, оце п’ять принципів SOLID, а це класичні патерни проєктування GoF, їх всього 23, вивчи їх і най буде з тобою сила»

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

Такий джун відкриває практичну задачу і звично думає:

«Який патерн тут можна використати?»

Тобто правило стає самоціллю. Це дуже тонкий, але критичний зсув мислення. Рішення починає підганятися під шаблон, а не під реальну задачу.

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

А як має думати розробник? Ну приблизно ось так:

  • Яку відповідальність має взяти на себе цей клас?
  • Яку конкретну проблему він вирішує в живому коді?
  • Де саме створювати обʼєкти?
  • Куди «покласти» нову поведінку, щоб завтра код не розсипався?

Якщо ви бачили мій YouTube-канал, вам знайома моя позиція: в програмуванні процес написання коду займає не більше 30% часу. А весь інший час — розмірковування та планування. Але це я такий розумний після 20 років досвіду, а що з джунами?

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

Саме про цю прогалину й піде мова далі.

Що таке GRASP і чому про них майже не говорять

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

І саме тут зʼявляються GRASP — не як альтернатива GoF, а як фундамент під ними. GRASP розшифровується як General Responsibility Assignment Software Patterns. Назва звучить складно, але суть у них дуже приземлена. Це не патерни в тому сенсі, в якому ми звикли говорити про Singleton чи Strategy. Тут немає готових класів, діаграм чи шаблонного коду.

GRASP — це принципи розподілу відповідальності. Вони відповідають на базові, але критично важливі питання:

  • який клас має відповідати за цю логіку?
  • хто створює обʼєкти?
  • де точка входу в сценарій?
  • де закінчується відповідальність одного обʼєкта і починається іншого?

Власне, GRASP — це про те, як думати перед тим, як писати код. Але вони вимагають думати, їх не можна просто загуглити і вставити шматок коду. До того ж у них майже немає UML-діаграм, їх складно показати на слайді або в туторіалі. Ось і виходить, що про GRASP знають значно менше, ніж про GoF: вони менш маркетингові.

У результаті GRASP часто пролітають повз навчальні курси і статті, хоча саме вони найбільше впливають на якість архітектури.

Чому відсутність GRASP — велика проблема

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

Саме з цього народжується такий феномен, як «божественні» сервіси. Так називають клас, який робить занадто багато, вирішує в системі все, наче він бог. Зазвичай виростає органічно, через відсутність чіткого розуміння відповідальності.

Спочатку є нормальний маленький сервіс. Потім у проєкті з’являється нова задача, і хтось думає: «О, тут уже є сервіс, давай просто додамо ще один метод». Потім ще один.Через пів року цей клас знає весь бізнес, усі репозиторії, всі інтеграції й половину домену. Формально код працює. Архітектурно — це катастрофа.

По-перше, такий сервіс неможливо нормально тестувати. Щоб протестувати один сценарій, треба підняти пів системи або написати купу моків.

По-друге, будь-яка зміна бізнес-логіки ламає його з усіх боків. Клас має десятки причин для зміни, тому кожен коміт стає ризикованим.

По-третє, такий сервіс стає точкою знання про всю систему. Новий розробник відкриває цей файл і бачить 800 рядків логіки, яка не має чіткої структури.

З точки зору GRASP «божественний» сервіс порушує одразу кілька принципів:
— Creator: він створює обʼєкти, якими не володіє;
— Controller: він одночасно і точка входу, і виконавець;
— High Cohesion: у ньому намішано все підряд;
— Low Coupling: він залежить від усього і всі залежать від нього.

Це не єдина можлива проблема, ще можливі і контролери по п’ятсот рядків, і класи-фабрики, які створені просто тому, що «десь читали, що так правильно». Але я пишу не про них, а про те, як їх позбутися і не допустити знов.

GRASP на практиці: три принципи як два пальці

Розглянемо три принципи, на яких найчастіше спотикаються реальні проєкти.

Creator: хто насправді має створювати обʼєкти

Найпопулярніша помилка — створювати обʼєкти там, де це просто зручно в моменті. У контролері, у сервісі, або ж ховати все це за фабрикою, навіть якщо вона насправді нічого не вирішує. У результаті в умовному інтернет-магазині контролер починає створювати Order, потім OrderItem, потім ще й Invoice, паралельно рахуючи суми. Логіка розмазується, залежності ростуть, будь-яка зміна починає боліти.

GRASP Creator формулює дуже просту ідею. Обʼєкт має створювати той, хто вже має всі необхідні для цього дані або логічно володіє створюваним обʼєктом. Якщо Order знає, з чого складається замовлення, то саме він і має створювати OrderItem. Якщо рахунок — частина життєвого циклу замовлення, то створення Invoice природно лежить усередині Order, а не в контролері.

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

Controller: точка входу, а не місце роботи

Друга класична проблема — неправильне розуміння ролі контролера. У багатьох проєктах контролер перетворюється на смітник. Він валідує, створює доменні обʼєкти, рахує, зберігає, викликає інтеграції і ще й формує відповідь. Формально це контролер, по факту — сервіс з HTTP-анотаціями.

GRASP Controller каже дуже неприємну для багатьох річ: контролер — це не місце бізнес-логіки. Це точка входу в систему. Він має прийняти запит, передати керування відповідному use case і повернути результат. Уся предметна логіка при цьому живе в окремих класах application-рівня.

Правильний контролер не дорівнює сервісу, він просто зв’язує зовнішній світ із внутрішнім. Коли все зроблено саме так, код стає більш передбачуваним та прозорим.

Pure Fabrication: коли штучні класи рятують дизайн

Третій принцип зазвичай викликає або страх, або фанатизм. Або розробник боїться створювати нові класи й пхає все в доменні сутності, або ж навпаки, робить один великий Service, який займається всім одразу.

GRASP Pure Fabrication дозволяє чесно сказати: так, цей клас не відображає домен напряму. І це нормально. Його завдання зменшити звʼязність і зробити систему читабельною. Репозиторії, інтеграційні сервіси, мапери, білінг, логування — усе це не домен, але без цього домен дуже швидко перетворюється на звалище технічних деталей.

Pure Fabrication це спосіб захистити предметну модель від того, що їй не належить.

Як із GRASP природно випливає SOLID

Тут важливо зробити паузу і зробити очевидний висновок. SOLID — це не відправна точка дизайну системи, а його наслідок. GRASP відповідає на питання «кому що робити», а SOLID — на питання «наскільки добре ми це зробили».

Коли Creator застосований правильно, створення обʼєктів не розмазане по всій системі, а логіка зʼявляється там, де їй природно бути, у результаті класи перестають займатися всім підряд і отримують одну зрозумілу причину для зміни. Це і є принцип Single Responsibility, але не як догма з книжки, а як природний ефект здорового дизайну.

Коли контролер залишається точкою входу, а не бізнес-двигуном, логіка вимушено виноситься в окремі сервіси і use case. Контролер починає залежати не від конкретних реалізацій, а від абстракцій. Так зʼявляється Dependency Inversion. Нетому, що так правильно, а тому, що інакше система просто не масштабується.

Pure Fabrication, своєю чергою, дозволяє додавати нову поведінку, не ламаючи існуючий код і не тягнучи технічні залежності в домен. Це автоматично дає Open/Closed і низьку звʼязність, без спеціальних зусиль і магічних інтерфейсів.

Отже, я наголошую: якщо GRASP застосований усвідомлено, SOLID не потрібно впроваджувати силоміць. Він зʼявляється сам. SOLID без GRASP зазвичай виглядає формально і механічно: давайте додамо ще один інтерфейс, бо так треба. SOLID разом із GRASP виглядає живим. У ньому кожна абстракція має причину, а дизайн легко пояснюється здоровим глуздом, а не цитатами з книжок.

А де ж тут GoF-патерни?

Та осьдечки, поруч. GRASP, SOLID і GoF — не конкуренти, а рівні одного процесу. В ідеалі правильна послідовність мислення розробника має виглядати наступним чином.

  1. GRASP — розподіляємо відповідальність. Який клас що робить? Де логіка має жити? Шо по сценаріям?
  2. SOLID — перевіряємо якість дизайну коду. Чи не перевантажені класи? Чи легко міняти реалізації?
  3. GoF — підбираємо інструмент реалізації. Який шаблон найкраще реалізує це рішення?

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

Використовуємо GRASP, думаємо, як це вписати в існуючий софт, щоб не завадити його нормальній роботі. Розуміємо: потрібен окремий клас для вибору алгоритму розрахунку ціни.

Застосовуємо SOLID, думаємо, як зробити це красиво і чисто. Робимо висновок: клас має залежати від інтерфейсу, а не від конкретної реалізації.

Нарешті добрались до GoF. Тут ідеально лягає Factory, тому що потрібно створювати реалізації залежно від умов.

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

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

А без чіткого розподілу відповідальності (GRASP) та якісних залежностей (SOLID) GoF-патерни перетворюються на архітектурний карґо-культ, при якому зміст підмінюється формою, а код залишається лайняним.

Висновок

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

Ось вам коротка формула для запамʼятовування:

GRASP → мислення
SOLID → критерії якості
GoF → інструментарій

І моя порада: краще за все практичні навички застосування GRASP напрацьовувати на рефакторінгу. У світі існує достатньо поганого коду, який можна (і треба) покращити.
Як завжди, дякую за увагу і чекаю на коментарі.

Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.

👍ПодобаєтьсяСподобалось27
До обраногоВ обраному13
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

якби ж лише не популяризація наративів окупанта...

якби ж лише не популяризація наративів окупанта...

Га? Це ж тема про СОЛІД. Ви про що?

Я про деякі коменти з аргументами нижче та про активність загалом, не дуже приємно спостерігати пости «звідти» через лайки (в кращому випадку).

Так, офтоп, по темі нижче коментах, за матеріал автору лайк, це база для кожного дева.

Ну нарешті про щось важливе, а не інфоциганщина-SOLID... дякую, Сергію!

Автор даного прикладу з Order порушує Single Responsibility Principle...?

Розумна людина вчиться на чужих помилках, а не на своїх. Людина ж мудра вчиться на чужих успіхах. Хорошого коду в світі вистачає, можна знайти його в опенсорсі. І вчитись на вдалих рішенях, які вже створили більш досвідчені колеги. А так, як пропонує автор — набиваючи власні гулі, ну що ж: «If you wanna be dumb, you gotta be tough.»

Типовий розвиток як спеціаліста:
bad simple code -> bad complex code -> good complex code -> good simple code

Далеко не всі дістаються останніх фаз

Ось вам коротка формула для запамʼятовування:

GRASP → мислення
SOLID → критерії якості
GoF → інструментарій

І це пряма цитата Стедхема.
Доу тепер не курилка, а пацанський паблік. Чи це прогрес чи регрес не ясно.

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

GRASP та SOLID — це просто 2 незалежних набори принципів, які запропонували різні автори.
GoF — це дещо старший набір патернів, які були актуальні в 90-х, зараз вони втрачають актуальність (але більшість з них все ще вартують ознайомлення).

Ви вивчили SOLID, але архітектура все одно кульгає? Чому GRASP важливіший за GoF (і як їх поєднати)

Відповідь на питання в першому реченні криється у факті існування 2 речення:
Архітектура кульгає, бо замість побудови архітектури більшість програмістів фокусуються на рівні коду (оті GRASP, SOLID, GoF і тд) і не задумуються про архітектуру системи.
Для початку треба дивитись на «Fundamentals of Software Architecture: An Engineering Approach» або краще «Software Architecture in Practice (SEI Series in Software Engineering)».

Нарешті гарна технічна стаття на DOU. Дякую автору за працю!

Дуже цікаво було дивитись як роками натягували SOLID на Front-end, а потім прийшов React і кожен цей принцип на тому самому органі крутив.

Давно TypeScript, так що і LSP є
А так от гайдлайн перекладений на реалії React
https://medium.com/@lamjed.gaidi070/solid-principles-in-react-a-practical-guide-for-developers-d1822955d165

Я це читав кілька років тому, вони виносять fetch() в useFetch() з додаванням пачки import/export і кажуть, що досягнули single responsibility x_x.

React вийшов як категорична альтернатива архітектурам рівня MVC, просуваючи принцип view-only. Одразу ж він пристрелив всі механізми наслідування/прототипів, які тоді вийшли з ES6 в нормальному вигляді.

В початку опису React, написано — що це реалізація паттерна MVVM.
З усіх принципів SOLID, найменше відповідає React лише принципу підстановки типів, Барбари Лісков LSP. Просто тому що JavaScript не мав механізмів підтримки на рівні синтаксису. Та народ тому і перейшов на TypeScript, та це ввели в новітні стандарти ECMA Script.
А взагалі то дякую, ви чітко підверджуєте те що я писав. Сучасні программісти, в реаліях нашої поточної системи — це Framework locked Code Monkey (гобліни, бидлокодери і т.д.), не те щоби особисто кожний, а так порібно аутсорс бізнесу, такі формальні робочі позиції. Бізнес модель на цьому побудована, так само в неї закладено — що ви через якись час уткнетесь в скляну стелю і поїдете її пробивати за кордоном, де розробляють сам React і інщі фреймверки відповідно порібні фундаментальні знання і де Code Monkey бути не прокатить. Робота може і така сама як і тут на видаленці через Internet, але співбесіду не пройти.

Ясно-понятно. Отам де написано про MVVM (https://medium.com/@ignatovich.dm/mvvm-architecture-in-react-a-beginners-guide-with-examples-bde116f1347c), воно проходить як анти-паттерн вашої першої статті — то можете пробувати написати третю, раптом вийде без конфліктів.

А по іншій частині вашого пафосного коменту прямо все мимо: я не дуже сучасний, я не в аутсорсі, українські скляні стелі давно пройдені, за кордон поїхав теж давно, хз що там саме не прокатило.

А по іншій частині вашого пафосного коменту прямо все мимо

До вас особисто я не маю жодних упереджень, по перше я вас не знаю особисто по друге ви явно щось же знаєте.
Звідки взявся пафос, а от звідси youtu.be/VvRVf7_j5gQ?t=799

а от звідси youtu.be/VvRVf7_j5gQ?t=799

Ой нє нє, дякую.
Оте життя заради коду та світле щасливе майбутнє можна ньюфагам з «вайті в айті» впарювати, для мене той етап слава Богу вже пройшов роки назад.

В Теслі я побував в 2020 — не сподобалось.
С Гугла рекрутер приходив в 2022 — відмовився.

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

де розробляють сам React і інщі фреймверки

Вже давно цих фреймворків як дворових собак в совку, але все одно знаходяться унікуми з «все гімно, ща я напишу найкласніший фремворк в світі, hold my beer»

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

З плином часу все окрім KISS відпадає і стає не релевантним.

Ну я один раз бачив задачу, під котру добре лягав патерн State. Один раз за 15 років.

А взагалі — бувають цікавинки, особливо коли треба оптимізувати перформанс. Наприклад, ось time-division CQRS — коли уся система перемикається між рід-онлі режимом читання й планування та режимом виконання запланованих дій і змін стану, коли кожен компонент працює ізольовано metapatterns.io/...​ractors-phased-processing

На ділі хто так каже в цей принцип вкладуть усе, наприклад YAGNI чи DRY.
Одне з головних пояснень KISS в застосуванні програмування, тримати алгоритмічну простоту рішень, тобто оптимально виконувати задачу намагаючись зводити до необхідного мінімуму кількість операцій та пам’яті. Також в принцип закладено ідею — робити те що потрібно, але не більше — тобто «не переускладненюй, концентруйся на задачі».
Але чомусь у програмістів початківців, цей принцип має інше розуміння. Зазвичай це розробляти софт експериментальним методом, нахрапом імплементуючи перше, що збреде в голову особливо не думаючи, головне зробити бігом. Разом з тим перевинаходячи колеса і велосипед.
А перше що прийде в голову зазвичай є — когнітивним упередженням. Так працює людська свідомість за природою uk.wikipedia.org/...​ki/Когнітивне_упередження
Якщо базуватись на відчуттях — а не на знаннях, тоді Земля — пласка і стоїть на слонах, які в свою чергу на Черепасі. Ще в дитинстві нас навчили — що це не так, це вам транслювала система освіти. Та це в дійсності революційні труди Миколая Коперника. Священника Джордано Бруно який ці труди розповсюджував — за це інквізиція спалила на вогнищі в свій час. Доки Христофор Колумб не довів вірність цього судження на практиці — припливши на Кубу (плив він в Індію за спеціями), за це і палили на вогнищі, щоби не підривали політичні і матеріальні інтереси католицької церкви. Інвестували в це королева Ізабелла І Кастильська та король Фердинанд ІІ Арагонський, які щойно виграли Реконкісту разом із інноваційним полководцем Ель Сідом. Війна йшла 770 років, а була виграна за допомогою інновації — вогнепальної зброї.
Власне Сократа який лежить в основі : логіки, науки та освіти, що вже розвили його учні і учні його учнів — Платон та Арістотель, теж стратили. Бо він це застосовував у політиці і дибатах і таким чином ставав проти владних і матеріальних інтересів політичних опонентів. Арістотель — пішов іншим політичним шляхом з рештою, навчив Александра Македонського як усіх фізично перемагати використовуючи логіку, і отримувати політичну владу таким чином. Александр в свою чергу відомий як засновник логістики. Напевно цьому теж треба вчитись — бо спалять на вогнищі чи заставлять випити отруту, якщо абстрактні судження і теорії не доведені на практиці.

Якщо рахунок — частина життєвого циклу замовлення, то створення Invoice природно лежить усередині Order, а не в контролері

Це, як на мене, поганий приклад. Invoice створюється на основі ордеру і, залежно від бізнес-процесу, може бути сформований після затвердження ордеру (наприклад, якщо потрібна ручна валідація), після доставки товару або після виконання робіт, etc. Тобто Invoice — окрема entity, яка завжди прив’язана до ордеру (кожен Invoice має рівно один Order), але сам Order може мати 0..N інвойсів (на початку їх може не бути взагалі; N інвойсів коли доставка розтягнута в часі — сьогодні доставили х товарів — а через декілька тижнів решту). Ви повинні спроєктувати доменну модель, entities та реалізувати їхню взаємодію — і в цій взаємодії Order не «створює» Invoice напряму. Створення ініціюється доменним сервісом / workflow, окремим Billing/Invoice сервісом, який застосовує правила та генерує інвойси, etc. Навіть у найпростішій бізнес-моделі Order не має створювати Invoice напряму — краще інкапсулювати в application/domain service — так легше розширювати і підтримувати.

Я навіть знаю приклад, коди спочатку формується інвойс, а вже після оплати на основі інвойсу створюється ордер.

а вже після оплати на основі інвойсу створюється ордер

не стикався з такими бізнесами ще — буду знати — дякую
а де таке можна зустріти — ваший приклад це про що саме?

Дійсно зазвичай навпаки. Order — ще не закрите замовлення на яке виставляється invoice чи receipt тобто чек. Це документ куплі-продажу, що має юридичну силу.
В e-commerce системах для обробки етапів прохрдження shopping cart, зазвичай вводиться такий паттерн як pipeline, де кожним етапом проходження замовлення в системі виділяється в окремий процесс в ціпочку, і це можна сконфігурувати гнучко під будь який тип бізнесу. Виставлення інвойсу в такій ципочці зазвичай на прикінці, потім відправка його покупцеві тім методом яким це робить бізнес десь можна електроннтми засобами адесь його передасть служба доставки чи клієнт сам забере на складі чи в точці продажу в якому оформив інтернет замовлення. На початку же процессу, зазвичай перерахунок ціни із урахуванням різних скидок, поограм лояльності і т.д. Тут вчить мат часть із предметної галузі задачі автоматизації — business domen, що зветься англіською.
Та як я вже писав, сьогочасний программіст посто конфігурує фреймверк типу : Magento, Drupal Commerce, Big Commerce, SAP Hybris, Broad leaf і т.п. навіть не знаючи — що під капотом є шаблони проектування. Просто робить свою роботу як сказали дяті з тетями з закордону, як колись називали Monkey Job.
Таку роботу справді ШІ відніме, якщо дяді і теті з закордону навчаться структуровано і логічно самостійно формувати свою думку в формальні вимоги. Сам ШІ каже, що це пряма небезпека для Junior-ів. На ділі за кордоном часто, серіал IT Crowd.

Это все тлен, просто пиши код что бы другим было понятно с кем работаешь сейчас

Big ball of mud 1999 s3.amazonaws.com/...​s/papers/bigballofmud.pdf
Декілька полколінь інженерв через це проходило і набило гулі намагаючись лбом прошибити стіну. Якщо вважаєте, що дотримуватись таких пратик дорого — спробуйте не доримуватись.

О хороша цитата з Пітера від загально визнаного ерудита ще з СРСР www.youtube.com/shorts/FPs46Uq5qqY
Оригінально це теза Теодора Рузвельта, 25-го віцепрезидента США. «Дураки вчаться на своїх помилках, а розумні — на чужих».

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

GRASP Creator формулює дуже просту ідею. Обʼєкт має створювати той, хто вже має всі необхідні для цього дані або логічно володіє створюваним обʼєктом. Якщо Order знає, з чого складається замовлення, то саме він і має створювати OrderItem. Якщо рахунок — частина життєвого циклу замовлення, то створення Invoice природно лежить усередині Order, а не в контролері

Щось мені це зовсім не подобається! Мало того, що доменні моделі, які мають бути POCO, а ще краще — immutable, щось створюють. Але вони ще й створюють це усередині себе! Роблячи неможливим створення моків, перекреслюючи Dependency Injection та Interface Seggregation.
Стосовно створення — я б взагалі переробив оператор new аби він приймав тільки інтерфейси і створював об’єкти через DI контейнер. Плюс працював як Lazy та створював об’єкт тільки якщо до нього звертаються.
Як на мене — ООП значно змінилося з часів Граді Буча. Звичайно код у C# можна писати як і 10 років тому. Але якщо писати за сучасними практиками — то дуже багато старих підходів треба просто викидати.

Стосовно створення — я б взагалі переробив оператор new аби він приймав тільки інтерфейси і створював об’єкти через DI контейнер. Плюс працював як Lazy та створював об’єкт тільки якщо до нього звертаються.

Магія така магія. А комусь потім це дебажити.

Плюс працював як Lazy та створював об’єкт тільки якщо до нього звертаються.

Lazy Loading Pattern — але це лише має сенс для опціональних залежностей, бо інакше вам так чи інакше створювати об"єкти — і краще якщо ви отримаєте потенційну помилку раніше.
Хоча може бути корисно в плані performance оптимізацій — наприклад відкрити з"єднання до бази даних пізніше, безпосередньо коли вам потрібні дані (ну і не забути закрити звичайно) — але це все залежить від use case-у — якщо у вас workflow розтягнуте в часі.

Я б сказав що більшість залежностей має бути Lazy. Тому що якщо один об’єкт від початку не може працювати без іншого — це вже дуже жорстка залежність, яких краще уникати.
Ось вам привід замислитися: якщо маємо бекенд, який обробляє запити від клієнтів. Це може бути наприклад Rest API. Логічно що клієнт може перервати з’єднання у будь-який момент не чекаючи результату. І це доволі частий сценарій: поки сторінка завантажується — клієнт побачив лінку — і пішов далі. А отже усі запити, які були, браузер закриє.
Тепер уявіть що буде робити ваш бекенд у цьому випадку? Якщо це класичний моноліт — то він на початку створить усі об’єкти: контролер, модель, репозиторій, конектори до інших сервісів. І потім відпрацює по-повній, тільки аби дізнатися що результат вже нікому не потрібен.
Інша справа коли бекенд побудований на async та Lazy — і cancelation можливий у будь-який момент.
Загальне правило гарної ІТ архітектури просте: чим більш вона гнучка, чим менше жорстких залежностей — тим вона краща. Уявіть що ваш бекенд — це термінатор Т-1000 з «рідкого металу» замість монолітного скелета. На відміну від архітектури «в камені» тут не потрібні споруди які простоять 100 років — у сучасному світі усе застаріває дуже швидко.
Фантазуючи далі: уявіть собі гаджети зроблені по «гнучкій» архітектурі (айфон з «рідкого металу»). І замість купувати нову модель — треба просто скачати оновлення, яке «пере-збере» плату смартфона. Так само, як мозок людини постійно перебудовує нейронні зв’язки.

1) Це 0.1% реальних користувачів. Відповідно, ви нічого не виграєте за перформансом.
2) У вас з’являються питання як звільняти ресурси, такі як відкриті транзакції чи сесії до інших сервісів, якщо юз кейс може перерватися на будь-якому кроці.
Себто, ускладнення коду без реального профіту.

поки сторінка завантажується — клієнт побачив лінку — і пішов далі

Як зазначив Denys Poltorak, відсоток користувачів, які клікають посилання одразу після рендерингу (чи в процесі завантаження) сторінки, дуже низький. Якщо йдеться про публічні сторінки, то скільки API-викликів ви фактично виконуєте? В ідеалі контент сторінки має бути попередньо пре-рендерений і віддаватися через якийсь CDN, а запити на рендерінг динамічних блоків повинні виконуватися швидко — бо, по-перше, read-only, а, по-друге, якщо вони повільні, то user experience відповідно просідає з усіма витікаючими наслідками. Якщо ж мова про back office (адмінку) або сторінки профілю, то там зазвичай SPA (переважно React) — у такому випадку сторінка не перезавантажується повністю, коли користувач клікає посилання.
А якщо глянути з іншого боку, то write операції ви навряд-чи захочете відміняти, вірно? А read операції, особливо в вашому прикладі, відміняти має мало сенсу. Але навіть якщо для якогось use case-у це буде доцільно, то безумовно це додасть складності в ваш проект.

Якщо йдеться про публічні сторінки, то скільки API-викликів ви фактично виконуєте? В ідеалі контент сторінки має бути попередньо пре-рендерений і віддаватися через якийсь CDN

Я бачив інше: захід на лендінг пейдж — і фронт починає кидати десятки запитів на API. Те саме відбувається майже на кожну зміну даних — кожен контрол хоче себе пере-малювати і шле запит за новими даними.
Можливо десь є нормальний фронтенд, але я чомусь бачив тільки такі. Більше того: іноді новий запит відправляється на кожне натискання кнопки! Юзер набирає у пошуку слово з 10 літер — це буде 10 запитів, 9 з яких перервуться.

аби він приймав тільки інтерфейси і створював об’єкти через DI контейнер

так шарповий автофак вже років триста дозволяє віддавати в конструктор фабрику, із якої екземпляр витягується тільки в разі потреби

junior або early middle, може без проблем перелічити GoF-патерни і навіть пояснити, чим Factory відрізняється від Abstract Factory

останній раз ці дурниці вчив перед співбесідою на міда років 10 тому.
Добре що на позиції сіньора/принципала цієї фігні вже не питають

GoF — підбираємо інструмент реалізації. Який шаблон найкраще реалізує це рішення?

Розповсюджена помилка.
Патерни є хаками для випадків, коли звичайний об’єктно-орієнтований (чи інший — залежно вашої парадигми коду) дизайн або ваш фреймворк та мова програмування не дозволяють вирішити певну проблему зручно та просто.
Використання патернів як обов’язкового або рекомендованого кроку розробки призводить до страшного та неефективного коду і є ознакою карго культу.
Література: 5 том Pattern-Oriented Software Architecture.

Після 30 років у девелопменті та 20 в архітектурі сформулював для себе основні принципи:
— control complexity
— protect critical variation points
— keep conceptual integrity

Плюс:
— define evolution rules
— document rationales/assumptions
— document violations of less surprize principle

I до всіх патернов треба відноситись як до рекомендацій.
Кожен принцип можна порушувати з reasonable rationals, якщо розумієш чому, і контролюєш (ну, чи думаєш шо контролюеш :-) ) наслідки...

Ось та людина, що мені потрібна!
Маєте час порев’ювити книжку?

:-O
Залежить від об’єму та дедлайну :-)
В принципі, маю час та натхнення. :-)

Дякую. Об’єм під 400 сторінок, половина — картинки.
PDF/EPUB github.com/...​atterns/releases/tag/v1.1
Веб (новіша версія) metapatterns.io
Якщо вирішите, що матеріал гідний детальних коментів — відкрию оригінал в Гугл Док для прив’язки дискусій до тексту.

Теж цікаво отримати фідбек від людини з

30 років у девелопменті

Льохко: чим більше я старію, тим з більшою недовірою відношуся до думки, що з роками люди мудріють. :-)

Ну мене тригернув саме перелік принципів на початку гілки.

Вибачте!
Я тут у пошуку роботи, та ще усякого навалилось.
Ніяк руки та голова поки що не доходять.
Поки не розгребуся — ніяк :-(

Колись років так 17 тому, директор минулої контори (а тоді це був стартап) рекомендував мені книгу Refactoring Марніна Фаулера, колегу Роберта Мартіна (дядька Боба) який дуже добре описав принципи SOLID в Clean Architecture. Сам дядько Боб ще має іншу книгу безцеллер — Clean Code та разом із Фаулером вони посилаються один на одного.
От саме у Мартна і були описані практичні приклади «smell code», тобто який «погано пахне». В тому числі був описані зазначені проблеми зі «стільба дробом» і т.д. Та описанно саме наслідки поганого дизайну і як це призводить до коду який погано підлягає підтримці, тобто в який дуже важко вносити зміни як то додавання чи переробка якихось функцій, виправлення дефектів (одне полагодив, 10 інших речей зламав) тощо. А коли код погано підтримується, це не проблема менеджерів — це проблема самих ІТ спеціалістів. Менеджери та власники бізнесу просто ставлять дедалайни і пред’являють притензії, коли не виконані потрібні для бізнесу і заробітку співвідношення ціни та якості. Вимагалось швидко якісно та дешево, вийшло довго погано і дорого.
Про критику базових речей, справа в тому — що більшість сучасних программістів працюють на фреймверках. Вони звісно використовують шаблони проектування та принципи — але часто навіть гадки не мають, що це вони. Умовно ви пишете не Java чи Kotlin,Scala,Groovy, JavaScript/TypeScript, С++, Rust — а на : Spring Framework, Nest.JS, React.JS, Angular, Boostrap, FastAPI, QT, GTKMM, POCO, Godot, Unreal і т.д. і т.п.
Тут в цілому — що важливо, швидко зробити якісь базові задачі і показати результати як на лабораторних роботах в університеті. Потім здати зазубрену теорію і т.д. та головне як найшвидше застафитись на проект та почати приносити прибутки, якщо поталанить ще і заробляти гроші. На самому проекті — теж, як висловився Сергій : «Грати цуценятко, яке подобається родині». Так це звортня сторона економіки процессу. Тільки ми чомусь забуваємо, що головна перевага пост СРСР, скажімо перед Індеєю була саме вища технічна якість рішень.

більшість сучасних программістів працюють на фреймверках

Так, фреймворк надає базову структуру для організації файлів проєкту та оркестрації власних компонентів, але далі все залежить від вас. Якщо не розглядати просту бізнес-логіку, то проєктування доменної моделі, коректна декомпозиція на entities та реалізація їхньої взаємодії — у цьому фреймворк вже не допоможе.

Чому б просто не казати що уся розробка, усі паттерни та принципи про виділення відповідальностей та абстракції ?

Ну, бо як мінімум, треба ще й про взаємодію компонентів думати.
І це ми ще про усілякі non-functional аспекти навіть не починаємо :-)

Підписатись на коментарі