...

суббота, 9 июня 2018 г.

[Из песочницы] Хочу стать тестировщиком

На самом деле три — четыре года назад мне совершенно не хотелось становиться тестировщиком. Я даже не слышал о такой профессии и не имел совершенно никакого представления, чем эти самые тестировщики занимаются. В то время я был самым настоящим строителем. Бывших строителей не бывает, я и сейчас знаю, как организовать строительную площадку, какой кирпич нужно использовать при возведении перегородок в санузлах, что входит в состав прямых затрат по сводному сметному расчету и что надо делать, если вдруг обнаружил пьяного каменщика, а он отказывается дыхнуть в трубку. Но на тот момент эта была моя стихия. Работать мне нравилось. Не нравилось – получать за свою работу много наказаний и мало денег. Хотелось как-то по-наоборот. Конечно, «много» и «мало» это понятия весьма и весьма относительные. Зарплата в 300 долларов в месяц для главного инженера — это более чем достаточно. Так рассуждал директор ПМК и его, наверное, можно понять. «Наверное» — потому что у меня этого не получилось. Честно говоря, я даже и не пытался понять, почему в конце месяца морально выматывающей работы я получаю вшивых триста баксов и еще должен этому радоваться. На мои вопросы о повышении зарплаты я натыкался на недоумевающий взгляд и выслушивал пространные рассуждения о далеких перспективах в будущем, нетерпеливости молодого поколения, сложной экономической ситуацией в наше непростое время, и один раз даже услышал вопрос, звучавший буквально: «А зачем тебе деньги?». После подобных заявлений, я все больше убеждался в том, что нужно коренным образом что-то менять. Но вся проблема была в том, что в нашем провинциальном райцентре найти альтернативу можно только если твой дядя – большой начальник, либо у тебя есть деньги для разворачивания собственного бизнеса.

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

Как оказалось, моих знаний компьютера на уровне пользователя Microsoft Office и Opera (которыми я втайне гордился) для того чтобы стать хотя-бы junior QA, было совершенно недостаточно. Я бы это сравнил, например, с человеком, который пришел устраиваться на работу водителем автобуса, имея большой опыт поездок в качестве пассажира. Разбираться нужно было во всем сразу и практически с нуля. Но, я ведь хотел что-то поменять, и я решился.

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

Далее я искал чего-бы потестировать в реальных условиях, и в одном чатике мне предложили поработать над небольшим сайтом с поиском. Сайт был сделан очень просто, минимальный UI, система поиска и все. Прежде всего нужно было составить цель моей работы — поиск багов, подготовка баг-репортов, составление чек листа, написание тест кейсов, выполнение автоматизации при помощи Selenium IDE.

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

На помощь пришел Python. Я начал изучать этот язык, как и все остальное, буквально с нуля. Писал “Hello world” (куда ж без него), знакомился с синтаксисом, печатал import this и читал дзен, узнал об основных понятиях ООП, таких как наследование, полиморфизм, инкапсуляция, а главное: понял, что программирование – это особое мышление, мне кажется, что написать хороший код не легче чем, например, написать поэму. Есть статья «Не будите программиста» и в ней очень здорово написано, как мыслят программисты. Я не программист, но начинаю их понимать. Профессия тестировщика это не профессия программиста, но знать основы программирования ему просто жизненно необходимо. Нельзя, ведь, тестировать автомобили, ничего не зная об их внутреннем устройстве хотя бы в общих чертах. Кроме этого, при помощи питона можно написать коротенькие автоматические тесты, импортировав фреймворк unittest.

Кроличья нора глубока, но глядя на ее вход, невозможно сразу представить всю глубину ее глубин. Оказалось, чтобы пользоваться unittest, необходимо иметь представление о языке разметки HTML, знать, что такое ID элемента, как использовать CSS и XPATH селекторы, что такое теги, основные элементы страницы, etc. Это нужно знать для выполнения правильной проверки наличия, либо отсутствия определенных элементов на странице сайта. Я написал несколько скриптов, выполняющих проверки, используя различные локаторы, но, признаюсь, пришлось порядком поднапрячь знакомого программиста. Впрочем, мой знакомый мне с удовольствием помог, и, Лёха, если ты читаешь этот пост – огромное тебе спасибо!

Да, совсем забыл – еще одна вещь! Английский язык! Английский язык я изучал в школе, плюс в детстве мама пыталась учить нас английскому языку дополнительно, и в результате у меня остались какие-то знания. После этого английский был заброшен за ненадобностью, а некоторое количество англицизмов в повседневной жизни ну никак нельзя назвать языковой практикой. На стройке он не нужен, но вот в IT он жизненно необходим. Сейчас уровень моего знания английского языка Pre-Intermediate, я могу составлять фразы и поддерживать несложную беседу, пойму технический монолог, при условии, если собеседник не будет говорить слишком быстро, глотая буквы, тогда я могу потерять нить разговора и мне придется переспрашивать. Говорила мне мама – учи английский! Но кто ж слушает маму? В моем городке из курсов английского только частные уроки от соответствующих учителей, которые хотят подзаработать в свободное время, но мы не ищем легких путей. Ведь есть же интернет! Lingualeo, Ororo, YouTube, весь английский мир к моим услугам! Но и тут оказалось не все так просто.

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

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

Меня приняли очень хорошо. Команда занималась разработкой и поддержкой браузерного приложения, основной задачей которого являлся расчет стоимости выполняемых работ строительной организации — потенциального покупателя этого приложения с последующим формированием необходимых документов в pdf формате. Кроме этого, там было еще много дополнительных фич, которые добавлялись в процессе разработки приложения. UI был на английском и немецком языках, так как заказчиком выступал субъект из Германии. Подробно рассказывать о приложении я не стану, так как связан договором о неразглашении информации, да это здесь, наверное, и не нужно. Главное то, что я начал участвовать в реальном проекте! И все заверте…

Мы договорились, что участвовать в проекте я буду удаленно. В команде были разработчики не только из Беларуси, но из Украины. В 11.00 по Минскому времени обычно начинался групповой созвон по Скайпу, в течении которого представитель Заказчика формулировал задачи для выполнения, он же, одновременно являясь тестировщиком, описывал найденные баги и создавал тикеты в Jira. Разработчики отчитывались о выполненной работе, делились соображениями о способах реализации предлагаемых фич, описывали проблемы, с которыми они сталкивались при разработке и предлагали способы их решения. Это был полноценный рабочий процесс и я был его частью! Все беседы, естественно, велись на английском языке и для меня это был очень полезный и интересный опыт. Как оказалось, мне было несложно понимать английскую речь, а встречающиеся незнакомые слова я сразу выписывал в свой словарик. Сложнее было в те моменты, когда мне необходимо было что-то сказать самостоятельно, но, к чести команды и Заказчика, меня терпеливо слушали и помогали, если мне было трудно подобрать слова.

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

Разработка программного обеспечения в нашей команде велась, на мой взгляд, по принципам гибкой модели (agile model). Одним из тезисов Agile-манифеста является цитирую: “Работающий продукт важнее исчерпывающей документации”. Хорошо это, или плохо — об этом написано много и единой точки зрения на этот счет, насколько я успел понять, нет. Но одно было понятно наверняка: здесь проблемы решались по мере поступления, и долгосрочное планирование практически не велось. Все это было связано с тем, что команда была совсем небольшой – 5 человек, я пришел шестым. Программисты, реализовав какую-нибудь фичу, либо закрыв очередной баг, давали мне задание протестировать изменяемую область приложения. Все работы велись на тестовом сервере, релизы на рабочий сервер выкатывались после тестирования не реже одного раза в неделю. Моей задачей было smoke тестирование, я просто заходил на страничку и проверял, не сломалось ли чего-нибудь. После этого я проверял (насколько мне позволяли мои постепенно увеличивающиеся знания продукта), как работает остальной основной функционал. Я находил баги! Баг-репорты я поначалу оформлял как New issues в GitHub но вскоре мне разрешили оформлять их тикетами в Jira.

Было очень интересно найти баг, повторить его в разных браузерах, разных операционных системах (я установил Ubuntu 16.04 и Windows 10 64 bit на своем компьютере), потом описать его, описать шаги для его воспроизведения, приложить скриншоты. Категорийность бага определял руководитель проекта. Он же просматривал созданный мной тикет и давал замечания, если они были. Для собственного удобства, я записал простейшую тест-сюиту в Selenium IDE, которая открывает все странички приложения последовательно, начиная с регистрации на сайте, проверяя его работоспособность — по сути smoke test, но даже это отлично ускоряло прогон чек-листа.
Нужно было начинать писать тест-план, о котором я читал, но как-то с трудом представлял себе этот документ. Про тест-план есть в книге Святослава Куликова “Тестирование программного обеспечения. Базовый курс”, и я прочитал, что это достаточно объемный документ, который, цитата: “описывает и регламентирует перечень работ по тестированию, а также соответствующие техники и подходы, стратегию, области ответственности, ресурсы, расписание и ключевые даты”. Из цитаты становится понятно, что такой документ не создашь за полчаса работы. Не создашь его и за один день, поэтому я принялся обдумывать основные положения тест-плана, делая какие-то наброски для себя. Но написать тест-план мне так и не удалось. Совершенно неожиданно для меня, руководитель проекта в общем чате сообщил о прекращении разработки приложения. Связанно это было с тем, что у Заказчика отсутствовали средства для финансирования разработки приложения. Команда была расформирована, и я остался не у дел. Признаюсь, я был довольно-таки разочарован, так как, не имея представления о финансовой стороне проекта, я не задумывался о возможном сворачивании всей работы над проектом. Расставаться с командой было жаль, но я получил рекомендации, и, самое главное, опыт, а это было очень здорово!

