Несмотря на мое традиционно скептическое отношение к незнакомым технологиям, я признаю преимущество функционального программирования. Ниже приведен примерный список из плюсов, которые описывают эту парадигму программирования. Кофе-брейк #131. Роль Java в разработке в веб- и мобильных приложений. Функциональное программирование — за и против - 2

Безопасный параллелизм

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

for( String item : items ) {
   process(item);
}
А теперь он становится:

items.parallelStream().forEach( item -> process(item) );

Быстрый ввод/вывод

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

Краткость выражения

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

Почему бы не стать функциональным?

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

Интуитивно понимать всегда трудно

При написании кода вы пытаетесь общаться с компьютером? Если вам так важно общение с компьютером, почему бы не писать машинным кодом? Разумеется, это очень сложно, поэтому для облегчения придумали компьютерные языки. Они также позволяют программистам создавать выражения, понятные другим программистам. Поскольку программное обеспечение становится больше и сложнее, нам нужны стратегии для управления сложностью. Мы достигаем этого за счет абстракций и сокрытия информации. Класс, который вы используете в Java для отправки электронной почты, довольно сложен, но интерфейс этого класса прост. Он скрывает детальную реализацию, открывая нам только внешнюю сторону управления. Особенности языка, такие как фигурные скобки и квадратные скобки, сообщают нам структуру, такую ​​как условные операторы и циклы. Только теперь мы перемещаем циклы, или, вернее, рекурсию, и условия в функции:

for( String item : items ) {
    System.out.println(item);
}
Выходит:

items.foreach( item -> System.out.println(item) );
Потоковый и лямбда-подход, безусловно, короче. Вы можете выразить ту же функциональность с меньшим количеством кода. Проблема в том, что теперь мы скрываем фактическое поведение внутри методов, которые мы должны знать. Циклы в Java используют ключевые слова. С другой стороны, лямбда-функции могут реализовывать различные формы рекурсии, и только имя здесь указывает на то, что оно делает. Например:

boo.fooble( item -> System.out.println(item) );
Вы больше не можете просто читать код, чтобы понять структуру. Это затрудняет отслеживание потока выполнения. Другая особенность — цепочка функций, когда результат одной функции является вводом следующей без назначения промежуточной переменной.

boolean result = boo
    .fooble( /*some code*/ )
    .bobble( /*some code*/)
    .goober( /*some code*/);
Для автора этого кода это может иметь смысл, потому что он вроде бы написал каждую функцию и знает, что она делает. Однако для тех, кто плохо знаком с кодом, это мало что говорит о параметрах, процессах или возвращаемых значениях каждой функции. Но если вы напишете то же самое в чем-то, что присваивает типы, вы получите:

Car car = boo.fooble( /*some parameters*/);
Tyre tyre = car.bobble( /*some parameters*/);
int pressure = tyre.goober( /*some parameters*/);
Возможно, это не идеальная аналогия, поскольку параметры и анонимные функции — это не одно и то же, но смысл здесь в том, что длинное предложение с неизвестными результатами и параметрами трудно читать без более глубокого понимания того, что делают функции, описанные ниже. И поэтому возможно, что функциональное программирование одновременно очень выразительно в удивительно коротких сегментах кода, но не поддается пониманию для людей, плохо знакомых с программным обеспечением. Смысл классов и интерфейсов связан с сокрытием данных, с созданием повторно используемых фрагментов кода, которые не требуют от разработчика понимания или, возможно, даже просмотра реализации. Они существуют для того, чтобы разработчики могли работать с большими сложными системами без головной боли. Это удобный способ организации кода, связанного с конкретными сущностями. Тем не менее, я плохо понимаю, как функциональное программирование помогает структурировать большие сложные проекты. Вероятно, этому есть субъективные причины.

Чтобы произвести впечатление на других своими навыками

Некоторые разработчики программного обеспечения настолько не уверены в своих навыках, что пытаются произвести впечатление через написание излишне сложного кода. Будучи senior-разработчиком, я не особо восхищаюсь другими разработчиками ПО, которые пытаются писать “умный” код. Конечно, это относится не только к функциональному программированию; то же самое может произойти в любом стиле кодирования. Но я заметил определенное интеллектуальное тщеславие сторонников функционального программирования. Суть в том, что разработчики программного обеспечения не работают в вакууме. Этот “умный” код нужно будет поддерживать, и если его трудно понять, то потом его будет трудно читать и изменять, трудно отлаживать. При управлении командами у нас есть люди на всех уровнях, поэтому разумнее всего писать код так, чтобы он был понятен всем причасным. Вот почему у нас понятные длинные имена переменных, поэтому мы ограничиваем длину методов. “Умный” код заберет лишнее время, поэтому писать его не слишком разумно. Я считаю, что функциональное программирование использует те же аргументы относительно кратких и мощных выражений, что и Perl много лет назад. Сторонники Perl гордились его выразительностью, тем, как они могли писать программы, которые были бы очень мощными всего и при этом занимали всего несколько строк кода. Это было правдой. Проблема состояла в том, что понять их было трудно. Возможно, функциональное программирование попало в ту ​​же ловушку.

Потому что это новая мода

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

Потому что это единственный верный способ

Много раз до этого я видел, как разработчики продвигают тот или иной подход как единственно верный. Но как мне сказал один друг, для разных работ нужны разные инструменты. Например, лично я использую Python и PyTorch для искусственного интеллекта, хотя есть множество других инструментов и технологий, каждый из которых имеет свои преимущества и недостатки. Существуют веские причины рассматривать функциональное программирование как вариант для сервисов, которые нуждаются в значительной масштабируемости. Это связано с безопасностью параллелизма, предлагаемой этим подходом. Но вы также должны знать о затратах и ​​потенциальных рисках. Несколько лет назад у меня был похожий опыт с внедрением зависимостей и тем, что Spring считался лучшим для этой работы. Когда я спросил сторонников Spring о том, какую пользу он приносит, четких ответов мне не дали. Сейчас я наблюдаю нечто подобное у некоторых приверженцев функционального программирования, которые, похоже, участвуют в какой-то религиозной кампании, а не в беспристрастной оценке технологии. Кстати, внедрение зависимостей было отличной идеей, и теперь я могу четко определить его преимущества. Точно так же я думаю, что мои опасения по поводу функционального программирования связаны с тем, как оно используется, а не с тем, является ли это полезным и действительным подходом.

Заключение

Смысл этой статьи — не дать четкий ответ на вопрос о том, следует ли вам заниматься функциональным программированием. Суть в том, как вы сами оцениваете любую новую технологию или подход. Главное: не позволяйте своему эго закрывать от вас объективную оценку. Разработка программного обеспечения — это не доказательство ваших интеллектуальных способностей или ваших личных качеств. Речь идет о создании ценности в реальном мире. Если вам помогает функциональное программирование, то используйте его.