Как эффективно работать с тикетами (issues) на GitHub

Тикеты на GitHub бывают разные: запросы на реализацию каких-то возможностей, отчёты об ошибках, жалобы от клиентов, оповещения от систем безопасности, ретроспективы для команды и т. д. Здесь мы рассмотрим, как команда может использовать и обсуждать их.

Содержание:


Что такое тикет?

Для многих команд «тикет» — это общий термин, который может означать:

  • Запрос на реализацию фичи.
  • Отчёт об ошибке.
  • Жалобу клиента.
  • Оповещение системы безопасности.
  • Ретроспективу для команды.

Публичный или приватный?

Для проектов можно создавать публичные и приватные тикеты. Публичные, как правило, предназначены для пользователей, клиентов, агентов и т. д. Приватные — для разработчиков, подрядчиков, партнёров и т. д.

Для публичных тикетов

  • Акцентируйте внимание на итоге.
  • Подчёркивайте, что можно сделать.
  • Не публикуйте конфиденциальную информацию.

Для приватных тикетов

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

Оценка

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

Оценка по приоритетности

  • Пример: Приоритетность 1 (сделать в первую очередь), Приоритетность 2 (сделать во вторую очередь), Приоритетность 3 (сделать в третью очередь) и т. д.
  • Аналогия: список дел, в котором Приоритетность 1 является главным приоритетом.
  • Достоинства: легко понять, в какой очерёдности команда будет выполнять задачи. Совместимо со многими системами отслеживания ошибок, приложениями для ведения списков дела и инструментами для управления задачами.
  • Не рекомендуется: некоторые команды используют Приоритетность 0 (P0), например, для обозначения экстренных ситуаций или помехи релиза.

Оценка по степени влияния

  • Пример: от Опасность 1 (минимальное влияние) до 5 (катастрофическое влияние).
  • Аналогия: возможные последствия ураганов оцениваются по шкале Саффира-Симпсона в диапазоне 1 (минимальный), 2 (умеренный), 3 (значительный), 4 (огромный), 5 (катастрофический).
  • Достоинства: легко понять с точки зрения влияния на бизнес. Хорошо подходит для цветового кодирования от зелёного до красного. Каждый может выставить оценку в соответствии со своей точкой зрения, без учёта приоритетности.
  • Не рекомендуется: некоторые команды переворачивают шкалу и используют диапазон от Опасность 0 (катастрофическое) до 5 (минимальное). Мы не рекомендуем так делать, потому что возникает диссонанс.

Оценка по степени ущерба

  • Пример: от Ущерба 1 (минимальный ущерб) до 10 (катастрофический ущерб).
  • Аналогия: шкала Рихтера оценивает ущерб от землетрясений в диапазоне от 1 (минимальные разрушения) до 10 (полные и необратимые разрушения).
  • Достоинства: легко понять с точки зрения влияния на клиентов. Можно использовать аналогии из реального мира. Хорошо подходит для кодирования по степени яркости в диапазоне от светлого до тёмного. Каждый может выставить оценку в соответствии со своей точкой зрения, без учёта приоритетности.

Оценка по размеру

  • Пример: по увеличению размера — «Маленькое», «Среднее», «Большое».
  • Аналогия: размеры одежды.
  • Достоинства: легко прикинуть, сколько потребуется сделать работы.

Оценка по критерию MoSCoW

  • Пример: MoSCoW — это мнемоническое сокращение от «must», «should», «could», «won’t». Фича «должна быть», «желательна», «может быть», «не будет».
  • Аналогия: любая беседа, посвящённая планированию, в ходе которой кто-то говорит: «Мы должны это сделать» или «Нам следует это сделать».
  • Достоинства: англоязычное кодирование категорий помогает говорить о тикетах с заинтересованными лицами. Система широко используется специалистами по пользовательскому взаимодействию.

    Примечание: мы предпочитаем использовать слово «would» вместо «won’t», потому что, исходя из нашего опыта, «would» говорит о том, что есть вероятность реализовать тикет в будущем, если что-нибудь изменится: «фича была бы реализована, если …».

Оценка по частоте

  • Пример: «Частота 1 %» означает, что затронут 1 % случаев, «Частота 100 %» — затронуты все случаи.
  • Аналогия: частота возникновения чего-либо или повторения в течение заданного периода или в рассматриваемом образце.
  • Достоинства: позволяет оценить, насколько часто возникает проблема. Можно преобразовывать в оценки вроде «20 раз в день». Можно подытоживать в виде «всегда», «часто», «иногда», «редко», «никогда». Можно выражать в процентах: «Затронуто 80 % случаев».

Совокупная оценка

Пример: оценивание тикета по комбинации приоритетности, влияния, ущерба, размера, MoSCoW и частоты.

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

  1. Продавцы сначала задают тикету Приоритетность 1.
  2. Продуктологи задают Опасность 1 (минимальное влияние), потому что опечатка тривиальна и не влияет на других клиентов.
  3. Маркетологи задают Ущерб 3 (средний), потому что опечатка сказалась на предоплате.
  4. Менеджер проекта задал «маленький» размер, потому что объём работ невелик.
  5. Проектировщики задали MoSCoW «должна быть», потому что это необходимо исправить.
  6. Команда по качеству задала Частоту 2 %, потому что проверка выявила опечатки в 2 % наименований клиентов.

Обсуждение оценок

Здесь приведены примеры обсуждения оценок тикетов.

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

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

— Форма должна быть такой: опасность * частота — лёгкость обходного решения = приоритет. Если какой-то из членов уравнения меняется (например, найдено новое обходное решение, или выясняется, что на падающую веб-страницу почти никто не заходит), тогда приоритет скорректируется. Одна лишь опасность без «оценки количества людей, на которых это повлияет» и «насколько сильно это на них повлияет?» выглядит лишь частью общей картины.

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

— У некоторых пользователей программа иногда вылетает, они теряет всю сделанную работу и очень сильно злятся. Они присваивают тикету высочайшую опасность. Но если с проблемой сталкивается только один человек, и это происходит периодически, причём пользователь уже приспособился чаще сохраняться, тогда продуктологу следует задать тикету более низкую приоритетность.

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

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

