ООП мертве? Як парадигми програмування воюють зі складністю
Програмісти сперечаються роками: процедурне програмування чи об’єктно-орієнтоване? Чи, може, функціональне? Ці парадигми формують не лише синтаксис коду, а спосіб мислення. Холівари виникають не тому, що хтось неправий — а тому, що різні підходи пристосовані до різних задач.

«Об’єктно-орієнтоване програмування — надзвичайно погана ідея, яка могла з’явитися лише в Каліфорнії» — Едсгер В. Дейкстра
«Ця парадигма [ООП] тісно відображає структуру систем „реального світу“, а отже, добре підходить для моделювання складних систем зі складною поведінкою» — Ніклаус Вірт
Двоє легендарних комп’ютерних вчених — і настільки протилежні думки. Хто має рацію? Щоб зрозуміти, треба поринути в історію — від машинного коду до мультипарадигмальних мов програмування.
Парадигма програмування — це спосіб мислення і написання програм, який визначає, як будуються програми та як вирішуються задачі.
Різні парадигми пропонують різні підходи — наприклад, писати програму як набір інструкцій (процедурна)*, як взаємодію об’єктів (об’єктно-орієнтована) або як обчислення через функції (функціональна).
Від перфокарт до спагеті-коду
Початок програмування був суворим. У 1940–1950-х роках програмісти буквально писали нулями та одиницями.
Налагодження програми могло зайняти дні. Наприклад, пошук помилки в програмі ENIAC вимагав перевірки тисяч фізичних з’єднань.
Асемблер трохи полегшив справу, дозволивши записувати інструкції в більш-менш зрозумілій формі (MOV AX, BX). Але писати навіть просту програму на кілька сотень інструкцій — це був виклик. Також потрібно було розуміння низькорівневої архітектури комп’ютера і апаратних обмежень.
У
10 INPUT "Enter a number"; A 20 IF A = 0 THEN GOTO 100 30 PRINT "Not zero" 40 GOTO 10 100 PRINT "Goodbye"
Цей підхід працював, поки програміст міг тримати всю програму в голові. Але як тільки об’єм коду виростав — його переставали розуміти навіть автори. Крім того, треба врахувати, що і сучасні можливості типу окремих файлів і функцій на той час були доступні далеко не завжди.

