Gernar
Frontend DeveloperJavaScript, архитектура кода

Интервью-вопрос

Что такое чистая функция

Чистая функция дает один и тот же результат для одинаковых входных данных и не создает побочных эффектов. Для frontend это важно в тестах, React-рендере, мемоизации и предсказуемом обновлении UI.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 60 правильно

Как лучше объяснить чистую функцию?

Вы отвечаете на базовый вопрос и хотите дать точное, но короткое определение без лишней теории.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

У чистой функции есть два признака. Первый, одинаковые входные данные дают одинаковый результат. Второй, вызов функции не меняет внешний мир.

Простой пример.

const add = (a, b) => a + b;

Здесь результат зависит только от a и b. Вызов не меняет внешние переменные, DOM, storage, сеть или аргументы. Такую функцию легко проверить тестом и безопасно переиспользовать.

Плохой пример.

let discount = 0.1;

function getFinalPrice(price) {
  return price * (1 - discount);
}

Функция выглядит безобидно, но зависит от внешней переменной discount. Если скидка изменится, тот же вызов getFinalPrice(100) вернет другой результат. Для чистого варианта скидку лучше передать аргументом.

function getFinalPrice(price, discount) {
  return price * (1 - discount);
}

Как проверить функцию на чистоту

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

Короткая проверка

1Все данные, влияющие на результат, приходят аргументами?
Да. Если нужны user, locale, time или random, передайте их явно.
2Функция меняет что-то снаружи?
Нет. Не мутируйте аргументы, глобальные переменные, DOM, storage и кеши без явного контракта.
3Одинаковый вызов можно безопасно повторить?
Да. Повторный вызов с теми же аргументами не должен менять состояние приложения.
4Результат можно закешировать по аргументам?
Да. Если кеш может стать неверным из-за времени или внешнего состояния, функция не чистая.

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

Побочные эффекты во frontend-коде

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

  • изменение глобальной переменной или модуля;
  • мутация объекта или массива, который пришел аргументом;
  • запрос к API;
  • запись в localStorage или sessionStorage;
  • изменение DOM;
  • запуск таймера, подписки или аналитического события;
  • чтение времени через Date.now() или случайности через Math.random(), если это влияет на результат.

Главное последствие, поведение функции становится труднее предсказать. Тестам нужны моки. Повторный вызов может что-то сломать. Оптимизации вроде мемоизации становятся опасными.

Плохой пример для списка задач.

function addTodo(todos, text) {
  todos.push({ id: Date.now(), text });
  return todos;
}

Здесь сразу две проблемы. Функция мутирует входной массив и сама создает id через текущее время. В React это может сломать обновление по ссылке. В тестах результат будет зависеть от момента запуска.

Более безопасная граница выглядит так.

function addTodo(todos, todo) {
  return [...todos, todo];
}

const todo = { id: Date.now(), text: "Read docs" };
const nextTodos = addTodo(todos, todo);

Создание id осталось нечистым действием, но оно вынесено наружу. Сама функция addTodo стала чистым преобразованием списка.

Сетевые запросы и чистые вычисления

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

Плохой пример.

function UserProfile({ id }) {
  const [user, setUser] = React.useState(null);

  fetch(`/api/users/${id}`)
    .then((response) => response.json())
    .then(setUser);

  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

Что здесь сломается. Запрос запускается при каждом рендере. Старый ответ может перезаписать данные для нового id. Ошибка не попадет в UI. При размонтировании останется незавершенный запрос. Безопаснее отделить чистую подготовку URL от побочного эффекта и явно обработать loading, error и отмену.

function getUserUrl(id) {
  return `/api/users/${encodeURIComponent(id)}`;
}

function UserProfile({ id }) {
  const [user, setUser] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    const controller = new AbortController();

    setLoading(true);
    setError(null);

    fetch(getUserUrl(id), { signal: controller.signal })
      .then((response) => {
        if (!response.ok) throw new Error("Request failed");
        return response.json();
      })
      .then(setUser)
      .catch((err) => {
        if (err.name === "AbortError") return;
        setError(err);
      })
      .finally(() => {
        if (!controller.signal.aborted) setLoading(false);
      });

    return () => controller.abort();
  }, [id]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Failed to load user</div>;
  if (!user) return <div>No user</div>;

  return <div>{user.name}</div>;
}

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

Связь с React

React хорошо показывает практическую пользу чистых функций. Рендер компонента должен быть предсказуемым. При тех же props и state он возвращает тот же UI и не меняет внешний мир.

Плохой пример.

function UserCard({ user }) {
  localStorage.setItem("lastUserId", user.id);

  return <div>{user.name}</div>;
}

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

function UserCard({ user }) {
  React.useEffect(() => {
    localStorage.setItem("lastUserId", user.id);
  }, [user.id]);

  return <div>{user.name}</div>;
}

То же правило относится к props. Нельзя менять объект, который пришел в компонент, а потом возвращать JSX. Такая мутация влияет на родителя или соседние компоненты и делает UI непредсказуемым. Если нужна измененная версия данных, создайте новый объект или массив.

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

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

При этом локальная мутация внутри функции не всегда проблема.

function normalizeNames(users) {
  const result = [];

  for (const user of users) {
    result.push(user.name.trim().toLowerCase());
  }

  return result;
}

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

Практический вывод для ответа

Хороший ответ на интервью может звучать так.

Чистая функция это функция, результат которой зависит только от входных аргументов. При одинаковых аргументах она возвращает одинаковое значение и не делает побочных эффектов. Она не меняет внешнее состояние, входные объекты, DOM, storage и не ходит в сеть. Во frontend это важно для тестов, React-рендера и мемоизации. Если функции нужны время, случайность или данные пользователя, я стараюсь передать их явно, а само вычисление оставить чистым.

После такого ответа полезно сразу привести пример. Не ограничивайтесь функцией сложения чисел. Лучше показать реальный frontend-сценарий, обновление списка, форматирование данных, расчет derived state или чистый reducer.

Частые ошибки

Где обычно ошибаются

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

  1. 1

    Считать возврат значения признаком чистоты

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

    Прятать зависимость во внешней переменной

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

    Мутировать входные данные

    Изменение массива или объекта, который пришел аргументом, создает побочный эффект. Во frontend это часто ломает сравнение по ссылке, мемоизацию и обновление UI. Надежнее вернуть новый массив или объект.
  4. 4

    Делать побочный эффект во время рендера

    Запросы, запись в localStorage, изменение DOM и подписки внутри рендера React-компонента могут повторяться неожиданно. Так появляются лишние запросы, рассинхронизация UI и сложные баги. Такие действия выносят в useEffect или обработчики событий.
  5. 5

    Смешивать расчет данных и сетевой запрос

    Функция, которая должна только подготовить параметры, не должна сама делать fetch. Иначе повторный вызов может отправить лишний запрос, перетереть состояние более поздним ответом или оставить компонент без отмены запроса. Лучше отдельно держать чистое вычисление URL или body, и отдельно эффект с AbortController, loading и error.
  6. 6

    Путать чистоту и иммутабельность

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

Follow-up

Что могут спросить дальше

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

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

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

Содержание