Что мне хочется сказать в итоге: моя история еще далеко не завершена. Это пока еще её самое начало. Сейчас я ищу работу удаленно в качестве junior QA, хочу участвовать в реальном проекте и готов работать в течении испытательного срока без оплаты труда, ведь мне нужен опыт, много опыта.

В целом я хочу сказать, что профессия тестировщика очень интересная. Я где-то читал про то, что тестировщиком работать скучно, однообразно и тому подобное, на что могу однозначно ответить — нет! У тестировщика очень нескучная работа, ведь это постоянный креатив, необходимость мыслить неординарно, уметь искать и находить нужную информацию в самых разнообразных источниках и этому нужно учиться. У каждого, кто пришел в тестирование своя история и у всех она разная, но с одинаковым результатом – работать в команде на позиции QA над реальным проектом. Я хочу получить этот результат, и для его получения я уже сделал немало. Да, признаюсь, пару раз были такие моменты, когда я начинал сомневаться в своем решении, но, поразмыслив, я понимаю, что решение правильное, просто это моя дорога и не всегда она прямая и ровная.

Если Вы решили стать тестировщиком – это классно! Нужно постоянно идти вперед, даже если кажется, что все очень сложно и непонятно. Будут подводные камни, будет непросто, к этому нужно быть готовым и каждую неудачу рассматривать как ступеньку вверх. Пусть мой рассказ послужит неким импульсом для тех, кто решил выбрать эту профессию, но еще не решился сделать первый шаг. Шагайте смело! У Вас все впереди!

Let's block ads! (Why?)

Запуск LAMP и сотен других веб-приложений в несколько кликов

image В прошлой публикации я рассказал как SSHeller помогает быстро и легко развернуть OpenVPN сервер. В версии 1.1.0 был добавлен еще один плагин — Docker.
Хочу сразу предупредить, что если у вас руки заточены под консоль, а управление Docker через командную строку кажется удобным и интуитивно понятным, — не нужно читать дальше, не нужно писать ничего в комментариях, я и так знаю, что SSHeller вам не нужен.

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

Нужен сервер с практически любым современным Linux дистрибутивом. Но, в отличии от OpenVPN, для Docker я бы порекомендовал Debian 9 либо Ubuntu 18.04. С Fedora и Centos дела обстоят чуть хуже, так как требуется ставить больше пакетов, дополнительно настраивать автозапуск, немного иначе построена работа с хранилищем.

Самое главное — это должна быть либо физическая либо виртуальная машина, но никак не контейнер (LXC, OpenVZ не подойдут). И нужен доступ по SSH. В прошлой публикации есть подробное описание, как зарегистрироваться и запустить сервер в DigitalOcean, а в конце — чуть менее подробная инструкция для Linode. Оба этих сервиса предлагают простые машины за $5/месяц и бонусы при регистрации по реферальной ссылке. Для начала этого будет вполне достаточно.

Как только у нас есть IP, логин и пароль к серверу, можно устанавливать SSHeller. Скачать его можно из релизов на GitHub, есть версии для macOS, Windows и Linux. После запуска добавляем наш сервер, подключаемся к нему и переходим к плагину Docker.

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


Запуск контейнера

В самом низу рабочей области располагается блок запуска нового контейнера. В нем отображается:


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

Выбрать можно любой официальный Docker образ и еще несколько дополнительных. Их перечень задается в файле https://github.com/delfer/ssheller/blob/master/plugins/docker-profiles.json и на момент публикации это:


  • jwilder/nginx-proxy — автоматически настраиваемый Nginx для доступа к контейнерам через доменное имя, а не порт
  • panubo/vsftpd — FTP сервер
  • coderaiser/cloudcmd — WEB-инетрфейс для доступа к файлам
  • webdevops/php-apache-dev — сборка Apache + PHP + модули для разработки (выводит ошибки)
  • webdevops/php-apache — сборка Apache + PHP + модули для "прода" (не выводит ошибки)
  • webdevops/php-nginx-dev — сборка Nginx + PHP + модули для разработки (выводит ошибки)
  • webdevops/php-nginx — сборка Nginx + PHP + модули для "прода" (не выводит ошибки)

Большинство контейнеров будут игнорировать содержимое поля Password, кроме перечисленных в docker-profiles.json (секция parameters):


  • panubo/vsftpd — пароль для подключения по FTP под пользователем admin
  • coderaiser/cloudcmd — пароль для входа под пользователем admin
  • mysql/mariadb/postgres/influxdb — пароль для подключения к базе db под пользователем admin
  • rabbitmq/couchdb/orientdb — пароль для подключения под пользователем admin

Важные особенности:


  • Тэги загружаются автоматически после выбора образа. Доступен поиск
  • Контейнер будет доступен на том порту, который указал его автор. Если порт занят — будет выбран первый свободный
  • Контейнер будет доступен на домене <имя контейнера>.<домен сервера> если есть соответствующие записи в DNS
  • Для контейнера будут созданы тома, если их указал автор образа, либо они заданы в docker-profiles.json (секция volumes)

Полезные советы:


  • jwilder/nginx-proxy нужно запускать первым, чтобы он занял 80 порт
  • panubo/vsftpd нужно запускать последним, так как при запуске он меняет права доступа к папке, чтобы иметь к ней полный доступ
  • почти всегда лучше использовать образ с тэгом alpine или latest-alpine — они работают так же как и обычные, но гораздо быстрее загружаются за счет меньшего веса
  • если вам нужна MySQL — используйте лучше MariaDB, если, конечно, вы точно не уверены в том, что MariaDB вам не подходит
  • если вы запускаете что-то с PHP, то fpm вам, скорее всего, не нужен, а нужен apache

Дополнительная информация
После нажатия на кнопку RUN, как только контейнер будет запущен, появится окно с отчетом, в котором будет указана выполненная команда docker run. Она расскажет много интересного о том, как все на самом деле устроено.


Доступ через доменное имя

Если вы запускаете несколько контейнеров с веб-интерфейсом, например Wordpress и NextCloud, и хотите, чтобы доступ к ним бы не через указание IP и порта, а по доменному имени, то нужно выпонить два простых условия:


  1. Первым запустить jwilder/nginx-proxy
  2. Иметь Wildcard DNS запись

Т.е. если, например, у вас есть домен example.com, то нужно добавить в него запись * типа A и указать IP сервера. В таком случае, запущенный контейнер worpdress будет доступен по адресу http://wordpress.example.com

А если домена нет — не проблема, можно воспользоваться сервисом nip.io — не нужно ни регистрироваться, ни добавлять записи. Если, например, у вашего сервера IP 172.104.129.183, и на нем запущены jwilder/nginx-proxy и nextcloud, то последний будет доступен по адресу http://nextcloud.172.104.129.183.nip.io


Запуск двух контейнеров с одним томом

Часто бывает, например, что один контейнер файлы публикует (nginx), а второй — загружает (panubo/vsftpd). Или второй нужен чтобы посмотреть/скачать содержимое первого.

Например, Jenkins после запуска спрашивает пароль, записанный в файле.


  1. Запускаем Jenkins
  2. Выбираем его том jenkins_var_jenkins_home
  3. Указываем пароль для доступа
  4. Запускаем coderaiser/cloudcmd
  5. Открываем http://coderaiser-cloudcmd.<домен>, вводим логин admin и пароль из п. 3
  6. Смотрим пароль от Jenkins


Взаимодействие между контейнерами

Также бывает нужно из одного контейнера подключаться к другому. Например wordpress при установке попросит указать параметры подключения к базе данных: адрес сервера (host) и порт.

localhost для взаимодействия между контейнерами работать не будет.

В общем случае, нужно указывать внешний IP адрес сервера и порт, отображенный
перед стрелкой (->) в списке контейнеров. Для простоты, добавлена возможность вместо IP адреса сервера указывать просто слово host.

Кроме MySQL, может пригодиться PostgreSQL, MongoDB, Redis, memcached, Tomcat, InfluxDB, CouchDB и т.п..


Файлообменник


  1. Запускаем nginx:alpine
  2. Вводим пароль администратора для web-интерфейса
  3. Выбираем том nginx_usr_share_nginx_html
  4. Запускаем coderaiser/cloudcmd:latest-alpine

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


Wordpress


  1. Придумываем и вводим пароль для базы данных
  2. Запускам mariadb
  3. Запускаем wordpress:apache
  4. Переходим по адресу http://<адрес сервера>, начинаем установку и указываем параметры подключения к СУБД:
    • Имя базы данных: db
    • Имя пользователя: admin
    • Пароль — тот, что ввели в п.1
    • Сервер базы данных: host
    • Префикс таблиц — любой

