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

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

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

 

 -Статистика

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





Интегрированное проектирование с помощью EMC Capital Projects и AVEVA NET Workhub and Dashboard

Вторник, 21 Июня 2016 г. 20:08 + в цитатник
В одной из прошлых публикаций мы рассказывали о программном продукте EMC Capital Projects, входящем в пакет EMC Documentum EPFM (Engineering, Plant and Facilities Management). Он позволяет следующее: компаниям-проектировщикам — готовить документацию и согласовывать её перед передачей подрядчикам, компаниям-подрядчикам — принимать эту документацию, проверять её комплектность, рассматривать, вносить правки. Но чем сложнее спираль проектирования, то есть чем больше участников вносят в проект изменения и дополнения, тем выше вероятность возникновения ошибок: несовместимости и конфликтов требований к оборудованию с выбранными для проекта моделями, массогабаритной несовместимостью объектов строительства, несоответствия элементов объекта особенностям рельефа, и т.д. Ситуация сильно усложняется, если в работу вовлечено одновременно несколько географически распределённых команд, работающих по множеству проектных дисциплин: требуется управлять изменениями с помощью Digital Enterprise-решений, чтобы не нарушать и не задерживать рабочий процесс. В EMC Capital Projects эта задача решается благодаря использованию технологий AVEVA NET. Что такое AVEVA NET Workhub and Dashboard Это интеграционный комплекс, который помогает пользователям организовывать совместную работу над проектными данными и документами. Информация может поступать из всевозможных источников — таблиц, баз данных, прочих структурированных хранилищ. В основе комплекса лежит идея накопления и систематизации знаний об объекте, с параллельным извлечением метаданных по каждому элементу. AVEVA NET Workhub and Dashboard может работать с данными любого формата, включая: трехмерные модели, данные лазерного сканирования, цифровые фотографии, 2D-чертежи, отсканированные документы, данные из систем ERP и т.д. Вся информация систематизируется и объединяется в едином рабочем пространстве. Специалист может сразу посмотреть все характеристики элемента и все документы, в которых он фигурирует: чертежи, 3D-модели, фотографии и т.д. Экономия времени получается колоссальная, ведь обычно инженеры-проектировщики тратят на поиск информации от 50% до 80% рабочего времени, перебирая залежи документов, таблиц и баз данных. Помимо этого, комплекс содержит инструменты анализа, проверки и контроля технической информации, позволяющие избежать принятия инженерных решений на основании ошибочных исходных данных. Интеграция Capital Projects и AVEVA NET Workhub and Dashboard Итак, AVEVA NET Workhub and Dashboard выполняет роль инструмента объединения и визуализации всей технической информации по проекту, с проверкой данных и автоматическим присвоением документам перекрёстных ссылок. Этот комплекс позволяет также контролировать комплектность документации и проводить проверки по метаданным объектов. Но, помимо этого, для эффективной работы над проектом необходимо точно управлять всеми этапами работ по подготовке документов: организовать процесс приемки и отправки документов, осуществлять маршрутизацию, распределять задачи по сотрудникам, назначать сроки выполнения работ по каждому документу, отслеживать своевременность выполнения, осуществлять рецензирование и проверку документации. Все эти задачи решает Capital Projects: он играет роль транспорта, позволяющего проектировщикам и подрядчикам обмениваться документацией с заказчиком, а заказчику — направлять документацию на рассмотрение правильным специалистам внутри организации. При этом Capital Projects позволяет управлять всеми стадиями процесса подготовки документа — от создания шаблона до утверждения финальной версии. Пример рабочего процесса: При планировании работ по проекту заказчик, зная какие документы он хочет получить от подрядчика, создаёт в Capital Projects заглушки для будущих документов. Эта информация автоматически передается на портал Supplier Exchange, где подрядчику сообщается, что для заглушки требуется наполнение — сам документ, например, DWG-чертёж. Разработчик документации (Supplier) загружает документ. Проводится предварительная проверка (Document control) на наличие ошибок в версиях, проверка правильности наименований и т.д. Затем документ передаётся в Capital Projects, где он оценивается инженером со стороны заказчика. В соответствии с правилами маршрутизации этот документ приходит на рассмотрение инженеру в виде PDF-файла. Допустим, это какой-то чертёж. Чтобы проверить точность ассортимента оборудования, всех параметров и размеров, инженер вынужден самостоятельно найти, открыть и проанализировать множество другой документации, имеющей отношение к представленному на чертеже оборудованию и конструкциям. В зависимости от сложности полученного чертежа ручной поиск и сверка данных может занимать от нескольких часов до нескольких дней. Благодаря интеграции с AVEVA NET Workhub and Dashboard процесс рассмотрения может проходить гораздо легче. Интеграция позволяет открывать и просматривать загруженные в Capital Projects документы, полученные от подрядчика, с помощью специального просмотрщика. Из поступающих чертежей система извлекает все инженерные данные. Каждый элемент распознается по уникальному имени, и вся новая информация дополняет уже имеющиеся метаданные, загруженные ранее. Теперь инженер оперирует не простым PDF-файлом, а полноценным чертежом, по каждому элементу которого можно сразу получить необходимую техническую информацию, связанные документы из любых проектных дисциплин: электрика, вентиляция, теплоснабжение и т.д. Помимо этого, появляется возможность просмотра трёхмерных моделей представленных на чертеже объектов, которые встраиваются в общую модель всего проекта. Система самостоятельно проверяет соответствие данных в чертежах и спецификациях, избавляя инженера от длительных поисков и сверок бумажных документов: По завершении анализа чертежа инженер вносит свои комментарии и отправляет чертёж на доработку. Заключение Интеграция EMC Capital Projects с AVEVA NET Workhub and Dashboard позволяет: Просматривать документы в виде чертежей или трёхмерных моделей, сразу получая всю связанную техническую информацию по каждому элементу. Автоматически обнаруживать несоответствия данных, полученных на разных стадиях проекта от разных дисциплин и специалистов. Многократно ускорить проверку проектной документации и снизить вероятность ошибок проектирования, строительства и дальнейшей эксплуатации.

Метки:  

[Перевод] Философия Channels

Понедельник, 20 Июня 2016 г. 20:38 + в цитатник
Прошло много времени с моего последнего поста о Channels, и вместе с этим много чего случилось — API разработано и стабилизировано, добавился функционал вроде "контроля за переполнением" (backpressure), ситуация с бекендами выглядит гораздо лучше, особенно после того, как слой взаимодействия локальных и удаленных машин стал немного взрослее. С другой стороны, однако, появилось недопонимание и озабоченность относительно направления, в котором развивается Channels; направления, которое этот проект задает для Django и Python. При разработке Channels пришлось касаться и даже бороться с моими собственными переживаниями об это направлении, выбирать правильный набор компромиссов — иногда даже из двух одинаково правильных вариантов. Я не стал публично обсуждать мои обоснования и видение развития для Channels настолько, насколько я бы мог; я надеюсь, этот пост немного прояснит мою точку зрения. Позвольте обозначить определенное множество проблем, которые я пытаюсь решить, объяснить, почему я выбрал тот дизайн архитектуры, который я выбрал, и рассказать о дальнейших шагах развития. Это всё не только о WebSocket-ах У многих людей реакция на Channels двойная: во-первых, почему-то все считают Channels только способом получить поддержку WebSocket в Django (это штука, которая случайно получилась в ходе разработки, а не единственная причина создания проекта — позже объясню подробнее), и во-вторых, потом все говорят, что реализация WebSocket-ов через распределенную систему обработки сообщений это стрельба из пушки по воробьям. Тут они правы. Асинхронные возможности Python становятся всё лучше, и можно достаточно просто запилить WebSocket-сервер за несколько часов с помощью существующих библиотек (например, Autobahn). Возможно Вам придется стандартизировать интерфейс, чтобы Вы могли общаться между этим сервером и остальным вашим проектом, но это не сложно. Это был первый путь, по которому я пошел, и именно так работали ранние версии Channels (тогда они еще назывались django-onair). Однако, по мере разработки и обдумывания, как запускать всё в серьезных масштабах, стала ясной реальная проблема. Понимаете, обработка WebSocket-протокола ИМХО не проблема; проблема — использование этих сокетов в большем проекте. Большинство сценариев использования сокетов событийно-ориентированные: вы посылаете данные в сокет, когда что-то происходит "снаружи", например, сохраняется модель, меняется внешняя система, или получено другое сообщение в другом WebSocket-е. Все эти разные источники событий живут в разных местах развернутого проекта. Если Вы пойдете по протоптанной тропинке запуска кучи серверов, каждый с вебсервером и Python-кодом, Вы быстро осознаете, что необходим способ взаимодействия между серверами. Поддержка WebSocket-ов это одна из задач, а вот отправка сообщения группам сокетам, когда что-то происходит — вот действительно то, ЧТО Вы пишите, когда разрабатываете приложения. Представьте себе чат-сервер большого масштаба, где разные люди залогинены на разных физических машинах. Как Вы будете отправлять входящие сообщения чата остальным собеседникам того же чата, на всех остальных серверах? Где Вы будете отслеживать, кто находится в чате? Что будете делать с протухшими сессиями? А как насчет системы, где вы отправляете уведомления пользователям, когда кто-то просматривает их профиль? Эти просмотры наверняка будут происходить на разных серверах, так как вы получите это событие через сервер, на котором WebSocket пользователя только что отвалился? Это очень сложная проблема, для решения которой и предназначен Channels; это не просто обработка протокола WebSocket, а задача построения сложных приложений вокруг WebSocket-ов. Обработка сообщений в распределенных системах это действительно трудно, и я верю, что это одна из тех задач, которые гораздо больше выигрывают от нескольких общих полированных решений, чем от от тупой инструкции, как заставить работать вместе кучу асинхронного кода. Обнадеживающая асинхронность Одна из вещей, которую делает Channels — это запуск Django в синхронной манере, что позволяет Вам писать все обработчики сообщений таким же образом. Channels просто выполняет этот код в быстром цикле, отбивая Вам охоту делать в этом цикле блокирующие операции. Проблема в том, что все кажется считают, что это единственный способ, которым предполагается писать код для Channels. Это не так: имеется в виду, что Channels делает коммуникацию между синхронными и асинхронными программами проще в целом, предлагая Вам выбор лучшего инструмента для задачи (и я бы поспорил, что для огромного числа простых бизнес-сценариев Вы возможно захотите синхронный код, так как его гораздо проще писать и поддерживать). На самом деле, Channels делают написание полностью асинхронных приложений, общающихся с остальным Python-проектом проще чем когда либо. В конце концов, это всё сервер с WebSocket-интерфейсов Daphne. Хотите асинхронные запросы по URL? Коммуникации с Интернетом Вещей? Исходящие сокеты? Вы можете писать асинхронный код как обычно, а потом, благодаря Channels, оставить остальной код в знакомом синхронном фреймворке и общаться в обе стороны с вашим специализированным кодом. Когда у Вас есть проект, запускающийся поверх Channels, становится проще чем когда либо добавлять больше асинхронного кода в виде отдельного процесса для выполнения какой-нибудь новой задачи, и иметь понятное рабочее решение для взаимодействия с другими асинхронными и синхронными процессами. Большую пользу приносят опыт коммьюнити и документация, статьи об использовании и разобранные примеры других кто уже все это проходил — в конце концов, всё это сделано на единой общей платформе в едином стиле. Больше протоколов Конечно, всё это приводит нас обратно к идее о том, что Channels не только про WebSocket-ы. Это униварсальная кросс-процессная система событий для Django (и, надеюсь, вообще для Python). WebSocket-ы это один из протоколов, поддержка которого указана в Channels, однако уже идет работа над интерфейсными серверами Slack (позволяющими Вам добавить интеграцию чата в серверный кластер), и почтой (что позволит писать обработчиков для входящих сообщений рядом с Вашим HTTP и WebSocket-кодом). Спецификации формата сообщений также позволяют делать альтернативные реализации; так же как существует много WSGI серверов, форматы сообщений позволяют существовать большому числу ASGI-совместимых HTTP и WebSocket-серверов, даже работающих вместе на одной системе. Некоторые протоколы не требуют функционала широковещательных сообщений, как WebSocket-ы, особенно, если в них нет stateful-коннектов, однако хорошая архитектура слоя каналов позволяет маршрутизировать их на тот же сервер. Хотя слой каналов проектировался как кросс-процессный и не зависящий от сети, это не означает, что каждое сообщение должно маршрутизироваться через центральную точку. Структура Channels была разработана так, чтобы позволить отличать сообщения, которые могут быть обработаны локально, от тех, что обязательно должны быть отправлены куда-либо еще. На самом деле, с недавним добавлением RedisLocalChannelLayer в пакет asgi_redis, Вы можете запускать сервера в стандартном режиме мастер-воркер (в оригинале socket-terminator and worker pair), и уровень каналов будет стараться оставлять как можно больше сообщений на одной машине, передавая данные по сети только тогда, когда ему необходимо найти определенный сокет, чтобы отправить что-нибудь другому пользователю, ну или отправить групповое сообщение. Распределенные системы это сложно В своем ядре Channels решает проблему коммуникации и широковещательных сообщений в распределенных системах. Распределенные системы — это область где нет идеальных решений; Вам всегда придется идти на компромиссы. Один из вариантов — логика At-least-once и at-most-once (гарантированная доставка и гарантированное отсутствие дублей). CAP-теорема про распределенные базы данных — это побочный эффект других компромиссов. Channels идет на определенный набор компромиссов, с целью быть лучшим решением для сценариев и протоколов, которые широко используются в Web, особенно для WebSocket-ов. К примеру, потери пакетов и закрытие сокета предпочтительные дублирующимся пакетам; клиент может переподключиться, но запилить распределенную систему дедупликации для действий — это дико сложно, если только Вы не сделаете абсолютно все действия идемпотентными. Я надеюсь написать отдельный пост о том, какие конкретно компромиссы я выбрал и какие были альтернативы; поверьте, каждый из них был выбран по конкретной причине. Channels никогда не будет подходить для любого случая, это недостижимая цель. Вместо этого, предполагается решение примерно для 90% случаев — что-то, что не всегда идеально, но в целом делает то, что Вы хотите; решение, где компромиссы перекрываются огромными преимуществами единой общей платформы и коммьюнити, которое образовывается вокруг проекта. Во многом это как Django, который не может быть идеальным web-фреймворком; просто невозможно решить каждую проблему каждого разработчика, но мы можем решить 90% остальных проблем, которые возникают у разработчиков постоянно, имея при этом стандарты и архитектуру, следствием которых является переиспользование кода и легкость в освоении. ASGI API, на основе которого написан Channels, умышленно сделано очень гибким. Оно специфицирует необходимый для работы минимум, так что Вы можете получить полноценно работающую среду для разных бекендов, оставляя многое на усмотрение конкретному бэкенду, и за счет этого получая большую гибкость в механизме передачи сообщений. По мере роста, Ваши потребности будут меняться и уточняться; в этом поможет абстракция слоя каналов, позволяя расти не выходя за пределы абстракции, будучи гибкой и одновременно сохраняя то же самое базовое API, с которым Вы работали, когда начинали; каналы, группы, отправка и получение. Я не ожидаю появления сайтов из Top-100, которые бы работали с немодифицированным слоем каналов ASGI, аналогично тому, как они бы не работали с дефолтной поставкой Django. По мере роста и уточнения требований, Вам понадобится решение, которое оставляет возможность неспешно и аккуратно заменить его, и моей целью при разработке ASGI было то, что даже когда Вы уберете весь код Channels, Вы останетесь с абстракцией и архитектурой, которая работает с намного более специализированными распределенными системами и событиями. Так же как и ядро Django, ASGI позволяет глубоко модицифировать и заменять части кода по мере роста, и уберется с Вашей дороги, когда больше не будет Вам нужно. В конце концов, это и есть философия Channels — решение, которое предлагается не как панацея, а как общая база для упрощения разработки приложений, которые используют несколько серверов и работают со statful-протоколами вроде WebSockets. Небольшие команды, web-студии, и сайты средних размеров могут использовать его без изменений; большие проекты скорее всего потребуют допиливания бекенда слоя каналов, и, может быть, часть базовых обработчиков, но все еще могут выиграть от знакомства разработчиков с абстракцией и паттернами Channels. Забегая вперед С учетом всего сказанного, какой путь предстоит Channels и ASGI? WebSocket-проекты сами по себе пока еще находятся во младенческом возрасте: очень немногие развернуты в достаточном масштабе; не говоря уж об использовании Channels — поэтому мы должны пройти период взросления не смотря ни на что. Сайты, которые уже используют Channels в продакшне, и отзывы, которые я получаю о нем, были в основном позитивными, так что тут мы на правильном пути ко взрослой жизни. Daphne сама по себе во многом основана на коде Twisted для обработки HTTP, и на Autobahn для WebSocket-ов — две библиотеки с длинной историей стабильности — в то время как ASGI основан на нашем опыте и исследованиях масштабируемых событийных систем в Eventbrite, моих предыдущих экспериментах с распределенной передачей сообщений, исследованиях и примерах из IT, и обсуждениях похожих проблем с коллегами. ASGI настолько же прочный, насколько возможно в ситуации, когда нет успешного open-source решения для примера. Отзывы, которые я получил в процессе обсуждения включения Channels в Django-1.10 были очень ценными (включить в релиз до дедлайна не удалось, но разработка пакета продолжится как внешнее приложение для Django-1.10). Некоторые последние изменения и разработки, такие как "backpressure" и "local-and-remote Redis backend", основаны как раз на отзывах к предложению, и я предполагаю, что добавится еще больше улучшений, по мере того как всё больше проектов на Channels выйдут в продакшн. Вместе с тем, я считаю, что фундаментальная абстракция архитектуры, "распределенная передача сообещний", является достаночно прочным и грамотным API для построения Django-приложений будущего, с потребностями и сложностью Web-приложений, гораздо большими, чем простая обработка запросов-ответов. Это место где Django и Python имеют возможность показать пример в архитектуре и эксплуатации такого типа приложений. Я также заинтересован в получении статуса PEP для форматов сообщений и интерфейса ASGI стандарта, но до этого я собираюсь попробовать другие web-фреймворки, чтобы убедиться, что этот стандарт действительно работает независимо от фреймворка, как всегда и предполагалось. Так же необходимо испытать и исправить потенциальные проблемы в реальных сценариях использования. Я не уверен, какое будущее уготовано Channels — в идеальном мире этот проект позволил бы Django и Python стать решением для гораздо большего класса проблем, чем те, для которых они используются сейчас; превнося лучшие качества языка, фреймворка и коммьюнити растущей аудитории разработчиков, сталкивающихся с написанием больших систем со stateful-протоколами. Также возможно, что проект останется слоем управления WebSocket-ами для Django, но даже для этой цели важно, чтобы его архитектура стала идеальной. Я надеюсь, что этот пост осветил некоторый контекст и планы на Channels; отзывы сообщества очень важны для развития проекта, и если Channels Вам помог, или у Вас есть еще вопросы, оставайтесь на связи и дайте мне знать. Важно, чтобы каждый понимал и реализацию, и контекст решаемой проблемы — одно без другого ничего не значит. И я надеюсь, что в перспективе мы будем иметь ясное представление, какое значение они имеют вместе.
Шахматы льда и пламени

Метки:  

Особенности кэширования компонентов в Unity3D

