Розділ 1. Еволюція болю: Від GOTO до ООП

Надворі 2026 рік. У нас є все: ШІ-асистенти, хмарні середовища розробки, фреймворки, які ледь не читають наші думки. Здавалося б, живи і радій, але чому тоді бізнес досі платить шалені гроші Software Architects? Чому на співбесідах сеньйорів досі ганяють по “древніх” патернах?

І чому проєкти, які починаються як “цукерочка”, через рік перетворюються на непідтримуване пекло, яке всі бояться чіпати?

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

Відповідь ховається в одному слові: Складність.

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

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

Ера Хаосу: Spaghetti Code та GOTO (1960-ті)

Уявіть собі шістдесяті роки. Ми пишемо на Асемблері, Fortran або ранньому BASIC. У нас немає функцій у сучасному розумінні. Немає циклів while чи for. У нас є лише одна суперзброя: оператор GOTO.

Типовий код того часу виглядав приблизно так:

1 Рядок 10: Зроби щось.
2 Рядок 20: Якщо умова Х — стрибни на рядок 500.
3 Рядок 500: Зроби щось і стрибни назад на рядок 30.

Це призводило до явища, яке отримало назву Spaghetti Code. Не в переносному сенсі, а буквально. Якщо ви спробуєте намалювати потік виконання на папері — це буде схоже на тарілку з макаронами. Зрозуміти логіку програми, просто читаючи код зверху вниз, було неможливо. Ви мусили тримати в голові всю карту пам’яті.

Spaghetti Code

Програми ставали непередбачуваними. І ось, у 1968 році, сталася революція. Великий і жахливий Едсгер Дейкстра (Edsger W. Dijkstra) пише свого знаменитого листа в ACM: “Go To Statement Considered Harmful”. Він сказав: “Хлопці, досить стрибати. Нам потрібна структура”.

Ера Структурного програмування та його “Спадщина” (1970-ті)

Так народилося Структурне програмування. Вчені Бом і Якопіні математично довели, що будь-яку програму можна написати, використовуючи всього три конструкції:

  1. Послідовність (Sequence): Роби А, потім Б.
  2. Розгалуження (Selection): if / else.
  3. Ітерація (Iteration): while / for.

Усе. Більше ніяких стрибків GOTO. З’явилися процедури і функції. Це був прорив! Код став читабельним від початку до кінця.

Але тут є нюанс, про який мовчать у підручниках. Структурне програмування принесло нам не тільки добро, а й “архітектурні забобони”, які досі псують життя розробникам.

Одне з правил Дейкстри звучало так: “One Entry, One Exit” — один вхід, один вихід.
Для мов на кшталт Pascal чи C, де треба було вручну чистити пам’ять перед виходом із функції, це було критично важливо. Ти увійшов, виділив пам’ять, зробив роботу, очистив пам’ять, вийшов. В одній точці.

Але що ми бачимо сьогодні в Java, C# чи PHP? Розробники, сліпо слідуючи цьому правилу, пишуть “Arrow Code” або “Код-стрілу”, з величезною кількістю вкладеностей.

 1 // Антипатерн Arrow Code (Спадщина "Одного виходу")
 2 public String processOrder(Order order) {
 3     String result = "Error";
 4     if (order != null) {
 5         if (order.isValid()) {
 6             if (order.hasPayment()) {
 7                  // 100 рядків логіки
 8                  result = "Success";
 9             }
10         }
11     }
12     return result; // Той самий "єдиний вихід"
13 }
An icon of a warning1

Це антипатерн у сучасному світі!

Сьогодні ми сповідуємо принцип “Fail Fast”. Якщо дані невалідні — робимо return одразу, на самому початку методу. У сучасному дизайні це називається Guard Clauses (загороджувальні конструкції). Це дозволяє тримати код плоским, чистим і зрозумілим.

1 // Сучасний підхід: Guard Clauses (Загороджувальні конструкції)
2 public String processOrder(Order order) {
3     if (order == null) return "Error";
4     if (!order.isValid()) return "Error";
5     if (!order.hasPayment()) return "Error";
6     
7     // 100 рядків логіки на першому рівні вкладеності
8     return "Success";
9 }

Не треба тягнути виконання до кінця функції заради святого правила сімдесятих. Правило було для іншого контексту.

Ера Процедурного Пекла: Глобальні дані (1980-ті)

З потоком керування розібралися. Але виникла нова проблема. У процедурному програмуванні дані (змінні) і поведінка (функції) були розділені.

Уявіть собі комунальну квартиру.

  • Змінні (дані) — це спільний коридор і кухня.
  • Функції — це мешканці.

Функція А прийшла, взяла змінну User, змінила їй ім’я. Функція Б прийшла, побачила User, видалила його. Функція В намагається нарахувати User зарплату… і програма падає з помилкою Null Reference. Хто винен? Хто змінив дані? Відслідкувати це у великій системі було практично неможливо.

Global State Problem

Дані були беззахисні. Будь-яка функція могла їх зіпсувати. Нам потрібен був сейф.

Народження ООП: Клітини і Повідомлення

Тут на сцену виходять скандинави Крістен Нюгорд і Оле-Йохан Дал зі своєю мовою Simula 67 (перша мова з об’єктами), і згодом Алан Кей (Alan Kay), батько мови Smalltalk.

Кей був біологом за освітою. Він подивився на клітину живого організму. Клітина має мембрану. Всередині йдуть складні хімічні процеси: мітохондрії, ядро, ДНК. Але ззовні клітина — це просто капсула. Клітини спілкуються одна з одною через обмін сигналами. Клітина А не лізе “руками” всередину Клітини Б. Вона просто надсилає повідомлення.

Так і народилася Інкапсуляція.
Ми вирішили: дані — це святе. Ми ховаємо їх всередину об’єкта. Ніхто ззовні не має права їх вільно модифікувати. Хочеш щось зробити? Виклич метод. Надішли об’єкту повідомлення.

An icon of a info-circle1

Історичний парадокс: Ми перемогли глобальні дані через ООП та інкапсуляцію. Але натомість отримали іншу проблему — Високу пов’язаність (Coupling). Коли C++ захопив світ, люди почали створювати тисячі класів, які залежали один від одного так міцно, що зміна одного файлу ламала половину проєкту.

І ось саме тут, коли класи вже існували, але порядку між ними ще не було, на сцену виходять Архітектори.

Від цегли до байтів: Історія патернів (1977-1994)

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

У 1977 році Крістофер Александер, професор архітектури будівництва, пише книгу “A Pattern Language”. Він усвідомив: проблеми в архітектурі повторюються. Не треба щоразу вигадувати велосипед. Треба описати ці повторювані рішення:

  • Context (Контекст): Де це відбувається?
  • Problem (Проблема): Який конфлікт треба вирішити?
  • Solution (Рішення): Як це зробити перевіреним способом.

Десять років по тому (1987) двоє геніальних програмістів, Кент Бек та Ворд Каннінгем, прочитали Александера і зрозуміли: “У коді ж те саме!”. Вони описали кілька перших патернів програмування. Спочатку індустрія сприйняла це скептично. Але у 1994 році стався великий вибух.

Четверо вчених (Еріх Ґамма, Річард Хелм, Ральф Джонсон, Джон Вліссідес) випускають книгу Design Patterns. Вони увійшли в історію як Банда Чотирьох (Gang of Four, GoF). Вони описали 23 класичні проблеми і дали їм назви: Factory, Command, Observer…

З’явилася спільна мова. Тепер розробник з Києва міг сказати колезі з Каліфорнії: “Застосуй тут Синглтон”, і їм не треба було малювати діаграми пів години.

Темний бік патернів

Здавалося б, щасливий кінець? Ми знайшли Святий Грааль? Ні.

Універсальна мова принесла нові «хвороби»:

  • Patternitis (Патерніт) та “Дитячі травми”: Спочатку це хвороба новачків. Прочитавши книгу GoF, розробник отримує “молоток”, і все навколо починає здаватися йому цвяхом. Він пхає патерни туди, де достатньо одного if/else. Але найгірше починається потім: попекшись на цьому і створивши непідтримуваного монстра, такий розробник доходить висновку, що “патерни — це взагалі маячня”. Дослужившись до позиції Тімліда, він через цю свою “дитячу травму” починає категорично забороняти підлеглим використовувати БУДЬ-ЯКІ патерни. Ця крайність дуже часто зустрічається в нашій індустрії.
  • Карго-культ: Люди копіюють структуру патерну, бо “так круто”, але не розуміють проблеми, яку він вирішує.

Джуніори ліпили складені Фабрики там, де треба було просто зробити new. Код ставав надскладним.

Протверезіння: GRASP і SOLID

У 1997 році Крейг Ларман випускає книгу “Applying UML and Patterns”. Він подивився на цей хаос і сказав: “Хлопці, ви побігли поперед батька в пекло. Ви вчите складні архітектурні форми (GoF), але ви досі не знаєте базових речей!”

An icon of a info-circle1

Цікавий факт про книгу Лармана: Це дуже забавна книга сама по собі. Вона напхана абсолютно різнорідними темами, які майже ніяк не пов’язані між собою. У Лармана було багато класних ідей, і він просто “запхав” їх усі в один том, навіть не подбавши про зв’язність. Це часто викликає острах у розробників: людина хоче розібратися в GRASP, купує книгу про UML і патерни, а виявляється, що GRASP — це лише одна з глав, яка стоїть особняком від інших відірваних тем.

Але саме в цій книзі народився GRASP (General Responsibility Assignment Software Patterns). Це не про класи. Це про Відповідальність. У кого є дані, той і робить роботу (Information Expert). Хто об’єкт використовує, той його і створює (Creator). Це фундамент, без якого ваші Синглтони і Декоратори — лише купа сміття.

Пізніше, на початку 2000-х, з’являється Роберт Мартін (відомий як “Uncle Bob”). Треба відразу розвіяти популярний міф: такої книги як “SOLID” у природі не існує.

Насправді, ці п’ять принципів були лише частиною його великої монографії “Agile Software Development, Principles, Patterns, and Practices”. Більше того, сам акронім “SOLID” придумав не він, а Майкл Фезерс (Michael Feathers), який помітив, що якщо скласти перші літери п’яти головних принципів Мартіна, вийде красиве слово. Принципів у тій книзі набагато більше, але ми фокусуємось на базовій п’ятірці.

По суті, SOLID — це ваша техніка безпеки, яка гарантує, що зміна одного класу не підірве інший.

Велика Картина (The Big Picture)

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

Knowledge Pyramid

Бачите логіку? Наша піраміда спускається від загальних архітектурних ідей все ближче і ближче “до землі” — тобто до вашого реального коду та серверів:

  • На самій вершині абстракції лежить ідеологія ООП. Це концепція капсул і повідомлень.
  • Спускаючись на крок нижче, ми маємо GRASP — це приземлені правила розподілу відповідальності (у який клас покласти метод).
  • Ще ближче до реальності знаходиться SOLID — “техніка безпеки”, яка гарантує гнучкість зв’язків між вашими класами.
  • Далі йде GoF — це інструменти для вирішення локальних, специфічних мікро-задач “на землі” (як створити об’єкт в конкретних умовах). Важливо розуміти: GoF — це не готові рішення для комплексних проблем системи. Це типові “сферичні коні”, дуже локалізовані цеглинки.
  • І вже після них, на самому фундаментальному рівні системної розробки, починається справжня, доросла Макро-архітектура (Enterprise Patterns, мікросервіси, HighLoad) — тобто те, як усі ці “дрібні” абстракції та патерни об’єднуються у величезну працюючу систему.

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

А як щодо Функціонального програмування?

Можливо, ви зараз думаєте: “Сергію, зараз хайп навколо функціонального програмування (ФП). Лямбди, стріми, незмінний стан (Immutability). Кажуть, що ООП застаріло. Чому ж ФП просто не вбило ООП і ці ваші патерни?”.

Щоб одразу відбити атаки фанатів чистого ФП (яких у нашій індустрії вистачає), поясню технічні причини. Чисте ФП просто не підходить для реалій більшості сфер розробки (чи то Enterprise, e-commerce, мобільна розробка, фронтенд або геймдев) з трьох фундаментальних причин:

  1. Проблема стану (State). Користувацький інтерфейс (UI) — це суцільний мінливий стан. Кнопка натиснута або ні, вікно активне або ні, користувач зробив свайп. У чистій ФП-парадигмі стану немає (Immutability). Намагатися малювати UI або обробляти введення від користувача на чистому ФП — це моделювати мінливий світ за допомогою статичних математичних формул. Це страшенно незручно і виглядає як мазохізм.
  2. Проблема послідовності (Sequence). Класична бізнес-транзакція (відкрили транзакцію -> перевірили баланс юзера -> списали гроші -> оновили статус -> закрили транзакцію) — це імперативний, послідовний алгоритм команд. ФП за своєю природою мислить категоріями обчислень, а не команд. Тому моделювати послідовні транзакції в чистому ФП — це надзвичайно складна і неприродна задача.
  3. Побічні ефекти та Монади (Side Effects). Логування, запис у БД, виклики сторонніх API — для чистого ФП це все “побічні ефекти”. Чиста функція має лише прийняти дані і повернути результат без зовнішнього впливу. Щоб якось вирішити це обмеження (і щоб програма могла хоч щось записати в базу), фанати ФП придумали обхідний шлях — “Монади” (на кшталт IO Monad). Але писати реальну бізнес-логіку на Монадах — це гарантовано зламати мозок 90% вашої команди розробників.

Тому сучасний світ працює на гібридній архітектурі:

  1. Макро-рівень (Бізнес-процеси та UI) — це ООП. Як тільки нам потрібна послідовна логіка, транзакції, збереження стану або малювання інтерфейсу — ми використовуємо ООП і патерни (контролери, сервіси). Побудувати великий Enterprise суто на функціях — це отримати непідтримуване спагеті з аргументів.
  2. Мікро-рівень (Трансформація даних) — це ФП. Усередині методів, коли треба просто відфільтрувати колекцію чи знайти потрібний елемент, ми використовуємо map, filter, чисті функції. Тут ФП безумовно перемагає.

Більше того, ФП часто спрощує класичні патерни. Наприклад, патерн “Стратегія” в ООП вимагав інтерфейсу і низки класів. З появою лямбд він перетворився на просту передачу функції як параметра. Суть залишилася (ізолювати алгоритм від клієнта) — синтаксис спростився.

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

Готові? Рухаємося до Архітектурних драйверів.


🔑 Висновки розділу

Що варто запам’ятати з цієї історії болю:

  • Еволюція — це вирішення попередньої проблеми. Структурне програмування вбило Spaghetti Code, а ООП перемогло “комуналку” глобальних станів. Не знаючи цього болю, ви не зрозумієте логіку патернів.
  • Сліпе слідування правилам минулого — антипатерн. Старе правило “одна точка виходу з функції” зараз породжує нечитабельний Arrow Code. Замінюйте його на сучасний Fail Fast (Guard Clauses).
  • Патерн — це інструмент, а не самоціль. Якщо ви застосовуєте GoF-патерн (“Стратегію”, “Фабрику”) там, де було б достатньо звичайного if/else — ви захворіли на “Патерніт” та займаєтесь овер-інжинірингом.
  • Від абстракції до землі. Архітектурне мислення будується згори донизу: спочатку розуміння ідеології ООП, потім правила розподілу відповідальності (GRASP), далі техніка безпеки зв’язків (SOLID), потім мікро-рішення (GoF), і лише на найнижчому (найширшому) рівні — Макро-архітектура (Enterprise, мікросервіси та HighLoad).
  • Жодної війни парадигм. Ми живемо в епоху гібридної архітектури. ООП ідеально працює на макро-рівні (структура модулів і класів), а Функціональне програмування рятує нас від проблем стану (Mutable State) на мікро-рівні всередині функцій.