Чистые функции в JavaScript

Чистая функция всегда возвращает одинаковый результат для одинаковых входных данных и не вызывает побочных эффектов. Используйте чистые функции для повышения тестируемости, предсказуемости и возможности кэширования (мемоизации). Там, где требуется взаимодействие с внешним миром, комбинируйте чистые и нечистые функции по границам системы.
Что такое «чистая функция»
Чистая функция — это функция, которая:
- всегда возвращает одно и то же значение при одинаковых входных параметрах;
- не изменяет внешнего состояния и не выполняет побочных действий (console.log, модификация глобальных переменных, обращение к сети, манипуляции DOM и т.п.).
Краткое определение: чистая функция = детерминизм + отсутствие побочных эффектов.
Характеристики чистой функции
Постоянный результат
Чистая функция возвращает одинаковый результат для одинаковых входных данных, независимо от контекста или числа вызовов.
Пример (чистая):
function multiply(a, b) {
return a * b;
}
multiply(2, 3); // 6
multiply(2, 3); // 6
multiply(2, 3); // 6Пример (нечистая):
function multiplyRandomNumber(num) {
return num * Math.floor(Math.random() * 10);
}
multiplyRandomNumber(5); // непредсказуемо
multiplyRandomNumber(5); // непредсказуемоФункции с использованием случайности, времени, состояния или внешних источников данных не являются чистыми.
Отсутствие побочных эффектов
Побочный эффект — любое наблюдаемое изменение вне области функции: запись в глобальную переменную, модификация переданного объекта, вывод в консоль, обращение к сети или изменение DOM.
Нечистая версия:
let count = 0;
function increment() {
count++;
console.log(count);
}
increment(); // 1
increment(); // 2Чистая версия — принимаем состояние как аргумент и возвращаем новый результат, не модифицируя внешние переменные:
function increment(count) {
return count + 1;
}
increment(1); // 2
increment(1); // 2Дополнительные правила при проектировании чистых функций
- Не изменяйте переданные аргументы; при необходимости работайте с копиями (например, […arr], {…obj}).
- Всегда возвращайте значение; функция без возвращаемого значения и без побочных эффектов ничего не делает.
- Не полагайтесь на внешнее состояние — все зависимости должны приходить через параметры.
Важно: чистота функции определяется её контрактом поведения, а не тем, как она реализована внутри. Функция может быть реализована эффективно и оставаться чистой.
Преимущества чистых функций
Тестируемость
Чистые функции легко тестировать: для заданных входных данных вы всегда знаете ожидаемый результат. Нет необходимости строить сложные подмены окружения или мокать глобальные состояния.
Мемоизация (кеширование результатов)
Поскольку чистые функции детерминированы и не имеют побочных эффектов, их удобно кешировать: для набора аргументов можно хранить уже вычисленный результат и возвращать его повторно. Это полезно для дорогостоящих вычислений.
Простой пример мемоизации для функции с примитивными аргументами:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const slowMultiply = (a, b) => {
// условно дорогое вычисление
return a * b;
};
const fastMultiply = memoize(slowMultiply);
fastMultiply(2, 3); // вычисляет и кеширует
fastMultiply(2, 3); // возвращает из кешаЗамечание: мемоизация корректна только для чистых функций. Для функций, зависящих от внешнего состояния, кеш может выдавать устаревшие результаты.
Параллельность и безопасность потоков
Чистые функции не изменяют разделяемое состояние, поэтому их можно безопасно запускать параллельно (в Web Workers, потоках и т.д.). Это уменьшает риск гонок и блокировок.
Предсказуемость и отладка
Из-за детерминизма поведение системы проще воспроизводить и отлаживать. Лог ошибок и воспроизводимые тесты становятся естественной частью процесса.
Когда чистые функции не подходят
Чистые функции — не панацея. Они не решают задачи взаимодействия с внешним миром:
- Работа с базой данных, сетевые запросы, ввод/вывод файлов — обязательные побочные эффекты.
- Логирование, чтение системного времени, пользовательский ввод — тоже побочные эффекты.
В таких местах используют нечистые функции, но рекомендуется ограничивать их область и выносить побочные операции к «краям» архитектуры (границы модуля, слой адаптеров).
Практика: как сделать нечистую функцию чистой
- Передавайте все внешние зависимости как аргументы (время, генератор случайных чисел, состояние).
- Не мутируйте аргументы — возвращайте новые значения.
- Выносите побочные эффекты в отдельные функции, которые вызываются один раз в краю приложения.
Пример: чтение и запись состояния (нечистая):
let state = { count: 0 };
function incrementInPlace() {
state.count += 1; // мутируем глобальное состояние
}Переработка в чистую логику и нечистый «адаптер»:
// чистая логика
function incrementPure(state) {
return { ...state, count: state.count + 1 };
}
// нечистый адаптер на краю приложения
function applyIncrement() {
state = incrementPure(state); // здесь — единственная точка мутации
}Такой подход упрощает тестирование: incrementPure легко протестировать, а applyIncrement концентрирует побочный эффект в одном месте.
Примеры полезных паттернов и сниппеты
- Непосредственно чистая обработка массивов:
function addTask(tasks, task) {
return [...tasks, task]; // не мутируем исходный массив
}- Чистая композиция функций:
const compose = (f, g) => x => f(g(x));- Пример мемоизации для функций с объектами как аргументами: используйте слабые карты (WeakMap) для сложных структур, но помните о ключах и жизненном цикле данных.
Решающее дерево: стоит ли делать функцию чистой?
flowchart TD
A[Нужен вывод/побочный эффект?] -->|Да| B[Оставить побочный эффект, но локализовать на краю]
A -->|Нет| C[Можно ли передать все зависимости как аргументы?]
C -->|Да| D[Реализовать функцию как чистую]
C -->|Нет| B
D --> E[Поддерживает ли функция мутацию аргументов?]
E -->|Да| F[Копировать аргументы и вернуть новый объект]
E -->|Нет| G[Можно тестировать и мемоизировать]Чек-листы по ролям
Разработчик:
- Передаю ли я все внешние зависимости через параметры?
- Не мутирую ли я входные данные?
- Есть ли у функции явный возвращаемый результат?
Ревьюер:
- Определена ли граница побочных эффектов?
- Можно ли протестировать функцию изолированно?
- Подходит ли мемоизация и безопасна ли она здесь?
Тестер:
- Покрывают ли тесты кейсы с одинаковыми входными данными?
- Проверены ли случаи с граничными значениями и null/undefined?
Ментальные модели и эвристики
- Детерминизм: если функция зависит только от аргументов — она, скорее всего, чистая.
- Границы ответственности: держите побочные эффекты на краю приложения.
- Минимизация состояния: чем меньше разделяемого состояния, тем проще логика.
Глоссарий в одну строку
- Побочный эффект — любое изменение или наблюдение вне локальной области функции.
- Мемоизация — кеширование результатов функции для ускорения повторных вызовов.
- Детерминизм — свойство возвращать один и тот же результат для одинаковых входных данных.
Когда смешивать чистые и нечистые функции
Архитектурно оптимально: бизнес-логику реализовать через чистые функции, а работу с внешними системами и состоянием — через тонкий слой адаптеров. Это делает код сменяемым, тестируемым и менее подверженным ошибкам.
Итог
Чистые функции повышают надежность, тестируемость и масштабируемость кода. Они особенно полезны в вычислительных задачах, трансформациях данных и там, где важна возможность параллельного выполнения. Однако для взаимодействия с внешним миром побочные эффекты неизбежны — их следует аккуратно изолировать.
Important: начните с малого — делайте чистыми самые критичные и часто тестируемые части приложения. Это даст быстрый выигрыш в качестве кода и удобстве сопровождения.
Критерии приёмки
- Функция возвращает детерминированный результат для одних и тех же аргументов.
- Нет модификации внешнего состояния внутри функции.
- Все внешние зависимости поставляются через параметры или явно документированы.
Сводка
Чистые функции — это простой и мощный инструмент для создания предсказуемого и тестируемого кода. Применяйте их там, где это уместно, и не забывайте локализовать побочные эффекты в отдельные компоненты.
Похожие материалы
Рабочий стол Windows постоянно обновляется — устранение
Как отключить AdBlock в Windows 10
Gmail и XMPP: настройка Jabber‑транспорта
Запись и публикация геймплея PS4
Поделиться Wi‑Fi через Ethernet в Ubuntu