Часть 1, Часть 2, Часть 3
Что такое состояние? Говоря простым языком, состояние — это любые временные данные, хранящиеся в памяти. Например, это могут быть переменные или поля внутри объектов. Само по себе состояние вполне безобидно, но изменяемое состояние является одним из самых больших источников проблем в ПО, особенно в сочетании с ООП.
Ограниченные возможности человеческого мозгаПочему изменяемое состояние доставляет столько неудобств? Как я говорил ранее, человеческий мозг — это самая мощная машина в известной нам вселенной. Тем не менее, наш мозг плохо взаимодействует с состоянием, поскольку мы не можем хранить более пяти фрагментов информации в нашей рабочей памяти одновременно.
Я бы сравнил программирование с неизменяемым состоянием с жонглированием. Не знаю насчет вас, но я бы смог жонглировать только двумя мячиками. Дайте мне больше мячиков и я их уроню. В программировании то же самое, я стал намного продуктивнее, а мой код намного надежнее, как только я отказался от неизменяемого состояния.
Проблемы с изменяемым состояниемДавайте рассмотрим на практике, как изменяемость создает проблемы в коде:
const increasePrice = (item, increaseBy) => {
// никогда так делайте
item.price += increaseBy;
return item;
};
const oldItem = { price: 10 };
const newItem = increasePrice(oldItem, 3);
// печатает newItem.price 13
console.log('newItem.price', newItem.price);
// печатает oldItem.price 13
// не ожидали?
console.log('oldItem.price', oldItem.price);
Эта ошибка крайне коварная, но изменяя аргументы функции, мы случайно изменили цену исходного элемента. Предполагалось, что он останется равным 10, но у нас значение поменялось на 13!
Как этого избежать? Созданием и возвратом нового объекта:
const increasePrice = (item, increaseBy) => ({
...item,
price: item.price + increaseBy
});
const oldItem = { price: 10 };
const newItem = increasePrice(oldItem, 3);
// печатает newItem.price 13
console.log('newItem.price', newItem.price);
// печатает oldItem.price 10
// все ок!
console.log('oldItem.price', oldItem.price);
Имейте в виду, что копирование с использованием оператора ES6 spread …
создает поверхностную, а не полную копию. Это означает, что в копии не будет вложенных свойств. Например, если сверху у item
было свойство item.seller.id
, seller
нового item
все равно будет ссылаться на старый item
. Для работы с неизменяемым состоянием используйте более надежные альтернативы, вроде immutable.js и Ramda lenses.
Предлагаемая конфигурация ESLint:
rules:
fp/no-mutation: warn
no-param-reassign: warn
Не прибегайте к методу push с массивами
Те же самые проблемы возникают при изменении массивов, если вы используете такой метод, как push
:
const a = ['apple', 'orange'];
const b = a;
a.push('microsoft')
// ['apple', 'orange', 'microsoft']
console.log(a);
// ['apple', 'orange', 'microsoft']
// не ожидали?
console.log(b);
А вы думали, что массив b
не изменится? Этой ошибки можно было бы легко избежать, создав новый массив вместо вызова push
:
Недетерминизм — этот странный термин обозначает неспособность программ выводить одни и те же выходные данные при одних и тех же входных данных. Вы можете сказать, что 2+2==4
, но это не всегда так с недетерминированными программами. Да, в большинстве случаев 2+2==4
, но иногда результат может быть равен 3, 5 и даже 1004.
Изменяемое состояние по своей природе недетерминировано и подвергает код недетерминизму (как показано выше). Ирония в том, что недетерминизм повсеместно считается нежелательным в программировании, однако наиболее популярные парадигмы программирования (ООП и императивное программирование) по своей сути недетерминированы из-за своей зависимости от изменяемого состояния.
НеизменяемостьЕсли изменяемость — не самый лучший вариант, что выбрать? Конечно, неизменяемость! Использование неизменяемости считается хорошей практикой и многими поощряется. Некоторые могут не согласиться с моим мнением и сказать, что изменяемое состояние — это круто (фанаты Rust?). Я могу ответить только одно — изменяемость только вредит кодовой базе и делает ее менее надежной.
Предлагаемая конфигурация ESLint:
rules:
fp/no-mutating-assign: warn
fp/no-mutating-methods: warn
fp/no-mutation: warn
Я никого не удивлю, сказав, что var
ни в коем случае нельзя использовать для объявления переменных в JavaScript. Однако многие удивятся, узнав, что ключевое слово let
тоже нужно избегать. Переменные, объявленные с помощью let
могут быть переназначены, что затруднит понимание кода.
В этой статье мы рассмотрели множество плохих практик в программировании, касающихся ограничения человеческой оперативной памяти (рабочей памяти), и использование ключевого слова let
не является исключением. Программируя с ключевым словом let
, мы должны держать в уме все побочные эффекты и потенциальные затруднительные ситуации. Можно случайно назначить неправильное значение переменной и потратить время на отладку.
Что можно использовать вместо let
? Конечно же ключевое слово const
! const
не гарантирует неизменяемость, но запрещает переназначение, что улучшает понимание кода. И, честно говоря, вам не нужен let
— в большинстве случаев код, который переназначает переменные, можно извлечь в отдельную функцию. Давайте рассмотрим пример:
let discount;
if (isLoggedIn) {
if (cartTotal > 100 && !isFriday) {
discount = 30;
} else if (!isValuedCustomer) {
discount = 20;
} else {
discount = 10;
}
} else {
discount = 0;
И тот же пример, извлечённый в функцию:
const getDiscount = ({isLoggedIn, cartTotal, isValuedCustomer}) => {
if (!isLoggedIn) {
return 0;
}
if (cartTotal > 100 && !isFriday()) {
return 30;
}
if (!isValuedCustomer) {
return 20;
}
return 10;
}
Программирование без ключевого слова let
— хорошая привычка, которая дисциплинирует. Вы научитесь разбивать свой код на небольшие и более управляемые функции, что, в свою очередь, приведет к тому, что кодовую базу станет легче читать и поддерживать.
Предлагаемая конфигурация ESLint:
rules:
fp/no-let: warn
Перевод статьи Ilya Suzdalnitski: Here’s How Not to Suck at JavaScript
Комментарии