LAMP


  1. Придумываем и вводим пароль для базы данных
  2. Запускам mariadb
  3. Запускаем webdevops/php-apache-dev:alpine
  4. Выбираем том webdevops-php-apache-dev_app
  5. Придумываем и вводим пароль для FTP сервера
  6. Запускаем panubo/vsftpd

Теперь можно загрузить .php файлы на ftp://<адрес сервера> и запустить их, перейдя по адресу http://<адрес сервера>

Далеко не все контейнеры доступны в приложении, а некоторые из доступных — не работают без дополнительных настроек — редактируйте файл на GitHub и присылайте свои Pull Request'ы. Если не знаете как — открывайте Issue. Этим вы поможете развитию проекта.

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

Let's block ads! (Why?)

Что это было и как: впечатления команды Redmadrobot от WWDC

Роботы-разработчики продолжают телеграфировать из Сан-Хосе. И если о технических новостях WWDC все уже знают — да и мы об этом писали, на этот раз mc_murphyvani2 bealex рассказывают об опыте, который получают участники конференции, и других вопросах, которые обычно остаются за кадром.

image

Иван Вавилов, руководитель iOS-команды


image

Организация конференции


К организации не придерешься — Apple давно проводит WWDC и все отполировала. В США целая культура стояния в очередях: они были на keynote, за едой, в туалеты, в магазин. Очереди организованы очень круто и движутся достаточно быстро. Проекторы, сцена, ряды стульев – все ровное, не придраться, везде чисто и приятно находиться. Залы большие, почти во всех на стульях были розетки. Во всем McEnery Convention Center был WiFi и столы с интернетом по проводу со скоростью порядка 500 Мбит/с, по всем коридорам играют HomePod, причем они синхронизированы между собой одной аудио-дорожкой. На конференции лично мне не хватало хорошего кофе, еда была стандартная американская — много сладкого, и не очень вкусно, но голодным точно не останешься.

image

На каждом WWDC можно купить сувениры с символикой конференции. Нам не хотелось терять 2 часа в очереди в первый день, поэтому мы зашли за ними во второй — очередь была поменьше, но, кроме XL+ размеров, уже ничего не было. Честно говоря, стремными жилетками, сумками-авоськами, термосами, больше похожими на ручные гранаты, детскими футболками из половой тряпки я остался недоволен — по мне это не уровень того, что производит Apple. Тем не менее, магазин закрылся еще в четверг, т.к. все раскупили.

image

Лаборатории


Главная фишка WWDC — так называемые технические лаборатории. Расписание их работы на всю неделю появляется следом за Keynote. В этом году было 12 зон с инженерами Apple, все они разделяются по темам, время работы зависит от темы, как правило, это от 2-3 часа, одна и та же лаба может быть проходить в течение 1-2 дней. Так что, если вы спросили что-то в первый день, можете завести радар, подготовить исходники и прийти на следующий день. Некоторые темы достаточно популярны (Networking, UICollectionView), к ним выстраивается очередь, но по моему опыту, меньше чем за час она расходится. Редко вы сразу попадаете на инженера, который готов ответить на ваш вопрос, поэтому коллеги отправят вас к другому — и так может происходить несколько раз. Из этого видно, как сильна специализация инженеров. Саша, к примеру, добрался до разработчика UILabel и спросил, как все-таки добавить ссылки без багов — ответ: «никак», а я пообщался с ребятами, которые занимаются NSURLSession и получил ответ на вопрос, почему до сих пор не сделали возобновляемый URLSessionUploadTask — «потому что пока нет отраслевого стандарта». Конечно, не стоит ожидать ответов на все ваши вопросы, крайне рекомендуется заводить радар или подготовить проект для показа. Возникло впечатление, что инженеры действительно не отпустят тебя, пока не ответят на вопрос, никто не торопит, несмотря на очереди. Заметил, что каждый день была лаба Swift Open Hours — это говорит о том, насколько Apple заинтересована в продвижении и развитии языка и вспомогательных инструментов, в частности Swift Package Manager.

image

Организация работы


Из разговоров с инженерами стало немного понятно, как они работают: как таковых грейдов у них нет, все они инженеры, но у каждого есть руководитель, с которым они обсуждают задачи. Сами задачи приходят от т.н. DRI, насколько я понял, это некий аналог наших менеджеров, они могут быть также с техническим бэкграундом, у дизайнеров – аналогично. DRI уже отвечают за выполнение задач перед вице-президентами.
Интересно то, что одновременно у инженера может быть несколько задач от разных «заказчиков» (DRI). Приоритеты, оценки устанавливаются локально самими инженером и руководителем. Со слов одного из инженеров, бывают задачи с постановкой «это должно работать» и все. Тут уже надо делать ASAP.

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

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

image

Александр Бабаев, технический директор RMR-Спб


image
Из года в год, приезжая на WWDC или следя за конференцией из России, я жду новостей про средства разработки. Понятно, что появляются новые SDK, понятно, что старые развиваются, и изредка нам даже дают новые железки. Средствам разработки раньше везло не так сильно.

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

В 2018 году разработчики получили не только обновление Xcode — сама система стала лучше. Тёмная тема, которая собрала аплодисменты, действительно удобна для работы в сумерках. До сих пор нужно было использовать AppCode, причём в режиме «полного экрана» (и тестировать приложения на подключенном устройстве), чтобы работать в тёмной теме, а теперь — пожалуйста, без лишних усилий: Xcode и все остальные приложения темные. А как красива новая динамическая тема с пустыней! Немножко напоминает застывшие Aerial-скринсейверы.

Обновилось и буквально всё остальное:

  • Стал лучше механизм автодополнения в Xcode, удобнее работать с документацией и навигацией по коду. Появился мультикурсор и отметки об изменениях на полях кода. Вернули сворачивание кода, которое пропало после прошлогоднего переписывания редактора.
  •  Подвезли также улучшения в Interface Builder. Выделили в отдельную панельку библиотеку компонентов, так удобнее, говорят. Посмотрим.
  • Instruments получил в обновлениях кастомные инструменты.
  • Скоро (не в первой бете, но, кажется, до релиза) обещают систему профилирования AutoLayout.
  • Отладчики Metal также прокачали.
  • По умолчанию теперь используется «новая» система сборки. Она лучше работает с зависимостями, меньше пересобирает и параллельнее работает.
  • Тестирование также можно теперь распараллелить, и запустить тесты в случайном порядке.
  • Приложения теперь можно «заверять» в Apple. И в какой-то момент компания запретит запускать незаверенные приложения. Само приложение после такой процедуры может распространяться не через App Store.
  • Подключили к уже существующему GitHub поддержку GitLab и Bitbucket.

Как видно, изменилось очень, очень многое. А ведь ещё и Swift 4.2 с несколькими приятными изменениями подвезли. Вот, что появилось:

  •  Удобный способ получить список всех кейсов для enum’ов.
  • #warning и #error
  • возможность писать интерфейсы к динамическим языкам программирования, пока только для property — методы подключат попозже. Главным образом это сделано для взаимодействия с Python.
  • возможность написания специализированных расширений (conditional conformances) — она появилась уже в 4.1, но в 4.2 заработала как следует.
  • работа со случайными числами (и перемешивание массивов).
  • новая структура работы с хешами объектов.
  • несколько новых методов работы с коллекциями.
  • Bool.toggle()

К сожалению, не успели доделать Swift 5, который обещают только в следующем году. Поэтому изменений немного.

Обновка получилась отличная. Я, правда, никуда с AppCode убегать не собираюсь, он тоже очень быстро развивается, и в нём также работать все приятнее. И обновляется он не раз в год, а постоянно. Но мы же сейчас про продукты Apple, верно?

Остальные изменения, как я уже говорил, не революционные. Но это не делает их хуже. AR взлетел из состояния «игрушка» в разряд инструмента, с которым можно работать. Возможность натренировать нейросеть с помощью CreateML — великолепный способ улучшить решение целого класса задач, чем мы и займёмся после возвращения с конференции. А ускорение, полученное в iOS 12, мы ждали последние лет пять.

Отдельно удивило появление iOS-приложений под macOS (Новости, Акции, Home и Диктофон). Подробностей нам рассказали безумно мало, но в сети есть место, где можно почитать, если хочется разобраться: твиттер вот этого товарища: twitter.com/stroughtonsmith заполнен информацией. Он уже всё расковырял и описал, как можно самому писать UIKit-приложения для macOS, какие там ограничения, что ещё предстоит сделать разработчикам Apple (там пока хватает острых углов: twitter.com/stroughtonsmith/status/1005104334546309120) и так далее. А если кто-то хочет попробовать Марципан сам, в сети уже появились рецепты для этого, например, вот такой github.com/biscuitehh/MarzipanPlatter. Я не пробовал и пока не планирую, так что про работоспособность ничего не скажу.

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

Артур Сахаров, технический директор


image
Помимо сессий, на конференции происходит уйма всего интересного. Многие посетители — не разработчики, и задача Apple — сделать WWDC информативной и интересной для всех.

На конференции предусмотрена система консультаций с экспертами Apple, и часть их организована по принципу забронированных аудиенций. Можно посоветоваться по дизайну и UX с командой Human Interface Design, с ревью-специалистами по публикации в AppStore, с командой маркетинга по продвижению приложений.

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

Design review by appointment


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

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

image

Podcast Studio


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

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

image

