Интервью с исследователем рынка и трендов разработки ПО в Центральной и Восточной Европе, Юджином Швабом-Чесару

Eugen Schwab-ChesaruПо долгу службы я взяла интервью у человека, много лет занимающегося исследованиями рынка, трендов разработки ПО и ИТ-услуг в Центральной и Восточной Европе, из них 15 — и в России. И хотя самое интересное, на мой взгляд, собеседник оставил «за кадром», тем не менее, рассказ этот может быть и любопытен, и вдохновляющ. Смотрите сами.

Юджин, здравствуйте, для начала скажите, как правильно произносится ваше имя?

По-румынски – Эуджен Шваб-Чесару, на английском – Юджин, по-русски – Евгений, в Москве, в России, все знают меня как Евгения из PAC.

Вы много работали с Россией. Не могли бы вы рассказать о своем опыте?

Я начал работать в PAC более 20 лет назад. Проводил исследования рынка услуг стратегического консалтинга, посвященных индустрии программного обеспечения и ИТ-услуг в Центральной и Восточной Европе. Ключевые страны этого региона: Россия, Польша, Чехия, Словакия, Венгрия, Турция и Румыния, также мы много работали с рынками Украины, Болгарии, Сербии. Наш офис в Румынии занимается как раз Центральной и Восточной Европой, и я управляю этим офисом более 20 лет.

С Россией мы начали работать 15 лет назад, тогда мы провели 20-30 встреч в Москве, и несколько – в Санкт-Петербурге. С тех пор поддерживали связь с российскими игроками в области ПО и ИТ-услуг, особенно среди крупных и средних компаний. Мы также связывались со многими оффшорными ИТ-компаниями, некоторые из них из России, и некоторые очень известны в Европе, США и во всем мире.

В чем суть вашей работы, чем вы занимаетесь?

Мы находимся в середине того, что нужно для стратегического маркетинга ИТ-компаний. Это исследование рынка, анализ рынка, конкурентный анализ, вплоть до прогноза и стратегических рекомендаций для компаний, занимающихся ПО и ИТ-услугами. Это ядро нашего бизнеса, то, чем наша компания занимается уже 45 лет в Европе и во всем мире.

Последние 10-15 лет мы много работали с пользователями — и со стороны компаний, и со стороны инвесторов. Это касается рынков ПО и ИТ-услуг, направлений и игроков. Например, ИТ-директора нас просят представить им картину, наше понимание, а также прогнозы о развитии различных технологий на разных рынках, о позиционировании разных компаний в конкретных областях, технологических направлениях, или в конкретном бизнесе.

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

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

  1. Код, программный продукт или ИТ-служба;
  2. Вертикаль, например, банковское дело или производственный или государственный сектор и пр.;
  3. Географическая координата, например, регион или страна, или группа стран.

Чтобы быть в состоянии всё это предоставлять, мы находимся в постоянном контакте с ИТ-компаниями, и лицами, принимающими решения в области ИТ. Мы проводим обширный опрос вместе с несколькими партнерами, особенно в Западной Европе, США и во всем мире, но также и в Восточной Европе (в меньшей степени – из-за размера, который вы сами можете себе представить).

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

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

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

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

Мы придерживаемся целостного взгляда на индустрию ПО и ИТ-услуг, это то, что мы предлагаем в Восточной Европе и пытаемся делать и в России.

Я знаю, что в ноябре в Санкт-Петербурге вы выступите с докладом «Тенденции и возможности в мировой индустрии программного обеспечения и ИТ-услуг». О чем будет доклад? Вы поделитесь своими исследованиями?

Да, мы собираемся поделиться последним результатом нашего опроса и нашими выводами: какие наиболее важные направления будут развиваться в инустрии разработки ПО и ИТ-сервисов. У нас в опросе есть длинный список из 20–30 тем, которые мы предлагаем во время интервью с лицами, принимающими решения в области ИТ, и в итоге получаем 10-15 направлений, которые стоят первыми в списке и упоминаются чаще. По этим темам мы и углубимся в детали.

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

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

Когда вы работаете, общаетесь с людьми из России, отличается ли это от общения с людьми из других стран?

Люди, с которыми я встречался – это руководители среднего и высшего звена. Они прекрасно ориентируются в том, что происходит в мире. В то же время, если сравнивать российских руководителей ИТ-компаний с подобными руководителями, например, из Польши, Чехии или Румынии, я чувствую, что российские руководители гордятся тем, что они из России, и что их локальный рынок потенциально полон возможностей.

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

А если у вас будет такой же разговор с российским руководителем — он уверен в своем успехе в крупных сделках, даже напрямую с крупными игроками Западной Европы. Они привыкли иметь дело с крупными организациями. Это очень мощно, я думаю, это очень важное условие для достижения успеха, потому что сегодня все происходит очень-очень быстро в ИТ-индустрии. И если вы запланировали небольшие шаги для выхода на внешний рынок, в конце дня вы будете удивлены, потому что когда через три года вы “созреете”, условия будут отличаться от того момента, когда вы приступили к осуществлению стратегического плана.

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

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

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

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

И ещё, конечно, самой важной составляющей сегодня являются человеческие ресурсы, правильные навыки. Я вижу индустрию, которая движет, собственно, индустрией и рынком в целом, и твердо верю, что российские компании могут быть гораздо более заметными в Западной Европе. Вообще, когда я думаю, что около полумиллиона ИТ-инженеров сегодня не хватает в Западной Европе, и если мы посчитаем все проекты, которые не были выполнены из-за нехватки ресурсов, если я смотрю на темпы роста цен и огромные планы цифровой трансформации практически всех организации в Европе, США, я могу сказать, что нет предела для компаний, которые действительно обладают правильными технологиями и необходимыми навыками, и серьезно относятся к реализации проектов в областях, где сегодня высок спрос.

Спасибо, что нашли время на этот разговор, что бы вы хотели пожелать нашим слушателям?

Надеюсь, что у вас появится много идей, и много ответов на вопросы, и – почему бы и нет – еще больше готовности развиваться, инвестировать и верить в будущее всей ИТ-индустрии России и всего мира.

Вопросы задавала: Юлия Крючкова.
Дата интервью: 9 сентября 2019.
N.B. Это сокращённый вариант интервью в переводе, оригинал на английском здесь.


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

Мы забыли про делегирование в JavaScript. Event delegation in React

Всем привет. Статья о делегирование событий в JavaScript и реализация его в react.js.

О чем собственно речь? Зачем и почему?

Для начала давайте кратко обсудим:

  1. что есть событие;
  2. как происходит распространение;
  3. обработка DOM Level 2 с примером на JavaScript;

И в конце: почему не надо забывать об делегировании в React.

Событие

JavaScript с HTML взаимодействуют между собой за счёт событий (events). Каждое событие служит для того, чтобы сказать JavaScript’у о том, что в документе или окне браузера что-то произошло. Для того чтобы отловить эти события нам нужны слушатели (listeners), этакие обработчики, которые запускаются в случае возникновения события.

Распространение событий

Порядок. Решая проблему: как понять, какой части страницы принадлежит событие? Было реализовано два способа: в Internet Explorer — “всплытие событий”, а в Netscape Communicator — “перехват событий”.

Всплытие событий

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

<!DOCTYPE html> <html>   <head>     <title>Some title</title>   </head>   <body>     <div id="myDiv">Click Me</div>   </body> </html>

В этом случае будет такой порядок:

  1. элемент div
  2. элемент body
  3. элемент html
  4. document
  5. window

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

В случае с перехватом событий работает все наоборот:

  1. window
  2. document
  3. элемент html
  4. элемент body
  5. элемент div

Задумывалось что событие можно будет обработать до того, как оно достигло целевого элемента (так решили в Netscape позже подхватили все современные браузеры).

В итоге мы имеем такую структуру распространения DOM-событий:

  1. window
  2. document
  3. элемент html
  4. элемент body // заканчивается фаза перехвата
  5. элемент div // целевая фаза
  6. элемент body // начинается фаза всплытия
  7. элемент html
  8. document
  9. window

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

Итак, переходим к обработке событий

Посмотрим типичный пример обработки события в JavaScript.

const btn = document.getElementById('myDiv') btn.addEventListener("click", handler)  // some code  btn.removeEventListener("click", handler)

Все бы нечего, но тут мы вспоминаем про наш любимы IE который подписывается на события с помощью attachEvent, а для удаления detachEvent. А еще можно подписываться на событие несколько раз. И не забываем что подписавшись анонимной функцией мы не имеем возможность отписаться.

Но мы же не г*внокодеры. Сделаем все по канону:

var EventUtil = {   addHandler: function (elem, type, handler) {     if (elem.addEventListener) {       elem.addEventListener(type, handler, false)     } else if (elem.attachEvent) {       elem.attachEvent("on" + type, handler)     } else {       elem["on" = type] = hendler     }   },    removeHandler: function (elem, type, handler) {     if (elem.removeEventListener) {       elem.removeEventListener(type, handler, false)     } else if (elem.detachEvent) {       elem.detachEvent("on" + type, handler)     } else {       elem["on" = type] = null     }   } }

Так хорошо, а как же объект event? Ведь в IE нет .target есть .srcElement, preventDefault? нет returnValue = false. Но нечего добавим пару методов:

var EventUtil = {   addHandler: function (elem, type, handler) {     if (elem.addEventListener) {       elem.addEventListener(type, handler, false)     } else if (elem.attachEvent) {       elem.attachEvent("on" + type, handler)     } else {       elem["on" = type] = hendler     }   },    getEvent: function (event) {     return event ? event : window.event   },    getTarget: function (event) {     return event.target || event.srcElement   },    preventDefault: function (event) {     if (event.preventDefault) {       event.preventDefault()     } else {       event.returnValue = false     }   },    removeHandler: function (elem, type, handler) {     if (elem.removeEventListener) {       elem.removeEventListener(type, handler, false)     } else if (elem.detachEvent) {       elem.detachEvent("on" + type, handler)     } else {       elem["on" = type] = null     }   },    stopPropagation: function (event) {     if (event.stopPropagation) {       event.stopPropagation()     } else {       event.cancelBubble = true     }   } }

И т.д. и т.п. и вот эти все танцы.

Хорошо мы молодцы, все проблемы решили, все ок. Правда код вышел довольно громоздким. А теперь представим, что нам нужно много подписок на множество элементов. Ух это займет не мало строк кода. Пример:

<ul> <li id="id1">go somewhere</li> <li id="id2">do something</li> <li id="some-next-id">next</li> </ul>  var item1 = document.getElementById('id1') var item2 = document.getElementById('id2') var itemNext = document.getElementById('some-next-id')  EventUtil.addHandler(item1, "click", someHandle) EventUtil.addHandler(item2, "click", someHandle2) EventUtil.addHandler(itemNext, "click", someHandle3)

И так для каждого элемента, и надо удаление не забыть, работа с таргет и тому подобное

И тут к нам на помощь приходит делегирование событий (event delegation).

Все что нам надо это подключить один единственный обработчик к наивысшей точке в DOM-дереве:

<ul id="main-id"> // навешиваем id на родительский элемент <li id="id1">go somewhere</li> <li id="id2">do something</li> <li id="some-next-id">next</li> </ul>  var list = document.getElementById('main-id')  EventUtil.addHandler(list, "click", function(event) {     event = EventUtil.getEvent(event)     var target = EventUtil.getTarget(event)      switch(target.id) {        case "id1":           // делаем что-то для элемента с id1           break        case "id2":           // делаем что-то для элемента с id1           break        case "some-next-id":           // делаем что-то для следующих элементов           break     } })

В итоге у нас только один обработчик в памяти, а для нужного действия можно использовать свойство id. Меньшее потребление памяти повышает общее быстродействие страницы в целом. Для регистрации обработчика событий требуется меньше времени и меньше обращений к DOM. Исключение разве что mouseover и mouseout, с ними все немного сложнее.

А теперь что насчёт React

Все что касается кросcбраузерности за нас уже все сделали ребята из facebook. Все наши обработчики событий получают экземпляр SyntheticEvent (ссылку на доки реакт). Который заботится о нас повторно используя события из пула удаляя все свойства после вызова обработчика.

Хорошо.

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

class Example extends React.Component {   handleClick () {     console.log('click')   }    render () {     return (       <div>         {new Array(20).fill().map((_, index) =>           <div             key={index} // elem.id             id={index} // elem.id             onClick={() => console.log('click')}           />         )}       </div>     )   } } 

В примере показан случай, когда есть какой-то лист с n-количеством элементов, а значит и с n-количеством регистраций обработчиков.

Запустим зайдем на страницу и проверим сколько обработчиков сейчас в деле. Для этого я нашёл не плохой скрипт:

Array.from(document.querySelectorAll('*'))   .reduce(function(pre, dom){     var clks = getEventListeners(dom).click;     pre += clks ? clks.length || 0 : 0;     return pre   }, 0) 

Работает в dev-tool хрома.

А теперь делегируем все это родительскому div элементу и ура, мы только что оптимизировали наше приложение в n=array.length раз. Пример код ниже:

class Example extends React.Component {   constructor () {     super()     this.state = {       useElem: 0     }   }   handleClick (elem) {     var id = elem.target.id     this.setState({ useElem: id })   }    render () {     return (       <div onClick={this.handleClick}>         {new Array(20).fill().map((_, index) =>           <div             key={index} // elem.id             id={index} // elem.id             useElem={index === this.state.useElem}           />         )}       </div>     )   } }

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

Статья написана на основе книги JavaScript для профессиональных веб-разработчиков, автор: Николас Закас.

Спасибо большое за внимание. Если есть чем поделится или нашли какой-то недочет, может ошибку или просто есть вопрос, то пишите в комментариях. Буду рад любой обратной связи!


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

PVS-Studio идёт в облака: Azure DevOps

Picture 9

Это вторая статья про использование статического анализатора PVS-Studio в облачных CI-системах, и на этот раз мы рассмотрим платформу Azure DevOps – облачное CI\CD-решение от компании Microsoft. В качестве анализируемого проекта в этот раз рассмотрим ShareX.

Нам потребуется три компонента. Первый — статический анализатор PVS-Studio. Второй — Azure DevOps, с которой мы будем интегрировать анализатор. Третий — проект, который мы будем проверять для демонстрации возможностей PVS-Studio при работе в облаке. Итак, приступим.

PVS-Studio — статический анализатор кода для поиска ошибок и дефектов безопасности. Выполняет анализ кода на языке C, C++, C# и Java.

Azure DevOps. В состав платформы Azure DevOps входят такие инструменты, как Azure Pipeline, Azure Board, Azure Artifacts и другие, позволяющие ускорить процесс создания программного обеспечения и повысить его качество.

ShareX – бесплатное приложение, позволяющее захватывать и записывать любую часть экрана. Проект написан на C# и отлично подходит для демонстрации настройки запуска статического анализатора. Исходный код проекта доступен на GitHub.

Вывод команды cloc для проекта ShareX:

Language files blank comment Code
C# 696 20658 24423 102565
MSBuild script 11 1 77 5859

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

Приступим к настройке

Для начала работы в Azure DevOps перейдем по ссылке и нажмём кнопку «Start free with GitHub».

Picture 2

Предоставим приложению Microsoft доступ к данным GitHub-аккаунта.

Picture 1

Для окончания регистрации придется создать аккаунт Microsoft.

Picture 12

После регистрации создадим проект:

Picture 5

Далее нам необходимо перейти в раздел «Pipelines» — «Builds» и создать новый Build pipeline

Picture 8

На вопрос, где расположен наш код, ответим – GitHub.

Picture 13

Авторизуем приложение Azure Pipelines и выберем репозиторий с проектом, для которого мы будем настраивать запуск статического анализатора

Picture 7

В окне выбора шаблона выберем «Starter pipeline».

Picture 17

Запускать статический анализ кода проекта мы можем двумя путями: используя Microsoft-hosted либо self-hosted агенты.

В первом варианте мы будем использовать Microsoft-hosted агенты. Такие агенты представляют собой обычные виртуальные машины, которые запускаются, когда мы запускаем наш pipeline, и удаляются после окончания задачи. Использование таких агентов позволяет не тратить время на их поддержку и обновление, но накладывает некоторые ограничения, например – невозможность установки дополнительного программного обеспечения, которое используется для сборки проекта.

Заменим предлагаемую нам конфигурацию по умолчанию на следующую для использования Microsoft-hosted агентов:

# Настройка триггеров запуска # Запускаем для изменений только в master-ветке trigger: - master  # Так как установка произвольного ПО в виртуальные машины  # запрещена, мы воспользуемся Docker-контейнером, # запущенном в виртуальной машине с Windows Server 1803 pool:   vmImage: 'win1803' container: microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-1803             steps: # Скачиваем дистрибутив анализатора - task: PowerShell@2   inputs:     targetType: 'inline'     script: 'Invoke-WebRequest                 -Uri https://files.viva64.com/PVS-Studio_setup.exe                 -OutFile PVS-Studio_setup.exe' - task: CmdLine@2   inputs:     workingDirectory: $(System.DefaultWorkingDirectory)     script: | # Восстанавливаем проект и скачиваем зависимости       nuget restore .\ShareX.sln # Создаем директорию, куда будут сохранены файлы с отчетами анализатора       md .\PVSTestResults # Устанавливаем анализатор       PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES                       /NORESTART /COMPONENTS=Core # Создаем файл с настройками и лицензионной информацией       "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe"            credentials      -u $(PVS_USERNAME)      -n $(PVS_KEY)  # Запускаем статический анализатор и конвертируем отчет в html.        "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe"           -t .\ShareX.sln            -o .\PVSTestResults\ShareX.plog       "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe"           -t html           -o .\PVSTestResults\           .\PVSTestResults\ShareX.plog      # Сохраняем отчеты анализатора - task: PublishBuildArtifacts@1   inputs:     pathToPublish: PVSTestResults     artifactName: PVSTestResults 

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

Сохраним pipeline и создадим переменные, которые будут использоваться для создания файла лицензии. Для этого откроем окно редактирования pipeline и в правом верхнем углу нажмем кнопку «Variables».

Picture 14

Добавим две переменные — PVS_USERNAME и PVS_KEY, содержащие имя пользователя и лицензионный ключ соответственно. При создании переменной PVS_KEY не забываем отметить пункт «Keep this value secret» для шифрования значения переменной 2048-битным RSA ключом, а также подавления вывода значения переменной в лог выполнения задачи.

Picture 15

Сохраняем переменные и запускаем pipeline кнопкой «Run».

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

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

Для запуска задачи на self-hosted агенте заменим предлагаемую конфигурацию по умолчанию на следующую:

# Настройка триггеров запуска # Запускаем анализ для master-ветки trigger: - master  # Задача запускается на self-hosted агенте из пула 'MyPool'  pool: 'MyPool'  steps: - task: CmdLine@2   inputs:     workingDirectory: $(System.DefaultWorkingDirectory)     script: | # Восстанавливаем проект и скачиваем зависимости       nuget restore .\ShareX.sln # Создаем директорию, куда будут сохранены файлы с отчетами анализатора       md .\PVSTestResults # Запускаем статический анализатор и конвертируем отчет в html.        "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe"           -t .\ShareX.sln          -o .\PVSTestResults\ShareX.plog       "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe"          -t html          -o .\PVSTestResults\          .\PVSTestResults\ShareX.plog # Сохраняем отчеты анализатора - task: PublishBuildArtifacts@1   inputs:     pathToPublish: PVSTestResults     artifactName: PVSTestResults

После окончания выполнения задачи архив с отчетами анализатора можно скачать на вкладке «Summary», либо же мы можем воспользоваться расширением Send Mail, позволяющим настроить отправку электронной почты, или поискать более удобный для нас инструмент на Marketplace.

Picture 21

О результатах анализа

Теперь рассмотрим некоторые из ошибок, которые удалось обнаружить в проверяемом проекте — ShareX.

Избыточные проверки

Для разминки давайте начнём с простых недочётов в коде, а именно — с избыточных проверок:

private void PbThumbnail_MouseMove(object sender, MouseEventArgs e) {   ....   IDataObject dataObject      = new DataObject(DataFormats.FileDrop,                      new string[] { Task.Info.FilePath });    if (dataObject != null)   {     Program.MainForm.AllowDrop = false;     dragBoxFromMouseDown = Rectangle.Empty;     pbThumbnail.DoDragDrop(dataObject,          DragDropEffects.Copy | DragDropEffects.Move);     Program.MainForm.AllowDrop = true;   }   .... }

Предупреждение PVS-Studio: V3022 [CWE-571] Expression ‘dataObject != null’ is always true. TaskThumbnailPanel.cs 415

Обратим внимание на проверку переменной dataObject на null. Для чего она здесь? dataObject просто не может иметь значение null в данном случае, так как инициализируется ссылкой на создаваемый объект. В итоге имеем избыточную проверку. Критично? Нет. Выглядит лаконично? Нет. Эту проверку явно лучше убрать, чтобы не загромождать код.

Давайте взглянем ещё на один фрагмент кода, к которому можно предъявить аналогичные замечания:

private static Image GetDIBImage(MemoryStream ms) {   ....   try   {     ....     return new Bitmap(bmp);     ....   }   finally   {     if (gcHandle != IntPtr.Zero)     {       GCHandle.FromIntPtr(gcHandle).Free();     }   }   .... } private static Image GetImageAlternative() {   ....   using (MemoryStream ms = dataObject.GetData(format) as MemoryStream)   {     if (ms != null)     {       try       {         Image img = GetDIBImage(ms);         if (img != null)         {           return img;         }       }       catch (Exception e)       {         DebugHelper.WriteException(e);       }     }   }   .... }

Предупреждение PVS-Studio: V3022 [CWE-571] Expression ‘img != null’ is always true. ClipboardHelpers.cs 289

В методе GetImageAlternative опять выполняется проверка на то, что переменная img не имеет значения null сразу после того, как создали новый экземпляр класса Bitmap. Отличие от предыдущего примера тут состоит в том, что для инициализации переменной img мы используем не конструктор, а метод GetDIBImage. Автор кода предполагает, что в этом методе может возникнуть исключительная ситуация, но объявляет только блоки try и finally, опуская catch. Следовательно, если произойдёт исключение, то вызывающий метод GetImageAlternative не получит ссылку на объект типа Bitmap, а будет вынужден обрабатывать исключение в собственном блоке catch. В этом случае переменная img не будет проинициализирована, и поток исполнения даже не дойдёт до проверки img != null, а сразу попадёт в блок catch. Следовательно, анализатор действительно указал на избыточную проверку.

Рассмотрим следующий пример предупреждения с кодом V3022:

private void btnCopyLink_Click(object sender, EventArgs e) {   ....   if (lvClipboardFormats.SelectedItems.Count == 0)   {     url = lvClipboardFormats.Items[0].SubItems[1].Text;   }   else if (lvClipboardFormats.SelectedItems.Count > 0)   {     url = lvClipboardFormats.SelectedItems[0].SubItems[1].Text;   }   .... }

Предупреждение PVS-Studio: V3022 [CWE-571] Expression ‘lvClipboardFormats.SelectedItems.Count > 0’ is always true. AfterUploadForm.cs 155

Присмотримся ко второму условному выражению. Там мы проверяем значение свойства Count, доступного только для чтения. Данное свойство показывает количество элементов в экземпляре коллекции SelectedItems. Условие выполняется только в том случае, если свойство Count больше нуля. Всё было бы хорошо, да вот только во внешнем операторе if уже выполняется проверка на то, что Count равен нулю. Экземпляр коллекции SelectedItems не может иметь количество элементов меньше нуля, следовательно, Count принимает значение либо равное нулю, либо больше нуля. Раз мы уже выполнили проверку в первом операторе if на то, что Count равен нулю, и оно оказалось ложным, бессмысленно писать в ветке else ещё одну проверку на то, что Count больше нуля.

Заключительным примером ошибки с номером V3022 будет следующий фрагмент кода:

private void DrawCursorGraphics(Graphics g) {   ....   int cursorOffsetX = 10, cursorOffsetY = 10, itemGap = 10, itemCount = 0;   Size totalSize = Size.Empty;    int magnifierPosition = 0;   Bitmap magnifier = null;    if (Options.ShowMagnifier)   {     if (itemCount > 0) totalSize.Height += itemGap;     ....   }   .... }

Предупреждение PVS-Studio: V3022 Expression ‘itemCount > 0’ is always false. RegionCaptureForm.cs 1100

Анализатор заметил, что условие itemCount > 0 всегда будет ложным, так как чуть выше выполняется объявление и одновременное присваивание переменной itemCount значения, равного нулю. Вплоть до самого условия эта переменная нигде не используется и не изменяется, следовательно, анализатор сделал правильный вывод об условном выражении, значение которого всегда ложно.

Что ж, давайте теперь рассмотрим что-то действительно интересное.

Самый хороший способ понять баг — это визуализировать баг

Как нам кажется, довольно интересная ошибка обнаружилась в этом месте:

public static void Pixelate(Bitmap bmp, int pixelSize) {   ....   float r = 0, g = 0, b = 0, a = 0;   float weightedCount = 0;    for (int y2 = y; y2 < yLimit; y2++)   {     for (int x2 = x; x2 < xLimit; x2++)     {       ColorBgra color = unsafeBitmap.GetPixel(x2, y2);        float pixelWeight = color.Alpha / 255;        r += color.Red * pixelWeight;       g += color.Green * pixelWeight;       b += color.Blue * pixelWeight;       a += color.Alpha * pixelWeight;        weightedCount += pixelWeight;     }   }   ....   ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount),     (byte)(g / weightedCount), (byte)(r / weightedCount),     (byte)(a / pixelCount));   .... }

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

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

Данный метод принимает на вход два параметра: объекта типа Bitmap и значение типа int, которое обозначает размер пикселизации. Алгоритм работы достаточно прост:

1) Разбиваем полученный на входе фрагмент изображения на квадраты со стороной, равной размеру пикселизации. К примеру, если у нас размер пикселизации равен 15, то мы получим квадрат, содержащий 15×15=225 пикселей.

2) Далее мы обходим каждый пиксель в этом квадрате и аккумулируем значения полей Red, Green, Blue и Alpha в промежуточные переменные, причём предварительно перемножая значение соответствующего цвета и значение альфа канала на переменную pixelWeight, получаемую путём деления значения Alpha на 255 (переменная Alpha имеет тип byte). Также при обходе пикселей мы суммируем значения, записанные в pixelWeight, в переменную с именем weightedCount. Фрагмент кода, выполняющий вышеописанные действия, выглядит следующим образом:

ColorBgra color = unsafeBitmap.GetPixel(x2, y2);  float pixelWeight = color.Alpha / 255;  r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight;  weightedCount += pixelWeight;

Кстати, обратите внимание на то, что если значение переменной Alpha равно нулю, то и pixelWeight не будет добавлять к переменной weightedCount никакого значения для данного пикселя. Это нам понадобится в дальнейшем.

3) После того, как мы обошли все пиксели в текущем квадрате, мы можем составить общий «усреднённый» цвет для данного квадрата. Код, выполняющий эти действия, выглядит следующим образом:

ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount),     (byte)(g / weightedCount), (byte)(r / weightedCount),     (byte)(a / pixelCount));

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

5) Возвращаемся к пункту 2 до тех пор, пока ещё остались необработанные квадраты.

Ещё раз обратим внимание, что переменная weightedCount не равна количеству всех пикселей в квадрате. К примеру, если в изображении встречается абсолютно прозрачный пиксель (значение ноль по альфа каналу), то переменная pixelWeight будет равна нулю для данного пикселя (0 / 255 = 0), следовательно, этот пиксель не внесёт никакой вклад во формирование значения переменной weightedCount. Это и логично — нет смысла учитывать цвета абсолютно прозрачного пикселя.

Всё кажется вполне разумным — пикселизация должна работать правильно. И она действительно работает правильно. Вот только не для png изображений, которые имеют в своём составе пиксели со значениями в альфа канале меньше 255 и неравными нулю. Обратите внимание на пикселизированную картинку снизу:

Picture 3

Увидели пикселизацию? И мы нет. Хорошо, теперь давайте раскроем эту небольшую интригу и поясним, где именно прячется баг в этом методе. Ошибка закралась в строку вычисления значения переменной pixelWeight:

float pixelWeight = color.Alpha / 255;

Дело в том, что автор кода, объявляя переменную pixelWeight типом float, подразумевал, что при делении поля Alpha на число 255 помимо нуля и единицы должны получаться дробные числа. Здесь и кроется проблема, так как переменная Alpha имеет тип byte, и при делении её на число 255 мы получаем целочисленное значение, и только потом оно будет неявно приведено к типу float, следовательно, происходит потеря дробной части.

Невозможность провести пикселизацию для изображений формата png, которые имеют некоторую степень прозрачности, легко объяснить. Так как значения альфа канала у данных пикселей лежит в диапазоне 0 < Alpha < 255, то, при делении переменной Alpha на число 255, мы всегда будем получать 0. Следовательно, значения переменных pixelWeight, r, g, b, a, weightedCount тоже всегда будут равны нулю. Как итог, наш усреднённый цвет averageColor будет с нулевыми значениями по всем каналам: красный — 0, синий — 0, зелёный — 0, альфа — 0. Закрашивая квадрат в такой цвет, мы не изменяем исходный цвет пикселей, так как averageColor является абсолютно прозрачным. Для того, чтобы исправить эту ошибку, необходимо просто явно привести поле Alpha к типу float. Исправленная строка кода может выглядеть следующим образом:

float pixelWeight = (float)color.Alpha / 255;

И пора привести сообщение, которое выдавал PVS-Studio на ещё некорректный код:

Предупреждение PVS-Studio: V3041 [CWE-682] The expression was implicitly cast from ‘int’ type to ‘float’ type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. ImageHelpers.cs 1119

И для сравнения приведём скриншот действительно пикселизированного изображения, полученного на исправленной версии приложения:

Picture 6

Потенциально возможное исключение NullReferenceException

public static bool AddMetadata(Image img, int id, string text) {   ....   pi.Value = bytesText;    if (pi != null)   {     img.SetPropertyItem(pi);     return true;   }   .... }

Предупреждение PVS-Studio: V3095 [CWE-476] The ‘pi’ object was used before it was verified against null. Check lines: 801, 803. ImageHelpers.cs 801

Данный фрагмент кода показывает, что его автор ожидал, что переменная pi может иметь значение null, именно поэтому перед тем, как вызывать метод SetPropertyItem, выполняется проверка pi != null. Странно, что перед этой проверкой происходит присваивание свойству pi.Value массива байт, ведь если pi будет равно null, то будет выброшено исключение типа NullReferenceException.

Аналогичная ситуация была замечена и в другом месте:

private static void Task_TaskCompleted(WorkerTask task) {   ....   task.KeepImage = false;    if (task != null)   {     if (task.RequestSettingUpdate)     {       Program.MainForm.UpdateCheckStates();     }     ....   }   .... }

Предупреждение PVS-Studio: V3095 [CWE-476] The ‘task’ object was used before it was verified against null. Check lines: 268, 270. TaskManager.cs 268

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

Предупреждение PVS-Studio: V3095 [CWE-476] The ‘Config.PhotobucketAccountInfo’ object was used before it was verified against null. Check lines: 216, 219. UploadersConfigForm.cs 216

Одно и тоже возвращаемое значение

Подозрительный фрагмент кода был обнаружен в методе EvalWindows класса WindowsList, который при любых обстоятельствах возвращает true:

public class WindowsList {   public List<IntPtr> IgnoreWindows { get; set; }   ....   public WindowsList()   {     IgnoreWindows = new List<IntPtr>();   }    public WindowsList(IntPtr ignoreWindow) : this()   {     IgnoreWindows.Add(ignoreWindow);   }   ....   private bool EvalWindows(IntPtr hWnd, IntPtr lParam)   {     if (IgnoreWindows.Any(window => hWnd == window))     {       return true;  // <=     }      windows.Add(new WindowInfo(hWnd));      return true;  // <=   } }

Предупреждение PVS-Studio: V3009 It’s odd that this method always returns one and the same value of ‘true’. WindowsList.cs 82

Кажется логичным, что если в списке с названием IgnoreWindows был бы найден указатель с таким же значением, как и у hWnd, то метод должен был вернуть значение false.

Список IgnoreWindows может заполняться либо при вызове конструктора WindowsList(IntPtr ignoreWindow), либо напрямую через доступ к свойству, так как оно публичное. Так или иначе, если верить Visual Studio, на данный момент в коде этот список никак не заполняется. Это ещё одно странное место этого метода.

Небезопасный вызов обработчиков событий

protected void OnNewsLoaded() {   if (NewsLoaded != null)   {     NewsLoaded(this, EventArgs.Empty);   } }

Предупреждение PVS-Studio: V3083 [CWE-367] Unsafe invocation of event ‘NewsLoaded’, NullReferenceException is possible. Consider assigning event to a local variable before invoking it. NewsListControl.cs 111

В данном случае у нас может произойти следующая неприятная ситуация: после проверки переменной NewsLoaded на неравенство null метод, выполняющий обработку события, может быть отписан, к примеру, в другом потоке, и, когда мы попадём в тело условного оператора if, переменная NewsLoaded уже будет равна null. Попытка вызвать подписчиков у события NewsLoaded, которое имеет значение null, приведёт к возникновению исключения NullReferenceException. Гораздо безопаснее воспользоваться null-условным оператором и переписать приведённый выше код следующим образом:

protected void OnNewsLoaded() {   NewsLoaded?.Invoke(this, EventArgs.Empty); }

Анализатор указал на ещё 68 аналогичных мест. Описывать здесь их не будем — паттерн вызова события в них подобный.

Возвращаем null из ToString

Не так давно из одной интересной статьи моего коллеги я узнал, что Microsoft не рекомендует возвращать null из переопределяемого метода ToString. PVS-Studio хорошо осведомлён об этом:

public override string ToString() {   lock (loggerLock)   {     if (sbMessages != null && sbMessages.Length > 0)     {       return sbMessages.ToString();     }      return null;    }  }

Предупреждение PVS-Studio: V3108 It is not recommended to return ‘null’ from ‘ToSting()’ method. Logger.cs 167

Зачем присваивать, если не используешь?

public SeafileCheckAccInfoResponse GetAccountInfo() {   string url = URLHelpers.FixPrefix(APIURL);   url = URLHelpers.CombineURL(APIURL, "account/info/?format=json"); .... }

Предупреждение PVS-Studio: V3008 The ‘url’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 197, 196. Seafile.cs 197

Как видно из примера, при объявлении переменной url ей присваивается некоторое значение, возвращаемое из метода FixPrefix. В последующей строке мы «перетираем» полученное значение, даже нигде его не использовав. Получаем что-то похожее на «мёртвый код» — работу выполняет, на итоговый результат никак не влияет. Данная ошибка, скорее всего, является результатом копипаста, так как подобные фрагменты кода встречаются ещё в 9 методах. Для примера, приведём два метода с аналогичной первой строкой:

public bool CheckAuthToken() {   string url = URLHelpers.FixPrefix(APIURL);   url = URLHelpers.CombineURL(APIURL, "auth/ping/?format=json");   .... } .... public bool CheckAPIURL() {   string url = URLHelpers.FixPrefix(APIURL);   url = URLHelpers.CombineURL(APIURL, "ping/?format=json");   .... }

Итого

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

В заключение предлагаем и вам загрузить и попробовать анализатор на своих проектах.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Oleg Andreev, Ilya Gainulin. PVS-Studio in the Clouds: Azure DevOps.


ссылка на оригинал статьи https://habr.com/ru/company/pvs-studio/blog/467359/

RubyRussia 2019. Михаил Пронякин: безопасен ли Ruby

На конференции RubyRussia будет много докладов о том, как писать код и как делать это лучше других. Но если продукт, который выпускает ваша компания, небезопасен, то это может привести к большим проблемам. Григорий Петров обсудил эту важную тему с Михаилом Пронякиным из компании «ОНСЕК», разработчик комплексной платформы «Валарм».

image
  
Расскажи, чем ты занимаешься и как используешь Ruby?

Когда-то, давным-давно я работал специалистом в области информационной безопасности. Делал что-то вроде пентестов веб-приложений и различных физических устройств. Параллельно с этим занимался системным программированием на языке C. В то время, как и сейчас, был популярен фреймворк Metasploit, который можно было расширять собственными модулями поиска и эксплуатации уязвимостей. Он был написан на Ruby — так началось мое знакомство с этим языком. Затем я перешел работать в компанию «Онсек», где сначала ускорял некоторые критические части бэкенда проекта путем переписывания кода с Ruby на C, а потом стал писать новую функциональность только на Ruby. Деятельность нашей компании связана с информационной безопасностью, мы делаем «Валарм» — платформу для защиты и тестирования веб-приложений (и не только). Получается, что я одновременно и Ruby-разработчик, и специалист по информационной безопасности.

Твой доклад будет связан с этой темой. Расскажи подробнее. 

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

На твой взгляд, как сейчас Rails в плане обеспечения безопасности?

В философии Ruby и Rails существует такое понятие как «приоритет соглашения над конфигурацией». Если в соглашении все продумано, то никаких проблем с безопасностью и не появится. Но если вдруг возникает ситуация, что ты можешь по умолчанию написать уязвимый код, то это уже может стать причиной серьезных проблем. Особенно у начинающих разработчиков, которые только начали изучать Rails и даже не задумываются про безопасность своего кода. Если смотреть в прошлое, то раньше с безопасностью везде было хуже, чем сейчас. Это касается не только Ruby, но и других языков и фреймворков. С каждым годом фичи безопасности интегрируются во фреймворки все больше и глубже. Например, в Rails уже из коробки везде есть CSRF токены, что очень хорошо. И все это работает под капотом, но если ты не знаешь как и хочешь сделать что-то кастомное, то безопасность приложения может быть нарушена. 

Если рассматривать уязвимости, то их можно очень грубо поделить на 2 вида: это уязвимости в runtime и уязвимости самого языка. Когда-то давно в Python была печальная история — оказалось, что в Python хеш для словаря рассчитывается без соли. Можно было злонамеренно спровоцировать бесконечное количество коллизий. В результате словари переполнялись, и сервера умирали от нескольких мегабайт атакующих запросов. Скажи, в мире Rails, по твоему опыту, сколько уязвимостей приходится на сам Ruby, а сколько уязвимостей приходится на рельсы?

Если рассматривать тему уязвимостей, то она намного более обширная, чем просто Ruby и Rails. Например, у нас есть nginx. Если его неправильно сконфигурировать, то можно будет просто прочитать некоторые файлы с сервера и получить секрет Rails приложения. И все, приложение взломано – делай, что хочешь. Нужно понимать, что безопасность не ограничивается одним языком и фреймворком — есть среда, где это исполняется, среда, где это собирается, и еще миллион разных нюансов.

А по количеству можно ли как-то прикинуть, где находится больше уязвимостей? Вне Ruby и Rails, в самом языке, в его runtime, в стандартной библиотеке или в Rails как фреймворке, который использует Ruby?

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

За последние несколько лет какая была самая смешная уязвимость, которую ты или твои коллеги находили? Из серии «Ааа, ну это ж надо было так облажаться».

Самая запоминающаяся уязвимость была в геме, позволяющим озвучивать тексты. Ты ему передаешь текст, и он его озвучивает. Гем вызывал консольную утилиту и передавал ей параметры без должного экранирования. В результате обнаружилась инъекция в Bash, и можно было услышать результат выполнения произвольной команды. Ты передаешь на вход команду «ls /», а голос в ответ диктует «slash dev slash etc» и так далее.

Последние несколько лет экосистема языков сверхвысокого уровня – Python, Ruby, JavaScript – опирается на огромное количество маленьких библиотек. Их много, и они зависят друг от друга так, что убери какой-нибудь pad-left, и это убивает экосистему. Злоумышленники начинают либо делать свои библиотеки, либо брать контроль над чужими и добавлять в них какой-нибудь вредоносный код, который не найдешь. Насколько это сейчас большая и страшная проблема? 

Проблема есть, но пока о ней никто особо не думает, все полагаются «на авось». Если у злоумышленника есть цель атаковать конкретную компанию и определенным способом, то его сегодня никто не остановит. Ничто не мешает сделать просто хороший гем, набрать много звезд на GitHub и дождаться, пока все начнут его использовать. Затем скрытно встроить в него вредоносный код, что совсем не сложно. Думаю, вопрос зависимостей сегодня – это вопрос доверия: проблема открыта и ещё только ждет своего решения.

Увидимся на RubyRussia!

Вопросы на тему безопасности можно будет задать Михаилу после доклада, а так же обсудить на стенде его компании. Посмотреть на то, какие еще темы будут обсуждать рубисты 28 сентября можно на сайте.

А кроме спикеров и участников, можно познакомиться и с замечательными компаниями, которые поддерживают конференцию:

Организатор — Evrone
Генеральный партнер — Toptal
Золотой партнер — Gett
Серебярные партнеры — Валарм, JetBrains, Bookmate и Cashwagon
Бронзовый партнер — InSales


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

Customer Experience Management – что это?

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

Меня зовут Роман Нохрин (Р), а отвечать на вопросы будет эксперт — Арсен Даллакян (А), управляющий партнер консалтинговой компании Russian Behavioral Unit.

(Р) Итак, переходим сразу к вопросу: хочется понять, чтобы синхронизировать разговор, о чем вообще мы будем говорить. Что такое customer experience (СХ), customer experience management (CEM)? Что я слышу от других: у очень большого количества людей это сводится к слову сервис. И хочется понять: что это на самом деле такое, как управлять и где болит?

(А) Да, это есть боль. Боль в том, что СХ-management (CEM), который зарождался как инструмент кроссфункционального сотрудничества внутри компании с целью повышения клиентской ценности на рынке, превратился в качество обслуживания клиентов, в сервис.
И почему я называю это болью, поднимаю красный флаг? Потому что я вижу:

все что превращается в России в сервис — все затухает.

Так была убита отличная идея customer relationship management (CRM), которая по сути и была предвестником СЕМ. Она говорила о том, что с клиентами надо строить отношения и тогда эти отношения будут приносить вам больше денег, нежели вы просто будете впаривать им новый продукт. Во что у нас вылилась CRM? В лучшем случае это программа, которая показывает, когда дни рождения у клиентов и рассылает им имейл. Это первое.

Второй этап – это когда начали называть своим языком сервис. И говорить: в сервисе у нас главная точка роста прибыльности, потому что счастливый клиент платит больше. Такой был тезис. Во что это вылилось?

(Р) Звучит вроде нормально?

(А) Звучит все прекрасно. Но вылилось это в то, что появились вылизанные отделы по работе с жалобами, появились отделы SMM, которые купируют негатив в сети и качество работы отдела по сервису заключается в том, есть ли негатив, есть ли необработанные жалобы и как быстро мы их отрабатываем.

(Р) Чувствуется, что что-то не то?

(А) Это на две головы вниз! Да, это все хорошо, но это не то, к чему мы шли.
Потом появилась надежда на СЕМ. Я слежу за развитием темы с 2006 года, поэтому вижу эволюцию. Появился СХ-management. Я за него зацепился, потому что увидел в нем потенциал объединяющего подхода разных функций: туда входят ИТ, маркетологи, продавцы, продуктовики, финансисты — все. Которые объединяются с целью повлиять на клиентское взаимодействие, чтобы больше с клиента заработать, потому что он [клиент] счастливый хочет больше платить.

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

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

Мы представляем сейчас клиентский опыт — часто его визуализируют в CJM. Вот мы его представили, на стену он лег. И чтобы его воспроизводить мы должны на что-то влиять — на что-то финансисты, на что-то продавцы, на что-то айтишники. Мы смотрим на каждый отрезок, на точку — какие функции влияют и в какой степени. И что нужно изменить, чтобы влияние это было наиболее позитивное. И эти изменения иногда доходят до уровня, что культуру [компании] надо менять, где-то надо рушить функциональные болота. Потому что для айтишника счастливый клиент — это тот, кто не нажимает лишние кнопки, для маркетолога — кто уходит с улыбкой из отделения, для продавца — кто получил скидку. Это все очень субъективно. И как раз идея СЕМ была в том, чтобы объективизировать понимание: что есть клиент через поведение, через конкретные действия и научиться управлять этим сообща.
Я увидел, что появились технологии, поддерживающие это, большие данные.

По сути СХ-management стоит на трех китах.

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

И вот эти три кита создают сильные предпосылки к возможности управлять клиентским опытом как цельным. И когда мы это осознали в 2017 году — мурашки по коже пошли. Мы разработали методологию как этим управлять, преподавали executive mba, и вроде компании начали принимать это.

(Р) А у меня такой вопрос: очень по многим темам, когда мы о них узнаем и видим, что в России появился тренд, оказывается, что в мире их используют уже 10-20 лет. Начинаешь интересоваться и оказывается, что тот же agile, который стал популярным у нас с 2013-15 года, впервые упоминается в литературе еще с конца 80х годов во всем мире [может без формулировки манифеста, но предпосылки]. С этой темой мы также отстаем или уже одинаково с остальным миром начинает развиваться? Когда ты говоришь что в 2017 году вдохновились: во всем мире или мы?

(А) В мире где-то в 14 году. Но важно понимать, у СЕМ получился ренессанс. СХ многие называли — впечатления клиента от компании. Только впечатления. Только кусочек счастья. Это была эволюция relationships. Об этом говорили еще в начале нулевых. Определение и термин не новые. Но потом, как инструмент управления всей клиентской ценностью — это был 14 год в штатах: «Zappos» появился, начал развиваться «Amazon». И в Россию эта история пришла в 17 году. Появились и конференции, и должности в компаниях начали появляться и я был хэппи. Какая история была смешная: в 14 году я думал: «во что трансформируется маркетинг?», понимая, что маркетинг как функция в компании умер. И обозначало это не как наука о создании добавочной ценности (это никогда не пропадет — это основа экономики капиталистического строя), а именно как функция внутри компании. Она действительно распалась на множество функций, кроссфункциональное взаимодействие стало. И я думал: что будет? Появился СЕМ — отлично. Но вот прошло 2 года. Почему поднимаю красный флаг? Потому что вижу, во что это превратилось.

Превратилось опять в нашу классическую функциональную дробежку, когда мы стали писать «customer service, customer experience». Просто две новые буквы модные и вместо customer service стал customer experience. И целью CX-руководителя внутри компании стало счастье клиентов опять, будь оно не ладно. Чтобы меньше жалоб, хороший NPS, CSI и так далее. Какой то сопутствующий, саппортирующий функционал для бизнеса. Опять появилось деление между бизнесом, который зарабатывает деньги и CX, который нужен, я не знаю для чего, чтобы были звездочки хорошие [в оценках].

(Р) Если получится быстро ответить и нам этом закончим: а почему так? Потому что слишком сложно, а человеческий мозг устроен так, что когда сложно – лучше упростить, а заодно наврать, что это оно [СЕМ] и есть, все поверили и ОК. Сидишь на своей топовой позиции СХ-менеджера, получаешь хорошо, а выполняешь то же, что и 10 лет назад. Или потому что на это нужны большие денежные вливания и никто не хочет тратиться?

(А) Хороший вопрос. В первую очередь, я думаю, что люди стремятся к обособлению. Несмотря на то, что вся история идет с кроссфункциональностью, agile и так далее, в человеческой природе на примитивном уровне: я, ты, мы разные, я защищаю свое. И в функциональном управлении это проявляется очень сильно: даже когда появляется благое дело.

Вот мы ставим Петю Иванова, чтобы он построил внутри компании кроссфункциональное взаимодействие, чтобы выстроил цельную систему управления клиентским опытом. Он должен бежать ко всем. Он бегает, бегает, но его все посылают. Он говорит: я замучился ко всем бегать, у меня нет ничего своего, вроде я управляю чем-то большим, но оно в [административном] управлении других. И он начинает что-то свое создавать. Начинает говорить: ладно, ты отвечаешь за продажи, ты — run, я тогда буду отвечать за программу, которая считывает клиентские улыбки в отделениях банка. Придумывает свои инновации, проекты, которыми только он управляет. Потом огораживает забор вокруг этих проектов и называет — эти проекты — есть проекты по улучшению клиентского опыта. И все сваливается в сервис опять.

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

Если вам проще и интереснее посмотреть видео-запись интервью — приглашаю на свой личный канал.


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