Написать пост

Когда применять функциональное программирование, а когда ООП — отвечают эксперты

Аватар Никита Прияцелюк

ООП или функциональное программирование? А может, всё сразу? Узнаём у экспертов, когда нужно применять ту или иную парадигму программирования.

Многие слышали про функциональное программирование и, возможно, задавались вопросом: «А зачем оно, когда есть ООП?». Мы спросили у экспертов, когда стоит использовать ту или иную парадигму.

Каждый инструмент хорош для определённого набора задач в определённой ситуации. Как некоторые могут забивать саморезы молотком, так и фанаты той или иной парадигмы при любых вводных могут задействовать то, чем лучше владеют. Однако опытные разработчики всё-таки руководствуются не симпатией к какой-либо технологии, а эффективностью. Также могут присутствовать ограничения, задаваемые заказчиком или программным окружением. Важен целый ряд факторов и характеристик разрабатываемого решения, его дальнейшей поддержки. Разумеется, выбор чаще опирается на собственный опыт, и в самом начале проектирования не всегда удаётся угадать будущее развитие и все нюансы использования ПО, так что универсального решения нет.

Объектно-ориентированное программирование (ООП) является более «традиционной» парадигмой. С её помощью разработано несчётное количество программ, в том числе огромные промышленные системы в финансовых организациях, телекоммуникации на производстве, складах, транспорте. Незнание принципов ООП фактически перекроет доступ ко всем этим системам.

Тут надо понимать, что если десяток лет пользовался ООП, то и сознание подстраивается под эту модель, проще проектировать именно через объекты и вызовы методов, а не через потоки данных и данные. Один из главных минусов ООП — чудовищная, запутанная система классов для системы, которая разрабатывается десятки лет большой командой. Как правило, некоторые «ядерные» классы оказываются на «дне» модели, их никто не рискует трогать даже в ущерб скорости разработки и устойчивости ПО. Появляются классы-наследники, переопределённые методы и прочий мусор, который со временем тоже становится «ядром системы».

Что касается функционального программирования (ФП). В каком-то сильно упрощённом виде оно используется и при ООП — в школах первые программы пишут функциями. С него нужно начинать изучение языков программирования, им же и завершать. Далее, в зависимости от нужд конкретного проекта, можно углублять знания той парадигмы, которая больше используется. Парадигма ФП влияет и на программирование, и на проектирование программного обеспечения. Для высоконагруженных систем переход к обработке потоков данных может быть спасением. Для выбора этой парадигмы в «большой» системе как минимум нужно иметь много данных и большую нагрузку (много вызовов, много пользователей). С одной стороны, она может дистанцировать бизнес-модель от реализации, с другой — позволит вовремя отвечать на запросы пользователей и иных внешних систем. Для небольших программ выбор ФП возможен, тут больше дело вкуса. Однако новичку может быть непросто разделить бизнес-модель на данные и потоки данных и спроектировать так, чтобы данные не хранились в классах и было чистое ФП.

Что выбрать новичку для изучения?

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

Я считаю, что каждый разработчик должен иметь представление и об ООП, и о ФП, знать сильные и слабые стороны каждого подхода и на основе этого определять, что лучше использовать для решения конкретной бизнес-задачи.

ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных (например содержат внутри себя ссылки на глобальные переменные) или коммуницируют с внешним миром (ввод-вывод).

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

Но эти подходы не являются взаимоисключающими. Нет необходимости выбирать только одну парадигму и следовать ей до конца. Вы можете передавать классы в чистые (то есть не связанные с внешними данными) функции или можете использовать чистые функции в качестве методов класса — одно другому не противоречит, а только дополняет. Если вы пишете простую и небольшую программу, следование той или иной парадигме — сугубо ваше личное мнение и видение прекрасного. Однако если вы пишете большой сервис с разноплановыми задачами, в определённый момент вы столкнетесь с необходимостью рефакторинга, так как для эффективного решения всех этих задач одного подхода будет недостаточно.