Lunchtime Sessions


Три дня из пяти в обеды проходят сессии, которые не транслируются через интернет и которые нельзя снимать самим — Lunchtime Sessions. В прошлые годы приезжали Базз Олдрин, Мишель Обама, Билл Найи и многие другие. Обычно это темы, которые касаются разработки лишь косвенно, но задают правильный контекст и настрой.
В этом году были «космический археолог» Сара Парсак (про снимки из космоса, по которым находят древние цивилизации и города), Раджеш Анандан из Unicef (про то, как IT помогает детям привлекать внимание и инвесторов для помощи африканским поселениям) и Даниелла Фейнберг из Pixar (про генеративные ландшафты в фильмах и алгоритмы, позволяющие добиваться реалистичных окружений и освещения).

Experts at WWDC


Конечно же, конференция собирает лучших экспертов в индустрии, и многие приезжают не только посмотреть, но и себя показать.
Дверь-в-дверь проходят еще две полноценные конференции: AltConf (для разработчиков под iOS и macOS, где выступают не-сотрудники Apple) и Layers (дизайнерская конференция с лучшими умами индустрии). Похоже, что один только AltConf по количеству сессий превосходит российские Mobius и DroidCon.

Вечерами в театрах Сан-Хосе устраивают шоу и очные записи подкастов с экспертами. В этом году ходили на запись The Talk Show Джона Грубера, ATPLive и RelayFM. Можно послушать, что думают компьютерные журналисты с 20-летним стажем об анонсах и новинках, и я вам скажу, что они намного более оптимистичны и информированы, чем наша общественность. Когда в наших блогах и статьях все поголовно пишут «ничего нового, эппл уже не торт», эти ребята проецируют тенденции на пару лет вперед и смотрят, как из анонсов-кирпичиков сложится новое будущее всей платформы. Довольно интересно и избавляет от пессимизма и нытья.

image

Демо-зона ARKit 2


Вы и так уже все знаете про обновления в ARKit: возможность смотреть дополненную реальность с нескольких устройств одновременно, текстуры мира, которые можно использовать для отражений, сканирование трехмерных объектов.

Чтобы все это потрогать вживую, на первом этаже поставили специальные столы и написали многопользовательскую игру SwiftShot в дополненной реальности. Нужно из рогатки сбивать деревянные башни противника, и в эту историю устраивали матчи 2 на 2 на четырех айпадах. За выигрыш вручали специальный значок, и все 5 дней стояла очередь. Работало все прекрасно.

image

Let's block ads! (Why?)

Знакомимся с хакерспейсом Нижнего Новгорода, CADR. Часть 2

Продолжаем интервью с основателем нижегородского хакерспейса CADR — Артёмом Попцовым (см. так же — первая часть).

Какое оборудование есть? И вообще сколько места?
Хакерспейс условно можно разделить на четыре основных зоны: монтажно-компьютерная зона, компьютерно-монтажная зона, зона презентаций и кухня. По данным зонам распределено следующее оборудование:
  • 3D-принтеры: RepRap, ToyREP.
  • Компьютеры: 3 рабочие станции, несколько ноутбуков, одноплатные компьютеры (Raspberry Pi, CubieBoard)
  • Лазерный принтер и сканер.
  • Станки: сверлильный станок, наждак.
  • Ручной инструмент: дрели, ключи, стамески, напильники, отвертки-завёртки и т.п.
  • Средства персональной защиты: халат, защитные очки, респираторы, перчатки и т.п.)
  • Инструменты для пайки: паяльная станция с феном, паяльники отдельно, сопутствующая расходка.
  • Измерительное оборудование: осциллограф цифровой и аналоговый, мультиметры, штангенциркули, микрометр и пр.
  • Источники питания.
  • Микроконтроллерные платформы: Arduino, STM32, ESP (8266, 32S, 12F).
  • Разная электронная рассыпуха и сопутствующие вещи: резисторы, конденсаторы, трансформаторы, светодиоды, транзисторы, кнопки, переключатели, провода и т.п.

Кроме этого у нас есть небольшая библиотека технической (и не только) литературы, откуда можно брать на чтение книги и журналы.

Особой популярностью пользуется зона чаепития и печенькоедения — работа мозгами потребляет много энергии, и её где-то надо пополнять. ;-)


Зона чаепития и печенькоедения.


Кадробиблиотека.


Монтажно-компьютерная зона.


Сверлильный станок.


Мотивирующие плакаты по технике безопасности.


Полка с инструментами над монтажно-компьютерной зоной.


3D-принтер RepRap.

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

Каждую среду и субботу я провожу Arduino-workshop'ы: обучаю программированию и немного электронике.

Недавно проводили workshop по битовым операциям в ЯП C++ и сдвиговым регистрам (видеозапись).

В этом году мы делали свой первый мастер-класс на выезде по программированию микроконтроллеров, совместно с DEFCON NN.


Мастер-класс по битовым операциям в ЯП C/C++ и сдвиговым регистрам.

Вероятно участники ведут свои проекты — список интересных проектов
Многие участники ведут проекты, как правило, под руководством опытных кадровчан. Опытные кадровчане делают всё сами и ещё мотивируют других к работе.

Вот примеры проектов:.

  • Ультразвуковой левитатор — устройство для левитации объектов посредством ультразвука.
  • «Параинформатор» — устройство для информирования о начале пар в НРТК.
  • Машина для переработки пластика для 3D принтера — переработка неудавшихся пластиковых деталей обратно в материал для 3D принтера.
  • «Cadrix» — матричный принтер, на который можно посылать задания через Telegram-бота.
  • «Нейробот» — робот, управляемый коннектомом червя (на основе проекта OpenWorm)
  • Очки дополненной реальности на ESP32S — за основу взят проект «Arduino Glasses a HMD for Multimeter» под авторством Алайна Мойера (нем. Alain Mauer). Есть, кстати, запись моей лекции с DEFCON NN по данной теме.
  • Трёхцветная светодиодная матрица.
  • CADOORBOT


Пётр Третьяков тестирует прототип очков дополненной реальности.
Общаетесь ли с другими техническими клубами (фаблаб, цмит и тп) в вашем городе или в России, мире?
В НРТК есть свой ЦМИТ и, поскольку мы сейчас находимся в симбиозе с колледжем, то имеем доступ к многим ресурсам местного центра.

Кроме того, мы сотрудничаем с некоммерческой организацией DEFCON NN (DC 7831) — участвуем в конференциях, проводимых ими.

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

На моей второй работе (в детском центре «Поцако») веду вместе с Петром Третьяковым (студентом НРТК, кадровчанином и отличным специалистом) курс «Мехатроника» для школьников, где мы учим детей всему, что требуется для создания роботов на базе платформы Arduino.

Помогает ли как-либо город?
На данный момент — нет. Возможно, мы просто не слишком активно рекламируем себя, чтобы быть замеченными городским управлением. :-)
Делаете ли выездные мастер классы?
Как уже упоминал ранее, в этом году мы делали свой первый мастер-класс на выезде по программированию микроконтроллеров, совместно с DEFCON NN. Было довольно весело, к нам подходили местные специалисты и учились программировать Arduino. В принципе, ещё не известно, кто кого больше учил. ;-)


Стенд хакерспейса CADR на DEFCON NN 0x08.

Расскажи про себя — что интересует, чем занимаешься по основной работе, является ли хакспейс хобби или серьезным направлением работы для тебя
Я — полуночный free software разработчик, пишущий на Лисп и других ЯП во имя добра. В дневное время — заведующий двумя лабораториями в Нижегородском радиотехническом колледже и просто преподаватель (веду курсы по программированию микроконтроллеров, ООП, системное программирование, администрирование GNU/Linux). Как можно предположить из текста выше, со-основатель Нижегородского хакерспейса CADR. Немного — поэт, немного — музыкант.

Основные хобби: чтение, программирование, игра на клавишных (включая попытки написания музыки — вот пример, если кому интересно).

Хакерспейс также можно назвать одним из моих основных и серьёзных хобби.

Читаю много литературы и новостей, в основном, зарубежных. Активно слежу за новинками технологий и науки. Интересуюсь тематикой трансгуманизма и биохакинга. Хочу, если можно так сказать, приблизить будущее и считаю, что образование — это вклад в будущее.

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

Являетесь ли инкубатором для какого либо коммерческого проекта?
Мы по условиям нашего сотрудничества с колледжем не можем вести коммерческую деятельность. Некоторые люди в CADR'е работали и работают над собственными проектами — вероятно, коммерческими. Но тут у меня точных данных нет.
Насколько робототехника развита в преподавании в школах вашего города?
В некоторых школах города используют Лего-роботов в образовании. Более подробно скажу об этом в ответе на следующий вопрос.

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

Считаешь ли ты что робототехника могла бы привлечь детей в школах к самостоятельному творчеству, к лучшему изучению предметов?
Да. Робототехника позволяет «пощупать» многие интересные вещи, такие, как программирование, электроника; также позволяет развить инженерные навыки человека с малых лет. По опыту могу сказать, что наиболее выдающиеся специалисты, с которыми мне приходится общаться, начинали заниматься техническим творчеством ещё в школе (многие — с младших классов). Я сам начал программировать в школьные годы и многое узнавал на практике. Делал это потому, что мне было интересно — увлечением моим в школьные годы были эксперименты с графикой в Pascal. Это было наглядно и выглядело эффектно (по крайней мере, для меня в том возрасте.) А вот, например, применимость знания английского языка я увидел позже, когда стал читать интересные для меня вещи на иностранном языке. Вот тут-то и начался прогресс в изучении английского языка.