Структурне програмування: перша революція
У 1968 році Едсгер Дейкстра опублікував статтю «Go To Statement Considered Harmful», яка стала переломним моментом в історії програмування. Він стверджував, що GOTO — це «отрута» для програми: вона руйнує передбачуваність і контроль потоку.
Рішенням стало структурне програмування: використання конструкцій if, while, for, блоків і вкладених структур. Завдяки цьому програма почала нагадувати дерево з чіткими гілками, а не хаотичний клубок стрибків від одного рядка коду до іншого.
Коли до цього підходу додалася можливість групувати логіку у вигляді процедур і функцій, з’явився процедурний стиль програмування. Він дозволяв організовувати код у зрозумілі блоки з назвами, які можна багаторазово викликати, що стало серйозним поштовхом у розвитку складніших програмних систем.
Мова C стала ідеальним інструментом для цього підходу. Вона дозволила створювати функції, модулі, маніпулювати пам’яттю, при цьому залишаючись максимально близькою до заліза.
«C — химерна, недосконала, але надзвичайно успішна» — Деніс Річі
UNIX написали на C — і це був доказ: можна створити повноцінну операційну систему, використовуючи даний підхід. Проте коли виникла потреба створювати програмні системи для автоматизації процесів у різних сферах діяльності людини, стало зрозуміло, що потрібен інший інструмент, який би дозволив простіше створювати моделі реального світу та повторно використовувати написаний код.
До речі, в тому самому 1968 році виник термін «криза програмного забезпечення», пов’язаний зі збільшенням продуктивності комп’ютерів і складністю проблем, що вони можуть вирішувати.
Основні проблеми пов’язані з «кризою програмного забезпечення»: проєкти перевищували бюджети в рази, не встигали в терміни, а якість була незадовільною. Причина — старі методи не масштабувалися. Ті самі проблеми будуть виникати і далі, стимулюючи використання нових підходів.
ООП: моделюємо світ об’єктами
Процедурний підхід добре працював для системного програмування, але в 1980-1990-х роках характер задач змінився. Бізнес почав вимагати автоматизації складних процесів — банківських систем, управління запасами, моделювання реальних об’єктів.
Використання послідовності дій для їх опису виявилось занадто складним. Найголовнішою проблемою стало дублювання логіки, яке призводило до збільшення кодової бази та ускладнення самого коду через велику кількість перевірок різноманітних умов. Потрібен був новий рівень абстракції.
З’явились Simula, Smalltalk, C++, Java. І з ними — об’єктно-орієнтоване програмування. Клас, об’єкт, наслідування, інкапсуляція — нова мова для моделювання реального світу.
«Об’єктно-орієнтоване програмування, яким воно з’явилося в Simula 67, дозволяє будувати структуру програмного забезпечення на основі структур реального світу та надає програмістам потужний спосіб спростити проєктування і створення складних програм» — Девід Ґелернтер
Книга Граді Буча з об’єктно-орієнтованого проєктування починається з пояснення проблеми складності і того, що ООП — це інструмент для її подолання.
«Основне завдання інженерії програмного забезпечення — це керування складністю. Складність програмних систем часто перевищує складність апаратного забезпечення, на якому вони працюють. ... Мета об’єктно-орієнтованої розробки полягає в подоланні притаманної ПЗ складності шляхом поділу її на керовані частини» — Граді Буч, Об’єктно-орієнтований аналіз та проєктування з прикладами застосування
ООП дозволяє створити програму, яка не просто виконує інструкції, а поводиться як взаємодіючі сутності. Наприклад, банківська система — це рахунки, клієнти, транзакції. Вони мають стан і поведінку. Саме це дозволяє ООП: будувати масштабовані, підтримувані системи. Проте мислити послідовністю команд при описі конкретної задачі для людини простіше. Для комп’ютера більш природно виконувати готову послідовність команд, а не створювати її спочатку з опису класів.
Саме тому продуктивність застосунків C++ була нижчою при використанні складних абстракцій і віртуальних викликах за звичайний C. Тим не менш це була плата за вищу продуктивність програмістів.
Ви не любите ООП?
«Ви не любите котів? Ви просто не вмієте їх готувати» — невідомий автор
Це ж можна сказати про об’єктно-орієнтоване програмування. За великим рахунком ООП просто інший підхід до структурування коду, він вимагає від програміста більш глибокого аналізу та напруження на етапі проєктування.
Зазвичай у коді, побудованому на основі вдало спроєктованих класів (ООП), ви побачите набагато менше конструкцій if, ніж у коді, написаному в процедурному стилі, який робить те саме. Це можна пояснити тим, що вибір послідовності інструкцій робиться в момент створення об’єкта, тоді як у процедурному підході перевірки умов зустрічаються безпосередньо під час виконання алгоритму. Імовірність зустріти дублювання логіки в процедурному коді також вища. При цьому мова не лише про copy-paste, а й про частини коду, які можуть бути написані по-різному, але з точки зору бізнес-логіки виконувати одну й ту саму функцію.
Водночас багато хто пише процедурний код під виглядом ООП. В цьому випадку ви побачите анемічну модель предметної галузі, часте використання сетерів/гетерів, класи без методів, або класи з тільки з методами без властивостей та велику кількість final.
Важливо розуміти, що не потрібно намагатись будь-яку задачу вирішити за допомогою ООП. Наприклад, алгоритм сортування.
З іншого боку, якщо у вас складна предметна галузь з широким набором сценаріїв, використання набору вдало спроєктованих класів може суттєво полегшити вам життя, зменшивши кількість і складність коду.
Проте справа не лише в синтаксисі — змінюється сам принцип розробки. ООП вимагає аналітичного мислення, і без нього дуже важко створити справді гарний код.