Воскресенье, 19 Июня 2016 г. 23:03 + в цитатник
Большинство unity-разработчиков знают, что не стоит злоупотреблять дорогими для производительности операциями, такими как, например, получение компонентов. Для этого стоит использовать кэширование. Но и для такой простой оптимизации можно найти несколько различных подходов. В этой статье будут рассмотрены разные варианты кэширования, их неочевидные особенности и производительность. Стоит отметить, что говорить мы будем в основном о “внутреннем” кэшировании, то есть получении тех компонентов, которые есть на текущем объекте для его внутренних нужд. Для начала откажемся от прямого назначения зависимостей в инспекторе — это неудобно в использовании, засоряет настройки скрипта и может привести к битым ссылкам при вылете редактора. Поэтому будем использовать GetComponent(). Базовые знания о компонентах и простой пример кэшированияВ Unity3D каждый объект на игровой сцене — это контейнер (GameObject) для различных компонентов (Component), которые могут быть как встроенными в движок (Transform, AudioSource и т.д.), так и пользовательскими скриптами (MonoBehaviour). Компонент может быть назначен напрямую в редакторе, а для получения компонента из контейнера в скрипте используется метод GetComponent(). Если компонент требуется использовать не единожды, традиционный подход — объявить в скрипте, где он будет использоваться, переменную для него, взять нужный компонент один раз и в дальнейшем использовать полученное значение. Пример: public class Example : MonoBehaviour { Rigidbody _rigidbody; void Start () { _rigidbody = GetComponent<Rigidbody>(); } void Update () { _rigidbody.AddForce(Vector3.up * Time.deltaTime); } } Кэширование при инициализации актуально также и для свойств, предоставляемых GameObject по умолчанию, таких как .transform, .render и других. Для доступа к ним явное кэширование все равно будет быстрее (да и большая часть из них в Unity 5 помечена как deprecated, так что хорошим тоном будет отказаться от их использования). Стоит отметить, что самый очевидный метод кэширования (прямое получение компонентов) изначально является наиболее производительным в общем случае и другие варианты лишь его используют и предоставляют более простой доступ к компонентам, избавляя от необходимости писать однотипный код каждый раз либо получать компоненты по запросу. Немного о том, что кэширование - не главноеТакже отмечу, что есть и намного более дорогие операции (такие как создание и удаление объектов на сцене) и кэшировать компоненты, не уделяя внимания им, будет пустой тратой времени. Например, в вашей игре есть пулемет, который стреляет пулями, каждая из которых является отдельным объектом (что само по себе неправильно, но это же сферический пример). Вы создаете объект, кэшируете в нем Collider, ParticleSystem и еще кучу всего, но пуля улетает в небо и убивается через 3 секунды, а эти компоненты не используются вообще. Для того, чтобы этого избежать, используйте пул объектов, об этом есть статьи на Хабре (1, 2) и существуют готовые решения. В таком случае вы не будете постоянно создавать и удалять объекты и раз за разом кэшировать их, они будут переиспользоваться, а кэширование произойдет лишь единожды. Производительность всех рассмотренных вариантов кэширования будет отображена в сводной диаграмме. Основы У метода GetComponent есть два варианта использования: шаблонный GetComponent() и обычный GetComponent(type), требующий дополнительного приведения (comp as T). В сводной диаграмме по производительности будут рассмотрены оба этих варианта, но стоит учесть, что шаблонный метод проще в применении. Также существует вариант получения списка компонентов GetComponents с аналогичными вариантами, они также будут проверены. В диаграммах время выполнения GetComponent на каждой платформе принято за 100% для нивелирования особенностей оборудования, а также есть интерактивные версии для большего удобства. Использование свойств Для кэширования можно использовать свойства. Плюс этого метода — кэширование произойдет только тогда, когда мы обратимся к свойству, и его не будет тогда, когда это свойство используется. Минус заключается в том, что в данном случае мы пишем больше однотипного кода. Самый простой вариант: Transform _transform = null; public Transform CachedTransform { get { if( !_transform ) { _transform = GetComponent<Transform>(); } return _transform; } } Этот вариант благодаря проверке на отсутствие компонента обладает проблемами с производительностью. !component, что это?Здесь нужно учитывать, что в Unity3D используется кастомный оператор сравнения, поэтому когда мы безопасно проверяем, закэшировался ли компонент ( if ( !component )), на самом деле движок обращается в native-код, что является ресурсозатратным, более подробно можно прочитать в этой статье. Есть два варианта решения этой проблемы: Использовать дополнительный флаг, указывающий, производилось ли кэширование: Transform _transform = null; bool _transformCached = false; public Transform CachedTransform { get { if( !_transformCached ) { _transformCached = true; _transform = GetComponent<Transform>(); } return _transform; } } Явно приводить компонент к object: Transform _transform = null; public Transform CachedTransform { get { if( (object)_transform > Но нужно учитывать, что этот вариант безопасен только в том случае, когда компоненты объекта не удаляются (что обычно происходит нечасто). Почему в Unity можно обратиться к уничтоженному объекту?Небольшая выдержка из статьи по ссылке выше: When you get a c# object of type “GameObject”, it contains almost nothing. this is because Unity is a C/C++ engine. All the actual information about this GameObject (its name, the list of components it has, its HideFlags, etc) lives in the c++ side. The only thing that the c# object has is a pointer to the native object. We call these c# objects “wrapper objects”. The lifetime of these c++ objects like GameObject and everything else that derives from UnityEngine.Object is explicitly managed. These objects get destroyed when you load a new scene. Or when you call Object.Destroy(myObject); on them. Lifetime of c# objects gets managed the c# way, with a garbage collector. This means that it’s possible to have a c# wrapper object that still exists, that wraps a c++ object that has already been destroyed. If you compare this object to null, our custom > Проблема здесь в том, что приведение к object хоть и позволяет обойти дорогой вызов native-кода, но при этом лишает нас кастомного оператора проверки существования объекта. Его C# обертка все еще может существовать, когда на самом деле объект уже уничтожен. Наследование Для упрощения задачи можно наследовать свои классы от компонента, кэширующего самые используемые свойства, но этот вариант неуниверсален (требует создания и модификации всех необходимых свойств) и не позволяет наследоваться от других компонентов, если это потребуется (в C# нет множественного наследования). Первая проблема может быть решена использованием шаблонов: public class InnerCache : MonoBehaviour { Dictionary<Type, Component> cache = new Dictionary<Type, Component>(); public T Get<T>() where T : Component { var type = typeof(T); Component item = null; if (!cache.TryGetValue(type, out item)) { item = GetComponent<T>(); cache.Add(type, item); } return item as T; } } Вторую проблему можно обойти, создав отдельный компонент для кэширования и используя в своих скриптах ссылку на него. Статическое кэширование Есть вариант использования такой особенности C#, как расширение. Она позволяет добавлять свои методы в уже существующие классы без их модификации и наследования. Это делается следующим образом: public static class ExternalCache { static Dictionary<GameObject, TestComponent> test = new Dictionary<GameObject, TestComponent>(); public static TestComponent GetCachedTestComponent(this GameObject owner) { TestComponent item = null; if (!test.TryGetValue(owner, out item)) { item = owner.GetComponent<TestComponent>(); test.Add(owner, item); } return item; } } После этого в любом скрипте можно получить этот компонент: gameObject.GetCachedTestComponent(); Но этот вариант снова требует задания всех необходимых компонентов заранее. Можно решить это с помощью шаблонов: public static class ExternalCache { static Dictionary<GameObject, Dictionary<Type, Component>> cache = new Dictionary<GameObject, Dictionary<Type, Component>>(); public static T GetCachedComponent<T>(this GameObject owner) where T : Component { var type = typeof(T); Dictionary<Type, Component> container = null; if (!cache.TryGetValue(owner, out container)) { container = new Dictionary<Type, Component>(); cache.Add(owner, container); } Component item = null; if (!container.TryGetValue(type, out item)) { item = owner.GetComponent<T>(); container.Add(type, item); } return item as T; } } Минус этих вариантов — нужно следить за мертвыми ссылками. Если не очищать кэш (например, при загрузке сцены), то его объем будет только расти и засорять память ссылками на уже уничтоженные объекты. Сравнение производительности Интерактивный вариант Как мы видим, серебряной пули не нашлось и самый оптимальный вариант кэширования — получать компоненты напрямую при инициализации. Обходные пути не оптимальны, за исключением свойств, которые требуют написания дополнительного кода. Использование атрибутов Атрибуты позволяют добавлять мета-информацию для элементов кода, таких как, например, члены класса. Сами по себе атрибуты не выполняются, их необходимо использовать при помощи рефлексии, которая является достаточно дорогой операцией. Мы можем объявить свой собственный атрибут для кэширования: [AttributeUsage(AttributeTargets.Field)] public class CachedAttribute : Attribute { } И использовать его для полей своих классов: [Cached] public TestComponent Test; Но пока что это нам ничего не даст, данная информация никак не используется. Наследование Мы можем создать свой класс, который будет получать члены класса с данным атрибутом и явно получать их при инициализации: public class AttributeCacheInherit : MonoBehaviour { protected virtual void Awake () { CacheAll(); } void CacheAll() { var type = GetType(); CacheFields(GetFieldsToCache(type)); } List<FieldInfo> GetFieldsToCache(Type type) { var fields = new List<FieldInfo>(); foreach (var field in type.GetFields()) { foreach (var a in field.GetCustomAttributes(false)) { if (a is CachedAttribute) { fields.Add(field); } } } return fields; } void CacheFields(List<FieldInfo> fields) { var iter = fields.GetEnumerator(); while (iter.MoveNext()) { var type = iter.Current.FieldType; iter.Current.SetValue(this, GetComponent(type)); } } } Если мы создадим наследника этого компонента, то сможем помечать его члены атрибутом [Cached], тем самым не заботясь о их явном кэшировании. Но проблема с производительностью и необходимость наследования нивелирует удобство данного метода. Статический кэш типов Список членов класса не меняется при выполнении кода, поэтому мы можем получить его один раз, сохранить его для данного типа и использовать в дальнейшем, почти не прибегая к дорогой рефлексии. Для этого нам потребуется статический класс, хранящий результаты анализа типов. Кэширование типовpublic static class CacheHelper { static Dictionary<Type, List<FieldInfo>> cachedTypes = new Dictionary<Type, List<FieldInfo>>(); public static void CacheAll(MonoBehaviour instance, bool internalCache = true) { var type = instance.GetType(); if ( internalCache ) { List<FieldInfo> fields = null; if ( !cachedTypes.TryGetValue(type, out fields) ) { fields = GetFieldsToCache(type); cachedTypes[type] = fields; } CacheFields(instance, fields); } else { CacheFields(instance, GetFieldsToCache(type)); } } static List<FieldInfo> GetFieldsToCache(Type type) { var fields = new List<FieldInfo>(); foreach ( var field in type.GetFields() ) { foreach ( var a in field.GetCustomAttributes(false) ) { if ( a is CachedAttribute ) { fields.Add(field); } } } return fields; } static void CacheFields(MonoBehaviour instance, List<FieldInfo> fields) { var iter = fields.GetEnumerator(); while(iter.MoveNext()) { var type = iter.Current.FieldType; iter.Current.SetValue(instance, instance.GetComponent(type)); } } } И теперь для кэширования в каком-либо скрипте мы используем обращение к нему: void Awake() { CacheHelper.CacheAll(this); } После этого все члены класса, помеченные [Cached] будут получены с помощью GetComponent. Эффективность кэширования с помощью аттрибутов Сравним производительность для вариантов с 1 или 5 кэшируемыми компонентами: Интерактивный вариант Интерактивный вариант Как можно видеть, этот метод уступает по производительности прямому получению компонентов (разрыв немного снижается с ростом их количества), но имеет ряд особенностей: Критичное снижение производительности происходит только при инициализации первого экземпляра класса Инициализация последующих экземпляров этого класса происходит значительно быстрее, но не так быстро, как прямое кэширование Производительность получения компонентов после инициализации идентична получению члена класса и выше, чем у GetComponent и различных вариантов со свойствами Но при этом инициализируются все члены класса, независимо от того, будут ли они использоваться в дальнейшем Шаг назад или использование редактора Уже когда я заканчивал эту статью, мне подсказали одно интересное решение для кэширования. Так ли необходимо в нашем случае сохранять компоненты именно в запущенном состоянии приложения? Вовсе нет, мы это делаем только единожды для каждого экземпляра, соответственно функционально это ничем не отличается от назначения их в редакторе до запуска приложения. А все, что можно сделать в редакторе, можно автоматизировать. Так появилась идея кэшировать зависимости скриптов с помощью отдельной опции в меню, которая подготавливает экземпляры на сцене к дальнейшему использованию. Последняя на сегодня простыня кодаusing UnityEngine; using UnityEditor; using System; using System.Reflection; using System.Collections; using System.Collections.Generic; namespace UnityCache { public static class PreCacheEditor { public static bool WriteToLog = true; [MenuItem("UnityCache/PreCache")] public static void PreCache() { var items = GameObject.FindObjectsOfType<MonoBehaviour>(); foreach(var item in items) { if(PreCacheAll(item)) { EditorUtility.SetDirty(item); if(WriteToLog) { Debug.LogFormat("PreCached: {0} [{1}]", item.name, item.GetType()); } } } } static bool PreCacheAll(MonoBehaviour instance) { var type = instance.GetType(); return CacheFields(instance, GetFieldsToCache(type)); } static List<FieldInfo> GetFieldsToCache(Type type) { var fields = new List<FieldInfo>(); foreach (var field in type.GetFields()) { foreach (var a in field.GetCustomAttributes(false)) { if (a is PreCachedAttribute) { fields.Add(field); } } } return fields; } static bool CacheFields(MonoBehaviour instance, List<FieldInfo> fields) { bool cached = false; UnityEditor.SerializedObject serObj = null; var iter = fields.GetEnumerator(); while (iter.MoveNext()) { if(serObj > У этого метода есть свои особенности: Он не требует ресурсов на явную инициализацию Объекты подготавливаются явно (перекомпиляции кода недостаточно) Объекты на время подготовки должны быть на сцене Подготовка не затрагивает префабы в проекте (если не сохранить их со сцены явно) и объекты на других сценах Вполне возможно, что текущие ограничения в дальнейшем можно будет устранить. Бонус для дочитавшихОсобенности получения отсутствующих компонентов Интересной особенностью оказалось то, что попытка получить отсутствующий компонент занимает больше времени, чем получение существующего. При этом в редакторе наблюдается заметная аномалия, которая и навела на мысль проверить это поведение. Так что никогда не полагайтесь на результаты профилирования в редакторе. Интерактивный вариант Заключение В данной статье вы увидели оценку различных методов кэширования компонентов, а также узнали об одном из полезных применений атрибутов. Методы, основанные на рефлексии, в принципе, могут применяться при создании проектов на Unity3D, если учитывать его особенности. Один из них позволяет писать меньше однотипного кода, но чуть менее производителен, чем решение “в лоб”. Второй на данный момент требует чуть больше внимания, но не влияет на итоговую производительность. Проект с исходниками скриптов для теста и proof-of-concept кэша с помощью атрибутов доступны на GitHub (отдельный пакет с итоговой версией здесь). Возможно, у вас найдутся предложения по улучшению. Спасибо за внимание, надеюсь на полезные комментарии. Наверняка этот вопрос рассматривался многими и вам есть что сказать по этому поводу. UPDATE В последней доступной версии (0.32) добавлены 2 новые фичи: Отдельный класс для кэширующего свойства () При использовании режима «в редакторе» перед сборкой сцены будет проведено кэширование нужных компонентов и выведено предупреждение, если что-то не было закэшировано заранее с помощью пункта меню (к сожалению, предложить сохранить сцену в OnPostProcessScene нельзя).

Метки:  

Валидация Fuel-плагинов в рамках Mirantis Unlocked validation program. Оно вам надо?

Суббота, 18 Июня 2016 г. 18:37 + в цитатник
Авторы: Евгения Шумахер, Илья Стечкин Всем привет. Да, если вы любите деньги, то оно вам надо. Дальше мы расскажем, что такое “валидация плагинов” и почему это полезно для бизнеса. Если у вас нет бизнес-интересов, а программирование — способ самовыражения, то дальше можете не читать. Плагины как часть архитектуры Fuel А если решили читать, то давайте сперва вспомним, что такое Fuel и Fuel-плагины (см. блог-посты от августа и октября 2015 года). Опросы пользователей показывают, что Fuel — очень популярный инструмент развертывания OpenStack. Возможность создавать плагины появилась во Fuel, начиная с версии 6.1. Нужно было позволить пользователям произвольно расширять функциональность этого инструмента развертывания облака. Раньше это можно было делать исключительно в ходе работы над кодом самого проекта. Внедрение плагинов позволило пользователям кастомизировать Fuel в обход сложной процедуры внесения изменений в “ядро” проекта. Впрочем, и сегодня у пользователей остается возможность внести лепту в развитие Fuel напрямую. Но это другая история, не связанная с темой нашей статьи. Расширять функциональность Fuel с помощью плагинов проще, потому что в этом случае не надо проходить через миллион циклов аппрува блюпринтов (определение терминов см. в Словаре контрибутора) и т.п. Дело в том, что плагин сам является проектом, которым управляете вы сами ( вот, например, как это делает Mellanox). Термины, которых нет в словаре: Fuel plugin framework — архитектурный термин, который говорит о том, что продукт создан таким образом, что его функционал можно расширять с помощью плагинов. Fuel plugin SDK (Software Development Kit) — набор инструментов, практик, документов и обучающих материалов, которые помогают разработчику создавать плагины для Fuel. Текущая версия доступна здесь, хотя в скором времени переедет сюда, см. коммит). В основе этого набора инструментов лежат правила, выведенные на основании анализа лучших практик разработки и внедрения плагинов. Для валидации плагина необходимо следовать этим правилам. Особенностью OpenStack является обязанность разработчика следовать правилам комьюнити. Если какая-то инициатива предложена без соблюдения соответствующих процедур, принятых в сообществе (например, заведение блюпринта) — она гарантировано не будет принята. В случае с плагинами для Fuel у разработчиков есть больше свободы. Необходимость строго следовать “best practices” возникает только в том случае, если предполагается валидация плагина. Например, необходимо следить за соблюдением норм, разработанных для свободных лицензий (преимущественно Apache 2.0): Fuel — открытый проект. А значит официальные плагины для Fuel тоже должны быть открытыми. Однако, для собственных нужд можно разрабатывать и использовать невалидированные плагины. Драйверлог — это каталог плагинов для OpenStack компонентов, который управляется сообществом разработчиков. Почему вам все-таки интересно провалидировать плагин В валидации есть бизнес-аспект. Если вы официально получите подтверждение того, что ваш плагин работает с MOS (Mirantis OpenStack), то он будет опубликован не только в Драйверлоге (что крайне рекомендуется, уже более 30 плагинов для Fuel официально известны сообществу, хотя на самом деле их гораздо больше, просто мы о них не знаем), но еще и на сайте Mirantis, для того, чтобы об этом знали клиенты нашей компании и могли использовать ваш продукт в своих решениях. В этом случае Mirantis выступает своего рода “кор-ревьюером”. Мы говорим: “Вот правила написания плагинов”. Если вы по какой-то причине решили не следовать этим правилам — это ваше право. До тех пор, пока вы не решили пройти процедуру валидации. Но если вы идете на валидацию, то мы читаем ваш код и можем поставить как +1, так и -1. То есть мы можем рекомендовать доработки к коду. Проверяем документацию (для валидации обязательно задокументировать плагин определенным образом — см. SDK). Кроме того, вы должны предоставить нам свой CI, включая сценарии тестирования и его результаты. Мы обязательно проводим собственные тесты по вашим сценариям. И их результаты должны совпасть с теми, которые предоставили вы. Варианты MOS с провалидированными плагинами могут быть взяты на поддержку саппортом Mirantis совместно с разработчиком плагина (т.е. Мирантис ответит на звонок клиента, но если проблема в функциональности, которую предоставил Fuel плагин, то заявка на поддержку будет передана разработчику плагина). Ограничения Валидация не означает, что мы гарантируем, что плагин подойдет для всех клиентов и всех инсталляций MOS. Разработчик плагина определяет конкретный юзкейс (вариант использования), а также описывает ограничения в работе плагина. Мирантис же проверяет корректность работы плагина в рамках конкретного юзкейса (варианта использования). Всегда есть риск того, что плагин не подойдет тому или иному клиенту. Плагин написан, задокументирован, успешно прошел валидацию, специалисты нашей компании или клиент заинтересованы в использовании плагина, но… В процессе работы выясняется, что у клиента очень специфические требования к деплойменту. И плагин не подходит. Таким образом, валидация не гарантирует того, что плагин подойдет всем клиентам. Может возникнуть необходимость дописать или переписать плагин. В таких случаях команда внедрения связывается с командой поддержки плагина и просит произвести необходимые доработки кода, чтобы расширить функциональность плагина. Важно помнить, что ни один плагин не является частью дистрибутива MOS. Последний не включает в себя ни один плагин. Плагины нужно скачивать отдельно. Это набор puppet манифестов, запакетированный в формате rpm, который позволяет поставить пакеты в определенном порядке. Эта часть должна быть открытой. При этом пакеты, которые ставятся плагином, могут быть проприетарными и являться объектом продажи. Например, Juniper Contrail. Для того, чтобы использовать данный плагин, необходимо купить у Juniper право на использование Juniper Contrail пакетов, указав при покупке количество нод и конфигурацию. Только после того, как клиент разместит купленные пакеты по конкретному адресу, MOS (с помощью плагина) получит возможность обращаться к ним. Этапы процесса валидации Валидация — процесс сложный, состоящий из нескольких этапов: 1. Предоставление спецификации (“спеки”), которая описывает дизайн плагина. 2. Ревью кода. Конечно, на практике мы не выставляем “оценки” валидируемому плагину, но даем свои комментарии. За разработчиком остается право игнорировать наши замечания. Но возможна ситуация, при которой процесс валидации не будет завершен до тех пор, пока изменения не будет внесены. 3. Ревью документации. Разработчики должны предоставить следующий пакет документов: — Руководство пользователя (user guide) — это самый главный документ, содержащий информацию о том, как и для чего пользоваться плагином, какие есть ограничения на его использование, какие “лайфхаки” пригодятся пользователю и с какими “подводными камнями” он может встретиться. — Тест-план. — Тест-репорт. Последние два документа позволяют нам проверить полноту покрытия юзкейса тестами. Для всех упомянутых выше документов разработаны шаблоны. Они являются частью Fuel Plugin SDK. Что касается тестов, то мы выделяем два типа: общие/обязательные и специфические. Мы рассчитываем, что партнеры будут добавлять уникальные тестовые сценарии в свой тест-план. Кроме того партнеры должны построить свой CI и разработать автоматические тесты для упрощения процедуры сборки и тестирования плагинов. Тест-репорт, в свою очередь, позволяет нам сравнить наши результаты тестирования с тем, что получилось у вас. Для того, чтобы провести это сравнение, мы полностью повторяем путь “безымянного пользователя” — просим прислать плагин в виде пакета и на своей лабе (или лабе партнера) последовательно осуществляем все шаги из представленного разработчиком руководства пользователя. После этого берем тест-план и выборочно проходимся по тестам. Таким образом мы получаем представление о том, насколько правдивы результаты тест-репорта. Обычно эта процедура позволяет отловить большое количество багов. Пока еще ни разу не было случая, чтобы у нас не возникло замечаний. Иногда это рутинные правки, но встречаются и критические ошибки, которые необходимо исправить до публикации. Некоторые партнеры считают нас слишком строгими. Но мы убеждены в том, что сила Mirantis — в надежности предлагаемых решений. 4. Мы просим партнеров провести для нас демонстрацию плагина в режиме реального времени. Это позволяет а) всем заинтересованным подразделениям Mirantis познакомиться с плагином и б) наглядно демонстрирует работоспособность предлагаемого решения. По окончании процесса валидации мы публикуем плагин в нашем партнерском каталоге. Разработчик также может самостоятельно опубликовать его в драйверлоге. Заключение Со стороны процесс кажется сложным, но для тех, кто знаком с тем, как работает комьюнити, тут нет ничего нового. Наша команда, отвечающая за работу с партнерами, помогает всем разработчикам плагинов добиться успеха. Мы не только развиваем SDK, но и всегда готовы ответить на вопросы. Для связи с нами используйте IRC-чат на freenode.net (общий канал Fuel называется #fuel, канал разработчиков Fuel — #fuel-dev, каналы для разработчиков основных компонентов Fuel: #fuel-library, #fuel-python, #fuel-ui, #fuel-qa, #fuel-infra. Gerrit-уведомления для всех репозиториев Fuel доступны здесь: #fuel-tracker), также вы можете использовать адрес почтовой рассылки unlocked-tech@mirantis.com. Более подробная информация здесь.

Метки:  

[Перевод] Декораторы и рефлексия в TypeScript: от новичка до эксперта (ч.3)

Пятница, 17 Июня 2016 г. 20:39 + в цитатник
Эта статья — третья часть серии: Часть 1: Декораторы методов Часть 2: Декораторы свойств и классов Часть 3: Декораторы параметров и фабрика декораторов Часть 4: Сериализация типов и metadata reflection API В прошлый раз мы узнали, что такое декораторы и как они реализованы в TypeScript. Мы знаем, как работать с декораторами классов, свойств и методов. В этой статье мы расскажем про: Последний оставшийся тип декораторов — декоратор параметра Реализацию фабрики декораторов Реализацию конфигурируемых декораторов Мы будем использовать нижеследующий класс для демонстрации данных концепций: class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(something : string) : string { return this.name + " " + this.surname + " says: " + something; } } Декораторы параметров Как мы уже знаем, сигнатура декоратора параметра выглядит следующим образом: declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void; Использование декоратора под названием logParameter будет выглядеть так: class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } } При компиляции в JavaScript здесь вызывается метод __decorate (о нем мы говорили в первой части). Object.defineProperty(Person.prototype, "saySomething", __decorate([ __param(0, logParameter) ], Person.prototype, "saySomething", Object.getOwnPropertyDescriptor(Person.prototype, "saySomething"))); return Person; По аналогии с предыдущими типами декораторов, мы можем предположить, что раз вызывается метод Object.defineProperty, метод saySomething будет заменен результатом вызова функции __decorate (как в декораторе метода). Это предположение неверно. Если внимательно посмотреть на код выше, можно заметить, что там есть новая функция __param. Она была сгенерирована компилятором TypeScript и выглядит следующим образом: var __param = this.__param || function (index, decorator) { // return a decorator function (wrapper) return function (target, key) { // apply decorator (return is ignored) decorator(target, key, index); } }; Функция __param возвращает декоратор, который оборачивает декоратор, переданный на вход (с именем decorator). Можно заметить, что когда декоратор параметра вызывается, его значение игнорируется. Это значит, что при вызове функции __decorate, результат ее выполнения не переопределит метод saySomething. Поэтому декораторы параметров ничего не возвращают. Оборачивание декоратора в __param используется, чтобы сохранить индекс (позицию декорируемого параметра в списке аргументов) в замыкании. class foo { // foo index > Теперь мы знаем, что декоратор параметра принимает 3 аргумента: Прототип декорируемого класса Имя метода, содержащего декорируемый параметр Индекс декорируемого параметра Давайте реализуем logProperty function logParameter(target: any, key : string, index : number) { var metadataKey = `log_${key}_parameters`; if (Array.isArray(target[metadataKey])) { target[metadataKey].push(index); } else { target[metadataKey] = [index]; } } Декоратор параметра, описанный выше, добавляет новое свойство (metadataKey) в прототип класса. Это свойство — массив, содержащий индексы декорируемых параметров. Мы можем считать это свойство метаданными. Предполагается, что декоратор параметра не используется для модификации поведения конструктора, метода или свойства. Декораторы параметров должны использоваться только для создания различных метаданных. Как только метаданные созданы, мы можем использовать другой декоратор для их чтения. К примеру, ниже приведена модифицированная версия декоратора метода из второй части статьи. Исходная версия выводила в консоль название метода и все ее аргументы при вызове. Новая версия читает метаданные, и на их основе выводит только те аргументы, которые помечены соответствующим декоратором параметра. class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } } function logMethod(target: Function, key: string, descriptor: any) { var originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { var metadataKey = `__log_${key}_parameters`; var indices = target[metadataKey]; if (Array.isArray(indices)) { for (var i = 0; i < args.length; i++) { if (indices.indexOf(i) > В следующей части мы узнаем лучший способ работы с метаданными: Metadata Reflection API. Вот небольшой пример того, что мы изучим: function logParameter(target: any, key: string, index: number) { var indices = Reflect.getMetadata(`log_${key}_parameters`, target, key) || []; indices.push(index); Reflect.defineMetadata(`log_${key}_parameters`, indices, target, key); } Фабрика декораторов Официальный proposal декораторов в TypeScript дает следующее определение фабрики декораторов: Фабрика декораторов — это функция, которая может принимать любое количество аргументов и возвращает декоратор одного из типов. Мы уже научились реализовывать и использовать все типы декораторов (класса, метода, свойства и параметра), но кое-что мы можем улучшить. Допустим, у нас есть такой фрагмент кода: @logClass class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } } Он работает, как полагается, но было бы лучше, если бы можно было везде использовать один и тот же декоратор, не заботясь о его типе, как в этом примере: @log class Person { @log public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @log public saySomething(@log something : string) : string { return this.name + " " + this.surname + " says: " + something; } } Добиться этого мы можем, обернув декораторы в фабрику. Фабрика может определить тип необходимого декоратора по аргументам, переданным в нее: function log(...args : any[]) { switch(args.length) { case 1: return logClass.apply(this, args); case 2: return logProperty.apply(this, args); case 3: if(typeof args[2] > Конфигурируемые декораторы Последний момент, который бы хотелось обсудить в этой статье, это то, как мы можем передавать аргументы в декоратор при его использовании. @logClassWithArgs({ when : { name : "remo"} }) class Person { public name: string; // ... } Мы можем воспользоваться фабрикой декораторов для создания конфигурируемых декораторов: function logClassWithArgs(filter: Object) { return (target: Object) => { // реализация декоратора класса будет тут, декоратор // будет иметь доступ к параметрам декоратора (filter), // потому что они хранятся в замыкании } } Ту же идею мы можем применить для остальных типов декораторов. Заключение Теперь у нас есть глубокое понимание всех четырех существующих видов декораторов, того как создавать фабрики декораторов и как их параметризовать. В следующей статье мы узнаем, как использовать Metadata Reflection API.

Метки:  

[Перевод] Компания Intuit ускорила сайт почти в 5 раз и увеличила конверсию на 20%

Пятница, 17 Июня 2016 г. 19:08 + в цитатник
Это второй кейс из серии «Как правильно ускорить сайт, и что это дает». Полный список кейсов мы публиковали здесь. В этом кейсе поучительно, как команда разработки договаривалась с другими подразделениями бизнеса, чтобы реально ускорить сайт. Компания Intuit производит программное обеспечение для бизнеса в области финансов и бухгалтерии. Первая версия веб-сайта компании была выпущена в 1996 году, затем постоянно модифицировалась. Менялись разработчики и стандарты Web, сайт обрастал разными «заплатками» и дополнительным кодом. В 2012 году сайт компании стал загружаться ужасно долго — 15 секунд. Команде разработчиков была поставлена задача снизить на 50% время загрузки 50 топовых страниц на 6 разных сайтах компании. Типичная веб-страница представляла собой следующее: Общий объем: 1,5-2 МБ Изображения: 50-70+ штук, объемом порядка 1,2 МБ Внешние CSS/JS: 30-40+ Объем Javascript: более 400 КБ 30х-редиректы: более 20 HTTP-запросы: более 120 Начинаем с нуля CSS/JS Одно из базовых правил ускорения загрузки сайта: «минимизируйте http-запросы». Это оказалось не так просто сделать, поскольку на сайтах использовалось множество изображений и CSS/JS файлов. Многие файлы использовались и на других сайтах компании, некоторые стили были прописаны внутри страниц и не выделены в файлы. Присутствовали глобальные переменные и функции Javascript. Команда провела глобальную чистку CSS/JS, оптимизировала проходы по дереву документа DOM, создала глобальный файл стилей и скриптов, который использовался на всех сайтах. Были созданы несколько таких же файлов, которые использовались только на определенных сайтах. В итоге запросы, связанные с CSS/JS, сократились в 10 раз (с 30-40 до 3-4). Работа с изображениями Основной частью работы было объединение изображений (спрайты), чтобы снизить количество http-запросов. В дизайне использовались изображения с прозрачностью (24-битовые PNG-файлы). Например, указанный ниже спрайт имел размер 306 КБ. Команда поработала с дизайнерами, убедила их отказаться от прозрачности в изображениях и перейти к использованию формата JPG. Это позволило сэкономить 250 КБ на таком спрайте (экономия более чем в 6 раз). Однако не всегда техника спрайтов помогала. На примере ниже показано сборное изображение для страницы с галереей скриншотов. Каждый скриншот – размером 102х768. Общий размер изображения – 5 МБ. Использовать спрайты нужно обдуманно! CDN Сайты компании уже использовали Akamai. Кое-где были проблемы с конфигурацией, файлы загружали иногда с CDN, а иногда со своих серверов. Все сайты использовали одинаковый CDN-домен images.smallbusiness.intuit.com. Файлы cookie передавались при каждом http-запросе к этому домену. Средний размер cookie: 0,8-1 КБ. Умножаем на более чем 100 запросов на странице, получаем, что порядка 100 КБ трафика тратилось только на cookie. Проблему решили переконфигурированием Akamai. Все статические файлы стали загружаться с CDN, перешли на использование CDN-домена без cookie. Тэги отслеживания На сайте использовалось 20-30 различных тэгов (пикселей) отслеживания. Команда сайта поработала с маркетологами, все тэги отслеживания были проверены на необходимость их наличия, от многих отказались. Те тэги, которые остались, были заменены их последней актуальной версией, чтобы улучшить производительность. В итоге из 20-30 их количество удалось сократить до 8-10. Другие виды оптимизации Все изображения, не используемые в спрайтах, проверили на оптимальную компрессию. Изображения, располагающиеся ниже первой видимой части страницы, стали загружаться позже, асинхронно. Перестали использовать все нестандартные шрифты. Удалили дублирующийся код. Удалили 30х-редиректы. Итоговая длительность всех работ по оптимизации составила 6 месяцев. Результат: оптимизированные страницы стали загружаться за 6 секунд вместо 15. Лучше, чем просто хорошо Затем у руководства компании стали возникать вопросы: можно ли еще больше ускорить загрузку страниц. Перед командой оптимизаторов встала необычная проблема, поскольку основные шаги оптимизации они уже проделали на предыдущем этапе. Команда провела анализ всех других возможных узких мест и определила следующие источники проблем с загрузкой: Программное обеспечение, ответственное за А/В-тестирование. Проблемный проигрыватель видео. Медленная шапка сайта. Javascript: очевидная возможность его дальнейшей оптимизации. Программное обеспечение А/В-тестирования Как видно на указанной выше картинке, на странице существовала череда блокирующих вызовов: каждый следующий ждет завершения предыдущего и без этого не стартует. Всякий такой вызов шел к серверу, ответственному за тестирование, а сервер уже определял, должен ли данный пользователь «попадать» в определенный А/В-тест, если да – то ему в определенном месте показывался другой контент. Многие пользователи при этом не попадали ни в какой тест. Данные запросы в сумме давали порядка 750 миллисекунд. То есть 3/4 секунды (из 6-ти), чтобы в большинстве случаев ничего не сделать для пользователя. Другая проблема, связанная с А/В-тестами, заключалась в их логике: Всем пользователям загружался контент по умолчанию Если пользователь попадал в категорию тех, на ком проводится А/В-тест, ему также загружался и тестовый контент, который он видел на экране вместо контента по умолчанию. Сама архитектура А/В-тестов была порочной. Контент на странице был организован следующим образом: контейнер div с именем класса, за которым располагался код Javascript, который обращался к серверу тестирования за нужным контентом. Если сервер тестирования определял, что этот пользователь участвует в А/В-тесте, и присылал другой контент, то код искал в DOM нужный div и вставлял в него измененный контент. Каждый такой код должен был пройти весь DOM, найти предстоящий перед ним div и вставить в него другой контент. Что в итоге было сделано: Удален код, связанный с уже закончившимися тестами Закомментированы неактивные пока тесты Перешли на другое ПО для проведения А/В-тестирования Проигрыватель видео Проигрыватель видео, как оказалось, не только проигрывает видео, но еще и добавляет проблем. Вот как выглядит загрузка видеопроигрывателя на странице: SWF-файл (сам проигрыватель) загружается за 6-8 секунд Много внешних вызовов к разным аналитическим сервисам: 23 запроса, 9 доменов, 7 SWF-файлов Статичная картинка, которую проигрыватель показывает, пока пользователь не нажал на проигрывание, занимает более 3 секунд загрузки Пока статичная картинка не загрузится, страница блокируется и другие элементы не загружаются Решение: заменили 3 разных проигрывателя видео на страницах на один, другого вендора. Рекомендация: если по каким-то причинам вы не можете перейти на другой проигрыватель видео, хотя бы используйте технику «lazyload». Общая шапка и меню Проблемы были очень существенны, если учесть, что шапка загружалась на ВСЕХ страницах всех сайтов: 2 обращения к А/В-тестированию Спрайты вместо CSS Javascript в событиях «mouseover» Сотни отслеживаний событий (event listeners) Более 1100 элементов DOM Команда создала специальную пустую страницу, где загружалась только шапка с меню: эта страница грузилась 5 секунд. В итоге команда полностью переписала навигацию с использованием стандартов HTML/CSS/JS, минимизировала использование изображений и проходов по DOM, использовала делегирование событий. В результате, каждая страница стала загружаться на 1-1,5 секунды быстрее. Использование библиотеки Control.js Эта библиотека написана Стивом Сандерсом, и помогает разработчикам контролировать, как Javascript загружается и выполняется. Когда скрипт загружается через обычный тэг <script>. Команда использовала Control.js на некоторых страницах, но не на всех сайтах. Оказалось, что непросто управлять кодом, который отслеживал событие onload. Другая причина – не всем разработчикам известна эта техника, и возникли сложности с написанием ТЗ для сторонних разработчиков. Предзагрузка ресурсов Предзагрузка использовалась на страницах, связанных с конверсиями различных типов, когда заранее известно, какая будет следующая страница, куда перейдет пользователь. Пока пользователь бездействует, либо вводит свои данные в форму, ресурсы следующей страницы загружаются в фоне. Когда пользователь попадает на следующую страницу, ее ресурсы уже находятся в кэше, что позволяет показать страницу очень быстро. Команда также оптимизировала ряд других моментов: переписали много Javascript-кода, увеличили использование «lazy load», где-то изменили инфраструктуру и перешли на более быстрый стек. Итоговые результаты В феврале 2012 года типичная страница «весила» 1,2 МБ и загружалась 15 секунд. По окончании процесса оптимизации в апреле 2013 года типичная страница «весила» 400 КБ и загружалась за 3,6 секунды. Визуально страницы практически не отличались. Влияние на бизнес Команда отмечает, что достаточно тяжело измерить влияние всего этого процесса, потому что он длился более года. За это время компания внедряла множество других изменений: в предложениях, тарифных планах, продуктах. Однако в конкретных изолированных периодах времени, когда единственным изменением было изменение скорости загрузки, регистрировались улучшения конверсии. Например, на 14 неделе 2012 года, когда единственным изменением было ускорение загрузки страницы с 9-12 секунд до 5-6 секунд, конверсии выросли на 14%. Как скорость влияет на конверсию Влияние на конверсию команда свела к следующей итоговой формуле. Каждое уменьшение времени загрузки страницы на 1 секунду дает следующее влияние: Если страница загружается за 7 секунд или более: +3% за каждую секунду ускорения Если страница загружается от 5 до 7 секунд: +2% за каждую секунду Если страница загружается менее чем за 5 секунд: +1%. Примечание: важно помнить, что это результаты 2013 года. С тех пор и 5 секунд загрузки страницы стали достаточно долгим временем.
[Перевод] Руководство по работе с Redux

Метки:  

[Перевод] Руководство по работе с Redux

Пятница, 17 Июня 2016 г. 07:23 + в цитатник
Сегодня Redux — это одно из наиболее интересных явлений мира JavaScript. Он выделяется из сотни библиотек и фреймворков тем, что грамотно решает множество разных вопросов путем введения простой и предсказуемой модели состояний, уклоне на функциональное программирование и неизменяемые данные, предоставления компактного API. Что ещё нужно для счастья? Redux — библиотека очень маленькая, и выучить её API не сложно. Но у многих людей происходит своеобразный разрыв шаблона — небольшое количество компонентов и добровольные ограничения чистых функций и неизменяемых данных могут показаться неоправданным принуждением. Каким именно образом работать в таких условиях? В этом руководстве мы рассмотрим создание с нуля full-stack приложения с использованием Redux и Immutable-js. Применив подход TDD, пройдём все этапы конструирования Node+Redux бэкенда и React+Redux фронтенда приложения. Помимо этого мы будем использовать такие инструменты, как ES6, Babel, Socket.io, Webpack и Mocha. Набор весьма любопытный, и вы мигом его освоите! Содержание статьи 1. Что вам понадобится 2. Приложение 3. Архитектура 4. Серверное приложение 4.1. Разработка дерева состояний приложения 4.2. Настройка проекта 4.3. Знакомство с неизменяемыми данными 4.4. Реализация логики приложения с помощью чистых функций 4.4.1. Загрузка записей 4.4.2. Запуск голосования 4.4.3. Голосование 4.4.4. Переход к следующей паре 4.4.5. Завершение голосования 4.5. Использование Actions и Reducers 4.6. Привкус Reducer-композиции 4.7. Использование Redux Store 4.8. Настройка сервера Socket.io 4.9. Трансляция состояния из Redux Listener 4.10. Получение Redux Remote Actions 5. Клиентское приложение 5.1. Настройка клиентского проекта 5.1.1. Поддержка модульного тестирования 5.2. React и react-hot-loader 5.3. Создание интерфейса для экрана голосования 5.4. Неизменяемые данные и pure rendering 5.5. Создание интерфейса для экрана результатов и обработка роутинга 5.6. Использование клиентского Redux-Store 5.7. Передача входных данных из Redux в React 5.8. Настройка клиента Socket.io 5.9. Получение actions от сервера 5.10. Передача actions от React-компонентов 5.11. Отправка actions на сервер с помощью Redux Middleware 6. Упражнения 1. Что вам понадобится Данное руководство будет наиболее полезным для разработчиков, которые уже умеют писать JavaScript-приложения. Как уже упоминалось, мы будем использовать Node, ES6, React, Webpack и Babel, и если вы хотя бы немного знакомы с этими инструментами, никаких проблем с продвижением не будет. Даже если не знакомы, вы сможете понять основы по пути. В качестве хорошего пособия по разработке веб-приложений с помощью React, Webpack и ES6, можно посоветовать SurviveJS. Что касается инструментов, то вам понадобится Node с NPM и ваш любимый текстовый редактор. 2. Приложение Мы будем делать приложение для «живых» голосований на вечеринках, конференциях, встречах и прочих собраниях. Идея заключается в том, что пользователю будет предлагаться коллекция позиций для голосования: фильмы, песни, языки программирования, цитаты с Horse JS, и так далее. Приложение будет располагать элементы парами, чтобы каждый мог проголосовать за своего фаворита. В результате серии голосований останется один элемент — победитель. Пример голосования за лучший фильм Дэнни Бойла: Приложение будет иметь два разных пользовательских интерфейса: Интерфейс для голосования можно будет использовать на любом устройстве, где запускается веб-браузер. Интерфейс результатов голосования может быть выведен на проектор или какой-то большой экран. Результаты голосования будут обновляться в реальном времени. 3. Архитектура Структурно система будет состоять из двух приложений: Браузерное приложение на React, предоставляющее оба пользовательских интерфейса. Серверное приложение на Node, содержащее логику голосования. Взаимодействие между приложениями будет осуществляться с помощью WebSockets. Redux поможет нам организовать код клиентской и серверной частей. А для хранения состояний будем применять структуры Immutable. Несмотря на большое сходство клиента и сервера — к примеру, оба будут использовать Redux, — это не универсальное/изоморфное приложение, и приложения не будут совместно использовать какой-либо код. Скорее это можно охарактеризовать как распределённую систему из двух приложений, взаимодействующих друг с другом с помощью передачи сообщений. 4. Серверное приложение Сначала напишем Node-приложение, а затем — React. Это позволит нам не отвлекаться от реализации базовой логики приложения, прежде чем мы перейдём к интерфейсу. Поскольку мы создаём серверное приложение, будем знакомиться с Redux и Immutable и узнаем, как будет устроено построенное на них приложение. Обычно Redux ассоциируется с React-проектами, но его применение вовсе ими не ограничивается. В частности, мы узнаем, насколько Redux может быть полезен и в других контекстах! По ходу чтения этого руководства я рекомендую вам писать приложение с нуля, но можете скачать исходники с GitHub. 4.1. Разработка дерева состояний приложения Создание приложения с помощью Redux зачастую начинается с продумывания структуры данных состояния приложения (application state). С её помощью описывается, что происходит в приложении в каждый момент времени. Состояние (state) есть у любого фреймворка и архитектуры. В приложениях на базе Ember и Backbone состояние хранится в моделях (Models). В приложениях на базе Angular состояние чаще всего хранится в фабриках (Factories) и сервисах (Services). В большинстве Flux-приложений состояние является хранилищем (Stores). А как это сделано в Redux? Главное его отличие в том, что все состояния приложения хранятся в единственной древовидной структуре. Таким образом все, что необходимо знать о состоянии приложения, содержится в одной структуре данных из ассоциативных (map) и обычных массивов. Как вы вскоре увидите, у этого решения есть немало последствий. Одним из важнейших является то, что вы можете отделить состояние приложения от его поведения. Состояние — это чистые данные. Оно не содержит никаких методов или функций, и оно не упрятано внутрь других объектов. Всё находится в одном месте. Это может показаться ограничением, особенно если у вас есть опыт объектно-ориентированного программирования. Но на самом деле это проявление большей свободы, поскольку вы можете сконцентрироваться на одних лишь данных. Очень многое логически вытечет из проектирования состояний приложения если вы уделите этому достаточно времени. Я не хочу сказать, что вам всегда нужно сначала полностью разрабатывать дерево состояний, а затем создавать остальные компоненты приложения. Обычно это делают параллельно. Но мне кажется, что полезнее сначала в общих чертах представить себе, как должно выглядеть дерево в разных ситуациях, прежде чем приступать к написанию кода. Давайте представим, каким может быть дерево состояний для нашего приложения голосований. Цель приложения — иметь возможность голосовать внутри пар объектов (фильмы, музыкальные группы). В качестве начального состояния приложения целесообразно сделать просто коллекцию из позиций, которые будут участвовать в голосовании. Назовём эту коллекцию entries (записи): После начала голосования нужно как-то отделить позиции, которые участвуют в голосовании в данный момент. В состоянии может быть сущность vote, содержащая пару позиций, из которых пользователь должен выбрать одну. Естественно, эта пара должна быть извлечена из коллекции entries: Также нам нужно вести учёт результатов голосования. Это можно делать с помощью другой структуры внутри vote: По завершении текущего голосования проигравшая запись выкидывается, а победившая возвращается обратно в entries и помещается в конец списка. Позднее она снова будет участвовать в голосовании. Затем из списка берётся следующая пара: Эти состояния циклически сменяют друг друга до тех пор, пока в коллекции есть записи. В конце останется только одна запись, которая объявляется победителем, а голосование завершается: Схема кажется вполне разумной, начнём её реализовывать. Есть много разных способов разработки состояний под эти требования, возможно, этот вариант и не оптимальный. Но это не особенно важно. Начальная схема должна быть просто хорошей для старта. Главное, что у нас есть понимание того, как должно работать наше приложение. И это ещё до того, как мы перешли к написанию кода! 4.2. Настройка проекта Пришло время засучить рукава. Для начала нужно создать папку проекта, а затем инициализировать его в качестве NPM-проекта: mkdir voting-server cd voting-server npm init -y В созданной папке пока что лежит одинокий файл package.json. Писать код мы будем в спецификации ES6. Хотя Node начиная с версии 4.0.0 поддерживает много возможностей ES6, необходимые нам модули все же остались за бортом. Поэтому нам нужно добавить в наш проект Babel, чтобы мы могли воспользоваться всей мощью ES6 и транспилировать код в ES5: npm install --save-dev babel-core babel-cli babel-preset-es2015 Также нам понадобятся библиотеки для написания unit тестов: npm install --save-dev mocha chai В качестве фреймворка для тестирования будем использовать Mocha. Внутри тестов будем использовать Chai в роли библиотеки для проверки ожидаемого поведения и состояний. Запускать тесты мы будем с помощью команды mocha: ./node_modules/mocha/bin/mocha --compilers js:babel-core/register --recursive После этого Mocha будет рекурсивно искать все тесты проекта и запускать их. Для транспилинга ES6-кода перед его запуском будет использоваться Babel. Для удобства можно хранить эту команду в package.json: package.json "scripts": { "test": "mocha --compilers js:babel-core/register --recursive" }, Теперь нам нужно включить в Babel поддержку ES6/ES2015. Для этого активируем уже установленный нами пакет babel-preset-es2015. Далее просто добавим в package.json секцию "babel": package.json "babel": { "presets": ["es2015"] } Теперь с помощью команды npm мы можем запускать наши тесты: npm run test Команда test:watch может использоваться для запуска процесса, отслеживающего изменения в нашем коде и запускающего тесты после каждого изменения: package.json "scripts": { "test": "mocha --compilers js:babel-core/register --recursive", "test:watch": "npm run test -- --watch" }, Разработанная в Facebook библиотека Immutable предоставляет нам ряд полезных структур данных. Мы обсудим её в следующей главе, а пока просто добавим в проект наряду с библиотекой chai-immutable, которая добавляет в Chai поддержку сравнения Immutable-структур: npm install --save immutable npm install --save-dev chai-immutable Подключать chai-immutable нужно до запуска каких-либо тестов. Сделать это можно с помощью файла test_helper: test/test_helper.js import chai from 'chai'; import chaiImmutable from 'chai-immutable'; chai.use(chaiImmutable); Теперь сделаем так, чтобы Mocha подгрузил этот файл до запуска тестов: package.json "scripts": { "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive", "test:watch": "npm run test -- --watch" }, Теперь у нас есть все, чтобы начать. 4.3. Знакомство с неизменяемыми данными Второй важный момент, связанный с архитектурой Redux: состояние — это не просто дерево, а неизменяемое дерево (immutable tree). Структура деревьев из предыдущей главы может навести на мысль, что код должен менять состояние приложения просто обновляя деревья: заменяя элементы в ассоциативных массивах, удаляя их из массивов и т.д. Но в Redux всё делается по-другому. Дерево состояний в Redux-приложении представляет собой неизменяемую структуру данных (immutable data structure). Это значит, что пока дерево существует, оно не меняется. Оно всегда сохраняет одно и то же состояние. И переход к другому состоянию осуществляется с помощью создания другого дерева, в которое внесены необходимые изменения. То есть два следующих друг за другом состояния приложения хранятся в двух отдельных и независимых деревьях. А переключение между деревьями осуществляется с помощью вызова функции, принимающей текущее состояние и возвращающей следующее. Хорошая ли это идея? Обычно сразу указывают на то, что если все состояния хранятся в одном дереве и вы вносите все эти безопасные обновления, то можно без особых усилий сохранять историю состояний приложения. Это позволяет реализовать undo/redo “бесплатно” — можно просто задать предыдущее или следующее состояние (дерево) из истории. Также можно сериализовать историю и сохранить её на будущее, или поместить ее в хранилище для последующего проигрывания, что может оказать неоценимую помощью в отладке. Но мне кажется, что, помимо всех этих дополнительных возможностей, главное достоинство использование неизменяемых данных заключается в упрощении кода. Вам приходится программировать чистые функции: они только принимают и возвращают данные, и больше ничего. Эти функции ведут себя предсказуемо. Вы можете вызывать их сколько угодно раз, и они всегда будут вести себя одинаково. Давайте им одни и те же аргументы, и будете получать одни и те же результаты. Тестирование становится тривиальным, ведь вам не нужно настраивать заглушки или иные фальшивки, чтобы «подготовить вселенную» к вызову функции. Есть просто входные и выходные данные. Поскольку мы будем описывать состояние нашего приложения неизменяемыми структурами, давайте потратим немного времени на знакомство с ними, написав несколько unit-тестов, иллюстрирующих работу. Если же вы уверенно работаете с неизменяемыми данными и библиотекой Immutable, то можете приступить к следующему разделу. Для ознакомления с идеей неизменяемости можно для начала поговорить о простейшей структуре данных. Допустим, у вас есть приложение-счётчик, состояние которого представляет собой число. Скажем, оно меняется от 0 до 1, потом до 2, потом до 3 и т.д. В принципе, мы уже думаем о числах как о неизменяемых данных. Когда счётчик увеличивается, то число не изменяется. Да это и невозможно, ведь у чисел нет «сеттеров». Вы не можете сказать 42.setValue(43). Так что мы просто получаем другое число, прибавляя к предыдущему единицу. Это можно сделать с помощью чистой функции. Её аргументом будет текущее состояние, а возвращаемое значение будет использоваться в качестве следующего состояния. Вызываемая функция не меняет текущее состояние. Вот её пример, а также unit тест к ней: test/immutable_spec.js import {expect} from 'chai'; describe('immutability', () => { describe('a number', () => { function increment(currentState) { return currentState + 1; } it('is immutable', () => { let state = 42; let nextState = increment(state); expect(nextState).to.equal(43); expect(state).to.equal(42); }); }); }); Очевидно, что state не меняется при вызове increment, ведь числа неизменяемы! Как вы могли заметить, этот тест ничего не делает с нашим приложением, мы его пока и не писали вовсе. Тесты могут быть просто инструментом обучения для нас. Я часто нахожу полезным изучать новые API или методики с помощью написания модульных тестов, прогоняющих какие-то идеи. В книге Test-Driven Development подобные тесты получили название «обучающих тестов». Теперь распространим идею неизменяемости на все виды структур данных, а не только на числа. С помощью Immutable списков мы можем, к примеру, сделать приложение, чьим состоянием будет список фильмов. Операция добавления нового фильма создаст новый список, который представляет собой комбинацию старого списка и добавляемой позиции. Важно отметить, что после этой операции старое состояние остаётся неизменённым: test/immutable_spec.js import {expect} from 'chai'; import {List} from 'immutable'; describe('immutability', () => { // ... describe('A List', () => { function addMovie(currentState, movie) { return currentState.push(movie); } it('is immutable', () => { let state = List.of('Trainspotting', '28 Days Later'); let nextState = addMovie(state, 'Sunshine'); expect(nextState).to.equal(List.of( 'Trainspotting', '28 Days Later', 'Sunshine' )); expect(state).to.equal(List.of( 'Trainspotting', '28 Days Later' )); }); }); }); А если бы мы вставили фильм в обычный массив, то старое состояние изменилось бы. Но вместо этого мы используем списки из Immutable, поэтому применяем ту же семантику, что и в предыдущем примере с числами. При вставке в обычный массив старое состояние изменилось бы. Но поскольку мы используем Immutable списки, то имеем ту же семантику, что и в примере с числами. Эта идея также хорошо применима и к полноценным деревьям состояний. Дерево является вложенной структурой списков (lists), ассоциативных массивов (maps) и других типов коллекций. Применяемая к нему операция создаёт новое дерево состояния, оставляя предыдущее в неприкосновенности. Если дерево представляет собой ассоциативный массив с ключом movies, содержащим список фильмов, то добавление новой позиции подразумевает необходимость создания нового массива, в котором ключ movies указывает на новый список: test/immutable_spec.js import {expect} from 'chai'; import {List, Map} from 'immutable'; describe('immutability', () => { // ... describe('a tree', () => { function addMovie(currentState, movie) { return currentState.set( 'movies', currentState.get('movies').push(movie) ); } it('is immutable', () => { let state = Map({ movies: List.of('Trainspotting', '28 Days Later') }); let nextState = addMovie(state, 'Sunshine'); expect(nextState).to.equal(Map({ movies: List.of( 'Trainspotting', '28 Days Later', 'Sunshine' ) })); expect(state).to.equal(Map({ movies: List.of( 'Trainspotting', '28 Days Later' ) })); }); }); }); Здесь мы видим точно такое же поведение, как и прежде, расширенное для демонстрации работы с вложенными структурами. Идея неизменяемости применима к данным всех форм и размеров. Для операций над подобными вложенными структурами в Immutable есть несколько вспомогательных функций, облегчающих «залезание» во вложенные данные ради получения обновлённого значения. Для краткости кода можем использовать функцию update: test/immutable_spec.js function addMovie(currentState, movie) { return currentState.update('movies', movies => movies.push(movie)); } Похожую функцию мы будем использовать в нашем приложении для обновления состояния приложения. В API Immutable скрывается немало других возможностей, и мы лишь рассмотрели верхушку айсберга. Неизменяемые данные являются ключевым аспектом архитектуры Redux, но не существует жесткого требования использовать именно библиотеку Immutable. В официальной документации Redux по больше части упоминаются простые объекты и массивы JavaScript, и от их изменения воздерживаются по соглашению. Существует ряд причин, по которым в нашем же руководстве будет использована библиотека Immutable: Структуры данных в Immutable разработаны с нуля, чтобы быть неизменяемыми и предоставляют API, который позволяет удобно выполнять операции над ними. Я разделяю точки зрения Рича Хайки, согласно которой не существует такой вещи, как неизменяемость по соглашению. Если вы используете структуры данных, которые могут быть изменены, то рано или поздно кто-нибудь ошибётся и сделает это. Особенно если вы новичок. Вещи вроде Object.freeze() помогут вам не ошибиться. Неизменяемые структуры данных являются персистентными, то есть их внутренняя структура такова, что создание новой версии является эффективной операцией с точки зрения времени и потребления памяти, особенно в случае больших деревьев состояний. Использование обычных объектов и массивов может привести к избыточному копированию, снижающему производительность. 4.4. Реализация логики приложения с помощью чистых функций Познакомившись с идеей неизменяемых деревьев состояний и функциями, оперирующими этими деревьями, можно перейти к созданию логики нашего приложения. В её основу лягут рассмотренные выше компоненты: древовидная структура и набор функций, создающих новые версии этого дерева. 4.4.1. Загрузка записей В первую очередь, приложение должно «загружать» коллекцию записей для голосования. Можно сделать функцию setEntries, берущую предыдущее состояние и коллекцию, и создающую новое состояние, включив туда записи. Вот тест для этой функции: test/core_spec.js import {List, Map} from 'immutable'; import {expect} from 'chai'; import {setEntries} from '../src/core'; describe('application logic', () => { describe('setEntries', () => { it('добавляет записи к состоянию', () => { const state = Map(); const entries = List.of('Trainspotting', '28 Days Later'); const nextState = setEntries(state, entries); expect(nextState).to.equal(Map({ entries: List.of('Trainspotting', '28 Days Later') })); }); }); }); Первоначальная реализация setEntries делает только самое простое: ключу entries в ассоциативном массиве состояния присваивает в качестве значения указанный список записей. Получаем первое из спроектированных нами ранее деревьев. src/core.js export function setEntries(state, entries) { return state.set('entries', entries); } Для удобства разрешим входным записям представлять собой обычный JavaScript-массив (или что-нибудь итерируемое). В дереве состояния же должен присутствовать Immutable список (List): test/core_spec.js it('преобразует в immutable', () => { const state = Map(); const entries = ['Trainspotting', '28 Days Later']; const nextState = setEntries(state, entries); expect(nextState).to.equal(Map({ entries: List.of('Trainspotting', '28 Days Later') })); }); Для удовлетворения этому требованию будем передавать записи в конструктор списка: src/core.js import {List} from 'immutable'; export function setEntries(state, entries) { return state.set('entries', List(entries)); } 4.4.2. Запуск голосования Голосование можно запустить вызовом функции next при состоянии, уже имеющем набор записей. Таким образом будет осуществлён переход от первого ко второму из спроектированных деревьев. Этой функции не нужны дополнительные аргументы. Она должна создавать ассоциативный массив vote, в котором по ключу pair лежат две первые записи. При этом записи, которые в данный момент участвуют в голосовании, больше не должны находиться в списке entries: test/core_spec.js import {List, Map} from 'immutable'; import {expect} from 'chai'; import {setEntries, next} from '../src/core'; describe('логика приложения', () => { // .. describe('далее', () => { it('берёт для голосования следующие две записи', () => { const state = Map({ entries: List.of('Trainspotting', '28 Days Later', 'Sunshine') }); const nextState = next(state); expect(nextState).to.equal(Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later') }), entries: List.of('Sunshine') })); }); }); }); Реализация функции будет объединять (merge) обновление со старым состоянием, обособляя первые записи лежат в отдельный список, а остальные — в новую версию списка entries: src/core.js import {List, Map} from 'immutable'; // ... export function next(state) { const entries = state.get('entries'); return state.merge({ vote: Map({pair: entries.take(2)}), entries: entries.skip(2) }); } 4.4.3. Голосование По мере продолжения голосования, пользователь должен иметь возможность отдавать голос за разные записи. И при каждом новом голосовании на экране должен отображаться текущий результат. Если за конкретную запись уже голосовали, то её счётчик должен увеличиться. test/core_spec.js import {List, Map} from 'immutable'; import {expect} from 'chai'; import {setEntries, next, vote} from '../src/core'; describe('логика приложения', () => { // ... describe('vote', () => { it('создаёт результат голосования для выбранной записи', () => { const state = Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later') }), entries: List() }); const nextState = vote(state, 'Trainspotting'); expect(nextState).to.equal(Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 1 }) }), entries: List() })); }); it('добавляет в уже имеющийся результат для выбранной записи', () => { const state = Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 3, '28 Days Later': 2 }) }), entries: List() }); const nextState = vote(state, 'Trainspotting'); expect(nextState).to.equal(Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 4, '28 Days Later': 2 }) }), entries: List() })); }); }); }); С помощью функции fromJS из Immutable можно более лаконично создать все эти вложенные схемы и списки. Прогоним тесты: src/core.js export function vote(state, entry) { return state.updateIn( ['vote', 'tally', entry], 0, tally => tally + 1 ); } Использование updateIn позволяет не растекаться мыслью по древу. В этом коде говорится: «возьми путь вложенной структуры данных ['vote', 'tally', 'Trainspotting'] и примени эту функцию. Если какие-то ключи отсутствуют, то создай вместо них новые массивы (Map). Если в конце отсутствует значение, то инициализируй нулем». Именно такого рода код позволяет получать удовольствие от работы с неизменяемыми структурами данных, так что стоит уделить этому время и попрактиковаться. 4.4.4. Переход к следующей паре По окончании голосования по текущей паре, переходим к следующей. Нужно сохранить победителя и добавить в конец списка записей, чтобы позднее он снова принял участие в голосовании. Проигравшая запись просто выкидывается. В случае ничьей сохраняются обе записи. Добавим эту логику к имеющейся реализации next: test/core_spec.js describe('next', () => { // ... it('помещает победителя текущего голосования в конец списка записей', () => { const state = Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 4, '28 Days Later': 2 }) }), entries: List.of('Sunshine', 'Millions', '127 Hours') }); const nextState = next(state); expect(nextState).to.equal(Map({ vote: Map({ pair: List.of('Sunshine', 'Millions') }), entries: List.of('127 Hours', 'Trainspotting') })); }); it('в случае ничьей помещает обе записи в конец списка', () => { const state = Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 3, '28 Days Later': 3 }) }), entries: List.of('Sunshine', 'Millions', '127 Hours') }); const nextState = next(state); expect(nextState).to.equal(Map({ vote: Map({ pair: List.of('Sunshine', 'Millions') }), entries: List.of('127 Hours', 'Trainspotting', '28 Days Later') })); }); }); В нашей реализации мы просто соединяем победителей текущего голосования с записями. А находить этих победителей можно с помощью новой функции getWinners: src/core.js function getWinners(vote) { if (!vote) return []; const [a, b] = vote.get('pair'); const aVotes = vote.getIn(['tally', a], 0); const bVotes = vote.getIn(['tally', b], 0); if (aVotes > bVotes) return [a]; else if (aVotes < bVotes) return [b]; else return [a, b]; } export function next(state) { const entries = state.get('entries') .concat(getWinners(state.get('vote'))); return state.merge({ vote: Map({pair: entries.take(2)}), entries: entries.skip(2) }); } 4.4.5. Завершение голосования В какой-то момент у нас остаётся лишь одна запись — победитель, и тогда голосование завершается. И вместо формирования нового голосования, мы явным образом назначаем эту запись победителем в текущем состоянии. Конец голосования. test/core_spec.js describe('next', () => { // ... it('когда остаётся лишь одна запись, помечает её как победителя', () => { const state = Map({ vote: Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 4, '28 Days Later': 2 }) }), entries: List() }); const nextState = next(state); expect(nextState).to.equal(Map({ winner: 'Trainspotting' })); }); }); В реализации next нужно предусмотреть обработку ситуации, когда после завершения очередного голосования в списке записей остаётся лишь одна позиция: src/core.js export function next(state) { const entries = state.get('entries') .concat(getWinners(state.get('vote'))); if (entries.size vote') .remove('entries') .set('winner', entries.first()); } else { return state.merge({ vote: Map({pair: entries.take(2)}), entries: entries.skip(2) }); } } Здесь можно было бы просто вернуть Map({winner: entries.first()}). Но вместо этого мы снова берём старое состояние и явным образом убираем из него ключи vote и entries. Это делается с прицелом на будущее: может случиться так, что в нашем состоянии появятся какие-то сторонние данные, которые нужно будет в неизменном виде передать с помощью этой функции. В целом, в основе функций трансформирования состояний лежит хорошая идея — всегда преобразовывать старое состояние в новое, вместо создания нового состояния с нуля. Теперь у нас есть вполне приемлемая версия основной логики нашего приложения, выраженная в виде нескольких функций. Также мы написали для них unit тесты, которые дались нам довольно легко: никаких преднастроек и заглушек. В этом и проявляется красота чистых функций. Можно просто вызвать их и проверить возвращаемые значения. Обратите внимание, что мы пока ещё даже не установили Redux. При этом спокойно занимались разработкой логики приложения, не привлекая «фреймворк» к этой задаче. Есть в этом что-то чертовски приятное. 4.5. Использование Actions и Reducers Итак, у нас есть основные функции, но мы не будем вызывать их в Redux напрямую. Между функциями и внешним миром расположен слой косвенной адресации: действия (Actions). Это простые структуры данных, описывающие изменения, которые должны произойти с состоянием вашего приложения. По сути это описание вызова функции, упакованное в маленький объект. По соглашению, каждое действие имеет атрибут type, описывающий, для какой операции это действие предназначено. Также могут использоваться и дополнительные атрибуты. Вот несколько примеров действий, подходящих для наших основных функций: {type: 'SET_ENTRIES', entries: ['Trainspotting', '28 Days Later']} {type: 'NEXT'} {type: 'VOTE', entry: 'Trainspotting'} При таком способе выражения нам ещё понадобится превратить их в нормальные вызовы основных функций. В случае с VOTE должен выполняться следующий вызов: // Этот action let voteAction = {type: 'VOTE', entry: 'Trainspotting'} // должен сделать это: return vote(state, voteAction.entry); Теперь нужно написать шаблонную функцию (generic function), принимающую любое действие — в рамках текущего состояния — и вызывающую соответствующую функцию ядра. Такая функция называется преобразователем (reducer): src/reducer.js export default function reducer(state, action) { // Определяет, какую функцию нужно вызвать, и делает это } Теперь нужно убедиться, что наш reducer способен обрабатывать каждое из трёх действий: test/reducer_spec.js import {Map, fromJS} from 'immutable'; import {expect} from 'chai'; import reducer from '../src/reducer'; describe('reducer', () => { it('handles SET_ENTRIES', () => { const initialState = Map(); const action = {type: 'SET_ENTRIES', entries: ['Trainspotting']}; const nextState = reducer(initialState, action); expect(nextState).to.equal(fromJS({ entries: ['Trainspotting'] })); }); it('handles NEXT', () => { const initialState = fromJS({ entries: ['Trainspotting', '28 Days Later'] }); const action = {type: 'NEXT'}; const nextState = reducer(initialState, action); expect(nextState).to.equal(fromJS({ vote: { pair: ['Trainspotting', '28 Days Later'] }, entries: [] })); }); it('handles VOTE', () => { const initialState = fromJS({ vote: { pair: ['Trainspotting', '28 Days Later'] }, entries: [] }); const action = {type: 'VOTE', entry: 'Trainspotting'}; const nextState = reducer(initialState, action); expect(nextState).to.equal(fromJS({ vote: { pair: ['Trainspotting', '28 Days Later'], tally: {Trainspotting: 1} }, entries: [] })); }); }); В зависимости от типа действия reducer должен обращаться к одной из функций ядра. Он также должен знать, как извлечь из действия дополнительные аргументы для каждой из функций: src/reducer.js import {setEntries, next, vote} from './core'; export default function reducer(state, action) { switch (action.type) { case 'SET_ENTRIES': return setEntries(state, action.entries); case 'NEXT': return next(state); case 'VOTE': return vote(state, action.entry) } return state; } Обратите внимание, что если reducer не распознает действие, то просто вернёт текущее состояние. К reducer-ам предъявляется важное дополнительное требование: если они вызываются с незаданным состоянием, то должны знать, как проинициализировать его правильным значением. В нашем случае исходным значением является ассоциативный массив. Таким образом, состояние undefined должно обрабатываться, как если бы мы передали пустой массив: test/reducer_spec.js describe('reducer', () => { // ... it('has an initial state', () => { const action = {type: 'SET_ENTRIES', entries: ['Trainspotting']}; const nextState = reducer(undefined, action); expect(nextState).to.equal(fromJS({ entries: ['Trainspotting'] })); }); }); Поскольку логика нашего приложения расположена в core.js, то здесь же можно объявить начальное состояние: src/core.js export const INITIAL_STATE = Map(); Затем мы импортируем его в reducer-е и используем в качестве значения по умолчанию для аргумента состояния: src/reducer.js import {setEntries, next, vote, INITIAL_STATE} from './core'; export default function reducer(state = INITIAL_STATE, action) { switch (action.type) { case 'SET_ENTRIES': return setEntries(state, action.entries); case 'NEXT': return next(state); case 'VOTE': return vote(state, action.entry) } return state; } Любопытно то, как абстрактно reducer можно использовать для перевода приложения из одного состояния в другое при помощи действия любого типа. В принципе, взяв коллекцию прошлых действий, вы действительно можете просто преобразовать её в текущее состояние. Именно поэтому функция называется преобразователь: она заменяет собой вызов callback-a. test/reducer_spec.js it('может использоваться с reduce', () => { const actions = [ {type: 'SET_ENTRIES', entries: ['Trainspotting', '28 Days Later']}, {type: 'NEXT'}, {type: 'VOTE', entry: 'Trainspotting'}, {type: 'VOTE', entry: '28 Days Later'}, {type: 'VOTE', entry: 'Trainspotting'}, {type: 'NEXT'} ]; const finalState = actions.reduce(reducer, Map()); expect(finalState).to.equal(fromJS({ winner: 'Trainspotting' })); }); Способность создавать и/или проигрывать коллекции действий является главным преимуществом модели переходов состояний с помощью action/reducer, по сравнению с прямым вызовом функций ядра. Поскольку actions — это объекты, которые можно сериализовать в JSON, то вы, к примеру, можете легко отправлять их в Web Worker, и там уже выполнять логику reducer-a. Или даже можете отправлять их по сети, как мы это сделаем ниже. Обратите внимание, что в качестве actions мы используем простые объекты, а не структуры данных из Immutable. Этого требует от нас Redux. 4.6. Привкус Reducer-композиции Согласно логике нашего ядра, каждая функция принимает и возвращает полное состояние приложения. Но можно легко заметить, что в больших приложениях этот подход может оказаться не лучшим решением. Если каждая операция в приложении должна знать о структуре всего состояния, то ситуация быстро может стать нестабильной. Ведь для изменения состояния потребуется внести кучу других изменений. Лучше всего в любых возможных случаях выполнять операции в рамках как можно меньшей части состояния (или в поддереве). Речь идёт о модульности: функциональность работает только с какой-то одной частью данных, словно остальное и не существует. Но в нашем случае приложение такое маленькое, что у нас не возникнет вышеописанных проблем. Хотя кое-что улучшить мы всё же можем: функции vote можно не передавать всё состояние приложения, ведь она работает только с одноимённым сегментом vote. И только о нём ей достаточно знать. Для отображения этой идеи мы можем модифицировать наши unit тесты для vote: test/core_spec.js describe('vote', () => { it('создаёт результат голосования для выбранной записи', () => { const state = Map({ pair: List.of('Trainspotting', '28 Days Later') }); const nextState = vote(state, 'Trainspotting') expect(nextState).to.equal(Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 1 }) })); }); it('добавляет в уже имеющийся результат для выбранной записи', () => { const state = Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 3, '28 Days Later': 2 }) }); const nextState = vote(state, 'Trainspotting'); expect(nextState).to.equal(Map({ pair: List.of('Trainspotting', '28 Days Later'), tally: Map({ 'Trainspotting': 4, '28 Days Later': 2 }) })); }); }); Как видите, код теста упростился, а это обычно хороший знак! Теперь реализация vote должна просто брать соответствующий сегмент состояния и обновлять счетчик голосования: src/core.js export function vote(voteState, entry) { return voteState.updateIn( ['tally', entry], 0, tally => tally + 1 ); } Далее reducer должен взять состояние и передать функции vote только необходимую часть. src/reducer.js export default function reducer(state = INITIAL_STATE, action) { switch (action.type) { case 'SET_ENTRIES': return setEntries(state, action.entries); case 'NEXT': return next(state); case 'VOTE': return state.update('vote', voteState => vote(voteState, action.entry)); } return state; } Это лишь небольшой пример подхода, важность которого сильно возрастает с увеличением размера приложения: главная функция-reducer просто передаёт отдельные сегменты состояния reducer-ам уровнем ниже. Мы отделяем задачу поиска нужного сегмента дерева состояний от применения обновления к этому сегменту. Гораздо подробнее шаблоны reducer-композиции рассмотрены в соответствующей секции документации Redux. Также там объясняются некоторые вспомогательные функции, во многих случаях облегчающие использование reducer-композиции. 4.7. Использование Redux Store Теперь, когда у нас есть reducer, можно начать думать, как всё это подключить к Redux. Как мы только что видели, если у вас есть коллекция всех действий, которые когда либо будут иметь место в вашем приложении, что вы можете просто вызвать reduce и получить на выходе финальное состояние приложения. Конечно, обычно у вас нет такой коллекции. Действия осуществляются постепенно, по мере возникновения разных событий: когда пользователь взаимодействует с приложением, когда данные приходят из сети, по триггеру таймаута. Приспособиться к ситуации помогает хранилище — Redux Store. Как подсказывает логика, это объект, в котором хранится состояние нашего приложения. Хранилище инициализируется reducer-функцией, наподобие уже реализованной нами: import {createStore} from 'redux'; const store = createStore(reducer); Далее можно передать (dispatch) действия в store, который затем воспользуется reducer-ом для применения этих действий к текущему состоянию. В качестве результата этой процедуры мы получим следующее состояние, которое будет находиться в Redux-Store. store.dispatch({type: 'NEXT'}); Вы можете получить из хранилища текущее состояние в любой момент времени: store.getState(); Давайте настроим и экспортируем Redux Store в файл store.js. Но сначала протестируем: нам нужно создать хранилище, считать его начальное состояние, передать action и наблюдать изменённое состояние: test/store_spec.js import {Map, fromJS} from 'immutable'; import {expect} from 'chai'; import makeStore from '../src/store'; describe('store', () => { it('хранилище сконфигурировано с помощью правильного преобразователя', () => { const store = makeStore(); expect(store.getState()).to.equal(Map()); store.dispatch({ type: 'SET_ENTRIES', entries: ['Trainspotting', '28 Days Later'] }); expect(store.getState()).to.equal(fromJS({ entries: ['Trainspotting', '28 Days Later'] })); }); }); Перед созданием Store нам нужно добавить Redux в проект: npm install --save redux Теперь можно создавать store.js, в котором вызовем createStore с нашим reducer-ом: src/store.js import {createStore} from 'redux'; import reducer from './reducer'; export default function makeStore() { return createStore(reducer); } Итак, Redux Store соединяет части нашего приложения в целое, которое можно использовать как центральную точку — здесь находится текущее состояние, сюда приходят actions, которые переводят приложение из одного состояния в другое с помощью логики ядра, транслируемой через reducer. Вопрос: Сколько переменных в Redux-приложении вам нужно? Ответ: Одна. Внутри хранилища. На первый взгляд это звучит странно. По крайне мере, если у вас не так много опыта в функциональном программировании. Как можно сделать хоть что-то полезное всего лишь с одной переменной? Но больше нам и не нужно. Текущее дерево состояний — единственная вещь, которая изменяется со временем в нашем базовом приложении. Всё остальное — это константы и неизменяемые значения. Примечательно, насколько мала площадь соприкосновения между кодом нашего приложения и Redux. Благодаря тому, что у нас есть шаблонная reducer-функция, нам достаточно уведомить Redux лишь о ней. А всё остальное есть в нашем собственном, не зависящем от фреймворка, портируемом и исключительно функциональном коде! Если мы теперь создадим входную точку нашего приложения — index.js, то сможем создать и экспортировать Store: index.js import makeStore from './src/store'; export const store = makeStore(); А раз уж мы его экспортировали, то можем теперь завести и Node REPL (например, с помощью babel-node), запросить файл index.js и взаимодействовать с приложением с помощью Store. 4.8. Настройка сервера Socket.io Наше приложение будет работать в качестве сервера для другого браузерного приложения, имеющего пользовательский интерфейс для голосования и просмотра результатов. Нам нужно организовать взаимодействие клиентов с сервером, и наоборот. Наше приложение только выиграет от внедрения общения в реальном времени, поскольку пользователям понравится сразу же наблюдать результаты своих действий и действий других. Для этой цели давайте воспользуемся WebSocket’ами. Точнее, возьмём библиотеку Socket.io, предоставляющую хорошую абстракцию для работающих в браузерах WebSocket’ов. К тому же тут есть и несколько запасных механизмов для клиентов, не поддерживающих WebSocket’ы. Добавляем Socket.io в проект: npm install --save socket.io Создаём файл server.js, экспортирующий функцию создания сервера Socket.io: src/server.js import Server from 'socket.io'; export default function startServer() { const io = new Server().attach(8090); } Этот код создает сервер Socket.io, а также поднимает на порте 8090 обычный HTTP-сервер. Порт выбран произвольно, он должен совпадать с портом, который позднее будет использоваться для связи с клиентами. Теперь вызовем эту функцию из index.js, и сервер будет запущен с началом работы приложения: index.js import makeStore from './src/store'; import startServer from './src/server'; export const store = makeStore(); startServer(); Можно немного упростить процедуру запуска, добавив команду start в наш package.json: package.json "scripts": { "start": "babel-node index.js", "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive", "test:watch": "npm run test -- --watch" }, Теперь после ввода следующей команды будет запускаться сервер и создаваться Redux-Store: npm run start Команда babel-node взята из ранее установленного нами пакета babel-cli. Она позволяет легко запускать Node-код с включённой поддержкой Babel-транспилирования. В целом, это не рекомендуется делать для боевых серверов, потому что производительность несколько снижается. Но зато хорошо подходит для наших учебных задач. 4.9. Трансляция Store из Redux Listener Теперь у нас есть сервер Socket.io и контейнер Redux состояния, но они пока никак не интегрированы. Изменим это. Сервер должен сообщать клиентам о текущем состоянии приложения (например, «за что сейчас голосуем?», «каков текущий результат?», «есть ли уже победитель?»). Это можно делать при каждом изменении с помощью передачи события из Socket.io всем подключённым клиентам. А как узнать, что что-то изменилось? Для этого можно подписаться на Redux store, предоставив функцию, которая будет вызываться хранилищем при каждом применении action, когда состояние потенциально изменилось. По сути, это callback на изменения состояния внутри store. Мы будем делать это в startServer, так что предоставим ему Redux store для начала: index.js import makeStore from './src/store'; import {startServer} from './src/server'; export const store = makeStore(); startServer(store); Подпишем получателя событий (listener) на наше хранилище. Он считывает текущее состояние, превращает его в простой JavaScript-объект и передаёт его на сервер Socket.io в виде события state. В результате мы получаем JSON-сериализованный снэпшот состояния, рассылаемый на все активные подключения Socket.io. src/server.js import Server from 'socket.io'; export function startServer(store) { const io = new Server().attach(8090); store.subscribe( () => io.emit('state', store.getState().toJS()) ); } Теперь при каждом изменении мы передаём полное состояние всем клиентам. Но это может повлечь за собой серьёзный рост трафика. Можно предложить различные способы оптимизации (например, отправлять только актуальную часть состояния, отправлять дифы вместо снэпшотов, и т.д.). В нашей реализации не будем этого делать в целях сохранения простоты кода. Помимо передачи снэпшота состояния было бы хорошо, если бы клиенты немедленно получали текущее состояние при подключении к серверу. Это позволит сразу синхронизировать состояние клиентских приложений с текущим состоянием сервера. На сервере Socket.io мы можем слушать события connection, передаваемые клиентами при каждом подключении. В обработчике события мы можем сразу отдавать текущее состояние: src/server.js import Server from 'socket.io'; export function startServer(store) { const io = new Server().attach(8090); store.subscribe( () => io.emit('state', store.getState().toJS()) ); io.on('connection', (socket) => { socket.emit('state', store.getState().toJS()); }); } 4.10. Получение Remote Redux Actions Вдобавок к передаче клиентам состояния приложения, нам нужно уметь получать от них обновления: пользователи будут голосовать, а модуль управления голосованием будет обрабатывать события с помощью действия NEXT. Для этого достаточно напрямую скармливать в Redux store события action, генерируемые клиентами. src/server.js import Server from 'socket.io'; export function startServer(store) { const io = new Server().attach(8090); store.subscribe( () => io.emit('state', store.getState().toJS()) ); io.on('connection', (socket) => { socket.emit('state', store.getState().toJS()); socket.on('action', store.dispatch.bind(store)); }); } Здесь мы уже выходим за рамки «стандартного Redux», потому что фактически принимаем в store удалённые (remote) actions. Но архитектура Redux вовсе не мешает нам: действия являются JavaScript-объектами, которые можно легко посылать по сети, поэтому мы сразу получаем систему, в которой принимать участие в голосовании может любое количество клиентов. А это большой шаг! Конечно, с точки зрения безопасности здесь есть ряд моментов, ведь мы позволяем любому клиенту, подключившемуся к Socket.io, отправлять любое действие в Redux store. Поэтому в реальных проектах нужно использовать что-то вроде файрвола, наподобие Vert.x Event Bus Bridge. Также файрвол нужно внедрять в приложения с механизмом аутентификации. Теперь наш сервер работает следующим образом: Клиент отправляет на сервер какое-то действие (action). Сервер пересылает его в Redux store. Store вызывает reducer, который исполняет логику, связанную с этим action. Store обновляет состояние на основании возвращаемого reducer-ом значения. Store исполняет соответствующий listener, подписанный сервером. Сервер генерирует событие state. Все подключённые клиенты — включая того, кто инициировал первоначальное действие — получают новое состояние. Прежде, чем мы закончим работу над сервером, давайте загрузим в него тестовый набор записей, чтобы посмотреть, как работает система. Записи можно поместить в файл entries.json. Пусть это будет список фильмов Дэнни Бойла. entries.json [ "Shallow Grave", "Trainspotting", "A Life Less Ordinary", "The Beach", "28 Days Later", "Millions", "Sunshine", "Slumdog Millionaire", "127 Hours", "Trance", "Steve Jobs" ] Далее просто загружаем список в index.js, а затем запускаем голосование с помощью действия NEXT: index.js import makeStore from './src/store'; import {startServer} from './src/server'; export const store = makeStore(); startServer(store); store.dispatch({ type: 'SET_ENTRIES', entries: require('./entries.json') }); store.dispatch({type: 'NEXT'}); Теперь можно перейти к клиентскому приложению. 5. Клиентское приложение Далее мы будем писать React-приложение, которое подключается к серверу и позволяет пользователям голосовать. И здесь мы тоже воспользуемся Redux. Собственно, это одно из наиболее распространённых его применений: в качестве движка в основании React-приложений. Мы уже познакомились с его работой, и скоро узнаем, как он совмещается с React и какое оказывает влияние на архитектуру. Рекомендую писать приложение с нуля, но можете скачать код с GitHub. 5.1. Настройка клиентского проекта В первую очередь мы создадим свежий NPM-проект, как мы это делали в случае с сервером. mkdir voting-client cd voting-client npm init –y Теперь для нашего приложения нужна стартовая HTML-страница. Положим её в dist/index.html: dist/index.html <!DOCTYPE html> <html> <body> <div src="bundle.js"></script> </body> </html> Документ содержит лишь <div> с ID app, сюда мы и поместим наше приложение. В ту же папку надо будет положить и файл bundle.js. Создадим первый JavaScript-файл, который станет входной точкой приложения. Пока что можно просто поместить в него простое логирующее выражение: src/index.js console.log('I am alive!'); Для облегчения процесса создания приложения воспользуемся Webpack и его сервером разработки, добавив их к нашему проекту: npm install --save-dev webpack webpack-dev-server Если вы их пока не устанавливали, стоит установить эти же пакеты глобально, чтобы можно было удобно запускать всё необходимое из командной строки: npm install -g webpack webpack-dev-server. Добавим файл конфигурации Webpack, соответствующий созданным ранее файлам, в корень проекта: webpack.config.js module.exports = { entry: [ './src/index.js' ], output: { path: __dirname + '/dist', publicPath: '/', filename: 'bundle.js' }, devServer: { contentBase: './dist' } }; Он обнаружит нашу входную точку index.js и встроит всё необходимое в бандл dist/bundle.js. Папка dist будет базовой и для сервера разработки. Теперь можно запустить Webpack для создания bundle.js: Webpack Далее запустим сервер, после чего тестовая страница станет доступна в localhost:8080 (включая логирующее выражение из index.js). webpack-dev-server Поскольку мы собрались использовать в клиентском коде React JSX синтаксис и ES6, то нам нужна еще пара инструментов. Babel умеет работать с ними обоими, поэтому подключим его и его Webpack-загрузчик: npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react Включаем в package.json поддержку Babel’ем ES6/ES2015 и React JSX, активируя только что установленные пресеты: package.json "babel": { "presets": ["es2015", "react"] } Теперь изменим конфигурационный файл Webpack, чтобы он мог найти .jsx и .js файлы и обработать их с помощью Babel: webpack.config.js module.exports = { entry: [ './src/index.js' ], module: { loaders: [{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel' }] }, resolve: { extensions: ['', '.js', '.jsx'] }, output: { path: __dirname + '/dist', publicPath: '/', filename: 'bundle.js' }, devServer: { contentBase: './dist' } }; Не будем тратить время на CSS. Если вы хотите сделать приложение красивее, то можете сами добавить в него стили. Либо можете воспользоваться стилями из этого коммита. В дополнение к CSS-файлу будет добавлена Webpack-поддержка для подключения стилей (и автопрефиксов), а также компонент, немного улучшенный для визуализации. 5.1.1. Поддержка модульного тестирования Для клиентского кода мы тоже будем писать модульные тесты. Для этого воспользуемся теми же библиотеками — Mocha и Chai: npm install --save-dev mocha chai Также будем тестировать и React-компоненты, для чего нам понадобится DOM. В качестве альтернативы можно предложить прогнать в настоящем веб-браузере тесты библиотекой наподобие Karma. Но это не является необходимостью для нас, поскольку мы можем обойтись средствами jsdom, реализацией DOM на чистом JavaScript внутри Node: npm install --save-dev jsdom Для последней версии jsdom требуется io.js или Node.js 4.0.0. Если вы пользуетесь более старой версией Node, то вам придётся установить и более старый jsdom: npm install --save-dev jsdom@3 Также нам понадобится несколько строк настройки jsdom для использования React. В частности, создадим jsdom-версии объектов document и window, предоставляемых браузером. Затем положим их в глобальный объект, чтобы React мог найти их, когда будет обращаться к document или window. Для этой настройки подготовим вспомогательный тестовый файл: test/test_helper.js import jsdom from 'jsdom'; const doc = jsdom.jsdom('<!doctype html><html><body></body></html>'); const win = doc.defaultView; global.document = doc; global.window = win; Кроме того, нам нужно взять все свойства, содержащиеся в jsdom-объекте window (например, navigator), и добавить их в объект global в Node.js. Это делается для того, чтобы предоставляемые объектом window свойства можно было использовать без префикса window., как это происходит в браузерном окружении. От этого зависит часть кода внутри React: test/test_helper.js import jsdom from 'jsdom'; const doc = jsdom.jsdom('<!doctype html><html><body></body></html>'); const win = doc.defaultView; global.document = doc; global.window = win; Object.keys(window).forEach((key) => { if (!(key in global)) { global[Перевод] Руководство по работе с Redux = window[Перевод] Руководство по работе с Redux; } }); Также мы воспользуемся Immutable коллекциями, поэтому придётся прибегнуть к той же уловке, что и в случае с сервером, чтобы внедрить поддержку Chai. Установим оба пакета — immutable и chai-immutable: npm install --save immutable npm install --save-dev chai-immutable Далее пропишем их в тестовом вспомогательном файле: test/test_helper.js import jsdom from 'jsdom'; import chai from 'chai'; import chaiImmutable from 'chai-immutable'; const doc = jsdom.jsdom('<!doctype html><html><body></body></html>'); const win = doc.defaultView; global.document = doc; global.window = win; Object.keys(window).forEach((key) => { if (!(key in global)) { global[Перевод] Руководство по работе с Redux = window[Перевод] Руководство по работе с Redux; } }); chai.use(chaiImmutable); Последний шаг перед запуском тестов: добавим в файл package.json команду для их запуска: package.json "scripts": "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js \"test/**/*@(.js, Почти такую же команду мы использовали в серверном package.json. Разница лишь в спецификации тестового файла: на сервере мы использовали --recursive, но в этом случае не будут обнаруживаться .jsx-файлы. Для возмодности найти и .js, и .jsx-файлы используем glob. Было бы удобно непрерывно прогонять тесты при любых изменениях в коде. Для этого можно добавить команду test:watch, идентичную применяемой на сервере: package.json "scripts": jsx)'", "test:watch": "npm run test -- --watch", 5.2. React и react-hot-loader Инфраструктура Webpack и Babel готова, займемся React! При построении React-приложений с помощью Redux и Immutable мы можем писать так называемые чистые компоненты (Pure Components, их ещё иногда называют Dumb Components). Идея та же, что и в основе чистых функций, должны соблюдаться два правила: Чистый компонент получает все данные в виде свойств, как функция получает данные в виде аргументов. Не должно быть никаких побочных эффектов — чтения данных откуда либо, инициации сетевых запросов и т.д. В целом у чистого компонента нет внутреннего состояния. Отрисовка зависит исключительно от входных свойств. Если дважды что-то отрисовать с помощью одного компонента, имеющего одни и те же свойства, то в результате мы получим один и тот же интерфейс. У компонента нет скрытого состояния, которое может повлиять на процесс отрисовки. Использование чистых компонентов упрощает код, как и использование чистых функций: мы можем понять, что делает компонент, посмотрев на его входные данные и результат отрисовки. Больше нам ничего не нужно знать о компоненте. Тестировать его также не сложно, почти как и тестировать логику приложения но основе чистых функций. Но если компонент не может обладать состоянием, то где оно будет находиться? В неизменяемой структуре данных внутри Redux store! Отделить состояние от кода пользовательского интерфейса — отличная идея. React-компоненты представляют собой всего лишь не имеющую состояния проекцию состояния на данный момент времени. Но не будем забегать вперёд. Добавим React в наш проект: npm install --save react react-dom Также настроим react-hot-loader. Этот инструмент сильно ускорит процесс разработки благодаря перезагрузке кода без потери текущего состояния приложения. npm install --save-dev react-hot-loader Было бы глупо пренебрегать react-hot-loader, ведь наша архитектура только поощряет его использование. По сути, создание Redux и react-hot-loader — две части одной истории! Для поддержки этого загрузчика сделаем несколько обновлений в webpack.config.js. Вот что получилось: webpack.config.js var webpack = require('webpack'); module.exports = { entry: [ 'webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './src/index.js' ], module: { loaders: [{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'react-hot!babel' }] }, resolve: { extensions: ['', '.js', '.jsx'] }, output: { path: __dirname + '/dist', publicPath: '/', filename: 'bundle.js' }, devServer: { contentBase: './dist', hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() ] }; В секцию entry включены две новые вещи для входных точек нашего приложения: клиентская библиотека от Webpack сервера разработки и загрузчик модулей (hot module loader) Webpack. Благодаря этому мы сможем использовать инфраструктуру Webpack для горячей замены модулей. По умолчанию такая замена не поддерживается, поэтому в секции plugins придётся подгружать соответствующий плагин и активировать поддержку в секции devServer. В секции loaders мы настраиваем загрузчик react-hot, чтобы он наряду с Babel мог работать с файлами .js и .jsx. Теперь при запуске или рестарте сервера разработки мы увидим в консоли сообщение о включении поддержки горячей замены модулей (Hot Module Replacement). 5.3. Создание пользовательского интерфейса для экрана голосования Этот экран будет очень простым: пока голосование не завершилось, всегда будут отображаться две кнопки, по одной для каждой из двух записей. А по завершении голосования будет показан победитель. По большей части пока мы занимались разработкой через тестирование, при создании React-компонентов применим другой подход: сначала пишем компоненты, а затем тесты. Дело в том, что Webpack и react-hot-loader имеют ещё более короткий контур обратной связи, чем модульные тесты. Кроме того, при создании интерфейса нет ничего эффективнее, чем наблюдать его работу своими глазами. Допустим, нам нужно создать компонент Voting и рендерить его в качестве входной точки приложения. Можно смонтировать его в div #app, который ранее был добавлен в index.html. И придётся переименовать index.js в index.jsx, ведь теперь он содержит JSX-разметку: src/index.jsx import React from 'react'; import ReactDOM from 'react-dom'; import Voting from './components/Voting'; const pair = ['Trainspotting', '28 Days Later']; ReactDOM.render( <Voting app') ); Компонент Voting получает пару записей в виде свойств. Пока что мы эту пару захардкодим, а позднее заменим реальными данными. Компонент чистый, поэтому ему не важно, откуда берутся данные. Изменим имя стартового файла в webpack.config.js: webpack.config.js entry: [ 'webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './src/index.jsx' ], Теперь при запуске или рестарте webpack-dev-server мы увидим сообщение об отсутствии компонента Voting. Напишем его первую версию: src/components/Voting.jsx import React from 'react'; export default React.createClass([];, render: function() { return <div > Пара записей выводятся в виде кнопок, их можно увидеть в браузере. Попробуйте внести в код компонента какие-нибудь изменения, они немедленно появятся в браузере. Без рестартов и перезагрузок страницы. Это к вопросу о скорости обратной связи. Если вы видите не то, что ожидаете, то проверьте выходные данные webpack-dev-server, а также лог браузера. Теперь можно добавить первый модульный тест. Он будет расположен в файле Voting_spec.jsx: test/components/Voting_spec.jsx import Voting from '../../src/components/Voting'; describe('Voting', () => { }); Для проверки отрисовки кнопок по свойству pair, нужно отрендерить компонент и проверить результат. Для этого воспользуемся вспомогательной функцией renderIntoDocument из пакета тестовых утилит React, который сначала нужно установить: npm install --save react-addons-test-utils test/components/Voting_spec.jsx import React from 'react'; import ReactDOM from 'react-dom'; import { renderIntoDocument } from 'react-addons-test-utils'; import Voting from '../../src/components/Voting'; describe('Voting', () => { it('renders a pair of buttons', () => { const component = renderIntoDocument( <Voting > После отрисовки компонента для поиска кнопок можно использовать другую вспомогательную функцию React — scryRenderedDOMComponentsWithTag. Их должно быть две, а что текстовое содержимое элементов должно совпадать с нашими двумя записями. test/components/Voting_spec.jsx import React from 'react'; import ReactDOM from 'react-dom'; import { renderIntoDocument, scryRenderedDOMComponentsWithTag } from 'react-addons-test-utils'; import Voting from '../../src/components/Voting'; import {expect} from 'chai'; describe('Voting', () => { it('renders a pair of buttons', () => { const component = renderIntoDocument( <Voting button'); expect(buttons.length).to.equal(2); expect(buttons[0].textContent).to.equal('Trainspotting'); expect(buttons[1].textContent).to.equal('28 Days Later'); }); }); Запускаем тест и проверяем: npm run test При клике на любую кнопку компонент должен вызвать callback-функцию. Она должна быть передана компоненту в виде свойства, как и пара записей. Добавим в тест соответствующую проверку. Эмулируем клик с помощью объекта Simulate из тестовых утилит React: test/components/Voting_spec.jsx import React from 'react'; import ReactDOM from 'react-dom'; import { renderIntoDocument, scryRenderedDOMComponentsWithTag, Simulate } from 'react-addons-test-utils'; import Voting from '../../src/components/Voting'; import {expect} from 'chai'; describe('Voting', () => { // ... it('invokes callback when a button is clicked', () => { let votedWith; const vote = (entry) => votedWith = entry; const component = renderIntoDocument( <Voting button'); Simulate.click(buttons[0]); expect(votedWith).to.equal('Trainspotting'); }); }); Написать этот тест не сложно. Для кнопок нам лишь нужен обработчик onClick, вызывающий vote с правильной записью: src/components/Voting.jsx import React from 'react'; export default React.createClass([];, render: function() [];, isDisabled: function() { return !!this.props.hasVoted; }, render: function() return <div > Добавим небольшой label на кнопку, который будет становиться видимым при получении свойства hasVoted. Сделаем вспомогательный метод hasVotedFor, который будет решать, нужно ли его отрисовывать: src/components/Voting.jsx import React from 'react'; export default React.createClass( getPair: function() return this.props.pair, isDisabled: function() { return !!this.props.hasVoted; }, hasVotedFor: function(entry) , isDisabled: function() { return !!this.props.hasVoted; }, hasVotedFor: function(entry) [];, isDisabled: function() { return !!this.props.hasVoted; }, hasVotedFor: function(entry) { return this.props.hasVoted > А сам компонент голосования теперь просто принимает решение, какой из двух компонентов нужно отрисовать: src/components/Voting.jsx import React from 'react'; import Winner from './Winner'; import Vote from './Vote'; export default React.createClass({ render: function() { return <div> {this.props.winner ? <Winner > Обратите внимание, что в компонент победителя добавлен ref. Мы будем использовать его в модульных тестах для получения необходимого DOM-элемента. У нас готов чистый компонент голосования! Заметьте, мы до сих пор не реализовали никакую логику: есть только кнопки, которые пока ничег

Метки:  

[Из песочницы] Правильное использование require в node.js

Среда, 15 Июня 2016 г. 19:24 + в цитатник
Предисловие Не так давно проект, на котором я работаю в данный момент, начал использовать модульную систему ES2015. Я не буду заострять внимание на этой технологии JavaScript, т.к статья совсем не об этом, а о том как технология сподвигла меня к одной мысли. Как многие знают, ES2015 Modules представляют собой импортирование/экспортирование скриптов крайне схожее по синтаксису с python и многими другими языками программирования. Пример: // Helper.js export function includes(array, variable) { return array.indexOf(variable) Helper'; assets(includes([1,2,3], 2), true); Все, кто интересовался модулями JavaScript знают, что импортирование и экспортирование возможно только на верхнем уровне модуля (файла с кодом). Следующий грубый пример кода вызовет ошибки: // sendEmail.js export default function sendEmails(emails_list) { import sender from 'sender'; export sender; // сделать что-то } Exception: SyntaxError: import/export declarations may only appear at top level of a module В отличие от ES2015 Modules — в модульной системе node.js импортирование и экспортирование возможны на любом уровне вложенности. Аналогичный код на node.js не вызовет ошибку: // sendEmail.js module.exports = function sendEmails(emails_list) { const sender = require('sender'); exports.sender = sender; // сделать что-то } Преимущество такого способа в том, что модули необходимые в обработчике явно импортированы внутри и не засоряют пространство имен модуля (особенно актуально, если импортируемый модуль нужен только в одном обработчике). Так же появляется возможность отложенного экспортирования данных модуля. Основные минусы: Об отсутствии модуля вы узнаете только во время вызова соответствующего обработчика Путь к импортироемому модулю может измениться, что приведет к изменению в каждом месте импортирования (например, в вашем модуле, в различных обработчиках используется lodash/object/defaults и вы решили обновиться до 4.x версии, где подключать нужно lodash/defaults). Разбор полетов В большинстве задач для которых используется node.js — front-end или основной веб-сервер, и высокая нагрузка на node.js частое явление. Пропуская способность вашего сервера должны быть максимально возможная. Измерение пропускной способности Для измерения пропускной способности веб-сервера используется великолепная утилита от Apache — ab. Если вы еще с ней не знакомы, то настоятельно рекомендую это сделать. Код веб-сервера одинаков за исключением обработчиков. Тест запускался на node.js 6.0 с использованием модуля ifnode, сделанного на базе express Импортирование модулей непосредственно в обработчик Код: const app = require('ifnode')(); const RequireTestingController = app.Controller({ root: '/', map: { 'GET /not_imported': 'notImportedAction' } }); RequireTestingController.notImportedAction = function(request, response, next) { const data = { message: 'test internal and external require' }; const _defaults = require('lodash/object/defaults'); const _assign = require('lodash/object/assign'); const _clone = require('lodash/lang/clone'); response.ok({ _defaults: _defaults(data, { lodash: 'defaults' }), _assign: _assign(data, { lodash: 'assign' }), _clone: _clone(data) }); }; Результат: $ ab -n 15000 -c 30 -q "http://localhost:8080/not_imported" Server Hostname: localhost Server Port: 8080 Document Path: /not_imported Document Length: 233 bytes Concurrency Level: 30 Time taken for tests: 4.006 seconds Complete requests: 15000 Failed requests: 0 Total transferred: 6195000 bytes HTML transferred: 3495000 bytes Requests per second: 3744.32 [#/sec] (mean) Time per request: 8.012 [ms] (mean) Time per request: 0.267 [ms] (mean, across all concurrent requests) Transfer rate: 1510.16 [Kbytes/sec] received Percentage of the requests served within a certain time (ms) 50% 6 66% 7 75% 8 80% 8 90% 10 95% 15 98% 17 99% 20 100% 289 (longest request) Импортирование модулей в начале файла Код: const app = require('ifnode')(); const _defaults = require('lodash/object/defaults'); const _assign = require('lodash/object/assign'); const _clone = require('lodash/lang/clone'); const RequireTestingController = app.Controller({ root: '/', map: { 'GET /already_imported': 'alreadyImportedAction' } }); RequireTestingController.alreadyImportedAction = function(request, response, next) { const data = { message: 'test internal and external require' }; response.ok({ _defaults: _defaults(data, { lodash: 'defaults' }), _assign: _assign(data, { lodash: 'assign' }), _clone: _clone(data) }); }; Результат: $ ab -n 15000 -c 30 -q "http://localhost:8080/already_imported" Server Hostname: localhost Server Port: 8080 Document Path: /already_imported Document Length: 233 bytes Concurrency Level: 30 Time taken for tests: 3.241 seconds Complete requests: 15000 Failed requests: 0 Total transferred: 6195000 bytes HTML transferred: 3495000 bytes Requests per second: 4628.64 [#/sec] (mean) Time per request: 6.481 [ms] (mean) Time per request: 0.216 [ms] (mean, across all concurrent requests) Transfer rate: 1866.83 [Kbytes/sec] received Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 7 90% 8 95% 14 98% 17 99% 20 100% 38 (longest request) Анализ результатов Импортирование модулей в начале файла уменьшило время одного запроса на ~23%(!) (в сравнение с импортированием непосредственно в обработчик), что весьма существенно. Такая большая разница в результатах кроется в работе функции require. Перед импортированием, require обращается к алгоритму поиска абсолютного пути к запрашиваемому компоненту (алгоритм описан в документации node.js). Когда путь был найден, то require проверяет был ли закеширован модуль, и если нет — не делает ничего сверхестественного, кроме вызова обычного fs.readFileSync для .js и .json форматов, и недокументированного process.dlopen для загрузки C++ модулей. Note: пробовал "прогревать" кеш для случая с непосредственным импортированием модулей в обработчик (перед запуском утилиты ab, модули были уже закешированы) — производительность улучшалась на 1-2%. Выводы Если вы используете node.js, как сервер (нет разницы какой — TCP/UDP или HTTP(S)), то: Импортирование всех модулей необходимо делать в начале файла, чтобы избегать лишних синхронных операций связанных с загрузкой модулей (один из главных анти-паттернов использования node.js как асинхронного сервера). Вы можете не тратить ресурсы на вычисление абсолютного пути запрашиваемого модуля (это и есть основное место для потери производительности).

Метки:  

Книга «Android для разработчиков»

Среда, 15 Июня 2016 г. 06:41 + в цитатник
Привет, Хаброжители! У нас вышло 3-е издание книги Пола и Харви Дейтл, Александера Уолда: Предлагаем подробно ознакомится с разработкой приложений для смартфонов и планшетов Android с использованием Android Software Development Kit (SDK). Многие навыки программирования для Android, представленные в книге, также применимы к разработке приложений для Android Wear и Android TV. В книге представлены передовые технологии разработки мобильных приложений для профессиональных программистов. В основу книги заложен принцип разработки, ориентированной на приложения, — концепции разработки продемонстрированы на примере полностью работоспособных приложений Android, а не фрагментов кода. Каждая из глав 2–9 начинается с вводной части, в которой вкратце описано разрабатываемое приложение. Затем приводятся результаты тестирования приложения и обзор технологий, применяемых в процессе его разработки. Далее выполняется подробный анализ исходного кода приложения. Целевая аудитория Предполагается, что читатели этой книги знают язык Java и имеют опыт объектно-ориентированного программирования. Также предполагается, что читатель знаком с XML — как вы увидите, проекты Android содержат много файлов XML, хотя программист часто работает с XML в редакторе, в основном скрывающем значительную часть XML. Мы используем только завершенные рабочие приложения, поэтому, даже не зная Java, но имея опыт объектно-ориентированного программирования на С++, C#, Swift или Objective-C, вы сможете быстро освоить излагаемый в книге материал, а заодно узнать много полезного о Java и объектно-ориентированном программировании. Эта книга не является учебником по Java. Особенности книги Разработка, ориентированная на приложения. В каждой из глав 2–9 представлено одно полное приложение — рассмотрены функции приложения, приведены снимки экрана выполняющегося приложения, результаты тестовых запусков и обзор технологий и архитектуры, используемых при создании приложения. Затем мы строим графический интерфейс приложения, представляем его полный исходный код и проводим подробный анализ этого кода; обсуждаем концепции, применяемые в программировании, и демонстрируем функциональность Android API, используемую при создании приложения. Android 6 SDK. В книге рассматриваются новые возможности Android 6 SDK (Software Development Kit). Android Studio IDE. Бесплатная среда Android Studio (созданная на базе IntelliJ IDEA Community Edition) в настоящее время является основной интегрированной средой, рекомендуемой для разработки приложений Android (исходные средства разработки Android работали на базе Eclipse IDE). Среда Android Studio в сочетании с бесплатным пакетом Android Software Development Kit (SDK) и бесплатным пакетом Java Development Kit (JDK) предоставляет все необходимое для создания, запуска и отладки приложений Android, поддержки их распространения (например, отправки в магазин Google Play) и т. д. Инструкция по поводу загрузки и установки этих продуктов приведена в разделе «Подготовка». Материальный дизайн. В Android 5 компания Google представила новый стиль приложений, основанный на спецификации материального дизайна. В этой спецификации Google приводит обзор целей и принципов материального дизайна, а также подробную информацию по методам анимации, стилевому оформлению экранных элементов, позиционированию элементов, использованию конкретных компонентов интерфейса пользователя, стандартным схемам взаимодействия с пользователем, доступности, интернационализации и т. д. В настоящее время компания Google использует принципы материального дизайна как в своих мобильных приложениях, так и в приложениях для браузеров. Материальный дизайн — весьма обширная тема. В этой книге мы сосредоточимся на следующих его аспектах: Использование встроенных материальных тем Android — темы гарантируют, что внешний вид встроенных компонентов Android будет соответствовать принципам материального дизайна. Использование встроенных шаблонов приложений Android Studio — эти шаблоны были разработаны компанией Google в соответствии с принципами материального дизайна. Использование компонентов интерфейса пользователя, рекомендованных в спецификации Google для конкретных целей, таких как FloatingActionButton, TextInputLayout и RecyclerView. Поддержка и библиотеки совместимости. При использовании новых возможностей Android разработчики часто сталкиваются с проблемой обеспечения обратной совместимости с более ранними платформами Android. Многие новые возможности Android теперь вводятся через библиотеки поддержки. Это позволяет разработчику использовать новые возможности в приложениях, ориентированных как на современные, так и на старые платформы Android. К числу таких библиотек принадлежит и AppCompat. Шаблоны приложений в Android Studio были обновлены; теперь в них используется библиотека AppCompat и ее темы, что позволяет новым приложениям, которые вы создаете, работать на большинстве устройств на базе Android. Если вы создаете приложения, изначально ориентированные на библиотеку AppCompat, вам не придется изменять реализацию кода, если вы захотите поддерживать старые версии Android в более широкой аудитории пользователей. REST-совместимые веб-сервисы и JSON. В главе 7 представлено приложение Weather Viewer, демонстрирующее использование веб-сервисов с поддержкой архитектурного стиля REST (Representational State Transfer) — в данном случае сервиса получения 16-дневного прогноза погоды с сайта OpenWeatherMap.org. Этот веб-сервис возвращает прогноз в формате JSON (JavaScript Object Notation) — популярном текстовом формате обмена данными, используемом для представления объектов в виде пар «ключ—значение». Приложение также использует классы из пакета org.json для обработки ответа веб-сервиса. Разрешения Android 6.0. В Android 6.0 используется новая модель разрешений, разработанная для удобства пользователя. До выхода Android 6.0 пользователь обязан был во время установки заранее предоставить все разрешения, которые могли когда-либо понадобиться приложению. Нередко это отпугивало пользователей от установки приложений. В новой модели приложение устанавливается, не запрашивая никаких разрешений. Вместо этого пользователю предлагается запросить разрешение только при первом использовании соответствующей возможности. Глава 5 знакомит читателя с новой моделью разрешений и демонстрирует, как в ней запросить у пользователя разрешение на сохранение изображения на внешнем носителе. Фрагменты. Начиная с главы 4 мы будем использовать фрагменты для создания и управления частями графического интерфейса каждого фрагмента. Объединяя несколько фрагментов, можно создавать интерфейсы, эффективно использующие пространство экрана планшетов. Разработчик может легко заменять фрагменты, что делает графический интерфейс более динамичным; пример переключения фрагментов рассматривается в главе 9. Паттерн View-Holder, компоненты ListView и RecyclerView. Каждое из приложений в главах 7–9 отображает прокручиваемый список данных. В главе 7 данные отображаются в списке ListView; также в ней описан паттерн View-Holder, повышающий скорость прокрутки за счет повторного использования компонентов графического интерфейса, выходящих за пределы экрана. При работе с ListView применение паттерна View-Holder желательно, но не обязательно. В главах 8 и 9 данные выводятся в более гибком и эффективном компоненте RecyclerView, для которого паттерн View-Holder обязателен. Печать. Возможности печати из приложений продемонстрированы на примере класса PrintHelper (глава 5), входящего в инфраструктуру печати Android. Класс PrintHelper предоставляет пользовательский интерфейс для выбора принтера, метод для проверки того, поддерживает ли заданное устройство печать, а также метод для печати объектов Bitmap. Класс PrintHelper является частью библиотеки Android Support Library. Режим погружения. Панель состояния в верхней части экрана и кнопки меню в нижней части можно скрыть, чтобы ваши приложения могли использовать большую часть экрана. Чтобы получить доступ к панели состояния, пользователь проводит пальцем от верхнего края экрана, а к системной панели с кнопками Back, Home и Recent Apps — от нижнего края. Тестирование на смартфонах Android, планшетах и в эмуляторе. Для достижения оптимального результата приложения следует тестировать на физических смартфонах и планшетах Android. Полезную информацию также можно получить при тестировании в эмуляторе Android (см. раздел «Подготовка»), однако эмуляция создает существенную нагрузку на процессор и может работать медленно, особенно в играх с большим количеством подвижных объектов. В главе 1 перечислены некоторые функции Android, не поддерживаемые эмулятором. Cloud Test Lab. Google работает над новым сервисом Cloud Test Lab — сайтом для тестирования приложений на широком спектре устройств, ориентаций устройства, локальных контекстов, языков и состояний сети. Вы сможете проводить автоматизированные тесты и получать подробные отчеты со снимками экранов и видеороликами, а также протоколами ошибок, которые помогут найти проблемы и улучшить приложения. Android Wear и Android TV. Android Wear работает на «умных часах»; Android TV работает на некоторых умных телевизорах и медиаплеерах, подключаемых к телевизору (обычно кабелем HDMI). Многие приемы программирования для Android, представленные в книге, также относятся и к разработке приложений для Android Wear и Android TV. Android SDK предоставляет эмуляторы для Android Wear и Android TV, поэтому вы сможете тестировать свои приложения для этих платформ, даже если у вас нет самих устройств. Мультимедиа. В приложениях используются разнообразные мультимедийные возможности Android, включая графику, изображения, покадровую анимацию, анимацию и работу с аудио. Отправка приложений в Google Play. В главе 10 описан процесс регистрации в Google Play и настройки учетной записи для продажи приложений. Вы узнаете, как подготовить приложение к отправке в Google Play, как установить цену на приложение, и познакомитесь с возможностями монетизации приложений через размещение рекламы и внутренние продажи. Также будут представлены ресурсы, которые могут использоваться для маркетинга приложений. Главу 10 можно читать после главы 1. Рецензенты книги Мы хотим поблагодарить рецензентов этого и двух предыдущих изданий книги. Они тщательно проверили текст и предоставили множество рекомендаций по его улучшению: Пол Бойстерьен (Paul Beusterien), главный специалист компании Mobile Developer Solutions; Эрик Дж. Боуден (Eric J. Bowden), главный управляющий компании Safe Driving Systems, LLC; Тони Кантрелл (Tony Cantrell) (Северо-западный технический колледж штата Джорджия); Иэн Дж. Клифтон (Ian G. Clifton), независимый подрядчик, разработчик приложений Android и автор книги «Android User Interface Design: Implementing Material Design for Developers, 2nd Edition»; Даниэль Гэлпин (Daniel Galpin), энтузиаст Android Гэннон и автор книги «Intro to Android Application Development»; Джим Хэзевэй (Jim Hathaway), разработчик из компании Kellogg; Дуглас Джонс (Douglas Jones), старший инженер-программист, компания Fullpower Technologies; Чарльз Ласки (Charles Lasky), муниципальный колледж Нагаутук; Энрике Лопес-Манас (Enrique Lopez-Manas), старший специалист по архитектуре Android и преподаватель информатики в Университете Алькала, Мадрид; Себастиан Никопп (Sebastian Nykopp), главный архитектор, компания Reaktor; Майкл Пардо (Michael Pardo), разработчик Android, компания Mobiata; Ронан «Зеро» Шварц (Ronan «Zero» Schwarz), директор по информационным технологиям, компания OpenIntents; Ариджит Сенгупта (Arijit Sengupta), Государственный университет Райта; Дональд Смит (Donald Smith), Колумбийский колледж; Хесус Убальдо (Jesus Ubaldo), Кеведо Торреро, Университет штата Висконсин, Парксайд; Дон Уик (Dawn Wick), Юго-Западный муниципальный колледж; Фрэнк Сю (Frank Xu), Университет Гэннон. Об авторах Пол Дж. Дейтел (Paul J. Deitel), генеральный и технический директор компании Deitel & Associates, Inc., окончил Массачусетский технологический институт (MIT) по специальности «Информационные технологии» (Information Technology). Обладатель сертификатов Java Certified Programmer, Java Certified Developer и Oracle Java Champion. Пол также получил премию Microsoft Most Valuable Professional (MVP) по C# в 2012–2014 годах. В Deitel & Associates, Inc. он провел сотни занятий по всему миру для корпоративных клиентов, включая Cisco, IBM, Siemens, Sun Microsystems, Dell, Fidelity, NASA (Космический центр имени Кеннеди), Национальный центр прогнозирования сильных штормов, ракетный полигон Уайт-Сэндз, Rogue Wave Software, Boeing, SunGard Higher Education, Stratus, Cambridge Technology Partners, One Wave, Hyperion Software, Adra Systems, Entergy, CableData Systems, Nortel Networks, Puma, iRobot, Invensys и многих других. Пол и его соавтор, д-р Харви М. Дейтел, являются авторами всемирно известных бестселлеров — учебников по языкам программирования, предназначенных для начинающих и для профессионалов, а также видеокурсов. Харви М. Дейтел (Dr. Harvey M. Deitel), председатель и главный стратег компании Deitel & Associates, Inc., имеет 50-летний опыт работы в области информационных технологий. Он получил степени бакалавра и магистра Массачусетского технологического института и степень доктора философии Бостонского университета. В 1960-е годы он работал в группах, занимавшихся созданием различных операционных систем IBM, в Advanced Computer Techniques и Computer Usage Corporation, а в 1970-е годы занимался разработкой коммерческих программных систем. Харви имеет огромный опыт преподавания в колледже и занимал должность председателя отделения информационных технологий Бостонского колледжа. В 1991 году вместе с сыном — Полом Дж. Дейтелом — он основал компанию Deitel & Associates, Inc. Харви с Полом написали несколько десятков книг и выпустили десятки видеокурсов LiveLessons. Написанные ими книги получили международное признание и были изданы на китайском, корейском, японском, немецком, русском, испанском, французском, польском, итальянском, португальском, греческом, турецком языках и на языке урду. Дейтел провел сотни семинаров по программированию в крупных корпорациях, академических институтах, правительственных и военных организациях. Александер Уолд (Alexander Wald), практикант в компании Deitel, помог нам преобразовать книгу и приложения для Android 4.3 и 4.4 с использованием Eclipse на Android 6 с использованием Android Studio. В настоящее время Александер собирается получить ученую степень бакалавра в области «Информационные технологии» в Уорчестерском политехническом институте с непрофильным образованием в области электротехники. Он заинтересовался математикой и наукой в раннем возрасте и пишет программный код около 9 лет. Его вдохновляет страсть к творчеству и новшествам, а также желание поделиться своими знаниями с другими. Более подробно с книгой можно ознакомиться на сайте издательства Оглавление Отрывок Для Хаброжителей скидка 25% по купону — Android for Programmers По факту оплаты бумажной книги отправляем на e-mail электронные версии книг, при покупке электронной книги — все доступные версии отправляются пользователям.

Метки:  

Хакер продает базу данных с 32 миллионами учетных записей Twitter

Суббота, 11 Июня 2016 г. 00:59 + в цитатник
По сообщению издания The Hacker News, неизвестный хакер продает учетные записи более чем 32 миллионов пользователей Twitter — цена архива составляет 10 биткоинов (больше $5800 по текущему курсу). Информация об архиве скомпрометированных учетных записей сервиса микроблогов была впервые опубликована в блоге проекта LeakedSource — этот сайт собирает информацию об «утекших» в сеть данных различных сервисов. Представители проекта в блоге заявили о том, что копию архива им передал хакер под ником Tessa88 — на прошлой неделе он опубликовал базу данных, включающую учетную информацию 1 млн пользователей соцсети «ВКонтакте» (представители компании позднее заявили, что база старая, а пользователи были оповещены). Человек с таким же псевдонимом «слил» базу на 400 с лишним млн аккаунтов MySpace в конце мая. База данных с учетными данными пользователей Twitter включает имена пользователей, почтовые адреса (иногда и дополнительный email), а также хранящиеся в незашифрованном виде пароли — всего для более чем 32 млн аккаунтов. Представители Twitter категорически отвергли возможность взлома, заявив, что «эти учетные данные были получены не в результате утечки из Twitter», а системы компании «не были взломаны». Представители LeakedSource убеждены, что утечка данных стала результатом работы зловредного софта. «Десятки миллионов людей “подхватили” вирус, который перехватывал введенные учетные данные социальных сетей, включая Twitter, в браузерах вроде Chrome и Firefox и отправлял их хакерам». По мнению экспертов Positive Technologies, для похищения учетных данных настолько большого количества пользователей соцсети злоумышленникам необходимо было создать огромный ботнет — при заявленном числе скромпрометированных учетных записей, превышающем 32 млн, зловредный софт должен был быть каким-то образом установлен на десятки миллионов компьютеров (до 100 млн машин). Создание такого ботнета маловероятно — теоретически это возможно, например, помощью эксплуатации низкоуровневых и массовых уязвимостей на протяжении долгого времени. Однако в данном случае вероятнее применение сценария с кросс-проверкой учетных записей ранее уже «утёкших» из других сервисов. Редакция The Hacker News вспоминает в этой связи ситуацию со взломом Twitter-аккаунта основателя соцсети Facebook Марка Цукерберга. Хакерам удалось получить его учетные данные с помощью взлома другой соцсети LinkedIn — им удалось получить хешированную версию пароля Цукерберга и взломать его. Этот же пароль использовался в Twitter и Pinterest-аккаунтах гендиректора Facebook. Поэтому существует вероятность того, что в базу данных, продаваемую хакером, входят учетные записи и пароли, похищенные ранее в ходе других взломов (LinkedIn, MySpace, Tumblr), а непосредственного взлома Twitter не происходило. Тем не менее, пока происхождение утекших учетных данных неизвестно, эксперты Positive Technologies рекомендуют всем пользователям Twitter сменить пароль — и если он использовался и на других сайтах, то сменить пароли и к ним.

Метки:  

Опыт использования Liferay Portal в eСommerce

Понедельник, 06 Июня 2016 г. 19:17 + в цитатник
Многие из нас уже не раз писали разного рода магазины. Но большие e-commerce проекты в быстро растущей и развивающейся компании разрабатывать приходится нечасто. К таким решениям предъявляются дополнительные требования, такие как конфигурируемость, адаптивность к изменениям, возможности встраивания в другие системы и прочее. Для написания такого решения компания Netcracker использовала Liferay Portal фреймворк. В итоге получили достаточно преимуществ, но и без проблем не обошлось.
Подробнее под катом

Метки:  

Дерево процессов

Суббота, 04 Июня 2016 г. 19:39 + в цитатник
Вывод комадлета Get-Process для восприятия не слишком удобен — просто список, в котором данные ко всему прочему отражены в стиле стран ближнего востока, то бишь справа налево, подается Microsoft как идеолгоически верный путь к пониманию сути того, что творится в процессах. Речь не о том как с помощью PowerShell палить вирусы, а об отсутствии визуального представления какой процесс какого Ерофеича родил, чай, ведь уже пятая версия PowerShell'а, а древовидного представления по-прежнему нема, да и в ближайшей перспективе, должно быть, не предвидется. И здесь из зрительного зала раздается: «А нафига нам древовидное представление процессов в PowerShell, когда есть ProcessExplorer, на худой конец — pslist?» Во-первых, GUI для консольщика как серпом по яйцам, во-вторых, какой резон разводить зоопарк из набора сторонних утилит, когда наличие PowerShell по сути является синонимом «уже все есть»? — остается лишь творить под цвет своих фломастеров. Преамбула приобретает некий сюрреалистический оттенок, да и рискует затянуться, если продолжать в том же роде, так что готовим фломастеры… Сюрприз! Если кто-то из читающих раскатал губу на WMI, то может смело закатывать ее фломастером обратно, ибо речь опять-таки пойдет о рефлексии, точнее не столько о ней самой, сколько о достижении цели через нее. Кудряво сказано, но да ладно. Как подсказывает Кэп, для построения дерева процессов нужно знать такие параметры, как имя процесса, его PID, а также PPID. Последний служит отправной точной при определении отпрыска от родителя, причем получить оный в Windows можно как миниму тремя приемами дзюдо: счетчики производительности, WMI и NtQuerySystemInformation. Первый (ровно как и второй) идет лесом, так как в грубом приближении является оберткой над NtQuerySystemInformation, токмо с тормозами в придачу — показатель варьируется от начинки ПК, но это тема отдельного разговора. Открываем Vim (или что у кого там любимое) и пишем: Set-Variable ($$ = [Regex].Assembly.GetType( 'Microsoft.Win32.NativeMethods' ).GetMethod('NtQuerySystemInformation')).Name $$ Итак, мы определили переменую $NtQuerySystemInformation. Теперь нужно получить указатель на структуру SYSTEM_PROCESS_INFORMATION, в Win7 x86 выглядящую так: +0x000 NextEntryOffset : Uint4B +0x004 NumberOfThreads : Uint4B +0x008 WorkingSetPrivateSize : _LARGE_INTEGER +0x010 HardFaultCount : Uint4B +0x014 NumberOfThreadsHighWatermark : Uint4B +0x018 CycleTime : Uint8B +0x020 CreateTime : _LARGE_INTEGER +0x028 UserTime : _LARGE_INTEGER +0x030 KernelTime : _LARGE_INTEGER +0x038 ImageName : _UNICODE_STRING +0x040 BasePriority : Int4B +0x044 UniqueProcessId : Ptr32 Void +0x048 InheritedFromUniqueProcessId : Ptr32 Void +0x04c HandleCount : Uint4B +0x050 SessionId : Uint4B +0x054 UniqueProcessKey : Uint4B +0x058 PeakVirtualSize : Uint4B +0x05c VirtualSize : Uint4B +0x060 PageFaultCount : Uint4B +0x064 PeakWorkingSetSize : Uint4B +0x068 WorkingSetSize : Uint4B +0x06c QuotaPeakPagedPoolUsage : Uint4B +0x070 QuotaPagedPoolUsage : Uint4B +0x074 QuotaPeakNonPagedPoolUsage : Uint4B +0x078 QuotaNonPagedPoolUsage : Uint4B +0x07c PagefileUsage : Uint4B +0x080 PeakPagefileUsage : Uint4B +0x084 PrivatePageCount : Uint4B +0x088 ReadOperationCount : _LARGE_INTEGER +0x090 WriteOperationCount : _LARGE_INTEGER +0x098 OtherOperationCount : _LARGE_INTEGER +0x0a0 ReadTransferCount : _LARGE_INTEGER +0x0a8 WriteTransferCount : _LARGE_INTEGER +0x0b0 OtherTransferCount : _LARGE_INTEGER Причем из всей структуры нас интересуют такие поля как NextEntryOffset, ImageName, UniqueProcessId и InheritedFromUniqueProcessId, так что для получения только этих четырех полей определять структуру в домене приложений слишком жирно — воспользуемся методами типа Marshal. Получаем указатель: if (($ta = [PSObject].Assembly.GetType( 'System.Management.Automation.TypeAccelerators' ))::Get.Keys -notcontains 'Marshal') { $ta::Add('Marshal', [Runtime.InteropServices.Marshal]) } $ret = 0 try { #задаем размер буфера минимальным значением $ptr = [Marshal]::AllocHGlobal(1024) if ($NtQuerySystemInformation.Invoke($null, ( $par = [Object[]]@(5, $ptr, 1024, $ret) )) -eq 0xC0000004) { #STATUS_INFO_LENGTH_MISMATCH $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3]) if ($NtQuerySystemInformation.Invoke($null, ( $par = [Object[]]@(5, $ptr, $par[3], 0) )) -ne 0) { throw New-Object InvalidOperationException('Что-то пошло не так...') } } } catch { $_.Exception } finally { if ($ptr -ne $null) { [Marshal]::FreeHGlobal($ptr) } } Указатель получили, читаем данные. Стоп! А ведь ImageName — это структура UNICODE_STRING, как быть? Делаем ход конем: $UNICODE_STRING = [Activator]::CreateInstance( [Object].Assembly.GetType( 'Microsoft.Win32.Win32Native+UNICODE_STRING' ) ) Вот теперь мы во всеоружии и готовы «читать» указатель. $len = [Marshal]::SizeOf($UNICODE_STRING) - 1 $tmp = $ptr $Processes = while (($$ = [Marshal]::ReadInt32($tmp))) #NextEntryOffset [Byte[]]$bytes = 0..$len{ [Marshal]::ReadByte($tmp, $ofb) $ofb++ } #конвертируем байты в UNICODE_STRING $gch = [Runtime.InteropServices.GCHandle]::Alloc($bytes, 'Pinned') $uni = [Marshal]::PtrToStructure( $gch.AddrOfPinnedObject(), [Type]$UNICODE_STRING.GetType() ) $gch.Free() New-Object PSObject -Property @{ ProcessName = if ([String]::IsNullOrEmpty(( $proc = $uni.GetType().GetField( 'Buffer', [Reflection.BindingFlags]36 ).GetValue($uni)) )) { 'Idle' } else { $proc } PID = [Marshal]::ReadInt32($tmp, 0x44) PPID = [Marshal]::ReadInt32($tmp, 0x48) } $tmp = [IntPtr]($tmp.ToInt32() + $$) } Переменная $Processes отныне хранит массив объектов PSObject, эдакие контейнеры для нужных нам данных. Теперь, согласно женевской конвенции, остается построить само дерево. function Get-ProcessChild { param( > После запуска получим в хосте древовидное представление процессов. Собственно, на этом вечерний эротический сеанс показ окончен, можно расходиться. Полный код#function Get-ProcessTree { <# .NOTES Вместо методов расширений .Where и .ForEach используются одноименные командлеты в целях совместимости с PS -lt v5 #> begin { Set-Variable ($$ = [Regex].Assembly.GetType( 'Microsoft.Win32.NativeMethods' ).GetMethod('NtQuerySystemInformation')).Name $$ $UNICODE_STRING = [Activator]::CreateInstance( [Object].Assembly.GetType( 'Microsoft.Win32.Win32Native+UNICODE_STRING' ) ) function Get-ProcessChild { param( System.Management.Automation.TypeAccelerators' ))::Get.Keys -notcontains 'Marshal') { $ta::Add('Marshal', [Runtime.InteropServices.Marshal]) } } process { try { $ret = 0 $ptr = [Marshal]::AllocHGlobal(1024) if ($NtQuerySystemInformation.Invoke($null, ( $par = [Object[]]@(5, $ptr, 1024, $ret) )) -eq 0xC0000004) { #STATUS_INFO_LENGTH_MISMATCH $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3]) if (($nts = $NtQuerySystemInformation.Invoke($null, ( $par = [Object[]]@(5, $ptr, $par[3], 0) ))) -ne 0) { throw New-Object InvalidOperationException( 'NTSTATUS: 0x{0:X}' -f $nts ) } } $len = [Marshal]::SizeOf($UNICODE_STRING) - 1 $tmp = $ptr $Processes = while (($$ = [Marshal]::ReadInt32($tmp))) ForEach-Object $ofb = 0x38{ [Marshal]::ReadByte($tmp, $ofb) $ofb++ } $gch = [Runtime.InteropServices.GCHandle]::Alloc($bytes, 'Pinned') $uni = [Marshal]::PtrToStructure( $gch.AddrOfPinnedObject(), [Type]$UNICODE_STRING.GetType() ) $gch.Free() New-Object PSObject -Property @{ ProcessName = if ([String]::IsNullOrEmpty(( $proc = $uni.GetType().GetField( 'Buffer', [Reflection.BindingFlags]36 ).GetValue($uni)) )) { 'Idle' } else { $proc } PID = [Marshal]::ReadInt32($tmp, 0x44) PPID = [Marshal]::ReadInt32($tmp, 0x48) } $tmp = [IntPtr]($tmp.ToInt32() + $$) } } catch { $_.Exception } finally { if ($ptr -ne $null) { [Marshal]::FreeHGlobal($ptr) } } } end { if ($Processes -eq $null) { break } $Processes | Where-Object { -not (Get-Process -Id $_.PPID -ea 0) -or $_.PPID -eq 0 } | ForEach-Object { "$($_.ProcessName) ($($_.PID))" Get-ProcessChild $_ } [void]$ta::Remove('Marshal') } #}

Метки:  

Сетевые технологии Интернета вещей

Вторник, 24 Мая 2016 г. 10:26 + в цитатник
Добрый день, уважаемые хабравчане! Сегодня мы бы хотели остановиться на описании различных сетевых технологий, разрабатывающихся для Интернета вещей. Интернет вещей (IoT, Internet of Things) становится следующим революционным скачком развития, сравнимым с изобретением парового двигателя или индустриализацией электричества. Сегодня цифровая трансформация переворачивает самые различные отрасли экономики и изменяет наше привычное окружение. При этом, как часто бывает в таких случаях, конечный эффект этих преобразований трудно спрогнозировать, находясь в начале пути. Начавшийся процесс, очевидно, не может быть равномерным и на сегодняшний день одни отрасли оказываются в большей степени готовы к изменениям, чем другие. К первым можно отнести потребительскую электронику, транспорт, логистику, финансовый сектор, ко вторым – например, сельское хозяйство. Хотя и здесь есть успешные пилотные проекты, обещающие интересные результаты. Проект TracoVino, одна из первых попыток использовать IoT в знаменитой долине Мозеля, старейшем винодельческом регионе современной Германии. В основе решения лежит облачная платформа, автоматизирующая все процессы в винограднике, от выращивания сырья до бутилирования. Данные, необходимые для принятия решений, поступают в систему от нескольких типов датчиков. Помимо определения температуры, влажности почвы и мониторинга окружающей среды, они могут определять количество солнечной радиации, кислотность почвы и содержание в ней биогенных элементов. Что это дает? TracoVino не только позволяет виноделам получить общее представление о состоянии их виноградника, но и проанализировать его определенные области, чтобы выявить проблемы, получить заблаговременную информацию о возможном заражении и даже получить прогнозы о качестве и количестве вина, что позволяет виноделам заключать форвардные контракты. Что еще можно подключить к сетям? К наиболее развитым сценариям использования IoT можно отнести «умные города». Согласно исследованиям Beecham Research, Pike Research, iSupply Telematics и министерства транспорта США на сегодняшний день в рамках реализации этих проектов по всему миру насчитывается более миллиарда устройств, отвечающих за те или иные функции в системах водоснабжения, управления городским транспортом, общественного здравоохранения и безопасности. Это умные парковки, оптимизирующие использование мест для стоянки, интеллектуальная система водоснабжения, следящая за качеством потребляемой жителями города воды, умные автобусные остановки, позволяющие получить точную информацию о времени ожидания нужного транспорта и многое другое. В промышленности уже функционируют сотни миллионов устройств, готовых к подключению. Среди них системы умного технического обслуживания и ремонта, логистического учета и безопасности, а также умные насосы, компрессоры и клапаны. Большое количество устройств задействовано в сферах энергетики и ЖКХ: многочисленные счетчики, элементы автоматики распределительных сетей, потребительское оборудование, электрозарядная инфраструктура и инфраструктура для возобновляемых и распределяемых источников энергии. В области здравоохранения к интернету вещей подключаются и будут подключены средства диагностики, мобильные лаборатории, различные имплантаты, устройства для телемедицины. Ожидается, что в ближайшие годы количество машинных подключений будет увеличиваться на 25% в год, а всего к 2021 году на планете будет 28 миллиардов подключенных устройств. Из них всего 13 миллиардов придется на привычные пользовательские гаджеты: смартфоны, планшеты, лэптопы и ПК – в то время как 15 миллиардов будут составлять пользовательские и промышленные устройства: разного рода датчики, терминалы для продаж, автомобили, табло, индикаторы и т.д. Несмотря на, казалось бы, поражающие воображение цифры из ближайшего будущего, и они не являются окончательными. IoT будет внедряться повсеместно, и чем дальше, тем больше устройств, простых и сложных, придется подключить. По мере развития технологий, а особенно под влиянием запуска сетей 5G после 2020 года, рост числа подключенных устройств пойдет стремительными темпами и очень скоро приблизится к 50 млрд. Массовый характер подключений и различные сценарии использования диктуют требования к сетевым технологиям IoT в самом широком диапазоне. Скорости передачи данных, задержки, надежность (гарантированность) передачи определяются особенностями конкретного применения. И тем не менее есть ряд общих целевых показателей, которые требуют от нас отдельно рассматривать сетевые технологии для IoT и их отличия от традиционных сетей мобильной связи. В первую очередь, стоимость реализации сетевой технологии в конечном устройстве должна быть в разы меньше существующих сегодня модулей GSM/WCDMA/LTE, используемых при производстве смартфонов и модемов, даже в самом доступном классе. Одна из причин, сдерживающих массовое внедрение подключенных устройств – высокая стоимость чипсета, реализующего полный стек сетевых технологий, включая передачу голоса и многие другие функции, не являющиеся необходимыми в большинстве IoT сценариев. Связанное с этим, но формулируемое отдельным, требование – низкое энергопотребление и продолжительное время автономной работы. Многие сценарии и области применения IoT предусматривают автономное питание подключенных устройств от встроенных элементов питания. Упрощение сетевых модулей и энергоэффективный дизайн позволяют добиться времени автономной работы до 10 лет при емкости элемента питания 5 Вт*ч. Таких показателей, в частности, удается достичь благодаря снижению объема передаваемых данных и использованию продолжительных периодов «молчания», в течение которых устройство не получает и не передает информацию и практически не потребляет электроэнергию. Впрочем, реализация конкретных механизмов отличается от технологии к технологии. Покрытие сети, еще одна характеристика, нуждающаяся в пересмотре. Сегодня покрытие мобильной сети обеспечивает достаточно устойчивую передачу данных в населенных пунктах, в том числе внутри помещений. Однако подключенные устройства могут находиться и там, где людей большую часть времени нет: отдаленные районы, протяженные железнодорожные перегоны, поверхность обширных водоемов, подвалы, изолированные бетонные и металлические короба, лифтовые шахты, контейнеры и т.п. Целевым ориентиром решения этой задачи, по мнению большинства участников IoT рынка, является улучшение бюджета линии на 20 dB относительно традиционных сетей GSM, являющихся лидером по покрытию среди мобильных технологий сегодня. Разные сценарии использования Интернета вещей в разных индустриях предполагают совершенно разные требования к связи. И речь не только о возможности быстрого масштабирования сети в плане числа требующих подключения устройств. Если в описанном нами примере «умного виноградника» были задействовано множество достаточно простых датчиков, то на промышленных предприятиях подключены будут весьма сложные роботы, выполняющие действия, а не просто фиксирующие определенные параметры окружающей среды. Можно вспомнить и про область здравоохранения, в частности про оборудование для телемедицины. Использование этих комплексов, предназначенных для проведения дистанционной диагностики, мониторинга сложных медицинских манипуляций и удаленного обучения с использованием видеосвязи в режиме реального времени несомненно будет предъявлять совершенно иные требования в плане задержек сигнала, передачи данных, надежности и безопасности. Технологии IoT должны быть достаточно гибкими, чтобы обеспечивать различный набор сетевых характеристик в зависимости от сценария использования, приоритизацию десятков и сотен различных видов сетевого трафика и оптимальное перераспределение ресурсов сети для сохранения экономической эффективности. Миллионы подключенных устройств, десятки сценариев использования, гибкое управление и контроль – все это должно быть реализовано в рамках единой сети. Решению поставленных задач посвящены многочисленные разработки последних лет в сфере беспроводной передачи данных, как связанные со стремлением адаптировать имеющиеся сетевые архитектуры и протоколы, так и с созданием новых системных решений с нуля. С одной стороны мы видим так называемые «капиллярные решения», довольно успешно решающие задачи IoT коммуникаций в рамках одного помещения или ограниченной территории. К таким решениям можно отнести популярные сегодня Wi-Fi, Bluetooth, Z-Wave, Zigbee и их многочисленные аналоги. С другой – современные мобильные технологии, которые, очевидно, находятся вне конкуренции с точки зрения обеспечения покрытия и масштабируемости хорошо управляемой инфраструктуры. Согласно исследованию Ericsson Mobility Report, покрытие GSM составляет 90% населенной территории планеты, сети WCDMA и LTE 65% и 40% соответственно при активно продолжающемся строительстве сетей. Шаги, предпринятые в рамках развития стандартов мобильной связи, в частности спецификации 3GPP Release 13 направлены как раз на достижение целевых для IoT показателей при сохранении преимуществ использования глобальной экосистемы. Эволюция этих технологий станет основой будущих модификаций стандартов мобильной связи, в том числе стандартов сетей пятого поколения (5G). Альтернативные технологии низкой мощности для нелицензируемого частотного спектра, в общем случае, направлены на более узкое применение. Необходимость создания новой инфраструктуры и закрытость технологий существенно сдерживают распространение подобных систем. Рассмотрим, какие расширения стандартов мобильной связи определены для включения в последнюю на сегодняшний день редакцию рекомендаций 3GPP Release 13. EC-GSM Рабочая группа GERAN, развивающая технологии GSM, предложила пакет расширенных функций под названием EC-GSM (варианты того же названия: EC-GPRS, EC-GSM-IoT). Данная технология предусматривает сравнительно небольшие изменения относительно базового GSM/GPRS/EDGE, что позволяет использовать подавляющее большинство установленных базовых станций этого стандарта без замены или модернизации аппаратного обеспечения. Приведем основные характеристики: Фактически, используется стандартная несущая GSM/GPRS, с изменениями, позволяющими увеличить бюджет линии, увеличить количество устройств и снизить стоимость реализации технологии в конечном устройстве. Основные привнесенные изменения: 1) Extended DRX (eDRX, Extended Discontinuous Reception) для GSM и Power Saving Mode (PSM) – снижение периодичности обязательных сигнальных сообщений, оптимизация интервалов приема и получения информации, поддержка длительных, до 52 мин., периодов «молчания», в течение которых устройство остается подключенным к сети, не передавая и не получая информацию. 2) Extended coverage – адаптация канального уровня сети, использующая, в том числе, многократное повторение передаваемой информации для улучшения покрытия на 20 dB по сравнению с традиционными системами. 3) Другие улучшения: упрощение сетевой сигнализации (отказ от поддержки той части сигнализации, которая обеспечивает совместную работу с WCDMA/LTE сетями); расширение механизмов аутентификации и безопасности соединения и др. Ключевое преимущество EC-GSM в готовности сетевой инфраструктуры (в большинстве случаев требуется только обновление программного обеспечения на узлах сети), а также в распространенности сетей стандарта GSM и их охвата. eMTC Вариант eMTC (встречаются также названия LTE-M, LTE Cat.M1) является адаптацией IoT для LTE сетей. Фокус по-прежнему на достижении целевых показателей массового IoT (стоимость, покрытие, срок автономной работы) при обеспечении максимальной совместимости с имеющейся у операторов сетевой инфраструктурой. Важное отличие технологии eMTC – высокая пропускная способность, до 1 Мбит/с в каждом направлении (от абонента и к абоненту). Самое время вспомнить про разнообразие сценариев использования IoT, к которым мы обращались в начале статьи. В определенных случаях такие скорости передачи данных будут явно востребованы. eMTC призван обеспечить снижение стоимости конечного IoT устройства за счет отказа от функциональности LTE, которая востребована и широко применяется в сетях мобильного широкополосного доступа (МШПД), но становится избыточной при массовом подключении IoT устройств. Это продолжение работы, начатой 3GPP в предыдущем релизе спецификаций (Release 12), определившей LTE Cat.0 для IoT. В eMTC также добавлены механизмы Extended DRX и PSM для LTE, которые решают задачу снижения энергопотребления аналогично тому, как это было показано выше для EC-GSM. Как и в случае с EC-GSM, eMTC имеет высокую степень готовности сетевой инфраструктуры и может быть развернута на существующих сетях LTE путем обновления ПО. Более того, сети МШПД и IoT могут сосуществовать и динамически перераспределять используемые ресурсы (частотный спектр, вычислительную мощность базовой станции и др.) в зависимости от типа и количества подключенных устройств и создаваемого ими трафика. NB-IoT Narrowband IoT (узкополосный IoT) – это относительно новое направление развития сетевых IoT технологий и несмотря на то, что его использование предусматривает тесное взаимодействие и интеграцию c LTE, речь все же идет о создании нового типа радиодоступа, характеристики которого имеют больше отличий, чем сходства с имеющимися технологиями. Ожидается, что существенная переработка протоколов канального уровня позволит снизить стоимость устройства NB-IoT по сравнению с LTE Cat.M1 на 90%. О поддержке технологии NB-IoT в своих продуктах уже заявили многие производители сетевого оборудования и абонентских модулей: Ericsson, Huawei, Nokia, Intel, Qualcomm, а также ведущие операторы связи, среди которых, например, Vodafone, Deutsche Telekom и China Unicom. Таким образом, с принятием финальной версии спецификаций EC-GSM, eMTC и NB-IoT, которое запланировано на июнь текущего года, участники рынка получат в свое распоряжение три эффективных инструмента развития сетей IoT. Каждый из них имеет свои особенности и преимущества в зависимости от конкретного сценария использования и характеристик той мобильной сети, на базе которой они будут развертываться. Однако в любом случае преимущества глобальной экосистемы, наличие и готовность развернутой сетевой и IT инфраструктуры, использование защищённого (лицензируемого) частотного спектра будут работать на снижение стоимости внедрения и эксплуатации. А значит, в ближайшем будущем нас ожидает взрывной рост проектов с их использованием. На этом закончим свой рассказ и благодарим хабравчан за внимание! В следующем посте мы сфокусируемся на технологических аспектах технологии NB-IoT.

