Сюрреализм на JavaScript. Советы по разработке на NodeJS

Привет, Хабра! Пол года назад я подумал: «А может книгу написать?», и таки написал.

9df75863f1c48cd7160aca06575cd067.jpg

Все документы оформлены, страницы сверстаны, а тираж — отпечатан. Я не буду клянчить у вас деньги на кикстартере или предлагать что-либо купить, а вместо этого попытаюсь заинтриговать советами по разработке на NodeJS в целях пиара и привлечения внимания к книге.

Совет 1. SQL запросы лучше хранить отформатированнымиSQL запросы лучше хранить отформатированными, т.к. код в этом случае гораздо проще читать и править. Т.к. SQL-запросы обычно довольно длинные, то лучше разбивать их на несколько строк, а строки в JavaScript — лучше всего выглядят в массиве.До:

var query = «SELECT g.id, g.title, g.description, g.link, g.icon, t.id as tag_id, c.title as comment_title FROM games AS g LEFT JOIN tags AS t ON t.game_id = g.id LEFT JOIN comments AS c ON c.game_id = g.id WHERE g.id = $1»; После: var query = [ «SELECT », » g.id, g.title, g.description, g.link, g.icon,», » t.id as tag_id, c.title as comment_title », » FROM games AS g », » LEFT JOIN tags AS t ON t.game_id = g.id », » LEFT JOIN comments AS c ON c.game_id = g.id », » WHERE », » g.id = $1» ]; Согласитесь, что во втором случае запрос гораздо понятнее для читателя. Кроме того, если вы перед запросом в базу выводите запрос в консоль, то массив, прокинутый в console.dir (), опять таки гораздо понятнее, чем строка, прокинутая в console.log ().8534885431a7883bf004052129e7a189.png

Совет 2. Жизнь становится проще, когда API различных компонентов принимает на вход любой формат Предположим мы создали клиента к базе данных и хотим выполнить некий SQL-запрос. На входе мы ожидаем строку и параметры. Т.к. воспользовавшись прошлым советом мы решили хранить длинные запросы в массивах, то хотелось бы забыть про его преобразование в строку на каждом запросе.До:

dataBase (query.join (»), parameters, callback); После: dataBase (query, parameters, callback); Код становится проще, когда функция запроса к базе (в данном случае dataBase), сама проверяет в каком виде ей передали запрос, и если это массив — сама делает ему join ().Совет 3. Разукрасьте консоль и отформатируйте вывод информации Дебажить программы на NodeJS трудно, т.к. очень сильно не хватает стандартной консоли разработчика со всеми фишками, типа «точек остановки». Все данные пишутся в консоль, и хочется сделать её более понятной. Если у вас нода крутится где-то на сервере, да ещё и в несколько инстансов, да ещё и несколько разных сервисов на каждой инстансе висит, а доступ вы имеете только по SSH, то консоль может реально заставить страдать.Если в NodeJS вывести в консоль строку вида «Hello world!» с управляющими ANSI-символами, она будет окрашена в разные цвета. Пример использования управляющих ANSI-символов:

8322953638bd6f57f63089efe5c5c306.png

Чтобы не запоминать подобные хаки, вы можете подключить модуль colors и использовать следующий синтаксис:

console.log («Error! Parameter ID not found.».red); Строка будет выведена красным цветом. При разработке с этим модулем вы можете раскрасить сообщения в консоли в различные цвета: Красный (ошибка). Желтый (предупреждение). Зеленый (все хорошо). Серый. Им можно выводить какие-либо параметры (например, параметры запроса), на которые можно не обращать внимания, пока не поймаете ошибку. Консоль до цветового выделения (при быстром просмотре информация воспринимается с трудом): 468094f86c5666711af60ffbfff5e48a.png

Консоль после цветового выделения (при быстром просмотре информация воспринимается достаточно быстро):

418d6f1d1720fce11665e58993dbbdff.png

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

var book = { console: function (message, color) { console.log ((«Book API:» + (message || »))[(color || white»)]); } } Если все сообщения в консоли подписаны модулями, которые их отправляют, вы не только сможете сделать выборку по конкретному модулю, но и моментально найдете источник бага в критической ситуации. Форматирование текста также упрощает восприятие информации:

6bf25ce22b65a34027f12a42c05c6465.png

Таким образом, вся информация в консоли будет подписана, и вы легко сможете понять, какие события происходят в тех или иных модулях. Опять же, цветовое выделение помогает в стрессовых ситуациях, когда отказала та или иная система и нужно срочно исправить ошибку, и вчитываться в логи нет времени (я не призываю вас дебажить на продакшне, просто всякое бывает). В своих проектах я решил переписать модуль консоли, чтобы иметь возможность раскрашивать не только строки, но и массивы и объекты, а также автоматически подписывать все инстансы. Поэтому при подключении модуля я передаю ему имя пакета, которым следует подписывать сообщения. Пример использования нового модуля консоли:

var console = require (»./my_console»)(«Scoring SDK»); console.green («Request for DataBase.»); console.grey ([ «SELECT *», » FROM \«ScoringSDK__game_list\», » WHERE key = $1;» ]); Пример вывода данных в консоль: 4e6a697a90691933b544ab920b511855.png

Совет 4. Оборачивайте все API в try/catch У нас на работе используется фреймворк express. Каково же было мое удивление, когда я узнал, что в стандартном объекте роутера нет обертки try/catch. Например: Вы написали кривой модуль Кто-то дернул его по URL`у У вас упал сервер WTF?! Поэтому всегда оборачивайте внешнее API модулей в try/catch. Если что-то пойдет не так, ваш кривой модуль, по крайней мере, не завалит всю систему. Та же ситуация на клиенте с шаблоном «медиатор» (его ещё называют «слушатели и публикующие»). Например:

Модуль А опубликовал сообщение. Модули Б и В должны услышать его и отреагировать. Система в цикле начинает перебирать подписчиков. Модуль Б падает с ошибкой. Цикл обрывается и callback-функция модуля В не вызывается. Гораздо лучше делать перебор в try/catch и если модуль Б действительно упадет с ошибкой, то по крайней мере не убьет систему и модуль В выполнит свою работу услышав событие.

Т.к. при написании API модулей мне приходилось вновь и вновь отделять приватные и публичные методы, а после оборачивать все публичные методы в try/catch, я решил это дело автоматизировать и написал небольшой модуль для автогенерации API. Например, кидаем в него объект вида:

var a = { _b: function () { … }, _c: function () { … }, d: function () { … } } Из именования методов ясно, что первые два — приватные, а последний — публичный. Модуль создаст обертку для вызова последнего, вида:

var api = { d: function () { try { return a.d (); } catch (e) { return false; } } }; Таким образом, я стал генерировать обертку для API всех модулей, которая в случае возникновения ошибки не пропускала её дальше. Это сделало код более стабильным, т.к. ошибка отдельного разработчика, слитая в продакшн, уже не могла уронить весь сервер со всем его функционалом.Пример генерации API:

var a = { _b: function () { … }, _c: function () { … }, d: function () { … } }

var api = api.create (a);

api.d (); // пример вызова Совет 5. Собирайте запросы в конфиги Я думаю, у каждого веб-разработчика была ситуация, когда был какой-либо жирный клиент, которому нужно было небольшое API для работы с базой данных на сервере. Пару запросов на чтение, пару на запись и ещё несколько для удаления информации. Логики в таком сервере обычно нет, и он представляет собой просто набор запросов.Чтобы не писать каждый раз обертки для таких операций, я решил вынести все запросы в JSON, а сервер — оформить в виде небольшого модуля, который предоставляет мне API для работы с этим JSON`ом.

Пример такого модуля под express:

var fastQuery = require (»./fastQuery»), API = fastQuery ({ scoring: { get: { query: «SELECT * FROM score LIMIT $1, $2;» parameters: [ «limit», «offset» ] }, set: { query: «INSERT INTO score (user_id, score, date) VALUES …», parameters: [ «id», «score» ] } }, profile: { get: { query: «SELECT * FROM users WHERE id = $1;», parameters: [ «id» ] } } }); Наверное, вы уже догадались, что модуль будет пробегать по JSON`у и искать объекты со свойствами query и parameters. Если такие объекты будут найдены, то он создаст для них функцию, которая будет проверять параметры, ходить в базу с запросами, и посылать клиенту результат. На выходе мы получим такое API: API.scoring.get (); API.scoring.set (); API.profile.get (); И уже его привяжем к объекту роутера: exports.initRoutes = function (app) { app.get (»/scoring/get», API.scoring.get); app.put (»/scoring/set», API.scoring.set); app.get (»/profile/get», API.profile.get); } Я не буду впраривать свой фреймворк для этой цели, т.к. стек технологий на сервере разный в разных фирмах. Для работы с подобным объектом вам в любом случае понадобится писать небольшую обвязку, поверх чего-либо, для обработки запросов и работы с базой. Кроме того, возможно, в момент, когда вы будете читать эти строки, уже будет несколько готовых фреймворков для этой задачи.А теперь представьте, что у вас есть ещё два серверных разработчика. Один пишет на PHP, а второй на Java. Если у вас вся серверная логика ограничивается только таким JSON`ом со списком запросов к базе, то вы можете моментально перенести/развернуть аналогичное API не только на другой машине, но и на абсолютно другом языке (при условии, что общение с клиентом стандартизировано и все общаются по REST API).