Приведу пример. Если вы пишете на Node.js, на первый взгляд удобнее использовать ФП. Дело в том, что сам по себе запрос на сервер — это функция с определённым входом и выходом (request, response). А работа с request’ом происходит с помощью цепочки функций (middleware), и результат (response) всегда будет одинаковый, если в качестве аргументов передавать одни и те же значения. Функциональный подход здесь смотрится естественно. С другой стороны, если Node.js-сервис подразумевает работу с БД, для описания моделей и работы с ними удобнее применить ООП-подход. Примером служит популярная библиотека sequelize.

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

ООП vs. ФП — вечная дилемма. Мне кажется, что наибольшей эффективности в разработке можно добиться, только если миксовать подходы. Точно не стоит писать проект только на ФП, потому что такой подход сильно ограничивает разработку: нужно постоянно прорабатывать поведение state.

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

Всегда стоит думать о логике проекта. Если в приложении много динамики, то архитектура REST API из функционального программирования отлично сработает, так делают те же ребята из tutu.ru. То же самое работает и наоборот — если у вас обычное сервисное приложение, где пользователю отображают JSON, то особо смысла использовать ФП нет.

«Когда применять функциональное программирование, а когда ООП?» — вопрос совсем непростой. Если посмотреть форумы, то понятно, что холивар возникает уже на этапе самого определения функционального программирования.

Если исходить из определения, что функциональный стиль — это когда результат выполнения кода всегда зависит только от поданных на вход значений, то лично я такой подход стараюсь применять как можно чаще. Это упрощает читабельность кода, его тестирование. Однако ФП более характеризуется тем, что аргументами одних функций являются другие, более простые функции, и вот это наиболее сложная часть, где можно легко выскочить за сложность вычислений O(n).

Не очень понятно, как сравнить ООП и функциональное программирование (ФП). Я считаю, что модели могут тесно пересекаться, не исключая друг друга, и где какую модель применять, зависит от архитектуры программы и задач, стоящими перед каждым модулем программы. Например ваш код имитирует движение транспортного средства (ТС). Нужно каждую секунду вычислять координаты ТС, его скорость, пройденный путь. Для того, чтобы это сделать, необходимо будет производить вычисления на основе предыдущих вычислений. Если применить функциональное программирование, то появляется необходимость хранения результатов вычислений и подачи их каждый раз на вход модели. Это создаст дополнительные логические сложности, поэтому в этой задаче лучше скомбинировать ООП и ФП. Для каждого ТС создаётся объект в стиле ООП, и каждый объект сам хранит свои предыдущие вычисления. Вам остаётся на вход подавать только ускорение, направление движения и время, в течение которого они действовали. Внутри же объекта ТС у вас будет два десятка методов, рассчитывающих его новое состояние. И вот тут рекомендация: стремиться большую часть из них сделать в виде простых функций, и только в одном или двух в вычисления добавить влияние его предыдущего состояния или используемого набора функций.

Сейчас порог входа в программисты очень высок, и изучать что-то одно и быть востребованным у программиста вряд ли получится.

Итак, какую парадигму выбрать?

ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных.

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

Тем не менее, эти подходы не являются взаимоисключающими. Вы можете передавать классы в чистые функции или использовать чистые функции в качестве методов класса. Нередко удачным подходом является именно смешение парадигм, а не использование какой-то одной.

При выборе парадигмы стоит смотреть на решаемую вами задачу, а также учитывать возможное развитие проекта, чтобы быть уверенным, что выбранная сегодня «правильная» парадигма не вынудит вас через полгода переписать весь проект.

Что до новичков, то определённо стоит изучить ООП, так как это более «традиционная» парадигма, которая много где используется. Но стоит иметь хотя бы общее представление о ФП, чтобы иметь у себя в арсенале ещё один инструмент.

Напоминаем, что вы можете задать свой вопрос экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков рубрики. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на experts@tproger.ru, мы расскажем, как это сделать.

Следите за новыми постами
Следите за новыми постами по любимым темам
34К открытий35К показов