К некоторым реализациям кода нужно относиться осторожно. Рассмотрим 8 плохих практик JavaScript, которые могут привести к серьёзным сбоям.
1. Сбой из-за null по умолчанию
Любой краш в работе приложения ведёт к финансовым убыткам и требует незамедлительной отладки. Однажды у меня ушло много времени на отладку следующего кода:
Если внутри компонента приложения dates имеет значение falsey , то при инициализации компонент будет иметь значение null.
Если items имеет значение falsey , то нужно инициализировать items в пустой массив по умолчанию. Но при dates со значением falsey происходит сбой именно из-за items со значением null.
Параметры функции по умолчанию позволяют именованным параметрам инициализироваться с дефолтными значениями, если значение не передано или не определено.
В нашем случае null является значением, несмотря на falsey .
Таким образом, дважды подумайте, прежде чем использовать null в качестве значения по умолчанию. Вместо этого используйте пустой массив.
2. Заключение свойств в квадратные скобки
Наверное, самая не очевидная из всех плохих практик JavaScript.
Даже выбор скобок может вызвать сбой. Вот пример поиска объекта с квадратными скобками:
В этом примере всё сделано верно. Минус в том, что такой код выполняется медленнее, чем поиск ключа объекта.
Настоящие проблемы начинаются, когда допущена какая-нибудь небольшая ошибка, например, опечатка:
Если при редактировании кода вы или ваш коллега допустили случайную ошибку — написали Joe с большой буквы вместо маленькой — JavaScript тут же возвращает undefined:
Приложение вылетит в тот момент, когда начнётся поиск свойства.
Так, joesProfile (который затем окажется undefined ) находится в коде, и нет никакой возможности узнать об этом. Всё выяснится только при сбое, который произойдёт во время поиска какого-либо свойства. Например, joesProfile.age.
Некоторые разработчики инициализируют верное возвращаемое значение по умолчанию при неудачном поиске:
По крайней мере, так не произойдёт краша. Если используете поиск в квадратных скобках, продумайте, что должно произойти при неудачном поиске.
Приведу реалистичный пример. Я взял этот код из репозитория, выложенного восемь месяцев назад. В целях конфиденциальности я переименовал почти все переменные, но дизайн, синтаксис и архитектура кода остались неизменными:
fixVideoTypeNaming — это функция, которая определяет тип видео на основе значений, переданных в качестве аргументов. Если аргумент — видеофайл, то функция определит тип видео с помощью свойства .videoType . Если аргумент — строка, то вызывающая функция переходит к videoType , мы пропускаем первый шаг. Выяснилось, что videoType .mp4property несколько раз в приложении было написано неправильно. Чтобы быстро исправить эту проблему с опечаткой, в качестве временного решения использовалось fixVideoTypeNaming.
Как вы уже догадались, приложение написано с помощью Redux — отсюда такой синтаксис.
Чтобы использовать эти селекторы, нужно импортировать их для использования в компонентах высшего порядка, затем прикрепить компонент к слушателю этого среза состояния.
Компонент пользовательского интерфейса:
HOC передаёт все пропсы этому компоненту, он получает их и отображает информацию, адаптируя данных от пропсов. В теории всё должно отлично работать, но так происходит не всегда.
Если мы вернёмся к контейнерам и посмотрим, как селекторы выбирают значения, мы поймём, в чём здесь подвох:
При разработке приложений часто проводятся промежуточные тесты. Они помогают устранить ошибки и убедиться, что приложение работает как задумано. Эти сниппеты кода не были протестированы — впоследствии это вызовет вылет приложения.
Например, state.app.media.video.videoType представляет собой цепочку из четырёх уровней. Допустим, что другой разработчик вносил правки в код и допустил ошибку — state.app.media.video стал со значением undefined. В таком случае приложение зависнет, потому что оно не воспринимает videoType со значением undefined.
Если вы допустите ещё одну опечатку в videoType , если не устраните её с помощью fixVideoTypeNaming , если возникнут проблемы с mp3 — во всех этих случаях произойдёт ещё один сбой. Проблема в том, что обнаружить все эти проблемы вы сможете только тогда, когда пользователь с ними столкнётся. К тому времени, может быть уже слишком поздно.
Будьте внимательнее. Не надейтесь, что в вашем приложении никогда таких багов не будет.
3. Краш приложения при проверке пустых объектов при рендеринге
Я тоже так делал, когда при условном рендеринге нужно было проверять, содержат ли данные какие-либо объекты. Для этого я использовал Object.keys . Если в данных были объекты, то компонент продолжал рендеринг, если поставленное условие соблюдалось:
Давайте представим, что мы вызвали какой-то API и получили в ответ items в качестве объекта. На первый взгляд кажется, что всё нормально. Ожидаемый тип items — объект, поэтому для работы с ним отлично подходит Object.keys . Затем мы инициализируем items в виде пустого объекта — на случай бага, который может вернуть items со значением falsey .
Но что, если items в будущем станет массивом? Object.keys(items) не вызовет краш приложения, но вернёт что-то странное, например, [«0», «1», «2»] . Как думаете, как поведут себя компоненты при рендеринге с такими данными?
Проблема сниппета в том, что если в пропсах у items будет значение null, то items не инициализируется с введённым вами дефолтным значением.
Таким образом, приложение вылетит, так что будьте осторожнее:
4. Вылет при проверке наличия массивов перед рендерингом
Этот пункт похож на предыдущий. Если вы привыкли делать так, как показано ниже, то обязательно проводите модульные тесты или применяйте arr перед рендерингом. В противном случае, если arr является объектным литералом, приложение вылетит.
Оператор && ; воспримет объектный литерал как truthy и применит функцию .map , что приведёт к сбою.
Будьте внимательны — внимательность сохранит вам время и силы, они понадобятся для решения более сложных и важных проблем.
5. Работа без линтера
Линтер — это чрезвычайно полезный инструмент. Я пользуюсь ESLint, популярным линтером для JavaScript: он помогает найти ошибки в коде, даже не выполняя его. Он помогает исправить ошибки в реальном времени, как бы выполняя роль наставника. Он объясняет, что с кодом не так и что нужно сделать, чтобы это исправить.
Ещё одна функция ESLint — если вы не согласны с какими-либо правилом проверки, можете просто отключить его. Тогда ESLint не будет помечать это как ошибку. Удобно, правда?
6. Деструктуризация при рендеринге списков
Такое иногда происходит, но этот баг не всегда легко обнаружить. Допустим, у вас есть список элементов, и вы хотите провести рендеринг группы компонентов для каждого элемента из этого списка. Если значение одного из элементов списка не соответствует ожидаемому, приложение вылетает — оно не знает, что делать с таким типом значения.
Всё идёт хорошо, код работает. Перейдём к вызову API. И вместо этого…
…мы получаем это. Что делать, если в клиенте API возникла проблема с обработкой потока данных, из-за которой код вернул этот массив?
Приложение не знает, что делать с таким типом значения:
Чтобы предотвратить сбой, установите объект по умолчанию на каждой итерации:
Теперь пользователи не будут осуждать вас — всё работает:
Мы предотвратили сбой, но я бы посоветовал разобраться с пропущенными значениями. Алгоритм в этом случае похож на решение проблемы с возвращением null для целых элементов, так как в обоих случаях нет никаких данных.
7. Недостаточно подробное исследование перед реализацией
Другой случай из плохих практик JavaScript. Раньше я тоже допускал подобную ошибку — был слишком уверен в своих знаниях, когда реализовывал поисковый запрос.
На самом деле, реализовать компонент поискового запроса было достаточно просто. Проблема заключалась в символах в запросах.
Я был уверен, что при отправке ключевых слов в виде запросов в поисковый API, допустима каждая нажатая пользователем клавиша — они же зачем-то существуют на клавиатуре. Это не всегда так.
Убедитесь, что регулярное выражение работает именно так, как вы запланировали — тогда не произойдёт сбоя из-за недопустимых символов:
Это пример самого распространённого и современного регулярного выражения для поискового API.
Раньше было так:
Как вы видите, здесь нет слэша / — это может привести к сбоям приложения. Угадайте, что случится, если отправить этот символ поисковому API.
Если честно, я бы не стал на сто процентов доверять примерам из интернета. Во-первых, большинство из них не были тщательно протестированы. Во-вторых, когда речь идёт о регулярных выражениях, нет никакого универсального решения, которое подойдёт во всех случаях.
8. Неограниченный размер загружаемых файлов
Ограничения размера загружаемых пользователями файлов — это хорошее решение. В большинстве случаев огромные файлы можно сжать без заметных потерь качества.
Мы с коллегами заметили, что иногда при загрузке изображение всё виснет. Не у всех наших пользователей мощный компьютер — нужно помнить об этом.
Покажу на примере, как ограничить размер файла 5 мегабайтами (5,000,000 байт):
Оставляйте ровно столько места, сколько нужно. Мы же не хотим, чтобы вместо небольших документов пользователи загружали огромные видеоигры.