DDD, коли ООП вже не вистачає
На початку
У цей час набирає популярності Domain-Driven Design (DDD) — підхід, запропонований Ерiком Евансом. Його ключова ідея — розбивати складні предметні області на менші ізольовані частини (обмежені контексти) й описувати їх за допомогою єдиної мови (Ubiquitous Language), зрозумілої і для розробників, і для експертів домену.
DDD став не лише технічною практикою, а й способом організації співпраці між бізнесом і розробкою. Моделі створюються не ізольовано програмістами, а в діалозі з експертами предметної галузі. Це дозволяє мінімізувати розрив між кодом і реальною логікою бізнесу.
Технічно DDD добре поєднується з ООП: агрегати, сутності, об’єкти-значення та сервіси природно реалізуються через класи й об’єктні відносини. Проте DDD виходить за межі ООП — він задає методологію побудови архітектури, яка з часом еволювала в мікросервісні підходи та сучасні розподілені системи.
«Складності не слід уникати; нею слід управляти» — Ерік Еванс
Таким чином DDD став ще одним етапом у боротьбі зі складністю: якщо структурне програмування приборкало хаос коду, а ООП допомогло моделювати реальні об’єкти, то DDD дозволив масштабувати ці моделі на рівень цілих доменів і великих організацій.
Несподіваний поворот: Go — мінімалізм від Google
Проте складність притаманна не тільки задачам, але й інструментам. Як відповідь на ускладнення стандартів розповсюджених мов у
«Менше — це експоненційно більше» — Роб Пайк
Go — це експеримент з управління складністю через спрощення. Замість додавання нових можливостей Go прибрав те, що вважав зайвим. В противагу Java чи C++ Go суттєво простіший.
«Go було створено простим, щоб уникнути тієї складності, яку ми бачили в інших мовах, особливо в тих, що надихнули нас, як-от C++ та Java» — Роберт Ґріземер
Go не підтримує ООП із наслідуванням класів, але містить деякі базові об’єктно-орієнтовані інструменти — інкапсуляцію, інтерфейси та методи. Замість наслідування Go використовує композицію, а поліморфізм досягається через інтерфейси. Це дозволяє писати код без надлишкової складності класичних ООП-мов.
Go дозволяє структурувати код через прості структури та методи. Проте має менші можливості по моделюванню складних предметних галузей. Go — радше процедурна мова і це плата за високу продуктивність коду.
З іншого боку, оскільки велика кількість програмістів пишуть процедурний код, не використовуючи об’єктно-орієнтовані можливості, то і при переході на Go не відчують дискомфорту від їх відсутності.
А що з іншими парадигмами?
Теоретичні основи функціонального програмування виникли ще до появи комп’ютерів — у
Оскільки ця парадигма має чіткий математичний базис, мови на зразок Haskell дають змогу виявляти багато помилок ще на етапі компіляції, зокрема типові невідповідності чи суперечності в структурі програми.
Проте кількість задач, які зручно вирішувати в рамках функціональної парадигми, значно менша у порівнянні з іншими. Про це свідчить статистика використання мов програмування.
Найбільш розповсюджений представник декларативного програмування — SQL. В розробці програм він має скоріше допоміжну функцію для отримання даних з БД і недооцінений великою кількістю розробників.
Не раз доводилось зустрічати випадки, коли розробник вибирає масив даних з БД і починає обробляти цей масив в коді замість того, щоб отримати бажаний результат за допомогою SQL-запита. Програма стає як менш зрозумілою, так і менш продуктивною. Проте це тема для іншої розмови.
Загалом декларативні парадигми не можуть повністю закрити поширені класи задач, тому і маємо «протистояння» підходів, які найчастіше використовуються: процедурного та об’єктно-орієнтованого.
Існують і інші парадигми, але вони менш розповсюджені і тому ми не будемо їх розглядати.

