Почему функциональное программирование?

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

Зачем переходить на функциональные технологии?

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

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

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

for( String item : items ) {
   process(item);
}
Вход в полноэкранный режим Выход из полноэкранного режима

становится

items.parallelStream().forEach( item -> process(item) );
Войти в полноэкранный режим Выход из полноэкранного режима

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

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

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

Мощное выражение

Разработчики программного обеспечения любят возможность выражать сложные идеи элегантными способами. Функциональное программирование позволяет разработчикам выражать некоторые довольно сложные идеи лаконичными способами. Такие распространенные конструкции, как циклы for, могут быть заменены потоками, которые абстрагируют общие операции, используемые в циклах. Несомненно, добавление лямбда-функций и потоков в 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*/);
Войти в полноэкранный режим Выход из полноэкранного режима

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

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

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

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

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

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

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

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

Потому что это новая горячая новинка

Всегда есть последняя горячая новая технология или парадигма. Новые технологии, языки и подходы появляются постоянно. Мы должны постоянно бросать себе вызов и оценивать новые технологии. Быть разработчиком программного обеспечения — это значит развивать технологии.

Но как разработчики программного обеспечения мы должны оценивать, какие технологии необходимы для обеспечения ценности. Изучение новой «горячей» технологии только потому, что она «горячая», не является разумным использованием вашего учебного времени. Это не означает, что вы не должны изучать функциональное программирование, скорее, вы должны оценить все плюсы и минусы для того типа приложений, которые вы пишете. Ограничит ли использование полнофункционального языка программирования ресурс разработчиков? Будет ли он существовать в долгосрочной перспективе или оставит вас на технологическом острове? С Java, по крайней мере, я знаю, что она теперь входит в основной язык.

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

Много раз я видел, как разработчики продвигают тот или иной подход как единственно верный. Но, как однажды сказал мне один друг, лошади для курсов. Для разных задач существуют разные инструменты. Я использую Python и PyTorch для ИИ. Часто существует множество вариантов инструментов и технологий, каждый из которых имеет свои преимущества и недостатки.

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

Много лет назад у меня был похожий опыт с Dependency Injection, и как Spring был единственным верным путем. Когда я спрашивал защитников о том, какие преимущества он дает, ответы не были четко сформулированы. Я наблюдаю подобное с некоторыми сторонниками функционального программирования, которые, похоже, ведут какую-то религиозную кампанию, а не беспристрастно оценивают технологию.

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

Заключение

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

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

Оцените статью
Procodings.ru
Добавить комментарий