Даже опытные Node-разработчики зачастую совершают банальные ошибки. Рассмотрим десятку распространенных и разберемся, как их избежать.
Популярность платформы Node.js в последние годы растет невероятными темпами. Ее используют для разработки такие гиганты, как Walmart и PayPal. Модулей в npm в сотни раз больше, чем в других пакетных менеджерах, и увеличивается каждый день. Но все же, несмотря на огромное количество поклонников, Node.js – не самый простой инструмент. Чтобы разобраться в его тонкостях и понять философию, нужно потрудиться.
Код всех примеров в статье можно найти на Github.
Пренебрежение инструментами разработчика
В отличие от PHP или Ruby, приложения на Node необходимо перезапускать после изменения исходного кода. А во время разработки нужно постоянно обновлять браузер, чтобы увидеть результаты вносимых правок. Конечно, все это можно делать вручную, но зачем? Среди тысяч npm-пакетов найдутся те, что могут взять этот труд на себя.
Автоматический рестарт
Что вы обычно делаете после сохранения изменений в кодовом редакторе? Эта комбинация уже закрепилась в разработчиках на уровне инстинктов:
- остановить приложение – CTRL+C;
- вернуться к предыдущей команде в терминале – UP;
- запустить приложение снова – ENTER.
Хватит повторять это раз за разом! Сделайте процесс разработки проще с помощью готовых инструментов, например:
Эти молчаливые помощники наблюдают за файлами проекта и при их изменении автоматически перезапускают сервер.
Возьмем для примера nodemon. Сначала пакет нужно установить глобально:
А теперь просто замените команду запуска node на nodemon :
Среди множества полезных опций пакетов nodemon и node-supervisor, разумеется, есть и возможность игнорирования определенных директорий или отдельных файлов.
Живая перезагрузка
Теперь поговорим о процессе разработки веб-приложений. Согласитесь, что самая скучная его часть – обновлять страничку в браузере. Эту операцию тоже можно автоматизировать с помощью npm-пакета livereload или ему подобных.
Они работают точно так же, как nodemon и node-supervisor: следят за файлами проекта и реагируют на их изменения. Только вместо перезапуска сервера происходит рефреш веб-страницы с помощью скрипта или специального браузерного плагина.
Мы не будем просто смотреть, как работает livereload, а сразу сделаем приложение на Node. Оно будет следить за файлами в папке, рассылать сообщения подключенным клиентам с помощью технологии server-sent events и обновлять страницу.
Сначала установим все необходимые npm-зависимости:
- станет основой для приложения; поможет следить за изменениями в файлах; обеспечит server-sent event (вместо SSE можно использовать веб-сокеты); будет минифицировать JavaScript файлы; поможет с представлениями.
Напишем простой Express-сервер, который будет рендерить главную страницу из представления home .
Раз уж мы работаем с Express, то инструмент для обновления браузера будем создавать как промежуточное ПО. Он определит конечную точку для SSE, а также создаст хелпер подключения клиентского скрипта для представления. В качестве аргументов обработчик принимает переменную app и папку, файлы в которой нужно отслеживать. Мы уже можем добавить следующие строчки в server.js перед установкой представлений:
На предмет изменений будут проверяться файлы в папке /views . Теперь переходим к самому обработчику:
Если переменная NODE_ENV не равна «development», обработчик просто ничего не делает. Это значит, что для продакшена его нельзя использовать.
Клиентский JavaScript-файл очень простой. Он слушает SSE-сообщения и при необходимости перезагружает страницу:
Файл eventsource-polyfill.js это специальный полифилл для SSE.
И последнее, что осталось сделать, – подключить сгенерированный скрипт на страницу /views/home.html с помощью хелпера:
Теперь каждый раз при изменении файла home.html браузер будет автоматически перезагружать домашнюю страницу ( http://localhost:1337/ ).
Блокировка цикла событий Node
Так как Node.js работает в одном потоке, блокировка цикла обработки событий означает блокировку вообще всего. Если у вас есть веб-сервер с тысячами подключенных клиентов и вы заблокировали event loop, им всем придется подождать, пока вы не одумаетесь.
Вот несколько действий, которые могут стать причиной простоя:
- функцией JSON.parse;
- Попытка сделать подсветку синтаксиса для большого файла с помощью Ace или highlight.js;
- Парсинг большой порции выходных данных, например, вывод команды git log из дочернего процесса.
Спорим, что вы даже не подозревали об этом! Вы можете делать все это неосознанно, ведь мы не так уж часто парсим 15 Мб данных за один раз. Но если злоумышленник застанет вас врасплох, то сервер может пострадать от DDOS-атаки.
К счастью, цикл событий можно контролировать на предмет задержек и других аномалий. Для этого существуют проприетарные решения, например, StrongOps, и ряд модулей с открытым исходным кодом, например, blocked.
Идея заключается в том, чтобы точно отслеживать время между интервалами. Чтобы рассчитать задержку, берется момент A и момент B и вычисляется разница между ними.
Рассмотрим следующий пример, который:
- очень точно определяет разницу между текущим временем и моментом, переданным в качестве параметра;
- через регулярные промежутки определяет задержку цикла событий;
- отображает ее зеленым или красным цветом.
Для наглядности каждые 300 миллисекунд мы будем серьезно нагружать цикл.
Не забудьте установить chalk перед запуском.
Вот что наш пример выводит в терминал:
Модули с открытым исходным кодом работают точно так же, поэтому вы можете смело их использовать:
Если связать этот метод с профилированием, можно точно определить, какая именно часть кода вызвала задержку.
Многократный вызов коллбэка
Сколько раз вы сохраняли файл и перезагружали Node-приложение только для того, чтобы увидеть его моментальный сбой? Скорее всего, вы дважды выполнили обратный вызов, то есть не остановили функцию после первого раза.
Чтобы воспроизвести ситуацию, создадим простой прокси-сервер с некоторой базовой проверкой. Установите зависимость request , запустите программу и откройте, например, http://localhost:1337/?url=http://www.google.com/.
Этот исходный код содержит почти все, кроме самого проксирования. Оно вынесено в отдельный фрагмент, чтобы вы внимательно на него посмотрели:
В обратном вызове обрабатывается ошибка, но после этого поток выполнения не останавливается. Таким образом, если программа получит неправильный адрес, функция respond будет вызвана дважды, и в терминале появится следующее сообщение:
Решить проблему можно так:
Ад коллбэков
Каждый, кто хочет раскритиковать Node.js, вспоминает об «аде коллбэков». Многие уверены, что от вложенных обратных вызовов никуда не деться, но это совсем не так. Есть несколько решений для поддержки чистоты кода, например:
- Использование модулей для управления потоком (async); ; .
Для примера создадим приложение и переработаем его для использования модуля async. Это будет простой анализатор ресурсов интерфейса, который делает следующее:
- Проверяет, сколько скриптов / стилей / изображений подключается в HTML-коде;
- Выводит в консоль их общее количество;
- Проверяет размер каждого ресурса;
- Выводит в терминал сумму этих размеров.
Помимо async, мы будем использовать следующие npm-модули:
- для получения данных страницы (тело, заголовки и т. д.); как бэкенд-аналог jQuery; , чтобы убедиться, что обратный вызов выполняется только один раз.
Выглядит не так уж ужасно, но вложенность может быть еще глубже. Распознать пирамиду коллбэков очень просто. Достаточно посмотреть на отступы и скобки в нижней части кода:
Команда для запуска приложения:
После небольшого рефакторинга с использованием async код преображается:
Создание больших монолитных приложений
Разработчики часто приходят в Node, сохранив образ мышления других языков. Они делают некоторые вещи так, как привыкли, а не так, как следовало бы. Например, размещают весь проект в одном огромном файле, не разбивая его на модули и npm-пакеты. Такой код сложно читать и невозможно тестировать.
Но не переживайте! Сделать приложение более понятным и модульным несложно. Это также поможет справиться с проблемой «пирамиды коллбэков», если она вас беспокоит.
Извлечем валидатор URL, а также все обработчики в отдельные модули. Тогда основной файл будет выглядеть так:
А это обработчик запроса:
Полный код примера вы можете найти на Github.
Все стало гораздо проще, легче для чтения и удобнее для тестов.
Node поощряет разработчиков писать небольшие модули и публиковать их в npm. Там можно найти самые разные полезные инструменты, например, для генерации случайного числа в заданном интервале. Вы должны стремиться к модульности и максимально упрощать свои Node-приложения.
Недостаточное логирование
Во многих руководствах по Node.js есть небольшие демонстрационные примеры, использующие для логирования команду console.log . В результате у разработчиков складывается впечатление, что именно так и нужно делать.
Но есть вещи, которые гораздо круче console.log , потому что они:
- не требуют использования util.inspect для сложных больших объектов;
- имеют встроенные сериализаторы для объектов ошибок, запросов и ответов;
- поддерживают несколько источников для управления логами;
- могут вести журнал на разных уровнях (отладка, информация, обычные и фатальные ошибки);
- автоматически включают в лог имя хоста, id процесса и имя приложения;
- поддерживают ротацию логов.
Все это можно использовать даже на продакшене, используя модуль bunyan или подобные ему. А если вы установите пакет глобально, то получите удобный инструмент командной строки для разработки.
Давайте разберем пример:
Если запустить его из командной строки, вы увидите что-то вроде этого:
В разработке лучше использовать CLI-инструмент:
Как видно, bunyan дает множество полезной информации о текущем процессе, что жизненно важно для работающего приложения. Есть еще полезная возможность подключать логи к потоку (или нескольким потокам).
Пренебрежение тестами
Приложение не может считаться «готовым», если оно не протестировано. Отсутствию тестов нет никакого оправдания, учитывая, сколько удобных инструментов для этого существует:
- фреймворки mocha, jasmine, tape и десятки других;
- пакеты для тестовых утверждений chai, should.js;
- модули для создания заглушек и mock-объектов наподобие sinon;
- инструменты покрытия кода istanbul, blanket.
По соглашению npm-модули должны иметь команду для запуска тестов, которая указывается в файле package.json .
Благодаря этому тесты можно запускать с помощью команды npm test , не вдаваясь в подробности реализации тестовых методов.
Прежде чем закоммитить любое изменение проекта, необходимо запустить тесты. К счастью, это так же просто, как набрать команду npm i pre-commit —save-dev .
Также вы можете установить определенный уровень покрытия кода и запретить все коммиты, несоответствующие ему. Модуль pre-commit просто автоматически запускает npm test перед фиксацией изменений.
Если вы не знаете с чего начать, чтобы освоить тестирование, обратитесь к онлайн-руководствам или изучите популярные Node-проекты на Github, например:
Пренебрежение линтингом кода
Вместо того, чтобы исправлять ошибки на продакшене, стоит отлавливать их прямо при разработке с помощью статических анализаторов кода.
ESLint и ему подобные инструменты решают огромное количество проблем:
- Устраняют потенциальные ошибки, например, присваивание в условных выражения, использование debugger ;
- Принудительно вводят хорошие практики кода, например, запрещают объявление переменной более одного раза или применение устаревшей конструкции arguments.callee ;
- Ищут возможные проблемы безопасности, например, eval() или небезопасные регулярные выражения;
- Обнаруживают фрагменты кода с низкой производительностью;
- Обеспечивают последовательное применение руководства по стилю.
Для более полного понимания ознакомьтесь с документацией ESLint. Вы также можете подробнее узнать о конфигурации, чтобы максимально точно настроить инструмент для вашего проекта.
Существуют и другие аналогичные пакеты для линтинга кода, например, JSLint или JSHint.
Если вы хотите самостоятельно разобрать АСД (абстрактное синтаксическое дерево) и создать собственный анализатор, рассмотрите Esprima или Acorn.
Отсутствие мониторинга или профилирования кода
Если вы не мониторите и не профилируете ваше Node-приложение, вы ничего о нем не знаете. Вы не осведомлены о таких важных вещах, как задержка цикла событий, загрузка процессора и системы, использование памяти.
Есть ряд проприетарных сервисов, которые могут позаботиться об этих вещах вместо вас: New Relic, StrongLoop или Concurix, AppDynamics.
Но вы можете сделать это самостоятельно с помощью модулей с открытым исходным кодом (look) или комбинации нескольких пакетов. Независимо от выбора, убедитесь, что вы всегда знаете статус своего приложения.
Отладка с помощью console.log
Когда что-то идет не так, легко вставить console.log и отладить программу. После этого вы просто удаляете строку и продолжаете писать.
Но следующий разработчик, которому придется поддерживать ваш код, может наткнуться на эту же самую проблему. Вот для чего нужны модули вроде debug. Вместо того чтобы вставлять и удалять console.log , вы можете заменить его на функцию debug и оставить ее в коде.
Когда следующий программист попытается понять этот фрагмент, он может просто запустить приложение, установив переменную окружения DEBUG .
Этот крошечный модуль имеет свои преимущества:
- Без использования переменной DEBUG, на консоль ничего не выводится.
- Можно выборочно отлаживать части кода.
- Подсвеченный вывод в терминал.
Давайте посмотрим на их официальный пример:
Если запустить пример командой node app.js , ничего не произойдет, но если включить флаг DEBUG, начинается магия:
Помимо приложений, debug также можно использовать для небольших модулей, опубликованных в npm. В отличие от более сложного регистратора он выполняет только отладочную работу и делает это хорошо.