LE Blog

Инженер с поэтической душой

27.08.2018 firtree_right Один за всех

Сидим мы как-то с нашим дата-инженером за столом. Вокруг рассыпаны какие-то крупные блестящие конфетти. «Интересно, что это?» — говорю. А он: «это пластиковые кружки». Рядом бразилец ржёт: «Чёрт возьми! Это так по-немецки! Абсолютно точный ответ! Ни на что не отвечает». Вспоминаю анекдот про математика, который сообщает заблудившимся путешественникам, что они находятся в гондоле воздушного шара. «А я и немец, и математик» — улыбается в бороду дата-инженер.

pigeons

Или ещё история. Мужчина хочет заказать еду за стойкой. Перед ним стоит дама. «Вы ждёте?» — спрашивает он. Она говорит: «Да». И он за ней стоит минуты три, пока я подхожу и делаю заказ. Получилось, что я вклинился в очередь. Парень был иностранец, потому что немец бы спросил точнее: «Ты уже заказала?» Потому что дама ждала как раз заказанную еду. И абсолютно не задумалась, зачем её спрашивает этот человек, не обратила внимание на меню у него в руках, и не стала уточнять, чего именно она ждёт. Задан вопрос — получен ответ. Я видел эту ситуацию издалека и по языку жестов решил, что она уже заказала, а он, ещё не определился. И, ничего не спрашивая, рванул к прилавку.

pigeons

Чем дальше, тем больше мне нравится эта немецкая черта: не думать за другого человека, особенно как противовес моему «подумать за всех». Очень интересно её исследовать и освоить. Не за тем, чтобы всё время так делать, но иногда было бы круто. Ведь в моей, культуре принято быть телепатом. С одной стороны, я ожидаю, что остальным многое понятно без слов. А с другой — симметрично уверен, что для меня их мотивы тоже прозрачны. Это, конечно, способствует развитию душевности, близости и чувствительности, но одновременно создаёт обширное поле для проекций и переносов. Большинство того, что кажется очевидным в мотивациях других, на самом деле, больше говорит обо мне. Неизвестно вообще, можно ли смоделировать то, что происходит внутри другого человека. Потому что тогда можно было бы предсказывать поведение, а этого не происходит в массе.

pigeons

Понятно, что реальность всегда намного сложнее. Все эти обобщения про нацию в целом — ни что иное, как способ иммигранта справиться со стрессом. Но лично для меня умение не задумываться, почему кто что делает, — это как глоток ключевой воды. Дорого отдал бы, чтобы узнать об этой опции лет на 20 раньше.

07.09.2016 firtree_right Телеграм-бот для Яндекс.ПДД

Введение

Увлечение чат-ботами докатилась и до меня. Как это может случиться наилучшим образом, — по необходимости. А необходимость возникла в совместном использовании Яндекс почты для домена. Оказалось, что веб-интерфейс для этого совершенно не приспособлен, но есть API. Но писать целый сайт для этого кажется накладным, а чат-бот — в самый раз. И вообще, мне кажется, это один из самых продуктивных способов использования технологии: интерфейс к API.

Ссылку на полный текст бота я приложу в конце. Сам бот не содержит в себе полного функционала всего API, а имеет лишь необходимую на данный момент часть. Сейчас хотел бы поделиться парой находок, которые пригодятся всем, кто захочет писать чат-ботов для Телеграм с помощью node.js.

Chat bot

Хуки в продакшне

У ботов Телеграм есть два способа работы: когда бот сам обращается за обновлениями по определённому адресу (polling) и с помощью веб-хуков, когда сервера сами дёргают заданный хук для передачи данных боту. В продакшне, конечно, удобнее работать с хуками, а при разработке — нет, поскольку сервер запускается на локальной машине. Кроме этого я рекомендую завести другого бота для разработки, чтобы те, кто пользуются вашим ботом в продакшне, не замечали, как вы разрабатываете. Возможность сделать это я нашёл пока только в одной библиотеке: node-telegram-bot-api с помощью недокументированной функции processUpdate. Делается это довольно просто. При инициализации бота в файле lib/bot.js:

if (process.env.NODE_ENV === 'production') {
    bot = new TelegramBot(config.botToken, {polling: false});
    bot.setWebHook(config.host + config.url);
} else {
    bot = new TelegramBot(config.devBotToken, {polling: true});
}

А затем уже в серверной части, которая, хоть и запускается всегда, имеет значение только для продакшна, в файле lib/web.js:

app.post(config.url, function (req, res) {
    options.bot.processUpdate(req.body);
    res.status(200).send({}).end();
});

Весь остальной код для бота работает в обоих случаях одинаково и в изменениях не нуждается, что совершенно прекрасно!

Оповещение об остановке

Второе, что нужно делать, как мне кажется, это оповещать хоть кого-нибудь о том, что сервер остановлен или запущен. Также это нужно, если при перезапуске бота, например, меняется кастомизированная клавиатура.

Если вы запускаете приложения с помощью pm2, то этот менеджер использует для остановки процесса тот же сигнал SIGINT, что мы используем, когда останавливаем сервер в разработке с помощью Ctrl-C. Очень удобно! В файле index.js

process.on('SIGINT', function () {
    Promise.all(config.permitUsers.map(function (userId) {
        return bot.sendMessage(userId, 'Бот временно выключается. Только спокойствие!', {
            reply_markup: {
                hide_keyboard: true
            }
        });
    })).then(gracefulClose).catch(function (err) {
        console.log(err);
        gracefulClose();
    });
});

Таким образом, останавливая наш сервер через Ctrl-C мы видим то же, что увидит пользователь, когда перезапускается приложение на сервере.

Материалы для самостоятельного изучения

  1. Полный на текущий момент код Телеграм-бота для Яндекс.ПДД;
  2. API Яндекс.ПДД;
  3. Как установить приложение node.js на ubuntu 16.04.

23.06.2016 firtree_right Тестирование сервера Node.js

Вводная

Уже не раз в этом блоге я ратовал за тестирование собственного кода. Поскольку писать мне интересно на разных языках, то и тестировать приходится по-разному. Каждый язык имеет свои паттерны, используя которые получается наиболее удобный код. Каждая библиотека или среда имеет в каком-то виде реализованные подходы к тестированию.

Для тестирования кода на ноде я использую жасмин. Для запуска задач — галп.

Сегодня хочу рассказать о двух простых приёмах для тестирования кода сервера, которые позволяют выделять и тестировать только то, что нужно.

Staged reality

Кто запускает сервер

Первое, что хотелось бы сделать, это запускать сервер отдельно для каждого теста. Желательно на другом порту. Может быть, даже параллельно с работающим сервером, на котором мы что-то пробуем руками. Для этого в ноде есть возможность определить, находимся ли мы в основном файле, или его загружает какой-то другой файл:

if (require.main === module) {
    app.use('/', require(path.join(__dirname, 'routes', 'main'))());
    var server = app.listen(5000, function () {
        ...
    });
} else {
    exports = module.exports = function (options) {
        app.use('/', require(path.join(__dirname, 'routes', 'main'))(options));
        return app;
    };
}

Таким образом, уже в тестовом фреймворке мы будем запускать:

app = require('index.js');
server = app(...).listen(6000, function (err) {...});

перед каждым тестом, а после каждого теста:

server.close(function (err) {...});

Очень удобно!

Имитация библиотек

Второе, что бы хотелось сделать при тестировании сервера, — имитировать используемые им библиотеки, чтобы тестировать только код сервера, а не библиотеки. Связки тоже важны, но только тогда, когда мы этого сами хотим. И тут появляется интересный момент. Если мы хотим имитировать библиотеки при тестировании, нам нужно использовать определённый паттерн при программировании. Паттерн фабрик. То есть мы не просто тестируем свой код, но устраиваем архитектуру и стиль так, чтобы его удобнее было тестировать.

В случае с паттерном фабрики мы возвращаем в качестве модуля не объект, а функцию, которая создаёт объект в соответствии с нашими параметрами:

exports = module.exports = function (options) {
    options = options || {};
    var customLib = options.customLib || require(...);

    /* GET main page. */
    router.get('/', function (req, res, next) {
        res.end('Real server text. CustomLib: ' + customLib.name);
    });

    return router;
};

Как видно в вызовах выше, мы передаём сквозным образом наши опции при тестировании, а при нормальном запуске, не передаём ничего.

Таким образом, получается, что при разработке через тестирование, или даже просто при использовании тестов важно не только писать тесты, но и писать код, который, во-первых, можно, а во-вторых, удобно тестировать.

Для самостоятельного изучения

Полный код примера в сборе