Что такое функциональное программирование и почему оно важно?
О функциональном программировании часто говорят как об академическом упражнении для теоретиков языков, и это жаль, потому что его главная идея вполне практична: чем меньше в коде вещей, которые могут неожиданно измениться, тем меньше ошибок вам придётся искать. Вот и всё. Остальное — детали.
Главная идея: чистые функции
Чистая функция при одних и тех же входных данных всегда возвращает один и тот же результат — и ничего не меняет за своими пределами. Никаких изменений глобальных переменных, никакой записи в базу данных, никакой зависимости от времени суток. Только так: на входе данные, на выходе результат, больше ничего не происходит.
Сравните это с типичной функцией в объектно-ориентированной кодовой базе, которая может читать из базы данных, изменять общий объект, писать что-то в лог и возвращать значение, — четыре разных действия одновременно, любое из которых может пойти не так, и ни одно не очевидно, если смотреть только на место вызова функции.
Иммутабельность: данные, которые не меняются
Функциональное программирование также во многом опирается на неизменяемые (иммутабельные) данные: созданное значение не изменяют на месте. Нужна изменённая версия? Создаётся новая. Это кажется расточительным, пока не задумаешься об альтернативе: если данные могут изменяться из десяти разных мест, то, чтобы найти виновника ошибки, придётся проверить все десять. Если же данные нельзя изменить после создания, этого класса ошибок просто не существует.
Почему это важно в повседневной работе
- Проще тестировать. Чистой функции не нужна никакая подготовка, кроме входных данных, — не нужно подменять базу данных моками или имитировать текущее время. Вызвали, проверили результат.
- Проще понимать. Чтобы понять, что делает функция, достаточно её прочитать, без отслеживания по всей кодовой базе, что ещё может затрагивать её данные.
- Безопаснее многопоточность. Огромная доля ошибок многопоточности возникает, когда два потока одновременно изменяют одни и те же данные. Иммутабельные данные делают этот класс ошибок структурно невозможным.
- Предсказуемее отладка. Когда состояние не может неожиданно измениться, у вопроса «почему изменилось это значение» гораздо меньше возможных ответов.
Чтобы извлечь пользу, не нужен «функциональный язык»
Такие языки, как Haskell и Clojure, изначально построены вокруг функционального программирования, но, чтобы получить реальную пользу от этих идей, менять язык не нужно. JavaScript, Python, Java и большинство современных языков позволяют писать чистые функции и там, где это уместно, работать с данными как с неизменяемыми. Методы массивов вроде map, filter и reduce — это и есть функциональное программирование, просто под другим именем, и большинство разработчиков постоянно ими пользуются, даже не задумываясь об этом.
Практический вывод — не «перепишите всё в функциональном стиле», а вот что: если функцию можно сделать чистой, а её данные — неизменяемыми, не искажая архитектуру, делайте так. Вы заметно меньше времени потратите на отладку состояния, которое изменилось там, где вы этого не ждали.
Мы применяем эти принципы в заказном ПО, которое разрабатываем, именно потому, что код, который проще тестировать и понимать, реже ломается в продакшене, — а для клиента это гораздо важнее через полгода после запуска, чем в первый день.