Метки:  

Моя эпичная история настройки целей сайта на Битриксе

Вторник, 17 Мая 2016 г. 01:23 + в цитатник
Друзья, любите ли вы настройку целей так как «люблю» её я? Для меня это дело всегда непростое и муторное, в интернетах почти нет информации об этом, а тема, на мой взгляд, важная. Сегодня мой пост посвящается настройке целей сайта на Битриксе, если пост понравится, то расскажу про другие CMS 1.1. На кой вообще нужны цели на сайте? Цели необходимо настроить, чтобы не просто смотреть на график посещаемости, а понимать какой канал является эффективным, а какой нет. Приведу цитату из справки Google: “Цели являются отличным индикатором эффективности работы вашего сайта или приложения. Целью может быть любое действие, в котором вы заинтересованы, называемое конверсией. Вот некоторые примеры целей: покупка (для сайта электронной торговли), прохождение уровня игры (для мобильного игрового приложения), отправка контактной информации (для сайта по привлечению клиентов). Определение целей – важнейший компонент планирования аналитической оценки. Правильно выбранные цели позволяют получать важную информацию, такую как количество конверсий и коэффициент конверсии для сайта или приложения. Без этих сведений практически невозможно оценить эффективность онлайн-бизнеса и маркетинговых кампаний.” support.google.com/analytics/answer/1012040?hl=ru Настраивайте только те цели, которые действительно будете анализировать и отслеживать. Например, регистрация, отправка контактных данных, запрос обратного звонка и другие действия, которые потенциально ведут к продаже можно считать целями. Цель, которая срабатывает в момент просмотра посетителем двух страницы, либо пребывании на сайте более трёх минут, на самом деле целями не являются." 1.2. Какими бываю цели? Основные цели — покупка товара — отправка контактных данных — звонок Вспомогательные цели -просмотр карточки товара -просмотр контактов -просмотр 3 страниц 1.2. Нууу… у меня есть счётчики, зачем мне ещё цели? Несмотря на то, что у вас настроены счетчики статистики, они не смогут отследить все. Сервисы отслеживают в основном просмотры страниц. А самое вкусное остаётся за бортом, например клики, отправки форм, и другие более сложные события (например, “регистрация на сайте, подтвержденная по е-майл”) Итак, на рисунке мы видим, что “просмотры страниц” сразу отправляются в аналитику, и тут все в порядке “клики, отправки форм” можно отследить с помощью Google Tag Manager. На практике не всегда просто настроить отслеживание валидной отправки формы. “сложные события” — это то, что не удается отследить с помощью предыдущих средств. Для их отслеживания в код сайта в нужные места внедряются небольшие коды java script, которые и отправляют необходимую информацию в аналитику. Хочется отметить, что для каждой системы, которой необходима информация о свершении цели, необходимо вставить свою команду в код. И тут нас ждёт опасность: после того как уже все настроено, отлажено и проверено появляется необходимость отправлять данные куда-нибудь еще. И чтобы это сделать придется пройтись везде, где мы уже вставляли коды. Другими словами, сделать всю работу два раза. Именно в этом случае нам на помощь приходит Google Tag Manager. 1.3. Зачем нужен Google Tag Manager? Установка только одного кода, все остальные МОЖНО НУЖНО установить через него. — Позволяет отслеживать клики, клики по ссылкам, отправку форма и др. — Позволяет создавать собственные правила, чтобы вызывать нужные коды в нужное время. — Позволяет отправлять данные о достижении целей куда угодно. Благодаря Tag Manager вырисовывается более удобная и правильная схема настройки целей: Этот способ позволяет масштабировать проделанную вами работу на любые системы, в которых нужны данные о достижении ваших целей. 1.3.1. Как правильно установить Google Tag Manager? Единственно правильный путь — установка после открывающего тега body и не включая его ни в какие другие блоки. Тогда код будет срабатывать сразу. А все что нужно активировать по окончании сборки модели DOM или когда страница загрузится полностью можно легко настраивать правилами Tag Manager 1.3.2. Что обычно устанавливают через GTM? Все сторонние коды, например: — Коды счетчиков GA, YM — Доп сервисы UpToCall, Jivosite и т.п. — Коды ретаргетинга для соцсетей. и тд и тп 1.4. Какой код надо вставить на сайт для отслеживания достижения целей? Команды, которые нужно вставить очень просты 2. Настройка целей в Битрикс Честно, я бы все формы на сайте реализовал через «навесные сервисы», проблем было бы меньше. На нашем сайте стоит обработчик JotForm adverbs.ru/feedback и я вслепую могу настроить все цели. На CMS же все формы всегда реализованы по-разному и иногда, простите, не через то место :-) Здесь я постараюсь по шагам описать процесс настройки нескольких целей на примере реального проекта. Сразу скажу, что я не являюсь программистом на Битрикс. Если в моих словах ниже будут ошибки в терминологии или предложенных вариантах решения просьба не закидывать меня помидорами, а подсказать или поправить в комментариях. Буду мегаблагодарен :-) 2.1. Определимся со списком целей Прежде чем приступать к настройке целей необходимо определиться с самими целями. Не поленитесь и составьте список целей с их названием, описанием, ссылками, скриншотами и комментариями. Уверяю через месяц вы уже не вспомните что и зачем делали. После нескольких десятков итераций наш файл целей выглядит так: Забегая вперед скажу, что Google Tag Manager позволяет отслеживать много разных событий без правки кода сайта. Однако на практике все таки много целей приходится настраивать, добавляя дополнительные коды в исходный код сайта. 2.2. Куда в битрикс вставлять код? На каждом сайте и в каждой CMS это придется делать в разных местах. Если вы ничего не понимаете в программировании, то вам точно нужен программист. Более того скажу, что даже для любого сайта, написанного на Битрикс, скорее всего вам придется вставлять коды в разные места. И даже для разных форм одного и того же сайта это будут различны места, особенно если над сайтом колдовали и шаманили несколько программистов в разное время :) Так приступим же, друзья, к практике:-) Разбирать будем на примере “живого” проекта a-tria.ru. Цели, описанные в таблице выше, как раз для него. Входим в админку сайта. 2.3. Настройка цели “Заказать звонок” 2.3.1. Вставка кода цели “Заказать звонок” Цель должна срабатывать НЕ при клике на кнопку, а при успешной отправке данных формы. Обычно, если какая то часть сайта представляет собой компонент, то при наведении на нее курсора мыши появляется всплывающее меню. Как на картинке ниже. Но при наведении на форму заказа звонка ничего не появляется, значит можно предположить, что форма каким-то образом “зашита” в шаблон сайта. Открываем для редактирования шаблон сайта. Находим в нем текст “Заказать обратный звонок” И видим, что ссылка открывает страницу по адресу /modal-forms/call-back/ Ну что ж, заглянем туда Здесь мы видим что в шаблон этой страницы включен компонент z-labs:ajax.call_order Его можно найти вот по этому пути: /bitrix/components/z-labs/ajax.call_order Но то, что нам нужно нашлось в шаблоне этого компонента чуть глубже, вот тут: /bitrix/components/z-labs/ajax.call_order/templates/call-back/template.php После просмотра файла было найдено место, где выводится сообщение об успешной отправке формы. Рядом с ним мы и вставили код, который отправляет данные о свершении целевого действия: yaCounterXXXXXXXX.reachGoal(‘forms_zvonok’);, где XXXXXXXX- номер вашего счетчика Яндекс метрики forms_zvonok — идентификатор цели в вашей Яндекс метрике. Более подробная информация о передаче информации о достижении цели в Яндекс метрику: yandex.ru/support/metrika/objects/reachgoal.xml ga(‘send’, ‘event’, ‘forms’, ‘zvonok’); , где ‘event’ — типа обращения ecent указывает, что мы отправляем в аналитику событие ‘forms’ — категория, ‘zvonok’ — действие на которое настроены цели в вашей аналитике. Более подробная информация об отслеживании событий в Google Analytics: developers.google.com/analytics/devguides/collection/analyticsjs/events?hl=ru 2.3.2. Настройка цели “Заказать звонок” в Google Analytics В Google Analytics переходим во вкладку “Администратор” > “Цели” — Указываем, что цель будет “Специальная”. — Указываем название: “Обратный звонок” и тип цели “Событие”. — Указываем подробные сведения о цели. Категория “forms”, Действие: “zvonok” 2.3.3. Настройка цели “Заказать звонок” в Яндекс метрике В яндекс метрике процесс настройки цели не менее простой. Заходим в раздел “Настрока” > “Цели” Указываем “Название”: “Обратный звонок”, Тип условия: “JavaScript событие”, идентификатор цели: “forms_zvonok” 2.4. Настройка цели “Форма участвовать в акции” 2.4.1. Вставка кода цели “Форма участвовать в акции” Редактируем шаблон Текст “Участвовать в акции” нашли, но тут нет ссылок как в прошлый раз. Возможно нажатие обрабатывается подключаемым скриптом. Посмотрим, что подключается к этому файлу: Помимо стандартных скриптов, подключается какой-то script.js Здесь мы находим определение функции, которая будет срабатывать при клике на элемент с классам “callbutton”. Именно этот класс установлен на нужной нам кнопке. Ниже видим код отвечающий за отправку сообщения. Вставляем код, который отправляет данные о достижении цели. 2.4.2. Настройка цели “Форма участвовать в акции” в Google Analytics и Яндекс метрике Аналогичным образом добавляем цели в Яндекс и Гугл. В Яндекс метрике идентификатор цели “forms_akciya”, в гугл аналитике событие с идентификаторами “forms”, “akciya” 2.5. Как понять что код отправляет данные? Думаю, что любой программист скажет вам, что невозможно писать код, если у вас нет средств отладки. Далеко не все знают про это, но средства отладки есть и здесь. 2.5.1. Отладка в Яндекс.Метрике Для того, чтобы увидеть отправляются ли данные в Яндекс.Метрику вам необходимо в адресной строке браузера ввести адрес сайта, на котором вы настраиваете цели и добавить параметр отладки: www.site.ru/?_ym_debug=1 Открыть инспектор кода, вкладку “Console”. Когда вы совершите на сайте целевое действие, то увидите сообщения о том что данные отправляются. 2.5.2. Отладка в Google Analytics В Google Analytics немного иной способ проверить отправляются ли данные в аналитику. Для этого есть отчеты в “режиме реального времени”--> “События” Просматривая этот отчет, мы практически в ту же секунду увидим визуальное отображение при совершении целевого действия на сайте. Если его нет, значит что-то не так. 2.5.3. Отладка в Google Tag Manager Невероятно, но факт, в GTM также есть система отладки, причем достаточно неплохая. В интерфейсе рядом с кнопкой “Опубликовать” жмем на стрелочку. В открывшемся меню выбираем “Предварительный просмотр и отладка”. После перехода в режим отладки, в том же браузере нужно открыть сайт, для которого вы настраиваете цели. В этом же окне откроется отладочная панель GTM Здесь вы увидите все события, которые фиксирует Google Tag Manager, а также какие теги были активированы на эти действия. 3. Вместо послесловия... Мы разобрались с общей правильной схемой настройки целей и разобрали по шагам пример настройки двух целей реального проекта. Напоследок хочу дать несколько советов: — Делайте все постепенно, после каждого шага проверяя, что все сделано правильно и работает. — Идентификаторы для целей гугл и яндекс делайте одинаковые, с идентичным написанием. Если идентификатор для яндекс “forms_zvonok”, то для гугл идентификаторы должны быть “forms”, “zvonok”, иначе это приведет к путаннице. — Давайте понятные названия для целей, т.е. названия должны быть такие, чтобы любой человек посмотрев на них мог вам сказать что это за цели. Например, если цель срабатывает при отправке формы “заказать звонок”, то пусть название будет “Форма — Заказать звонок”. Если же цель срабатывает при нажатии на кнопку “Заказать звонок”, и не важно отправил он форму или нет, то назовите цель “Кнопка — Заказать звонок”. Либо придумайте любой другой понятный принцип именования. — Используйте только латинские маленькие буквы и знак подчеркивания в названии идентификаторов. Так будет меньше шансов ошибиться в написании и вы не потратите лишние пару часов на поиск багов. — Обязательно запишите все что вы сделали, например в таблицу настройки целей — Сначала лучше сделать меньше, но чтобы это четко работало, чем много, но работающее через раз и непонятно как. PS: буду благодарен за любые комментарии :-)

Метки:  

Шахматы льда и пламени

Понедельник, 16 Мая 2016 г. 16:09 + в цитатник
          Игра эта зовется кайвассой. Ее завезли в Дощатый город на волантинской торговой галере, а сироты разнесли ее вверх и вниз по Зеленой Крови. При дорнийском дворе все помешались на ней…           Десять фигур, каждая из которых ходит по-разному, а доска меняется с каждой игрой, смотря как игроки перемешают свои квадратики.                                                     Джордж Мартин «Пир стервятников»  Cyvasse — ещё одна игра родившаяся в художественном произведении. И как это обычно и бывает, дело вновь не обошлось без участия армии фанатов. Хотя автор и уделяет игре большое внимание (в «Танце с драконами», Тирион Ланистер только и делает, что в неё играет), детальное описание правил, всё же — не дело автора художественного произведения. Впрочем, за фанатами «не заржавело». Разнообразных реализаций «Кайвассы» десятки. Квадратные и гексоганальные — найдутся на любой вкус! Я хочу рассказать о той, что понравилась мне больше всего. Большинство версий «Кайвассы» (за все не скажу, мог что-то и пропустить) — это всё те же, привычные нам с детства, шахматы, только с катапультами и драконами. Да, фигуры ходят непривычно, а по доске, щедрой рукой, можно разбросать горы и водоёмы, но принципы самой игры не меняются — шахматное взятие и король, которого необходимо съесть. Zane Fisher подошёл к вопросу более творчески. На мой взгляд, его версия игры гораздо более глубока, в тактическом плане. За счёт чего? Вот давайте вместе и посмотрим. Большая «естественность» и продуманность — вот что в первую очередь бросилось мне в глаза. Посмотрите, как «конница» обходит «горы». Каждая фигура, в этой игре, может перемещаться не более чем на заданное число шагов и, выполняя «тихий ход», она может «поворачивать» как угодно, главное — не возвращаться на ранее пройденные клетки и не заступать на территорию занятую противником или «горами». Через клетки занятые своими фигурами проходить можно (исключением является «Слон», который не может проходить «сквозь» другие фигуры), нельзя лишь завершать на них ход. Это был вызов!Так уж получилось, что в чисто техническом плане, реализовать подобные блуждания на ZRF непросто. Ход фигуры состоит из нескольких шагов в произвольном направлении, как правило, с дополнительным условием не посещения уже пройденных полей. Я делал игру с подобной механикой, но в тот раз использовал механизм частичных ходов (да и то не слишком удачно, в редких случаях фигура могла загнать себя в тупик). Когда я совсем уж было думал, что без Axiom здесь не обойтись, решение само, вдруг, неожиданно пришло мне в голову: Много кода(define check-target (if (position-flag? is-target?) (set-flag is-succeed? true) ) ) (define check-target-dir (if (and (on-board? $1)(position-flag? is-target? $1)) (set-flag is-succeed? true) ) ) (define check-dir-3 (if (on-board? $1) $1 (if (not (or enemy? (piece? Mount))) (check-target) (if (not-speared?) (check-target-dir $2) (check-target-dir $3) (check-target-dir $4) ) ) (opposite $1) ) ) (define check-branch-3 mark (if (on-board? $1) $1 (if (not (or enemy? (piece? Mount))) (check-target) (if (not-speared?) (check-dir-3 $1 $1 $2 $3) (check-dir-3 $2 $1 $2 $4) (check-dir-3 $3 $1 $2 $5) ) ) ) back ) (define move-3 ( (check-pass) (set-position-flag is-target? true) START (while (on-board? next) next (set-flag is-succeed? false) (if (or empty? (piece? Point)) (check-branch-3 w sw nw se ne) (check-branch-3 nw w ne se e) (check-branch-3 ne nw e w se) (check-branch-3 e ne se nw sw) (check-branch-3 se e sw ne w) (check-branch-3 sw se w e nw) (if (flag? is-succeed?) add) ) ) )) (piece (name LightHorse) ... (moves (move-3) ... ) ) На самом деле, всё просто. Вместо того чтобы пытаться проложить путь от текущего поля (при таком подходе было бы сложно бороться с дубликатами генерируемых ходов), можно его пометить позиционным флагом, а затем, перебрав все поля доски (благо — их немного, доска небольшая), попытаться добраться до отмеченного поля, за заданное число шагов. Звучит не особо впечатляюще, но для меня это было поворотным пунктом. С этого момента я поверил, что игру можно реализовать на чистом ZRF. Взятие осуществляется только «по прямой» и, в большинстве случаев, по «шахматному принципу» — фигура выполняющая взятие становится на место взятой фигуры (здесь снова есть исключение — «Катапульта», о которой я расскажу ниже). При этом, брать можно далеко не любую фигуру! Zane вводит понятие «зацепления» (Engagement). Также как взятие, «зацепление» распространяется по прямой, на количество шагов индивидуальное для каждого типа фигуры. С понятием «зацепления» тесно связано «вооружение» фигуры. Все фигуры делятся на легко-, тяжело- и не вооружённые. «Ополченец» (Rabble) — лёгкая фигура, не может просто так атаковать тяжёлую, например «Слона» (Elephant). Для того чтобы атаковать, ему требуется «зацепление» цели ещё одной лёгкой (или тяжёлой) фигурой. С другой стороны, «Слон» легко может атаковать «Ополченца» (за исключением, разве что случая, когда он находится в воде). Также он может в одиночку «зацепить» любую тяжёлую фигуру противника, даже «Дракона» (Dragon). Не вооружённые (unarmored) фигуры («Арбалетчик» и «Катапульта») могут быть взяты без «зацепления». Это тоже было непростоВ основном, из за количества писанины. У разных фигур — разная дистанция зацепления, да и вообще, они разные. «Дракон» — может летать через горы, «Копейщики» цепляют всего два поля перед собой, надо учесть действие «воды», а проверка на присутствие дружеских/вражеских «Крепостей» поблизости — это вообще мрак. В общем всё сложно (и не исключено, что в коде есть ошибки), но вроде всё работает как задумано: Ещё код(define set-engaged (if (flag? is-light-engaged?) (set-flag is-heavy-engaged? true) else (set-flag is-light-engaged? true) ) ) (define check-escape (if (or enemy? (piece? Mount)) (set-flag is-escaped? true) ) ) (define check-other mark (set-flag is-escaped? false) (if (on-board? $1) $1 (check-escape) (if (and friend? (not-in-zone? water) (or (piece? Rabble) (piece? LightHorse) (piece? HeavyHorse) (piece? Elephant) (piece? Crossbow) (piece? Dragon) (piece? Tower) (piece? King))) (set-engaged) (if (or (piece? HeavyHorse) (piece? Elephant) (piece? Dragon) (piece? Tower)) (set-flag is-heavy-engaged? true) ) (if (piece? Tower) (set-flag is-enemy-tower? true) ) ) ) (if (and (on-board? $1) (not-flag? is-escaped?)) $1 (check-escape) (if (and friend? (not-in-zone? water) (or (piece? Elephant) (piece? Crossbow) (piece? Trebuchet) (piece? Dragon))) (set-engaged) (if (or (piece? Elephant) (piece? Dragon)) (set-flag is-heavy-engaged? true) ) ) ) (if (and (on-board? $1) (not-flag? is-escaped?)) $1 (check-escape) (if (and friend? (not-in-zone? water) (or (piece? Crossbow) (piece? Trebuchet))) (set-engaged) ) ) (if (and (on-board? $1) (not-flag? is-escaped?)) $1 (check-escape) (if (and friend? (not-in-zone? water) (piece? Trebuchet)) (set-engaged) ) ) back ) (define check-spears (if (on-board? $1) (if (and (friend? $1) (not-in-zone? water $1) (piece? Spears $1)) (set-engaged) ) ) ) (define check-friend-tower (if (on-board? $1) (if (and (enemy? $1) (piece? Tower $1)) (set-flag is-light-engaged? false) ) ) ) (define check-engaged (verify (not-piece? Mount)) (set-flag is-enemy-tower? false) (set-flag is-light-engaged? false) (set-flag is-heavy-engaged? true) (if (or (piece? Crossbow) (piece? Trebuchet)) (set-flag is-light-engaged? true) ) (if (or (piece? HeavyHorse) (piece? Elephant) (piece? Dragon) (piece? Tower)) (set-flag is-heavy-engaged? false) ) (check-spears sw) (check-spears se) (check-other w) (check-other e) (check-other nw) (check-other ne) (check-other sw) (check-other se) (if (and (not-piece? Tower) (not-piece? Crossbow) (not-piece? Trebuchet) (not-flag? is-enemy-tower?)) (check-friend-tower w) (check-friend-tower e) (check-friend-tower nw) (check-friend-tower ne) (check-friend-tower sw) (check-friend-tower se) ) (verify (and (flag? is-light-engaged?) (flag? is-heavy-engaged?))) ) (define common-1 ( $1 (verify enemy?) (check-engaged) add )) (piece (name King) ... (moves (common-1 w) (common-1 e) (common-1 nw) (common-1 ne) (common-1 sw) (common-1 se) ... ) ) Как я уже сказал, много писанины, но с этого момента, разработка игры стала, по большей части, механической работой. Был, правда, ещё момент с Rabble, но об этом ниже. Дистанция «зацепления» совпадает с максимальной дистанцией хода фигуры лишь в самых простых случаях (Rabble, Spears и King). Обычной является ситуация, при которой расстояние, на котором возможно «зацепление», меньше максимального хода фигуры (Light Horse, Heavy Horse, Dragon). Впрочем, есть исключение. «Слон» (Elephant) — тяжёлая фигура перемещающаяся лишь на одну клетку за ход, но «зацепить» вражескую фигуру он может на расстоянии двух клеток! Более того, он может её съесть, передвинувшись на две клетки, но лишь при условии, что путь не загораживают какие либо преграды (в отличии от других фигур, «Слон» не может проходить через клетки, занятые другими фигурами. Все эти особенности делают игру ещё более интересной, в тактическом плане. Посмотрите, например решение одной из задач учебника: Расстояние «зацепления», для конницы — единичка. Чтобы взять вражескую фигуру (при отсутствии других фигур, выполняющих «зацепление»), придётся подойти вплотную, но во время боя, есть свобода выбора — остановиться на поле взятой фигуры или двигаться дальше (наподобие дамки в шашках). Максимальная дистанция — три шага для Light Horse и два для Heavy Horse. Детальные параметры всех фигур можно посмотреть в руководстве к игре: Rabble (x6) — light armor, movement allowance 1, engagement range 1 Spears (x3) — light Armor, Movement Allowance 1, Engagement Range 1 Light Horse (x3) — light Armor, Movement Allowance 3, Engagement Range 1 Heavy Horse (x2) — heavy armor, Movement Allowance 2, Engagement Range 1 Elephant (x2) — heavy armor, Movement Allowance 1, Engagement Range 2 Crossbows (x2) — unarmored, Movement Allowance 2, Engagement Range 3 Trebuchet (x1) — unarmored, Movement Allowance 1, Engagement Range 4 (min. 2) Dragon (x1) — heavy armor, Movement Allowance 4, Engagement Range 2 Tower (x2) — heavy armor, Movement Allowance 0, Engagement Range 1 King (x1) — light armor, Movement Allowance 1, Engagement Range 1 Там же можно найти информацию об особенностях каждой из фигур. Некоторые из этих примечаний вводят в смущение. Например, для «Арбалетчика» (Crossbows) написано следующее: "Crossbows cannot capture". Для чего может понадобиться фигура неспособная брать фигуры противника? Вновь, всё дело в «зацеплении»! Crossbows — не защищённая фигура неспособная к ближнему бою, но она может «захватывать» фигуры противника на большом расстоянии. Следующая задача из учебника это иллюстрирует. Если «Арбалетчик» сдвинется так, чтобы «захватить» все цели, «Всадник» сможет побить их за три хода: К сожалению, в задачке есть досадный недочёт Дистанции «тихого хода» Light Horse вполне достаточно, чтобы переместиться на позицию, с которой он может убить все три вражеских фигуры без посторонней помощи. В своей реализации, я исправил это, переместив фигуру «Всадника» ниже. Другая слабо защищённая (unarmored) фигура — «Катапульта» (Trebuchet). Здесь со взятием всё в порядке! Фигура бьёт издалека, на дистанцию от двух до четырёх шагов (противника расположившегося вплотную «Катапульта» побить не может). Уникальность этой фигуры в том, что после выполнения взятия «Катапульта» продолжает оставаться в тылу. Чтобы взять фигуру противника, она перемещается на один шаг в противоположном направлении (конечно, если там есть свободное место)! Это важное стратегическое оружие, «Катапульту» необходимо всячески оберегать! Не было бы счастья, да несчастье помоглоВ процессе подготовки игры к публикации, мне понадобились примеры начальной расстановки фигур. Дело это не простое и я постарался подойти к нему со всем тщанием. На первый взгляд, для фигур почти не остаётся места. Что-то занимают горы, что-то вода (в неё тоже не хочется соваться). К счастью, всё не так плохо как кажется, поскольку большая часть фигур может свободно проходить через территорию, занятую другими дружественными фигурами. Конницу вполне можно ставить во второй ряд, за «Копейщиками» и «Ополченцами». Дракона можно ставить вообще где угодно — он перелетает через горы. После нескольких минут мучений, у меня получилось что-то вроде этого: Я отослал дистрибутив на публикацию и только потом заметил, что «Катапульты» расположены крайне неудачно. Да, я оставил за ними место, но в оригинальной версии игры, через горы они стрелять не умеют! Возможно этот скриншот так и остался бы забавным казусом, но мне пришла в голову интересная идея: «почему бы катапультам и не стрелять через горы, ведь они стреляют навесом»? Так родилось следующее дополнение: если на пути выстрела вдруг оказалась гора, «Катапульта» не может выполнить «зацепление», но коль скоро нашёлся «наводчик», уже зацепивший цель, пульнуть в неё камушек поверх гор «Катапульта» вполне способна! По моему, это неплохая идея, добавляющая в игру ещё больше тактических возможностей. «Крепость» (Tower) — ещё одна очень странная фигура. Она не двигается! Совсем. В общем-то это даже логично. Где (кроме японских мультиков) вы видели двигающиеся крепости? Задача крепости — защита (и она с этой задачей прекрасно справляется). Фигура находящаяся вблизи дружеской крепости не может быть «зацеплена». Чтобы её убить, сначала придётся разрушить крепость, а это не просто. Кроме того, «Король» (King) умеет «прыгать» сквозь дружескую «Крепость», оказываясь по другую сторону от неё за один ход. Есть мнение, что крепость справляется со своей задачей слишком хорошо Это последняя задачка из учебника, которая, по идее, должна решаться за семь ходов. Поймите меня правильно. Короля, в этой позиции, можно съесть «Всадником» всего за три хода! При условии, что он не будет ни на что реагировать, когда «Всадник» подскачет к нему вплотную. В реальной жизни, так не бывает. Для меня очевидно, что сдвинув «Катапульту» вправо и взяв мешающего ей «Ополченца» при помощи «Слона», задачу можно было бы решить за отведённое число ходов. Но мешают крепости! Пока «Копейщики» рядом с ними, они не могут быть «взяты под прицел», а они закрывают «Короля»! В оригинальных правилах, говорится следующее: A piece that is adjacent to one or more opposing Towers cannot engage any pieces except the adjacent Tower(s)Возможно, здесь имелась в виду ситуация, когда фигура может «зацепить» и «Крепость» и охраняемую ей фигуру (то есть стоит вплотную к ним обеим). Не знаю. Это хорошая тема, над которой стоит подумать. Пока же, я разрешил «Дракону» (Dragon) брать фигуру, находящуюся под защитой крепости, с расстояния в два шага (в радиусе действия его «зацепления», но не вплотную). На мой взгляд, это немного оживляет игру. Стоит рассказать о двух самых слабых фигурах, к которым, в равной степени, подходит выражение «мал, да удал». «Копейщик» (Spears) — это, в каком-то смысле, аналог шахматной пешки. Может идти только вперёд и атакует всего два поля перед собой (правда, при этом, ни во что не превращается). Чем может быть полезна такая фигура? Конечно же, у неё есть секрет. Контролируемые ею два поля (всего два) ни одна из вражеских фигур не может «проскочить» за один ход. Например, это означает, что конница не может атаковать «Копейщика» с фронта, даже если тот «зацеплен» другой фигурой. Сначала она должна подойти вплотную. Spears — это превосходный защитный юнит, напоминающий «Телохранителя» (Хиа) из монгольской игры Хиашатар. С «Ополченцами» (Rabble) всё обстоит немного проще. Они могут ходить (и «бить») на один шаг, в любую сторону, как «Король». Фокус заключается в том, что игрок имеет право сделать два «тихих» хода «Ополченцами» подряд. Это атакующий юнит. Сделав два «тихих» хода, можно создать две угрозы, в разных концах доски. Одного «Ополченца», скорее всего съедят, но другим можно будет организовать прорыв. Это решение мне тоже безумно нравится. Хотя и доставило мне некоторое количество проблем, в части реализацииТут вот в чём дело, порядок ходов в ZRF (да и в ZoG в целом) жёстко задан. Если бы каждому из игроков всегда приходилось делать по два хода (как в "Марсельских шахматах"), это было бы просто. Как-то вот так: (turn-order White White Black Black) Но нам-то требуется, чтобы право повторного хода предоставлялось только после «тихого» хода Rabble и чтобы вторым ходом был тоже «тихий» ход, но уже другого Rabble. И никак иначе! И, кстати, право, а не обязанность. Вот тут, мне пришлось пойти на компромиссы. Было понятно, что без механизма пропуска хода (pass) здесь не обойтись, но автор на этот счёт выразился предельно чётко: "He must move a piece, or forfeit the game". К счастью, в ZoG предусмотрен режим, при котором пропуск хода выполняется (автоматически), лишь при отсутствии любых разрешённых ходов (это конечно тоже не совсем правильно, поскольку, при использовании данной опции, игроку не будет засчитано поражение, при отсутствии возможности хода). Собственно, код(define rabble-1 ( (set-position-flag from-pos? true) (verify (not-enemy? a8)) (verify (not is-moved?)) $1 (verify (or empty? (piece? Point))) (set-flag other-rabble? false) mark START (while (on-board? next) next (if (not-position-flag? from-pos?) (if (and friend? (piece? Rabble)) (set-flag other-rabble? true) ) ) ) back (if (flag? other-rabble?) (if (empty? a8) (create Point a8) (set-attribute is-moved? true) ) ) (if (not-empty? a8) (capture a8) mark START (while (on-board? next) next (if is-moved? (set-attribute is-moved? false) ) ) back ) add )) (piece (name Rabble) ... (attribute is-moved? false) (moves (rabble-1 nw) (rabble-1 ne) (rabble-1 sw) (rabble-1 se) (rabble-1 w) (rabble-1 e) ) ) ) Решение не идеально. Двигая первый Rabble мы помечаем его атрибутом, после чего, игрок уже обязан найти и передвинуть какой-то другой свой Rabble (его противник просто пропускает один ход). Во время второго перемещения, кстати, снимается установленный атрибут и если этого не произойдёт, игра, скорее всего, просто остановится. Поэтому, очень важно, ещё на первом ходу, найти хотя бы ещё один другой Rabble и, если такого нет, всю эту магию не включать! Как обычно, задним умом пришла мысль, что хорошо бы еще и проверять возможность хода этим самым другим Rabble. К счастью, это было совсем просто. Так родился патч. Осталось рассказать про рельеф местности. Помимо фигур, на доске могут быть расположены «горы» и «водоёмы». С «горами» всё понятно — ни одна фигура не может располагаться «на горе» и только «Дракон» умеет перелетать через горы. Разумеется, «горы» перекрывают «обзор», препятствуя «зацеплению» вражеских фигур (про нюанс с «Катапультой» я уже говорил). С «водоёмами» всё сложнее. Фигуры в них размещать можно, но с потерей возможности «зацепления» ими вражеских фигур. Это также добавляет тактического разнообразия игре. В этом месте, у ZoG таки тоже есть свои особенностиЕсли с «горами» всё было просто (ну, фигуры и фигуры), то «вода» доставила хлопот. Нет, в принципе, её я тоже мог сделать фигурами. Я как-то раз уже так делал и вот к чему всё привело. Это не лаг был, а такая конструктивная особенность! Впрочем, в тот раз выбора не было. Рисование зелёных квадратиков на чёрном фоне отняло бы кучу времени и раза в два раздуло и без того полуторно-мегабайтный дистрибутив, на 90% заполненный радикально-чёрными «задниками». В общем, в этот раз, воду я рисовал прямо на доске, параллельно отмечая её в качестве игровой зоны в описании игры. К сожалению, таким образом, можно нарисовать хоть и любую, но лишь жёстко фиксированную карту. Ни о каком авторском «Nine Tile» речи уже не идёт. А жаль,… но тут уж ничего не попишешь. Ну вот, в общем-то, и всё. Игра опубликована, с любезного дозволения автора. Интерфейс вышел похуже чем в оригинальной версии (например, не подсвечиваются «зацепления»), но зато работает AI. Во всяком случае, задачки учебника решает «на ура». Играет, в принципе, тоже вполне вменяемо (особенно, если компьютер помощнее, да время «на обдумывание» выставлено побольше. Сама игра мне очень понравилась. В тактическом плане, она показалась мне не менее сложной чем Ko Shogi, выгодно отличаясь от последней большей «лаконичностью» и продуманностью. Все фигуры работают! И для игры вполне достаточно относительно небольшой доски. А о том, как ослабить «Крепости», я ещё подумаю.

Метки:  

Преимущества стекирования Juniper

Воскресенье, 15 Мая 2016 г. 17:57 + в цитатник
Часть 1. Технология виртуального шасси Одним из основных преимуществ решений Juniper перед конкурентами являются их возможности стекирования. Вариантов довольно много, начиная от базового Virtual Chassis и заканчивая целым рядом датацентровых технологий. Часть 1. Virtual Chassis Рассмотрим возможности и особенности базовой технологии Virtual Chassis, которая позволяет объединить до 10 устройств (в зависимости от модели) EX серии и QFX серии в одно логическое устройство, а также способы ее настройки и мониторинга. Начнем с того, какие модели поддерживают технологию Virtual Chassis: • EX2200 Ethernet Switch до 4 устройств • EX3300 Ethernet Switch до 10 устройств • EX4200 Ethernet Switch до 10 устройств • EX4300 Ethernet Switch до 10 устройств • EX4550 Ethernet Switch до 10 устройств • EX4600 Ethernet Switch до 10 устройств • QFX5100 Switch до 10 устройств. Это те модели, которые на момент написания статьи производятся и доступны для заказа, и, соответственно, могут использоватся в виртуальном шасси. Технологии Virtual Chassis сочетают масштабируемость и компактный форм-фактор отдельностоящих коммутаторов с высокой доступностью, пропускной способностью и плотностью портов традиционных шасси. Virtual Chassis позволяет обеспечить экономичное развертывание коммутаторов и доступность сети в местах, где стоимость установки шассийных устройств может быть слишком высока, или где физически невозможно поставить шассийные коммутаторы. Благодаря тому, что несколько устройств управляются как единое целое, Virtual Chassis позволяет использовать такие же возможности по резервированию, как и использование нескольких Routing Engine (RE) в шассийных коммутаторах, включая технологию graceful Routing Engine switchover (GRES). Для подключения в виртуальное шасси могут применяться специализированные интерфейсы, которые находятся на устройствах EX4500, EX4550 и EX4200, а также медные и оптические порты. Порты, которые используются для подключения в виртуальное шасси, называются Virtual Chassis Ports (VCP). Например, в коммутаторе EX2200-24T-4G 24 10/100/1000Base-T и 4 SFP для подключения в виртуальное шасси можно использовать как SFP, так и медные порты. Это позволяет разнести членов одного виртуального шасси на расстояние до 80 км. Mixed Virtual Chassis дает возможность использовать в рамках одного виртуального шасси коммутаторы разных серий и даже разных линеек, как показано на рисунке ниже. Варианты стекирования коммутаторов в Mixed Virtual Chassis В виртуальном шасси есть разделение по ролям: два коммутатора получают роль RE0 и RE1, а остальные используются в качестве линейных карт. RE0 выполняет роль мастера, задача которого – управление остальными членами виртуального шасси, и именно он отвечает за создание таблиц маршрутизации и распространение их по линейным картам. На нем также хранятся файлы конфигурации. RE1 – backup, на случай выхода RE0 из строя. Рассмотрим распределение ролей коммутаторов в виртуальном шасси на примере ниже. Критерии выбора мастера: — Коммутатор с наивысшим приоритетом. Приоритет может быть от 1 до 255, по умолчанию он 128 на всех коммутаторах; — Коммутатор, который был мастером до перезагрузки; — Коммутатор с наибольшим uptime (разница должна быть больше 1 мин);. — Коммутатор с наименьшим MAC адресом. Каждый из членов виртуального шасси получает свой ID от 0 до 9. Настроить данный ID можно вручную с мастера (у которого ID 0) (после перезагрузки номер сохраняется и может служить как номер слота в определении интерфейса). Из-за того, что ID закрепляется за коммутатором, может возникнуть следующая ситуация: когда один коммутатор выходит из строя и вы его меняете, новый член получает не номер вышедшего из строя коммутатора, а следующий свободный или вовсе не подключается к виртуальному шасси, если все ID уже заняты. Поэтому, чтобы заменить вышедший из строя коммутатор новым, сперва нужно с мастера снять его ID следующим образом: {master:0} user@Switch-1> request virtual-chassis recycle member-id <member-id> Или можно это сделать с помощью ID команды: user@host> request virtual-chassis renumber member-id <current-member-id> new-member-id <new-member-id> Единый менеджмент-интерфейс и единая консоль В рамках виртуального шасси все management-интерфейсы объединяются в один, с единым IP адресом: {master:0}[edit] user@switch# show interfaces vme unit 0 { family inet { address 10.210.14.148/27; } } {master:0}[edit] user@Switch-1# run show interfaces terse vme Interface Admin Link Proto Local Remote vme up up vme.0 up up inet 10.210.14.148/27 То же самое происходит и с консольным портом, в какой бы вы не подключились, вы попадете на RE0. Совместимость программного обеспечения Все члены виртуального шасси должны быть с одинаковой версией софта. Когда мы добавляем новый коммутатор в виртуальное шасси, мастер проверяет его версию софта, если софт отличается, то новый коммутатор получит ID, но не станет активным членом виртуального шасси. Для этого нужно обновить на новом коммутаторе программное обеспечение с помощью команды: request system software add member <member-id> reboot Эта команда загружает образ из главного коммутатора через VCPs нового коммутатора, а затем перезагружает его, при этом новый коммутатор может быть не подключен напрямую к главному. Автоматическое обновление программного обеспечения С помощью настройки автоматического обновления софта, при подключении каждого нового коммутатора с версией, отличной от мастера, его софт будет автоматически обновляться. set virtual-chassis auto-sw-update package-name <package-name> Nonstop Software Upgrade Технология Nonstop software upgrade (NSSU) позволяет обновлять ПО на всех членах виртуального шасси с минимальными потерями. Для корректной работы данной технологии нужно, чтобы: — Все члены виртуального шасси были подключены по топологии «кольцо»; — Master и backup были смежными; — Линейные карты были настроены в режиме преконфигурации; — На всех членах виртуального шасси должна быть одна версия софта; — Должны быть включены NSR и GRES; Опционально можно активировать NSB командой request system snapshot. Процесс NSSU: — Скачиваем образ софта. Если используем смешанное виртуальное шасси, то скачиваем оба образа. — Копируем его на RE0, рекомендовано в папку /var/tmp, — Из командной строки RE0 запускаем NSSU командой: request system software nonstop-upgrade /var/tmp/package-name.tgz или для смешанных виртуальных шасси request system software nonstop-upgrade set [/var/tmp/package-name.tgz /var/tmp/package-name.tgz] Рекомендованное расположение коммутаторов Master и Backup При построении виртуального шасси рекомендуются такие схемы подключения: Или Если элементы виртуального шасси разнесены территориально, то стоит использовать схему ниже: Рекомендованные топологии виртуального шасси На следующих рисунках показаны топологии виртуального шасси, которые могут быть развернуты на основе конкретных потребностей пользователей. Топология «кольцо» является наиболее часто используемой, но виртуальное шасси также можно развернуть в «Full mesh» топологии или топологии нескольких колец. Топология «кольцо» рекомендуется при развертывании виртуального шасси в смешанном режиме. Кольцо Full mesh Топология нескольких колец Настройка Virtual Chassis Настройка виртуального шасси происходит на уровне иерархии [edit virtual-chassis] {master:0}[edit virtual-chassis] user@switch# set? Possible completions: + apply-groups Groups from which to inherit configuration data + apply-groups-except Don't inherit configuration data from these groups > auto-sw-update Auto software update > fast-failover Fast failover mechanism id Virtual Chassis identifier, of type ISO system-id > mac-persistence-timer How long to retain MAC address when member leaves Virtual Chassis > member Member of Virtual Chassis configuration no-split-detection Disable split detection. Only recommended in a 2 member setup preprovisioned Only accept preprovisioned members > traceoptions Global tracing options for Virtual Chassis Чтобы свести к минимуму прерывания трафика во время сценария отказоустойчивости RE, нужно включить graceful Routing Engine switchover. {master:0}[edit chassis] user@switch# set redundancy graceful-switchover? Possible completions: > graceful-switchover Enable graceful switchover on supported hardware Есть несколько вариантов: динамический и Preprovisioning. Динамический: 1. Включаем мастер коммутатор и устанавливаем ID 0 и приоритет 255. 2. Подключаем backup коммутатор и устанавливаем ID 0 и приоритет 255. {master:0}[edit virtual-chassis] user@Switch-1# set member <member-id> mastership-priority <priority> 3. При использовании только двух коммутаторов рекомендуется отключить split detection. [edit virtual-chassis] user@switch# set no-split-detection 4. Включаем питание на остальных коммутаторах. 5. Если мы используем смешанное виртуальное шасси, то добавляем команду: user@device> request virtual-chassis mode mixed reboot 6. На каждом индивидуальном коммутаторе указываем порты, которые будут в роли VCPs. Preprovisioning 1. Включаем мастер коммутатор (предварительно собираем все серийные номера коммутаторов, которые будут в кластере). 2. Если у нас будет использоваться смешанное виртуальное шасси: user@device> request virtual-chassis mode mixed reboot 3. Опционально настраиваем IP адрес на менеджмент интерфейсе: user@switch# set interfaces vme unit 0 family inet address /ip-address/mask/ 4. Ставим Preprovisioning mode: [edit virtual-chassis] user@switch# set preprovisioned 5. Прописываем роль, ID для каждого члена виртуального шасси [edit virtual-chassis] user@switch# set member 0 serial-number BM0208105168 role routing-engine user@switch# set member 1 serial-number BM0208124111 role line-card user@switch# set member 2 serial-number BM0208124231 role routing-engine user@switch# set member 3 serial-number BM0208124333 role line-card 6. При использовании только двух коммутаторов, рекомендуется отключить split detection. [edit virtual-chassis] user@switch# set no-split-detection 7. Включаем остальных членов виртуального шасси. 8. Если используем смешанное виртуальное шасси, то вводим на каждом коммутаторе user@device> request virtual-chassis mode mixed reboot 9. Указываем какие интерфейсы будем использовать в роли VCPs user@switch> request virtual-chassis vc-port set pic-slot pic-slot-number port port-number local Например, чтобы использовать встроенные 40G порты на коммутаторе EX4600 user@switch> request virtual-chassis vc-port set pic-slot 2 port 0 local Где порт 0 – первый 40G встроенный порт. user@switch> request virtual-chassis vc-port set pic-slot pic-slot-number port port-number local Мониторинг Virtual chassis Для мониторинга виртуального шасси используется команда show virtual-chassis с различными ключами: {master:0} user@switch> show virtual-chassis? Possible completions: <[Enter]> Execute this command active-topology Virtual Chassis active topology device-topology PFE device topology fast-failover Fast failover status login protocol Show Virtual Chassis protocol information status Virtual Chassis information vc-path Show virtual-chassis packet path vc-port Virtual Chassis port information | Pipe through a command Проверка состояния портов виртуального шасси: show virtual-chassis vc-port {master:0} user@Switch-1> show virtual-chassis vc-port fpc0: — Interface Type Trunk Status Speed Neighbor or ID (mbps) ID Interface PIC / Port vcp-0 Dedicated 2 Up 32000 1 vcp-0 vcp-1 Dedicated 1 Up 32000 1 vcp-1 fpc1: — Interface Type Trunk Status Speed Neighbor or ID (mbps) ID Interface PIC / Port vcp-0 Dedicated 2 Up 32000 0 vcp-0 vcp-1 Dedicated 1 Up 32000 0 vcp-1 Проверка информации о состоянии: show virtual-chassis status {master:0} user@Switch-1> show configuration virtual-chassis preprovisioned; member 0 { role routing-engine; serial-number BM0208105168; } member 1 { role line-card; serial-number BM0208124231; } {master:0} user@Switch-1> show virtual-chassis status Preprovisioned Virtual Chassis Virtual Chassis ID: 8d5c.a77f.8de8 Mastership Neighbor List Member ID Status Serial No Model priority Role ID Interface 0 (FPC 0) Prsnt BM0208105168 ex4200-24t 129 Master* 1 vcp-0 1 vcp-1 1 (FPC 1) Prsnt BM0208124231 ex4200-24t 0 Linecard 0 vcp-0 0 vcp-1 Вот так кратко мы рассмотрели технологию Virtual chassis, варианты настройки и мониторинга.
Как средствами OpenGL менять прозрачность у картинки?

Метки:  

Как средствами OpenGL менять прозрачность у картинки?

Воскресенье, 15 Мая 2016 г. 16:38 + в цитатник
Необходимо взять белую картинку и накладывать ее на экран, постепенно меняя у нее прозрачность. Но как средствами OpenGL менять прозрачность у картинки? Вообще, какие есть функции в OpenGL для реализации таких эффектов? Вот их примерный список:

Метки:  

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

Воскресенье, 08 Мая 2016 г. 09:21 + в цитатник
Пусть задана окружность (x0,y0,r0). Необходимо внутри разместить случайные точки так, чтобы они были распределены равномерно.
Игра Сапёр (клон). Тестирование

Метки:  

Игра Сапёр (клон). Тестирование

Четверг, 05 Мая 2016 г. 20:04 + в цитатник
8Observer8 Поиграйте, пожалуйста, если найдёте ошибку – сообщите. К примеру, если номера будут неправильные, то покажите скриншот: Minesweeper_v1.2.1 (Win_x86) Посмотрите, запускается ли из вашего браузера: Minesweeper_v1.2.1 (WebGL) Minesweeper_v1.2.1 (WebPlayer) Если у вас Linux или Mac, то сообщите запускается ли. Если один раз запустится, то я буду знать, что, скорее всего, и остальные приложения запустятся. Linux [...]

8Observer8


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



  • Minesweeper_v1.2.1 (Win_x86)


Посмотрите, запускается ли из вашего браузера:



  • Minesweeper_v1.2.1 (WebGL)

  • Minesweeper_v1.2.1 (WebPlayer)


Если у вас Linux или Mac, то осведомите запускается ли. Если один раз запустится, то я буду знать, что, скорее всего, и другие приложения запустятся. Linux я ещё смогу назначить на Virtual Box, а Mac едва ли:



  • Minesweeper_v1.2.1 (Linux_x86)

  • Minesweeper_v1.2.1 (Mac_x86)


С меня «плюс» в репутацию если пустите что либо и уведомите запустилось ли или нет. Особливо увлекательно – Мас и Linux (известите дистрибутив)


Язычок: C#

Движок: Unity2D

Исходники: Minesweeper_v1.2.1 (Source)

Базовый туториал



тема на форуме


Метки:  

Поиск сообщений в predefglas
Страницы: 2 [1] Календарь