Бот для заказа еды и первое боевое крещение стажера

Предисловие

В Mad Devs сотрудники традиционно питаются за счет компании. Все начинается с того, что утром ответственный сотрудник должен выбрать кафе на Namba Food, открыть 2 корпоративных заказа, оповестить всех, чтобы они выбрали себе еду. Далее нужно проконтролировать, что все сделали заказ, оформить его не позднее определенного времени (иначе обед придет слишком поздно) и ждать звонка оператора (чтобы заменить блюда, которых не окажется в кафе, что бывает в 95% заказов). Когда еду привозят, нужно снова всех оповестить, что еда пришла.

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

Испытание стажера

Поговаривают, что задача создать бота для хорошего джуна — это пару вечеров под пивко. Мы пока не джуны. Мы стажеры Mad Devs, и в рамках автоматизации всех рутинных процессов нам дали первое боевое задание — написать бота для заказа еды с сайта Namba Food.

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

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

Наша команда использует два основных мессенджера — Telegram и Slack. Поэтому бот умеет отправлять в эти мессенджеры сообщения. Это не большое достижение, но оно дает такие ценные знания, как поиск токена во всем разнообразии документации по API, а также принуждает к использованию библиотек.

Для Telegram мы использовали Python-telegram-bot, маленькую, простую библиотеку с хорошей документацией. А вот для Slack мы написали свои функции, что отняло больше времени, но дало гибкость, которой нет в стандартных слаковских библиотеках.

Еще одно важное понятие при создании бота для заказа еды — CRON. Он дает возможность запускать функции в нужное время, не отвлекаясь от работы. CRON может вызывать ваши функции вне программы или напротив, вы можете встроить его в код. Мы работали параллельно: один писал внешний CRON, а другой использовал внутренний. Запуская CRON извне, вы сталкиваетесь с внешним кодом и разрастанием программы без необходимости, внутренний крон небольшой и аккуратный, но он вызывает путаницу, пока вы не уверены в своем коде. Единого идеального решения не существует. В итоге мы выбрали библиотеку Schedule.

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

До этого этапа все, что мы писали ранее, имело определенное название — функциональное программирование. Но теперь перед нами встала дилемма, так как заказ делается из двух кафе. Вызывать функции два раза? Не по-программистски это. С подачи ментора мы обратили внимание на объектно-ориентированное программирование. Для заказа из первого кафе создавался один объект, для заказа из второго кафе — другой. На словах все просто, но на деле мы только писали бота, а к нему уже предъявлялись новые и новые требования. Функционал менялся, все падало. И тут случилась роковая ошибка с токеном!

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

Когда мы впервые столкнулись с понятием переменных окружения, то обнаружили, что это словарь. Мы создали переменную, приравняли ее к переменной окружения, но вместо самой переменной мы вставили её значение. Есть ключ, есть значение — словарь! А что не так?

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

  • Документ с именем и значением переменной окружения, который в дальнейшем активируют, он добавлен в .gitignore и хранится лично у вас.
  • В коде названия переменных и обращение к переменным окружения, документ активируется вами на личном компьютере.

Мы закончили с проектом, можно было запускать его в работу. Однако, на этом этапе появился еще один технический момент. Чтобы запустить скрипт, его нужно завернуть в докер и выложить на сервер. Сказано — сделано, мы обернули скрипт.

Когда работаешь со скриптом без базы данных и прочих радостей бэкэнда, работа кажется легкой и беззаботной. Так и есть. Создаем requirements, к которому цепляется контейнер докера и скачивает все, что нужно. Затем указываем образ-основу и не забываем написать, что докер должен сам перезагружаться, если упадет.

Первый день запуска! Все просчитано и сто раз запущено с одним и тем же результатом. Бот открывает заказ, и… Одно из кафе появилось из ниоткуда, в нашем списке такого не было! В чем проблема? Номера кафе на стейджинге, где мы их тестировали, и на продакшене отличаются. Благодаря этой ошибке мы получили еще один ценный урок — необходимо перепроверять данные “боевого” сервера.

Следующий день — суббота, день когда большая часть офиса предпочитает отдыхать, боту тоже следовало отдохнуть, но в девять утра, как слишком усердный сотрудник, он открыл заказ. И хуже всего то, что в заказе открылись те же кафе, что и днем раньше, хотя должны были открыться случайные кафе из списка. Мы сделали два вывода, благодаря этому фиаско. Первый— в докере необходимо прописывать скрипт, который обновляет время. Второй — случайный выбор часто оказывается неслучайным и непродуманным, поэтому лучше использовать цикл for или четко задавать последовательность кафе.

Скриншот с сайта Namba Food
Скриншот с сайта Namba Food

Была и еще одна оплошность с нашей стороны. Поскольку хлеб с разных кафе заказывается разный(где-то лепешки, а где-то боорсоки), то мы, чтобы не засорять код if’ами, писали прямо в комментарий для операторов перебор такого типа: “Если заказ с кафе Дияр, то +2кг боорсоков, если заказ с Вкуса востока, то 5 лепешек, и т.д.”, перечисляя все 12 вариантов кафе…Что ж, получив поток гнева от операторов Namba Food и нагоняй от менторов, мы вместе с этим усвоили и еще один урок:

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

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

А у нас уже куча фидбека от пользователей и куча планов, как его улучшить:

  • Добавить проверку статуса доставки и автоматическое информирование о доставке
  • Доверить выбор кафе сотрудникам по средствам голосования
  • Автоматический заказ хлеба и боорсоков на имя бота
  • Добавить автоматическую замену блюда, если заказанного блюда в кафе не оказалось (при этом, учитывать обычные предпочтения конкретного сотрудника)
  • И еще несколько пожеланий из серии “… по текущей погоде на улице, дню недели и фазе луны определить настроение сотрудника и заказать ему то, что он в таком состоянии обычно потребляет. Без участия самого сотрудника…”(надеюсь, это достанется реализовывать будущим стажерам =))
Бот в Telegram сообщает радостную весть
Бот в Telegram сообщает радостную весть

Уроки, которые мы извлекли

  • Учиться пользоваться переменными окружениями нужно сразу, иначе — FAIL!
  • Обязательно проверять время внутри докер-контейнера.
  • Рэндом выбирающий между небольшим числом вариантов часто выдает похожий или идентичный выбор, отчего кажется неработающим.
  • Данные на тестовом и на “боевом” сервере могут и, вероятнее всего, будут отличаться.
  • Код может быть грубым и некрасивым, но отрабатывать он должен в соответствии с ожиданиями, если есть сомнения — никаких релизов
  • В рабочее окружение можно пускать только как следует протестированный продукт. Долгие и нудные проверки работоспособности должны проводиться только в тестовом окружении, не мешая будущим пользователям.
  • Для фидбека по продукту лучше создать документ и отправлять пользователей в этот документ, вместо того чтобы выслушивать (иначе, обязательно что-нибудь забудется)

И самое главное:

Продукт разработки — решение проблемы. Мы пишем код, чтобы делать жизнь лучше, решать реальные проблемы, а не создавать кому-то новые.

Thanks to Oleg Puzanov.