Хватит спорить про функциональное программирование и ООП

habr.png

Пост содержит некоторое количество стёба, минздрав убедительно просит неподготовленного читателя воздержаться от прочтения.
Статьи на тему «ФП лучше» или «ООП лучше» напоминают дебаты, что же лучше для обеда, вилка или ложка. Традиционно джуны начинали с ложки, но кто-то очень авторитетный однажды поведал, что ест только мясо и использует вилку, поэтому зародилась новая мода — есть вилкой. Ей едят и каши, и супы, и даже умудряются лакать смузи. Интернет завален статьями, какие мы молодцы, что научились есть смузи вилкой и преодолели все грабли. Это и смешно и грустно, с одной стороны даёт конкурентное преимущество бывалым ребятам, которые показывают сверхрезультаты просто игноррируя этот хайп, с другой приходится переучивать коллег и сотрудников, вычищая из их головы нанесённый ветром мусор. В этой статье я постараюсь рассказать своё видение, которое не претендует на абсолютную истину, но очень хорошо работает на практике
Как принято в науке, начинём с определений. Классичесткое определение ООП включает в себя следование принципам наследования, инкапсуляции и полиморфизма. Но если у нас нет одного из этих составляющих, будет ли это ООП? И если нет, то что это будет? Пока занудная часть аудитории зависла над этим непрактичным и ничего нам не дающим вопросом, вспомним, что было до ООП. А до него было процедурное программирование. И основная идея ООП на тот момент была в связи данных и функций для их обработки. Идея простая, но довольно революционная, сложно представить насколько, но об этом позже.

Функциональное программирование, в вольной трактовке, рассматривает программу как математическую формулу. Формулы хорошо формализуются и при одинаковых входных данных возвращают одинаковые выходные. Вся программа состоит из подпрограмм, следующих тем же принципам. Чем это отличается от того-же процедурного программирования? ФП активно использует чистые функции, в идеале вся программа должна быть чистой функцией. Чистые функции не имеют сайд-эффектов, их можно легко меморизовать, легко тестировать и т.п… Можно сказать, в чистых функциях основная идея и преимущество ФП.

А теперь два вопроса:
— Можем ли мы использовать чистые функции в ООП?
— Можем ли мы привязывать функции к данным в ФП?
Ответ на оба из них утвердительный. Статические методы классов могут быть чистыми, даже методы экземпляров можно считать чистыми, если они не создают сайдэффектов и мы рассматриваем свойства экземпляра класса как параметры. Границы классических определений раздуваются и кто-то может быть не согласен, но на практике это просто работает. И такие методы формализуются и тестируются ничуть не хуже, чем классические чистые функции написанные по всем канонам. Про привязку методов к данным чуть сложнее, накладывают ограничения используемый язык и библиотеки. Скажем, в JavaScript это делается элементарно, не уверен за Haskell и Erlang. Теперь самое интересное, что это даёт, и почему уже ООП поднял такой хайп лет 20–30 назад. Если вы пишете небольшую программу — вы можете писать как хотите, кроме вашего чувства прекрастного это ни на что не влияет. Когда вы создаёте действительно большую программу — возникает проблема сложности. Не вычислительной сложности, считаем, что здесь мы делаем всё хорошо, а воспринимаемой сложности. Скажем, у вас есть 50 000 строк кода, и все они целесообразны. Как их организовать так, чтобы не сойти в с ума (или не уходить с работы в 11 ночи)? Мы не можем уменьшить колличество операций, но можем уменьшить число связей между ними (в этом нам и помогает инкапсуляция). Мы скрываем сложную реализацию за простым интерфейсом и дальше работаем только с интерфейсом. Например, интернет — жутко сложная штука, но большенству разработчиков достаточно знания протокола HTTP, чтобы выполнять свою работу и оставить сетевой, физический и другие уровни сисадминам. Чем слабее связанность наших модулей, тем меньше сложности на этапе их интеграции между собой. Чем меньше один модуль занет про другой, тем меньше их связанность. Привязка методов к данным внутри модуля помогает нам избавиться от этого знания у потребителей модуля. В этом основное прагматическое преимущество ООП. Над чем? Над подходом без привяки данных и методов. ФП, как мы выяснили, про это не говорит. Вы можете передавать классы как аргументы в чистые функции, или можете использовать чистые функции как методы класса, одно другому не противоречит, а только дополняет.

По практике, где работает преимущественно один подход, а где преимущественно другой? Когда я пишу бекенд на NodeJS, он сам собой как-то получается в функциональной парадигме. Почему? Потому что запрос на сервер — это по природе своей функция, с фиксированным инпутом и аутпутом. Функциональный подход ложится на серверные запросы очень естественно и код получается компактнее и гибче. Когда я создаю фронтенд для браузера — как правило лучше работает ООП, потому что кроме инпута и аутпута у нас есть поток пользовательских событий, а так же программные события, такие как начало и конец анимаций, запросы на сервер и т.п… Функциональный подход работал бы, если бы нужно было просто отрисовать статическую страничку без интерактива, при использовании ФП во фронтенде страдает либо интерактив, либо время разработки (по нашим замерам, раза в 3). Почему?

Любая парадигма программирования базируется на определённой модели реальности, а модель это всегда упрощение. В ФП модель накладывает больше ограничений на мир, поэтому её проще формализировать. По той же причине, когда она становится не релевантна условиям задачи — она начинает требовать больше костылей. Например, ФП на фронтенде решили проблему пользовательского ввода созданием массива событий (экшенов, привет redux). Но это является нерелевантной абстракцией, которая кроме того, что бъёт по производительности, здорово увеличивает время разработки. Таким подходом можно создать todo-лист, но на действительно больших проектах придётся разбивать лбом кирпичи, а потом писать победные статьи для таких же несчастных. Для сравнения, когда мы писали биржевой терминал (на vanilla js, естественно) с canvas, svg и обновлением несколько раз в секунду по websockets, в некоторых часто обновляемых компонентах мы не удаляли div’ы, а переиспользовали, так как их пересоздание — сравнительно дорогая операция в браузере (и это важно, если у вас действительно большое приложение). Если вы программируете на ФП на фронте — у вас даже такой мысли не возникнет, так как вы уже смирились с иммутабельностью (кстати, замечательная вещь для работы с многопоточностью, которой в JS не бывает), с тем, что каждый экшн проходит через каждый редьюссер и другим хламом. С другой стороны, на бекенде часто получается обходиться без классов совсем, так как там, как раз, можно избежать расходов на их создание, так как условия задач очень релевантны модели ФП. Но, в основной массе, Java и PHP разработчики как раз не спешат изучать ФП, в первых рядах фронтендеры, которым это реально меньше всего нужно. Как упражнение для ума — интересно конечно, только программы получаются г… но, а кому-то ими пользоваться. При том, что фронтенд — сравнительно молодой раздел IT, и своих нерешённых задач там на несколько поколений. Чем, казалось бы, не упражнение для ума?

© Habrahabr.ru