Совет 6. Выносите все в конфиги Т.к. писать конфиги я люблю, у меня неоднократно возникала ситуация, когда у системы есть стандартные настройки, настройки для конкретного случая и настройки, возникшие в данные момент времени. Мне приходилось делать mix разных JSON объектов. Я решил выделить отдельный модуль для этих целей, а заодно добавил в него возможность брать JSON объекты из файла, т.к. хранить настройки в отдельном json-файле тоже очень удобно. Таким образом, теперь, когда мне нужно задать настройки для чего-либо я пишу: var data = config.get («config.json», «save.json», { name: «Petr», age: 12 }); Как вы уже могли догадаться, модуль перебирает переданные ему аргументы. Если это строка — то он пытается открыть файл и прочитать настройки из него, если это объект — то он сразу пытается скрестить его с предыдущими.В примере выше мы сначала берем некие стандартные настройки из файла config.json, потом накладываем на них сохраненные настройки из файла save.json, а потом добавляем настройки, которые актуальны в данный момент времени. На выходе мы получим mix из трех JSON объектов. Количество аргументов переданных модулю может быть любым. Например, мы можем попросить пригнать только настройки по умолчанию:

var data = config.get («config.json»); Совет 7. Работа с файлами и Модуль Social Link для СЕО Одна из главных фич, которые мне нравятся в NodeJS, возможность работать с файлами и писать парсеры на JavaScript. При том API NodeJS предоставляет множество методов и способов для решения задач, но на практике — нужно совсем не много. За полгода активной работы с парсерами я использовал только две команды — прочитать и записать в файл. Притом, чтобы не страдать с callback-функциями и различными проблемами асинхронности, всю работу с файлами я всегда делал в синхронном режиме. Так появился небольшой модуль работы с файлами, API которого очень напоминало localStorage: var file = requery (»./utils.file.js»), // подключили модуль text = file.get («text.txt); // прочитали текст в файле file.set («text.txt», «Hello world!»); // записали текст в файл На основание этого модуля работы с файлами, стали появятся другие модули. Например, модуль для СЕО. В одной из прошлых статей я уже писал, что существует огромное количество различных meta-тегов связанных с СЕО. Когда я начинал писать систему сборку для HTML приложений, СЕО я уделил особое внимание.Суть заключается в том, что у нас есть небольшой текстовый файл с описанием сайта/приложения/игры и непосредственно HTML файл для разбора. Модуль Social Link должен найти все meta-теги связанные с СЕО в HTML файле и заполнить их. Внешнее API модуля ожидает на входе текст из файла. Это сделано для того, чтобы была возможность подключать его к системам сборки и прогонять через него текст нескольких файлов не вызывая каждый раз лишнюю процедуру чтения/записи в файл.

Например, до модуля:

После модуля: Некий заголовок Список и описание всех meta-тегов для СЕО и не только, вы можете посмотреть в книге http://bakhirev.biz/.Совет 8. Без callback`ов жизнь проще Многие разработчики жалуются на бесконечные цепочки callback`ов при написании сервера на NodeJS. На самом деле вы не всегда обязаны их писать и часто можно выстроить архитектуру, при которой такие цепочки будут минимальны. Рассмотрим небольшую задачу.Задача: Перегнать файлы с сервера А на сервер Б, получить некоторую информацию из базы данных, обновить эту информацию, отправить данные на сервер В.

Решение: Это довольно рутинная процедура, которую мне неоднократно приходилось выполнять для решения каких-либо задач по сортировке / обработке контента. Обычно разработчики создают цикл и некую callback-функцию с методом nextFile (). Когда на очередной итерации мы вызываем nextFile (), механизм callback`ов начинается с начала, и мы обрабатываем следующий файл. Как правило, требуется в один момент времени обрабатывать только один файл и при удачном завершении процедуры переходить к обработке следующего файла. Упростить вложенность нам поможет код вида:

var locked = false, index = 0, timer = setInterval (function () { if (locked) return; locked = true; nextFile (index++); }, 1000); Теперь мы будем раз в секунду пытаться начать обработку файла. Если программа освободится, то она выставит locked в значение false и мы сможем запустить следующий файл на обработку. Такие конструкции очень часто помогают уменьшать вложенность, распределить нагрузку по времени (т.к. очередная итерация обработки у нас запускается не чаще, чем один раз в секунду) и хоть немного сползать с бесконечных callback`ов.ИтогоФайлы с модулями можно скачать тут: http://bakhirev.biz/_node.zip (сейчас 2 часа ночи и мне лень разбираться с GitHub`ом и приводить код в человеческий вид).Книга тут: http://bakhirev.biz/На случай хабро-эффекта тут в PDF.

Если советы выше пришлись вам по вкусу, то хочу сразу предупредить, что книга совсем про другое. А ещё там в конце список разных умных людей, которые внесли неоценимый вклад сами того не подозревая, и которых точно следует найти и прочитать по отдельности.

© Habrahabr.ru