Парадигма — религия, или наука?

Зайду с козырей: КДПВ этой заметки имеет прямое отношение к тексту.

Амбидиректус Вульгариус
Амбидиректус Вульгариус

Если вы сфокусируете взгляд на синей рамке, вы увидите растопырившего лапы Амбидиректуса с вытянутой шеей и маленькой головой, развернутой влево. Перефокусируясь на красную рамку, вы познакомитесь с его приготовившимся нырнуть в стену справа Alter Ego, с плотно ввинченной в шею головой и замершими во взмахе назад передними конечностями.

Не знаю, подразумевал ли двоякую трактовку автор этой скульптуры, притулившейся во внутреннем дворике бюджетного отельчика в Базеле, но я слишком хорошо усвоил — единственный преподанный мне отцом лет в пять — урок фотографии: пространство до обреза должно быть больше с той стороны, куда направлен взгляд протагониста. Поэтому я сразу увидел обе ипостаси этого мутанта.

Я показал вам этого Амбидиректуса для того, чтобы проиллюстрировать основную мысль этого текста: споры о превосходстве той или иной парадигмы над остальными — не имеют никакого смысла и всегда являются религиозной войной остроконечников против тупоконечников. Попытаюсь продемонстрировать это.

ООП vs ФП

Это ложная дихотомия. Нет там никакого «или». Хороший код в 2025 году содержит практически в равных пропорциях функциональный и объектно-ориентированный код. В монархическую джаву тащат лямбды, данные (data) в хаскеле — мало чем отличаются от объекта. Складывать методы — внутрь экземпляра класса, или функции — внутрь неймспейса — одно и то же (хорошо, все-таки, что ни один хаскель-адвентист не знает моего домашнего адреса). В некоторых случаях проще писать код в функциональном стиле, и насквозь объектно-ориентированный руби всячески поощряет так делать уже 30 лет:

some_array.map(&λ1).filter(λ2).reduce(&λ3)

Эти методы противоречат классическому ООП: они не курочат receiver, но возвращают новый объект. Есть и мутирующие братья методов выше, с восклицательным знаком на конце, но их используют только либо совсем неофиты, либо, наоборот, профессионалы, твердо знающие, зачем именно здесь вдруг потребовалось изменение по месту.

Stream API в джаве просто невозможно было бы написать в чистой ООП-парадигме, а многие трюки с данным в хаскеле возможны только благодаря глобальной ленивости языка, но никак — не парадигме, и в тот же жадный идрис — уже приходится приносить практически ООП-шные объекты в качестве наборов данных. Джаваскрипт вообще начинался как »everything is a function», и посмотрите, куда нас это завело.

Полиморфизм вообще неотличим в ООП и ФП подходах.

В общем, нет никакого «чистого ООП» и «чистого ФП» в рамках прикладных задач. Некоторые — удобнее решать в почти незамутненной ООП-парадигме (круд наподобие бейскамповского, например). Некоторые — в кристальном ФП (длинные преобразования типа мап-редьюса, достаточно разок заглянуть в код хадупа/спарка, чтобы понять, почему). Но подавляющее большинство задач лучше всего решать с переключением стиля, точно так же, как лучше держать в гараже Toyota Tundra и Lotus Evija, а не один Chevrolet Lacetti на все случаи жизни. Ну, или уж тогда велосипед «Украина», как я.

ООП vs Акторная модель

Если уж и противопоставлять ООП чему-нибудь другому — там это акторной модели. Тут будет уместно процитировать Джо Армстронга:

We don«t have shared memory. I have my memory, you have yours, we have two brains, one each, they are not joined together. To change your memory I send you a message, I talk or wave my arms. You listen, you see, your memory changes, but without asking you a question or observing your response I do not know that you have received my messages. — Joe Armstrong, Concurrency Is Easy

Есть актор, процесс, объект, кто угодно. Снаружи может быть вызван (пока не так важно, как именно) его API. Что у него там внутри — не наше дело. Это детали имплементации. Еще пара слов от Армстронга:

To me, programs are black boxes. You send a message in and you get a message out. I don«t care if it’s written in Scala or Haskell or Erlang or Python or Ruby. I care about this relationship between inputs and outputs.

В такой парадигме вообще не важно, что скрывается за интерфейсом: типично-жосткий хаскель, или податливый к манкипатчингу руби. Но главное, что мы заведомо не можем быть уверены в том, что наше сообщение (вызов API) было понято (да и вообще получено). Вот тут и возникает первое серьёзное отличие в парадигмах:

в ООП мы априори считаем, что сообщение получено и понято, а если это не так — мы столкнулись с исключительной ситуацией

Самым знаменитым адептом этой концепции был поручик Ржевский с его пикап-линией: «Мадам, разрешить вам впердолить!». Разработка в рамках такой парадигмы неизбежно приводит к тому, что мы полагаемся на волю случая, как незадачливый Герман, свято убежденный в ждущем его за заломом банкомёта — тузе.

Акторная модель, с другой стороны, мобилизует разработчика думать сначала о плохом. Что будет, если сообщение потеряется? Будет неверно понято? Застрянет навсегда в очереди? Как ни странно, к такому типа мышления очень быстро привыкаешь, а в качестве награды — получаешь изящный код в success path.

Как по мне, основное преимущество акторной модели именно в этом: в ней гораздо сложнее допустить ошибку, связанную с недостатком внимания к странным неочевидным вариантам развития событий.

Довлеющая парадигма

Мне практически всё равно, в какой парадигме писать код. Я люблю повторять следующую максиму:

Я абсолютно убежден, что невозможно быть хорошим архитектором, если не владеть на уровне выше среднего джавой/дотнетом, js/ts, haskell/idris, erlang/elixir, ruby/perl/python. (Я не упоминаю c/c++, потому что с таким багажом — само собой разумеется.)

В переводе на гуманитарный, чтобы выбрать в меню: Blanquette de Veau или Crème brûlée — надо понимать, хотите ли вы сытное мясное рагу или вазюльку переслащенного сливочного крема. Тип задачи почти всегда подскажет, нужны ли вам объекты (да, если ваши бизнес-сущности прям просятся быть нарисованными диаграммой классов со свойствами и стрелочками между ними), или акторы (если диаграмма больше похожа на диаграмму последовательностей), или функции (бизнес-логика сводится к диаграмме состояний).

Да, мой дорогой придирчивый читатель, в подавляющем большинстве случаев — нам потребуются все три, и еще пять на сдачу. Но тут уже можно набросать правильную архитектуру: классы в монолите, акторы в микросервисах, функции в лямбдах.

Никто в современном мире не заставляет вас выбирать одну парадигму и слепо ей следовать.

Кругозор — прекрасная штука; не такая занудная, как эрудиция, не такая беспечная, как осведомленность, и не такая отталкивающая, как краткое содержание «Войны и мира» для поступающих в ВУЗы.

Поэтому каждый раз, когда вы видите перед собой новую задачу, имеет смысл сначала подумать, какой подход больше всего на нее похож. И да, на дворе подходит к концу первая четверть XXI века: все инструменты (языки) уже худо-бедно научились помогать разработчику с выбранной им парадигмой. Просто не нужно тащить функции в круд, акторы — в мап-редьюс, а объекты — в долгоживущие микросервисы с богатым внутренним миром.

Удачного парадигмоза!

© Habrahabr.ru