Как стать автором
Поиск
Написать публикацию
Обновить
732.56
Яндекс
Как мы делаем Яндекс

События, которым можно доверять: выстраивание процесса работы с разметкой приложения с точки зрения аналитика

Время на прочтение16 мин
Количество просмотров398

Привет! Меня зовут Николай Олигеров. Сейчас я работаю аналитиком данных в Яндекс Путешествиях, а до этого я был продуктовым аналитиком в Лавке — мой рассказ будет именно про неё.

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


Что такое разметка событий и зачем она нужна

Простыми словами разметка — это система, с помощью которой мы отслеживаем, что происходит в приложении. Каждый раз, когда пользователь совершает какое‑то действие (например, открывает главную страницу), на сервер прилетает событие. У него есть название — скажем, lavka.main_page.opened — и набор параметров, описывающих контекст: идентификатор офера, стоимость доставки, тип зоны и так далее.

Для каждого события определяется собственный набор параметров — в зависимости от бизнес‑логики и целей аналитики. Например, событие lavka.main_page.viewed описывает просмотр главной страницы, и у него будет свой набор данных.

Так главную страницу видит пользователь Лавки…

…а так выглядит событие lavka.main_page.viewed, которое уходит к нам в DWH.
  {
        "available_eats_retail_types" = [
         # Список доступных к заказу ритейл-магазинов: цветы/зоо/аптеки
            "flower_store";
            "pet_store";
            "pharmacy"
        ];
        "category_groups" = [
        # Список категорий групп, которые юзер видел
            {
                "block_elements" = [
                    {
                        "category_id" = "9136ff2a16acac3060b5568478dc8885";
                        "category_name" = "Лето оставит след";
                        "position" = 1
                    }
                ];
                "block_name" = "group";
                "block_position" = 4;
                "category_group_id" = "8fe85692d6f7c675cf03d7ea7822e135";
                "category_group_name" = "Что интересного"
            }
        ];
        "depot_available" = [
           # Список доступных Лавок: обычная и суперлавка
            "regular";
            "supermarket"
        ];
        "expanded_story_viewed" = [
        # Какие сторис видел юзер. Поскольку сторисов не было, то элемент — пустой
        ];
        "goals_viewed" = {
           # Блок целей с параметрами целей
            "block_elements" = [
                {
                    "currency_sign" = "₽";
                    "expires" = "2025-07-31T20:59:59+00:00";
                    "goal_id" = "1013";
                    "goal_type" = "skus_total_sum";
                    "has_accept" = 1;
                    "progress" = {
                        "current_counter" = "0";
                        "max_counter" = "1500";
                        "text" = "1"
                    };
                    "remaining_time_text" = "За 14 дней";
                    "reward_info" = {
                        "reward_text" = "200 ₽";
                        "reward_type" = "promocode"
                    };
                    "status" = "in_progress";
                    "text" = "Как получить 50 000 ₽ в Яндекс Путешествиях и промокод?";
                    "title" = "Как получить 50 000 ₽ в Яндекс Путешествиях и промокод?"
                }
            ];
            "block_position" = 3
        };
        "hubs" = {
            # Хабами мы называем элементы наверху (до информера с роботом-курьером): «Готовая еда», «Есть горячее», «Продукты и не только», «Аптека», «Цветы», «Избранное», «Вы покупали»
            "block_elements" = [
                {
                    "hub_id" = "ready_to_eat_coffe";
                    "hub_size" = "2x1"
                };
                {
                    "hub_id" = "coffee_msk";
                    "hub_size" = "2x1"
                };
                {
                    "hub_id" = "grocery_coffee";
                    "hub_size" = "2x2"
                };
                {
                    "hub_id" = "pharmacy";
                    "hub_size" = "1x1"
                };
                {
                    "hub_id" = "flower_store";
                    "hub_size" = "1x1"
                };
                {
                    "hub_id" = "favourites";
                    "hub_size" = "1x1"
                };
                {
                    "hub_id" = "history_products";
                    "hub_size" = "1x1"
                }
            ];
            "block_position" = 1
        };
        "key_ids" = {
            # Тут лежат тип Лавки (может быть обычная, может быть Супермаркет), id офера (стоимость доставки в зависимости от логистической зоны) и id Лавки
            "depot_type" = "regular";
            "offer_id" = "56705776c1a441e1a8cc872010cef2d3";
            "place_id" = "72893"
        };
        "lavka_orders" = [];
        "logistic_params" = {
            # Это логистические параметры: тип курьера (вело/такси), стоимость доставки, флаг наличия ровера, флаг сурджа (повышенного спроса), минимальная стоимость доставки и флаг активной зоны доставки
            "courier_type" = "eda_courier";
            "delivery_cost" = 0;
            "is_rover_available" = 0;
            "is_surged" = 0;
            "min_cost" = 0;
            "zone_mode" = "active"
        };
        "market_parcel" = [];
        "scrolled" = 0;
        "total_categories_viewed" = 1;
        "user_info" = {
            # Информация о юзере. Вся информация лежит в захешированном виде, и мы работаем с обезличенными данными
            "has_plus" = 1;
            "personal_phone_id" = "abcd1234567890abcd1234567890";
            "plus_balance" = 4993;
            "yandex_uid" = "123456789"
        }
    }