На мой взгляд, сейчас многие технические вещи преподаются отвлечённо от практики, и люди не видят применимости знаниям, которые они получают. Раньше дети более активно помогали взрослым в их работе и учились на практике. Об этом, кстати, интересно говорит Пол Грэм в своей книге «Хакеры и живописцы». А теперь тебе нужно проучиться примерно десяток лет в школе и ещё примерно половину этого времени в высшем учебном заведении, чтобы начать серьёзно что-то делать в технической области. Или так, по крайней мере, нас учат думать. Хотя это всё относительно. Помню, меня впечатлил пример Джейми Завински, который стал выдающимся программистом и хакером (в классическом смысле слова), имея за спиной только среднее образование. То есть, важна именно практика. Думаю, если мы будем более активно привлекать людей с раннего возраста к интересным проектам, то у нас будет намного больше грамотных технических специалистов — вероятно, это даже важнее, чем основная система образования с оценками и экзаменами.

Однако недостаточно просто добавить робототехнику в школы, надо ещё с умом выбрать платформу для обучения. Популярные сейчас Лего-роботы может быть и являются неплохим стартом, но ограничивают детей в более серьёзном творчестве, отчасти из-за своей проприетарной натуры и фиксированного набора деталей, отчасти из-за большой стоимости наборов. Думаю, более перспективным направлением является использование платформы Arduino и технологий 3D-печати и лазерной резки для создания роботов. Да, это сложнее — но с другой стороны, Arduino доступна всем и даже учащийся школы может позволить себе купить один из вариантов данной платформы, вместо покупки каких-нибудь чипсов. ;-)

3D-печать тоже становится всё более доступной для широкой публики — например, у нас в колледже есть несколько принтеров, ну и в CADR есть два общедоступных. Был также в детском центре «Кулибин», находящийся при 113-й школе — они тоже имеют и 3D-принтер, и лазерный резак. То есть, эти технологии входят в обиход.

Но даже если нет доступа к 3D-принтеру или лазерному резаку, то основу для робота можно купить готовую или заказать для резки, и это всё равно будет дешевле лего-роботов. Воспользоваться 3D-принтером можно и у нас в CADR'е, например.

В общем, если у вас есть проект, для работы над котором нужно место и ресурсы, то приходите в наш хакерспейс CADR — или ближайший к вам хакерспейс. Список активных хакерспейсов по всему миру можно найти на сайте hackerspaces.org. Отдельно на нашем сайте мы поддерживаем собственный список хакерспейсов в России. Если рядом с вами нет хакерспейса, то организуйте новый. Именно так и происходит появление новых хакерспейсов. Если это получилось у меня, то сможете и вы.

Ссылки


Статья распространяется на условиях CC-BY-NC-ND 4.0

Let's block ads! (Why?)

[Из песочницы] Исследование рынка вакансий BA/SA

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

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

Меня интересовали следующие вопросы, затронутые в данном исследовании:


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

Спойлер: легко и просто не получилось.

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


Сбор

Для сбора данных воспользуемся поиском по вакансиям через hh API.

Искать буду с помощью простого текстового запроса "systems analyst", "business analyst" и "product owner", потому как активности и зоны ответственности у этих позиций, как правило, пересекаются.

Для этого нужно сформировать запрос вида https://api.hh.ru/vacancies?text="systems+analyst" и распарсить полученный JSON.

Чтобы в выборку попали максимально релевантные вакансии, искать будем только в заголовках вакансий, добавив в запрос параметр search_field=name.

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


  • название вакансии
  • город
  • дата публикации
  • зарплата — верхняя и нижняя границы
  • валюта, в которой указана зарплата
  • gross — T/F
  • компания
  • обязанности
  • требования к кандидату

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


Смотреть код
# без этих товарищей никуда :)

library(jsonlite)
library(curl)
library(dplyr)
library(ggplot2)
library(RColorBrewer)
library(plotly)

hh.getjobs <- function(query, paid = FALSE)
{
  # Makes a call to hh API and gets the list of vacancies based on the given search queries

  df <- data.frame(
    query = character() # мой исходный запрос  
    , URL = character() # ссылка на вакансию
    , id = numeric() # id вакансии
    , Name = character() # название вакансии
    , City = character()
    , Published = character()
    , Currency = character()
    , From = numeric() # ниж. граница зарплатной вилки
    , To = numeric() # верх. граница
    , Gross = character()
    , Company = character() 
    , Responsibility = character()
    , Requerement = character()
    , stringsAsFactors = FALSE
    ) 

  for (q in query)
  {
    for (pageNum in 0:99) {
      try(

        {
          data <- fromJSON(paste0("https://api.hh.ru/vacancies?search_field=name&text=\""
                                  , q
                                  , "\"&search_field=name"
                                  , "&only_with_salary=", paid
                                  ,"&page="
                                  , pageNum))

          df <- rbind(df, data.frame(
            q,
            data$items$url,
            as.numeric(data$items$id),
            data$items$name,
            data$items$area$name,
            data$items$published_at,
            data$items$salary$currency,
            data$items$salary$from,
            data$items$salary$to,
            data$items$salary$gross,
            data$items$employer$name,
            data$items$snippet$responsibility,
            data$items$snippet$requirement,
            stringsAsFactors = FALSE))

        })

      print(paste0("Downloading page:", pageNum + 1, "; query = \"", q, "\""))
    }

  }

  names <- c("query", "URL", "id", "Name", "City", 
             "Published", "Currency", "From", "To", 
             "Gross", "Company", "Responsibility", "Requirement")
  colnames(df) <- names

  return(df)
}

В функции hh.getjobs() на вход принимается вектор интересующих нас поисковых запросов и уточнение, интересуют нас только вакансии с указанной зарплатой или все подряд (по умолчанию берем второй вариант). Создается пустая dafa frame, а затем используется функция fromJSON() пакета jsonlite, которая принимает на вход URL и возвращает структурированный list. Далее из узлов этого списка мы достаем интересующие нас данные и заполняем соответствующие поля data frame.

По умолчанию данные отдаются постранично, по 20 элементов на каждой странице. Максимально по одному запросу можно получить 2000 вакансий. Все полученные данные мы записываем в df.

Лайфхак 1: совершенно не факт, что по нашему запросу найдется 2000 вакансий, и начиная с какого-то момента нам будут приходить пустые страницы. В этом случае R ругается и выпрыгивает из цикла. Поэтому содержимое внутреннего цикла заботливо обернем в try().

Лайфхак 2: во внутренний цикл также имеет смысл добавить вывод в консоль текущего статуса сбора данных, потому как дело это небыстрое. Я сделала так:

print(paste0("Downloading page:", pageNum + 1, "; query = \"", query, "\""))

После заполнения данными столбцы переименовываются так, чтобы с ними было удобно работать, и возвращается полученная data frame.

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

source("functions.R")

# Step 1 - get data
# 1.1 get vacancies (short info)

jobdf <- hh.getjobs(query = c("business+analyst"
                      , "systems+analyst"
                      , "product+owner"), 
                    paid = FALSE)

Теперь из полного описания вакансий вытащим experience и key_skills.

Функции hh.getxp передаем data frame, проходимся по сохраненным ссылкам на вакансии, и из полного описания достаем значение требуемого опыта работы. Полученное значение сохраняем в новом столбце.


Смотреть код
hh.getxp <- function(df)
  {
    df$experience <- NA

    for (myURL in df$URL) {
      try( 
        {
          data <- fromJSON(myURL)

          df[df$URL == myURL, "experience"] <- data$experience$name
        }
      )

      print(paste0("Filling in ", which(df$URL == myURL, arr.ind = TRUE), "from ", nrow(df)))

    }

    return(df)
}

Описание новой вспомогательной функции отправляется в functions.R, а основной скрипт теперь обращается к ней:

# s.1.2 get experience (from full info)

jobdf <- hh.getxp(jobdf)

# 1.3 get skills (from full info)

all.skills <- hh.getskills(jobdf$URL)

В фрагменте выше мы также формируем новую data frame all.skills вида "id вакансии — навык":


Смотреть код
hh.getskills <- function(allurls)
{
  analyst.skills <- data.frame(
    id = character(), # id вакансии
    skill = character() # название скилла
  )

  for (myURL in allurls) {

        data <- fromJSON(myURL)

        if (length(data$key_skills) > 0)
          analyst.skills <- rbind(analyst.skills, cbind(data$id, data$key_skills))

    print(paste0("Filling in "
                 , which(allurls == myURL, arr.ind = TRUE)
                 , " out of "
                 , length(allurls)))

  }

  names(analyst.skills) <- c("id", "skill")
  analyst.skills$skill <- tolower(analyst.skills$skill)

  return(analyst.skills)
}

Препроцессинг

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

> length(unique(jobdf$id))
[1] 1478
> length(jobdf$id)
[1] 1498