— Внутренняя система отслеживания ошибок в Google оперирует и приоритетностью, и опасностью. P0 S0 — самая срочная задача, P2 S2 — стандартная, P4 S4 — наименее срочная. Это нечто вроде дежурной шутки, что опасность не имеет смысла (потому что по сути она не отличается от приоритетности).

— Мы используем только приоритетность. Тестировщик присваивает на основании эвристик начальное значение (например, падениям — П1, косметическим улучшениям — П5). Разработчик ориентируется на это значение, чтобы выбрать баги, с которыми нужно начать работать в первую очередь. А затем приоритетность корректируется в зависимости от пользовательского опыта и поведения приложения. Если нам действительно нужно посмотреть, какие значения задал тестировщик, то мы используем функцию «истории» или «ревизии» в нашем приложении для отслеживания ошибок.

Шаблон тикета

Шаблон поможет вашей команде эффективно и ёмко охватить важные темы.

В нем могут использоваться:

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

Файл с авторским шаблоном: TEMPLATE.md

Запуск постмортемов

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

Запустить разбор можно:

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

Безобвинительные постмортемы

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

ссылка на оригинал статьи https://habr.com/ru/company/nix/blog/499804/

Создание архитектуры собственной API обертки с учетом паттернов GRASP и принципов SOLID

Вступление

Знаете, что я не люблю? — Я не люблю, когда мне приходится через раз вылезать из моей уютной IDE, чтобы посмотреть в документации по API какого-то сервиса, какие там параметры есть в очередной сущности. Ну серьезно, некоторые сервисы если и создают свой SDK/обертку или что у них там, то мало кто озаботится тем, чтобы реализовать взаимодействие пользователя с оберткой посредством преобразователя данных (Data Mapper). А ведь это может сильно упростить жизнь простым смертным разработчикам при использовании API.

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

В статье я:

  • опишу образ моего мышления при обдумывании архитектуры по принципам GRASP и SOLID
  • выстрелю себе в ногу, а затем исправлю ситуацию
  • разработаю прототип с первыми сущностями и методами

Поиски подходящей архитектуры

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

image

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

В процессе изучения я обратил внимание, что к API можно обращаться посредством «входящих вебхуков», а так-же через приложение, авторизация которого происходит средствами OAuth2.0.
Наиболее простым способом мне видится реализация через «входящие вебхуки», но стоит понимать, что это не очень безопасный способ, да и не хочется терять поддержку приложений.

Тем более что официальная обертка только с ними и работает, а мы вроде как должны быть лучше. Значит наш велосипед должен поддерживать работу с разными движками авторизации, и быть способным переключать их на ходу.

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

$leads = BX24Wrapper\Engine\WebHook::instanсe('https://b24-xxxxxx.bitrix24.ru/rest/1/********/')     ->resources()     ->leads()     ->list(['STATUS' => 'NEW']);  $applicationEngine = BX24Wrapper\Engine\Application::instanсe('client_id', 'client_secret', 'access_token',...); $leads = $applicationEngine     ->resources()     ->leads()     ->list(['STATUS' => 'NEW']); // даем возможность пользователю самому указать апи-метод и параметры, на всякий случай $responseFromSomeCall = $applicationEngine->get('crm.some.list', ['SOME' => 'VALUE']); 

То есть, у нас имеется 2 движка (WebHook и Application), эти движки принимают в себя либо «входящий вебхук», либо авторизационные данные приложения. Метод resources() вернет нам объект, содержащий список доступных ресурсов, с которыми мы можем работать(leads, deals, tasks…), а ресурсы в свою очередь — список доступных методов для работы с данными(list, add, update…). Ну, вроде все выглядит неплохо… да? Да, с точки зрения использования этот код действительно выглядит простым, но вот с архитектурной точки зрения мы выстрелили себе в ногу… из гаубицы.

Для того, чтобы осознать глубину проблемы, давайте нарисуем ориентировочную UML-схему с распределенными обязанностями:


Открыть схему

Engine\AbstractBasic — отвечает за взаимодействие с API.
Resources – справочник, в котором перечислены ресурсы к которым мы можем обращаться.
Resource\AbstractBasic — сервис, предоставляющий список доступных API-методов, собирает готовый запрос для API-движка, и приводит ответ к формату сущности.
Entity\AbstractBasic — сущность-маппер, в которой будет хранится API-ответ.

Глядя на схему, уже становится очевидно, что Engine\AbstractBasic и Resources стали взаимозависимыми, потому как движок вызывает ресурсы, передавая в них свой собственный экземпляр. И, таким нехитрым образом, мы наступили на GRASP-паттерн «низкая связанность», который призывает нас минимизировать количество связей между компонентами программы.
Теперь взглянем на свойство _engine класса Resources – оно нигде не используется внутри данного класса и предназначено только для передачи оного в конструкторы Resource\AbstractBasic и прочих однотипных классов. То есть, класс стал своеобразным мостом между движком и конкретной схемой, что начинает отдавать еще и нарушением «высокого зацепления».

Так же, все эти методы-фабрики orders(), contacts() и т.д. приводят нас к тому, что класс Resources станет самым изменяемым и самым огромным, и это выводит нас на паттерн «Устойчивый к изменениям» и SOLID-принцип «Открытости/закрытости». В идеале, любое расширение программы должно производиться добавлением нового объекта, а не редактированием уже существующих.

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

В первую очередь подумаем, как нам избавится от взаимной зависимости Engine и Resources? Resources, по сути своей, является обычным справочником со списком доступных ресурсов, с которыми можно взаимодействовать, и автоматически инициализирует объект выбранного.

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

В итоге, после всех изменений код для пользователя приобретает следующий вид:

$leads = Resource\Lead::instanсe(new Engine\WebHook('https://b24-xxxxxx.bitrix24.ru/rest/1/********/'))     ->list(['STATUS' => 'NEW']);  $applicationEngine = BX24Wrapper\Engine\Application::instanсe('client_id', 'client_secret', 'access_token',...); $leads = Resource\Lead::instanсe($applicationEngine)     ->list(['STATUS' => 'NEW']);  $responseFromSomeCall = $applicationEngine->get('crm.some.list', ['SOME' => 'VALUE']);

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


Открыть схему

Как можно увидеть, схема стала значительно проще для понимания, хотя мы, по сути, всего лишь избавились от одного класса. Конечно, все согласно формуле: меньше классов → меньше связей. Если мы хотим вообще избавится от связей, то можно создать один единственный суперкласс, который и API-метод подберет, и запрос отправит, и сущности создаст, да еще и спинку вам почешет. Вот только поддерживать такой класс придется на тех еще костылях.

image

Поэтому очень важно грамотно распределить обязанности, чтобы получить баланс между связями и зацеплением. Ответом на вопрос: «что делает класс?» – всегда должно быть что-то одно, если в описании класса есть буква «и», то появляется явный намек на то, что класс делает больше одного направленного действия, а значит это, как минимум, два класса, и стоит подумать об их разделении.

Принимая в учет вышесказанное, и прочитав описание Resource\AbstractBasic, можно с уверенностью сказать, что он выполняет больше одного действия. Первое — это предоставление API-движку запроса в нужном формате, а второе – конвертация API-ответа к сущности.
Делегируем логику создания сущности отдельному классу – «билдеру», получаем максимально зацепленный класс Resource\AbstractBasic и можем приступить к написанию кода…

Беда в пакете

image

Потратив некоторое время на реализацию первого движка и тестов под него, я решил приступить к созданию первого ресурса. Открыв API-документацию, я начал пробегать глазами по методам, чтобы выбрать первого кандидата на почетную роль ресурса. И взор мой пал на весьма нестандартный метод пакетных запросов. Я на него посмотрел, подумал и понял, что нет смысла его реализовывать без запросов, которые можно в него вложить…

И на этом моменте в моей голове щёлкнуло! Если я в будущем буду поддерживать этот метод (а поддерживать я его буду, ведь на больших нагрузках он может сильно помочь), то как пользователи будут передавать в него запросы? Строить руками? Даже в официальной обертке есть поддержка пакетных запросов, а у меня нет?! Я никак не мог согласится с таким исходом и принялся переосмыслять текущую архитектуру.

За построение запросов отвечает Resource, и вроде вот оно решение — нужно вложить в этот ресурс другие ресурсы, и дело в шляпе!

$resource = Resource\Batch::instanсe(new Engine\WebHook('https://wtfkjg.ru/fdsgfds')); $resource->sendBatchCalls([     'lead_ids' => Resource\FindByComm::find(['type' => 'EMAIL', 'values' => ['79780001122'], 'entity_type' => 'LEAD']),     'contacts' => Resource\Contact::list(['ID' => "\$result['lead_ids'][LEAD]"]), ]);

Но возникает проблема с тем, что при вызове какого-либо метода ресурса сразу отправляется запрос в API-движок, чего нам категорически не нужно. Значит придется отделять построение запроса от ресурса, и я выделил для этого отдельный класс Request. Этот класс сможет вызывать как ресурс, так и пользователь для построения пакетного запроса.

image

Но теперь я наткнулся на то, что добавление нового метода к ресурсу, влечет за собой еще и создание нового класса Request. К тому же, давать пользователю знание об еще одной группе классов мне не очень хочется…

В конечном итоге, я задал себе вопрос, а зачем нужна группа классов ресурсов? Разве они не стали обычными посредниками между запросом и движком? Да и в принципе, разве это не уменьшенная копия Resources, которую я выпилил до этого? Тут аналогичные нарушения «устойчивости» и «открытости/закрытости» из-за всех этих методов list, get и т.д.

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

$engine = new Engine\WebHook('https://b24-xxxxxx.bitrix24.ru/rest/1/********/');  $leads = $engine->execute(Request\Lead\Items::all(['STATUS' => 'NEW'], ['ID' => 'DESC']));  $responseFromSomeGetCall = $engine->execute(Request\Custom::get('some.api.method', ['SOME' => 'PARAMS'])); $responseFromSomePostCall = $engine->execute(Request\Custom::post('some.api.method', ['SOME' => 'PARAMS']));  $response = $engine->execute([     'lead_ids' => Request\FindByComm\Find::byPhone('79780001122', 'LEAD'),     'contacts' => Request\Contact\Items::firstPage(['ID' => "\$result['lead_ids'][LEAD]"], ['ID' => 'ASC']),     ]);  


Открыть схему

Пользователь сперва инициирует нужный ему движок, затем подготавливает запрос(ы), который посылает в метод execute движка, движок извлекает из запроса нужные данные, подготавливает полноценный http-запрос и отправляет его. При необходимости догружает элементы, если API-метод подразумевает работу с несколькими страницами выдачи. Затем отправляет полученный ответ в билдер, который в свою очередь создает на основе данных сущности-мапперы, которые отдаются пользователю для дальнейшей работы.

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

Разумеется, не обошлось без проблем:
Во-первых, пострадала типизация, так как пользователь посылает запрос на метод execute, у которого на возвращении прописан mixed. А это значит, что IDE не поймет, что этот метод вернет в тот или иной момент. Но это не слишком большая проблема, т.к. со сторонней библиотекой нужно взаимодействовать через собственный фасад, а там и типизацию можно указать.

Скрытый текст

Вы же так и делаете, да?

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

Итоги

Через несколько итераций создания архитектуры, удалось создать достаточно гибкую и расширяемую архитектуру для обертки под API внешней системы. Хочу обратить ваше внимание, что эта архитектура не завязана конкретно на Б24. Она прекрасно подойдет практически ко всем сервисам!

Давайте быстренько пробежимся по ней взглядом с точки зрения GRASP и SOLID, и убедимся, что мы ничего не упустили.

GRASP:

  • Информационный эксперт: каждый класс выполняет только свою работу и не нарушает инкапсуляцию.
  • Создатель: объекты может создавать только пользователь и Builder, все остальные классы не участвуют в создании объектов.
  • Контроллер: не вижу каким образом его можно было бы тут применить.
  • Низкая связанность: компоненты имеют между собой минимальное количество связей.
  • Высокое зацепление: проистекает от информационного эксперта, и, если он не нарушен, то и зацепление само собой в порядке.
  • Полиморфизм: архитектура построена на взаимодействии интерфейсов, а не конкретных классов. Соответственно функционал можно легко модифицировать и расширять.
  • Чистая выдумка: вынесение функционала создания сущностей в класс Builder.
  • Перенаправление: все тот же Builder, потому что он является посредником между сущностью и пользователем.