Вдаваться в детали реализации под капотом не будем, но на верхнем уровне всё устроено довольно просто: пользователь взаимодействует с интерфейсом, фронт собирает данные: что именно видел юзер, какие параметры были у ручек в момент запроса (например, была ли наценка, сколько стоила доставка и т. п.) — и формирует лог. Этот лог отправляется в Яндекс AppMetrica, а дальше уже DWH Лавки переносит события в свой контур.

Почему мы выбрали именно AppMetrica?

Во‑первых, это продукт Яндекса. Во‑вторых, она отлично подходит для продуктовой аналитики: многое можно посчитать и посмотреть прямо в веб‑интерфейсе. Для менеджеров это по сути self‑service‑аналитика с минимальным порогом входа. У нас даже есть дефолтные воронки, доступные прямо в интерфейсе AppMetrica. Ну и в‑третьих, сами логи удобно обрабатывать: параметры приходят в JSON‑структуре, легко парсятся, а события покрывают весь пользовательский путь — это сильно ускоряет фическоринг и любые продуктовые исследования.

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

Зачем нужна разметка фронта

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

Кроме того, фронтовая разметка упрощает аналитику. Допустим, мы хотим исследовать поведение пользователей, которые в течение сессии видели сурдж (дополнительную наценку из‑за повышенного спроса). Теоретически, это можно восстановить с бэка, но для этого нужно много склейки: привязывать параметры запросов к сессиям, разбирать логи. А с фронта такие вещи вытаскиваются одним SQL‑запросом.

Разметка помогает Лавке в самых разных задачах:

  • строить дашборды по пользовательским действиям и воронкам;

  • исследовать поведение пользователей и скоринг новых фичей;

  • измерять метрики в A/B‑тестах.

На сегодня разметка в Лавке — это:

  • 150 млн событий в сутки;

  • более 40 дашбордов, которые её используют;

  • более 35 экспериментов в месяц, завязанных на фронтовую разметку;

  • 5 мобильных приложений (Лавка, Delivery Club, Яндекс Еда, Go, Маркет);

  • более 50 аналитиков, которые ежемесячно используют эти данные.

Но так хорошо было не всегда. Чтобы добиться таких результатов, нам пришлось проделать довольно кропотливую работу. Но обо всём по порядку.

Какие были проблемы с разметкой в Лавке 

Сначала мы вели документацию вручную — в нашей внутренней Вики (аналог Confluence). На старте это казалось разумным: событий было не так много, всё можно было описать руками.