Почти полторы тысячи вакансий! Выглядит неплохо. И по всей видимости, несколько вакансий попались в результатах поиска дважды — по разным запросам. Поэтому первым делом оставим только уникальные записи: jobdf <- jobdf[unique(jobdf$id),].

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

1) убедиться, что все имеющиеся данные по зарплатам представлены в единой валюте,

2) выделить в отдельную data frame те вакансии, для которых зарплата указана.

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

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


Смотреть код
quotations.update <- function(currencies)
{
    # Parses the most up-to-date qutations data provided by the Central Bank of Russia
    # and returns a table with currency rate against RUR

    doc <- XML::xmlParse("http://www.cbr.ru/scripts/XML_daily.asp")

    quotationsdf <- XML::xmlToDataFrame(doc, stringsAsFactors = FALSE)

    quotationsdf <- select(quotationsdf, -Name) 

    quotationsdf$NumCode <- as.numeric(quotationsdf$NumCode)
    quotationsdf$Nominal <- as.numeric(quotationsdf$Nominal)
    quotationsdf$Value <- as.numeric(sub(",", ".", quotationsdf$Value))

    quotationsdf$Value <- quotationsdf$Value / quotationsdf$Nominal
    quotationsdf <- quotationsdf %>% select(CharCode, Value)

    return(quotationsdf)

}

Чтобы курсы корректно обрабатывались в R, нужно убедиться, что десятичная часть отделена точкой. Кроме того, стоит обратить внимание на колонку Nominal: где-то он равен 1, где-то 10 или 100. Это значит, один фунт стерлингов стоит ~85 рублей, а, скажем, за сотню армянских драмов можно купить ~13 рублей. Для удобства дальнейшей обработки я привела значения к номиналу 1 относительно рубля.

Теперь можно и переводить. Наш скрипт делает это с помощью функции convert.currency(). Актуальный курс валют в ней берется из таблицы quotations, куда мы сохранили данные из XML, предоставляемой Центробанком. Также на вход функция принимает целевую валюту для конвертации (по умолчанию RUR) и таблицу с вакансиями, значения зарплатных вилок в которой необходимо привести к единой валюте. Функция возвращает таблицу с обновленными зарплатными цифрами (уже без столбца Currency, за ненадобностью).

С белорусскими рублями пришлось повозиться: после получения весьма странных данных в несколько подходов, я провела небольшой рисерч и узнала, что начиная с 2016 года в Беларуси используется новая валюта, которая отличается не только курсом, но и аббревиатурой (теперь не BYR, а BYN). В справочниках hh до сих пор используется аббревиатура BYR, про которую XML от Центробанка ничего не знает. Поэтому в функции convert.currency() я не самым изящным образом сначала заменяю аббревиатуру на актуальную, и только затем перехожу непосредственно к конвертации.

Выглядит все это следующим образом:


Смотреть код
convert.currency <- function(targetCurrency = "RUR", df, quotationsdf)
{
  cond <- (!is.na(df$Currency) & df$Currency == "BYR") 
  df[cond, "Currency"] <- "BYN"

  currencies <- unique(na.omit(df$Currency[df$Currency != targetCurrency]))

  # Нижний порог зарплатной вилки (если указан)
  if (!is.null(df$From))
  {
    for (currency in currencies)
    {
      condition <- (!is.na(df$From) & df$Currency == currency)

      try(
        df$From[condition] <- 
          df$From[condition] * quotationsdf$Value[quotationsdf$CharCode == currency]
      )
    }
  }

  # Верхний порог зарплатной вилки (если указан)
  if (!is.null(df$To))
  {
    for (currency in currencies)
    {
      condition <- !is.na(df$To) & df$Currency == currency

      try(
        df$To[condition] <- 
          df$To[condition] * quotationsdf$Value[quotationsdf$CharCode == currency]
      )
    }
  }

  return(df %>% select(-Currency))
}

Также можно учесть, что некоторые данные по зарплатам представлены в значениях gross, то есть на руки сотрудник будет получать несколько меньше. Чтобы рассчитать зарплату net для резидентов РФ, нужно вычесть из указанных цифр 13% (для нерезидентов вычитается 30%).


Смотреть код
gross.to.net <- function(df, resident = TRUE)
{
  if (resident == TRUE)
    coef <- 0.87
  else
    coef <- 0.7

  if (!is.null(df$Gross))

  {

  if (!is.null(df$From)) # Нижний порог зарплатной вилки (если указан)
  {
    index <- na.omit(as.numeric(rownames(df[!is.na(df$From) & df$Gross == TRUE,])))

    df$From[index] <- df$From[index] * coef
  }

  if (!is.null(df$To)) # Верхний порог зарплатной вилки (если указан)
  {
    index <- na.omit(as.numeric(rownames(df[!is.na(df$To) & df$Gross == TRUE,])))

    df$To[index] <- df$To[index] * coef
  }

    df <- df %>% select(-Gross)
  }

  return(df)
}

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

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

get.positions <- function(df)
{
  df$lvl <- NA

  df[grep(pattern = "lead|senior|старший|ведущий|главный", 
                   x = df$Name, ignore.case = TRUE), "lvl"] <- "senior"

  df[grep(pattern = "junior|младший|стажер|стажёр", 
             x = df$Name, ignore.case = TRUE), "lvl"] <- "junior"

  df[is.na(df$lvl), "lvl"] <- "middle"

  return(df)
}

В основной скрипт добавляем блок подготовки данных.


Добавили
# Step 2 - prepare data

# 2.1. Convert all currencies to target currency

# 2.1.1 get up-to-date currency rates

quotations <- quotations.update()

# 2.1.2 convert to RUR

jobdf <- convert.currency(df = jobdf, quotationsdf = quotations)

# 2.2 convert Gross to Net

# jobdf <- gross.to.net(df = jobdf)

# 2.3 define segments

jobdf <- get.positions(jobdf)

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


  • средний уровень зарплат BA/SA,
  • наиболее востребованные умения и личные качества на этой позиции,
  • зависимости (если есть) между определенными навыками и уровнем зп.

Средний доход BA/SA

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

В нашей data frame jobdf эти значения находятся в колонках To и From соответсвенно. Я хочу найти средние значения и записать их в новый столбец Salary.

Для кейсов, где зарплата указана полностью, это легко сделать с помощью функции mean(), отфильтровав все остальные записи, где данные по вилке отсутствуют полностью или частично. Но в этом случае от нашей исходной выборки, которая и так невелика, осталось бы менее 10%. Поэтому я вычисляю коэффициент Подгониана, который подсказывает, насколько в среднем отличаются значения To и From в вакансиях, где указана полная вилка, и с его помощью примерно заполняю недостающие данные в кейсах, где пропущенно только одно значение.


Смотреть код
select.paid <- function(df, suggest = TRUE)
{
  # Returns a data frame with average salaries between To and From
  # optionally, can suggest To or From value in case only one is specified

  if (suggest == TRUE)
  {
  df <- df %>% filter(!is.na(From) | !is.na(To))

  magic.coefficient <- # shows the average difference between max and min salary 

    round(mean(df$To/df$From, na.rm = TRUE), 1)

  df[is.na(df$To),]$To <- df[is.na(df$To),]$From * magic.coefficient
  df[is.na(df$From),]$From <- df[is.na(df$From),]$To / magic.coefficient

  }

  else
  {
    df <- na.omit(df)
  }

  df$salary <- rowMeans(x = df %>% select(From, To))

  df$salary <- ceiling(df$salary / 10000) * 10000

  return(df %>% select(-From, -To))
}

Это "мягкая" фильтрация данных, которая в функции select.paid() задается параметром suggest = TRUE. Альтернативно мы можем указать suggest = FALSE при вызове функции и просто выпилить все строки, где зарплатные данные хотя бы частично отсутствуют. Однако с использованием мягкой фильтрации и волшебнго коэффициента мне удалось сохранить в выборке почти четверть от исходного набора данных.

Переходим к визуальной части:

На этом графике можно визуально оценить плотность распределения зарплат BA/SA в двух столицах и в регионах. Но что если конкретизировать запрос и сравнить, сколько получают миддлы и сеньоры в столицах?

Из полученного графика видно, что разница в зарплатных ситуациях у миддлов и сеньоров в Москве и Питере не слишком различается. Так, в Санкт-Петербурге мидлы получают, как правило, в районе 70 т.р., в то время как в Москве пик плотности приходится на ~120 т.р., а разница в доходах старших специалистов уровня в Москве и Санкт-Петербурге отличается в среднем на 60 тысяч.

Также мы можем взглянуть, например, на московские зарплаты аналитиков в разрезе должности:

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

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

Полный код для графиков под катом.


Посмотреть
# Step 3 - analyze salaries

# 3.1 get paid jobs (with salaries specified)

jobs.paid <- select.paid(jobdf)

# 3.2 plot salaries density by region

ggplotly(ggplot(jobs.paid, aes(salary, fill = region, colour = region)) +
  geom_density(alpha=.3) +
  scale_fill_discrete(guide = guide_legend(reverse=FALSE)) +
  scale_x_continuous(labels = function(x) format(x, scientific = FALSE), name = "Зарплата, руб.",
                     breaks = round(seq(min(jobs.paid$salary), 
                                        max(jobs.paid$salary), by = 30000),1)) +
  scale_y_continuous(name = "Плотность распределения") +
    theme(axis.text.x = element_text(size=9), axis.title = element_text(size=10)))

# 3.3 compare salaries for middle / senior in capitals

ggplot(jobs.paid %>% filter(region %in% c("Москва", "Санкт-Петербург"),
                            lvl %in% c("senior", "middle")),
       aes(salary, fill = region, colour = region)) +
  facet_grid(lvl ~ .) +
  geom_density(alpha = .3) +
  scale_x_continuous(labels = function(x) format(x, scientific = FALSE), name = "Зарплата, руб.",
     breaks = round(seq(min(jobs.paid$salary), 
                                        max(jobs.paid$salary), by = 30000),1)) +
  scale_y_continuous(name = "Плотность распределения") +
  scale_fill_discrete(name = "Город") +
  scale_color_discrete(name = "Город") +
    guides(fill=guide_legend(
                 keywidth=0.1,
                 keyheight=0.1,
                 default.unit="inch") 
      ) + theme(legend.spacing = unit(1,"inch"), axis.title = element_text(size=10))

# 3.4 plot salaries in Moscow by position

ggplotly(ggplot(jobs.paid %>% filter(region == "Москва"), aes(salary, fill = lvl, color = lvl)) +
  geom_density(alpha=.4) +
  scale_fill_brewer(palette = "Set2")  +
  scale_color_brewer(palette = "Set2") +
  theme_light() +
    scale_y_continuous(name = "Плотность распределения") +
  scale_x_continuous(labels = function(x) format(x, scientific = FALSE), 
                     name = "Зарплата, руб.",
                     breaks = round(seq(min(jobs.paid$salary), 
                                        max(jobs.paid$salary), by = 30000),1)) +
  theme(axis.text.x = element_text(size=9), axis.title = element_text(size=10)))

Анализ навыков (Key skills)

Переходим к ключевой цели исследования — определить наиболее востребованные навыки для BA/SA. Для этого проведем анализ тех данных, что в явном виде указаны в специальном поле вакансии — key skills.


Наиболее популярные навыки

Ранее мы получили отдельную data frame all.skills, куда записали пары "id вакансии — навык". Найти наиболее часто встречающиеся скиллы несложно с помощью функции table():

tmp <- as.data.frame(table(all.skills$skill), col.names = c("Skill", "Freq"))
htmlTable::htmlTable(x = head(tmp[order(tmp$Freq, na.last = TRUE, decreasing = TRUE),]),
          rnames = FALSE, header = c("Skill", "Freq"),
          align = 'l', css.cell = "padding-left: .5em; padding-right: 2em;")

Получится примерно следующее:

Здесь Freq — это количество вакансий, в поле "key_skills" которых указан соответствующий навык из столбца Skill.

"Но это еще не все!"(ц) Совершенно очевидно, что одни и те же скиллы запросто могут встречаться в разных вакансиях в синонимичных выражениях.

Я составила небольшой словарь синонимов названий скиллов и разделила их по категориям.

Словарь представляет собой csv-файл со столбцами category — одно из следующего: Activities, Toools, Knowledge, Standards и Personal; skill — основное название навыка, которое я буду использовать вместо всех найденных синонимов; syn1, syn2,… syn13 — собственно возможные вариации для каждого навыка. Некоторые строки могут содержать пустые столбцы синонимов.

category;skill;syn1;syn2;syn3;syn4;syn5;syn6;syn7;syn8;syn9;syn10;syn11;syn12;syn13
tools;axure;;;;;;;;;;;;;
tools;lucidchart;;;;;;;;;;;;;
standards;archimate;;;;;;;;;;;;;
standards;uml;activity diagram;use case diagram;ucd;class diagram;;;;;;;;;
personal;teamwork;team player;работа в команде;;;;;;;;;;;
activities;wireframing;mockup;mock-up;мокап;мок-ап;wireframe;прототип;ui;ux/;/ux;;;;

Сначала импортируем словарь, а затем раскидаем скиллы заново на основе имеющихся эквивалентностей:

# Analyze skills
# 4.1 import dictionary

dict <- read.csv(file = "competencies.csv", header = TRUE,
                         stringsAsFactors = FALSE, sep = ";", na.strings = "", 
                         encoding = "UTF-8")

# 4.2 match skills with dictionary

all.skills <- categorize.skills(all.skills, dict)

Под катом можно посмотреть начинку функции categorize.skills().


those very guts!
categorize.skills <- function(analyst_skills, dictionary)
{
  analyst_skills$skill.group <- NA
  analyst_skills$category <- NA

  for (myskill in dictionary$skill)
  {
    category <- dictionary[dictionary$skill == myskill, "category"]

    mypattern <- paste0(na.omit(t(dictionary %>%
                                    filter(skill == myskill) %>%
                                    select(starts_with("syn")))), collapse = "|")

    if (nchar(mypattern) > 1) 
      mypattern <- paste0(c(myskill, mypattern), collapse = "|")
    else
      mypattern <- myskill

    try(
      {
        analyst_skills[grep(x = analyst_skills$skill, pattern = mypattern),"skill.group"] <- myskill
        analyst_skills[grep(x = analyst_skills$skill, pattern = mypattern),"category"] <- category
      }
    )

  }

  return(analyst_skills)
}

Я добавляю к исходной data frame с навыками столбец category и skill. group — для категории и обобщающего названия навыка соответсвенно. Затем я прохожусь по импортированному словарю и из каждой строчки синонимов составляю паттерн для функции grep(). Добавляя каждое непустое значение колонки к строке, я разделяю их чертой, чтобы получить условие "или". Так, для всех скиллов из исходной таблицы, в которые входит паттерн uml|activity diagram|use case diagram|ucd|class diagram, я запишу в колонку skill.group значение "uml". И так будет с каждым!.. скиллом из исходной data frame.

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

В тройке лидеров теперь управление проектами, бизнес-анализ и документирование, а знание UML сместили из топ-7.

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

Например, для категории Knowledge дело обстоит следующим образом:


Смотреть код
tmp <- merge(x = all.skills, y = jobdf %>% select(id, lvl), 
          by = "id", sort = FALSE)
tmp <- na.omit(tmp)

ggplot(as.data.frame(table(tmp %>% filter(category == "knowledge") %>%
                             select(skill.group)))) +
  geom_bar(colour = "#666666", stat = "identity",
                 aes(x = reorder(Var1, Freq), y = Freq, fill = reorder(Var1, -Freq))) +
  scale_y_continuous(name = "Число вакансий") +
  theme(legend.position = "none",
        axis.text = element_text(size = 12)) +
  coord_flip()

Из графика видно, что наибольшим спросом пользуются знания в области баз данных, методологий разработки ПО и 1С. Далее идут знания в области CRM, ERP-систем и основы программирования.

В том, что касается стандартов, действительно большим спросом пользуется знание SQL и UML, на пятки им наступает нотация ARIS, а вот ГОСТы занимают всего лишь шестое место.


Здесь код
ggplot(as.data.frame(table(tmp %>% filter(category == "standards") %>%
                             select(skill.group)))) +
  geom_bar(colour = "#666666", stat = "identity",
                 aes(x = reorder(Var1, Freq), y = Freq, fill = Var1)) +
  scale_y_continuous(name = "Число вакансий") +
  theme(legend.position = "none",
        axis.text = element_text(size = 12)) +
  coord_flip()

Что касается используемых тулов, — мы лишний раз видим подтверждение тому, что основным инструментом аналитика является голова. Без линейки MS Office и таск-трекинговых систем не обойтись, а в остальном мало кого волнует, в каком именно редакторе аналитик создает свои схемы или набрасывает макеты интерфейсов.


Здесь код
ggplot(tmp %>% filter(category == "tools")) +
  geom_histogram(colour = "#666666", stat = "count",
                 aes(skill.group, fill = skill.group)) +
  scale_y_continuous(name = "Число вакансий") +
  theme(legend.position = "none",
        axis.text = element_text(size = 12)) +
  coord_flip()


Влияние навыков на доход

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

Для начала соединим интересующие нас столбцы из таблиц по вакансиям jobs.paidи скиллам all.skills, чтобы было удобнее строить графики на основе полученной data frame.

# 4.4 vizualize paid skills
tmp <- na.omit(merge(x = all.skills, y = jobs.paid %>% select(id, salary, lvl, City), 
             by = "id", sort = FALSE))

Получится таблица следующего вида:

> head(tmp)
        id                skill        skill.group   category salary    lvl     City
2 25781585              android          mobile os  knowledge  90000 middle Владимир
3 25781585 проектный менеджмент project management activities  90000 middle Владимир
5 25781585 управление проектами project management activities  90000 middle Владимир
6 25781585                  ios          mobile os  knowledge  90000 middle Владимир
7 25750025                 aris               aris  standards  70000 middle   Москва
8 25750025        бизнес-анализ  business analysis activities  70000 middle   Москва

Из городов я решила отфильтровать Москву и Питер, т.к. по ним больше всего данных. Сначала взглянем на активности:


Здесь код
ggplotly(ggplot(tmp %>% filter(category == "activities"), 
       aes(skill.group, salary)) +
  coord_flip() +
  geom_count(aes(size = ..n.., color = City)) +
    scale_fill_discrete(name = "Город") +
    scale_y_continuous(name = "Зарплата, руб.") +
  scale_size_area(max_size = 11) +
  theme(legend.position = "bottom", axis.title = element_blank(),
        axis.text.y = element_text(size=10, angle=10)))

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

Теперь проанализируем личные качества желаемых кандидатов:


Здесь код
ggplot(tmp %>% filter(category == "personal", City %in% c("Москва", "Санкт-Петербург")), 
                aes(tools::toTitleCase(skill), salary)) +
           coord_flip() +
           geom_count(aes(size = ..n.., color = skill.group)) +
           scale_y_continuous(breaks = round(seq(min(tmp$salary), 
                                        max(tmp$salary), by = 20000),1),
                              name = "Зарплата, руб.") +
           scale_size_area(max_size = 10) +
           theme(legend.position = "none", axis.title = element_text(size = 11),
                 axis.text.y = element_text(size=10, angle=0))

Что касается используемых инструментов, начиная от пакета MS Office и заканчивая софтом для составления диаграмм и создания мокапов, — здесь данных оказалось слишком мало, чтобы на их основании делать какие-то выводы о связи между владением определенным инструментом и уровнем дохода. Более того, чем выше зарплата, обозначенная в вакансии, тем меньше внимания уделяется конкретным инструментам в арсенале аналитика.

В том, что касается стандартов, картина немного отличается: умение обращатсья с нотациями UML и ARIS, а также знания SQL стабильно востребованы (в своих пропорциях) при разных уровнях зарплат, а вот знание IDEF — уже не такой популярный запрос, который и вовсе отсутствует на "максималках".


Анализ текста вакансий

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


Импорт и подготовка описаний

Посмотрим, как выглядит типичное описание вакансии в нашей исходной data frame:

> jobdf$Responsibility[[1]]
[1] "Training course in business analysis. ● Define needs of the user/client, understand the problem which needs to be solved. ● "
> jobdf$Requirement[[1]]
[1] "At least 6 months&#39; experience in business analysis. ● Knowledge of qualitative methods such as usability testing, interviewing, focus groups. ● "

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


Смотреть код
hh.get.full.desrtion <- function(df)
{
  df$full.description <- NA

  for (myURL in df$URL) {
    try(
      {
    data <- fromJSON(myURL)

    if (length(data$description) > 0)
    {
      df$full.description[which(df$URL == myURL, arr.ind = TRUE)] <- data$description
    }

    print(paste0("Filling in "
                 , which(df$URL == myURL, arr.ind = TRUE)
                 , " out of "
                 , length(df$URL)))
      }
    )
  }

  df$full.description <- tolower(df$full.description)

  return(df)
}

Полное описание вакансии может содержать юникод-символы списка, html-теги и пр., от чего нам нужно избавиться в первую очередь. Это можно сделать с использованием уже знакомой нам стандартной функции gsub:

remove.Html <- function(htmlString) { #remove html tags
  return(gsub("<.*?>", "", htmlString))
}

Это, впрочем, не является обязательнм шагом, поскольку сравнивать тексты вакансий я собираюсь все с тем же словарем, составленным вручную. Следующая функция принимает на вход data frame и словарь (также в виде df), пробегается по столбцу с полным описанием вакансии, ищет совпадения со словарем и формирует новую df вида "id, skill.group, category".


Смотреть код
skills.from.desc <- function(df, dictionary)
{
  sk <- data.frame(
      id = numeric()
    , skill.group = character()
    , category = character()
  )

  for (myskill in dictionary$skill)
  {
    category <- dictionary[dictionary$skill == myskill, "category"]

    mypattern <- paste0(na.omit(t(dictionary %>%
                                    filter(skill == myskill) %>%
                                    select(starts_with("syn")))), collapse = "|")

    if (nchar(mypattern) > 1) 
    {
      mypattern <- paste0(c(myskill, mypattern), collapse = "|")
    }
    else
    {
      mypattern <- myskill
    }

    cond = grep(x = df$full.description, pattern = mypattern)

    tmp <- data.frame(
      id = df[cond, "id"],
      skill.group = rep(myskill, length(cond)),
      category = rep(category, length(cond))

    )

    sk <- rbind(sk, tmp)

  }

  return(sk)
}

Снова о самых востребованных навыках

# 5 text analysis
# 5.1 get full descriptions

jobdf <- hh.get.full.description(jobdf)

jobdf$full.description <- remove.Html(tolower(jobdf$full.description))

sk.from.desc <- skills.from.desc(jobdf, dict)

Проверим, что получается?

> head(sk.from.desc)
        id skill.group category
1 25638419       axure    tools
2 24761526       axure    tools
3 25634145       axure    tools
4 24451152       axure    tools
5 25630612       axure    tools
6 24985548       axure    tools

> tmp <- as.data.frame(table(sk.from.desc$skill.group), col.names = c("Skill", "Freq"))
> htmlTable::htmlTable(x = head(tmp[order(tmp$Freq, na.last = TRUE, decreasing = TRUE),], 20), rnames = FALSE, header = c("Skill", "Freq"), align = 'l', css.cell = "padding-left: .5em; padding-right: 2em;")

Да, вот теперь баланс сил точно сместился! Project management, который неожиданно лидировал при анализе полей key_skills, теперь не входит даже в десятку (и в двадцатку тоже).

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

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

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


И снова о деньгах

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

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

tmp <- na.omit(merge(x = sk.from.desc, y = jobs.paid %>% 
               filter(City %in% c("Москва", "Санкт-Петербург")) %>% 
               select(id, salary, lvl, City), 
               by = "id", sort = FALSE))
> head(tmp)
        id                  skill.group   category salary    lvl   City
1 25243346                          uml  standards 160000 middle Москва
2 25243346      requirements management activities 160000 middle Москва
3 25243346 designing business processes activities 160000 middle Москва
4 25243346         communication skills   personal 160000 middle Москва
5 25243346                    mobile os  knowledge 160000 middle Москва
6 25243346                     ms visio      tools 160000 middle Москва

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


Развернуть код
ggplotly(ggplot(tmp %>% filter(category == "activities"), aes(skill.group, lvl)) +
  geom_count(aes(color = salary, size = ..n..)) +
  scale_size_area(max_size = 13) +
  theme(legend.position = "right",
        legend.title = element_text(size = 10),
        axis.title = element_blank(),
        axis.text.y = element_text(size=10)) +
  coord_flip() +
  scale_color_continuous(labels = function(x) format(x, scientific = FALSE), 
                         breaks = round(seq(min(tmp$salary), max(tmp$salary), 
                                            by = 70000),1),
                         low = "blue",
                         high = "red", name = "Зарплата, руб."))

Что мы видим из этого графика?

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

Во-вторых, несмотря на это, наиболее "денежными" занятиями является непосредственно бизнес-анализ, создание макетов интерфейсов и проведение исследований.

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

Глядя на график, можно сделать вывод, что на самом деле в зарплатном сегменте свыше 150 т.р. есть необходимость в специалистах со знанием не только UML или ARIS, но и IDEF, и ГОСТов, однако спрос на знание ГОСТов заметно ниже — в этой части первоначальный вывод подтверждается.

Некоторые изменения наблюдаются и в области личностных качеств аналитика:

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


А как же бесценный опыт?

Например, вот так выглядит плотность распределения зарплат в Москве для специалистов-аналитиков с различным стажем:

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

Для сравнения, в Санкт-Петербурге разделение куда более выраженное:

Проведенный анализ ключевых навыков и текстов вакансий BA/SA показал, что


  • качество такого анализа во многом зависит от словаря категорий навыков. В процессе работы я несколько раз обновляла и дополняла таблицу навыков и инструментов, но и сейчас классификация неидеальна;
  • потолок зарплаты аналитика в Москве (и в целом в России) составляет приблизительно 200 т.р. Все, что выше этой цифры, встречается весьма эпизодически и требует нетипичных скиллов вроде знания статистики или специфической предметной области;
  • на рынке вакансий довольно размытая граница между миддлами и сеньорами;
  • главный инструмент аналитика — по-прежнеу голова (выбор тулов, как правило, остается на усмотрение аналитика и не влияет на доход)
  • поле key_skills в вакансиях на hh заполняется через раз, и на основе только него нельзя делать выводы о наиболее востребованных навыках аналитика;
  • анализ текста вакансий, в свою очередь, оказался достовернее и полезнее, поскольку данных в результате парсинга описаний было собрано в пять(!) раз больше;
  • чтобы прийти к успеху во всех отношениях, аналитику стоит наиболее активно прокачивать навык бизнес-анализа, создания добротного UX и английский язык;
  • нельзя недооценивать коммуникативные навыки. Впрочем, их значимость снижается где-то после отметки в 150 т.р.;
  • в том, что касается стандартов, заслуживает упоминания SQL, а также нотации UML & ARIS. Для меня видеть такую популярность языка запросов довольно неожиданно, т.к. за несколько лет мне не приходилось активно его использовать. И это, пожалуй, единственный вывод данного исследования, который противоречит здравому смыслу личному опыту.
wordcloud2::wordcloud2(data = table(sk.from.desc$skill.group), 
                                     rotateRatio = 0.3, color = 'random-dark')

image

Let's block ads! (Why?)