Русский
Русский
English
Статистика
Реклама

Блог компании cardsmobile

Как мы автоматизировали выгрузки и другие Ad-hoc задачи аналитика с помощью Zeppelin

16.12.2020 18:10:53 | Автор: admin

На момент написания этой статьи в компании Cardsmobile, которая разрабатывает мобильное приложение Кошелёк, работает 195 человек: 8 аналитиков и 187 потенциальных заказчиков аналитиков. Мы делаем приложение для конечных пользователей, а также работаем с ритейлом, банками, брендами и другими партнерами. Долгое время работа аналитика в Кошельке состояла не только из исследований поведения пользователя, но и из различных выгрузок, типовых анализов для партнеров и прогнозов для потенциальных клиентов. Конечно, дашборды сильно спасали нам жизнь и позволяли всей компании следить за показателями продукта. Но мы всё ещё тратили время на остальную текучку, и с ростом команды (заказчиков) и бизнеса упёрлись: Ad-hoc задач стало слишком много, а исследования, желание развиваться и светлое будущее простаивали в отсутствие у нас времени.


Так много вокруг классных конференций, интересных статей про различные аналитические исследования, data-science, data-driven, data-счастье. А мы смотрели на всю эту красоту и не знали, где среди всего потока текучки найти время на эксперименты. Многие рассказывают, как сделать классно, но мало кто рассказывает, КАК преодолеть нарастающую текучку и освободить ресурсы для интересных и творческих задач. В этой статье я расскажу про наш опыт выхода в светлое будущее. Дальше будут примеры, как мы автоматизируем Ad-hoc задачи аналитиков в Zeppelin.


Что такое Zeppelin


Zeppelin это OpenSource Notebook от Apache, который позволяет обращаться к различным БД на разных языках (Python, R, SQL, Spark). Но что делает его особенно кайфовым, так это набор визуальных элементов dynamic forms.


В одном ноутбуке мы можем извлекать данные по api из Amplitude, быстро считать агрегаты из Clickhouse, дополнять результат данными из MSSQL и обрабатывать все это на Python. А готовые отчеты заворачивать в Excel в удобном заказчику формате и класть в html-ссылку, по которой их можно легко скачать.


Изначально мы начали использовать его просто как notebook, в котором было удобно писать на разных языках. Потом изучили возможности Zeppelin получше, нашли встроенные динамические формы: инбоксы, выпадающие списки и чеклиты лампочка над головой загорелась! Сразу придумали, сколько всего мы можем автоматизировать. У нас было много типовых задач с готовым кодом, в котором надо было просто менять значения переменных. Мы перенесли весь наш код в Zeppelin, вынесли переменные в динамические формы и дали заказчикам возможность самостоятельно заполнять их и запускать скрипты. Идея понравилась и нам, и всей остальной команде! :)


Какие динамические формы есть


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


image


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


image


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


image


Какие задачи мы автоматизируем в Zeppelin


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


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


С какими задачами обычно приходят:


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

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


image


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


Более изощренный кейс из жизни. Отдел маркетинга совместно с нашими стратегическими партнерами решили провести промо-акцию с определенной механикой. Пользователи нашего приложения должны были совершить цепочку действий, становясь участниками розыгрыша подарков. Раз в неделю мы хотели получать список участников недели, рандомно определять победителей, поздравлять их и отправлять подарки. Аналитик направления создал notebook в Zeppelin, который собирал пользователей, соответствующих условиям участия в розыгрыше за прошедшую календарную неделю. Маркетолог самостоятельно запускал notebook и забирал участников недели.


Подведение итогов А/B-тестов, измерение base-line метрик в тестовой и контрольной группах. Когда мы тестируем новый функционал или триггерную коммуникацию, мы смотрим не только на изменение целевой метрики, но и на то, как в целом меняется поведение пользователя. Мы выделили 4 base-line метрики пользовательского поведения:


  • Активность в приложении
  • Выпуски карт лояльности и других продуктов
  • Отписки
  • Обращения в саппорт

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


image


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


  • Пользователи, которые пришли в период
  • Добавили 5 или менее карт из топ 10 программ лояльности
  • Начали проходить определенный сценарий, но не закончили
  • Пользовались приложением больше 2х раз за последний месяц
  • И можно добавить фильтры по модели устройства, сотовому оператору и географии

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


И все это так и было, пока один аналитик не обленился достаточно, чтобы не писать код для выборки пользователей в Clickhouse, а собрать когорту в Amplitude и выгрузить ее по api. Что, согласитесь, сильно проще и быстрее. Привычный и уже понятный интерфейс Amplitude, где любой менеджер может самостоятельно собрать когорту со всеми фильтрами из примера выше, проверить ее размер, дополнительно проконтролировать себя и проверить пользователей из когорты, что они попали в нее верно.


Как выглядит механика:


  • Продакт-менеджер или маркетолог создает когорту в Amplitude. При необходимости сложные кейсы показывает аналитику.
  • Копирует id когорты, который находится в адресной строке
  • Вставляет в notebook в Zeppelin
  • Выставляет дополнительные фильтры, данных для которых нет в Amplitude
  • Присваивает рассылке уникальный sub_id и запускает notebook

Что происходит в это время:


  • Скрипт берет id когорты и выгружает ее по api из Amplitude
  • Полученный DataFrame очищается от лишних строк в Python
  • При необходимости база получателей дополнительно фильтруется по полу и/или возрасту
  • Так же выделяется контрольная группа, если мы хотим измерить эффективность рассылки (а мы редко не хотим)
  • Получатели записываются в БД для истории и передаются в csv-файл, который мы для удобства скачивания кладем в кликабельную ссылку

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


image


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


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

Success. Победили текучку, освободили время для развития аналитики в компании


Самый приятный абзац этой статьи наступило светлое будущее! Мы уже автоматизировали большую часть наших Ad-hoc задач. Теперь в спринте их меньше 10%. В освободившееся время мы проводим исследования, выдвигаем и проверяем гипотезы, усложняем наши продукты аналитики и применяем подходы из тех самых статей и выступлений на конференциях. Другими словами, мы наконец занимаемся интересной аналитической работой. А главное, у нас появилось время принимать активное участие в развитии Кошелька.


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


Data-счастье еще впереди, но мы уже сильно воодушевились, ожили и бежим ему навстречу.

Подробнее..

Кошелёк Mobile Challenge итоги конкурса и подробный разбор решений командой разработки

18.12.2020 14:16:13 | Автор: admin

У нас было две платформы, 1 000 000 рублей призового фонда, 6 призовых мест, 26 тысяч строк кода, которые нужно прочитать и оценить, а также 20 страниц фидбеков, несколько критериев оценки, ящик Бадвайзера, пинта чистого эфира и 12 пузырьков успокоительного. Не то, чтобы всё это было категорически необходимо для проведения конкурса разработчиков, но если уж начал оценивать решения, то к делу надо подходить серьёзно.

Подводим итоги конкурса Кошелёк Mobile Challenge и в деталях разбираем решения участников.

Задание

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

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

Конкурс в цифрах

1 задание

2 платформы (iOS и Android)

1 000 000 рублей призового фонда

100 пользователей в телеграм-канале конкурса

1 месяц на разработку решения

13 участников

11 судей

6 призовых мест

Победители

iOS

1 место (250 000 рублей) Антон Спивак

2 место (150 000 рублей) Роберт Шагинян

3 место (100 000 рублей) Павел Протасов

Android

1 место (250 000 рублей) Андрей Эрдман

2 место (150 000 рублей) Виталий Кириллов

3 место (100 000 рублей) Георгий Ипполитов

Разбор решений

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

Напомним основные критерии оценки:

  1. Чистота и расширяемость кода.

  2. Плавность, скорость и отзывчивость интерфейса.

  3. Стабильность работы.

  4. Процент поддерживаемых ОС и устройств.

Технические требования:

  1. Нативное iOS- или Android-приложение, без кросс-платформы.

  2. Для Android: поддержка 23+ API.

  3. Для iOS: поддержка iOS 11+.

Разбор iOS решений

Соответствие техническим требованиям

Все представленные решения нативные, написаны на Swift, что не может не радовать. У большей части минимальная поддерживаемая версия ОС 11, что дает дополнительные баллы при оценке. А кто-то, вероятно, просто забыл указать, так как в проектах мы не нашли использования специфичного для новых версий API. Xcode же при создании нового проекта любезно ставит минимальной ту версию, которая для текущего SDK самая свежая.

Типичные ошибки и рекомендации

Несоблюдение принципа DI (Dependency Inversion)

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

Нарушение принципа SR (Single Responsibility)

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

Создание лишних уровней абстракции

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

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

Использование reference вместо value типов там, где в этом нет явной необходимости

Пример:

class RequestModel: Request {    var method: HTTPMethod = .get    var headers: [String : String]? = nil    var url: URL = URL(string: "https://textures.cardsmobile.ru")!    var parameters: [String : String]? = nil    var contentType: ContentTypeRequestEnum = .applicationURLEncoder}

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

Использование лишних 3rd party зависимостей

Многим разработчикам кажется, что библиотека Alamofire является стандартом индустрии для организации сетевого слоя. Она очевидно имеет ряд преимуществ и позволяет не изобретать велосипед каждый раз при работе с сетью в большом приложении. И все же, использовать Alamofire только для того, чтобы быстро написать AF.request(url).response { } выглядит необоснованным решением. В чем отличие от URLSession.shared.dataTask(with: url) { }.resume()? Кажется, что разницы никакой. При этом подключая такую большую библиотеку в своей проект, мы увеличиваем размер дистрибутива и потенциально затягиваем чужие баги. Хочется пошутить, перефразировав Билла Гейтса нативного URLSession должно хватить всем. Но как мы знаем, в любой шутке есть доля правды.

Неправильное сохранение контекстов в Core Data

Во время работы с несколькими контекстами (NSManagedObjectContext) при сохранении данных встречаются случаи последовательного вызова метода save() у контекстов. Оптимальнее подписываться на нотификацию NSManagedObjectContextDidSave и мержить данные с background контекста в главный view контекст. Опыт подсказывает, что сохранение через нотификации быстрее с точки зрения перформанса.

Рекомендуем для ознакомления книгу с описанием лучших практик использования Core Data.

Чрезмерное использование глобальных очередей DispatchQueue.global

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

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

Использование background Quality Of Service (QoS)

Код в очереди с приоритетом background может при некоторых системных условиях не выполниться, что может повлечь за собой трудноуловимые баги (ведь кажется, редко бывает, что нам неважно, выполнится указанный код или нет). Лучше заменять на QoS utility. Подробности в треде.

Чрезмерная нагрузка главного потока задачами

Самые яркие примеры из предложенных решений загрузка из сети и ресайзинг картинок на главном потоке.

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

Игнорирование важных событий жизненного цикла приложения и UIViewController

Конкурсанты предложили логичную фичу при открытии штрихкода увеличивать яркость экрана, чтобы он считывался легче. Для этого можно управлять яркостью в методах viewWillAppear и viewWillDisappear (для того, чтобы вернуть яркость в исходное состояние). Но, как показывает практика, обычно пользователь после предъявления карты просто сворачивает приложение. В таком случае яркость останется на максимуме, и одна звезда в App Store вам обеспечена.

Рекомендуем не забывать про нотификации UIApplicationDidEnterBackgroundNotification и UIApplicationDidBecomeActiveNotification, которые сигнализируют о том, что приложение было свернуто и развернуто соответственно.

Отсутствие адаптивности под разные размеры экранов

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

Орфографические ошибки в коде

Частые ошибки затрудняют читабельность и ревью кода, снижает поиск нужных элементов по проекту. Понятно, что такие ошибки часто возникают из-за того, что замылился глаз или в состоянии потока быстро программируешь, пока не ускользнула мысль. Но всё же мы за код без ошибок (во всех их проявлениях), и поэтому рекомендуем включить проверку орфографии в Xcode с помощью Edit > Format > Spelling and Grammar > Check Spelling While Typing.

Проблемы с лейаутом констрейнов

В ситуациях, когда констрейны расставлены неверно, система не может однозначно их интерпретировать, чтобы понять финальный лейаут UI. Xcode помогает в этом и выбрасывает в консоль сообщения вида Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want.

Рекомендуем следить за этими сообщениями, а также ознакомиться со статьей Debugging Tricks and Tips.

Предупреждения (warnings) на этапе компиляции

Сюда могут относиться сообщения анализатора кода (swiftlint, если он у вас встроен в проект, встроенного анализатора об использовании deprecated методов и т.д.), обновление до рекомендуемых настроек проекта и т.д.

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

Неиспользуемый и закомментированный код в проекте

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

Магические константы

Как пример: выражение self.viewModel.objects().count%5 тяжело понять почему именно эти значения используются, особенно когда возвращаешься к коду через какое-то длительное время.

Как вариант, рекомендуем применить в этом месте метод рефакторинга Замена магического числа символьной константой. Развернутое описание этого и других методов можно найти в книге Рефакторинг. Улучшение существующего кода Мартина Фаулера.

Что понравилось с технической стороны

Архитектура презентационного слоя

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

Наличие билдера плюс со стороны работы с DI (Dependency Injection).

В одном из решений работа с зависимостями организована с использованием Dependency Container, что позволяет упростить работу с ними.

Сокрытие использования конкретной библиотеки за оберткой

В нескольких решениях использовалась библиотека Kingfisher для подгрузки картинок, только в одном использование было спрятано за классом ImageLoader. Это позволяет не размазывать 3rd party зависимость по всему проекту.

Использование ключевого слова final в декларации класса, метода или свойства

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

Что понравилось с продуктовой стороны

Идеи организации списка карточек

#1 Размер карточки увеличивается при скролле, при двойном нажатии появляется штрихкод

#2 Горизонтальный скрол, дополнительно переходить на детали не требуется, чтобы показать штрихкод

Наличие офлайн-режима и быстрый запуск при холодном старте

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

Поддержка тёмной темы

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

Локализация

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

Поиск

Фильтрация

#1 Возможность показывать только просроченные или действительные элементы

#2 Фильтрация по категориям

Разбор Android-решений

Соответствие техническим требованиям

Все присланные решения нативные и написаны с использованием Kotlin, минимально поддерживаемый системный API 23+ использовали все.

Типичные ошибки и рекомендации

Почти все решения были сделаны без группировки по категориям. Данных апи было достаточно для организации удобного UI. Отсутствовала сортировка drag&drop.

Нарушение принципа DI (Dependency Inversion), повсеместное использование паттерна Singleton

В одном из проектов CardsInteractor из domain-слоя обращается напрямую в CardsRepository из data-слоя, что нарушает последний принцип SOLID. Для решения этой проблемы можно добавить Interface для CardsRepository, который будет находиться в Domain-слое.

Чрезмерное использование синглтонов ухудшает способность кода быть к тестированию. Дружеская рекомендация: прочесть книгу Роберта Мартина Чистая архитектура и изучить проект на github.

Монолитность проектов

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

Отсутствует разделение кода на слои Clean Architecture: data, domain, presentation

В одном из решений CardsInteractor обращается к Storage напрямую и загружает картинки из сети. Хотелось бы видеть тут разделение ответственности. Например, часть логики из CardsInteractor вынести в Repository и DataSource. CardsViewModelImpl отвечает за сортировку списка карт, что, вероятно, не должно выполнятся в презентационном слое.

Использование глобальных функций-расширений, таких как CharSequence?.isNotNullAndEmpty, List<T>.isEmpty

На наш взгляд, использовать подобные функции избыточно. Вместо них можно использовать, к примеру, функции-расширения языка Kotlin CharSequence?.isNullOrEmpty(), isEmpty() и т.д.

Использование неочевидных названий для переменных

Как пример view с названиями info1TitleTextView, info2TitleTextView, info3TitleTextView, info4TitleTextView. На первый взгляд тяжело сказать, чем отличаются данные view. Хочется видеть более говорящие названия.

Использование deprecated методов

Для примера в одном из проектов используется window.decorView.systemUiVisibility, View.SYSTEMUIFLAGLAYOUTFULLSCREEN, SYSTEMUIFLAGLAYOUTHIDE_NAVIGATION. Использование deprecated методов можно смело относить к техническому долгу, с которым рекомендуем бороться как можно быстрее.

Что понравилось с технической стороны

Многомодульность некоторых проектов и следование принципам Clean Architecture

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

Навигация

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

Слава богам, что мы не нашли реализаций навигации через неявные интенты :)

Что понравилось с продуктовой стороны

#1 Хороший поиск и дизайн горизонтального скролла

#2 3д карта, интересный подход с быстрым доступом к штрихкоду из списка и общий элемент в навигации

#3 Понравилась группировка карт и удобный скролл к нужной группе


Спасибо за ревью команде жюри: Филиппу Шубину, Константину Степаненко, Константину Малкову, Николаю Ашанину, Андрею Бусику, Богдану Маншилину, Александру Пряничникову, Антону Давыдову, Александру Юдину, Семёну Задорожному, Кириллу Белоглазову.

Спасибо всем участникам! Надеемся в будущем ещё порадовать сообщество разработчиков подобными активностями.

А ещё недавно мы запустили телеграм-канал Cardsmobile | Кошелёк Engineering, в котором делимся опытом разработки, интересными историями и новостями не только команд iOS и Android, но также QA и backend. Рады там всем, нам будет интересно обменяться опытом решения подобных задач и с другими компаниями.

Подробнее..

Виджеты в iOS 14 возможности и ограничения

17.09.2020 14:13:09 | Автор: admin


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

Начнем с определения виджетами называются представления, показывающие актуальную информацию без запуска основного мобильного приложения и всегда находящиеся под рукой у пользователя. Возможность их использовать уже есть в iOS (Today Extension), начиная с iOS 8, но мой сугубо личный опыт их использования довольно печален под них хоть и выделен специальный рабочий стол с виджетами, но я всё равно редко туда попадаю, привычка не выработалась.

Как итог, в iOS 14 мы видим перерождение виджетов, глубже интегрированных в экосистему, и более удобных для пользователя (в теории).



Работа с картами лояльности одна из основных функций нашего приложения Кошелёк. Периодически в отзывах в App Store появляются предложения от пользователей о возможности добавления виджета в Today. Пользователи, находясь у кассы, хотели бы как можно быстрее показать карту, получить скидку и убежать по своим делам, ведь промедление на любой квант времени вызывает те самые укоризненные взгляды в очереди. В нашем случае виджет может сэкономить несколько пользовательских действий для открытия карты, таким образом сделав операцию оплаты товара на кассе более быстрой. Магазины тоже будут благодарны меньше очередей на кассе.

В этом году Apple неожиданно для всех выпустила релиз iOS практически сразу после презентации, оставив разработчикам сутки на доработки своих приложений на Xcode GM, но мы оказались готовы к релизу, так как свой вариант виджета наша iOS-команда стала делать еще на бета-версиях Xcode. Сейчас виджет находится на ревью в App Store. Обновление устройств до новой iOS по статистике происходит довольно быстро; скорее всего, пользователи пойдут проверять, у каких приложений виджеты уже есть, найдут наш и будут счастливы.

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



Добавление виджета в проект


Как и другие подобные дополнительные возможности, виджет добавляется как расширение (extension) к основному проекту. После добавления Xcode любезно генерирует код виджета и других основных классов. Вот тут нас ждала первая интересная особенность для нашего проекта этот код не компилировался, так как в одном из файлов автоматически подставлялся префикс в названиях класса (да-да, те самые Obj-C префиксы!), а в генерируемых файлах нет. Как говорится, не боги горшки обжигают, видимо, разные команды внутри Apple не договорились между собой. Будем надеяться, что к релизной версии исправят. Для того, чтобы настроить префикс своего проекта, в File Inspector основного таргета приложения заполните поле Class Prefix.

Для тех, кто следил за новинками WWDC, не секрет, что реализация виджетов возможна только с использованием SwiftUI. Интересный момент, что таким образом Apple форсит обновление на свои технологии: даже если основное приложение написано с использованием UIKit, то тут, будьте любезны, только SwiftUI. С другой стороны, это хорошая возможность попробовать новый фреймворк для написания фичи, в этом случае он удобно вписывается в процесс никаких изменений состояния, никакой навигации, требуется только задекларировать статичный UI. То есть вместе с новым фреймворком появились и новые ограничения, потому как старые виджеты в Today могут содержать больше логики и анимацию.

Одно из основных нововведений SwiftUI возможность предпросмотра без запуска на симуляторе или девайсе (preview). Классная вещь, но, к сожалению, на больших проектах (в нашем ~400K строк кода) работает крайне медленно даже на топовых макбуках, быстрее запустить на девайсе. Альтернатива такому способу иметь под рукой пустой проект или плейграунд для быстрого прототипирования.

Возможность для дебага также есть с помощью выделенной Xcode схемы. На симуляторе отладка работает нестабильно даже к версии Xcode 12 beta 6, поэтому лучше пожертвовать один из тестовых девайсов, обновить до iOS 14 и тестировать на нем. Будьте готовы, что эта часть и на релизных версиях будет работать не как ожидается.

Интерфейс


На выбор пользователю даются разные типы (WidgetFamily) виджетов трёх размеров small, medium, large.



Для регистрации необходимо явно указать поддерживаемые:
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: CardListWidgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего")        .supportedFamilies([.systemSmall, .systemMedium])    }}

Мы с командой решили остановиться на small и medium выводить одну любимую карту для маленького виджета или 4 для medium.

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



Цвет кнопки Добавить виджет кастомизируем с помощью Assets.xcassets -> AccentColor, имя виджета с описанием тоже (пример кода выше).

Если уперлись в ограничение по количеству поддерживаемых видов, то можно расширить его с помощью WidgetBundle:

@mainstruct WalletBundle: WidgetBundle {    @WidgetBundleBuilder    var body: some Widget {        CardListWidget()        MySecondWidget()    }}

Так как виджет показывает слепок некоторого состояния, то единственная возможность для пользовательского интерактива это переход в основное приложение по нажатию на какой-то элемент или весь виджет. Никакой анимации, навигации и переходов на другие view. Но есть возможность прокинуть диплинку в основное приложение. При этом для small виджета зоной нажатия является вся область, и в этом случае используем widgetURL(_:) метод. Для medium и big доступны нажатия по view, и в этом нам поможет структура Link из SwiftUI.

Link(destination: card.url) {  CardView(card: card)}

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



При проектировании интерфейса виджета могут помочь следующие правила и требования (согласно гайдлайнам Apple):
  1. Сфокусируйте виджет на одной идее и проблеме, не пытайтесь повторить всю функциональность приложения.
  2. В зависимости от размера выводите больше информации, а не просто масштабируйте контент.
  3. Выводите динамическую информацию, которая может меняться в течение дня. Крайности в виде полностью статической информации и информации, меняющейся ежеминутно, не приветствуются.
  4. Виджет должен давать актуальную информацию пользователям, а не быть еще одним способом открыть приложение.

Внешний вид настроили. Следующий шаг выбрать, какие карты и каким образом показывать пользователю. Карт может быть явно больше четырёх. Рассмотрим несколько вариантов:
  1. Дать возможность пользователю выбирать карты. Кто, как не он, знает, какие карты важнее!
  2. Показывать последние использованные карты.
  3. Сделать более умный алгоритм, ориентируясь, например, на время и день недели и статистику (если пользователь по будням вечером ходит во фруктовую лавку у дома, а на выходных ездит в гипермаркет, то можно помочь пользователю в этом моменте и показывать нужную карту)

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

Пользовательские настройки виджета


Настройки формируются с помощью интентов (привет Андроид-разработчикам) при создании нового виджета файл интента добавляется в проект автоматически. Кодогенератор подготовит класс-наследник от INIntent, который является частью фреймворка SiriKit. В параметрах интента стоит магическая опция Intent is eligible for widgets. Доступны несколько типов параметров, можно настраивать свои подтипы. Так как данные в нашем случае это динамический список, то еще устанавливаем пункт Options are provided dynamically.

Для разных типов виджета настраиваем максимальное количество элементов в списке для small 1, для medium 4.
Этот тип интента используется виджетом как источник данных.



Далее настроенный класс интента необходимо поставить в конфигурацию IntentConfiguration.
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: WidgetConstants.widgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего.")        .supportedFamilies([.systemSmall, .systemMedium])    }}

В случае, если пользовательские настройки не требуются, то есть альтернатива в виде класса StaticConfiguration, которая работает без указания интента.

Изменяемыми на экране настроек являются title и description.
Название у виджета должно помещаться на одну строку, иначе оно обрезается. При этом допустимая длина для экрана добавления и настроек виджета отличаются.
Примеры максимальной длины названия для некоторых устройств:

iPhone 11 Pro Max28 для настроек21 для меню добавленияiPhone 11 Pro25 для настроек19 для меню добавленияiPhone SE24 для настроек19 для меню добавления

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



Также можно изменить цвет фона и значения параметров WidgetBackground и AccentColor по умолчанию они уже лежат в Assets. При необходимости их можно переименовать в конфигурации виджета в Build Settings у группы Asset Catalog Compiler Options в полях Widget Background Color Name и Global Accent Color Name соответственно.



Некоторые параметры могут быть скрыты (или показаны) в зависимости от выбранного значения в другом параметре через настройку Relationship.
Стоит отметить, что UI для редактирования параметра зависит от его типа. К примеру, если укажем Boolean, то мы увидим UISwitch, а если Integer, то тут у нас уже выбор из двух вариантов: ввод через UITextfield или пошаговое изменение через UIStepper.



Взаимодействие с основным приложением.


Связку настроили, осталось определить, откуда сам интент возьмет реальные данные. Мостик с основным приложением в этом случае файл в общей группе (App Groups). Основное приложение пишет, виджет читает.
Для получения URL к общей группе используется следующий метод:
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group.ru.yourcompany.yourawesomeapp)

Сохраняем всех кандидатов, так как они будут использоваться пользователем в настройках как словарь для выбора.
Далее операционная система должна узнать, что данные обновились, для этого вызываем:
WidgetCenter.shared.reloadAllTimelines()// Или WidgetCenter.shared.reloadTimelines(ofKind: "kind")

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

Обновление данных


В целях бережного отношения к батарейке пользовательского девайса Apple продумали механизм обновления данных на виджете с использованием timeline механизма генерации слепков (snapshot). Напрямую разработчик не обновляет и не управляет view, но зато предоставляет расписание, руководствуясь которым, операционная система нарежет снапшотов в бэкграунде.
Обновление происходит по следующим событиям:
  1. Вызов используемого ранее WidgetCenter.shared.reloadAllTimelines()
  2. При добавлении виджета пользователем на рабочий стол
  3. При редактировании настроек.

Также в распоряжении разработчика три вида политик по обновлению таймлайнов (TimelineReloadPolicy):
atEnd обновление после показа последнего cнапшота
never обновление только в случае принудительного вызова
after(_:) обновление через определенный промежуток времени.

В нашем случае достаточно попросить систему сделать один снепшот до момента, пока данные карт не обновились в основном приложении:

struct CardListProvider: IntentTimelineProvider {    public typealias Intent = DynamicMultiSelectionIntent    public typealias Entry = CardListEntry    public func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData)    }    public func getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void) {        let entry = CardListEntry(date: Date(), cards: testData)        completion(entry)    }    public func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) {        let cards: [WidgetCard]? = configuration.cards?.compactMap { card in            let id = card.identifier            let storedCards = SharedStorage.widgetRepository.restore()            return storedCards.first(where: { widgetCard in widgetCard.id == id })        }        let entry = CardListEntry(date: Date(), cards: cards ?? [])        let timeline = Timeline(entries: [entry], policy: .never)        completion(timeline)    }}struct CardListEntry: TimelineEntry {    public let date: Date    public let cards: [WidgetCard]}

Более гибкий вариант пригодился бы при использовании автоматического алгоритма подбора карточек в зависимости от дня недели и времени.

Отдельно стоит отметить показ виджета, если он находится в стэке из виджетов (Smart Stack). В этом случае для управления приоритетами мы можем воспользоваться двумя вариантами: Siri Suggestions или через установку значения relevance у TimelineEntry с типом TimelineEntryRelevance. TimelineEntryRelevance содержит два параметра:
score приоритет текущего снапшота относительно других снапшотов;
duration время, пока виджет остается актуальным и система может поставить его на верхнюю позицию в стэке.

Оба способа, а также возможности конфигурации виджета, были подробно рассмотрены на сессии WWDC.

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

Text поддерживает следующие стили:
relative разница времени между текущей и заданной датами. Тут стоит отметить: если дата указана в будущем, то начинается обратный отсчет, а после показывается дата от момента достижения нуля. Такое же поведение будет и для следующих двух стилей;
offset аналогично предыдущему, но есть индикация в виде префикса с ;
timer аналог таймера;
date отображение даты;
time отображение времени.

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

let components = DateComponents(minute: 10, second: 0) let futureDate = Calendar.current.date(byAdding: components, to: Date())! VStack {   Text(futureDate, style: .relative)      .multilineTextAlignment(.center)   Text(futureDate, style: .offset)      .multilineTextAlignment(.center)   Text(futureDate, style: .timer)      .multilineTextAlignment(.center)   Text(Date(), style: .date)       .multilineTextAlignment(.center)   Text(Date(), style: .time)      .multilineTextAlignment(.center)   Text(Date() ... futureDate)      .multilineTextAlignment(.center)}



Превью виджета


При первом отображении виджет будет открыт в режиме превью, для этого нам необходимо вернуть TimeLineEntry в методе placeholder(in:). В нашем случае это выглядит так:
func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData) }

После чего к view применяется модификатор redacted(reason:) с параметром placeholder. При этом элементы на виджете отображаются размытыми.



Мы можем отказаться от этого эффекта у части элементов, использовав unredacted() модификатор.
Также в документации сказано, что вызов метода placeholder(in:) происходит синхронно и результат должен вернуться максимально быстро, в отличие от getSnapshot(in:completion:) и getTimeline(in:completion:)

Скругление элементов


В гайдлайнах рекомендуется согласовать скругление у элементов со скруглением виджета, для этого в iOS 14 была добавлена структура ContainerRelativeShape, которая позволяет применить к view форму контейнера.

.clipShape(ContainerRelativeShape()) 

Поддержка Objective-C


В случае необходимости добавить в виджет код на Objective-C (например, у нас на нем написана генерация изображений штрихкодов) всё происходит стандартным способом через добавление Objective-C bridging header. Единственная проблема, с которой мы столкнулись при сборке Xcode перестал видеть автогенерируемые файлы интентов, поэтому мы также добавили их в bridging header:

#import "DynamicCardSelectionIntent.h"#import "CardSelectionIntent.h"#import "DynamicMultiSelectionIntent.h"

Размер приложения


Тестирование проводилось на Xcode 12 beta 6
Без виджета: 61.6 Мб
С виджетом: 62.2 Мб

Резюмирую основные моменты, которые рассмотрели в статье:
  1. Виджеты отличная возможность пощупать SwiftUI на практике. Добавляйте их в проект, даже если минимальная поддерживаемая версия ниже iOS 14.
  2. WidgetBundle используется для увеличения числа доступных виджетов, вот отличный пример как много различных виджетов имеет приложение ApolloReddit.
  3. Для добавления пользовательских настроек на самом виджете поможет IntentConfiguration или StaticConfiguration, если пользовательские настройки не нужны.
  4. Общая папка на файловой системе в общей группе App Groups поможет синхронизировать данные с основным приложением.
  5. На выбор разработчику предоставляется несколько политик обновления таймлайна (atEnd, never, after(_:)).

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

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

Всёкажется невозможным,пока небудет сделано, или Как работали HR Кошелька во времена бушующего коронавируса

16.03.2021 14:17:52 | Автор: admin

Приближается годовщина массового перехода компаний на удалённую работу. Мы, как и многие другиекомпании, прошли все пять стадий принятия неизбежного.Кажется,сейчасможно подвестинебольшие итоги:как мы впервые жили, работали, достигали результатов и не останавливались ни наминутув эпоху пандемии.Думаем, что наш опыт может пригодиться другим HR-командам, переживающим этап быстрого роста компании, и мы рады им поделиться.

Подбор

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

Абсолютный рекорд, которого достигли наши рекрутеры8человек в неделю. За весь год,проведённый удалённо,команда Кошелька выросла вдвое.104новых члена команды, вихчисле разработчики, аналитики,руководители и многие другие.Что стоит за этойцифрой? Работавсегодвух(на тот момент)рекрутеров!

Welcome-пакет, который мы дарим новичкам при приёме на работуWelcome-пакет, который мы дарим новичкам при приёме на работу

Трудоустройство и адаптация персонала

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

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

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

Работа распределённых команд

Оказавшись в ситуации, когда просто нельзя останавливаться (проекты запущены, договорённости определены, нужнопродолжатьработатьв том же темпе),когда каждый день важен,мы столкнулись с большим вызовом как быстро настроить общение команд без потери качества?В Кошелькевсегда былатёплая и дружеская атмосфера. Нам, правда, нравится общаться друг с другом.Турнирынастолок, короткие разговоры за кофе,брейнштормыв удобных переговорных. Чем заменитьэтоживое общение?Что ж.В пандемиювесь наш мирпоместилсяврамкахSlackиTeams.Команды встречаются на короткиедейли(мы,HR, называем ихБудилки),авместемыпроводим большиесессии ифасилитациивонлайне.Научились не стесняться неожиданного появления детей в камере и знаем в лицо всех домашних питомцев.

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

Пример презентации из внутреннего выступления: прошлая неделя, командаКардхолдерарассказывает о себеПример презентации из внутреннего выступления: прошлая неделя, командаКардхолдерарассказывает о себе

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

Часть лонгрида Филиппа Часть лонгрида Филиппа

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

Результаты?Например,Кошельку удалосьвыйти на рынок Беларусисо своимплатёежнымсервисом КошелёкPayв абсолютно удалённом формате.Все переговорыспартнёрами, заключённыедоговоры,акциипродвижениявсё этомысделали, находясь в России,каждый у себя дома.С участием новых членов команды.И это только одинуспешный примериз множества других.

Wellbeing

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

Конечно, мы такжеделаем ставкуинаразныепрактики для поддержанияфизического здоровья, неразрывно связанного с ментальным.Те, кто уже переболел коронавирусом или вакцинировался, могутбесплатнозаниматься спортом в офисе или по корпоративнойскидке вFitMost.Во времялокдаунаофисные занятия переезжали на онлайн-платформы: тренеры вели уроки в прямом эфире в ZOOM. А ещё у нас был специальный чат про домашние тренировки, где мы делились упражнениями и, например, рабочими схемами круговых.

Фото из нашего внутреннего инстаграм-анонса о возобновлении офисного спортаФото из нашего внутреннего инстаграм-анонса о возобновлении офисного спорта

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

Внутренниекоммуникации

До пандемии у нас была традиция регулярно собирать сводку новостей по отделам и верстать большое письмо с главными событиями месяца. Мы называем это письмоХроники Кошелька, и в пандемию его объёмтолько вырос.Помимо блоков с новостями команд у нас появился общий блок с полезными ссылками (к примеру, навнутренниевыступления)ираздел сновостямиовсейкомпании. Сейчас мы уже так привыкли к Хроникам, что, если они вдруг не приходят вовремя, команда начинает нервничать. Кстати, следующей задачей после написания этой статьи у нас стоят эти самыеХроники запрошлый месяц!

Так выглядят Хроники Кошелька. В оформлениихедераиспользован мем сгенеральным директором КирилломГорынейТак выглядят Хроники Кошелька. В оформлениихедераиспользован мем сгенеральным директором КирилломГорыней

Для общения по рабочим вопросам мы, как и многие, используемSlack. В разгар пандемии туда переехала вообще вся коммуникация: появились чаты про кулинариюиспорт в изоляции, образовались книжный клуб и клубсинефилов(видели бы вы наштредо сериале Sexeducation!). Конечно,не обошлось ибезмемошной.Одно время мы даже выбирали лучший мем недели и дарили автору сертификат в Озон. Хитсреди каналов Служба сутулых собак,гдесотрудники техподдержкипубликуют забавныеотзывыпользователейо нашем Кошельке.

Раз в неделювсемсотрудникамвSlackприходит дайджест событий, в которые можно вписаться и для обучения, и для развлечения. Когда к нам присоединяется новыйсотрудник, он пишет небольшой рассказ о себе в общий каналиногда такие представления приводят к объёмнымтредам. Например, напрошлой неделе нашновыйскрам-мастер Юляв своём сообщении-знакомствеупомянула, что работает удалённо из Новосибирска, и в комментариях собрался целыйфан-клубновосибирских мест. Открытая коммуникация всегда была одной из наших ценностей, и здорово, что у нас получаетсятранслировать её даже в таких базовых вещах, как первая встреча сотрудника с командой.

Представление Юли в общем чате...Представление Юли в общем чате......и реакция коллег, которые вспоминают сибирское детство/юность...и реакция коллег, которые вспоминают сибирское детство/юность

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

Всё началось с подготовки: концепция, активности, выборлокаций иподрядчика для трансляции,сборысъёмочной группы. Нам хотелось разрушить стену(читайпроникнуть сквозь монитор)между участниками корпоратива, которые сидели по домам, ируководителями, которые вели трансляцию из студии. На помощь пришёл интерактив: каждому из почти двухсот членов команды в конце декабря курьером доставили КошелёкChristmasBox. Да, даже тем, кто живёт в самых дальних уголках России. В составбокса входили расходные материалы длямастер-классоввонлайне, сразу трёх: мы варили глинтвейн, пекли имбирное печенье и делали венок из ветокнобилиса.

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

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

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

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

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

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

Пример вопроса о коллеге вквизе-тимбилдинге. Амбициозные мечты наш конёкПример вопроса о коллеге вквизе-тимбилдинге. Амбициозные мечты наш конёк

Обучение и развитие

Пандемия показала, как важно быстро находить решения, быть гибкими, перестраиваться идвигаться дальше.КомандаHRдаёт возможности для обучениясотрудниковполезнымнавыкам через внешние и внутренние программы. В 2020 году их стало намного больше. Мы двигаемся как в направленииhardskills(например, приглашаем спикеров из интересных нам компаний и поддерживаем инициативы сотрудников в их профессиональном развитии), так и делаем большую ставку наsoftskillsи внутренние программы.Тестируем форматMOOCи студий развития навыков, например, учимсяуправлению временем.

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

Корпоративная культура и ценности

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

Для разработки ценностей был выбран двойной путьbottom-upиtop-down: мнения сотрудников объединились в смысловые категории, уточнялись через работу ссобственниками и топ-командой (связь ценностей с миссией и стратегией),далееформулировалсявординг. Следующий этап трансляциянакоманду.

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

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

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

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

Попугай по имени Чуи, любимец нашего UX/UI дизайнера ДимыПопугай по имени Чуи, любимец нашего UX/UI дизайнера ДимыГермес, мопс нашего ведущего разработчика Саши. Ревизия подаркаГермес, мопс нашего ведущего разработчика Саши. Ревизия подарка

Резюмируя

Чему мы точно научились в пандемию, так это принимать быстрые решения и продюсировать новые проекты удалённо. Эти навыки удачно работают в комбо, если у вас есть команда иззаряженныхHR и активных сотрудников, которые собственным участием привлекутк активностям и остальную команду.

Что важно помнить:

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

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

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

  • Открытоговорите о проделанной работе. Большая часть работы HR остаётся подводной частью айсберга: она есть, но не видна сотруднику со стороны. Устраивайтедемо-дни, презентации, показывайте не только результат, но и процессыинсайты всегда интересны сотрудникам вне вашего отдела. Мы, например, делаем это раз в квартал, в том числе задавая коллегам вопрос: чем ещё мы могли бы вам помочь? Как сделать работу комфортнее?

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

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

Подробнее..

Как мы загружали банковскую карту из iPhone в брелок

18.08.2020 14:15:47 | Автор: admin

С каждым годом всё больше компаний проявляют интерес к проектам, связанным с интернетом вещей (Internet of Things, IoT).

В статье я расскажу о созданной нами IoT платформе, о способах загрузки банковских карт в носимые устройства, об исследовании возможностей фреймворка Core NFC iOS и о возможной схеме мошенничества с использованием смартфонов с NFC.

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

Привет, Хабр!


Меня зовут Максим. Промышленной разработкой я занимаюсь с 2005 года. В Кошельке работаю с 2013 года, а с 2015 года помогаю бизнесу компании развивать новые финтех-сервисы в качестве руководителя подразделения.

В Кошельке наша команда запустила немало инновационных продуктов. Это и одна из первых в мире полностью виртуальная банковская карта в смартфоне с возможностью бесконтактной оплаты (за год до запуска Apple Pay в России и задолго до запуска Apple Card), и первая транспортная карта, и первая карта болельщика, и первая кампусная карта в смартфоне.

В прошлом году мы совместно с Mastercard запустили сервис Кошелёк Pay единственный в мире сервис, который, в отличие от аналогов, работает независимо от производителя смартфона или операционной системы. Например, Кошелёк Pay работает на смартфонах Huawei, на которых отсутствуют сервисы Google.

Благодарности


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

Саше Прыймак, который под моим руководством выполнил описанное в статье исследование.

Также большое спасибо за участие и поддержку:
Кате Туркиной, Антону Давыдову, Лёше Ершову, Даше Алексеенко.

Платформа IoT


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

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

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

Пример носимых устройств умные часы, фитнес-браслеты, кольца, брелоки.

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

Умные вещи сейчас мировой тренд. Об этом свидетельствуют собранные различными мировыми агентствами статистические данные (см. ссылки в конце статьи).

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

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

  1. Элемент безопасности, или Secure Element это полноценный компьютер, выполненный в цельном кристалле кремния размером около 5-20 квадратных миллиметров. Он имеет свою операционную систему, систему ввода-вывода, центральный процессор, несколько крипто-процессоров для реализации криптографических операций, оперативную и постоянную память. Элементы безопасности используют при производстве банковских карт, SIM-карт, а также встраивают в смартфоны и другие устройства. Элемент безопасности практически невозможно взломать и получить оттуда данные (отсюда и название).

    Как и на любой компьютер, в элемент безопасности можно установить приложения так называемые апплеты. Мы в нашей статье будем работать с платежным апплетом, благодаря установке и персонализации которого носимое устройство с элементом безопасности и имеет сервис бесконтактной оплаты.
  2. Стандарт GlobalPlatform Card Specification он описывает работу операционной системы элемента безопасности в целом, а также сценарии и протоколы безопасного управления содержимым элемента безопасности.
  3. TSM (Trusted Service Manager) сервис для управления содержимым в элементе безопасности. Он управляет жизненным циклом апплетов и их персонализацией под конкретного пользователя на конкретном элементе безопасности.
  4. Для превращения носимого устройства в платежный инструмент платежными системами применяется технология токенизации по стандарту EMV это процесс получения от платежной системы токена (суррогатного номера), связанного с номером реальной банковской карты. Для каждой банковской карты, в связке с форм-фактором устройства оплаты, токен всегда уникален, что обеспечивает дополнительную безопасность при оплате токеном.

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

Первый сценарий это взаимодействие с активными носимыми устройствами. Активными называют носимые устройства, в которых есть свой элемент питания (например, аккумулятор). Как правило, внутри вещи работает своя операционная система и имеется модуль BLE для связи со смартфоном. Производитель устройства предоставляет SDK и ключи доступа для взаимодействия с элементом безопасности.

Именно так работают все умные часы и фитнес-браслеты с функцией бесконтактной оплаты.Тут всё просто и понятно берем и делаем.

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

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

Этот сценарий мы (условно) разбиваем уже по типу смартфонов:

  1. Любые смартфоны без NFC
  2. Смартфон Android c NFC
  3. iPhone c NFC

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

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

Для второго типа (Android c NFC) реализация понятна. Смартфон в этом случае можно использовать в качестве терминала, запитать пассивное устройство от NFC-антенны и загрузить в него токен банковской карты.

В нашем исследовании я подробно распишу, как мы прорабатывали третий тип смартфонов (iPhone с NFC). В качестве носимых устройств мы использовали брелки от компании ISBC партнера, с которым мы запускаем пилот.

Цель исследования


Можем ли мы дать возможность пользователю Кошелька на платформе iOS загрузить свою банковскую карту в носимое устройство, приложив его к iPhone?

То есть:

  1. Пользователь в приложении приложении Кошелёк вводит данные своей банковской карты
  2. Пользователь прислоняет к задней стенке iPhone носимое устройство
  3. Банковская карта загружается в носимое устройство

Соответственно, техническая задача определить, можно ли во внешний элемент безопасности (Secure Element) загрузить банковскую карту, используя обычный iPhone и его NFC антенну, через протокол ISO/IEC 7816 (T=CL).

Дополнительные задачи:

  1. Получить ATR (Answer To Reset) чипа, не убирая его от считывателя
  2. Получить UID (Unique Identifier) чипа

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

Что имеем:

  • iPhone 8 c iOS 13.5
  • Тестовое носимое устройство брелок ISBC со встроенным чипом JCOP 3 EMV P60 и загруженными апплетами производства NXP:
    PPSE и апплет, реализующий спецификацию M/Chip Advance Card Specification Payment and Data Storage v1.1;
  • ключи от Issuer Security Domain чипа.

Решение


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

  1. Установление соединения чипа с NFC модулем iPhone
  2. Установка на чип апплетов Mastercard M/Chip Advance и PPSE
  3. Персонализация апплетов

Установление соединения


Именно здесь речь пойдет о фичах фреймворка Core NFC, добавленных в iOS 13.
Кстати, в iOS 14 никаких существенных изменений относительно предмета статьи не случилось, поэтому все описанное актуально и для нее.

Итак, в тринадцатой версии яблочной ОС стало возможным не только считывать данные с NFC меток, как это было в iOS 12 (но не раньше iOS 11, до нее взаимодействие по NFC было возможно только в рамках Apple Pay), но и записывать их, а также общаться на языке APDU-команд с любым чипом, который соответствует одному из следующих стандартов:


Для этого в Core NFC были добавлены два новых класса: NFCNDEFReaderSession и NFCTagReaderSession.

Первый используется для взаимодействия с NDEF метками, а второй для всего остального, соответственно.

В нашем случае это чип, поддерживающий спецификацию GlobalPlatform Card Specification 2.2.1 и стандарт ISO/IEC 7816, значит, будем использовать второй класс.

В документации написано, что нужно сделать (помимо написания кода, конечно), чтобы начать общение с чипом по ISO 7816:


Но ниже есть вот такое интересное ограничение:

Important
Core NFC doesn't support payment-related Application IDs.

Как раз его мы и хотим пощупать, узнав, что конкретно оно означает.

Добавляем строку, например Allow NFC connection для ключа NFCReaderUsageDescription в файле info.plist. С любым другим значением этого ключа тоже работает.


[Здесь в колонке слева не сам ключ, а его описание, XCode прячет формальные названия]

Дальше, если мы хотим взаимодействовать с чипом, как с устройством стандарта ISO/IEC 7816, то в значении ключа com.apple.developer.nfc.readersession.iso7816.select-identifiersукажем список ID всех апплетов (Application Identifier или AID), с которым будет взаимодействовать приложение.


Здесь стоит пояснить, что эти идентификаторы не просто случайный набор символов.
Это шестнадцатеричные (hex) строки, содержащие информацию о приложении, которому они присвоены.

AIDы могут быть длиной от 5 до 16 байт (два символа в строке = один байт). Они состоят из двух частей, первая определяет провайдера приложения (для Mastercard это A000000004), вторая говорит, какой именно это продукт данного провайдера (для продукта с именем Mastercard это 1010, а, например, для Maestro это 3060).

Кроме того, иногда в AID требуется поместить дополнительную информацию, например, если на чипе находятся два одинаковых приложения от одного провайдера, но для разных банков. Для этого существует поддержка Long AID (или Extended AID). До тех пор, пока длина AID не превышает 16 байт, в него можно записывать все, что угодно. Например, мы взяли Mastercard AID и в конце дописали к нему TEST, итог: A0000000041010BB5445535401.

Единственный AID, который выбивается из списка 325041592E5359532E444446303101.
На самом деле это обычная (только в hex-формате), что называется, plain-text строка 2PAY.SYS.DDF01. Это AID PPSE, который платежным апплетом, как таковым, не является. Он лишь содержит данные окружения, необходимые платежным приложениям.

Установка апплетов


Для установки апплетов на чип необходимо защищенное соединение (Secure Channel Protocol или SCP); мы сделали это за кадром с помощью обычного PC/SC считывателя и платформы Cardsmobile TSM.

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

Понадобится любая IDE с поддержкой JCOP Shell и эмулятором JavaCard, например, вот эта.

Создаем пустой проект, указываем желаемый AID (например 0000000000) и запускаем.

Дальше разбираемся по шагам:

  1. /card
    Получаем ATR, отправляем SELECT без идентификатора, чтобы был выбран Card Manager;

  2. auth
    Создаем защищенное соединение с эмулятором, иначе ничего установить не получится;

  3. ls (опционально)
    С помощью этой команды можно увидеть, какие приложения установлены на вашем девайсе/эмуляторе;

  4. install [packageAID] [appletAID] [instanceAID]
    Устанавливаем апплет:
    packageAID идентификатор пакета (Module), например, 0000000000
    appletAID идентификатор апплета (Load File), например, 000000000000
    instanceAID идентификатор, который будет присвоен вашему апплету после установки, например, A0000000041010;

  5. ls
    Проверяем, появился ли ваш апплет в списке установленных приложений:


Персонализация апплетов


На самом деле, персонализация апплета очень простая штука; всё, что требуется, это загрузить в него необходимые платежные данные. Для этого нужно выбрать апплет командой SELECT по его AID, установить защищенное соединение и отправить выбранному апплету команды STORE DATA с данными внутри.

Теперь вернемся к списку AIDов в файле info.plist зачем он нужен, и как конкретно Core NFC выбирает, с каким апплетом взаимодействовать?

Выглядит это примерно так:

  1. Программа идет по списку сверху вниз;
  2. Для каждого AID она формирует и отправляет команду SELECT;
  3. AID первого апплета, ответившего 9000 (статус успешного ответа, здесь список всех возможных ответов) записывается в поле initialSelectedAID объекта типа NFCISO7816Tag, который кладется в массив обнаруженных чипов

@available(iOS 13.0, *)public protocol NFCISO7816Tag : NFCNDEFTag, __NFCTag {/*** @property initialSelectedAID The Hex string of the application identifier (DF name) selected by the reader when the tag is discovered.*               This will match one of the entries in the com.apple.developer.nfc.readersession.iso7816.select-identifiers*               in the Info.plist.*/@available(iOS 13.0, *)var initialSelectedAID: String { get }


Дальше из массива можно выбрать любой такой объект, и с помощью метода sendCommand отправлять APDU-команды выбранному апплету.

А теперь поговорим об этом ограничении:

Core NFC doesn't support payment-related Application IDs.

То есть Core NFC не поддерживает платежные AIDы, а именно боевые, с которыми работают платежные терминалы.

Конечно, платежный AID в список info.plist добавить можно, вот только Core NFC его проигнорирует и не будет отправлять для него SELECT (кстати, здесь список всех использующихся AID'ов). Apple таким образом защищают свою технологию Apple Pay, закрывая сторонним разработчикам доступ к любым платежным функциям iPhone (и всему, что с этим связано).

Обходные пути


Первое, что приходит в голову а можно ли добавить в info.plist не AID платежного апплета, а AID Card Managerа (Card Manager это группа сервисов внутри операционной системы чипа, управляющих картой, которые отвечают за администрирование и безопасность), чтобы потом вручную послать ему команду SELECT с AID нужного апплета?

Здесь мы споткнулись о первый подводный камень Core NFC не позволяет отправлять команду SELECT, содержащую AID, который не прописан в info.plist.

Хорошо, добавили A0000000041010, но и тут неудача Core NFC не позволяет отправлять команду SELECT, содержащую платежный AID, вне зависимости от того, есть он в info.plist или нет.

Разберемся, как именно работает ограничение по идентификаторам.

В info.plist мы указали следующие AIDы:
1. A000000001510000 - GlobalPlatform Card Manager AID
2. 325041592E5359532E444446303101 - Proximity Payment System Environment (PPSE)
3. A0000000041010 - Mastercard Credit/Debit (Global)
4. A00000000401 - Mastercard PayPass
5. A00000000410101213 - Mastercard Credit
6. A00000000410101215 - Mastercard Credit
7. A00000000410101214 - Придуманный платежный AID
8. A00000000410101216 - Придуманный платежный AID
9. A0000000041010121F - Придуманный платежный AID
10. A0000000041010BB5445535401 - Придуманный платежный Long AID
11. A0000000041010BB5445535405 - Придуманный платежный Long AID
12. A000000004101FBB5445535401 - Придуманный не платежный AID
13. A000000004101F1213 - Придуманный не платежный AID
14. A00000000F1010 - Придуманный не платежный AID
15. A0000000040F - Придуманный не платежный AID

Мы установили 14 платежных апплетов с разными AID (пп. 2-11 платежные AID-ы), и попробовали отправить Card Manager команды SELECT с каждым из этих AID.

Ответили номера 12-15.

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

Жаль, но этот способ отпадает.

Второй способ персонализации, предусмотренный GlobalPlatform, это команда INSTALL [for personalization].


Она отправляется в Card Manager и содержит AID апплета, который нужно персонализировать.

После этого можно отправлять команды STORE DATA в Card Manager, а он будет пересылать их в целевое приложение.

Но есть одно ограничение. Для того, чтобы апплет поддерживал такой способ персонализации, он должен реализовывать интерфейс org.globalplatform.Application.

Card Manager, на команду INSTALL [for personalization] с Mastercard Credit/Debit (Global) AID, который был присвоен апплету M/Chip Advance от NXP, отвечал ошибкой 6985 (Conditions of use not satisfied),

а значит надо проверить, реализует ли он интерфейс Application.

Для этого мы написали простое приложение-пустышку, реализующее этот интерфейс. Как и ожидалось, на INSTALL [for personalization] оно ответило 9000.

Но когда Application был убран из интерфейсов, реализуемых приложением, оно стало отвечать на эту команду 6985, как и в случае с апплетом M/Chip Advance.

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

Дополнительные задачи


  1. Получение UID чипа
    Это сделать можно, очень простым способом.
    При старте сессии NFC модуль ищет чипы, AID'ы апплетов которых прописаны в info.plist, и складывает их в массив.
    После этого любой из них можно оттуда достать, и если его тип NFCISO7816Tag, то у него есть поле identifier, в котором и находится UID чипа.
    /** * @discussion The hardware UID of the tag.*/var identifier: Data { get }
    

  2. Получение ATR чипа
    А вот ATR, похоже, Core NFC получать не умеет, потому что во фреймворке для этого нет отдельных инструментов, а с помощью APDU-команд ATR получить нельзя.

Выводы


Что же в сухом остатке?

  1. Мы научились работать с чипами стандарта ISO/IEC 7816 (в том числе и получать их UID), используя новые возможности фреймворка Core NFC;
  2. Разобрались с ограничениями, наложенными Apple на свою технологию;
  3. Выяснили, что на данный момент, используя iPhone, персонализировать апплеты с платежными идентификаторами, при этом не реализующие интерфейс Application невозможно;
  4. Взяли на заметку, что с любыми другими AID все работает на ура эти знания пригодятся нам в будущем при работе с нефинансовыми сервисами.

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

В рамках поставленной задачи мы остановились на использовании внешнего BLE считывателя для загрузки токена банковской карты из iPhone в пассивное носимое устройство.

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

Побочный результат исследования


В ходе проведения работ родилась схема потенциального мошенничества, которую нельзя воспроизвести посредством смартфонов Apple,

но вполне можно реализовать через Android-смартфон с поддержкой NFC и технологией Host Card Emulation.

Суть вкратце:

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

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

  1. Первый смартфон прикладывается к любой банковской карте;
  2. Второй прикладывается к платежному терминалу;
  3. Второй смартфон эмулирует банковскую карту, пересылая APDU-команды, посылаемые терминалом, первому смартфону;
  4. Первый смартфон транслирует эти команды приложенной банковской карте, передавая ее ответы второму смартфону;
  5. Второй смартфон передает полученные ответы терминалу;
  6. Платеж произведен.

То есть потенциально мошенник может приложить смартфон к вашему карману и оплатить покупку. Будьте осторожны!

Чтобы не стать жертвой подобной схемы мошенничества, вы можете, например, перенести банковские карты в смартфон и не носить с собой пластик.

Вместо заключения


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

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

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


Если вас заинтересовала тема, описанная в статье, то ниже несколько ссылок для более подробного изучения:
  1. Книга И. М. Голдовского Банковские Микропроцессорные Карты
  2. Концепция EMV Payment Tokenisation
  3. Статьи с анализом рынка IoT:

Подробнее..

Как добавить 3D в iOS приложение c помощью SceneKit

26.11.2020 12:17:06 | Автор: admin


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


Предыстория


Изначально в Кошельке 3D-рендер был написан, как кроссплатформенное решение, и использовал под капотом OpenGL. Данная реализация по-прежнему используется в Android-версии приложения.


Здесь стоит отметить, что в 3D был не только экран карты, но и список карт. Так как в перспективе мы планируем совместить список карт с возможностью показа предложений из каталога, решено было уйти от этого решения. Теперь для списка карт мы используем UICollectionView c кастомным layoutом. Кстати, коллеги из Android-команды в свое время выступали на конференции с докладом по этой теме: Приложение Кошелёк: Как мы оживляем карты.



В 2018 году на WWDC Apple объявила, что в следующих версиях iOS будет прекращена поддержка OpenGL, и рекомендовала всем переходить на Metal. Детальное сравнение двух технологий можно посмотреть в докладе Metal for OpenGL Developers с WWDC. К слову, несмотря на то, что OpenGL уже несколько лет, как deprecated, в iOS 14 он по-прежнему доступен для использования; понятно, что обновлений для него с тех пор нет. Предварительно оценив время и силы, которые необходимо потратить на перенос существующего решения, мы решили использовать новую технологию и попробовать написать MVP. Самым простым решением было использовать какой-нибудь готовый движок для работы с 3D, где будет поддержка как Metal, так и OpenGL. Это бы дало возможности настройки параметров через визуальный редактор, а также упростило работу, связанную с моделями, текстурами, камерой, освещением и анимациями. Но тянуть движок в проект ради прямоугольника с красивой текстурой это слишком. Если взять для сравнения Unity, то это и увеличение размера приложения (судя по документации, минимальный размер приложения составляет 12 МБ, для нас это равно +20% к размеру приложения), и стоимость 200$ в год, и необходимость показывать лого на сплэш-скрине (решается покупкой более дорогой лицензии), и увеличенное время загрузки. А если смотреть в сторону open source решений (LibGDX, urho3d), то поддержки Metal у них нет. И тут нам на помощь приходит нативное решение от Apple SceneKit, лишенное недостатков, описанных выше (отсутствует влияние на размер приложения если сравнивать пустые проекты, то импортирование SceneKit увеличило размер приложения на 1 КБ; бесплатность, визуальный редактор и прочее).


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


SceneKit


SceneKit высокоуровневый фреймворк, работающий поверх Metal или OpenGL, упрощающий работу с 3D-графикой и позволяющий легко добавлять анимации, работу с физикой, частицы и реалистичный рендеринг, основанный на физике. Самым интересным для нас является как раз последний пункт.
SceneKit поддерживает несколько концепций для визуализации материалов это blinn, constant, lambert, phong и physically based. Подробнее про каждую из моделей можно почитать в документации. Но сравнение этих подходов хорошо видно на изображении:


Как видите, physically based тут явный фаворит.


Physically based rendering (PBR) концепция визуализации материалов, основанная на физических принципах. Подробно данная тема раскрыта в докладе Advances in SceneKit Rendering с WWDC. Если кратко, то суть данного подхода в том, что любая поверхность состоит из трёх составляющих: диффузной, металлической и неровности. Попробуем разобраться с каждой из составляющих на примере создания золотой поверхности:


  • диффузная (diffuse) определяет количество света, диффузно отраженного от поверхности. Рассеянный свет одинаково отражается во всех направлениях и поэтому не зависит от точки зрения.
    Так как золото отражает однородный свет по всей поверхности, то наша диффузная текстура будет полностью жёлтой и выглядеть так:
  • металлическая (metalness) определяет, насколько поверхность схожа с металлом. Более низкие значения (тёмные цвета) приводят к тому, что материал больше похож на диэлектрическую поверхность. Более высокие значения (яркие цвета) делают поверхность более металлической, и, соответственно, более зеркальной.
    Так как мы рисуем полностью золотую текстуру без примесей, а золото это металл, то и metalness текстура должна быть полностью белой.
  • неровность (roughness) определяет, насколько поверхность кажется гладкой. Более низкие значения (тёмные цвета) приводят к тому, что материал выглядит блестящим, с четко определёнными зеркальными бликами. Более высокие значения (яркие цвета) заставляют зеркальные блики расширяться, а диффузное свойство материала становится более световозвращающим.
    Золото практически идеально ровный материал, поэтому текстура неровности приближена к чёрному, а для создания дефектов на поверхности добавим более светлые участки:

Сложив вместе три составляющих, мы получим:


Для наглядности связь между metalness и roughness представлена ниже:


Еще один важный момент: данная технология доступна в SceneKit с iOS 10, но на практике выяснилось, что из-за перехода на вторую версию Metal итоговое изображение очень сильно отличается, и в этом случае нам приходится поддерживать две конфигурации. На тот момент минимальной поддерживаемой версией в приложении была iOS 9. А так как даже 3.8% пользователей на версиях с 9 по 10 по количеству для нас равнялись населению небольшого города (55 000), отказаться от них было тяжелым решением. Взвесив все за и против (в первую очередь это упрощение верстки, так как с iOS 11 началась эра экранов iPhone X и safe area, а также снижение нагрузки на тестирование), мы решили поднять минимальную версию до iOS 11.


Реализация


Основой SceneKit является сцена (SCNScene), поэтому первым делом необходимо добавить её. В документации сказано, что сцену и изображения текстур для оптимизации лучше сохранять в каталог с разрешением .scnassets. Мы можем добавить его через File > New > File > раздел Resource > SceneKit Catalog. В дальнейшем все файлы, связанные со SceneKit, будем помещать в него. Для создания сцены у нас есть два простых варианта: либо добавить её программно и из кода добавлять на неё объекты, либо же воспользоваться визуальным редактором, создав сцену через File > New > File > раздел Resource > SceneKit Scene File. Выберем второй вариант, в результате получим сцену с камерой.

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


Следующим шагом будет добавление 3D-модели на сцену. Можно воспользоваться готовыми примитивами:


Но вряд ли это то, что вам нужно. Можно, конечно, попробовать составить объект из примитивов, но текстурирование объектов SceneKit не поддерживает, нет возможности редактировать текстурные координаты, доступен только просмотр. Поэтому предусмотрена поддержка сторонних моделей, выгруженных из 3D-редакторов. Мы использовали Blender. Основным форматом для загрузки моделей в SceneKit является DAE (Digital Asset Exchange или Collada). При загрузке мы можем сразу же конвертировать его в SCN формат, или сделать это позже через Editor > Convert to SceneKit file format (.scn). Также доступна конвертация и в обратную сторону.


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


Есть и более интересный способ: добавить текстуру окружения и сгенерировать источники света автоматически на её основе. В этом случае источник света может быть любой формы. Наша выглядит вот так:


Добавляем текстуру в проект, а дальше в настройке сцены в разделе Background and Lighting используем в качестве Environment. При необходимости можно её же использовать в качестве фона, поместив в Background. Стоит отметить, что текстуры могут быть в HDR, а размеры текстуры должны соответствовать определенному формату. Для сферической текстуры (мы находимся в сфере), как у нас, соотношение ширины к высоте должно быть 2 к 1, для кубической (мы находимся в кубе) соотношение сторон должно быть 6 к 1. Детально про форматы можно почитать в документации, раздел Using Cube Map Textures.


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


Осталось добавить материал. Как было сказано выше, для PBR нам понадобится три варианта текстур: diffuse, metalness, roughness.


Так как мы хотели добиться металлического эффекта отражения на части карты, как от фольги, при отрисовке metalness текстуры эти участки делаем светлыми, чтобы приблизить поверхность к металлу. Основной же фон остается чёрным, так как основная поверхность карты это пластик. Для roughness текстуры, наоборот, основную поверхность делаем светлой, чтобы придать эффект матовости при отражении, а выделенные участки затемняем, чтобы получить зеркальное отражение.
Переходим на вкладку настройки материала в Shading, выбираем Physically Based и устанавливаем текстуры. Одним из основных параметров у текстур является intensity. Свойство изменяется в диапазоне от 0.0 до 1.0 и в зависимости от типа текстуры приводит к разным результатам. Например, при уменьшении значения для текстуры нормалей (к этому вернёмся ниже) мы получаем сглаживание поверхности, для metalness и roughness текстур общее затемнение. Таким образом, в случае необходимости мы можем сгладить общее влияние текстуры на конечный результат.


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


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


Теперь нужно вывести сцену на экран. Для этого используется объект SCNView, наследник UIView. Можем инициализировать его из кода, указав нашу сцену, либо просто добавив его на view у UIViewController через storyboard. Для дебага мы можем выставить свойство allowsCameraControl в true, чтобы иметь возможность управлять камерой сцены, и showStatistic для отображения FPS и количества полигонов на экране.


let sceneView = SCNView(frame: view.bounds, options: nil)sceneView.scene = SCNScene(named: "SceneKitAsset.scnassets/Scene.scn")sceneView.allowsCameraControl = truesceneView.showsStatistics = trueview.addSubview(sceneView)

Собираем проект и получаем итоговый результат:


Apple Watch


Отдельный плюс использования SceneKit это поддержка Apple Watch. Для отображения карты на часах необходимо проделать всего несколько шагов. Добавим нашу сцену и все материалы, связанные с ней, в Target extension Apple Watch.
Для вывода сцены вместо SCNView используется WKInterfaceSCNScene. Добавляем его в Storyboard на наш контроллер и создаем свойство:


@IBOutlet weak var sceneInterface: WKInterfaceSCNScene!

Работа с WKInterfaceSCNScene выглядит почти так же, как и SCNView, но за исключением пары моментов. Не поддерживается автоматическое управление камерой, поэтому для оживления нашей сцены добавляем бесконечную анимацию вращения карты. Также не поддерживаются текстуры окружения в HDR формате, и в этот раз для установки источников света воспользуемся свойством autoenablesDefaultLighting, с помощью которого на сцену автоматически будет добавлен всенаправленный источник света.


guard let scene = SCNScene(named: "SceneKitAsset.scnassets/Scene.scn") else {    return}// Take card and start forever rotation animationif let card = scene.rootNode.childNode(withName: "Card", recursively: true) {    card.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0,                                                              y: 2,                                                              z: 0,                                                              duration: 1)))}

В результате получим:


Проблемы, с которыми мы столкнулись в продакшн версии


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


func grayscaleImage(image: UIImage) -> UIImage? {    guard let ciImage = CIImage(image: image) else {        return nil    }    let grayscale = ciImage.applyingFilter("CIPhotoEffectNoir", parameters: [:])    let black = grayscale.applyingFilter("CIToneCurve", parameters: [        "inputPoint0": CIVector(x: 0.0, y: 0.0),        "inputPoint4": CIVector(x: 1.0, y: 0.9)    ])    return UIImage(ciImage: black)}

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


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


func noise() -> UIImage? {    guard let randomGenerator = CIFilter(name: "CIPhotoEffectMono") else {        return nil    }    randomGenerator.setValue(CIFilter(name: "CIRandomGenerator")?.outputImage,                             forKey: kCIInputImageKey)    randomGenerator.setDefaults()    if let ciImage = randomGenerator.outputImage {        let rect = CGRect(x: Int.random(in: 0...100),                          y: 0,                          width: 1_024,                          height: 1_024)        if let ref = CIContext(options: nil).createCGImage(ciImage, from: rect) {            let image = UIImage(cgImage: ref)            return image        }    }    return nil}

Помимо прочего, у нас была необходимость изменять направление источников света в зависимости от положения телефона. Здесь вроде бы всё просто: посчитали угол наклона устройства через CMMotionManager и повернули источники света. Но текстура окружения не поддерживает поворот, поэтому пришлось и камеру, и карту положить в отдельный объект, и уже его вращать. За счет этого создаётся впечатление смены направления источников света.
На самом деле в продакшне использование текстуры окружения принесло нам больше всего проблем. На версиях ниже iOS 12 сцена отказывалась рендериться с включённой текстурой окружения, а на части устройств (от iPhone 6 до iPhone X) при этом чёткой зависимости не прослеживалось. С выходом iOS 14 мы получили очень долгую загрузку, поэтому на этих версиях программно добавляем источники света. Результат уже, конечно, выглядит не так интересно.
Чтобы не фризить приложение, объекты можно загрузить в бэкграунде с помощью метода
prepareObjects:withCompletionHandler:, но здесь есть один важный момент. Несмотря на вызов completionHandler, так как ещё ни одного кадра не было отрисовано, на мгновение можно увидеть пустую сцену. Чтобы этого избежать, необходимо дождаться отрисовки в делегате SCNView SCNSceneRenderDelegate в методе renderer(_:didRenderScene:atTime:)


Итоги


Как это часто бывает, Apple предоставила разработчикам довольно качественный инструмент для работы с 3D графикой. В результате, приложив минимум усилий, мы получили MVP, на основе которого можно строить продакшн решение.


Плюсы подхода:


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

Минусы подхода:


  • отсутствует кроссплатформенность;
  • сложно что-то оптимизировать, использование Metal дало бы более широкие возможности в этом плане.

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


Кстати, о своих подходах к решению различных задач мы регулярно рассказываем в Telegram-канале.

Подробнее..

Кошелёк запускает конкурс мобильных разработчиков с призовым фондом в 1 000 000 рублей

02.11.2020 12:06:03 | Автор: admin
Полчаса назад мы запустили Кошелёк Mobile Challenge конкурс для iOs и Android разработчиков, в котором можно решить реальный кейс приложения и заработать от 100 до 250 тысяч рублей. Лучших участников мы также пригласим в команду, чтобы вместе запустить наш сервис на европейских рынках в 2021 году.




Мы, Cardsmobile, разработчик приложения Кошелёк, запустили конкурс мобильных разработчиков Кошелёк Mobile Challenge, он будет проходить со 2 ноября по 15 декабря. Приём заявок идёт до 4 декабря, оценка работ членами жюри с 5 по 14 декабря, а оглашение победителей случится уже 15 декабря.

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

Ближе к середине конкурса мы сделаем пост с ревью присланных работ, рассказав про самые любопытные варианты решения задачи. Затем у вас будет ещё две недели, чтобы запрыгнуть в последний вагон конкурса, а уже 15 декабря мы объявим имена троих победителей для каждой платформы, которые поделят между собой денежный призовой фонд. И для iOS, и для Android он будет распределяться так:

1 место 250 000 рублей
2 место 150 000 рублей
3 место 100 000 рублей

Лучших мы будем рады пригласить в команду: в следующем году мы запускаем приложение на нескольких европейских рынках, будет здорово делать это вместе. Мы молоды, но уже достаточно известны; умеем делать то, чего не делал никто до нас и не боимся рисковать. Присоединяйтесь! Объединяться в команды для решения задач тоже можно, но помните, что в этом случае приз придётся делить на всех.

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

Критерии оценки заданий просты: это чистота и расширяемость кода, скорость и отзывчивость интерфейса, стабильность работы и процент поддерживаемых версий ОС и устройств. Конечно, и оригинальность идеи сыграет роль. В основной состав жюри вошли: руководитель клиентской разработки Николай Ашанин, руководитель продуктового направления Константин Степаненко, руководитель Android-разработки Андрей Бусик и операционный директор Филипп Шубин. Помогать с разбором заданий будут лиды разработки iOS и Android, продуктовые менеджеры и аналитики, то есть бОльшая часть техкоманды.

Но и это ещё не всё! Конкурс поддерживают наши друзья из образовательного портала GeekBrains. Каждому участнику ребята подарят скидку 45% на курсы по программированию (промокод придёт на вашу почту после отправки задания), а победителям любой курс на выбор. Заманчиво? Пожалуй.

P.S. Ну а чтобы познакомиться получше и делиться новостями, мы запустили ещё и соцсети команды. Будем показывать, что происходит в нашем офисе в сердце Петроградской стороны и кто работает над продуктом. Инстаграм вот здесь, Вконтакте тут, и да, даже Facebook. Классический вид на питерские крыши и обзор офисного кикера прилагаются.

Спишемся!
И удачи!
Подробнее..

За двумя мобильными сервисами HMS и GMS в одном приложении

05.10.2020 10:14:40 | Автор: admin


Привет, Хабр! Меня зовут Андрей, я делаю приложение Кошелёк для Android. Уже больше полугода мы помогаем пользователям смартфонов Huawei оплачивать покупки банковскими картами бесконтактно через NFC. Для этого нам потребовалось добавить поддержку HMS: Push Kit, Map Kit и Safety Detect. Под катом я расскажу, какие проблемы нам пришлось решать при разработке, почему именно так и что из этого вышло, а также поделюсь тестовым проектом для более быстрого погружения в тему.

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

Вот что удалось выяснить на этапе первоначальной проработки


  • Huawei распространяет AppGallery и HMS без ограничений можно скачать и установить их на устройства других производителей;
  • После того, как мы установили AppGallery на Xiaomi Mi A1, все обновления начали подтягиваться в первую очередь с новой площадки. Сложилось впечатление, что AppGallery успевает обновлять приложения быстрее конкурентов;
  • Сейчас Huawei стремится как можно быстрее наполнить AppGallery приложениями. Чтобы ускорить миграцию на HMS, они решили предоставить разработчикам уже знакомый (похожий на GMS) API;
  • На первых порах, пока экосистема Huawei для разработчиков не заработает на полную мощность, отсутствие Google-сервисов скорее всего будет являться главной проблемой для пользователей новых смартфонов Huawei, и они будут всеми способами пытаться их установить.

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

  • Исключается риск попадания версии, предназначенной для Google Play, на девайсы Huawei и наоборот;
  • Можно внедрить любой алгоритм выбора мобильных сервисов, в том числе с использованием feature toggle;
  • Тестировать одно приложение проще, чем два;
  • Каждый релиз можно выкладывать на все площадки распространения;
  • Не приходится переключаться с написания кода на управление сборкой проекта при разработке/модификации.

Для работы с разными реализациями мобильных сервисов в одной версии приложения необходимо:

  1. Спрятать все обращения за абстракцию, сохранив работу с GMS;
  2. Добавить реализацию для HMS;
  3. Разработать механизм выбора реализации сервисов в рантайме.

Методика внедрения поддержки Push Kit и Safety Detect значительно отличается от Map Kit, поэтому рассмотрим их отдельно.

Поддержка Push Kit и Safety Detect


Как и положено в таких случаях, процесс интеграции начался с изучения документации. В разделе предостережений обнаружились вот такие пункты:
  • If the EMUI version is 10.0 or later on a Huawei device, a token will be returned through the getToken method. If the getToken method fails to be called, HUAWEI Push Kit automatically caches the token request and calls the method again. A token will then be returned through the onNewToken method.
  • If the EMUI version on a Huawei device is earlier than 10.0 and no token is returned using the getToken method, a token will be returned using the onNewToken method.
  • For an app with the automatic initialization capability, the getToken method does not need to be called explicitly to apply for a token. The HMS Core Push SDK will automatically apply for a token and call the onNewToken method to return the token.

Главное, что нужно вынести из этих предостережений существует разница в получении пуш-токена на разных версиях EMUI. После вызова метода getToken(), реальный токен может быть возвращен через вызов метода onNewToken() сервиса. Наши испытания на реальных устройствах показали, что телефоны с EMUI < 10.0 на вызов метода getToken возвращают null или пустую строку, после чего происходит вызов метода onNewToken() сервиса. Телефоны с EMUI >= 10.0 всегда возвращали пуш-токен из метода getToken().

Можно реализовать вот такой источник данных, чтобы привести логику работы к единому виду:
class HmsDataSource(   private val hmsInstanceId: HmsInstanceId,   private val agConnectServicesConfig: AGConnectServicesConfig) {   private val currentPushToken = BehaviorSubject.create<String>()   fun getHmsPushToken(): Single<String> = Maybe       .merge(           getHmsPushTokenFromSingleton(),           currentPushToken.firstElement()       )       .firstOrError()   fun onPushTokenUpdated(token: String): Completable = Completable       .fromCallable { currentPushToken.onNext(token) }   private fun getHmsPushTokenFromSingleton(): Maybe<String> = Maybe       .fromCallable<String> {           val appId = agConnectServicesConfig.getString("client/app_id")           hmsInstanceId.getToken(appId, "HCM").takeIf { it.isNotEmpty() }       }       .onErrorComplete()}

class AppHmsMessagingService : HmsMessageService() {   val onPushTokenUpdated: OnPushTokenUpdated = Di.onPushTokenUpdated   override fun onMessageReceived(remoteMessage: RemoteMessage?) {       super.onMessageReceived(remoteMessage)       Log.d(LOG_TAG, "onMessageReceived remoteMessage=$remoteMessage")   }   override fun onNewToken(token: String?) {       super.onNewToken(token)       Log.d(LOG_TAG, "onNewToken: token=$token")       if (token?.isNotEmpty() == true) {           onPushTokenUpdated(token, MobileServiceType.Huawei)               .subscribe({},{                   Log.e(LOG_TAG, "Error deliver updated token", it)               })       }   }}

Важные замечания:

  • Предложенное решение работает не во всех случаях. При тестировании на физических устройствах проблем выявлено не было, но на пуле устройств, предоставляемых AppGallery для онлайн-дебаггинга, подход не срабатывает. Причём не срабатывает из за того, что вызова метода HmsMessageService.onNewToken() не происходит, что, кажется, не соответствует документации. Причина такого поведения по сей день остаётся для нас невыясненной;
  • Оказалось, что на некоторых устройствах метод HmsMessageService.onMessageReceived() может вызываться на main потоке, поэтому будьте аккуратнее с походами в БД и сеть из него;
  • Как только вы добавите зависимость от библиотеки com.huawei.hms:push, в манифесте проекта после сборки будет объявлен сервис com.huawei.hms.support.api.push.service.HmsMsgService, сконфигурированный для работы в отдельном процессе :pushservice. С этого момента, при порождении каждого процесса, в нём будет создаваться свой экземпляр класса Application. Это принципиально важно осознавать, если вы обращаетесь к файлам или БД или, например, собираете данные о скорости инициализации приложения через Firebase Performance. Мы встретились с порождением второго процесса только на не-Huawei устройствах, куда были установлены AppGallery и HMS.

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


  • Создаём по отдельному источнику данных для каждого типа сервисов;
  • Добавляем по репозиторию для пушей и безопасности, принимающих на вход тип мобильных сервисов и выбирающих конкретный источник данных;
  • Некая сущность бизнес-логики определяет, какой тип мобильных сервисов (из доступных) уместно использовать в конкретном случае.

Разработка механизма выбора реализации сервисов в рантайме


Как действовать, если на устройстве установлен всего один тип сервисов или их нет вовсе, понятно, а вот что делать, если одновременно установлены и Google-, и Huawei-сервисы?

Вот что мы обнаружили и из чего исходили:

  • При внедрении любой новой технологии её нужно использовать в приоритете, если устройство пользователя полностью соответствует всем требованиям;
  • На устройствах с EMUI >= 10.0 алгоритм получения пуш-токена отличается от предыдущих версий;
  • Подавляющее большинство устройств Huawei без Google-сервисов будут иметь версию EMUI 10.0 и выше;
  • На новые устройства Huawei пользователи будут пытаться установить Google-сервисы, чтобы пользоваться всеми привычными приложениями. Надёжного способа сделать это нет, поэтому мы не должны рассчитывать на стабильную и корректную работу Google-сервисов на таких устройствах;
  • Технически пользователи смартфонов других вендоров могут установить себе AppGallery и Huawei-сервисы, но мы предполагаем, что на текущий момент таких пользователей очень мало.

Разработка алгоритма оказалась, наверное, самым выматывающим делом. Здесь в одну точку сошлось множество технических и бизнесовых факторов, но в конечном итоге нам удалось прийти к наилучшему для нашего продукта решению. Сейчас даже немного странно, что описание самой обсуждаемой части алгоритма помещается в одно предложение, но я рад, что в конечном итоге получилось просто:
В случае, если на устройстве установлены оба типа сервисов и удалось определить, что версия EMUI < 10 используем Google, иначе используем Huawei.

Для реализации итогового алгоритма требуется найти способ определить версию EMUI на устройстве пользователя.
Один из способов сделать это прочитать системные свойства:
class EmuiDataSource {    @SuppressLint("PrivateApi")    fun getEmuiApiLevel(): Maybe<Int> = Maybe        .fromCallable<Int> {            val clazz = Class.forName("android.os.SystemProperties")            val get = clazz.getMethod("getInt", String::class.java, Int::class.java)            val currentApiLevel = get.invoke(                    clazz,                    "ro.build.hw_emui_api_level",                    UNKNOWN_API_LEVEL            ) as Int            currentApiLevel.takeIf { it != UNKNOWN_API_LEVEL }        }        .onErrorComplete()    private companion object {        const val UNKNOWN_API_LEVEL = -1    }}

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

Итоговая реализация алгоритма, учитывающая тип операции, для которой выбирается сервис, и определение версии EMUI устройства, может выглядеть так:
sealed class MobileServiceEnvironment(   val mobileServiceType: MobileServiceType) {   abstract val isUpdateRequired: Boolean   data class GoogleMobileServices(       override val isUpdateRequired: Boolean   ) : MobileServiceEnvironment(MobileServiceType.Google)   data class HuaweiMobileServices(       override val isUpdateRequired: Boolean,       val emuiApiLevel: Int?   ) : MobileServiceEnvironment(MobileServiceType.Huawei)}

class SelectMobileServiceType(        private val mobileServicesRepository: MobileServicesRepository) {    operator fun invoke(            case: Case    ): Maybe<MobileServiceType> = mobileServicesRepository            .getAvailableServices()            .map { excludeEnvironmentsByCase(case, it) }            .flatMapMaybe { selectEnvironment(it) }            .map { it.mobileServiceType }    private fun excludeEnvironmentsByCase(            case: Case,            envs: Set<MobileServiceEnvironment>    ): Iterable<MobileServiceEnvironment> = when (case) {        Case.Push, Case.Map -> envs        Case.Security       -> envs.filter { !it.isUpdateRequired }    }    private fun selectEnvironment(            envs: Iterable<MobileServiceEnvironment>    ): Maybe<MobileServiceEnvironment> = Maybe            .fromCallable {                envs.firstOrNull {                    it is HuaweiMobileServices                            && (it.emuiApiLevel == null || it.emuiApiLevel >= 21)                }                        ?: envs.firstOrNull { it is GoogleMobileServices }                        ?: envs.firstOrNull { it is HuaweiMobileServices }            }    enum class Case {        Push, Map, Security    }}

Поддержка Map Kit


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

  1. Определить тип сервисов для отображения карт;
  2. Заинфлейтить соответствующий layout и работать с конкретной реализацией карт.

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

class MapFragment : Fragment(),   OnGeoMapReadyCallback {   override fun onActivityCreated(savedInstanceState: Bundle?) {       super.onActivityCreated(savedInstanceState)       ViewModelProvider(this)[MapViewModel::class.java].apply {           mobileServiceType.observe(viewLifecycleOwner, Observer { result ->               val fragment = when (result.getOrNull()) {                   Google -> GoogleMapFragment.newInstance()                   Huawei -> HuaweiMapFragment.newInstance()                   else -> NoServicesMapFragment.newInstance()               }               replaceFragment(fragment)           })       }   }   override fun onMapReady(geoMap: GeoMap) {       geoMap.uiSettings.isZoomControlsEnabled = true   }}

class GoogleMapFragment : Fragment(),   OnMapReadyCallback {   private var callback: OnGeoMapReadyCallback? = null   override fun onAttach(context: Context) {       super.onAttach(context)       callback = parentFragment as? OnGeoMapReadyCallback   }   override fun onDetach() {       super.onDetach()       callback = null   }   override fun onMapReady(googleMap: GoogleMap?) {       if (googleMap != null) {           val geoMap = geoMapFactory.create(googleMap)           callback?.onMapReady(geoMap)       }   }}

class HuaweiMapFragment : Fragment(),   OnMapReadyCallback {   private var callback: OnGeoMapReadyCallback? = null   override fun onAttach(context: Context) {       super.onAttach(context)       callback = parentFragment as? OnGeoMapReadyCallback   }   override fun onDetach() {       super.onDetach()       callback = null   }   override fun onMapReady(huaweiMap: HuaweiMap?) {       if (huaweiMap != null) {           val geoMap = geoMapFactory.create(huaweiMap)           callback?.onMapReady(geoMap)       }   }}

Теперь можно написать отдельную реализацию для работы с картой для каждого отдельного фрагмента. Если потребуется реализовать одинаковую логику, то можно поступить по знакомому алгоритму подогнать работу с каждым типом карт под один интерфейс и передать одну из реализаций этого интерфейса в родительский фрагмент, как это сделано в MapFragment.onMapReady()

Что из этого вышло


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

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

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

Пользуясь этим методом, мы уже добавили в приложение Analytics Kit, APM, работаем над поддержкой Account Kit и не планируем на этом останавливаться, тем более, что с каждой новой версией HMS становится доступно всё больше возможностей.

Послесловие


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

Пользуясь выходом в публичное пространство, хочу поблагодарить всю команду Кошелька и особенно umpteenthdev, Артёма Кулакова и Егора Аганина за неоценимый вклад в интеграцию HMS в Кошелёк!

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


  • Полный код демонстрационного проекта на GitHub;
  • Скачать AppGallery на телефон любого производителя. Актуальную версию приложения HMS-Core можно загрузить из AppGallery;
  • Push Kit codelab;
  • Map Kit codelab;
  • Safety Detect codelab;
  • Инструкция к сервису онлайн-дебаггинга своих приложений на устройствах Huawei. Возможность использования появляется после регистрации в AppGallery Connect;
  • Ветка приложения Кошелёк на 4PDA.
Подробнее..

Как выбрать тимлида

26.01.2021 20:09:19 | Автор: admin

Будучи разработчиком, я выработал в себе привычку читать доки и мануалы систематически и в большом объеме. Сейчас я руковожу отделомiOSразработки вCardsmobileи практически не пишу код, но привычка осталась.Статей про менеджмент написано не меньше, чем по программированию. И начитавшись публикаций на очередную такую тему, я кое-что понял: зря я не читал их, пока активно кодил. Ведь в моей команде всегда есть как минимум один менеджер и хорошо было бы разбираться в том, что он делает. Хотя бы немного. Ведь если он делает свою работу плохо, то лучше подыскать нового?

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

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

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

6стилей менеджмента

Существует множество классификаций менеджерских стилей. Больше других мне нравится классификация Дениэла Гоулмана,опубликованная вHBRв 2000 году. В ней выделяется 6 стилей:

  • авторитарный,

  • авторитетный,

  • демократический,

  • образцовый,

  • товарищеский,

  • обучающий.

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

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

Хорошие и плохие стили

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

Источник:Герцберг, Harvard Business Review, 2000Источник:Герцберг, Harvard Business Review, 2000

Из графика видно, что самый правильный стиль авторитетный. Однако не спешите делать выводы. На самом деле не всё так однозначно. Во-первых, многие успешные менеджеры были авторитарны. К примеру, 2002 стал очень удачным годом дляTheNewYorkTimes: газета выиграла вв 7 из 13 номинаций Пулитцеровской премии.В том году компанией руководил человек, отличающийся авторитарным стилем Хауэлл Рейнс. Однако он был смещен со своего поста уже в 2003 году.

Более близким нам всем примером авторитарного менеджера является Стив Джобс. Хотя на самом деле исследователине могут четко классифицировать его стиль(и это целый материал для отдельной статьи, доказывающей, что Джобс хороший менеджер), онближе всего к авторитарному. Авторитарный стиль статистически плохой, а значит и Джобс статистически плохой руководитель. Однако только он смог вывестиAppleиз кризиса в конце 90-х и обеспечить попадание в топ-3 самых дорогих компаний в мире в 2011 году.

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

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

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

Образцовый стиль

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

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

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

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

Не каждый способен соблюдать темп образцового руководителяНе каждый способен соблюдать темп образцового руководителя

Мы понимаем все негативные эффекты такого подхода, поэтому не ожидаем от каждого сотрудника, что он будет техлидом. Наша карта компетенций подразумевает очень широкое распределение по уровням: от тех, кто только стал мидлом, до уровней principal, techlead, platform lead.

Обучающий стиль

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

Мы верим в то, что сможем достигать крутых результатов, если будем развивать индивидуальные навыки своих сотрудников. Для этого каждые полгода мы проводим performance review. Оно включает в себя отзывы от коллег, self-review и 1-1 с руководителем для рефлексии. После этого мы составляем план на полгода таким образом, чтобы сотрудник знал, что ему делать для улучшения своих навыков. Каждый пункт этого плана является целью и ставится по фреймворку SMART. Например, если одной из целей является крупный рефакторинг, то мы определяем definition of done этого рефакторинга. Так он станет конкретным и измеримым. Планируем объём работ: реально ли закончить этот рефакторинг за 3-6 месяцев? И думаем о том, какую пользу он принесет компании.

Девиз обучающего стиляДевиз обучающего стиля

Демократический стиль

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

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

Частый ответ на вопрос о странномlegacyрешении в проектеЧастый ответ на вопрос о странномlegacyрешении в проекте

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

Авторитетный стиль

Справиться с этой проблемой помогает авторитетный стиль. Авторитетный лидерон как самый энергичный таксист на районе, который знает все новости города, где выпить самый вкусный кофе, куда поехать в поисках приключений и какой номер телефона у Людочки из бюро находок в аэропорту; самый полезный человек, с которым вы когда-либо могли завести знакомство. Так и авторитетный лидер обладает наиболее полным и четким видением стратегии компании, знает, зачем и что мы делаем, способен замотивировать самого Тони Робинсона и в некоторых случаях дажеШайя ЛаБафа.

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

Именно таким должен быть настоящий лидерИменно таким должен быть настоящий лидер

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

Товарищеский стиль

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

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

Всё это выглядит очень увлекательно до некоторых пор: пока стендапы не превращаются в бесполезный ритуал, похожий на спор продавцовAvonи свидетелей Иеговы:

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

Не переживай, это не твоя вина, только ни в коем случае не трать свободное время на это.

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

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

Девиз товарищеского стиляДевиз товарищеского стиля

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

Авторитарный стиль

Давайте представим себе ужасную ситуацию. Через неделю дедлайн проекта, приуроченного к важной дате. Это может быть праздник (Новый год, 8 марта) или распродажа (Черная пятница или 11.11). Переносить дату релиза нельзя, в этом случае проект окажется никому не нужен. Однако вы построили диаграмму сгорания по проекту, и она четко указывает его окончание: ровно через 3 недели после необходимой даты.

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

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

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

Обучать людей банально нет времени.

Ситуация выглядит патовой. Мы перепробовали всеописанные вышестили, кроме одного. Быть может, воспользоваться мозгами команды? Устроить консилиум. Одна головахорошо, а девятьгидра! Я уверен, что, привлеки я коллективный разум к решению этой головоломки,мы найдем лучший выход из ситуации. И работать никто сверхурочно не будет, и качеством, возможно, не придется жертвовать. Но решение будет найдено слишком поздно. Принять сложное коллективное решениеэто как очередь за колбасой отстоять. Место занимать нужно за неделю, а в процессе, возможно, придется с кем-то подраться. Единственное применение коллективного разума в этой ситуации может быть в стиле мы тут подумали, и я решил. И это совершенно не демократический стиль. Этот стиль называется авторитарным. И для него даже собирать никого не нужно. Если вы уже знаете, как выйти из этой ситуациивыходите. Раздайте всем чёткие инструкции и следите за их исполнением. Не думал, чтокогда-нибудь это скажу, но микроменеджментнаш друг (осталось вырвать эти слова из контекста и меня можно будет недурно дискредитировать за такое).

Не многие знают, но волчара с этого фотооказался волчицей.Не многие знают, но волчара с этого фотооказался волчицей.

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

Алгоритм подбора подходящего руководителя

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

1.Какие из стилей предпочитаете лично вы, как сотрудник?

2.Какой стиль подходит команде, которую вы рассматриваете в данный момент?

3.Какой стиль будет подходить команде через год, два, пять (зависит от того как на долго вы собираетесь с ней остаться)?

4.Какой стиль использует руководитель?

5.Способен ли он менять свой стиль?

О том, как ответить на эти вопросы мы поговорим ниже, а пока разберемся, как интерпретировать ответы:

1.Запишите ответ на первый вопрос, скорее всего у вас выйдет несколько предпочтительных стилей.

2.Стили из вопросов 2-4 должны быть из списка ваших предпочтительных.

3.Ответы на вопросы 2 и 4 должны совпадать.

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

Как понять, какой из стилей предпочтителен для вас

Это самый трудный вопрос, ответ на который вам придется найти самостоятельно. Отведите себе время на рефлексию после прочтения статьи.

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

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

Какой стиль подходит команде

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

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

Какой стиль будет подходить команде черезN-время

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

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

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

Реакция вашего друга на вопрос о стиле его руководителя.Реакция вашего друга на вопрос о стиле его руководителя.

Альтернативным способом получить такую информацию является собеседование. Если вы хотя бы раз проходили собеседование в любую компанию, то наверняка слышали вопросы вроде: Кем вы видите себя через 5 лет?, Назовите ваши слабые стороны и т.д. Очевидно, что все эти вопросы задают не с целью потянуть интервью подольше, они нужны для определения софт-скиллов. На самом деле такими вопросами можно многое узнать о мотивации кандидата, составить его психологический портрет. Такого рода вопросы это мощный инструмент, который можно использовать и в обратную сторону. Не упускайте свою возможность задать вопросы, когда вам дают слово, попросите о дополнительной сессии с будущим начальником, коллегами. Задавайте вопросы, пока не сформируете полное представление о стиле руководства в компании. Конечно, этими техниками нужно уметь пользоваться, и если вы не знакомы с софт-скилловой стороной проведения собеседований, почитайте про интерпретациюфраз-маркеров, фреймворкиSTARиPARLA. Как и в случае с любым другим инструментом, чем больше вы будете практиковаться, тем лучше у вас будет получаться.

Стиль спускается каскадно от топ-руководителей

Есть один важный нюанс. Скорее всего, вы не идете на работу в маленький стартап, где вашим руководителем будет сам основатель. Это значит, что у вашего руководителя будет ещё как минимум один руководитель на уровень выше. Если ваш непосредственный руководитель милейший человек, который всегда за своих орлов, а его руководитель авторитарный деспот с усами, то так или иначе вы будете ощущать авторитарную нотку управления на себе. Если кто-то из директоров вздумает установить в офисе камеры и будет лично следить за тем, кто уходит на 15 минут раньше положенного, то никто вам не поможет. Каким бы подходящим ни был ваш тимлид, он будет не в силах спасти вас от бушующего СТО. Именно поэтому при собеседовании нужно задавать вопросы по фреймворкуSTARилиPARLA, узнавать отзывы бывших сотрудников. Важно не то, что говорят руководители, а то, как они поступают.

Заключение

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

Подробнее..

Как вести технический блог?

04.07.2020 12:05:52 | Автор: admin


Я работаю в коммерческой разработке с 2011 года. С конца 2012 занимаюсь разработкой под iOS. Свою первую техническую статью я написал на Хабре в начале 2017 года про подход к локализации мобильных приложений. Потом выпустил ещё несколько статей по iOS-разработке на Хабре и в конце 2017 года я перешёл в новую компанию и решил вести блог про solution architecture https://medium.com/@nvashanin, где начал описывать общие концепты, обязанности архитектора, его скилл-сет и т.д. К лету 2020 года количество просмотров моих статей перевалило за 800 тысяч, а количество времени, которое люди потратили на прочтение больше 6 млн минут, или около 12 лет чистого времени. Флагманская статья была переведена другими людьми на разные языки: например, польский или испанский.

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

Предыстория


Мне стало интересно развиваться в сторону архитектора программного обеспечения ещё в 2016 году. В начале 2017 года я побывал на первом в своей жизни трёхдневном интенсиве, где матёрый архитектор рассказывал про основные концепции. К концу интенсива мы объединялись в команды, которые должны были взять предложенную проблему, придумать под неё архитектурное решение и защитить перед другими командами. Честно признаться, тогда это вышло у меня плохо.

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

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

Зачем вести технический блог


Я часто слышу вопрос: Зачем мне блог?. Вот лишь несколько причин и они актуальны не только для IT-сферы.

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

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

А ещё, грамотно развивая личный бренд, вы можете стать инфлюенсером то есть специалистом, к чьему мнению прислушиваются другие участники сообщества. Один из ярких примеров кейс ребят из Rambler. В 2016 году у них получилось зарекомендовать VIPER как лучшую архитектуру слоя презентации на iOS и альтернативу более классическим MVC и MVVM среди русского iOS-сообщества. И всё благодаря толковым выступлениям и полезным статьям. Очень много компаний и разработчиков перетащили VIPER себе, некоторые до сих пор её используют.

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

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

Заработать деньги. Большинство заводит блог, чтобы заработать. Это частая причина для трэвел или бьюти сферы, но, что парадоксально, в IT-блогинг редко идут за деньгами. Хотя возможности всё же есть: я купил себе часы на деньги, которые принёс мне блог хотя и не ставил перед собой цели заработать.

О чём писать в блоге


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

Для себя я выделил следующие направления или рубрики:

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

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

Работаете с высоконагруженным back-endом расскажите, как делаете хайлоад и почему именно так. Верстаете UI расскажите про интересную анимацию, которую сделали. Использовали в проекте новую технологию расскажите о своём опыте работы с ней. Ваши подводные камни и инсайды помогут другим не сидеть над проблемой часами.

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

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

Статьи с юмором. Возьмите серьёзную тему и расскажите о ней в легком ключе. Аудитории такое заходит.

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


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

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

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

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

Как написать хорошую статью


Делитесь уникальным контентом. Главное правило хорошего текста: он должен быть уникальным. Никому не интересно читать то, что уже где-то было опубликовано. К тому же, по правилам некоторых площадок в том числе Хабра публиковать неуникальный контент запрещено: это карается не только минусами в карму, но и баном.

Составляйте план. Интересная и понятная статья начинается с плана и структурирования основных мыслей. Набросать хороший скелет статьи мне помогают mind maps. Для этой статьи получился небольшой mind map, но иногда выходят и солидные. Ниже mind map, который я собрал, чтобы рассказать в Твиттере о своём опыте жизни в Нидерландах.

image

Сделайте план настолько подробным, насколько сможете. Постарайтесь выделить все основные идеи, которые хотите раскрыть в статье.

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

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

Придумайте ёмкое название. Нет секретов, которые помогут вам подобрать идеальную формулировку или длину заголовка. Формулируйте заголовок так, чтобы он раскрывал содержание статьи и помогал читателю сделать выбор: читать или не читать. Иногда срабатывают броские и компрометирующие заголовки они цепляют внимание, но важно, чтобы содержание статьи оправдало ожидания читателя. Также есть смысл изучить выдачу поисковиков, чтобы понять, какие темы чаще всего ищут. Всегда подбирайте название после того, как закончили писать содержание статьи так оно получится максимально релевантным.

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

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

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

image

image

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

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

Проверка орфографии, пунктуации и стилистики текста. Советую пользоваться специальными автоматизированными сервисами: Орфограммка для орфографии, пунктуации и стилистики; Advego для стилистической проверки текста; Главредом царём сервисов, который очищает текст от словесного мусора. Для текстов на английском языке я использую платную версию сервиса Grammarly.

Проверка ревьюерами. В тех областях, о которых я пишу от iOS разработки до архитектуры у меня есть несколько хороших ревьюеров специалистов, чьему мнению я доверяю. Они хорошо разбираются в теме и могут дать комментарии по содержанию: в iOS разработчик из VK, мой хороший друг из Санкт-Петербурга, а в архитектуре архитектор из Google в Кремниевой долине. Если у вас на примете нет таких экспертов, покажите статью руководителю или коллегам. Большие компании, в которых процесс производства контента поставлен на поток, сотрудничают с внешними рецензентами.

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

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

Где публиковать статью: выбор площадки


Если у компании есть собственный блог, начать можно с него. Если же нет варианта два: уже существующие площадки или блог на своём домене.

Блог-платформ много: у русскоязычной аудитории популярен Хабр, у англоязычной Medium. Платформ для создания собственного блога также немало. У каждого из подходов есть свои плюсы и минусы.

Популярная платформа


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

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

+ Простой запуск. Работа над статьей идёт по простому сценарию: написание > публикация.

Базовая кастомизация стилистики. Если хочется сложной вёрстки или нестандартных фишек, такой вариант не подойдёт.

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

Зависимость от востребованности площадки. Если сервис теряет популярность, то и ваш блог теряет популярность.

Блог на собственном домене


Плюсы популярных платформ это минусы блогов на своём домене. А минусы платформ плюсы собственных площадок.

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

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

Как продвигать статью


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

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

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

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

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

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

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

image

image

image

image
Скрины сообщений в Linkedin

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

Как монетизировать блог


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

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

Виды монетизации


Есть два направления монетизации.

Прямая. Вы получаете деньги непосредственно за написание статьи.

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

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

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

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

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

Механизмы монетизации


Я протестировал несколько механизмов монетизации. Делюсь их результатами.

Нативная монетизация блог-платформы. Делюсь опытом с Medium. Каждый читатель или автор может купить платный аккаунт за $ 5 и читать все статьи на платформе. Статьям, которые ему нравятся, читатель ставит лайк на Medium это называется хлопок. Деньги, которые читатель заплатил за подписку, распределяются между авторами понравившихся ему статей. Лучшие авторы могут получать неплохие деньги. В конце месяца Medium отправляет всем авторам рассылку, в которой раскрывает топовые цифры по доходам. В мае 2020 года цифры выглядели так: лучший автор получил $ 25000 за месяц со всех своих статей, а за лучшую статью выплатили больше $ 10000. Я получил за одну из статей $ 50 за месяц она была опубликована за 8 месяцев до того, как я протестировал инструмент.

image
Скрин экрана с информацией о том, сколько я заработал на одной из своих статей


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

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

Аффилированные программы есть у многих программных продуктов и магазинов на HubSpot опубликован список доступных программ: от Coursera до Amazon. Я выбрал последний: в каждой моей статье есть пара ссылок на книги по архитектуре. Если кто-то покупает их по моей рекомендации, Amazon перечисляет мне определенный процент. Механика проста: если вы пишете полезный контент для своей аудитории, правильно подобранная аффилированная программа может вас озолотить. Ссылки в моих статьях до сих пор генерируют в среднем от $100 до $150 каждый месяц даже с учётом того, что последнюю статью я написал почти год назад (но я обещаю исправиться и снова начать писать!).

image
Мой доход в аффилированной программе Amazon за октябрь 2019 года

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

Аффилированные программы самый прибыльный и проверенный способ монетизации. Они могут приносить тысячи долларов ежемесячно при условии, что вы пишете постоянно. Кейсы подтверждают: один автор сделал линк-приглашение в водители Uber, когда агрегатор только начинал свою экспансию, и заработал $ 50000, а другой написал статью об Instagram-ботах на Medium и заработал $2000.

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

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

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

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

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

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

Длина статьи, тэги и ещё три лайфхака


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

Лучше доработать статью, чем выпустить сырую. Звучит по-капитански, но иногда мне приходилось дорабатывать уже опубликованную статью. Такой подход рушит ваш авторитет в глазах читателей и приводит к уменьшению трафика. Солженицын в Круге первом описывает правило последних вершков: когда 95% работы закончено, остаётся всего ничего, но эти 5% работы самые сложные с точки зрения мотивации и самые важные с точки зрения удобства читателя. Всегда доделывайте статью до конца, даже если вам и кажется, что и так сойдет иначе зачем это всё?

Оптимальная длина одной статьи 7 минут. Такова статистика Medium. Прочтение статей с самым высоким рейтингом на платформе занимает 7 минут. Конечно, это не универсальное число. На мой взгляд, наилучший тайминг от 6 до 10 минут. В такой статье достаточно контента, чтобы полноценно раскрыть тему, но её не сложно прочитать за один раз. Многое также зависит от площадки: где-то оптимальная длина поста 3 минуты, а где-то возможно, все 30 минут.

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

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

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

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

Вместо выводов, или что дальше


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

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

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

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

Получилась самая большая статья, которую я когда-либо писал. Я очень надеюсь, что раскрыл тему и замотивировал вас начать писать. Это долгий путь, но он того стоит. Write it right now!

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


Grammarly сервис для проверки текстов на английском языке.
Главред сервис для проверки текста на русском языке.
Орфограммка сервис для проверки орфографии, пунктуации и стилистики.
Advego сервис для проверки стилистики.
Пиши, сокращай Максима Ильяхова и Людмилы Сарачевой лучшая книга на русском языке для тех, кто хочет писать понятные и ёмкие тексты.
Текст, который продает товар услугу или бренд Анны Шуст ещё одна книжная находка, которая поможет прокачать навык писательства.
Блог на миллион долларов книга известного эксперта по диджитал-маркетингу Наташи Кортни-Смит.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru