Side effects
Side effect (побічний ефект) - це будь-яка дія функції, яка змінює стан поза межами самої функції або залежить від зовнішнього стану, а не просто обчислює результат зі своїх аргументів. Саме побічні ефекти роблять код складнішим для тестування та передбачення, тому їх намагаються зібрати в окремому, чітко окресленому шарі програми.
Теорія
TL;DR
- Побічний ефект - це будь-який вплив функції на зовнішнє оточення або залежність від нього поза поверненим значенням.
- Класичні приклади:
console.log,alert, зміна DOM,fetch, запис у глобальну змінну, робота з файлами. - Залежність від зовнішнього стану теж вважається ефектом:
Math.random(),Date.now(), читання глобальної змінної. - Функція, яка залежить лише від аргументів і нічого навколо не чіпає, називається чистою (pure).
- Чистий код легше тестувати, передбачати й безпечніше виконувати паралельно.
- Повністю прибрати ефекти неможливо: без них немає UI, мережі та сховища. Мета - ізолювати та контролювати їх.
Швидкий приклад
let count = 0;
// impure: the function changes an outer variable
function increment() {
count++;
}
// pure: the result depends only on the arguments
function add(a, b) {
return a + b;
}
increment(); // count is now 1, the outside world changed
add(2, 3); // always 5, nothing around it changedЩо саме вважають побічним ефектом
Побічний ефект - це будь-яка зміна поза самою функцією: глобальні змінні, вивід у консоль, запит до сервера, зміна DOM, робота з файлами тощо. Дзеркальний випадок теж рахується: якщо функція читає зовнішній стан, її результат уже не визначається тільки аргументами.
| Приклад | Що робить |
|---|---|
console.log('Hello') | Змінює консоль, тобто зовнішнє оточення |
alert('Hi!') | Взаємодіє з користувачем |
document.body.style.color = 'red' | Змінює DOM |
fetch('/api') | Виконує HTTP-запит |
Math.random() | Залежить від зовнішнього стану (генератор випадкових чисел) |
Date.now() | Залежить від поточного часу |
| Зміна глобальної змінної | Змінює стан програми поза функцією |
Нечиста функція проти чистої
Функція з побічним ефектом не лише рахує, а й змінює стан програми:
let count = 0;
function increment() {
count++; // changes an outer variable
}Функція без побічних ефектів працює тільки зі своїми аргументами і нічого навколо не торкається:
function add(a, b) {
return a + b;
}Той самий контраст видно на роботі з масивами: мутація вхідних даних - це ефект, а повернення нової копії - ні.
// impure: mutates the caller's array
function addItem(list, item) {
list.push(item);
return list;
}
// pure: returns a new array, the input stays untouched
function addItemPure(list, item) {
return [...list, item];
}Чому це важливо і як це контролювати
Код без побічних ефектів легше тестувати (не потрібні моки зовнішнього світу), його результат простіше передбачити, і він безпечніший для паралельного виконання та кешування. Але частина ефектів неминуча: без них програма не спілкувалася б із користувачем, сервером чи файлом. Тому на практиці роблять так:
- обчислення тримають у чистих функціях, а ефекти виносять на край програми (обробники подій, шар запитів до
api.example.com, запис у сховище); - зовнішні залежності передають аргументом, а не беруть із глобальної області, наприклад
function greet(now) { ... }замість викликуDate.now()всередині; - замість мутації вхідних об'єктів повертають нові значення;
- у тестах підмінюють саме цей тонкий шар ефектів, а решту коду перевіряють напряму.
| Ознака | Side effect |
|---|---|
| Змінює зовнішні дані | Так |
| Залежить від зовнішнього стану | Так |
| Повертає різні результати на однаковому вводі | Можливо |
| Приклад | console.log, fetch, операції з DOM |
| Без побічних ефектів | Чиста функція |
Одним реченням: side effect - це будь-який зовнішній вплив функції (зміна оточення або залежність від нього), не пов'язаний напряму з її поверненим результатом.
Типові помилки
- Вважати ефектом лише запис і не помічати читання:
Math.random()таDate.now()роблять функцію нечистою, хоча нічого не змінюють. - Думати, що чистота - це заборона ефектів. Заборонити їх не можна, можна лише винести їх з ядра логіки.
- Мутувати переданий об'єкт чи масив і вважати це «просто оптимізацією»: виклик-власник отримує змінені дані й ловить важкі баги.
- Ховати запит до мережі чи запис у сховище всередині функції з невинною назвою на кшталт
getUser(), через що її неможливо викликати в тесті. - Плутати побічний ефект із поверненням значення:
return- це не ефект, ефект - це все, що функція робить додатково.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.