SOLID:

  • Принцип единственной ответственности: аналогично, что и с информационным экспертом только другими словами.
  • Принцип открытости/закрытости: мы без лишнего труда можем добавить новый API-движок, новый запрос, другой билдер, и все благодаря интерфейсам.
  • Принцип подстановки Барбары Лисков: текущая архитектура не предусматривает несоблюдение предоставленных интерфейсов, и, что бы их нарушить, еще нужно постараться.
  • Принцип разделения интерфейса: интерфейсы предполагают максимально узкий спектр функционала, даже разделять нечего.
  • Принцип инверсии зависимостей: все классы взаимодействуют друг с другом через интерфейсы, благодаря чему, зависимость от подробностей сведена, если не к 0, то к минимуму.

В целом, я считаю, что получилось весьма неплохо. Какие выводы из всего этого можно сделать? Как минимум, всегда пытайтесь выведать информацию о выполняемой задаче, если не на 100%, то хотя бы на 90%, что бы не приходилось потом переделывать сделанное, как я. Впрочем мне повезло, и все, что я исправил — это схему и интерфейс движка. А ведь мог бы создать десятки ресурсов, а потом их все со слезами на глазах удалять…

Так-же, советую вам распечатать шаблоны GRASP и принципы SOLID на бумаге и повесить где-нибудь возле рабочего стола, если вы с ними не знакомы. Это позволит вам легко их заучить и начать строить архитектуру приемлемого уровня. Даже если вы работаете на какой-нибудь галере «формошлепом», то хорошее знание этих вещей даст вам шанс пробиться в неплохую продуктовую компанию.

Так же буду благодарен за объективную критику как архитектуры и образа мышления, так и кода.
Ссылочка на код: https://github.com/Dangetsu/bitrix24-api-wrapper
Обертка пока что только в разработке, потому еще не оформлена, как подобает.

Код пишется под версию PHP 7.1, если будет достаточно желающих, то могу создать отдельную репку под меньшие версии.

Благодарности:
slyshkin за советы по изложенной теме и вычитку текста

Полезные ссылочки:

ссылка на оригинал статьи https://habr.com/ru/post/499612/

Когда начнет работать раздельный сбор мусора и зачем нужен видеомониторинг площадок для мусорных контейнеров

Проблема

Когда наконец человечество осознало, что создает слишком много мусора, ситуация в мире уже приближалась к катастрофической. "Мусорный остров" в Тихом океане размером в три Франции и огромные площади суши, отданные под захоронение мусора, заставили мировое сообщество начать поиск решения мусорной проблемы. Например, только в России площадь полигонов для захоронения коммунальных отходов сегодня сравнима с территорией Нидерландов.

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

В теории люди должны отделить биоразлагаемые отходы от вторсырья, складывая их в разные контейнеры. Вторсырье должны отвезти на мусороперерабатывающие заводы, чтобы произвести из него новый товар. Таким образом, создавая подобные процессы в стиле «лента Мебиуса» можно значительно сократить объемы мусора, который требует сжигания или захоронения.
На практике попытки коммерческих и общественных организаций, и даже целых регионов России организовать раздельный сбор мусора столкнулись с рядом сложностей. Во-первых, это по-прежнему невысокая доля участия населения в процессе. Труд же той части населения, которая осуществляет раздельный сбор мусора, нивелируется той частью населения, которые этого не делают. Во-вторых, региональные операторы по обращению с бытовыми отходами при транспортировании мусора могут допускать смешивание контейнеров с вторсырьем и контейнеров с несортированным мусором. Такие факты полностью обесценивает попытки людей изменить текущую экологическую ситуацию. В-третьих, несвоевременный вывоз мусора с площадок для размещения мусорных контейнеров приводит к замусоренности окружающей территории, включая придомовую территорию жилых домов. Это еще один факт, который отрицательно сказывается на формировании у населения эко-культуры, ведь как утверждает теория «разбитых окон»:

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

.

Мировой опыт

Например, в Швейцарии, площадь которой немногим меньше территории всех российских полигонов для захоронения мусора, у населения очень высокоразвитый уровень эко-культуры. Гражданин Швейцарии может самостоятельно осуществлять раздельный сбор мусора или оплатить повышенный тариф за утилизацию смешанного мусора, который отсортирует за эти деньги кто-то другой. А для нарушителей установленного порядка в стране работает «мусорная полиция», которая ведет контроль за тем, кто и сколько создает мусора и как его выносит. За нарушение правил обработки отходов предусмотрены высокие штрафы, а для доказательства фактов нарушений используются материалы видеофиксации.

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

Техническое решение

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

Преимущество заключается в том, что одна видеокамера может осуществлять мониторинг нескольких контейнеров размещенных на площадке (порядка 6-8 контейнеров), а также всей прилегающей территории, что значительно экономичнее, чем установка и обслуживание датчиков в каждый контейнер. Кроме того, такое решение соответствует принципу переиспользования инфраструктуры и данных, являющемуся частью концепции «Умный город». В настоящее время во дворах многих жилых домов уже установлены видеокамеры для обеспечения безопасности, и они непрерывно генерируют видеоданные. Переиспользование этих данных для целей мониторинга переполнения мусорных контейнеров возможно и экономически оправданно. Кроме того, видеоаналитика позволяет, одновременно решать и другие полезные задачи для улучшения комфорта населения. Вот некоторый их перечень:

  1. мониторинг порчи или кражи контейнера,
  2. раннее предупреждение возгораний мусора,
  3. несанкционированный выброс мусора,
  4. оценка площади разброса мусора,
  5. фиксация автотранспорта создающего помехи движению и доступа к площадке,
  6. состояние очистки площадки и прилегающей к ней территории от снега,
  7. мониторинг открытых люков и состояния дорожного полотна,
  8. мониторинг состояния зеленых насаждений.

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

Так как российское законодательство в настоящее время не предъявляет требования к обеспечению площадок для хранения бытовых отходов средствами мониторинга, то эта деятельность регулируется рынком. Именно поэтому, все больше российских региональных операторов по обращению с бытовыми отходами внедряют системы мониторинга наполнения мусорных контейнеров, что позволяет им организовать вывоз мусора по принципу «точно в срок». Для населения это означает чистоту и порядок на площадках, а для регионального оператора это значительная экономия затрат на перевозках и обеспечение высоких стандартов качества обслуживания.

Выводы

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

Мировой рынок утилизации отходов прогнозирует уверенный рост в горизонте 2020 -2025 годов на 75%. Требования населения к качеству оказываемых услуг возрастают по мере перехода городов в категорию «Умный город». Это вызывает усиление контрактных требований к своевременному вывозу бытовых отходов с площадок и обострение конкуренции между региональными операторами по обращению с бытовыми отходами. Поэтому региональным операторам необходимо начинать сегодня создавать систему мониторинга, которая будет жизненно необходима уже завтра. На какой технологии будет работать эта система, и какие метрики мониторинга будут в ней обрабатываться, каждый регоператор будет решать исходя из места, времени и обстоятельств.

Единственное о чем можно говорить с уверенностью, так это о том, что время раздельного обращения с мусором в нашей стране пришло и отступать нам некуда, позади – планета, здоровье и наша жизнь.

ссылка на оригинал статьи https://habr.com/ru/post/499846/

Qbs: Шаблон настольного приложения

Введение

Давным давно, когда Qbs только вышла, я начинал писать эту статью, но так её и не закончил… Кажется, пришло время ее дописать. С тех пор многое изменилось, у Qbs наконец-то появилась документация, но примеров (к сожалению) в ней по-прежнему не так много. В этой статье я расскажу как написать шаблон (почти) полноценного десктопного приложения с использованием Qt.Widgets. По-хорошему, было бы неплохо сделать это на чистом C++, но я слишком ленив, чтобы сделать тестовый UI с помощью нативного АПИ под 3 платформы. Для примера я написал простое приложение ("рыбу"), состоящее из основного приложения, библиотеки и плагина, которое мы и будем разбирать

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

image

Код расположен на гитхабе, и был протестирован под Windows, Linux и macOS.

Я не буду подробно описывать процесс сборки, установки и настройки Qbs, это достаточно подробно описано в документации.

Предвосхищая комментарий о том, что Qbs объявлена устаревшей в пользу CMake как система сборки для Qt, сразу отмечу, что сейчас проект развивается сообществом и недавно вышла новая версия.

И так, приступим.

Любой Qbs проект состоит из корневого элемента Project, который может в себе содержать один или несколько продуктов, а также ссылки над подпроекты (для организации иерархии, каждая вложенная папка содержит подпроект). Product — это результат сборки чего-либо — например, бинарник приложения, статическая/динамическая библиотека, сгенерённые файлы переводов, и тому подобное. Продукты могут зависеть либо от модулей (таких как модуль cpp, модули Qt), либо от других продуктов.

Корневой файл проекта тривиален:

Project {     name: "Qbs Fish"     minimumQbsVersion: "1.16"     references: [          "src/src.qbs",          "tests/tests.qbs",     ]     qbsSearchPaths: "qbs"      AutotestRunner {} }

Мы задаем имя проекта и минимальную версию Qbs. Для сборки необходима версия Qbs 1.16 так как используются некоторые фичи, добавленные только в этой версии (например, модуль freedesktop). Свойство references содержит ссылки на подпроекты (папки src и tests). AutotestRunner — это продукт, при сборке которого запускаются тесты. Переменная qbsSearchPaths отвечает за то, где Qbs будет искать пользовательские модули и айтемы и задается в виде относительного (от текущего файла) пути. Папка qbs содержит две подпапки — modules (для, гм, модулей) и imports (для айтемов).

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

Казалось бы, логично объявить пользовательский айтем, скажем, MyProduct, наследующий Product, вынести в него всё общее и отнаследовать от него MyApplication, MyLibrary, MyPlugin… К сожалению, тогда мы потеряем возможность использовать встроеные айтемы, такие как CppApplication, DynamicLibrary и иже с ними, так как Qbs не поддерживает множественное наследование. Эти айтемы предоставляют ряд удобных вещей — установку продукта, его отладочных символов и мультеплексирование (например, возможность собирать "fat binaries" под iOS).

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

Итак, разберем содержимое модуля buildconfig. Сперва мы задаем "входные" свойства с помощью которых пользователь может сконфигурировать проект, например, собрать статически или включить поддержку санитайзера.

Module {     property bool staticBuild: false     property bool frameworksBuild: qbs.targetOS.contains("macos") && !staticBuild      property bool enableAddressSanitizer: false     property bool enableUbSanitizer: false     property bool enableThreadSanitizer: false      property string libDirName: "lib"

Задать эти свойства с командной строки можно так:

qbs modules.buildconfig.staticBuild:true modules.buildconfig.enableUbSanitizer:true

Затем мы объявляем вспомогательные константы — относительные пути куда ставить части проекта (там много однотипных "свитчей" по целевой платфоме, поэтому приведу только пару таких "свитчей"):

    readonly property string appTarget:         qbs.targetOS.contains("macos") ? "Fish" : "fish"      readonly property string installAppPath: {         if (qbs.targetOS.contains("macos"))             return "Applications";         else if (qbs.targetOS.contains("windows"))             return ".";         else             return "bin";     }

Полный список констант

  • appTarget — имя главного бинарника (бандла на маке)
  • installAppPath — путь, куда будет установлен главный бинарник, например "Applications" на маке или "bin" на линуксе
  • installBinaryPath — путь, куда будут установлены вспомогательные бинарники (на маке кладутся внутрь главного бандла, на остальных платформах рядом с главным бинарником)
  • installLibraryPath — путь, куда ставить дллки
  • installPluginPath — путь, куда ставить плагины
  • installDataPath — путь, куда ставить ресурсы и данные

Наконец, мы объявляем общие свойства, такие как флаги компилятора и версия языка:

    Depends { name: "cpp" } // включаем модуль, реализующий поддержку С/С++      cpp.cxxLanguageVersion: "c++17" // задаем версию языка     cpp.separateDebugInformation: true // форсим отделение дебаг инфы      Properties {         condition: qbs.toolchain.contains("gcc")         cpp.cxxFlags: { // флаги компилятора (но не линковщика)             var flags = [];             if (enableAddressSanitizer)                 flags.push("-fno-omit-frame-pointer");             return flags;         }         cpp.driverFlags: { // флаги компилятора И линковщика             var flags = [];             if (enableAddressSanitizer)                 flags.push("-fsanitize=address");             if (enableUbSanitizer)                 flags.push("-fsanitize=undefined");             if (enableThreadSanitizer)                 flags.push("-fsanitize=thread");             return flags;         }     } }

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

Library {     Depends { name: "buildconfig" }     Depends { name: "bundle" }     Depends { name: "cpp" }

buildconfig — это наш кастомный модуль
bundle — это модуль, реализующий поддержку бандлов на яблочных платформах
cpp — этот модуль мы уже видели выше, в нем живет поддержка C/C++/Objective-C.

Затем мы устанавливаем тип нашей библиотеки в зависимости от того, статический билд или нет, а также включаем или выключаем упаковку в бандл:

    type: buildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary"      bundle.isBundle: buildconfig.frameworksBuild

Мы устанавливаем includePaths так, чтобы там находился родительский каталог для того, чтобы инклюды к нашим хедерам включали имя библиотеки: #include <library/header>. Также, мы объявляем всмомогательные дефайны, чтобы было проще разбираться с макросами импорта/эспорта (скорей бы модули!).

     cpp.includePaths: [".."]      cpp.defines: buildconfig.staticBuild                ? ["FISH_STATIC_LIBRARY"]                : ["FISH_LIBRARY"]

Дальше идет установка sonamePrefix и rpaths. Установка sonamePrefix нужна только на маке, так как по умолчанию soname включает полный путь к библиотеке (а не только имя библиотеки), поэтому мы заменяем абсолютный путь на "@rpath" — список относительных путей для поиска. Также мы задаем этот список (rpaths) равным одному элементу — текущей папке библиотеки (rpathOrigin, раскрывается в "$ORIGIN" на линуксе или "@loader_path" на маке). Таким образом, все наши библиотеки смогут искать зависимости рядом с собой:

cpp.sonamePrefix: qbs.targetOS.contains("macos") ? "@rpath" : undefined cpp.rpaths: cpp.rpathOrigin

Затем мы объявляем свойства, которые наша библиотека экспортирует, то есть те свойства, которые будут автоматически добавлены в продукты, которые зависят от нашей библиотеки:

    Export {         Depends { name: "cpp" }         cpp.includePaths: [".."]         cpp.defines: buildconfig.staticBuild ? ["FISH_STATIC_LIBRARY"] : []     }

Также как и выше, мы экспортируем includePaths, содержащие родительский каталог — теперь любой продукт, где бы он не находился в проекте, сможет делать #include <library/header>. Кроме того, в статической сборке вы делаем так, чтобы зависимые продукты объявляли макрос "FISH_STATIC_LIBRARY", иначе при включении заголовков нашей библиотеки, они будут пытаться импортировать символы из несуществующей dll (венда боль).

Наконец, мы говорим, куда ставить нашу библиотеку:

    install: !buildconfig.staticBuild     installDir: buildconfig.installLibraryPath     installDebugInformation: !buildconfig.staticBuild }

Теперь, когда у нас есть базовый айтем, мы можем создать пример готовой библиотеки FishLib. Этот код создаст нам бинарник libFishLib.so (FishLib.dll на винде, libFishLib.dylib на маке) и установит его и его отладочные символы в соответствующую папку:

MyLibrary {     name: "FishLib"     files: [         "class.cpp",         "class.h",         "fishlib_global.h",     ] }

Что может быть проще!

Базовый айтем для приложений сильно проще и единственное отличие — это то, как мы задаем rpaths:

    cpp.rpaths: FileInfo.joinPaths(cpp.rpathOrigin,                                    "..",                                    qbs.targetOS.contains("macos")                                    ? "Frameworks"                                    : buildconfig.installLibraryPath)

Результатом является "$ORIGIN/../lib/fish" на линуксе и "@loader_path/../Frameworks/" на маке — то есть мы поднимаемся на уровень выше от bin (или папки MacOS внутри бандла) и спускаемся в lib/fish (или Frameworks). В Windows библиотеки ставятся рядом с бинарником, так как там rpath не завезли.

Базовый айтем для плагинов и пример плагина я расписывать не буду, там всё тривиально, мы просто переиспользуем MyLibrary.qbs.

Итак, настало время собрать это всё в готовое приложение. Как обычно, мы наследуем базовый айтем и импортируем необходимые модули и продукты. Стоит отметить, что при включении зависимости от плагина, мы явно говорим о том, что с ним не надо линковаться (но его надо собрать до сборки нашего приложения). Также, зависимости можно подключать в только если выполнено условие, например, модуль ib доступен только на яблочных платформах:

MyApp {     Depends { name: "buildconfig" }     Depends { name: "ib"; condition: qbs.targetOS.contains("macos") }     Depends { name: "freedesktop" }     Depends { name: "Qt.core" }     Depends { name: "Qt.widgets" }     Depends { name: "FishLib" }     Depends { name: "FishPlugin"; cpp.link: false }

Мы задаем имя нашего продукта и имя бинарника так, чтобы файл назывался с большой буквы на Маке и с маленькой на Линуксе и Винде (Fish.app, fish и fish.exe, соответственно); "правильное" название хранится в buildconfig.appTarget:

    name: "Fish"     targetName: buildconfig.appTarget

Задаем список файлов:

    files: [         "Fish-Info.plist",         "fish.desktop",         "fish.rc",         "fish.xcassets",         "main.cpp",         "mainwindow.cpp",         "mainwindow.h",         "mainwindow.ui",     ]

Fish-Info.plist содержит свойства бандла на маке (например, копирайт). Минимальный Info.plist Qbs генерит сама, но мы можем переопределять свойства руками в файле или с помощью свойства bundle.infoPlist.

fish.desktop содержит свойства приложения в Линуксе — имя приложения в "меню пуск", какую команду запускать, какую иконку использовать. Этот файл установится автоматически благодаря зависимости от модуля freedesktop.

Аналогично, fish.rc содержит свойства экзешника в Windows (например, всё то же имя иконки).

Каталог fish.xcassets содержит исходники для иконки на маке, имя иконки мы задаем с помощью модуля ib:

    Properties {         condition: qbs.targetOS.contains("macos")         ib.appIconName: "Fish"     }

Qbs скомпилирует из png-файлов различного разрешения, находящихся в каталоге fish.xcassets/Fish.appiconset файл Fish.icns и пропишет имя иконки в результирующий Info.plist. Так как модуль ib доступен только на яблочных платформах, нужно завернуть установку его свойств в Properties, иначе получим ошибку о том, что свойства ib.appIconName нет.

Ставим иконку приложения на Линуксе. Модуль freedesktop позволяет ставить svg иконки в share/icons/hicolor/scalable/apps, но у меня нет векторного варианта иконки, поэтому ставим "ручками" в share/pixmaps:

    Group {         name: "fish.png"         condition: qbs.targetOS.contains("linux")         files: [ "fish.png" ]         qbs.install: true         qbs.installDir: "share/pixmaps"     }

В итоге получились следующие иерархии каталогов:

Linux

На самом деле, мы ставим в <install-root>/usr/local, но опустим префикс и install-root:

/bin /bin/fish /bin/fish.debug /bin/tool /bin/tool.debug /lib /lib/fish /lib/fish/libFishLib.so /lib/fish/libFishLib.so.debug /lib/fish/plugins /lib/fish/plugins/libFishPlugin.so /lib/fish/plugins/libFishPlugin.so.debug /share /share/applications /share/applications/fish.desktop /share/pixmaps /share/pixmaps/fish.png

macOS

Содержимое папок *dSYM/ опущено для простоты

/Applications/Fish.app /Applications/Fish.app.dSYM /Applications/Fish.app/Contents /Applications/Fish.app/Contents/Frameworks /Applications/Fish.app/Contents/Frameworks/FishLib.framework /Applications/Fish.app/Contents/Frameworks/FishLib.framework.dSYM /Applications/Fish.app/Contents/Info.plist /Applications/Fish.app/Contents/MacOS /Applications/Fish.app/Contents/MacOS/Fish /Applications/Fish.app/Contents/MacOS/tool /Applications/Fish.app/Contents/MacOS/tool.dSYM /Applications/Fish.app/Contents/PkgInfo /Applications/Fish.app/Contents/PlugIns /Applications/Fish.app/Contents/PlugIns/libFishPlugin.dylib /Applications/Fish.app/Contents/PlugIns/libFishPlugin.dylib.dSYM /Applications/Fish.app/Contents/Resources /Applications/Fish.app/Contents/Resources/Fish.icns

Windows

/FishLib.dll /FishLib.pdb /fish.exe /fish.pdb /plugins /plugins/FishPlugin.dll /plugins/FishPlugin.pdb /tool.exe /tool.pdb

Выведение

Как видно, Qbs позволяет собирать достаточно сложные проекты, из коробки поддерживает различные платформы (включая Android, iOS и различные микроконтроллеры). Благодаря декларативному синтаксису, работать с этой системой сборки одно удовольствие, проекты получаются простыми и достигается максимальное переиспользование кода.

ссылка на оригинал статьи https://habr.com/ru/post/183020/

Как за 40 минут проанализировать конкурентов и настроить digital-стратегию

Мы поговорили с Андреем Юнисовым — директором по маркетингу в швейцарской компании Bacula Systems. Ранее он работал с Билайн, МТС, Мегафон, Tele2, Lamoda и другими известными брендами. Андрей рассказал про сервис SimilarWeb и про то, как анализ конкурентов поможет выстроить стратегию, спланировать бюджет и допустить меньше ошибок.

Эта статья — конспект интервью с Андреем.

Инструменты, которые позволяют анализировать конкурентов, появились в России недавно. Я имею в виду численный формат. То есть не просто зайти на сайт и посмотреть, как расположены картинки и тексты, а именно получить серьёзные численные данные.


Сегодня мы можем узнать, что происходит на сайте:

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

Точность этих данных повысилась в последние 2 года, появились инструменты, которые помогают решать эти задачи. Поэтому конкурентный анализ — это шаг номер один в digital-проекте.

Инструменты для анализа сайтов

Есть два типа устройств, которые позволяют собирать данные. Первый — это счётчики статистики на вашем сайте. Но их нельзя поставить на сайт конкурентов, если его не взломать. Второй тип не требует никакой установки на сайт — это такие сервисы как SimilarWeb, ComScore и Alexa.

SimilarWeb использует несколько способов получения данных. Первый — это партнёрство с тулбарами. Это такие программы, которые устанавливаются в браузер (например, Яндекс.бар). Как только вы установили такую программу в браузер, все ваши действия фиксируются. Видно, какие сайты вы посещаете, в какой последовательности, сколько проводите там времени, что делаете.

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

Для конкурентного анализа используйте SimilarWeb

Сегодня можно изучить конкурентов за 40 минут: посмотреть, какие они используют источники трафика, как по их сайту ходят пользователи, где у них на сайте «узкие места». Это делается с помощью SimilarWeb.

SimilarWeb — крупный сервис, а одно из его приятных бонусов — бесплатная версия. Функционала хватит для того, чтобы проанализировать любой значимый сайт. Достаточно, чтобы у него было больше 5 тысяч посещений в месяц. Всё, что меньше — статистически не репрезентативно.

Для конкурентного анализа используйте SimilarWeb
Сегодня можно изучить конкурентов за 40 минут: посмотреть, какие они используют источники трафика, как по их сайту ходят пользователи, где у них на сайте «узкие места». Это делается с помощью SimilarWeb.

Ранее такие инструменты были неточными из-за нехватки данных. Но в последние годы точность повысилась, и по моему опыту ошибки в анализе составляют 20-30%.

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

Можно оценить тренд: набирается посещаемость на сайте конкурента или падает. Соответственно, в динамике мы можем делать выводы, какую стратегию выполнять.

Что можно проверить в SimilarWeb

Например, можно проверить, сколько всего посетителей было на сайте у конкурента. Можно смотреть до 5 конкурентов одновременно.

Вот, что проверяю я:

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

То есть SimilarWeb раскрывает те метрики конкурентов, которые есть в стандартных счётчиках статистики на вашем сайте.

Вот главный экран сервиса. Здесь я выбрал три сайта, которые будем дальше анализировать:

Первый — это относительно маленький интернет-магазин artikstudio.ru. Сравним его с Lamoda и Wildberries — двумя большими магазинами одежды. Обратите внимание на то, как ведут себя графики и метрики, когда вы анализируете маленький сайт и большой сайт. Или когда вы выбираете большой сайт и сравниваете с таким же большим конкурентом.

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

Я не советую делать вывод на основе средних чисел — это «средняя температура по больнице». Советую искать места на графиках, где данные ведут себя аномально или противоречат друг другу. Анализируйте именно это место, а не просто смотрите на средние числа внизу экрана.

Сверху в презентации я обвёл красным прямоугольником то место, в котором наблюдается противоречие. Трафик Lamoda снизился, а у Wildberries поднялся.

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

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

Если у нас такая ситуация как на экране, то можно предположить несколько вещей:

  • Lamoda мог подсесть технический компонент;
  • Wildberries могли запустить рекламную кампанию и перетянуть клиентов Lamoda;
  • изменился контент: новый ассортимент, цены или появление акции.

Всё надо проверить и сделать выводы. Понять, сработает ли у вас.

Из данных SimilarWeb вам надо получить инсайт — модное понятие среди маркетологов.

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

Используйте SimilarWeb в связке с другими сервисами

Что нам делать, если мы склоняемся к тому, что на трафик повлияло изменение контента? Например, мы видим, что аномалия выпала на последнюю неделю ноября, а сейчас февраль. Мы не можем зайти на сайт конкурента и увидеть тот самый контент, который привёл трафик — там уже все поменялось. Тогда мы обращаемся к сервису Wayback Machine. Этот инструмент также называется веб-архив:

Здесь мы ввели страницу Lamoda и видим временной график, где изображены снимки сайта на определённый момент времени. Если нажать на синие или зеленые точки, то откроется тот внешний вид сайта Lamoda, который был на эту дату.

Wayback Machine — сервис который показывает прошлые версии сайтов

Возьмите за правило: если в SimilarWeb трафики расходятся, надо идти в Wayback Machine и смотреть, что там происходило.

Wayback Machine чем-то похож на поискового робота. Он заходит к вам на сайт и сохраняет копию себе. Иногда эта копия не полная, поэтому сайты не всегда корректно отображаются.

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

Также надо понимать, что он хранит довольно много данных. Как-то со знакомым мы нашли, как выглядел один сайт в 1999 году. 20 лет назад!

Изучайте как можно больше показателей в SimilarWeb

Посмотрите деление мобильного трафика:

Это данные 2016 года, здесь только 30% мобильного трафика. В 2019 его будет больше: около 40% пользователей зайдут с десктопа и 60% с мобильных устройств.

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

Посмотрите показатели отказов:

Синяя и рыжая линии поднимаются. Это рост показателя отказов примерно на 10-15%. Тут можно сделать предположения. Например, если вы видите, что вместе с этим растёт посещаемость, то скорее всего конкуренты включили рекламную кампанию с некачественным трафиком.

Посмотрите распределение источников трафика по ресурсам:

Сверху — относительные значения (в процентах), снизу — абсолютные (в единицах посещаемости.)

Здесь можно посмотреть, какие источники трафика используют ваши конкуренты и в каком объёме. Какое между ними распределение. Видно, какого трафика больше: органического или проплаченного.

Здесь отчётливо видно, что у Wildberries намного выше Brand Awareness — то есть пользователи чаще заходят напрямую на этот сайт, что очень хорошо. Так бренд получает бесплатные целевые заходы на свой ресурс.

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

Если вы ищете зоны, куда можно относительно дёшево вторгнуться, чтобы вести маркетинг на переполненном рынке, то посмотрите на поисковое распределение. В нашем случае видно, что у двух больших магазинов небольшой трафик с поиска по видео. Это дырки в видео-поиске, что для нас неплохо — можно подтянуть оттуда трафик на свой сайт довольно недорого, если заняться своим YouTube-каналом.

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

Если мы занимаемся СММ или таргетированной рекламой, нам надо как-то попытаться понять, в какую площадку нам инвестировать. Считается, что во ВКонтакте больше людей, и поэтому многие решают инвестировать туда. Но лучше посмотреть под другим углом. Например, здесь видно, что на Facebook меньше давления в случае Lamoda и Wildberries. Мы можем там таргетированно начать что-то делать. И брать Instagram, естественно.

Дальше важный отчёт — популярные страницы:

К сожалению, в бесплатной версии он больше не доступен, есть только в платной.

Но из него можно выцепить много интересной информации. Я взял в пример интернет-магазин Asos. Этот отчёт нам показывает посещаемость в процентах для каждой страницы на сайте.

На 5-й позиции мы видим pgcheckout.aspx, что имеет отношение к началу оформления заказа. Мы видим, что через эту страницу проходят 2,16% пользователей. Что это для нас значит? Это маркер конверсии. То есть мы видим, что людей, которые начинают что-то заказывать, всего 2,16%.

На 3-м месте страница корзины. То есть мы знаем, что в корзину попадает 3,11%, а на чекаут — 2,16%. А если адрес каждого следующего шага корзины отличается, то мы можем оценить все этапы конверсии этого магазина.

Тут же мы видим, что раздел sale (под номером 9) занимает половину всего трафика раздела women (2 строка). Затем можно посмотреть на signin (4 строка) и понять, что на сайте логинится 2,78% людей.

Фактически можно сделать юзабилити-анализ на данных конкурента и оценить функционал.

Платная версия SimilarWeb стоит в пределах нескольких сотен долларов — не такая страшная сумма.

И последняя полезная функция на сегодня — отчёт «Исходящие ссылки». Он показывает, на какие сайты переходят пользователи после просмотра. Я проверил Wildberries:

На 4 месте мы видим сайт wbpay.ru. Смотрим его, и становится ясно, что это сайт, через который клиенты Wildberries оплачивают покупки. Получаем:

Так с помощью SimilarWeb можно за минут 40 изучить конкурентов: источники трафика, поведение пользователей, функционал, потенциальные ошибки. Если смотреть более подробно и совмещать SimilarWeb с другими инструментами, будет легче оценить бюджет на источники трафика.

ссылка на оригинал статьи https://habr.com/ru/post/499850/