Мультипарадигмальність: нова норма
Сучасні мови програмування вже давно перестали бути «чистими» представниками лише однієї парадигми. Вони еволюціонували, інтегруючи у себе найкращі підходи з різних стилів програмування — процедурного, об’єктно-орієнтованого, функціонального та навіть декларативного (наприклад, у вигляді анотацій). Це дозволяє розробникам обирати найзручніший стиль для вирішення конкретної задачі, не обмежуючи себе рамками однієї парадигми.
Python — яскравий приклад мультипарадигмової мови. Він однаково добре підходить і для написання простих скриптів у процедурному стилі, і для побудови великих систем з використанням ООП, і для функціонального програмування завдяки підтримці вищих функцій, лямбд і генераторів.
Java — колись майже синонім класичного ООП, поступово відкрився для інших підходів: у сучасних версіях з’явилися лямбда-вирази, стріми, функціональні інтерфейси, що значно розширило інструментарій розробника.
JavaScript та PHP теж починали як прості скриптові мови, але сьогодні підтримують і ООП (класи, наслідування, інтерфейси), і функціональний стиль (callback-функції, map/reduce/filter), і навіть елементи реактивного та асинхронного програмування.
Мультипарадигмальність стала стандартом для нових мов і майбутнє програмування — за гнучкістю й здатністю поєднувати різні ідеї в одному рішенні. Саме тому вибір мови дедалі частіше визначається не її «чистотою», а зручністю поєднання різних підходів для розв’язання реальних задач.
Коли яку парадигму обирати
Функціональне програмування:
— Задачі без стану: трансформації, фільтрація, агрегація.
— Паралельні обчислення без загальнодоступних даних.
Де використовується: обробка потоків даних, компілятори, математичні обчислення.
ООП:
— Моделювання складних доменів з багатьма сутностями.
— Довготривалий стан та складна поведінка об’єктів.
Де використовується: enterprise-системи, ігрові рушії, симулятори.
Процедурне:
— Чітка послідовність операцій, мінімум абстракцій.
— Критична продуктивність, системний рівень.
Де використовується: ядра ОС, драйвери, вбудовані системи.
Декларативне:
— Опис бажаного результату, а не способу його досягнення.
— Складні запити та трансформації даних.
Де використовується: бази даних (SQL), конфігурації (YAML/JSON), веброзмітка (HTML/CSS).
Важливо: більшість сучасних проєктів комбінує підходи. Вибір залежить від конкретної частини системи, а не від проєкту загалом.
Ключові висновки
Розвиток парадигм та мов програмування — це еволюція, направлена на управління складністю, пов’язаною зі збільшенням масштабу задач.
Це відповідь на проблеми, з якими вже не справлялись старі підходи. GOTO не справлявся з тисячами рядків коду, процедури — з мільйонами, ООП — з розподіленими системами.
Не існує універсальної мови чи парадигми. Важливо вміти обирати інструмент залежно від контексту.
ООП не мертве. Процедурний стиль — не пережиток. Функціональне програмування — не панацея.
Проблема часто в тому, що ми користуємось молотком там, де потрібна викрутка.
Водночас парадигми програмування можна розглядати як інший вимір мислення, у якому розробник рухається, створюючи програмний продукт. І що важливо — для цього не обов’язково змінювати мову програмування. Знання та комбінування парадигм відкриває більше свободи й можливостей у написанні коду. Це дозволяє адаптуватися до задач різного рівня складності, створювати гнучкіші архітектури й зрештою писати якісніше програмне забезпечення.
Навчіться думати в різних парадигмах — і холівари втратять сенс.
Примітка. Коректніше вживати термін «імперативна парадигма» замість «процедурна», адже саме імперативний підхід охоплює базові принципи управління станом програми через послідовні інструкції. Проте серед програмістів частіше йде мова про процедури та функції, адже знайомство з цим стилем відбувалося переважно через мови на кшталт C, де акцент робився саме на процедурному програмуванні.
Література та посилання
- Edsger Dijkstra — Go To Statement Considered Harmful
- Grady Booch — Object-Oriented Analysis and Design
- Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software
- Less is exponentially more
- Введення в управління складністю
Ілюстрації Юлії Дмитракович
Найкращі коментарі пропустити