Код, который легко читать и сложно сломать, — мечта любого разработчика. Функциональное программирование (ФП) даёт инструменты для этого: чистые функции, неизменяемые данные и композицию. В статье разберём, как применять ФП в JavaScript без академической скуки, с примерами и лайфхаками.

Digital-студия WNDER

Что такое функциональное программирование и зачем оно нужно

Функциональное программирование — это стиль, где вычисления строятся на функциях, а не на изменении состояния. Вместо того чтобы менять переменные и объекты, вы создаёте новые значения. Главные принципы: чистые функции, неизменяемость, функции как граждане первого класса и композиция.

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

Чистые функции: фундамент функционального стиля

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

Пример грязной функции:

let counter = 0;
function increment() {
  counter++;
  return counter;
}

Здесь функция зависит от внешней переменной и меняет её. Результат зависит от того, сколько раз её вызвали. Это грязно.

А вот чистая версия:

function increment(value) {
  return value + 1;
}

Теперь функция не трогает внешний мир. Её легко тестировать: передали 5 — получили 6, и всегда так.

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

Неизменяемость: не меняй — создавай новое

В функциональном стиле данные не изменяются. Вместо этого вы создаёте копии с нужными изменениями. В JavaScript для этого есть методы массивов: map, filter, reduce, а для объектов — spread-оператор.

Сравните императивный и функциональный подход к добавлению элемента в массив:

// Императивный: мутируем исходный массив
const numbers = [1, 2, 3];
numbers.push(4);
// numbers теперь [1, 2, 3, 4]

// Функциональный: создаём новый массив
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4];
// numbers остался [1, 2, 3], newNumbers — [1, 2, 3, 4]

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

Композиция и каррирование: собираем пазл из функций

Композиция — это объединение нескольких функций в одну. Результат одной становится аргументом другой. В JavaScript есть готовая функция compose в библиотеках вроде Ramda или Lodash, но можно написать свою:

const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = (n) => n + 1;
const double = (n) => n * 2;
const addOneThenDouble = compose(double, addOne);
console.log(addOneThenDouble(5)); // (5 + 1) * 2 = 12

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

function greet(greeting, name) {
  return `${greeting}, ${name}!`;
}

const curriedGreet = (greeting) => (name) => `${greeting}, ${name}!`;
const sayHello = curriedGreet('Привет');
console.log(sayHello('Вася')); // Привет, Вася!

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

Лайфхак: Используйте каррирование для настройки функций с повторяющимися аргументами. Например, создайте функцию логирования с фиксированным префиксом.

Практические приёмы и сравнение подходов

Вот несколько приёмов, которые можно внедрить уже сегодня:

  • Используйте map, filter, reduce вместо циклов for — код становится декларативнее.
  • Избегайте изменения аргументов функции. Если нужно изменить объект — сделайте копию через {...obj, field: newValue}.
  • Для обновления вложенных объектов используйте глубокое копирование или библиотеки вроде Immer.
  • Выносите побочные эффекты (запросы, логирование) в отдельные функции, а ядро логики делайте чистым.

Сравним императивный и функциональный подходы в таблице:

Аспект Императивный стиль Функциональный стиль
Изменение данных Мутирует исходные структуры Создаёт новые копии
Читаемость Часто требует вникания в циклы Декларативен: что делаем, а не как
Тестирование Сложнее из-за побочных эффектов Легче: чистые функции предсказуемы
Переиспользование Ниже из-за связанности с контекстом Выше: функции независимы

Что в итоге

Функциональное программирование в JavaScript — это не религия, а набор приёмов, которые делают код надёжнее. Начните с малого: делайте функции чистыми, не мутируйте данные, используйте map/filter/reduce. Постепенно внедряйте композицию и каррирование. Главное — не фанатизм, а здравый смысл: иногда императивный цикл проще и быстрее.

Помните: цель — не следовать догмам, а писать понятный и поддерживаемый код. ФП даёт для этого мощные инструменты.

Студия WNDER