Но со временем событий стало больше сотни. И если, например, нужно было добавить новый параметр (скажем, delivery_conditions_id), то его приходилось вручную прописывать во все соответствующие события. Это было неудобно, долго и вечно откладывалось — а потом и вовсе забывалось.

В итоге документация довольно быстро переставала соответствовать реальности: в интерфейсе и в логах одно, в описаниях — другое. Ни аналитики, ни разработчики не могли на неё полагаться.

Как выглядела ручная документация 

Второй проблемой было отсутствие системного подхода к созданию событий. Аналитики просто в свободной форме описывали, что им нужно: «Давайте логировать это» или «Нужно событие, когда пользователь видит зону доставки».

В результате получалось два варианта:

  • Либо обсуждение с разработкой затягивалось — приходилось долго выяснять детали, спорить о названиях и параметрах.

  • Либо разработка делала события по‑своему, без уточнений — и аналитики в итоге получали совсем не то, что планировали.

Как вы понимаете, оба варианта неудачные. Становилось очевидно: нужен единый, понятный всем формат описания событий — такой, который бы устраивал и разработчиков, и аналитиков.

В таком виде мы раньше передавали ТЗ разработке по событиям
В таком виде мы раньше передавали ТЗ разработке по событиям

К накопившимся проблемам добавлялось и техническое легаси. Например:

  • события могли срабатывать не в тот момент — триггеры отрабатывали некорректно;

  • одни и те же действия могли логироваться разными событиями, создавая дубли;

  • часть параметров собиралась неправильно или не передавалась вообще — и мы никак это не отслеживали.

На всё это накладывалось отсутствие мониторинга: ни у аналитиков, ни у разработчиков не было инструментов, которые бы сигнализировали о сбоях. Если события падали или начинали собираться криво, мы могли узнать об этом только через пару недель, когда уже что‑то пошло не так в аналитике или экспериментах.

В какой‑то момент стало понятно: текущую разметку нельзя считать надёжной. Мы больше не могли ей доверять — и приняли решение перевести Лавку на новую, полностью переосмысленную систему событий.

Как работает новый процесс разметки

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

Первым делом мы навели порядок в процессе постановки задач. Теперь любая новая разметка начинается с формы в таск‑трекере. Её заполняет заказчик — чаще всего продакт‑менеджер. Обязательные поля:

  • краткое описание фичи;

  • будет ли запускаться эксперимент и, если да, на какие метрики смотрим;

  • нужен ли дашборд под эту фичу;

  • ссылка на макет в Figma;

  • когда планируется отдать задачу фронту;

  • кто будет ответственным разработчиком.

Сами события описываются в YAML‑файлах, которые мы храним в отдельном репозитории. Это даёт сразу несколько плюсов:

  • есть единый источник правды по всей разметке;

  • можно отслеживать изменения;

  • любой аналитик или разработчик может посмотреть историю правок.

Описанием событий занимается аналитик. После внесения изменений аналитик формирует pull request в репозиторий и проходит код‑ревью.

Каждое событие обязательно содержит:

  • название;

  • платформы, на которых оно работает (например, webview, десктоп, мобильный touch);

  • описание, откуда и зачем появилось событие (в том числе ссылка на тикет);

  • триггеры — при каком действии пользователя событие должно сработать;

  • параметры — какие данные передаются, в каком формате (строка, число, массив, словарь) и с какими возможными значениями.

  - name: 'lavka.timeslot_buttons.clicked' # Имя события
    platforms: ['desktop_web', 'mobile_web', 'webview'] # На каких платформах событие будет отрабатывать. В данном случае событие будет работать на всех платформах
    description: 'The user cllicked on timeslot buttons on the delivery bottomsheet or cart screen' # Описание события. Часто сюда добавляем название тикета, в рамках которого делали событие
    triggers: # Перечисление, в каких случаях событие будет срабатывать
      - 'User clicked on buttons and choose slot or asap delivery'
    data: # Ниже — параметры события, которые отправляются
      allOf: 
        - $ref: './definitions.yaml#/definitions/Timeslot' # Пример, как в определениях выглядит Timeslot, есть ниже
# Все параметры, которые мы используем больше чем в 1 событии, мы оборачиваем в определения и складываем в отдельное место
# Это делается, чтобы при изменении параметров достаточно было поменять их в одном месте

        - $ref: './definitions.yaml#/definitions/KeyIds'
        - $ref: './definitions.yaml#/definitions/LogisticParams'
        - $ref: './definitions.yaml#/definitions/UserInfo'
        - properties:
            origin_screen:
              $ref: './timeslots.yaml#/definitions/DeliveryOriginScreen'
            source: # 
              description: 'Surface of event. If user was in bottomsheet - then "bottomsheet". Separate buttons on the bottom sheet are "bottom_button". On other cases - did not send'
              type: string 
              enum: ['bottomsheet', 'bottom_button'] # Возможные варианты параметра source: либо шторка, либо кнопка
# Вот как выглядит раздел с определениями
# В определения можно сложить любую структуру: и словари, и списки, и даже булевые значения
definitions:
  NumericBoolean:
    type: integer
    enum: [0, 1] # Это определение для булева значения: эта переменная может принять значения 1 или 0
  Timeslot:
    description: 'for special delivery terms which user can choose in different places' # Отдельно можно сделать описание для определений
    type: object
    additionalProperties: false
# Какие параметры должны передаться обязательно. Если бэк не отправит эти параметры или фронт не сможет собрать, событие не улетит в AppMetrica
    required: ['start', 'end', 'is_tommorow']
    properties:
      start:
        type: string
      end:
        type: string
      is_tomorrow:
        $ref: './definitions.yaml#/definitions/NumericBoolean'

Чтобы разметка оставалась чистой и рабочей, мы добавили несколько уровней валидации.

Прежде всего, на каждый YAML‑файл с описанием события запускаются автотесты — они проверяют корректность синтаксиса. Если забыли закрыть скобку или неправильно оформили структуру, тест сразу это подсветит. Но тест не скажет, адекватна ли бизнес‑логика события или можно ли его вообще реализовать — для этого у нас есть ревью.

После этого идёт код‑ревью. Каждый pull request на изменение разметки рассматривают двое:

  • фронт‑разработчик, который оценивает техническую реализуемость;

  • аналитик, который смотрит на логику и соответствие стандартам.

Такой двойной контроль помогает ловить и технические, и методологические ошибки до того, как они попадут в прод.

Ну и главное: на основе этих же YAML‑файлов мы автоматически генерируем документацию. Это полностью избавило нас от ручного описания событий и решило проблему неактуальности. Документация теперь всегда соответствует коду.

Сборник советов: как составить ТЗ к разработке на разметку

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

Все события мы делим на четыре типа:

События открытия страниц. Отправляются, когда страница полностью загрузилась и все элементы отображаются для пользователя. Здесь фиксируем, что именно прогрузилось. Это фундамент для всей остальной аналитики.

События просмотра. Раньше мы отправляли их каждые 10 секунд — и быстро пожалели об этом: в системе появлялось огромное количество почти идентичных событий, которые не несли новой информации. В итоге от такой периодичности отказались и сократили объём событий просмотра в 10 раз, не потеряв при этом бизнес‑полезности.

Сейчас событие просмотра отправляется только в трёх случаях:

  • пользователь покидает страницу;

  • сворачивает приложение;

  • превышен лимит параметров (AppMetrica не принимает события с объёмом параметров больше 64 КБ).

Что логируем: какие элементы действительно были видны юзеру. Под «просмотром» мы понимаем, что пользователь видел более 90% элемента (например, карточки товара / категории / баннера) в течение хотя бы одной секунды.

События кликов. Логируем только там, где клик не приводит к открытию новой страницы. Просто зафиксировали нажатие кнопки + параметры в момент клика.

Технические события. Это отдельный пул событий для отладки и мониторинга. Они помогают следить за корректностью работы всей системы разметки. О них — чуть позже.

Мы стараемся не плодить минорные события, а складываем информацию в параметры уже существующих. Например, если на чекауте появился чекбокс «Оставить у двери», мы не делаем под него отдельное событие, а логируем всё, что нужно, в событие просмотра страницы чекаута. Какие параметры учитываем:

  • был ли чекбокс виден на странице (долистал ли пользователь до него);

  • положение чекбокса при открытии страницы;

  • положение чекбокса при закрытии страницы;

  • был ли клик по чекбоксу.

Зачем столько? Исследования показывают, что пользователи могут нажимать на кнопки хаотично, поэтому мы фиксируем не только итог, но и путь к нему. Наша разметка кросс‑платформенная — по возможности, события и структура одинаковы на вебе, десктопе и в мобайле. И ещё важный принцип: никаких дублей. Если клик уже зафиксирован как часть события просмотра страницы — отдельное событие клика не создаём.

Контроль качества

Недостаточно просто сделать хорошую разметку, важно смотреть, что она отрабатывает корректно:

  • те события, которые должны приходить, приходят в нужном количестве (не теряются ли они где‑то);

  • не приходят те события, которых мы не ждём;

  • внутри событий приходят нужные параметры.

У разработки есть свои мониторинги на корректность отправки событий. Но у нас были случаи, когда мы узнавали о поломанном событии, только когда ломалось что‑то в дашбордах или воронках или мы не могли посчитать экспы.

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

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

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

  • продовые события — содержат параметры, которые реально нужны для аналитики;

  • технические события — пустышки с единственным параметром timestamp.

Важные параметры (например, delivery_price) помечены как обязательные. Событие не уходит с фронта, если параметр не определён. То есть если Лавка закрыта и цена доставки не может быть получена, событие просто не отправится.

А вот технические события отправляются всегда. Это позволяет сравнивать объёмы: если технических событий приходит сильно больше, чем продовых — значит, где‑то пошёл сбой.

Мониторинг и алерты. Мы собрали дашборд, где видно количество событий в разбивке по группам, экранам, типам событий. Поверх этого — система алертов в Telegram. Мы получаем уведомления, если:

  • резко увеличивается или падает число событий на каком‑то экране;

  • начинает приходить много событий без ключевых параметров;

  • появляются события, которых нет в «белом списке» (их не заводили аналитики);

  • разница между числом технических и продовых событий выходит за допустимый порог (значит, часть событий теряется);

  • внезапно изменилось поведение событий просмотра (например, они вдруг перестали приходить на определённых платформах).

Пример, как на дашборде выглядит поломанное событие: резкое падение общего количества событий. По оси oX — даты, по оси oY — количество событий. Цветом выделены разные события. Сами события на дашборде мы разделили на группы (они видны в оглавлении слева). На этой вкладке мониторятся события взаимодействия с карточкой товара 
Пример, как на дашборде выглядит поломанное событие: резкое падение общего количества событий. По оси oX — даты, по оси oY — количество событий. Цветом выделены разные события. Сами события на дашборде мы разделили на группы (они видны в оглавлении слева). На этой вкладке мониторятся события взаимодействия с карточкой товара
Пример, как в AppMetrica выглядит количество технических событий открытия главной lavka.service_events.main_page_opening и продовых событий загрузки главной lavka.main_page.opened
Пример, как в AppMetrica выглядит количество технических событий открытия главной lavka.service_events.main_page_opening и продовых событий загрузки главной lavka.main_page.opened

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

Ещё момент: вы видите перед событием название Superapp.Showcase.Event.eda. Дело в том, что Лавка работает на вебвью (грубо говоря, это когда приложение по факту открывает внутри себя сайт). И вот этот префикс добавляет нативная часть приложения, когда отправляет события в AppMetrica.

Пример, как в AppMetrica выглядит количество технических событий открытия главной lavka.service_events.main_page_opening и продовых событий просмотра главной lavka.main_page.viewed 
Пример, как в AppMetrica выглядит количество технических событий открытия главной lavka.service_events.main_page_opening и продовых событий просмотра главной lavka.main_page.viewed 

Здесь у нас доля теряемых событий выше, это связано с техническими особенностями реализации. Мы отдельно исследовали объём потерь и их влияние на качество аналитики. Пришли к тому, что такая точность нас устраивает.

А вот как это выглядит на наших дашбордах:

Синяя линия — доля потерянных событий открытия страницы (на первом графике) и событий просмотра страницы (на втором графике). Красными линиями указаны SLA (потеря не более 5% событий). Этот график показывает долю потерянных событий открытия страниц. cart — это события на корзине; category/subcategory —  это события на экране категорий/подкатегорий; checkout — это события на экране оплаты; main_page — это события на главной
Синяя линия — доля потерянных событий открытия страницы (на первом графике) и событий просмотра страницы (на втором графике). Красными линиями указаны SLA (потеря не более 5% событий). Этот график показывает долю потерянных событий открытия страниц. cart — это события на корзине; category/subcategory — это события на экране категорий/подкатегорий; checkout — это события на экране оплаты; main_page — это события на главной
Аналогичный график, но для событий просмотра страниц
Аналогичный график, но для событий просмотра страниц

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

Мониторинг со стороны разработки. События у нас контролируются не только аналитиками, но и разработкой. Большинство ключевых событий покрыто автотестами, плюс у фронта есть свой мониторинг — в реальном времени.

Это особенно важно, потому что у аналитиков обновление данных идёт батчами раз в сутки (чтобы не перегружать DWH) и время реакции на проблему может доходить до 30 часов. Поэтому разработка — первый рубеж защиты, а аналитики — последний. Мы отлавливаем то, что могло пройти мимо технических проверок.

Сессии AppMetrica в нашем случае не слишком удобны: из‑за особенностей поведения пользователя они часто рвутся. Например, пользователь оформил заказ, а потом несколько раз сворачивал и разворачивал приложение, пока ждал курьера — AppMetrica может разбить это на несколько сессий. Но по бизнес‑логике это всё — один и тот же заказной флоу. Поэтому мы собираем кастомные сессии по следующим правилам:

  • время между двумя событиями < 1 часа — значит, это одна сессия;

  • максимальная длина сессии — 6 часов (дальше принудительный разрыв);

  • не больше 10 000 событий в одной сессии (иначе тоже разрыв).

Зачем это нужно? В рамках сессий мы считаем фичи и метрики:

  • добавляем флаги вроде «было ли открытие корзины»;

  • собираем уникальные оферы, показанные пользователю;

  • отслеживаем, были ли просмотры нужных экранов или взаимодействие с элементами.

Это сильно упрощает скоринг фичей и аналитику A/B‑тестов: нужные данные уже собраны в одном месте и удобно агрегируются.


Процесс переразметки мы завершили полтора года назад. С тех пор всё работает стабильно:

  • легаси не копится;

  • дубли и потерянные параметры не возникают;

  • разметка масштабируется без увеличения трудозатрат.

Мы перестали сталкиваться с «плавающими» триггерами и другими сюрпризами. Разметка теперь управляемая и предсказуемая. Документация и записи воркшопов позволяют аналитикам быстро вникать в процесс и работать с разметкой самостоятельно — без бутылочного горлышка в виде одного эксперта. А главное — мы не копим технический долг. Текущая разметка позволяет быстро скорить фичи, запускать A/B‑тесты и уверенно отвечать на продуктовые вопросы.

Теги:
Хабы:
+11
Комментарии0

Публикации

Информация

Сайт
www.ya.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия