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

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

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

 

 -Статистика

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

Habrahabr/New








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

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

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

[Из песочницы] Сумасшедший искусственный интеллект

Понедельник, 12 Июня 2017 г. 20:24 + в цитатник

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


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


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


За дело!


Словарь терминов


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

Обучение ИИ


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


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

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


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


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


С учётом того, что в фокусе сознания человек может держать, по моим наблюдениям, только одну мысль (для этого был придуман простой эксперимент, в ходе которого необходимо перечислить алфавит, произнося после каждой буквы цифру, т.е.: А1 Б2 В3 и т.д.), а все остальные мысли представим как непрерывный поток(эфир), из которого мы иногда ловим в фокусе одну из цепочек мыслей (мыслепредложение).


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


Подытожим


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

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

https://habrahabr.ru/post/330736/


Метки:  

SQL Server Integration Services (SSIS) для начинающих – часть 2

Понедельник, 12 Июня 2017 г. 19:47 + в цитатник

Часть 1 – habrahabr.ru/post/330618

В этой части изменим логику загрузки справочника Products:
  1. При помощи компонента «Union All» объединим два входящих потока в один;
  2. Для новых записей будем делать вставку, а для записей, которые уже были добавлены ранее будем делать обновление. Для разделения записей на добавляемые и обновляемые воспользуемся компонентом Lookup;
  3. Для обновления записей применим компонент «OLE DB Command».

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

Итого в этой части мы познакомимся с четырьмя новыми компонентами: Union All, Lookup, OLE DB Command и Multicast.

Дальше так же будет очень много картинок.

Продолжим знакомство с SSIS


Создадим новый пакет:


И переименуем его в «LoadProducts_ver2.dtsx»:


В области «Control Flow» создадим «Data Flow Task»:


Двойным щелчком по элементу «Data Flow Task» зайдем в его область «Data Flow». Создадим два элемента «Source Assistant» для соединений SourceA и SourceB. Переименуем эти элементы в «Source A» и «Source B» соответственно:


«Source A» настроим следующим образом:

Текст запроса:
SELECT
  ID SourceProductID,
  Title,
  Price
FROM Products

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

«Source B» настроим следующим образом:

Текст запроса:
SELECT
  ID SourceProductID,
  'B' SourceID,
  Title,
  Price
FROM Products

В результате набор A у нас будет иметь 3 колонки [SourceProductID, Title, Price], а набор B будет иметь 4 колонки [SourceProductID, SourceID, Title, Price].

Воспользуемся элементом «Union All», чтобы объединить данные из 2-х наборов в один. Направим в него синие стрелки из «Source A» и «Source B»:


Каким образом делается сопоставление колонок двух входящих наборов, можно увидеть дважды щелкнув на элементе «Union All»:


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

В данном случае значения SourceID набора «Source A» будут равны NULL.

Объединение двух наборов в данном случае делается на стороне SSIS. Здесь стоит обратить внимание на то, что базы источники и принимающая база могут располагаться на разных серверах/экземплярах SQL Server, по этой причине мы не всегда сможем так просто написать SQL запрос используя в нем таблицы из разных баз с применением SQL-операции UNION или JOIN (который можно было использовать вместо Lookup описанного ниже).

Для того чтобы заменить NULL значения на «A» воспользуемся компонентом «Derived Column» в который направим поток из «Union All»:


Двойным щелчком зайдем в редактор «Derived Column» и настроим его следующим образом:


Проделаем следующее (мышь в помощь):
  1. Укажем в «Derived Column» значение «Replace 'SourceID'» — это будет означать что мы на выходе заменяем старую колонку SourceID на новую;
  2. Перетащим в область «Expression» функцию REPLACENULL;
  3. Перетащим на место первого аргумента функции REPLACENULL колонку SourceID;
  4. В качестве второго аргумента пропишем константу «A».

Для того чтобы понять, что произошло с данными после прохождения «Union All» сделайте «Enable Data Viewer» для стрелки, идущей от «Union All» к «Derived Column»:


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

Здесь видно, что на этом этапе (до Derived Column) в колонке SourceID для строк первого набора стоят значения NULL.

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


Дважды щелкнув по нему настроим данный элемент:


Здесь мы скажем, что те строки, для которых не найдено соответствие, мы будем перенаправлять в поток «no match output». В этом случае на выходе мы получим 2 набора «Lookup Match Output» и «Lookup No Match Output».
Например, если выставить значение «Ignore failure», то в строках, для которых не нашлось сопоставления в поле TargetID (см. ниже) будет записано значение NULL и все строки будут возвращены через один набор «Lookup Match Output».

«Full cache» говорит о том, что набор, который будет использоваться в качестве справочника одним SQL запросом (см.на следующей вкладке) будет полностью загружен в память и строки будут сопоставляться уже с кэша без повторных обращений к SQL Server.
Если же выбрать «Partial cache» или «No cache», то на вкладке Advanced можно будет прописать запрос с параметрами, который будет выполняться для сопоставления каждой строки входящего набора. Для интереса можно поиграться с этим свойством и через SQL Server Profiler посмотреть какие будут формироваться запросы при выполнении пакета.

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

Я прописал здесь запрос:
SELECT
  SourceID,
  SourceProductID,
  ID TargetID
FROM Products

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

Для определение связи нужно при помощи мыши перетащить поле SourceProductID на SourceProductID и поле SourceID на SourceID.

Добавим компонент «Destination Assistant» для вставки записей с потока «Lookup No Match Output»:


Перетащим синюю стрелку с «Lookup» на «OLE DB Destination» и в диалоговом окне выберем поток «Lookup No Match Output»:


В итоге мы получим следующее:


Дважды щелкнув по «OLE DB Destination» настроим его:




Обработку вставки новых записей мы сделали.

Теперь для обновления ранее вставленных записей воспользуемся компонентом «OLE DB Command» и перенесем на него синюю стрелку от Lookup:


В этот компонент автоматически будет направлен поток «Lookup Match Output», т.к. поток «Lookup No Match Output» мы уже выбрали ранее:


Дважды щелкнем на «OLE DB Command» и настроим его:



Пропишем следующий запрос на обновление:
UPDATE Products
SET
  Title=?,
  Price=?
WHERE ID=?

На следующей вкладке укажем каким образом будут задаваться параметры на основании данных строк входящего набора «Lookup Match Output»:


Через SSMS добавим новых продуктов в базу DemoSSIS_SourceB:
USE DemoSSIS_SourceB
GO

-- добавим новых товаров
SET IDENTITY_INSERT Products ON

INSERT Products(ID,Title,Price)VALUES
(6,N'Точилка',NULL),
(7,N'Ластик',NULL),
(8,N'Карандаш простой',NULL)

SET IDENTITY_INSERT Products OFF
GO

Для того чтобы отследить как менялись данные, вы можете, перед запуском пакета на выполнение, в необходимых местах сделать «Enable Data Viewer»:


Запустим пакет на выполнение:


В итоге мы должны увидеть, что 3 строки было вставлено при помощи компонента «OLE DB Destination» и 10 строк обновлено при помощи компонента «OLE DB Command».

Запрос прописанный в «OLE DB Command» выполнился для каждой строки входящего набора, т.е. в данном примере 10 раз.

В «OLE DB Command» можно прописать более сложную логику на TSQL, например, сделать проверку, были ли изменены Title или Price, и делать обновление соответствующей строки только если какое-то из значений отличается.

Для наглядности добавим новую колонку в таблицу Products в базе DemoSSIS_Target:
USE DemoSSIS_Target
GO

ALTER TABLE Products ADD UpdatedOn datetime
GO

Давайте теперь пропишем следующую команду:

Текст команды:
DECLARE @TargetID int=?
DECLARE @Title nvarchar(50)=?
DECLARE @Price money=?

IF(EXISTS(
      SELECT Title,Price
      FROM Products
      WHERE ID=@TargetID
      EXCEPT
      SELECT @Title,@Price
    )
  )
BEGIN
  UPDATE Products
  SET
    Title=@Title,
    Price=@Price,
    UpdatedOn=GETDATE()
  WHERE ID=@TargetID
END

Так же можно было бы все это оформить в виде хранимой процедуры, а здесь прописать ее через вызов «EXEC ProcName ?,?,?». Здесь, думаю, кому как удобнее, мне порой удобнее, чтобы все было прописано в одном месте, т.е. в SSIS-проекте. Но если использовать процедуру, то тоже получаем свои удобства, в этом случае можно, было бы просто изменить процедуру и избежать переделки и повторного развертывания SSIS-проекта.

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


Сделаем в базе DemoSSIS_SourceA обновление:
USE DemoSSIS_SourceA
GO

UPDATE Products
SET
  Price=30
WHERE ID=2 -- Корректор

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


После выполнения пакета проверим это при помощи запроса:
USE DemoSSIS_Target
GO

SELECT *
FROM Products
ORDER BY UpdatedOn DESC



В рамках данной части рассмотрим еще компонент «Multicast». Данный компонент позволяет получить из одного потока несколько. Это может быть полезно, когда одни и те же данные необходимо записать в два или более разных мест – т.е. входит один набор, а выходит столько его копий сколько нам нужно, и с каждой копией этого набора мы можем делать что захотим.

Для примера создадим в базе DemoSSIS_Target еще одну таблицу LastAddedProducts:
USE DemoSSIS_Target
GO

CREATE TABLE LastAddedProducts(
  SourceID char(1) NOT NULL, -- используется для идентификации источника
  SourceProductID int NOT NULL, -- ID в источнике
  Title nvarchar(50) NOT NULL,
  Price money,
CONSTRAINT PK_LastAddedProducts PRIMARY KEY(SourceID,SourceProductID),
CONSTRAINT CK_LastAddedProducts_SourceID CHECK(SourceID IN('A','B'))
)
GO

Для очистки этой таблицы добавим в область «Control Flow» компонент «Execute SQL Task» и пропишем в нем команду «TRUNCATE TABLE LastAddedProducts»:




Перейдем в область «Data Flow» компонента «Data Flow Task» и добавим компонент следующим образом:


Обратите внимание на желтый восклицательный знак – это произошло из-за того, что мы добавили колонку UpdatedOn и не привязали ее. Зайдем в элемент «OLE DB Destination», перейдем на вкладку Mappings оставим для колонки UpdatedOn в качестве входящего поля Ignore и нажмем OK:


Создадим еще один элемент «OLE DB Destination» и перетащим на него вторую синюю стрелку от элемента Multicast:


Переименуем для наглядности:


Настроим «To LastAddedProducts»:




Удалим через SSMS три последние вставленные записи:
USE DemoSSIS_Target
GO

DELETE Products
WHERE SourceID='B'
  AND SourceProductID>=6

И запустим пакет на выполнение:


В итоге добавление произошло в 2 таблицы – Products и LastAddedProducts.

Заключение по второй части


В этой части мы рассмотрели каким образом можно делать синхронизацию небольших справочников. Здесь конечно не учитывается тот момент, что данные в источниках могут еще удаляться, но при необходимости вы сможете попробовать сделать это самостоятельно, т.к. при удалении иногда нужно учитывать дополнительные факторы, например, на удаляемую запись могут быть ссылки из других таблиц (в следующей части планируется это сделать).
Чтобы не нарушать ссылочную целостность, иногда запись в принимающей таблице удаляется логически, для этого, например, можно в эту таблицу добавить поле Deleted типа bit (флаг логического удаления) или DeletedOn типа datetime (дата/время логического удаления).

Порой на сервере, на котором располагается база Target делается вспомогательная промежуточная база (обычно ее называют Staging) и первым делом «сырые» данные из Source загружаются в нее. Так как теперь Target и Staging находятся на одном сервере, то вторым шагом мы можем легко написать SQL-запрос (например, используя SQL-конструкцию MERGE или запрос с применение конструкции JOIN), который оперирует с наборами обеих этих баз.

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

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

Спасибо за внимание! Удачи!

Продолжение следует…
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330702/


Метки:  

Дайджест интересных материалов для мобильного разработчика #206 (05-12 июня)

Понедельник, 12 Июня 2017 г. 17:37 + в цитатник
На прошлой неделе Apple на WWDC представила iOS 11 с обновленным App Store, машинным обучением, дополненной реальностью и прочими интересными новинками – что мы почти всю неделю и обсуждали. Кроме этого у нас есть новый опрос Developer Economics, инженеры будущего, монетизация от инди-разработчиков и метрики успеха.



Побеждаем Android Camera2 API с помощью RxJava2 (часть 1)

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

12 часов в шкуре Android разработчика глазами JS разработчика

Все началось с Kotlin. Случайно попалась статья про новый язык, что на нем можно писать под Android. Соприкоснувшись с темой, узнал что изначально приложения под Android пишутся на JAVA. Решил узнать на сколько трудоемко писать приложения под Android, в чем преимущества платформы на практике.

Новый опрос Developer Economics 2017

Чем занимаются разработчики в вашей стране? Какие инструменты они используют? Хотите узнать? Developer Economics – самый масштабный опрос разработчиков в мире.

Дайджест доступен и в виде рассылки. Подписаться вы можете тут.

iOS


Android


Windows


Разработка


Аналитика, маркетинг и монетизация


Устройства, IoT, AI



< Предыдущий дайджест. Если у вас есть другие интересные материалы или вы нашли ошибку — пришлите, пожалуйста, в почту.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330728/


[recovery mode] Готовим git reset правильно

Понедельник, 12 Июня 2017 г. 16:44 + в цитатник
Очень короткая заметка из серии «Хозяйке на заметку».

Предисловие


У плохо организованных разработчиков, типа меня, часто так бывает, что накомитишь всякого, а потом оказывается что не то и не туда, но git помнит всё и весь этот разгул анархии остаётся в истории.
Мне всегда казалось что это можно исправить, но сколько раз я не начинал искать ответа на этот вопрос — как в git удалить камиты — столько раз мне это не удавалось.
А всё потому что мои поиски приводили меня к rebase, а надо было искать reset.
На мой вкус вопрос недостаточно освещён и моя заметка призвана закрыть этот пробел.

Алгоритм


  1. git branch // смотрим где мы находимся — в какой ветке
  2. git status // проверяем что у нас актуальная версия
  3. git log // ищем камит к которому мы хотим откатиться
  4. sudo git reset --hard 7bcdf46b14b2dacc286b0ad469d5f9022d797f68 // указываем камит начиная с которого нам надо забыть наши изменения, при этом из локальной ветки все камиты с указанного будут забыты — удалены
  5. git push --force origin feature/draft // заливаем локальную ветку в оригинальную (ветку сервера) — из оригинальной ветки будут удалены все «лишние» камиты
  6. Победа !

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

PS
На самом деле камиты из репозитория не удаляются, удаляется связь этих камитов с деревом изменений, таким образом эти камиты пропадают из ветки, но git помнит всё.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330722/


Метки:  

Реформа SQL-ориентированного подхода в DAO

Понедельник, 12 Июня 2017 г. 16:09 + в цитатник

Вводная


Мне часто в проектах приходится сталкиваться с фреймворками по работе с БД. Концептуально, эти фреймворки можно разбить на 2 больших класса:
  • ORM-ориентированные
  • SQL-ориентированные
Некоторые из них хороши, какие-то не очень. Но субъективно могу сказать: SQL-ориентированные уступают в развитии ORM-ориентированным. Подчеркну, в развитии, а не в возможностях. Хоть изменить эту чашу весов и не получится, но предложить необычный взгляд на мир SQL-ориентированного подхода — вполне. Кому интересно, добро пожаловать под кат

Немного обзора


Прежде чем преступить, предлагаю вспомнить, совсем кратко, что есть. Кто в теме, смело пропускайте обзор.
И так, на сцене представители ORM-ориентированного подхода:Эти представители справляются со своей задачей по-разному. Последний, например, DSL-way, предлагает конструировать SQL запросы, используя, либо сгенерированные объекты по схеме Вашей БД, либо просто строки. Другие, требуют описывать соответствие между java-объектами и таблицами БД. Но не суть. Их все объединяет одна идея: максимально изолировать разработчика от написания SQL-запросов, предлагая взамен — ORM-мышление.

С другой стороны собрались представители SQL-ориентированного подхода:Все эти решения в том или ином виде являются надстройками над пакетом java.sql.*. И тем эти фреймворки круче, чем сильнее изолируют разработчика от него. Тем не менее, используя их, мыслить Вам придется сначала категориями SQL, а потом уж ORM.

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

Что не так?


Я умышленно не давал оценочных суждений тому или иному подходу, ORM vs SQL vs Генераторы. Каждый решает сам, в купе с имеющимися обстоятельствами, что выбирать. Но вот готов предложить определенную альтернативу как в стилистическом выражении так и в концептуальном для SQL-ориентированных. Но прежде скажу, что мне не нравится на уровне кода (производительность, debug и прочее — опускаю) в существующих решениях:
  1. Определенная многословность для достижения простых вещей
  2. Бойлерплейт, бойлерплейт, бойлерплейт… и еще раз бойлерплейт
  3. Отсутсвие точки в коде, где можно посмотреть sql-orm или orm-sql варианты
  4. В том или ином виде конструирование SQL-запроса по условиям фильтрации
  5. Многознание для использования API фреймворка — узнай про +100500 сущностей прежде, чем преступить кодить
Многое из перечисленного заставило спросить: 'А каким фреймворк должен быть, чтобы тебе понравилось?'

Декларативный стиль


Каким? Думаю простым, таким, чтобы взял и начал кодить. Но, а если серьезно? Декларативным. Да, я сторонник декларативного стиля в таких вещах, нежели императивного. Что в java приходит на ум первым, если речь идет о декларативном подходе? Да всего 2 вещи: аннотации и интерфейсы. Если эти 2 субстанции скрестить и направить в русло SQL-ориентированного решения, то получим следующее:
ОРМ
public class Client {
    private Long id;
    private String name;
    private ClientState state;
    private Date regTime;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public ClientState getState() {
        return state;
    }
    public void setState(ClientState state) {
        this.state = state;
    }
    public Date getRegTime() {
        return regTime;
    }
    public void setRegTime(Date regTime) {
        this.regTime = regTime;
    }
}
enum ClientState {
    ACTIVE(1),
    BLOCKED(2),
    DELETED(3);
    private int state;
    ClientState(int state) {
        this.state = state;
    }
    @TargetMethod
    public int getState() {
        return state;
    }
    @TargetMethod
    public static ClientState getClientState(int state) {
        return values()[state - 1]; // только для краткости
    }
}
public interface IClientDao {
    @TargetQuery(query =
            "SELECT id, name, state " +
            "  FROM clients " +
            "  WHERE id = ?", type = QT_SELECT)
    Client findClient(long clientId);
}
Табличка
-- Таблица клиентов
CREATE TABLE clients (
  id bigint NOT NULL,
  name character varying(127) NOT NULL,
  state int NULL,
  reg_time timestamp NOT NULL,
  CONSTRAINT pk_clients PRIMARY KEY (id)
);
Это простой рабочий пример, суть которого подчеркнуть идею декларативного стиля, которая находит отражение в рассматриваемом фреймворке. Сама идея разумеется не нова, заметки о похожем я встречал в статьях IBM где-то за 2006 год, а некоторые приведенные фреймворки уже используют эту идею. Но увидев такой пример, я бы резонно задал несколько вопросов:
  1. А кто реализует контракт IClientDao и как получить доступ к реализации?
  2. А где описывается меппинг полей?
  3. А как насчет чего-то посложнее? А то эти примеры на 3 копейки уже надоели.
Предлагаю по порядку ответить на эти вопросы и походу раскрыть возможности фреймворка.

Просто Proxy


>>1. А кто реализует этот контракт и как получить доступ к реализации?
Контракт реализуется самим фреймворком с помощью инструмента java.lang.reflect.Proxy и реализовывать его самому для SQL целей не нужно. А получить доступ к реализации очень просто, с помощью… Да в прочим на примере показать легче:
IClientDao clientDao = com.reforms.orm.OrmDao.createDao(connection, IClientDao.class);
Client client = clientDao.findClient(1L);
Где connection — это объект для доступа к БД, например, реализация java.sql.Connection или javax.sql.DataSource или вообще Ваш объект. Client — Ваш ORM объект и пожалуй единственная вещь от самого фреймворка — это класс com.reforms.orm.OrmDao, который покроет 98% всех Ваших нужд.

Концепция


>>2. А где описывается меппинг полей?
Как и обещал выше, я затрону 2 вещи: стиль и концепцию. Чтобы ответить на второй вопрос, нужно рассказать о концепции. Посыл таков, что, чтобы предложить новое — нужен радикальный взгляд на решение. А как насчет SQL-92 парсера? Когда мне впервые пришла эта мысль, я откинул ее так далеко, что думал больше с ней не встречусь. Но как тогда сделать SQL-ориентированный фреймворк удобным? Пилить очередную надстройку? Или делать очередной хелпер к хелперу фреймворка? На мой взгляд, лучше ограничить поддерживаемый набор SQL конструкций — как неплохой компромисс, и взамен получить нечто удобное, на мой взгляд. Мэппинг осуществляется на основе дерева выражений, после парсинга SQL-запроса. В примере выше имена колонок соотносятся с именами полей ORM объекта один к одному. Разумеется, фреймворк поддерживает меппинг и более сложный, но о нем чуть позже.

Примеры


>> 3. А как насчет чего-то посложнее? А то эти примеры на 3 копейки уже надоели.
А есть ли смысл вообще заморачиваться с SQL-92 парсером, если фреймворк не будет уметь делать, чего-то посложнее? Но показать все в примерах без объема — не простая задача. Разумеется я покажу, правда буду опускать местами декларации таблиц в SQL и части java кода.
Одной из немногих вещей, которая мне никогда не нравилась в SQL-ориентированных решениях — это необходимость конструирования SQL-запросов. Например, когда определенные критерии фильтрации могут быть заданы, а могут быть и не заданы. И Вам наверняка знаком такой фрагмент кода, точнее его упрощенный вариант:
    // где-то в DAO
    private String makeRegTimeFilter(Date beginDate, Date endDate) {
        StringBuilder filter = new StringBuilder();
        if (beginDate != null) {
            filter.append(" reg_time >= ?");
        }
        if (endDate != null) {
            if (filter.length() != 0) {
                filter.append(" AND");
            }
            filter.append(" reg_time < ?");
        }
        return filter.length() == 0 ? null : filter.toString();
    }
И написал я этот фрагмент именно так, как чаще всего встречаю в старых проектах. И это при том, что проверки дат появятся еще раз при установке значений в PreparedStatement. А что предлагается наш друг? А предлагает он динамические фильтры. На примере разбираться легче, поэтому посмотрим на поиск клиентов за определенный интервал:
public interface IClientDao {
    @TargetQuery(query =
            "SELECT id, name, state " +
            "  FROM clients " +
            "  WHERE regTime >= ::begin_date AND " +
            "        regTime < ::end_date", type = QT_SELECT)
    List findClients(@TargetFilter("begin_date") Date beginDate,
                             @TargetFilter("end_date") Date endDate);
}
И суть такова, что если значение параметра, например, beginDate будет равно null, то ассоциированный с ним SQL-фильтр regTime >= ::begin_date будет вырезан из финального SQL-запроса и в нашем случае на сервер БД уйдет следующая строка:
SELECT id, name, state FROM clients WHERE regTime < ?
Если оба значения будут равны null, то секции WHERE не будет в итоговом запросе. И заметьте — в коде только декларация и никакой логики. На мой взгляд, но очень хочется послушать и других, это сильное оружие и сильная сторона фреймворка. По java коду скажу, что я сам не фанат аннотаций и большое их количество в большинстве проектов просто раздражает. Поэтому предусмотрел определенную им альтернативу — фильтры по свойствам bean объекта:
// Бойлерплейт get/set код вырезал. В своем файле.
public class ClientFilter {
    private Date beginDate;
    private Date endDate;
}
public interface IClientDao {
    @TargetQuery(query =
            "SELECT id, name, state " +
            "  FROM clients " +
            "  WHERE regTime >= ::begin_date AND " +
            "        regTime < ::end_date", type = QT_SELECT)
    List findClients(@TargetFilter ClientFilter period);
}
Получается довольно лаконично и понятно. Про динамические фильтры стоит отдельно сказать, что они поддерживают, точнее они могут быть вырезанными из подзапросов и всех предикатов, исключая OVERLAPS и MATCH. Последние я ни разу не встречал в 'живых' SQL выражениях, но упоминание о них в спецификации SQL-92 имеется.
Разумеется, фреймворк поддерживает и статические, обязательные к указанию фильтры. И синтаксис у них такой же, как в HQL или SpringTemplate — ': именованный параметр'.
Правда, со статическими фильтрами всегда связана одна проблема: 'Как реагировать на null параметр'? Быстрым ответом кажется сказать — 'Кидай исключение, не ошибешься'. Но всегда ли это нужно? Давайте перейдем к примеру, загрузки клиентов с определенным статусом и проверим это:
public interface IClientDao {
    @TargetQuery(query =
            "SELECT id, name, state " +
            "  FROM clients " +
            "  WHERE state = :state", type = QT_SELECT)
    List findClients(@TargetFilter("state") ClientState state);
}
Но вот задача, что делать, если статус клиента в БД может отсутствовать? Колонка state допускает NULL значения и нам нужен поиск именно таких клиентов, безстатусников? Концепция SQL-92 парсера спасает вновь. Достаточно заменить выражение фильтрации по статусу с ':state' на ':state?' как движок фреймворка модифицирует секцию WHERE в следующий вид '… WHERE state IS NULL', если, конечно, на вход методу падать null.

Про меппинг


Фильтры в запросах это конечно хорошо, но в java решениях большое внимание уделяется мэппингу результата работы SQL-запроса на ORM объекты и их связывание и связывание самих сущностей. Этого настолько много, посмотрите чего стоит одна спецификация JPA. Или посмотрите в своих проектах на перекладывания из ResultSet в объекты предметной области или установку значений в PreparedStatement. Громоздко, не правда ли? Я выбрал путь, может меннее надежный и менее изящный, но однозначно — простой. Зачем далеко ходить, когда можно устроить меппинг прям в SQL-запросе. Это ведь первое, что приходит в голову?
Давайте сразу к примерам. И вот задача, как мепить результат запроса, если все колонки таблицы отличаются от полей ORM класса, плюс ORM имеет вложенный объект?
ORM классы
public class Client {
    private long clientId;
    private String clientName;
    private ClientState clientState;
    private Address address;
    private Date regTime;
    // ниже как обычно...
}

enum ClientState {

    ACTIVE(1),
    BLOCKED(2),
    DELETED(3);

    private int state;

    ClientState(int state) {
        this.state = state;
    }

    @TargetMethod
    public int getState() {
        return state;
    }

    @TargetMethod
    public static ClientState getClientState(int state) {
        return values()[state - 1]; // для примера сойдет
    }
}

public class Address {
    private long addressId;
    private String refCity;
    private String refStreet;
}

public interface IClientDao {
    @TargetQuery(query =
            "SELECT cl.id AS client_id, " + // underscore_case -> camelCase преобразование
            "       cl.name AS clientName, " + // можно сразу в camelCase
            "       cl.state AS client_state, " + // any_type to enum преобразование автоматически поддерживается, если в enum имеется аннотация @TargetMethod
            "       cl.regDate AS t#regTime, " + // t# - это директива, что нам нужна и дата и время java.sql.Timestamp -> java.util.Date
            "       addr.addressId AS address.addressId, " + // обращение к вложенному объекту через точку, а как еще?
            "       addr.city AS address.refCity, " +
            "       addr.street AS address.refStreet " +
            "  FROM clients cl, addresses addr" +
            "  WHERE id = :client_id", // допускается указание именованного параметра для простого фильтра
            type = QT_SELECT)
    Client findClient(long clientId);
}
Этот пример более преближен к реальным условиям. Эстетичность меппинга определенно хромает, но все же мне ближе этот вариант, чем бесконечные аннотации или xml файлы. Да, с надежностью беда, здесь и runtime и вопросы рефакторинга ORM объектов и не всегда возможность взять SQL и протестировать в своем любимом БД-клиенте. Но я не скажу, что это безвыходная ситуация — от runtime и рефакторинга спасают тесты. Чтобы запрос проверить в БД-клиенте придется его 'почистить'. Еще момент: Какой SQL-запрос уйдет на сервер БД? Все секции AS будут вырезаны из запроса. Если же потребуется сохранить, например, для client_id значение cid в качестве алиаса, то нужно добавить перед этим алиасом двоеточие: cl.id AS cid:client_id и cid будет жить -> cl.id AS cid в финальном запросе.

И последнее, немного бизнес логики


Идеальные дао, когда одна операция — один декларативный метод. И это конечно хорошо, но так бывает не всегда. В приложении часто нужно получить гибридную или составную сущность, когда часть данных формируется одним SQL-запросом, другая часть вторым SQL-запросом и т.д. Или сделать определенные склейки, проверки. Возможно это и не суть самого дао, но такие манипуляции как правило имеют место быть и их изолируют, но делают публичными, дабы вызывать из разных участков программы. Но что нам делать с интерфейсами, если все же приспичит засунуть бизнес логику в дао? Неожиданно для меня и к удивлению многих разработчиков в java8 появились дефолтные методы. Круто? Да я знаю, что новость протухла, ведь на дворе 2017 год, но не сыграть ли на этом? А что если скрестить декларативный стиль принятый за основу разработки DAO слоя и дефолтные методы для бизнес логики? А давайте посмортим, что из этого получится, если понадобиться добавить проверку ORM объекта на null и загрузить данные из другого DAO:
public interface IClientDao {
    // этот метод уже видели
    @TargetQuery(query =
            "SELECT id, name, state " +
            "  FROM clients " +
            "  WHERE id = ?", type = QT_SELECT)
    Client findClient(long clientId);

    // получить дао внутри другого дао доступно из коробки
    IAddressDao getAddressDao();

    // метод с бизнес логикой
    default Client findClientAndCheck(long clientId, long addressId) throws Exception {
        Client client = findClient(clientId);
        if (client == null) {
            throw new Exception("Клиент с id '" + clientId + "' не найден");
        }
        // Здесь код может быть сколь угодно сложным, если нужно
        IAddressDao addressDao = getAddressDao();
        client.setAddress(addressDao.loadAddress(addressId));
        return client;
    }
}
Не знаю, прав ли, но мне хочется назвать это — интерфейсным программированием. Как-то так. На самом деле и все, чего хотелось рассказать. Но это точно не все, что умеет фреймворк, помимо оговоренного: выборку ограниченного числа колонок, управление схемами, постраничную разбивку (не для всего правда), манипуляции с упорядочиванием данных, отчеты, обновление, встаку и удаление.

Заключение


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

https://habrahabr.ru/post/330720/


Метки:  

«Ближе к народу»: Как сделать IaaS доступнее

Понедельник, 12 Июня 2017 г. 14:04 + в цитатник
Согласно данным одного из whitepaper’ов компании Cisco, к концу этого года более 70% задач по обработке данных будут решать IaaS-провайдеры. Сегодня мы поговорим о том, какие подходы позволили нам сделать сделать IaaS дружелюбным и удобным.


/ Flickr / Dennis Skley / CC

Исследования различных аналитических компаний и экспертов только подкрепляют прогнозы роста значимости ЦОД. Например, Statista говорят об увеличении годового траффика дата-центров до 9,8 эксабайт в 2018 году. Такие нагрузки обостряют задачи эффективного использования ресурсов, повышения гибкости ИТ-инфраструктуры и своевременной реакции на запросы клиентов.

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

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

Для реализации этой возможности от нас не потребовались какие-то сложные манипуляции. Что мы сделали: выбрали графическую библиотеку для вывода информации и настроили интеграцию vSphere c помощью .NET SDK для получения данных о нагрузке отдельных виртуальных машин. Все это — еще один шаг в сторону модели pay-as-you-go — оплаты только за потребленные ресурсы и ввода системы автоматического масштабирования при пиковых нагрузках.

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

  • редизайн основных разделов сайта компании (и еще одно улучшение в дальнейшем)
  • разработка специального калькулятора, который позволяет быстро оценить потенциальные затраты на содержание инфраструктуры и перейти к ее запуску
  • настройка предустановленного ПО (панель управления и возможность отслеживания состояния лицензий) и инструментария для управления контентом (предустановленные CMS)
  • автоматизация различных служебных задач (пример нашего DNS-менеджера)

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


/ Flickr / Bruno Cordioli / CC

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

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

Здесь не помешает политика открытости. Следуя этому принципу, мы подготовили и опубликовали соответсвующие материалы о ЦОД, в которых размещено наше оборудование: SDN (Санкт-Петербург), Dataspace (Москва) и AHOST (Алма-Ата). Помимо этого мы привели базовые данные о производительности.

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

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

Еще больше наших материалов по теме:



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

https://habrahabr.ru/post/330714/


Метки:  

NNCP: лечение online- и цензуро- зависимости store-and-forward методом

Понедельник, 12 Июня 2017 г. 13:23 + в цитатник
В этой статье поднят вопрос удручающей ситуации с доступностью данных в Интернете, злоупотреблением цензурой и тотальной слежкой. Власти ли или корпорации в этом виноваты? Что поделать? Создавать собственные соцсети, участвовать в сетях анонимизации, строить mesh-сети и store-and-forward решения. Демонстрация NNCP утилит для создания этих store-and-forward friend-to-friend решений.



Кто виноват?



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

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

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

Всё не так плохо — всё гораздо хуже. Потому-что доступность информации, глобальная тотальная слежка и цензура уже давно стали де-фактом, без каких-либо принятых законопроектов. Вот только зачинщиками всего этого были и являются корпорации типа Google, Facebook, Microsoft и Apple.

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

Какой Web был раньше, с технологической точки зрения? У пользователей стоит специальная программа (старый добрый тёплый ламповый Web-броузер), которая по стандартизованному HTTP протоколу (говорящему какой документ, какой ресурс нужно получить), подключаясь к серверам, получает HTML (возможно плюс изображения) документ и отображает его. Это распределённая сеть хранения документов в которой один протокол. Мы по сети получаем готовые документы, которые можно сохранить на жёстком диске и читать без дальнейших подключений к серверам.

Как работает Web «корпораций»? У пользователя стоит специальная программа (до сих пор обзывающаяся Web-броузером), которая по транспортному протоколу (это HTTP, но который к гипертексту отношения уже никакого не имеет, который можно заменить какими угодно другими протоколами передачи файлов) получает программу, написанную на JavaScript (хотя сейчас это может быть какой-нибудь WebAssembly, являющийся обычным бинарным исполняемым кодом, аналогично .exe файлу), запускает её в виртуальной машине и эта программа по своему собственному протоколу (то есть правилам взаимодействия с сервером и форматом сообщений) начинает общение с сервером чтобы получить данные и их отобразить на экране. Сохранить отображённые данные, думая что это документ, вряд ли получится. Автоматизировать получение документов тоже не получится, потому-что каждый отдельный сайт это отдельная программа и свой протокол общения со своим форматом сообщений (структурой JSON запросов, например). Теперь это распределённая сеть приложений, скачиваемых на пользовательские компьютеры.

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

Что такое закрытая проприетарная программа? Это то, когда вы не управляете своим компьютером, когда вы не знаете что программа на нём собирается делать Не вы говорите своей машине что ей надо выполнять, а программа вам говорит что вам дозволено делать. Безусловно, всё это касается и любой другой проприетарной программы, не только автоматически скачанному JS-коду. Однако, существенная разница между установленным Microsoft Windows с каким-нибудь Microsoft Word на вашем компьютере и JS-кодом — вы их ставите один раз и если не замечаете ничего опасного или настораживающего в их работе, то просто не беспокоитесь и доверяете. Однако в Web мире вы, каждый раз заходя на сайт, можете получить новую версию программы и любой современный броузер вам этого даже не сообщит. Если раньше вы не замечали что сайт отсылает на сервер приватные данные, то, зайдя на него через пять минут, он может начать. Никто, без специальных плагинов и танцев с бубном, вам об этом не скажет и не предупредит что вы используете другую версию скачанной программы. Экосистема заточена на беспрекословное скачивание несвободных программ. Владельцы таких сайтов могут заставить выполнить на пользовательских компьютеров что им заблагорассудится буквально поменяв несколько файлов на свои серверах и новая версия программы автомагически будет исполнятся.

Может проблемы преувеличены, ведь это же не обычная .exe программа которая имеет доступ до огромного количества ресурсов компьютера, а программа запускающаяся, по идее, в изолированной виртуальной машине? К сожалению, размер и сложность кодовой базы современных броузеров так огромны, что даже просто сделать анализ их безопасности очень затратно, не говоря уж о том, что эта кодовая база меняется настолько быстро, что любой анализ на момент своего завершения будет не актуален. Сложность это главный враг любой системы безопасности. Сложные протоколы типа TLS доказали, что даже если сотни миллионов людей используют и разрабатывают под OpenSSL, который является свободной программой с открытым исходным кодом — там могут быть фатальные критические ошибки. Плюс все мы видели что атаки типа Row hammer можно произвести и из броузера. Кроме того, есть успешные атаки на кэш процессора нацеленные на узнавание ключа AES, выполняемые тоже из броузера. Виртуальная машина столь стремительно изменяющаяся и с такой сложностью не может быть безопасна по определению, ведь даже полная виртуализация в каком-нибудь Xen или KVM не помогает против некоторых атак. Да и какой смысл делать хорошее изолированное окружение, когда бизнес корпораций это наоборот сбор как можно большего количества данных?

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

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

Кто-то может возразить: какая же это слежка за мной в магазине, если я вот пришёл в таком-то пальто и вот моё лицо видно — я сам выдал эту информацию. Действительно, IP адрес, TCP-порты, User-Agent своего броузера я сам присылаю и не могу не присылать — так уж устроен Web. Но если продавец начинает спрашивать как меня зовут, откуда я, ходить за мной по пятам — это уже запрос информации которая не нужна для совершения транзакции купли-продажи, это уже слежка. А ведь сайты корпораций именно этим и занимаются, уничтожая доступ к информации по стандартизованным протоколам (которые так мало о нас говорят) и форматам документов — если они заставят использовать своё ПО, то в нём то уж они вольны следить как заблагорассудится.

Опросите большинство людей кого конкретно по-настоящему задели блокировки Роскомнадзора и они потеряли доступность какой-то информации? Не считая громких кратковременных блокировок типа Github, максимум что люди скажут, так это потеря Рутрэкера. Однако, как и в случае с Pirate Bay, стоит понимать что это уже не прихоть властей и не политика, а власть корпораций типа Голливуда и подобных им. Их денежные состояния и влияние на власть стран достаточно весомы. Самим властям закрывать Рутрэкер или Pirate Bay не имеет смысла, так как, в основном, это дешёвые (с точки зрения инфраструктуры) развлечения отвлекающие людей от политики (потенциально создающей опасность для власти).

А вот потери тонн информации из-за того, что сайт перестал работать простыми HTTP+HTML методами, заставляя людей использовать его ПО, вынуждающее быть постоянно в online (если человек не будет в online, то как о нём собрать информацию?) — по моему, сказались и сказываются всё сильнее и сильнее. Отключите человека от Интернета и он не в состоянии вообще ничего сделать, ни даже прочитать почту или посмотреть свои фотографии или вспомнить о встрече, ведь всё это осталось в облаках.

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

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

Есть много людей которые используют практически только ВКонтакте и YouTube: каждое их действие уже отслеживается, вся их переписка (у них нет email, только учётная запись в ВК или Telegram) читается, вся поступающая информация тривиально цензурируется (сколько раз Facebook был замечен за манипуляцией людьми, путём цензуры данных?). И таких уже возможно большинство, однако никто насильно их не заставлял и выбор есть до сих пор. Для них у провайдеров уже есть специальные более дешёвые тарифные планы в которых доступ только до ряда сервисов. Когда масса этих людей станет совсем критической, то останутся только такие тарифные планы, так как какой смысл поддерживать провайдеру инфраструктуру обеспечивающие доступ ко всему Интернету, когда достаточно иметь пиринг с полдюжиной сетей корпораций и 99.99% пользователей довольны? Цены на полноценные тарифы будут повышаться (если останутся вообще) и это уже станет барьером для доступности Интернета.

Людей волнует что в TLS соединении будет принудительно выставлен CA сертификат как это например произошло в Казахстане, для того чтобы следить, прослушивать и цензуру устраивать? Но, при этом, этих же самых людей не волнует что другие стороны (сервисы корпораций) вообще устанавливают своё закрытое ПО и свои протоколы? Эти же самые люди, путём размещения информации только в соцсетях, как-раз поддерживают централизацию и гегемонию одной корпорации над всеми данными. Они уже давно самостоятельно роют себе Интернет-могилы, пытаясь свалить все беды на «дефолтного» крайнего.

К корпорациям с распростёртыми руками, а власть, которая делает куда менее катастрофичные вещи, хаять.

Что делать?



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

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

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

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

Если не нравится что всё большее количество провайдеров не даёт статические IP-адреса, вообще не даёт полноценных адресов, а только внутренний адрес за NAT-ом, то размещение ресурсов внутри overlay сетей типа Tor hidden service (.onion) или I2P (.i2p) может быть единственным способом подключиться к вам извне. Необходимо не забывать об участии в подобных сетях и жертвовать, зачастую неиспользуемые, ресурсы ваших компьютеров. Развивать и поддерживать не только low-latency сети, априори подверженные ряду атак из-за своей природы, но и сети типа Freenet и GNUnet. Чтобы не было как всегда: пока гром не грянет.

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

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

Но не забываем про желания корпораций о запрете изменения прошивок на WiFi-маршрутизаторах и не забываем что огромное количество WiFi модулей не работают без бинарных блобов. Подобный vendor-lockin не исключает жёсткий контроль над трафиком, как например, в современных проприетарных ОС невозможно установить программы не криптографически подписанные корпорациями (одобрившими запуск данного ПО). Сделать WiFi чип самостоятельно в домашних условиях очень дорого и не исключено что с рынка пропадут и те, на которых можно было бы сделать mesh-сеть. Речь не только про WiFi, но и любые другие беспроводные решения с толстым каналом связи. Любительские радиостанции можно сделать и в домашних условиях, но ёмкость их каналов плачевна, да и нельзя так просто взять и поднять такую станцию дома — нужны разрешения.

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

Кроме того, mesh-сети и их протоколы хорошо заточены исключительно под real-time соединения. Они рассчитаны на то, чтобы можно было бы в реальном времени открывать сайты или удалённо использовать терминал. Если связанность в сети теряется, то это равносильно разрыву кабеля и, до восстановления, другой участок сети будет недоступен, приводя в негодность real-time low-delay программы. Необходимо ещё и обеспечивать хорошее резервирование каналов — что дорого и ресурсоёмко.

Более кардинальное решение это забыть о real-time сервисах и вспомнить что и без них жизнь вполне возможна. Так ли критично что ваше сообщение дойдёт не сиюсекундно, а возможно через несколько минут или часов? Электронная почта остаётся самым надёжным и распространённым методом общения и она не гарантирует никаких временных рамок по доставке: задержки в десятки минут штатны.

Для чтения большинства сайтов real-time не нужен в принципе. Сайт можно скачать, доступными во всех свободных ОС, программами типа GNU Wget и отправить зеркало сайта к себе. Существует и стандарт для хранения Web-данных: WARC (Web ARChive), используемый в Internet Archive. Один файл может содержать полностью весь Web-сайт. Такой же подход используется и в Freenet сети: сайты тоже помещаются в архив, чтобы не качать через сеть сотни блоков данных, которые наверняка были бы запрошены, а атомарно сразу же получить всё. Подчеркну: формат Web архивов уже стандартизован и десятки петабайт Internet Archive сделаны именно в нём. Ценные страницы стоит сохранить просто средствами Web-броузера на диске, ведь сегодня ссылка рабочая, а завтра уже запросто нет. Вы возможно усвоили информацию по ней, но дать своему товарищу уже не сможете. Это потерянная информация, особенно в условиях централизованных систем типа CDN и соцсетей.

Если отказаться от real-time и обязательного сиюминутного online режима работы, то не нужны даже mesh-сети, а достаточны гораздо менее требовательные store-and-forward (сохранить и отправить дальше) решения. Одной из таких сетей, существующей до сих пор, является FidoNet, который, до распространения дешёвого Интернета, был вполне себе распространённой (среди того самого меньшинства) глобальной сетью. Да, сообщения шли часами или целыми днями, но, поверьте, интересного общения в Фидо запросто было куда больше чем сейчас можно найти среди невероятного количества Интернет форумов и рассылок.

Node-to-Node CoPy



Я ничуть не призываю возродить FidoNet или UUCP (Unix-to-Unix CoPy), про использование которого я уже писал раньше!

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

Во-вторых, тот же FidoNet, был создан для простых персональных компьютеров с DOS-ом, в отличии от UUCP бывшим де-факто методом связи между Unix-системами. Сейчас почти любая свободная ОС это Unix-like мир. Не многие захотят отказаться от привычного почтового клиента и привычных программ для общения с «внешним миром». FidoNet с UUCP это совершенно иные экосистемы.

В идеале, хотелось бы иметь нечто, что было бы как можно более прозрачно для email и передачи файлов. Вместе с этим и имело современную криптографическую безопасность. Этого можно достичь с UUCP, но прикручивая к нему дополнительные обёртки для шифрования и аутентификации. Кроме того, ни FidoNet ни UUCP не заточены из коробки на работу через сменные накопители информации. Да, в FidoNet можно скопировать исходящие пакеты на дискету и на целевом узле скопировать их назад, но это ручное действие, легко получающееся в виду простоты системы, но штатно не предусмотренное (нет команд «вот тебе дискета, я хочу пойти к Вовану», «вот я с дискетой от Вована, побывавшая и у Васька, действуй»).

Для удовлетворения этой хотелки и создан набор свободных (GNU GPLv3+) программ NNCP. Они заточены на то, чтобы, как можно с меньшими человеческими затратами, организовать современную небольшую store-and-forward сеть.

Рассмотрим как всем этим пользоваться на конкретных примерах.

Мы, Алиса (возьмём имена уж из мира криптографии) и Боб, установили себе NNCP и у нас куча nncp-* команд. Для начала создадим (командой nncp-cfgnew) свои собственные пары криптографических ключей:

alice% nncp-cfgnew | tee alice.yaml
self:
  id: ZY3VTECZP3T5W6MTD627H472RELRHNBTFEWQCPEGAIRLTHFDZARQ
  exchpub: F73FW5FKURRA6V5LOWXABWMHLSRPUO5YW42L2I2K7EDH7SWRDAWQ
  exchprv: 3URFZQXMZQD6IMCSAZXFI4YFTSYZMKQKGIVJIY7MGHV3WKZXMQ7Q
  signpub: D67UXCU3FJOZG7KVX5P23TEAMT5XUUUME24G7DSDCKRAKSBCGIVQ
  signprv: TEXUCVA4T6PGWS73TKRLKF5GILPTPIU4OHCMEXJQYEUCYLZVR7KB7P2LRKNSUXMTPVK36X5NZSAGJ632KKGCNODPRZBRFIQFJARDEKY
  noiseprv: 7AHI3X5KI7BE3J74BW4BSLFW5ZDEPASPTDLRI6XRTYSHEFZPGVAQ
  noisepub: 56NKDPWRQ26XT5VZKCJBI5PZQBLMH4FAMYAYE5ZHQCQFCKTQ5NKA
neigh:
  self:
    id: ZY3VTECZP3T5W6MTD627H472RELRHNBTFEWQCPEGAIRLTHFDZARQ
    exchpub: F73FW5FKURRA6V5LOWXABWMHLSRPUO5YW42L2I2K7EDH7SWRDAWQ
    signpub: D67UXCU3FJOZG7KVX5P23TEAMT5XUUUME24G7DSDCKRAKSBCGIVQ
    noisepub: 56NKDPWRQ26XT5VZKCJBI5PZQBLMH4FAMYAYE5ZHQCQFCKTQ5NKA
    sendmail:
    - /usr/sbin/sendmail
spool: /var/spool/nncp/alice
log: /var/spool/nncp/alice/log


NNCP создаёт исключительно Friend-to-Friend (F2F) сети, где каждый участник знает о соседях с которыми общается. Если Алисе надо контактировать с Бобом, то, предварительно, они обязаны обменяться своими публичными ключами и прописать их в конфигурационные файлы. Узлы обмениваются между собой, так называемыми, зашифрованными пакетами — неким аналогом OpenPGP. Каждый пакет явно адресован заданному участнику. Это не Peer-to-Peer (P2P), где любой может подключиться к любому и что-то отослать: это создаёт возможность Sybil атак, когда ноды злоумышленника могут вывести всю сеть из строя или, как минимум, следить за активностью участников в ней.

Простейший конфигурационный файл содержит следующие поля:

  • self.id — идентификатор нашей ноды
  • self.exchpub/self.exchprv и self.signpub/self.signprv — ключи используемые для создания зашифрованных пакетов
  • self.noisepub/self.noiseprv — опциональные ключи используемые при общении узлов по TCP соединению
  • neigh — содержит информацию обо всех известных участниках сети, «соседях». Всегда содержит запись self — в ней находятся ваши публичные данные и именно её можно смело раздавать людям, так как в ней только публичные части ключей
  • spool — путь до spool-директории где находятся исходящие зашифрованные пакеты и необработанные входящие
  • log — путь до журнала в котором сохраняются все выполняемые действия (отправленный файл/письмо, принятые, итд)


Боб генерирует свой файл. Обменивается с Алисой своим ключом и добавляет её в свой конфигурационный файл (Алиса делает то же самое):

alice% cat bob.yaml
self:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
  exchprv: HXDO6IG275S7JNXFDRGX6ZSHHBBN4I7DQ3UGLOZKDY7LIBU65LPA
  signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
  signprv: TT2F5TIWJIQYCXUBC2F2A5KKND5LDGIHDQ3P2P3HTZUNDVAH7QUPO6L7GFDTZKXFNVAIEQY7GDO2NNESVZXX6JL3BXRF7JVYQGYU3IA
  noiseprv: NKMWTKQVUMS3M45R3XHGCZIWOWH2FOZF6SJJMZ3M7YYQZBYPMG7A
  noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
neigh:
  self:
    id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
    exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
    signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
    noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
    sendmail:
    - /usr/sbin/sendmail
  alice:
    id: ZY3VTECZP3T5W6MTD627H472RELRHNBTFEWQCPEGAIRLTHFDZARQ
    exchpub: F73FW5FKURRA6V5LOWXABWMHLSRPUO5YW42L2I2K7EDH7SWRDAWQ
    signpub: D67UXCU3FJOZG7KVX5P23TEAMT5XUUUME24G7DSDCKRAKSBCGIVQ
    noisepub: 56NKDPWRQ26XT5VZKCJBI5PZQBLMH4FAMYAYE5ZHQCQFCKTQ5NKA
spool: /var/spool/nncp/bob
log: /var/spool/nncp/bob/log


Далее, Боб хочет послать файл Алисе:

bob% nncp-file -cfg bob.yaml ifmaps.tar.xz alice:
2017-06-11T15:33:20Z File ifmaps.tar.xz (350 KiB) transfer to alice:ifmaps.tar.xz: sent


и ещё и бэкап своей файловой системы, но делая через Unix-way конвейеры:

bob% export NNCPCFG=/path/to/bob.yaml
bob% zfs send zroot@backup | xz -0 | nncp-file - alice:bobnode-$(date "+%Y%m%d").zfs.xz
2017-06-11T15:44:20Z File - (1.1 GiB) transfer to alice:bobnode-20170611.zfs.xz: sent


Затем он может посмотреть на то, чем загромождена его spool директория:

bob% nncp-stat
self
alice
        nice: 196 | Rx:        0 B,   0 pkts | Tx:    1.1 GiB,   2 pkts


Каждый пакет, кроме информации об отправителе и получателе, содержит ещё и, так называемый, nice уровень. Это просто однобайтное число — приоритет. Почти все действия можно сопроводить ограничением на максимально допустимый уровень nice. Это аналог grade в UUCP. Используется для того, чтобы в первую очередь обрабатывать пакеты с более высоким приоритетом (меньшим значением nice), чтобы почтовые сообщения проходили не смотря на то, что в фоне пытается передаться фильм на DVD. По-умолчанию, для отправки файлов задаётся приоритет 196, который мы и видим. Rx это принятые пакеты, ещё не обработанные, а Tx это пакеты для передачи.

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

При передаче файлов можно указать опцию -chunked, указав ей размер кусочка на которые должен будет быть побит файл. Тут используется очень напоминающая BitTorrent схема: файл бьётся на части и добавляется meta-файл содержащий информацию о каждом кусочке, для гарантированного, с точки зрения целостности, восстановления файла. Это может быть полезно, если нужно скрыть размеры огромных файлов (как с трупом — одним куском его проблематично перетаскивать, а вот разделив на шесть частей, куда проще). Ещё полезно, когда нужно передать большие объёмы данных через накопители заведомо меньших размеров: тогда передача данных произведётся за несколько итераций.

Теперь это надо как-то передать Алисе. Один из способов — через накопитель данных, через копирование файлов на файловой системе.

Боб берёт USB flash накопитель, создаёт на нём файловую систему, запускает:

bob% nncp-xfer -mkdir /mnt/media
2017-06-11T18:23:28Z Packet transfer, sent to node alice (1.1 GiB)
2017-06-11T18:23:28Z Packet transfer, sent to node alice (350 KiB)


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

Вместо USB накопителя может выступать временная директория из которой будет создан ISO образ для записи на компакт-диск. Это может быть какой-то публичный FTP/NFS/SMB сервер, подмонтированный в /mnt/media директорию. Подобный NAS может быть размещён на работе или ещё где-то — лишь бы двое контактирующих участников хоть изредка но имели возможность подключиться к нему. Это может быть портативный PirateBox, по пути собирающий и раздающий NNCP пакеты. Это может USB dead drop, к которому время от времени, подключаются совершенно разные и незнакомые люди: если в целевой директории есть неизвестные ноды, то мы их игнорируем и можем только узнать о факте их существования и количестве передаваемых пакетов.

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

Использование nncp-xfer не требует никаких приватных ключей: нужны только знания о соседях — их идентификаторы. Таким образом, можно отправляться в дорогу с spool директорией и минимальным конфигурационным файлом (без приватных ключей) подготовленным командой nncp-cfgmin, не боясь за компрометацию ключей. nncp-toss вызов, требующий приватных ключей, можно выполнить в любое другое удобное время. Однако, если всё же боязно за безопасность приватных ключей или даже конфигурационного файла, где все ваши соседи перечислены, то можно использовать утилиту nncp-cfgenc, позволяющую его зашифровать. В качестве ключа шифрования используется парольная фраза введённая с клавиатуры: в зашифрованном файле есть соль, пароль усиливается CPU и memory hard алгоритмом Balloon, поэтому, имея хорошую парольную фразу, можно особо не беспокоиться о компрометации (кроме паяльника).

Алисе достаточно выполнить команду чтобы скопировать предназначенные для неё файлы в свою spool директорию:

alice% nncp-xfer /mnt/media
2017-06-11T18:41:29Z Packet transfer, received from node bob (1.1 GiB)
2017-06-11T18:41:29Z Packet transfer, received from node bob (350 KiB)
alice% nncp-stat
self
bob
        nice: 196 | Rx:    1.1 GiB,   2 pkts | Tx:        0 B,   0 pkts


Мы видим, что у неё появились необработанные (Rx) пакеты в spool директории. Однако, не всё так просто. У нас хоть и сеть друзей (F2F), доверяй, но проверяй. Вы должны явно разрешить заданной ноде отправлять вам файлы. Для этого Алиса должна добавить в раздел Боба конфигурационного файла указание куда помещать передаваемые от него файлы.

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
  signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
  noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
  incoming: /home/alice/bob/incoming


После этого, нужно запустить обработку входящих зашифрованных пакетов командой nncp-toss (аналогично tosser-у в FidoNet):

alice% nncp-toss
2017-06-11T18:49:21Z Got file ifmaps.tar.xz (350 KiB) from bob
2017-06-11T18:50:34Z Got file bobnode-20170611.zfs.xz (1.1 GiB) from bob


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

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

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
  signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
  noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
  incoming: /home/alice/nncp/bob/incoming
  freq: /home/alice/nncp/bob/pub


теперь Боб может сделать запрос на файл:

bob% nncp-freq alice:pulp_fiction.avi PulpFiction.avi
2017-06-11T18:55:32Z File request from alice:pulp_fiction.avi to pulp_fiction.avi: sent
bob% nncp-xfer -node alice /mnt/media


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

alice% nncp-toss
2017-06-11T18:59:14Z File /home/alice/nncp/bob/pub/pulp_fiction.avi (650 MiB) transfer to bob:PulpFiction.avi: sent
2017-06-11T18:59:14Z Got file request pulp_fiction.avi to bob


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

Теперь, представим, что Алиса и Боб знают Еву (но эта Eve хорошая, не плохая криптографическая, ведь у нас же сеть друзей), однако Алиса не имеет прямого контакта с ней. Они живут в разных городах и только Боб курирует между ними время от времени. В NNCP поддерживаются транзитные пакеты, устроенные аналогично луковичному шифрованию в Tor: Алиса может создать зашифрованный пакет для Евы, и поместить его в ещё один зашифрованный пакет для Боба, с указанием, что его надо переслать Еве. Длина цепочки не ограничена и промежуточный участник знает только предыдущее и последующее звено цепи, не зная настоящего отправителя и получателя.

Указание транзитного пути задаётся записью via в ноде. Например Алиса хочет сказать что Ева доступна через Боба:

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
  signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
  noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
eve:
  self:
  id: URVEPJR5XMJBDHBDXFL3KCQTY3AT54SHE3KYUYPL263JBZ4XZK2A
  exchpub: QI7L34EUPXQNE6WLY5NDHENWADORKRMD5EWHZUVHQNE52CTCIEXQ
  signpub: KIRJIZMT3PZB5PNYUXJQXZYKLNG6FTXEJTKXXCKN3JCWGJNP7PTQ
  noisepub: RHNYP4J3AWLIFHG4XE7ETADT4UGHS47MWSAOBQCIQIBXM745FB6A
  via: [bob]


Как конкретно Ева контактирует с Бобом Алисе не известно и нет необходимости знать: как-то сообщения до неё должны дойти и Боб видит только факты отправки транзитного трафика. Исходящее сообщение до Евы у Боба создаётся автоматически nncp-toss-ом после обработки сообщения от Алисы.

Если речь идёт о хорошей безопасности, то для этого нужно использовать компьютеры с воздушным зазором (air-gapped) — не подключённые к сетям передачи данных, в идеале имеющих, например, только CD-ROM/RW. А «перед» ними находится компьютер в который втыкаются флешки или трафик от других нод: на нём можно убедиться что флешки не содержат ничего вредоносного, и, если он подключён к Интернету или другой сети, то уязвимости ОС не могут скомпрометировать air-gapped компьютер. На подобную ноду приходят только транзитные пакеты, которые породят исходящие сообщения для air-gapped компьютера записываемые на компакт-диск.

Если в конфигурационный файл добавить раздел:

notify:
  file:
    from: nncp@bobnode
    to: bob+file@example.com
  freq:
    from: nncp@bobnode
    to: bob+freq@example.com


то на почту bob+file@example.com будут отправляться уведомления о переданных файлах, а на bob+freq@example.com уведомления о запрошенных файлах.

NNCP может быть легко интегрирован с почтовым сервером для прозрачной передачи почты. Чем обычный SMTP не подходит, ведь он тоже store-and-forward? Тем, что нельзя использовать флешки (без плясок), тем что SMTP трафик, как и почтовые сообщения, не очень компактен (бинарные данные в Base64 виде), плюс в нём довольно серьёзные ограничения на максимальное время ожидания доставки корреспонденции. Если вы деревушка в Уганде и до вас раз в неделю ходит курьер с флешкой, то SMTP тут никак не подойдёт, а NNCP в самый раз передаст пачку писем для каждого жителя деревушки и заберёт назад в город.

Для отправки почты используется nncp-mail команда. Фактически это точно такая же передача файла, но на целевой машине вызывается sendmail, вместо сохранения сообщения на диск, и само сообщение сжимается. Настройка Postfix занимает буквально считанные строки:

  • в master.cf указывается как использовать nncp транспорт (как вызвать nncp-mail команду)
  • для заданного домена/пользователя задаётся что должен быть использован этот nncp транспорт
  • указать что заданный домен или пользователь является транзитным (relay)


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

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  exchpub: GQ5UPYX44T7YK5EJX7R73652J5J7UPOKCGLKYNLJTI4EBSNX4M2Q
  signpub: 654X6MKHHSVOK3KAQJBR6MG5U22JFLTPP4SXWDPCL6TLRANRJWQA
  noisepub: M5V35L5HOFXH5FCRRV24ZDGBVVHMAT3S63AGPULND4FR2GIPPFJA
  sendmail: [/usr/sbin/sendmail, "-v"]


Подобный прозрачный подход позволяет полностью избавиться от POP3/IMAP4 серверов и почтовых клиентов сохраняющих письма как черновики, когда нужно прозрачно принимать и отсылать почту между вашим почтовым сервером и ноутбуком. Почтовый сервер всегда смотрит в Интернет, а ноутбук без разницы как часто подключается: NNCP примет почту и отправит в его локальный sendmail, который доставит до локального почтового ящика. И аналогично отправка: падающие сообщения в локальный почтовый сервер на ноутбуке сохранятся в NNCP spool директории и будут отправлены на сервер как только, так сразу (с точки зрения почтового клиента почта успешно отправлена сразу же). Плюс сжатый почтовый трафик.

Использование переносных накопителей не всегда удобно, особенно сейчас, когда Интернет у нас всё же работоспособный имеется. NNCP можно удобно использовать для передачи пакетов поверх TCP соединений. Для этого имеется nncp-daemon демон и nncp-call команда его вызывающая.

Для обмена зашифрованными пакетами можно было бы использовать rsync протокол, возможно поверх OpenSSH соединения, но это бы означало какой-то костыль, плюс очередное звено и очередные ключи для аутентификации нод. В NNCP используется самостоятельный протокол синхронизации SP (sync protocol), используемый поверх Noise-IK зашифрованного и аутентифицированного канала связи. NNCP пакеты хоть и зашифрованы, но светиться ими публично в каналах связи не хорошо — поэтому над этим и имеется дополнительный слой. Noise предоставляет совершенную прямую секретность (PFS, когда компрометация приватных ключей Noise не даст прочитать ранее перехваченный трафик) и двустороннюю аутентификацию: к вам не смогут подключиться незнакомые люди и что-то отправить или попытаться принять.

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

SP старается быть экономным в количестве пересылаемых пакетов данных: уже в момент Noise-IK рукопожатия сразу же отправляются списки пакетов доступных для скачивания, а запросы на скачивание отправляются пачкой.

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

Протокол позволяет продолжить скачивание любого пакета с любого произвольного места. То есть, при обрыве соединения, самое ощутимая потеря это TCP+Noise рукопожатие, а дальше продолжение с того же места. Как только на ноде появляется новый пакет для отправки, то противоположная сторона сразу же (раз в секунду) об этом осведомляется, позволяя ей прервать текущее скачивание и поставить в очередь более приоритетный пакет.

% nncp-daemon -nice 128 -bind [::]:5400


команда поднимает демона, слушающего на всех адресах по 5400 TCP порту, но пропускающий пакеты с уровнем nice не выше 128-го. Если возможные адреса демона заданной ноды известны, то их можно прописать в конфигурационном файле:

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  ...
  addrs:
    lan: "[fe80::be5f:f4ff:fedd:2752%igb0]:5400"
    pub: "bob.example.com:5400"


а затем вызвать ноду:

% nncp-call bob
% nncp-call bob:lan
% nncp-call bob:main
% nncp-call bob forced.bob.example.com:1234


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

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

К сожалению, в SP протоколе нет возможности договориться сторонам о том, что соединение может быть закрыто. Сейчас это реализуется за счёт timeout: если обе ноды ничего не имеют для отправки и получения, то через заданное время они рвут соединение. Но это время можно выставить -onlinedeadline опцией на очень большой срок (часы) и тогда у нас будет долгоживущее соединение, в котором сразу будут слаться оповещения о новых пакетах. Это позволяет экономить на дорогих рукопожатиях протокола. В случае с отправкой почты, это также и очень быстрое оповещение о пришедшей корреспонденции, без необходимости как в POP3 постоянно делать polling, разрывая соединение.

TCP соединения можно использовать и без подключения к Интернету. Например, немножко помазавшись shell-скриптами можно настроить на ноутбуке поднятие ad-hoc WiFi сети и слушать демоном на IPv6 link-local адресе, пытаясь постоянно подключаться в другому известному адресу. И проезжая в метро на эскалаторе, два ноутбука, относительно быстро, могут увидеть друг друга в общей ad-hoc сети и быстро «пострелять» друг в друга NNCP пакетами. WiFi довольно быстр, поэтому даже за считанные секунды можно передать одной только почты в больших количествах. А если это вагон электрички, где регулярно одни и те же люди, примерно в одно и то же время ездят каждый день на работу или с неё, то там могут быть и десятки минут постоянной связи. Но не забываем, что, передавая каждый день двухтерабайтный жёсткий диск, мы получим только в одном направлении 185 мегабитный канал связи с отличным качеством (без ошибок).

Ещё одной отличительной утилитой является nncp-caller. Это демон, который занимается вызовом соседей по TCP в заданное время, по заданным адресам, с заданными параметрами передачи/приёма. Его конфигурация прописывается для каждой нужной ноды с использованием cron-выражений. Например:

bob:
  id: FG5U7XHVJ342GRR6LN4ZG6SMAU7RROBL6CSU5US42GQ75HEWM7AQ
  ...
  calls:
    -
      cron: "*/10 9-21 * * MON-FRI"
      nice: 128
      addr: pub
      xx: rx
    -
      cron: "*/1 21-23,0-9 * * MON-FRI"
      onlinedeadline: 3600
      addr: lan
    -
      cron: "*/1 * * * SAT,SUN"
      onlinedeadline: 3600
      addr: lan


Это описание вызовов говорит следующее: каждые десять минут в будние дни, в рабочее время, свяжись с Бобом (по публичному адресу, чтобы попытка соединения с локальным IPv6 link-local адресом не выдала его адреса в сеть), принимая только высокоприоритетные пакеты (наверняка почта), но ничего не отправляя; в не рабочее время пытайся связаться с ним по LAN адресу, принимая пакеты любых приоритетов и удерживая TCP соединение (незачем тратиться на рукопожатия), как минимум, в течении часа простоя; в выходные дни работай круглосуточно только с его LAN адресом.

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

Рассмотрены конечно же не все функции и сценарии использования NNCP. Совсем не рассмотрены форматы пакетов и SP протокол. Основной принцип при создании этих утилит: KISS и бойкот сложности. Хотя этот набор далеко не всё делает в Unix-way стиле и берёт много задач сверх минимально необходимого для store-and-forward сетей, но это исключительно для экономии на дополнительных зависимостях.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330712/


Метки:  

Копировщик RFID-меток стандарта EM-Marin

Понедельник, 12 Июня 2017 г. 12:32 + в цитатник
Как известно, во многих системах доступа используются карты RFID стандарта EM-Marin с частотой 125 КГц. Не исключением стал и домофон моего дома. Одна проблема – неплохо бы научиться копировать такие карты, ибо ценники на их копирование не радуют. В сети, конечно, существует довольно много схем копировщиков (да и китайцы продают их за копейки), но почему бы не собрать свой собственный копировщик? Вот об этом и нижеприведённая статья.

Начинать разработку копировщика стоит с выяснения, а на что вообще можно скопировать такие метки? Почитав форумы, можно узнать, что наиболее распространёнными болванками для копирования являются T5577, T5557, EM4305.
Теперь нужна схема. Возьмём аналоговую часть такого копировщика у RECTO и подключим её к микроконтроллеру atmega8. Дополним преобразователем уровней для подключения к COM-порту на базе max232 (желающие могут использовать ST232 или ещё что, чтобы подключится по USB, но у меня на компьютере COM-порт есть, как есть и переходник USB-COM, так что у меня такой задачи не стояло).
Получится вот такая схема:


Что она из себя представляет? Сдвоенный эмиттерный повторитель, колебательный контур, детектор и RC-фильтры. За счёт того, что RC-фильтры имеют разные постоянные времени, сравнивая между собой уровни напряжения между каскадами можно выделять изменение сигнала RFID-метки. Данной задачей у нас будет заниматься встроенный в atmega8 компаратор. Генерацию 125 КГц сигнала у нас будет обеспечивать встроенный в atmega8 ШИМ-контроллер.
Комбинация RFID-метка – считыватель образуют трансформатор, где метка является вторичной обмоткой. Передача информации меткой производится путём изменения нагрузки вторичной обмотки. В результате в катушке считывателя (первичной обмотке) изменяется ток. Выделением этих импульсов тока и занимается приведённая выше аналоговая часть схемы. колебательный контур нужно настроить на максимальное напряжение в контрольной точке, например, сматывая/наматывая витки катушки. Правда, говорят, лучше всё же напряжение немного меньше максимума — стабильнее работает. У меня в контрольной точке около 40 В.

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



Метод расшифровки манчестерского кодирования и код для этого я взял у Shads. Можно, конечно, было написать свой собственный, но я торопился запустить копировщик — хотелось убедиться, что схема рабочая и приём меток производится. Так этот фрагмент и остался в коде копировщика. Также оказалось, что у меня компаратор настроен инверсно, чем нужно коду декодирования. Изменил в коде. Итак, мы получили последовательности нулей и единиц. Как из них получить код карты?
А очень просто. Примем, что номер карты по нибблам имеет вид AB CD EF GH IJ. Карта выдаёт вот что:

1) Девять единиц в начале;
2) Ниббл A;
3) Чётность ниббла A (1 бит);
4) Ниббл B;
5) Чётность ниббла B (1 бит);

16) Ниббл I;
17) Чётность ниббла I (1 бит);
18) Ниббл J;
19) Чётность ниббла J (1 бит);
20) Ниббл чётности колонок для нибблов A B C D E F G H I J;
21) Бит 0.

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

Карту мы читать научились, а вот как передать данные карте? Для этого нужно просто включать или выключать частоту 125 КГц в соответствии с протоколом обмена с картой. На время “молчания” считывателя карта питается запасённой энергией.
Болванки T5557/T5577 полностью совместимы между собой по протоколам записи, однако, имеют немного разные минимальные и максимальные времена импульсов (к счастью, времена T5557 перекрываются с T5577). У EM4305 протокол записи иной.

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



Запись начинается с сигнала StartGape – требуется отключить сигнал 125 КГц примерно на 300 мкс. Это сигнал карте, что сейчас ей начнут передавать данные. Дальше следует передать болванке информацию. Кодирование передаваемых данных – тот же манчестер.

Болванки T5557/T5577 и EM4305 многофункциональные и умеют разные виды модуляций, поддерживают пароли и ещё много чего. В каждой болванке на борту имеется набор блоков по 32 бита. Назначение этих блоков разное. В некоторых – выдаваемый код ключа (он занимает два блоков). В других – конфигурация. В третьих – идентификатор производителя. Мы будем использовать ограниченный функционал, поэтому желающие разобраться, что значат все эти биты, могут заглянуть в документацию к болванкам (я приложил её к архиву).

Блоки собраны в две страницы (0 и 1).


В нулевой странице есть блок конфигурации с индексом 0. Его мы и будем задавать. Для T5557/T5577 у нас будут следующие конфигурационные байты: 0x00,0x14,0x80,0x40 в соответствии с таблицей из документации (красным я отметил выбранные единичными битами режимы):



Таким образом, у нас выбрано: частота передачи данных RF/64 (125 КГц/64), кодирование типа манчестер, выдача блоков до второго (в блоках 1 и 2 у нас будет располагаться код, выдаваемый картой). Перед записью следует отправить код операции (2 бита opcode) и один бит защёлки (lockbit). Коды операции 10b и 11b предшествуют записи данных для страниц 0 и 1 (младший бит задаёт номер страницы, старший — код записи страницы). У нас выдаётся 10b для кода операции (вся работа идёт с нулевой страницей) и 0b для бита защёлки. После передачи всех этих данных необходимо передать трёхбитный адрес записываемой страницы. Все передачи данных для T5557/T5577 ведутся от старшего бита к младшему.



Задав код карты в блоках 1 и 2 и конфигурацию в блоке 0 можно получить дубликат RFID-метки. Как видите, всё просто.

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



Кодирование передаваемых карте данных – по времени импульса, то есть, частотная модуляция. Конфигурационное слово хранится в 4 байте и для себя я определил его так: 0x5F,0x80,0x01,0x00 (кодирование манчестер, RF/64, выдача слова 6). В слова 5 и 6 я записываю код карты (те самые 64 бита, что выдаёт карта). EM4305 требует чтобы передача велась от младшего бита к старшему. Карта понимает, что с ней начинают обмен после выдачи ей комбинации импульсов:
  1. Отключаем поле на 48 мкс.
  2. Включаем поле на 96 мкс.
  3. Выключаем поле на 320 мкс.
  4. Включаем поле на 136 мкс.
  5. Отключаем поле до следующей команды.


Команда на запись блока карте передаётся так:
  1. Шлём вышеуказанную последовательность импульсов.
  2. Шлём 0b.
  3. Передаём CC0-CC1 и их чётность P. (0101b для записи, см. таблицы ниже).
  4. Передаём адрес блока (см. таблицу), два дополняющих нуля и чётность адреса.
  5. Передаём данные блока (32 бита).



Формат команды


Коды команд


Формат адреса блока

Таким образом задаётся конфигурация болванки EM4305 и её код.

Собственно, ничего большего простому копировщику и не требуется.

Я сделал несколько вариантов копировщика с разными дисплеями. Например, вот копировщик с дисплеем 1602:


А вот видео работы копировщика на дисплее LPH9157-02:




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



В архиве все схемы, печатки, программы и документация на болванки. Есть версия для Arduino Nano (её нужно прошивать отдельно через программы для заливки сторонних прошивок).

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

Отдельное большущее спасибо RECTO, BolshoyK и Shads — без вас я бы развлекался бы с разработкой довольно долго!

Спасибо за внимание.

P.S. Я не являюсь профессионалом в копировании ключей и в болванках, поэтому вполне мог в чём-то ошибиться. Однако, копировщик работает и в нём ошибок пока никто не нашёл.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330710/


Метки:  

[Перевод] Вы — не Google

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

Рациональные люди не принимают решения таким образом. Но именно так программисты часто решают использовать что-то вроде MapReduce.

Вот как комментировал этот выбор Joe Hellerstein своим студентам (на 54-той минуте):

Дело в том, что в мире сейчас есть где-то 5 компаний, обрабатывающие данные подобных объёмов. Все остальные гоняют все эти данные туда-сюда, добиваясь отказоустойчивости, которая им на самом деле не нужна. Люди страдают гигантоманией и гугломанией где-то с середины 2000-ых годов: «мы сделаем всё так, как делает Google, ведь мы же строим один из крупнейших (в будущем) сервисов по обработке данных в мире!»


imageСколько этажей в вашем датацентре? Google сейчас строит четырёхэтажные, как вот этот в Оклахоме.


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

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

image

Да, это ещё одна «анти карго-культ» статья. Но подождите! У меня ниже есть полезный чек-лист, который поможет вам принимать более взвешенные решения.

Клёвая технология? Ну, давайте заценим.



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

  1. Задумайтесь о том, какую проблему вам нужно решить в вашем продукте. Поймите её до конца. Вашей задачей должно быть решить эту проблему с помощью каких-то инструментов, а не решить «какую-то» проблему, которую может решить понравившийся вам инструмент.
  2. Выберите несколько потенциальных инструментов. Не выбирайте себе одного «любимчика», пока не будете иметь списка из нескольких альтернатив!
  3. Для каждого кандидата найдите его "Главный Документ" и прочтите его. Не комментарии или переводы, а «тот самый» документ.
  4. Определите исторический контекст, который привёл к созданию данного инструмента таким, каким он был создан.
  5. Идеалов не бывает. Взвесьте преимущества и недостатки каждого инструмента. Поймите, на какие компромиссы пришлось пойти его создателям, чтобы достичь главных заявленных ими целей.
  6. Думайте! Трезво и честно ответьте себе на вопрос, совпадают ли ваши цели и приоритеты с целями и приоритетами создателей тех инструментов, которые вы планируете использовать. Какой новый факт об этом инструменте мог бы заставить вас отказаться от его использования? Например, насколько должен отличаться масштаб ваших задач от тех, на которые рассчитан инструмент, чтобы заставить вас задуматься о нерациональности его применения?


А ещё вы — не Амазон


Вышеуказанную методику достаточно легко применять на практике. Совсем недавно я обсуждал с одной компанией их планы использовать Cassandra для нового (достаточно нагруженного операциями чтения) проекта. Я прочёл оригинальный документ, в котором описывались принципы работы Dynamo и, зная, что Cassandra достаточно близка к Dynamo, понял, что эти системы строились на принципе приоритизации операций записи (Амазон хотел, чтобы операция «добавить в корзину» абсолютно всегда проходила успешно). И они даже добились этого, пожертвовав, однако, гарантированной консистентностью и функциональностью традиционных реляционных баз данных. Но компания, с которой я обсуждал применения этого решения, не ставила запись данных в приоритеты (все данные приходили одним блоком раз в сутки), а вот консистентность и производительное чтение были как-раз очень нужны.

image
Амазон продаёт много разных товаров. Если операция «добавить в корзину» вдруг начнёт работать нестабильно — они потеряют много денег. У вас та же ситуация?

Компания, которая хотела внедрить у себя Cassandra, делала это потому, что один из запросов в их PostgreSQL выполнялся несколько минут и они считали, что упёрлись в аппаратные возможности платформы. После буквально пары уточняющих вопросов, оказалось, что перенос их базы на SSD ускорял этот запрос на 2 порядка (до нескольких секунд). Это всё ещё было медленно, конечно, но просто оцените, как переход на SSD (секундное дело), смог ускорить узкое место где-то в 100 раз. Без всяких фундаментальных переделок архитектуры. Стало совершенно ясно, что переход на Cassandra — не верный путь. Здесь требовался некоторый тюнинг существующего решения, возможно — перемоделирования схемы базы данных, но определённо не попытки решить проблему ЧТЕНИЯ инструментом, созданным для решения проблем ЗАПИСИ.

К тому же, вы — не LinkedIn


Я был очень удивлён, когда узнал, как одна моя знакомая компания решила построить свою архитектуру вокруг Kafka. Это показалось мне странным, поскольку их основной задачей был процессинг нескольких десятков (в хороший день — нескольких сотен) достаточно важных транзакций. Для такой входной нагрузки подошло бы даже решение "бабушка, вручную записывающая операции в бумажный блокнот". Kafka, чтобы вы понимали, был создан для сбора всей аналитической информации LinkedIn (все вот эти сообщения, запросы, группы, чаты, новости, оценки и т.д.). Огромное количество данных. Не могу оценить их объёмы сейчас, но несколько лет назад LinkedIn называл цифру порядка 1 триллиона событий в день, с отдельными пиками до 10 миллионов в секунду. Да, я понимаю, что и 10 транзакций в день тоже с его помощью можно успешно обработать. Но зачем это делать? Разница теоретической нагрузки и практической необходимости составляет 10 ПОРЯДКОВ!
image
Наше Солнце, например, всего на 6 порядков больше Земли.

Да, вполне возможно, что инженеры этой компании рационально оценили все преимущества Kafka и выбрали этот инструмент по каким-то неизвестным мне причинам, не связанным с нагрузками. Но более вероятно, что они просто подпали под влияние какого-то восторженного отзыва, статьи, мнения сообщества или что-то ещё, кричавшее им «бери Kafka»… Нет, ну вы подумайте, 10 порядков!

Я уже говорил, что вы — не Амазон?


Ещё более популярной вещью, чем распределённые базы данных Амазона, является их архитектурный подход для масштабирования: сервис-ориентированная архитектура (SOA). В 2001 году Амазон осознал, что не может эффективно масштабировать нагрузки на свой front end, и, работая над решением этой проблемы, пришел к SOA. Эта история успеха передавалась устно и письменно от одного инженера к другому и вот уже мы приходим к ситуации, когда каждый первый стартап из трёх программистов и нуля пользователей начинает свой жизненный путь с того, что разбивает свою домашнюю страницу на наносервисы. К тому времени, когда Амазон осознал необходимость перехода на SOA, у них было 7800 сотрудников и они продавали товаров на 3 миллиарда долларов в год.

imageВот этот зал вмещает 7000 людей. А у Амазона было 7800, когда понадобился переход на SOA.

Я не говорю, что вам нужно ждать момента найма 7800-го сотрудника для внедрения SOA. Просто задайте себе вопрос — это вот именно та проблема, которую нужно решать сейчас? Других уже нет? Если вы мне скажете, что это действительно так и ваша компания из 50 человек вот прямо сейчас упирается именно в отсутствие SOA, то как вы объясните тот факт, что есть в десятки раз большие компании, которые от этого нисколько не страдают?

И даже Google — не Google


Использование хорошо масштабирующихся инструментов, вроде Hadoop или Spark может быть очень интересным. Однако, на практике классические инструменты лучше подходят для обычных и даже больших нагрузок. Иногда кажущиеся «большими» объёмы могут даже полностью поместиться в ОЗУ одного компьютера. Вы знали, что уже сегодня можете получить терабайт ОЗУ за примерно 10000 долларов? Даже если у вас есть целый миллиард пользователей (а у вас его нет), то за эту цену вы получите целый килобайт данных в ОЗУ на каждого из них. Очень быстро доступный килобайт. Возможно, этого недостаточно для ваших задач и придётся что-то читать\писать с диска. Но сколько это будет дисков — вот прямо тысячи? Вряд ли. Вам не понадобятся решения вроде GFS и MapReduce, которые создавались, на минуточку, для хранения поискового индекса ВСЕГО ИНТЕРНЕТА.

image
Место на жестких дисках сегодня стоит значительно дешевле, чем в 2003 году, когда было опубликовано описание GFS.

Возможно, вы читали документацию по GFS и MapReduce. Тогда вы можете помнить, что основной проблемой, которую пытался решить Google, была не вместимость, а пропускная способность. Они построили распределённую систему, чтобы получить доступ к нужным данным быстрее. Но сегодня на дворе 2017 год и пропускная способность устройств выросла. Учтите, что вам не понадобится обрабатывать столько же данных, сколько обрабатывает Google. Так, может быть, достаточно будет купить жестких дисков получше (возможно, SSD) и этого хватит?

Возможно, вы рассчитываете со временем вырасти. Но считали ли вы, на сколько именно? Будете ли вы аккумулировать данные быстрее, чем будет падать стоимость SSD? Насколько вашему бизнесу нужно будет вырасти до того момента, когда все ваши данные перестанут помещаться на одной физической машине? В 2016 году система Stack Exchange, обслуживающая 200 миллионов запросов в сутки, использовала всего 4 SQL сервера: один для Stack Overflow, один для всего остального и две реплики.

Вы можете пройти по указанному мной выше чек-листу и всё равно остановиться на Hadoop или Spark. И это даже может быть верным решением. Важно, однако, понимать, что верное здесь и сейчас решение не обязательно останется таким надолго. Google знает это хорошо: как только они решили, что MapReduce недостаточно хорошо работает для построения поискового индекса, они перестали его использовать.

Прежде всего поймите вашу проблему


Эта мысль не моя и она далеко не нова. Но, возможно, в интерпретации этой статьи, вместе с чек-листом выше она достучится до вашего сердца. Если нет, вы можете просмотреть такие материалы, как Hammock Driven Development, How to Solve It или The Art of Doing Science and Engineering — во всех них проскакивает один и тот же призыв думать, пытаться понять проблему перед тем, как героически бросаться её решать. В книге G. Polya есть такая фраза:
Глупо пытаться найти ответ на вопрос, которого ты не знаешь. Грустно работать над тем, что не решает твою проблему.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330708/


Метки:  

[Перевод] Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3

Понедельник, 12 Июня 2017 г. 12:15 + в цитатник

От переводчика


Недавно наткнулся в python digest на туториал по Flask+Bokeh. Туториал ориентирован на новичков, не требуется даже знать синтаксис Python и HTML. Примеры работают под Ubuntu 16.04, на Windows немного отличается работа с виртуальными окружениями.
image


Вступление


Bokeh — это мощная библиотека с открытым исходным кодом, которая позволяет визуализировать данные для веб-приложений, не написав ни строчки на javascript. Изучение библиотек для визуализации вроде d3.js может оказаться полезным, но гораздо легче написать несколько строк кода на Python, чтобы решить задачу.
С Bokeh мы можем создавать поразительно детальные интерактивные визуализации или же более простые вещи, вроде столбчатых диаграмм.
Давайте разберёмся, как можно использовать Flask и Bokeh для визуализации данных в веб-приложении.


Инструменты


Всё, что описано далее, работает как на Python 2, так и на Python 3, однако, рекомендуется использовать Python 3 для новых приложений. Я использовал Python 3.6.1 на момент написания этой статьи. Помимо самого Python, нам потребуются следующие зависимости:


  • Веб-фреймворк Flask, версия 0.12.2
  • Библиотека визуализации данных Bokeh, версия 0.12.5
  • Библиотека анализа данных pandas, версия 0.20.1
  • pip и virtualenv поставляются вместе с Python 3. Они понадобятся, чтобы изолировать Flask, Bokeh и pandas от остальных проектов.

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


Установка Bokeh и Flask


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


python3 -m venv barchart

Активируйте виртуальное окружение.


source barchart/bin/activate

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


pip install bokeh==0.12.5 flask==0.12.2 pandas==0.20.1

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


Installing collected packages: six, requests, PyYAML, python-dateutil, MarkupSafe, Jinja2, numpy, tornado, bokeh, Werkzeug, itsdangerous, click, flask, pytz, pandas
  Running setup.py install for PyYAML ... done
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for tornado ... done
  Running setup.py install for bokeh ... done
  Running setup.py install for itsdangerous ... done
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 PyYAML-3.12 Werkzeug-0.12.2 bokeh-0.12.5 click-6.7 flask-0.12.2 itsdangerous-0.24 numpy-1.12.1 pandas-0.20.1 python-dateutil-2.6.0 pytz-2017.2 requests-2.14.2 six-1.10.0 tornado-4.5.1

Теперь мы можем перейти непосредственно к нашему приложению.


Запуск приложения на Flask


Мы напишем простое Flask-приложение и добавим столбчатую диаграмму на страницу.
Создайте папку для своего прокта с файлом app.py с таким содержанием:


from flask import Flask, render_template

app = Flask(__name__)

@app.route("//")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1
    return render_template("chart.html", bars_count=bars_count)

if __name__ == "__main__":
    app.run(debug=True)

Это простое Flask-приложение, в котором есть функция chart. chart принимает целое число, которое позже будет использоваться для определения количества данных для отрисовки. Функция render_template внутри chart будет использовать шаблонизатор Jinja2 для генерации HTML.
Последние 2 строки позволяют нам запустить приложение из консоли на 5000 порту в режиме отладки. Никогда не используйте режим отладки в продакшене, для этого существуют WSGI-серверы проде Gunicorn.
Создайте папку templates внутри папки проекта. Внутри неё создайте файл chart.html. Он нужен функции chart из app.py, поэтому без него приложение не будет работать правильно. Заполните chart.html Jinja2-разметкой.




  
    
  
  
    

Bugs found over the past {{ bars_count }} days


Заготовка chart.html будет показывать количество столбцов, переданное в функцию chart через URL.
Сообщение внутри тега h1 отвечает теме нашего приложения. Мы будем строить график количества багов, найденных систимой автоматического тестирования за каждый день.
Теперь мы можем протестировать наше приложение.
Убедитесь, что виртуальное окружение всё ещё активно и вы находитесь в папке с app.py. Запустите app.py с помощью команды python.


$(barchart) python app.py

Перейдите на localhost:5000/16/. Вы должны увидеть большое сообщение, которой меняется, когда вы меняете URL.

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


Генерация столбчатой диаграммы


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


import random
from bokeh.models import (HoverTool, FactorRange, Plot, LinearAxis, Grid,
                          Range1d)
from bokeh.models.glyphs import VBar
from bokeh.plotting import figure
from bokeh.charts import Bar
from bokeh.embed import components
from bokeh.models.sources import ColumnDataSource
from flask import Flask, render_template

Остальная часть файла будет использовать Bokeh вместе с модулем random для генерации данных и столбчатой диаграммы.
Данные для диаграммы будут генерироваться заново при каждой перезагрузке страницы. В реальном приложении используйте более надёжный и полезный источник данных!
Продолжайте изменять app.py. Код после импортов должен выглядеть следующим образом.


app = Flask(__name__)

@app.route("//")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1

    data = {"days": [], "bugs": [], "costs": []}
    for i in range(1, bars_count + 1):
        data['days'].append(i)
        data['bugs'].append(random.randint(1,100))
        data['costs'].append(random.uniform(1.00, 1000.00))

    hover = create_hover_tool()
    plot = create_bar_chart(data, "Bugs found per day", "days",
                            "bugs", hover)
    script, div = components(plot)

    return render_template("chart.html", bars_count=bars_count,
                           the_div=div, the_script=script)

Функция chart сгенерирует списки с данными с помощью встроенного модуля random.
chart вызывает 2 функции: create_hover_tool и create_bar_chart. Мы пока не написали эти функции, поэтому продолжим добавлять код после функции chart:


def create_hover_tool():
    # эту функцию мы напишем чуть позже
    return None

def create_bar_chart(data, title, x_name, y_name, hover_tool=None,
                     width=1200, height=300):
    """Создаёт столбчатую диаграмму.
        Принимает данные в виде словаря, подпись для графика,
        названия осей и шаблон подсказки при наведении.
    """
    source = ColumnDataSource(data)
    xdr = FactorRange(factors=data[x_name])
    ydr = Range1d(start=0,end=max(data[y_name])*1.5)

    tools = []
    if hover_tool:
        tools = [hover_tool,]

    plot = figure(title=title, x_range=xdr, y_range=ydr, plot_width=width,
                  plot_height=height, h_symmetry=False, v_symmetry=False,
                  min_border=0, toolbar_location="above", tools=tools,
                  responsive=True, outline_line_color="#666666")

    glyph = VBar(x=x_name, top=y_name, bottom=0, width=.8,
                 fill_color="#e12127")
    plot.add_glyph(source, glyph)

    xaxis = LinearAxis()
    yaxis = LinearAxis()

    plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
    plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
    plot.toolbar.logo = None
    plot.min_border_top = 0
    plot.xgrid.grid_line_color = None
    plot.ygrid.grid_line_color = "#999999"
    plot.yaxis.axis_label = "Bugs found"
    plot.ygrid.grid_line_alpha = 0.1
    plot.xaxis.axis_label = "Days after app deployment"
    plot.xaxis.major_label_orientation = 1
    return plot

Здесь много кода, с которым нужно разобраться. Функция create_hover_tool пока только возвращает None, потому что пока нам не нужно отображать подсказки при наведении.
Внутри функции create_bar_chart мы преобразуем сгенерированные данные в объект типа ColumnDataSource, который мы можем передать на вход функциям Bokeh для построения графика. Мы задаём диапазоны осей x и y.
Пока у нас не настроены подсказки при наведении, список tools пуст. Вся магия происходит в вызове функции figure. Мы передаём ей всё необходимое для построения графика: размер, панель инструментов, границы, настройки поведения графика при изменении размера окна браузера.
Мы создаём вертикальные столбцы с помощью класса VBar и добавляем их на график с помощью функции add_glyph, которая задаёт правила, по которым данные превращаются в столбцы.
Последние 2 строки изменяют оформление графика. Для примера я убрал логотип Bokeh при помощи plot.toolbar.logo = None и добавил подписи к обеим осям. Я рекомендую держать документацию bokeh.plotting перед глазами, чтобы знать, как можно кастомизировать визуализацию.
Нам нужно сделать всего пару изменений в файле templates/chart.html, чтобы отобразить график. Откройте файл и замените его содержимое следующим.




  
    
    
    
  
  
    

Bugs found over the past {{ bars_count }} days

{{ the_div|safe }} {{ the_script|safe }}

2 из 6 добавленных строк нужны для загрузки CSS-файлов Bokeh, ещё 2 для загрузки его скриптов, и ещё 2 — для генерации графика.
Всё готово, давайте проверим наше приложение. Flask должен автоматически перезгрузить приложение, когда вы сохраните изменения в app.py. Если вы остановили веб-сервер, вы можете снова его запустить командой python app.py.
Откройте в браузере localhost:5000/4/.

Выглядит немного пусто, но мы можем изменить количество столбцов на 16, если перейдём на /localhost:5000/16/.

Ещё в 4 раза больше...

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


Добавление подсказок при наведении


Внутри app.py измените функцию create_hover_tool


def create_hover_tool():
    """Generates the HTML for the Bokeh's hover data tool on our graph."""
    hover_html = """
      
$x
@bugs bugs
$@costs{0.00}
""" return HoverTool(tooltips=hover_html)

Встраивание HTML в приложение на Python может показаться довольно странным, но именно так мы определяем вид подсказки. Мы используем $x, чтобы показать x-координату столбца, @bugs, чтобы показать поле "bugs" источника данных и $@costs{0.00}, чтобы показать поле "costs" как числ с 2 знаками после запятой.
Убедитесь, что вы заменили return None на return HoverTool(tooltips=hover_html).
Вернитесь в браузер и перезагрузить страницу localhost:5000/128/.

Отличная работа! Попробуйте поиграть с количеством столбцов в URL и посмотрите как график будет выглядеть.
График выглядит заполненным примерно при 100 столбцах, но вы можете попробовать задать любое значение. На 50,000 получается грустная картина:

Да уж, похоже, нам нужно сделать ещё что-то, чтобы можно было отображать больше пары сотен столбцов за раз.


Что дальше?


Мы создали отличный график на Bokeh. Далее вы можете изменить цветовую схему, подключить другой источник данных, попробовать другие типы графиков или придумать, как отображать огромные объёмы данных.
С помощью Bokeh можно делать гораздо больше, так что стоит посмотреть официальную документацию, репозиторий на GitHub, страничку Bokeh на Full Stack Python.
Есть вопросы? Задайте мне из через GitHub либо через Twitter @fullstackpython или @mattmakai.

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

https://habrahabr.ru/post/330706/


Метки:  

[Из песочницы] Создаем свой кастомный плагин Style – Темизация Views в Drupal 8

Понедельник, 12 Июня 2017 г. 11:27 + в цитатник
Модуль Views (Представления) является составляющей ядра Drupal 8. На сегодняшний день об этом известно всем. Twig – это новый обработчик шаблонов в Drupal 8. Об этом нам тоже уже известно. Но как же программно взаимодействовать с модулем Views, для того чтобы темизировать View с использованием Twig? Кроме перекрывания шаблонов, как это происходит в любой другой системе модулей, у нас есть более мощная альтернатива в виде Views плагинов (Display, Style, Row и Field).

В данной статье мы разберемся, как в Drupal 8 создать Style плагин для Views. Мы используем разметку вкладок Bootstrap и реализуем вкладочный вывод наших результатов Views. В конфигурации Views, настройки Style позволят нам определить, какое поле будет использоваться как копия вкладочной навигации, и оставлять видимыми в соответствующих вкладках остальные поля. В целом, каждый результат View будет представлять собой вкладку, поэтому такой пример не подойдет для представлений, у которых слишком много результатов. Наша цель – продемонстрировать процесс создания собственного Style плагина в Drupal 8.

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

Что такое Style плагин?


Style плагин отвечает за передачу списков. Яркими примерами Style плагинов являются Неформатированный список, Список HTML, Таблица или Сетка. Их использует плагин Display, а они в свою очередь используют плагины Row, которые представляют один пункт списка.

В Drupal 8, все типы плагинов Views построены с использованием новой системы плагинов и имеют общий функционал (все они наследуются из одного и того же PluginBase).

Давайте создадим такой же Style плагин, который может быть использован большинством типов Display (Page, Block и т.д) и который использует плагин Field.

Style плагин Вкладки Bootstrap


Первый шаг – это создание нашего класса плагина в папке Plugin/views/style нашего модуля:

namespace Drupal\demo\Plugin\views\style;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;

/**
 * Views стиль, который передает разметку для Bootstrap вкладок.
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *   id = "bootstrap_tabs",
 *   title = @Translation("Bootstrap Tabs"),
 *   help = @Translation("Uses the Bootstrap Tabs component."),
 *   theme = "demo_bootstrap_tabs",
 *   display_types = {"normal"}
 * )
 */
class BootstrapTabs extends StylePluginBase {

  /**
   * Разрешает ли этот Style плагин Row плагины?
   *
   * @var bool
   */
  protected $usesRowPlugin = TRUE;

  /**
   * Поддерживает ли Style плагин группировку строк?
   *
   * @var bool
   */
  protected $usesGrouping = FALSE;

  /**
   * {@inheritdoc}
   */
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['tab_nav_field'] = array('default' => '');
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);
    $options = $this->displayHandler->getFieldLabels(TRUE);
    $form['tab_nav_field'] = array(
      '#title' => $this->t('The tab navigation field'),
      '#description' => $this->t('Select the field that will be used as the tab navigation. The rest of the fields will show up in the tab content.'),
      '#type' => 'select',
      '#default_value' => $this->options['tab_nav_field'],
      '#options' => $options,
    );
  }
}

Плагин Drupal, который мы создадим – это ViewsStyle с кое-какими базовыми данными о нем, переданными в аннотации. Не говоря о самых очевидных данных, у нас есть ключевые слова theme и display_types, о которых стоит упомянуть. Theme объявляет какую функцию темы будет использовать плагин для передачи данных, а display_types объявляет, каким видом плагинов Display может использоваться этот Style (в нашем случае все типы Display, если других не определено: normal). Если хотите узнать более подробную информацию о всех возможных конфигурациях Annotation, ознакомьтесь с Drupal\views\Annotation\ViewsStyle класса Annotation.

Используя два свойства класса мы объявили, что наш плагин использует Row плагины, но не разрешает группирование. Убедитесь, что вы проверили все родительские и учли другие похожие опции. Например, класс, который мы наследуем, уже объявил, что поля Views могут быть использованы с плагином Style.

Как мы уже говорили ранее, используя два метода, мы создаем опцию плагина и элемент формы, которые могут определить, какое поле должно задействоваться в качестве навигации вкладки. Используя обработчик текущего состояния ($this->displayHandler), мы можем загрузить все доступные поля View, которые добавил движок сайта. И этот новый элемент формы будет доступен в форме настроек Style:

image

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

Тема


Самое время определить тему demo_bootstrap_tabs , как мы это делаем обычно (внутри нашего файла .module):

/**
 * Implements hook_theme().
 */
function demo_theme($existing, $type, $theme, $path) {
  return array(
    'demo_bootstrap_tabs' => array(
      'variables' => array('view' => NULL, 'rows' => NULL),
      'path' => drupal_get_path('module', 'demo') . '/templates',
    ),
  );
}

Плагин Style по умолчанию передает шаблону объект $view и результат $rows . Обработка этих переменных (если это необходимо) перед тем, как передать шаблону – это задание препроцессора.

/**
 * Подготовка переменных для шаблона представления demo_bootstrap_tabs.
 *
 * Шаблон: demo-bootstrap-tabs.html.twig.
 *
 * @param array $variables
 *   Ассоциативный массив содержит:
 *   - view: объект view.
 *   - row: массив пунктов rows. Каждый row – это массив из контента.
 */
function template_preprocess_demo_bootstrap_tabs(&$variables) {
  $view = $variables['view'];
  $rows = $variables['rows'];
  $variables['nav'] = array();

  // Подготовка навигации вкладок.
  $field = $view->style_plugin->options['tab_nav_field'];
  if (!$field || !isset($view->field[$field])) {
    template_preprocess_views_view_unformatted($variables);
    return;
  }

  $nav = array();
  foreach ($rows as $id => $row) {
    $nav[$id] = array(
      '#theme' => 'views_view_field',
      '#view' => $view,
      '#field' => $view->field[$field],
      '#row' => $row['#row'],
    );
  }

  template_preprocess_views_view_unformatted($variables);
  $variables['nav'] = $nav;
}

Что же происходит здесь? Во-первых, мы проверяем настройки плагина Style на предмет того, было ли использовано имя поля (того, которое было выбрано при конфигурировании View). Если нет, мы вызываем return, но только после того, как препроцессор выполнит функцию template_preprocess_views_view_unformatted. Далее, по результатам Views проходит цикл, и, в итоге, создается массив контента для нашей вкладочной навигации. Для этого, мы используем функцию темы views_view_field для того, чтобы передать выбранное поле. Наконец, мы передаем этот массив шаблону и запускаем препроцессор стиля неформатированного списка.

Шаблон


В Drupal 8 больше нет функций тем, теперь все обрабатывается в шаблонах Twig. Давайте посмотрим, как файл demo-bootstrap-tabs.html.twig выглядит в нашей папке шаблонов модуля:

    {% for tab in nav %} {% set active = '' %} {% if loop.index0 == 0 %} {% set active = 'active' %} {% endif %}
  • {{ tab }}
  • {% endfor %}
{% for row in rows %} {% set active = '' %} {% if loop.index0 == 0 %} {% set active = 'active' %} {% endif %}
{{ row.content }}
{% endfor %}

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

Первое, что мы передаем – это пункты навигации вкладок (из нашей переменной nav). Когда цикл перебирает этот массив, мы также извлекаем пользу из значения индекса цикла для того чтобы сделать первый пункт активным значением по умолчанию, а также для того, чтобы у нас была возможность нацеливаться на окна контента вкладок, используя уникальные ID. Что же касается реального значения пунктов, мы просто выводим передаваемый массив, который был создан в препроцессоре, и Drupal занимается его передачей. С учетом изложенного, возможно, хорошей идеей будет убедиться, что поле, которое вы здесь используете, относительно короткое, без ссылки и основной разметки. Названия, возможно, будут работать нормально. Но тут дело в соответствующей конфигурации View.

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

Заключение


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

Данная статья является переводом статьи "Theming Views in Drupal 8 – Custom Style Plugins".
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330704/


Метки:  

Теория и практика unattended upgrades в Ubuntu

Понедельник, 12 Июня 2017 г. 09:59 + в цитатник
Unattended upgrades — это родной для Debian/Ubuntu (и других основанных на них дистрибутивов GNU/Linux) механизм автоматических обновлений. По умолчанию он включён в системе благодаря наличию установленного пакета unattended-upgrades и конфигурационного файла /etc/apt/apt.conf.d/50unattended-upgrades, а настроен на обновления пакетов только из security-репозитория, куда попадают, например, критичные исправления для пакета libssl, которые выходят в результате очередного пополнения базы уязвимостей CVE.



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

Итак, какие возможности предоставляют unattended upgrades и к каким проблемам могут привести?

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

К слову о времени срабатывания unattended upgrades пакетов: проверка обновлений и их установка в системах Ubuntu/Debian определяется в /etc/cron.daily/apt. Файл запускается из /etc/crontab, в котором по умолчанию задано раннее утро (06:25).

Применение для репозитория/PPA


Перейдём к практике. У нас была следующая проблема: один пакет устанавливался на все серверы, но не в родной его версии, а с определенными модификациями. Что делать? Очевидные варианты:
  • «Давайте проклянём на несколько часов одного из стажёров и пусть себе выкатывает!» — возможно, это и окажется полезным для стажёра, но только на этапе обучения работы с системой и при условии, что он совсем не умеет работать apt. Дальше это действительно превратится в проклятие. Вдобавок, получаемый результат будет больше зависим от человеческого фактора, чем хотелось бы.
  • «Выкатить везде с Chef/Puppet/Ansible/…!» — отличная мысль, но не наш случай на тот момент и в той ситуации. Ради одного пакета пришлось бы победить дракона, т.е. завести множество машин в выбранную систему управления конфигурациями.
  • Поскольку статья посвящена unattended upgrades, легко догадаться, что именно этот механизм и предлагает иной способ решить задачу, не потратив на неё множество человекочасов…

Действительно: конфигурация unattended upgrades позволяет активировать автоматическое получение всех обновлений какого-либо пакета для выбранного репозитория — например, вашего собственного или PPA, которому вы имеете основания очень доверять. Чтобы вся эта автомагия заработала на минимальном уровне, достаточно на целевой машине создать конфиг /etc/apt/apt.conf.d/51unattended-upgrades-custom, в котором будут всего три строки:

Unattended-Upgrade::Allowed-Origins {
"Origin:Suite";
};


Если у вас свой репозиторий, то слова Origin и Suite должны как минимум вызывать ассоциации из разряда «Где-то я уже это видел…». Подскажу, что такие параметры можно увидеть у репозитория на самой машине, где должен обновляться пакет, в файле *_InRelease. Иллюстрация для хорошо известной ppa:nginx/stable:

$ head -10 /var/lib/apt/lists/ppa.launchpad.net_nginx_stable_ubuntu_dists_trusty_InRelease 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Origin: LP-PPA-nginx-stable
Label: NGINX Stable
Suite: trusty
Version: 14.04
Codename: trusty
Date: Sat, 11 Feb 2017 21:55:33 UTC
Architectures: amd64 arm64 armhf i386 powerpc ppc64el

Видим два параметра:
  • Origin — «происхождение» репозитория, что может указывать на имя мейнтейнера или самого репозитория;
  • Suite — ветка дистрибутива; например, stable, testing для Debian или trusty, xenial для Ubuntu.

Примечание: Более подробную документацию по файлам Release/InRelease и используемым в них параметрах см. на wiki.debian.org.

Таким образом, если у вас свой репозиторий пакетов, нужно добавить эти параметры. В результате, для данного примера с PPA nginx/stable конфигурация, разрешающая unattended upgrades для всех пакетов из него, будет выглядеть следующим образом:

Unattended-Upgrade::Allowed-Origins {
  "LP-PPA-nginx-stable:trusty";
};

Эти настройки логично автоматизировать (тем методом, который вам наиболее близок и/или уже используется), создавая все необходимые конфигурационные файлы при разворачивании новых инсталляций ОС. А проверить, как себя поведёт очередной запуск unattended upgrades, можно следующим образом:

$ unattended-upgrade -v --dry-run

Флаги здесь простые: -v — быть более многословным, а --dry-run — не применять изменения. При пробном запуске мы сразу увидим, что у этого решения могут быть обратные стороны:
  • Если установка пакета требует интерактивности, т.е. вмешательства пользователя (особенно актуально, если вы делаете масштабное обновление системы, поскольку уже давно не этого не делали) — unattended upgrades просто ничего не будут делать.
  • Пример с автоматическим обновлением nginx из PPA не стоит проверять в реальной жизни production, если только вы не хотите прослыть «грязным Гарри» обновления пакетов. Ещё раз вспомните название утилиты и запомните, что обновлять так можно только то, что действительно не требует никакого присмотра. Например, библиотеки (хотя даже тут бывают подводные камни, если вспомнить хотя бы недавние танцы Ubuntu с libc и сломанным DNS resolver) и софт, не требующий конфигурации и запускающийся по требованию (atop, htop и подобные).

Ещё один «обратный» пример, который даже не относится к добавлению специфичных PPA в unattended upgrades, но с которым мы не раз сталкивались на практике, — это обновления безопасности для PostgreSQL. В стандартной конфигурации Ubuntu Server (т.е. установки только обновлений безопасности через unattended upgrades) автоматическое обновление критичных уязвимостей в этой СУБД приводило к её рестарту в то время, когда не все этого ожидали. Если такое поведение может оказаться неожиданным (нежелательным) и для вашего случая — см. опцию Package-Blacklist ниже.

Дополнительные возможности


В /etc/apt/apt.conf.d/50unattended-upgrades можно увидеть, что ещё умеют unattended upgrades. Настроек не так много, но некоторые из них полезны:
  • Package-Blacklist — список пакетов, которые запрещено обновлять подобным способом. Тут же нам в примере сразу предлагают это сделать для libc, а выше описан другой пример — с PostgreSQL. Но помните, что на другой чаше весов: откладывая критичные исправления, вы рискуете безопасностью.
  • AutoFixInterruptedDpkg — если последний процесс установки/обновления не смог завершиться по каким-либо причинам, вероятно, вам приходилось исправлять ситуацию вручную. То же самое делает и эта опция, т.е. вызывает dpkg --force-confold --configure -a. Обратите внимание, что здесь указана опция --force-confold — она означает, что будут сохранены старые версии конфигов, если возникнут конфликты.
  • MinimalSteps — выполнять обновления минимально возможными частями. Позволяет прервать обновление отправкой SIGUSR1 процессу unattended-upgrade.
  • InstallOnShutdown — устанавливать обновления перед выключением компьютера. Лично мне кажется плохой идеей, т.к. не хотелось бы получить труп после плановой перезагрузки сервера.
  • Mail и MailOnlyOnError — кому отправлять письма об обновлениях и/или проблемах с ними. Письма отправляются через стандартный MTA sendmail (используется переменная окружения SENDMAIL_BINARY). К сожалению, только письма, а выполнять curl к какому-то API здесь нельзя.
  • Automatic-Reboot — перезагружать автоматически после окончания установки, если есть файл /var/run/reboot-required. Сам файл появляется, например, после установки пакета ядра Linux, когда срабатывает правило /etc/kernel/postinst.d/update-notifier. В общем, ещё одна опция из набора «грязного Гарри».
  • Automatic-Reboot-Time — если вы хотите сделать свои тёмные дела ночью, пока никто не видит… задаёт конкретное время автоматической перезагрузки.
  • Acquire::http::Dl-Limit — это уже из общего набора параметров apt. Ограничивает скорость загрузки обновлений, чтобы не забить канал.

Выводы


Unattended upgrades — инструмент автоматической установки обновлений, встроенный в дистрибутивы на базе Debian и Ubuntu. Обычно его используют для установки обновлений безопасности (security updates) из соответствующего репозитория, но легко расширить применение и на любые другие репозитории. Инструмент будет полезен для поддержки простых в установке и обновлении программ и скриптов, собранных в пакеты, — для реализации требуется лишь собственно репозиторий и несколько строк в конфиге на обновляемой машине.

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

https://habrahabr.ru/post/330406/


PHP-Дайджест № 110 – свежие новости, материалы и инструменты (28 мая – 11 июня 2017)

Понедельник, 12 Июня 2017 г. 01:10 + в цитатник


Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 7.2.0 Alpha 1, свежие предложения из PHP Internals, Symfony 3.3.0, Yii 1.1.19 и 2.0.12, нововведния Laravel 5.5, спор о Visual Debt и многое другое.
Приятного чтения!




Новости и релизы




PHP


  • Короткий синтаксис для анонимных функций — В Internals активно обсуждается возможная реализация коротких лямбд. Ранее рассматривался и был отклонен на голосовании синтаксис с тильдой: $x ~> $x + 1;, а также ^($x) => $x + $y. На данный момент рассматриваются следующие возможные варианты:
    fn(params) => expr
    function(params) => expr
    (params) ==> expr
    (params) => expr
    [](params) => expr
    

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


Инструменты


  • reactphp/http v0.7.0 — HTTP/HTTPS сервер на базе ReactPHP.
  • hollodotme/fast-cgi-client — FactCGI клиент для отправки (а)синхронных запросов в PHP-FPM. Слайды, примеры использования с Redis и с RabbitMQ.
  • samdark/hydrator — Извлечение данных и заполнение данными объектов. Пост в поддержку.
  • phunkie/phunkie — Набор структур для функционального программирования на PHP. Туториал в поддержку.
  • gilbitron/sqsd — Демон эмулирует работу Amazon SQSD на локальной машине.
  • formapro/pvm — Библиотека для описания схемы процесса (workflow). Поддерживает асинхронные переходы и параллельное выполнение задач.
  • Composercat — Десктопное GUI приложение для Composer.
  • php-enqueue/enqueue-dev — Очередь сообщений с поддержкой транспортов AMQP (RabbitMQ, ActiveMQ), STOMP, Amazon SQS, Redis, Doctrine DBAL, Filesystem.


Материалы для обучения




Занимательное


  • joaoescribano/UltimaPHP — Сервер популярной некогда игры Ultima Online на PHP.
  • 6 фалов, которые являются валидным PHP — GIF, PDF, JPG, которые можно выполнить как PHP.
  • Тайпхинты и интерфейсы — визуальный шум? — Jefаrey Way опубликовал видео, в котором рекомендует удалить тайпхинты, интерфейс, и объявление final, называя их визуальным шумом. На что получил ряд критических ответов, например тут, тут, и тут. Энтузиасты даже создали специальный инструмент статического анализа для поиска «визуального шума» — phpvisualdebt.

Спасибо за внимание!

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

Прислать ссылку
Быстрый поиск по всем дайджестам
<- Предыдущий выпуск: PHP-Дайджест № 109

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

https://habrahabr.ru/post/330696/


Метки:  

Дайджест свежих материалов из мира фронтенда за последнюю неделю №266 (5 — 11 июня 2017)

Понедельник, 12 Июня 2017 г. 00:07 + в цитатник
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.

Веб-разработка
CSS
Javascript
Браузеры
Занимательное

Веб-разработка



CSS



JavaScript



Браузеры



Занимательное



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



Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330694/


[Перевод] Идиоматичный Redux: Дао Redux'а, Часть 1 — Реализация и Замысел

Воскресенье, 11 Июня 2017 г. 21:41 + в цитатник

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


Введение


Я потратил много времени, обсуждая онлайн паттерны использования Redux, была ли это помощь тем, кто изучает Redux в Reactiflux каналах, дискуссии о возможных изменениях в API библиотеки Redux на Github'е, или обсуждение различных аспектов Redux'а в комментариях к тредам на Reddit'е или HN (HackerNews). С течением времени, я выработал свое собственное мнение о том, что представляет собой хороший, идиоматичный Redux код, и я хотел бы поделиться некоторыми из этих мыслей. Несмотря на мой статус мейнтейнера Redux'а, это всего лишь мнения, но я предпочитаю думать, что они являются достаточно хорошими подходами :)


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


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


В то же время, продолжаются жалобы на то, как Redux «заставляет» вас делать вещи определенными способами. Многие из этих жалоб на самом деле включают концепции связанные с тем, как Redux обычно используется, а не фактическими ограничениями наложенными самой библиотекой Redux. (Например, только в одном недавнем HN треде я видел жалобы: «слишком много шаблонного кода», «константы action'ов и action creator'ы не нужны», «я вынужден редактировать слишком много файлов чтобы добавить одну фичу», «почему я должен переключаться между файлами чтобы добраться до своей логики?», «термины и названия слишком сложны для изучения или запутанны», и слишком много других.)



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


Этот пост будет разделен на две части. В «Часть 1 — Реализация и Замысел» мы рассмотрим фактическую реализацию Redux, какие конкретные ограничения он накладывает, и почему эти ограничения существуют. Затем, мы рассмотрим первоначальный замысел и проектные цели для Redux, основываясь на обсуждениях и заявлениях авторов (особенно на ранней стадии процесса разработки).


В «Часть 2 — Практика и Философия» мы исследуем распространенные практики, широко используемые в приложениях Redux, и опишем почему эти практики существуют в первую очередь. Наконец, мы рассмотрим ряд «альтернативных» подходов к использованию Redux и обсудим, почему многие их них возможны, но не обязательно «идиоматичны».


Изложение основ


Изучение Трех принципов


Начнем со взгляда на теперь известные Три Принципа Redux'а


  • Единый источник истины: Состояние всего вашего приложения хранится в дереве объектов внутри единственного хранилища.
  • Состояние доступно только для чтения: Единственный способ изменить состояние — выпустить action — объект, описывающий что произошло.
  • Изменения выполняются чистыми функциями: Чтобы указать как дерево состояния трансформируется action'ами, вы пишите чистые reducer'ы.

В самом прямом смысле, каждое из этих заявлений — ложь! (или, заимствуя классическую реплику из «Возвращение джежая» — «они верны… с определенной точки зрения.»)


  • «Единый источник истины» — неверно, потому что (по FAQ Redux'а) вам не нужно помещатьвсе в Redux, состояние хранилища не обязано быть объектом, и вам даже не обязательно иметь единственный store.
  • «Состояние доступно только для чтения» — неверно, так как на самом деле ничто не мешает остальной части приложения модифицировать текущее дерево состояния.
  • И «Изменения выполняются чистыми функциями» неверно, потому что функции-reducer'ы могут также мутировать дерево состояния напрямую или запускать другие побочные эффекты.

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


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


«Язык» или «Мета-язык»


В своей речи на ReactConf 2017 «Приручение Мета-языка» Ченг Лу описывает, что только исходный код является «языком», а все остальное, наподобие комментариев, тестов, документации, туториалов, блог постов, и конференций, является «мета-языком». Другими словами, исходный код сам по себе может передать только определенную часть информации. Много дополнительных слоев передачи информации на уровне человека требуется, чтобы люди понимали «язык».


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


«Язык» (в этом случае основная библиотека Redux) имеет минимальную экспрессивность, и следовательно концепции, нормы и идеи, окружающие Redux, все находятся на уровне «мета-языка». (Фактически, пост Понимание «Приручения Мета-языка», который раскладывает по полочкам идеи из выступления Ченга Лу, называет Redux конкретным примером этих идей.) В конечном счете, это означает, что понимание того, почему определенные практики существуют вокруг Redux'а, и решения о том, что является и не является «идиоматичным» будут включать мнения и обсуждения, а не просто определение, основанное на исходном коде.


Как Redux работает на самом деле


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


Ядро Redux: createStore


Функция createStore центральная часть функциональности Redux'а. Если мы отсечем комментарии, проверку ошибок, и код для пары продвинутых возможностей, таких как store enhancers (усилители хранилища — функции, расширяющие возможности store — примечание переводчика) и observables, вот как выглядит createStore (пример кода позаимствован из «построй-мини-Redux» туториала под названием «Взламывая Redux»):


function createStore(reducer) {
    var state;
    var listeners = []

    function getState() {
        return state
    }
    
    function subscribe(listener) {
        listeners.push(listener)
        return unsubscribe() {
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }
    
    function dispatch(action) {
        state = reducer(state, action)
        listeners.forEach(listener => listener())
    }

    dispatch({})

    return { dispatch, subscribe, getState }
}

Это примерно 25 строк кода, но все же они включают ключевую функциональность. Код отслеживает текущее значение состояния и множество подписчиков, обновляет значение и уведомляет подписчиков когда action диспатчится, и предоставляет API для store.


Взгляните на все те вещи которые этот фрагмент не включает:


  • Иммутабельность
  • «Чистые функции»
  • Middleware (промежуточный программный слой между «отправкой» action и reducer'ами — примечание переводчика)
  • Нормализация
  • Селекторы
  • Thunks
  • Sagas (саги)
  • Должны ли типы action'ов быть строками или Символами, и должны ли обозначаться константами или быть инлайновыми
  • Должны ли вы использовать action creator'ы (функции, создающие action'ы — примечание переводчика) для констуирования этих action'ов
  • Должен ли store содержать несериализуемые элементы, такие как промисы или инстансы классов
  • Должны ли данные хранится нормализованными или иерархичными
  • Где должна жить асинхронная логика

В этом ключе стоит процитировать pull-request Дэна Абрамова для примера «классический счетчик»:


Новый пример «классический счетчик» направлен на то, чтобы развеять миф о том, что Redux требует Webpack, React, горячую перезагрузку, саги, action creator'ы, константы, Babel, npm, CSS модули, декораторы, отличное знание латыни, подписки на Egghead, научную степень, или степень С.О.В. Нет, это всего лишь HTML, некоторые кустарные скрипт-тэги и старые добрые манипуляции с DOM. Наслаждайтесь!

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


Изложив все те вещи до которых createStore нет дела, важно отметить, что на самом деле эта функция требует. Настоящая функция createStore навязывает два конкретных ограничения: action'ы, которые доходят до store, обязаны быть простыми объектами, и action'ы обязаны иметь поле «type» не равное undefined.


Оба этих ограничения происходят от оригинальной концепции «Flux архитектуры». Цитируя секцию Flux Action'ы и Диспатчер из документации Flux:


Когда новые данные попадают в систему, как через человека, взаимодействующего с приложением, так и через web api вызов, эти данные упаковываются в action — объект, содержащий новые поля данных и конкретный action тип. Мы зачастую создаем библиотеку вспомогательных методов называемых ActionCreators которые не только создают объект action, но и передают action диспатчеру. Различные действия идентифицируются аттрибутом «type». Когда все store получают action, они обычно используют этот аттрибут для определения, следует ли им реагировать на него и каким образом. В приложении Flux, stor'ы и view контролируют сами себя; на них не воздействуют внешние объекты. Action'ы поступают в stor'ы через функции обратного вызова которые они определяют и регистрируют, а не методами установки (сеттерами).

Изначально Redux не требовал специальное поле «type», но позже была добавлена проверка валидности, чтобы помочь отловить возможные опечатки или неправильный импорт констант action'ов, и для избежания бесполезных споров о базовой структуре объектов.


Встроенная утилита: combineReducers


Здесь мы начинаем наблюдать некоторые ограничения, знакомые большему количеству людей. combineReducers ожидает, что каждый reducer среза, переданный в него, будет «корректно» реагировать на неизвестный action, возвращая свое состояние по-умолчанию и никогда не вернет undefined. Она также ожидает, что значением текущего состояния является простой JS объект, и что имеется точное соответствие между ключами в объекте текущего состояния и в объекте функции-reducer'а. И наконец, combineReducers выполняет проверку на равенство по ссылке, для определения все ли срез-reducer'ы вернули свое предыдущее значение. Если все вернувшиеся значения выглядят как предыдущие значения, combineReducers полагает, что ничего нигде не изменилось и, в качестве оптимизации, возвращает исходный корневой объект состояния.


Изначальное преимущество: инструменты разработчика Redux (DevTools)


Инструменты разработчика Redux состоят из двух основных частей: enhancer'а (усилителя) для store который реализует перемещение во времени путем отслеживания диспатченных action'ов, и пользовательского интерфейса, позволяющего просматривать и управлять историей. Сам по себе store enhancer не заботится о содержимом action'ов или состояния, он просто хранит action'ы в памяти. Изначально интерфейс инструментов разработчика нужно было рендерить внутри дерева компонентов вашего приложения, и он также не заботился о содержимом action'ов или состояния. Тем не менее, расширение Redux DevTools работает в отдельном процессе (по крайней мере в Chrome), и, следовательно, требует сериализуемости всех action'ов и состояния, для того, чтобы все возможности перемещения во времени работали корректно и быстро. Возможность импорта и экспорта состояния и action'ов также требует чтобы они были сериализуемыми.


Другое полу-требование для отладки с помощью перемещения во времени — иммутабельность и чистые функции. Если функция-reducer мутирует состояние, тогда переход между acton'ами в отладчике приведет к неконсистентным значениям. Если у reducer'а есть побочные эффекты, тогда эти побочные эффекты будут проявляться каждый раз когда DevTools повторяет action. В обоих случаях, отладка путем перемещения во времени не будет работать полностью как ожидается.


Основные связки с UI: React-Redux и connect


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


В частности, каждый раз, когда action диспатчится и подписчики уведомляются, connect проверяет, изменился ли корневой объект состояния. Если нет, connect предполагает что ничего в состоянии не изменилось, и пропускает дальнейшую работу по рендерингу (Вот почему combineReducers пытается, по мере возможности, вернуть тот же самый корневой объект состояния). Если же корневой объект состояния изменился, connect вызовет предоставленную функцию mapStateToProps, и выполнит неглубокую проверку на равенство между текущим результатом и предыдущим, для выявления изменились ли props, рассчитанные от данных store. Опять же, если содержимое данных выглядит одинаково, connect не будет ререндерить обернутый компонент. Эти проверки на равенство в connect'е являются причиной того, почему случайные мутации состояния не приводят к ререндерингу компонентов, это из-за того, что connect предполагает, что данные не изменились и ререндеринг не нужен.


Сопутствующие библиотеки: React и Reselect


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


Также, хотя React'овский компонент может реализовать shouldComponentUpdate, используя любую логику какую захочет, самая распространенная реализация полагается на неглубокие проверки равенства текущих props и новых входящих props, например:


return !shallowEqual(this.props, nextProps)

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


Подводя итог техническим требованиям Redux


Центральная функция Redux'а createStore сама по себе накладывает только два ограничения на то, как вы должны писать свой код: action'ы должны быть простыми объектами, и должны содержать определенный type. Ее не заботит иммутабельность, сериализуемость, побочные эффекты или какое на самом деле принимает значение поле type.


С учетом вышесказанного, широко используемые части вокруг этого ядра, включая Redux DevTools, React-Redux, React и Reselect, действительно полагаются на правильное использование иммутабельности, сериализуемости action'ов/состояния и чистых функций-reducer'ов. Основная логика приложения может работать нормально если эти ожидания проигнорированы, но, с большой долей вероятности, отладка перемещением во времени и ререндеринг компонентов сломаются. Они также повлияют и на любые другие случаи использования, связанные с постоянством.


Важно отметить, что иммутабельность, сериализуемость и чистые функции никаким образом не навязываются Redux'ом. Функция-reducer вполне может мутировать свое состояние или выполнять AJAX-вызов. Любая другая часть приложения вполне может вызывать getState() и модифицировать содержимое дерева состояния напрямую. Полностью возможно помещать промисы, функции, Символы, инстансы класса или другие не сериализуемые значения в action'ы или дерево состояний. Вам не следует делать ничего из этого, но это возможно.


Замысел и дизайн Redux


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


Влияние на Redux и его цели


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


  • Основной целью Redux'а является предсказуемость мутаций состояния, путем наложения ограничений на то, как и когда изменения могут происходить. Redux заимствует идею «Архитектуры Flux» о разделении логики изменения от остальной части приложения, и использовании простых объектов «action'ов» для описания изменений, которые должны произойти.
  • Возможности для разработчиков, такие как отладка перемещением во времени, являются одними из ключевых вариантов использования Redux. Следовательно, такие ограничения как иммутабельность и сериализуемость существуют в основном для того, чтобы подобные возможности были доступны при разработке, а также для упрощения отслеживания потока данных и логики изменения.
  • Redux хочет чтобы реальная логика обновления состояния была синхронной, а асинхронное поведение было отделено от обновления состояния.
  • Архитектура Flux предлагает несколько индивидуальных «stores» (хранилищ) для различных типов данных. Redux объединяет несколько таких «stor'ов» в единое дерево состояния для облегчения работы с отладкой, сохранением состояния и возможностями наподобие отменить/повторить.
  • Единственная корневая функция-reducer может сама состоять из множества меньших функций-reducer'ов. Это позволяет в явном виде контролировать как обрабатываются данные, в том числе порядок зависимостей, когда обновление одного среза состояния требует предварительного вычисления другого среза, в отличие от механизмов наподобие Flux'овского эмиттера событий store.waitFor(), выстраивающего цепочки зависимостей.

Также стоит взглянуть на заявленные проектные цели в ранней версии README Redux'a.


Философия и проектные цели


  • Вам не нужна книга по функциональному программированию для того, чтобы использовать Redux.
  • Всё (Stores, Action Creator'ы, конфигурация) способно на «горячую» перезагрузку.
  • Сохраняет преимущества Flux'а, но добавляет другие полезные свойства благодаря своей функциональной природе.
  • Предотвращает некоторые анти-паттерны распространенные в коде Flux.
  • Отлично работает в изоморфных приложениях, потому что не использует синглтоны, и данные могут быть «увлажнены».
  • Не имеет значения как вы храните ваши данные: вы можете использовать JS объекты, массивы, ImmutableJS и т.д.
  • Под капотом, Redux держит все ваши данные в виде дерева, но вам не нужно об этом думать.
  • Позволяет эффективно подписываться на меньшие обновления, чем обновления индивидуальных Stor'ов.
  • Предоставляет зацепки для реализации мощных инструментов разработчика (например, переходы по времени, запись/проигрывание) без обязательной их установки
  • Предоставляет точки расширения, чтобы можно было легко поддержать промисы или генерировать константы вне ядра Redux.
  • Никаких оберточных вызовов в ваших stor'ах и action'ах. Ваши вещи — это ваши вещи.
  • Невероятно просто тестировать в изоляции без моков (mocks).
  • Можно использовать «плоские» Stor'ы, или композировать и переиспользовать Stor'ы также как композируются компоненты.
  • Поверхность API минимальна.
  • Я уже упоминал «горячую» перезагрузку?


Проектные принципы и подход


Прочитывая документацию Redux, ранние issue-треды, и многие другие комментарии Дэна Абрамова и Эндрю Кларка, можно заметить несколько конкретных тем касательно задуманного использования Redux.


Redux был создан как реализация архитектуры Flux


Redux изначально задумывался «всего лишь» как еще одна библиотека реализующая архитектуру Flux. В результате она уследовала многие коцепции от Flux: идею «отправки (dispatching) action'ов», то что action'ы — это простые объекты с полем type, использование «функций создания» action'ов (action creators), то, что «логика обновления» должна быть отделена от остальной части приложения и централизована, и многое другое.


Я часто вижу вопросы «Почему Redux делает ТАК», и на многие из подобных вопросов ответ: «Потому что Архитектура Flux'а и конкретные библиотеки Flux делали ТАК».


Поддерживаемость обновления состояния является главным приоритетом


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


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


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


История action'ов должна иметь семантическое значение


Хотя ядро Redux'а не заботит какие конкретные значения содержатся в поле type ваших action'ов, довольно очевидно, что типы action'ов должны нести некоторый смысл и информацию. Redux DevTools и другие утилиты логирования отображают поле type для каждого диспатченного action'а, так что важно иметь значения, понятные с беглого взгляда.


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


Redux задуман для введения в принцины функционального программирования


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


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


Redux поощряят тестируемый код


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


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


Функции-reducer'ы следует организовывать по срезу состояния


Redux берет концепт индивидуальных «хранилищ» из архитектуры Flux и объединяет их в единственный store. Самым прямолинейным соответствием между Flux и Redux является создание отдельного ключа верхнего уровня или «среза» в дереве состояний для каждого Flux хранилища. Если Flux приложение имеет раздельные UsersStore, PostsStore и CommentsStore, эквивалент в Redux будет иметь корневое дерево состояний, выглядящее так: { users, posts, comments }.


Можно ограничиться единственной функцией, содержащей всю логику по обновлению всех срезов состояния, но любое осмысленное приложение захочет разбить такую функцию на более мелкие функции для облегчения сопровождения. Самым очевидным способом это сделать является раздление логики в зависимости от того, какой срез состояния должен быть обновлен. Это значит, что каждый «reducer среза» должен заботиться только о своем срезе состояния, и, насколько ему известно, этот срез может быть всем состоянием. Этот паттерн «композиции reducer'ов» можно многократно повторяться для обработки обновлений иерархической структуры состояния. И утилита combineReducers включена в состав Redux'а специально для упрощения использования этого паттерна.


Если каждую функцию-reducer среза можно вызывать отдельно и предоставлять ей только собственный срез состояния в качестве параметра, то это означает, что с одним и тем же action'ом можно вызывать несколько reducer'ов среза, и каждый из них может обновлять свой срез состояния независимо от других. Основываясь на заявлениях Дэна и Эндрю, возможность одного action'а привести к обновлениям нескольких reducer'ов среза, является ключевой особенностью Redux'а. Про это часто говорят: «action'ы имеют отношение 1-ко-многим с reducer'ами.»


Логика обновления и поток данных выражены явно


Redux не содержит никакой «магии». Некоторые аспекты его реализации (такие как applyMiddleware и store enhancers) чуть сложнее понять сразу, если вы не знакомы с более продвинутыми принципами ФП, но в остальном все должно быть явным, ясным и отслеживаемым с минимальным количеством абстракций.


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


В оригинальном диспатчере Flux'а, Stor'ам нужно было событие waitFor(), которое могло быть использовано для задания цепочек зависимостей. Если CommentsStore нуждался в данных от PostsStore для того, чтобы правильно обновить себя, он мог вызвать PostsStore.waitFor(), чтобы гарантировать, что он выполнится после того как PostsStore обновится. К сожалению, такую цепочку зависимостей было нелегко визуализировать. Тем не менее, с Redux такая последовательность операций может быть достигнута явным вызовом конкретных функций-reducer'ов в нужной последовательности.


Вот в качестве примера некоторые (немного модифицированные) цитаты и сниппеты из Дэновского гиста «Combining Stateless Stores»


В этом случае, commentsReducer не зависит полностью от состояния и action'а. Он также зависит и от hasCommentReallyBeenAdded (былЛиКомментарийДействительноДобавлен). Мы добавляем этот параметр к его API. Да, его теперь нельзя использовать «как есть», но в этом весь смысл: reducer теперь имеет явную зависимость от других данных. Он не store верхнего уровня. То, что управляет им, обязано каким-то образом предоставить ему эти данные.

export default function commentsReducer(state = initialState, action, hasPostReallyBeenAdded) {}

// в другом месте
export default function rootReducer(state = initialState, action) {
  const postState = postsReducer(state.post, action);
  const {hasPostReallyBeenAdded} = postState;
  const commentState  = commentsReducer(state.comments, action, hasPostReallyBeenAdded);
  return { post : postState, comments : commentState };
}

Это также применимо к идее «reducer'ов высшего порядка». Отдельный reducer среза может быть обернут другими reducer'ами, чтобы получить такие способности как отмена/повтор или пагинация.


API Redux'а должно быть минимальным


Эта цель повторялась снова и снова Дэном и Эндрю на протяжении разработки Redux'а. Проще всего процитировать некоторые из их комментариев:


Andrew — #195:


Зачастую лучший API — это отсутствие API. Текущие предложения для middleware и stor'ов высшего порядка обладают огромным преимуществом в том, что они не требуют особого отношения со стороны ядра Redux — они просто обертки вокруг dispatch() и createStore() соответственно. Вы даже можете использовать их сегодня, до релиза 1.0. Это огромная победа для расширяемости и быстрых инноваций. Мы должны поддерживать паттерны и соглашения вместо жестких, привилегированных API.

Dan — #216:


Вот почему я решил написать Redux вместо использования NuclearJS:
  • Я не хочу жесткую зависимость от ImmutableJS
  • Я хочу настолько малый API насколько возможно
  • Я хочу сделать так, чтобы можно было легко спрыгнуть с Redux когда появиться что-то получше

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


Я изо всех сил старался избегать API наподобие createStore, потому что они привязывают вас к конкретной реализации. Вместо этого, для каждой сущности (Reducer, Action Creator) я попытался найти минимальный способ взаимодействия с ней без какой-либо зависимости от Redux. Единственный код, импортирующий Redux и действительно жестко зависимый от него, будет в вашем корневом компоненте и компонентах, подписанных на него.



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


Это связано с целью «минимального API». Некоторые библиотеки Flux, такие как Flummox Эндрю, имели некую форму асинхронного поведения, встроенную непосредственно в библиотеку (например, START/SUCCESS/FAILURE action'ы для промисов). И хотя наличие чего-то встроенного в ядро означало, что оно всегда доступно, это также ограничивало гибкость.


Снова проще всего процитировать комментарии из обсуждений и Hashnode AMA (вопросы/ответы) с Дэном и Эндрю:


Andrew — #55:


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

Andrew — #215:


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

Как однажды сказал Дэн (не помню где… возможно в Slack) мы нацелены быть Koа для Flux библиотек. В конечном счете, когда сообщество повзрослеет, мы планируем поддерживать коллекцию «благословленных» плагинов и расширений, возможно в рамках reduxjs организации на Github'е.


Dan — Hashnode AMA:


Мы не хотели прописывать что-то подобное в самом Redux'е, потому что знаем, что многие люди не готовы изучать Rx операторы для выполнения базовых асинхронных операций. Они полезны, когда у вас сложная асинхронная логика, но мы не хотели вынуждать каждого пользователя Redux изучать Rx, так что мы намеренно поддержали гибкость middlewar'ов.

Andrew — Hashnode AMA:


Причина, по которой middleware API вообще существует, в том, что мы совершенно не хотели останавливаться на конкретном решении для асинхронности. Моя предыдущая библиотека Flux — Flummox — имела, по сути, встроенный промис-middleware. Для кого-то это было удобно, но из-за того, что она была встроенной, вы не могли изменить ее поведение или отказаться от нее. С Redux мы знали, что сообщество придумает множество лучших асинхронных решений, чем то, что мы бы могли сделать самостоятельно. Redux Thunk рекламируется в документации потому, что это абсолютно минимальное решение. Мы были уверены, что сообщество придумает нечто другое и/или лучшее. Мы были правы!

Заключительные мысли


Я потратил много времени на исследование для этих двух постов. Было невероятно интересно прочитать ранние дискуссии и комментарии, и увидеть как Redux эволюционировал в то, что мы знаем теперь. Как видно из процитированного ранее README, видение Redux'а было ясным с самого начала, и было несколько конкретных идей и концептуальных скачков, которые привели к окончательному API и реализации. Надеюсь, этот взгляд на внутренности и историю Redux поможет пролить свет на то, как Redux работает на самом деле, и почему он был построен таким образом.


Обязательно ознакомьтесь с Дао Redux'а, Часть 2 — Практика и Философия, где мы взглянем на то, почему существует множество паттернов использования Redux, и я поделюсь своими мыслями о плюсах и минусах многих «вариаций» того, как можно использовать Redux.




Источник: blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-1


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

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

https://habrahabr.ru/post/330692/


Метки:  

Об оптимизации комбинаторных алгоритмов

Воскресенье, 11 Июня 2017 г. 21:24 + в цитатник
Не знаю, стоило ли делать отдельную заметку по оптимизации уже опубликованных алгоритмов или нужно было просто добавить в старую статью revised code. Я решил, что все же новенькое будет интереснее. Сразу должен сказать, что данная заметка предназначена не для профессиональных программистов, а скорее, для «студентов» гуманитариев, интересующихся программированием. Речь с одной стороны пойдет о приеме оптимизации кода на языке С для генерации всех перестановок, с другой стороны о видимых скоростных улучшениях, которые удалось получить по сравнению с кодом на С из моей покрывшейся пылью статьи. Основная задача: объяснить некоторые приемы сокращения кода неспециалистам, которым приходится сталкиваться с алгоритмизацией.

О первом
В алгоритме генерации всех перестановок после обмена значений в массиве необходимо обернуть часть этого массива — хвост. В первой реализации для этого используются 4 довольно затратных приема, которые сводятся к: а) разбиению массива на два — 2 операции, б) переворачиванию одного из полученных массивов. с) склеиванию массивов в один.
Если выразить это, например, на языке PHP, то получится следующая конструкция:
$a=array_merge(array_reverse(array_slice($a, 0, $i)),array_slice($a, $i));

Если читатель познакомился со статьей по ссылке, то наверное заметил, что эта строчка кода фактически является полным аналогом тех операций, которые используются в коде на языке С.
Однако там операции разнесены в функции, что сильно запутывает.
Это выражение на PHP также довольно трудно читается (это не относится к программистам на языке haskell), но у нее есть один важный плюс — это однозначность в понимании необходимых действий для оптимизации. После непродолжительного медитативного созерцания этой строки она начинает осмысляться как одна операция, для которой можно попытаться найти более простой аналог, а главное более быстрый. Для PHP у меня получилось следующее:
	
         $c=$a;
	 $z=0;
	for ($i-=1; $i > -1; $i--) $a[$z++]=$c[$i];	

Пришлось ввести в алгоритм еще одну копию массива и относительно простой цикл для переопределения части массива, также добавилась одна переменная z.
Прокомментирую этот участок: 1) после обмена элементов массив С равен А; 2) цикл for начинает работать от индекса, на котором осуществлен обмен (i) к нулевому индексу, i уменьшается. Переменная z наоборот увеличивается и части массива А присваиваются элементы из массива С, но в обратном порядке. Таким образом получаем нужный результат — массив А с перевернутой частью. В реализации из переменной i вычитается 1, чтобы не выйти за пределы массива.

Фактически мы получили метод оптимизации из трех шагов, который заключается: 1) в кодировании полного алгоритма, т.е. как он мыслится и выводится на бумаге, со всеми избыточными операциями; 2) в поиске и сведении некоторых разрозненных операций в одну строку 3) к поиску более простых аналогов для операций в этой строке, если это возможно.

Код на Си получилось очень компактным:

Смотреть
#include 
#include 
int main() {
        char b[] = "1234";
        char a[] = "4321";
        char d[8];
        int fact = 24; 
             int i;
             int j;
             char c;
             int z;
             int y=0;
          while (y != fact) {
          printf("%s\n", a);
          i=1;
          while(a[i] > a[i-1]) {
          i++;
          }
          j=0;
          while(a[j] < a[i]) {
          j++;    
          }
      c=a[j];
      a[j]=a[i];
      a[i]=c;
    strcpy(d, a);
	z=0;
	for (i-=1; i > -1; i--) a[z++]=d[i];	
y++;  
   }
}


Получилось 4 цикла и условие выхода — когда переменная достигнет факториала числа всех перестановок для n. Я нарочно выбросил сравнение массивов, чтобы немного ускорить работу.

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

Рекурсивный алгоритм выдал время работы для n=11:
real 2m9.213s
user 0m2.920s
sys 0m26.290s
Алгоритм из первой статьи выдал для n=11:
real 2m15.510s
user 0m19.750s
sys 0m34.300s

Текущая версия для n=11
real 1m46.459s
user 0m3.130s
sys 0m24.000s
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330690/


Метки:  

[Из песочницы] Туториал: Создание простейшей 2D игры на андроид

Воскресенье, 11 Июня 2017 г. 19:46 + в цитатник
Этот туториал предназначен в первую очередь для новичков в разработке под андроид, но может быть будет полезен и более опытным разработчикам. Тут рассказано как создать простейшую 2D игру на анроиде без использования каких-либо игровых движков. Для этого я использовал Android Studio, но можно использовать любую другую соответствующее настроенную среду разработки.

Шаг 1. Придумываем идею игры
Для примера возьмём довольно простую идею:

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



Шаг 2. Создаём проект
В Android Studio в верхнем меню выбираем File -> New -> New Project.



Тут вводим название приложения, домен и путь. Нажимаем Next.



Тут можно ввести версию андроид. Также можно выбрать андроид часы и телевизор. Но я не уверен что наше приложение на всём этом будет работать. Так что лучше введите всё как на скриншоте. Нажимаем Next.



Тут обязательно выбираем Empty Activity. И жмём Next.



Тут оставляем всё как есть и жмём Finish. Итак проект создан. Переходим ко третьему шагу.

Шаг 3. Добавляем картинки

Скачиваем архив с картинками и распаковываем его.

Находим папку drawable и копируем туда картинки.



Позже они нам понадобятся.

Шаг 4. Создаём layout

Находим activity_main.xml, открываем вкладку Text и вставляем туда это:



    
    
        
        
    

На вкладке Design видно как наш layout будет выглядеть.



Сверху поле в котором будет сама игра, а снизу кнопки управления Left и Right. Про layout можно написать отдельную статью, и не одну. Я не буду на этом подробно останавливаться. Про это можно почитать тут.

Шаг 5. Редактируем MainActivity класс

В первую очередь в определение класса добавляем implements View.OnTouchListener. Определение класса теперь будет таким:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {

Добавим в класс нужные нам статические переменные (переменные класса):

public static boolean isLeftPressed = false; // нажата левая кнопка
public static boolean isRightPressed = false; // нажата правая кнопка

В процедуру protected void onCreate(Bundle savedInstanceState) {
добавляем строки:

GameView gameView = new GameView(this); // создаём gameView

LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout
gameLayout.addView(gameView); // и добавляем в него gameView

Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки
Button rightButton = (Button) findViewById(R.id.rightButton);

leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)
rightButton.setOnTouchListener(this);

Классы LinearLayout, Button и т.д. подсвечены красным потому что ещё не добавлены в Import.
Чтобы добавить в Import и убрать красную подсветку нужно для каждого нажать Alt+Enter.
GameView будет подсвечено красным потому-что этого класса ещё нет. Мы создадим его позже.

Теперь добавляем процедуру:

public boolean onTouch(View button, MotionEvent motion) {
    switch(button.getId()) { // определяем какая кнопка
        case R.id.leftButton:
            switch (motion.getAction()) { // определяем нажата или отпущена
                case MotionEvent.ACTION_DOWN:
                    isLeftPressed = true;
                    break;
                case MotionEvent.ACTION_UP:
                    isLeftPressed = false;
                    break;
            }
            break;
        case R.id.rightButton:
            switch (motion.getAction()) { // определяем нажата или отпущена
                case MotionEvent.ACTION_DOWN:
                    isRightPressed = true;
                    break;
                case MotionEvent.ACTION_UP:
                    isRightPressed = false;
                    break;
            }
            break;
    }
    return true;
}

Если кто-то запутался - вот так в результате должен выглядеть MainActivity класс:

package com.spaceavoider.spaceavoider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
    public static boolean isLeftPressed = false; // нажата левая кнопка
    public static boolean isRightPressed = false; // нажата правая кнопка
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        GameView gameView = new GameView(this); // создаём gameView
        LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout
        gameLayout.addView(gameView); // и добавляем в него gameView
        Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки
        Button rightButton = (Button) findViewById(R.id.rightButton);
        leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)
        rightButton.setOnTouchListener(this);
    }
    public boolean onTouch(View button, MotionEvent motion) {
        switch(button.getId()) { // определяем какая кнопка
            case R.id.leftButton:
                switch (motion.getAction()) { // определяем нажата или отпущена
                    case MotionEvent.ACTION_DOWN:
                        isLeftPressed = true;
                        break;
                    case MotionEvent.ACTION_UP:
                        isLeftPressed = false;
                        break;
                }
                break;
            case R.id.rightButton:
                switch (motion.getAction()) { // определяем нажата или отпущена
                    case MotionEvent.ACTION_DOWN:
                        isRightPressed = true;
                        break;
                    case MotionEvent.ACTION_UP:
                        isRightPressed = false;
                        break;
                }
                break;
        }
        return true;
    }
}

Итак, класс MainActivity готов! В нём инициирован ещё не созданный класс GameView. И когда нажата левая кнопка — статическая переменная isLeftPressed = true, а когда правая — isRightPressed = true. Это в общем то и всё что он делает.

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

Шаг 6. Создаём класс GameView

Теперь наконец-то создадим тот самый недостающий класс GameView. Итак приступим. В определение класса добавим extends SurfaceView implements Runnable. Мобильные устройства имею разные разрешения экрана. Это может быть старенький маленький телефон с разрешением 480x800, или большой планшет 1800x2560. Для того чтобы игра выглядела на всех устройствах одинаково я поделил экран на 20 частей по горизонтали и 28 по вертикали. Полученную единицу измерения я назвал юнит. Можно выбрать и другие числа. Главное чтобы отношение между ними примерно сохранялось, иначе изображение будет вытянутым или сжатым.

public static int maxX = 20; // размер по горизонтали
public static int maxY = 28; // размер по вертикали
public static float unitW = 0; // пикселей в юните по горизонтали
public static float unitH = 0; // пикселей в юните по вертикали

unitW и unitW мы вычислим позже. Также нам понадобятся и другие переменные:

private boolean firstTime = true;
private boolean gameRunning = true;
private Ship ship;
private Thread gameThread = null;
private Paint paint;
private Canvas canvas;
private SurfaceHolder surfaceHolder;

Конструктор будет таким:

public GameView(Context context) {
    super(context);
    //инициализируем обьекты для рисования
    surfaceHolder = getHolder();
    paint = new Paint();

    // инициализируем поток
    gameThread = new Thread(this);
    gameThread.start();
}

Метод run() будет содержать бесконечный цикл. В начале цикла выполняется метод update()
который будет вычислять новые координаты корабля. Потом метод draw() рисует корабль на экране. И в конце метод control() сделает паузу на 17 миллисекунд. Через 17 миллисекунд run() запустится снова. И так до пока переменная gameRunning == true. Вот эти методы:

@Override
public void run() {
    while (gameRunning) {
        update();
        draw();
        control();
    }
}

private void update() {
    if(!firstTime) {
        ship.update();
    }
}

private void draw() {
    if (surfaceHolder.getSurface().isValid()) {  //проверяем валидный ли surface

        if(firstTime){ // инициализация при первом запуске
            firstTime = false;
            unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните
            unitH = surfaceHolder.getSurfaceFrame().height()/maxY;

            ship = new Ship(getContext()); // добавляем корабль
        }

        canvas = surfaceHolder.lockCanvas(); // закрываем canvas
        canvas.drawColor(Color.BLACK); // заполняем фон чёрным

        ship.drow(paint, canvas); // рисуем корабль

        surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas
    }
}

private void control() { // пауза на 17 миллисекунд
    try {
        gameThread.sleep(17);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

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

Шаг 7. Создаём класс SpaceBody

Он будет родительским для класса Ship (космический корабль) и Asteroid (астероид). В нём будут содержаться все переменные и методы общие для этих двух классов. Добавляем переменные:

protected float x; // координаты
protected float y;
protected float size; // размер
protected float speed; // скорость
protected int bitmapId; // id картинки
protected Bitmap bitmap; // картинка

и методы

void init(Context context) { // сжимаем картинку до нужных размеров
    Bitmap cBitmap = BitmapFactory.decodeResource(context.getResources(), bitmapId);
    bitmap = Bitmap.createScaledBitmap(
            cBitmap, (int)(size * GameView.unitW), (int)(size * GameView.unitH), false);
    cBitmap.recycle();
}

void update(){ // тут будут вычисляться новые координаты
}

void drow(Paint paint, Canvas canvas){ // рисуем картинку
    canvas.drawBitmap(bitmap, x*GameView.unitW, y*GameView.unitH, paint);
}

Шаг 8. Создаём класс Ship

Теперь создадим класс Ship (космический корабль). Он наследует класс SpaceBody поэтому в определение класа добавим extends SpaceBody.

Напишем конструктор:

public Ship(Context context) {
    bitmapId = R.drawable.ship; // определяем начальные параметры
    size = 5;
    x=7;
    y=GameView.maxY - size - 1;
    speed = (float) 0.2;

    init(context); // инициализируем корабль
}

и переопределим метод update()

@Override
public void update() { // перемещаем корабль в зависимости от нажатой кнопки
    if(MainActivity.isLeftPressed && x >= 0){
        x -= speed;
    }
    if(MainActivity.isRightPressed && x <= GameView.maxX - 5){
        x += speed;
    }
}

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

Шаг 9. Создаём класс Asteroid

Добавим класс Asteroid (астероид). Он тоже наследует класс SpaceBody поэтому в определение класса добавим extends SpaceBody.

Добавим нужные нам переменные:

private int radius = 2; // радиус
private float minSpeed = (float) 0.1; // минимальная скорость
private float maxSpeed = (float) 0.5; // максимальная скорость

Астероид должен появляться в случайной точке вверху экрана и лететь вниз с случайной скоростью. Для этого x и speed задаются при помощи генератора случайных чисел в его конструкторе.

public Asteroid(Context context) {
    Random random = new Random();

    bitmapId = R.drawable.asteroid;
    y=0;
    x = random.nextInt(GameView.maxX) - radius;
    size = radius*2;
    speed = minSpeed + (maxSpeed - minSpeed) * random.nextFloat();

    init(context);
}

Астероид должен двигаться с определённой скорость вертикально вниз. Поэтому в методе update() прибавляем к координате x скорость.

@Override
public void update() {
    y += speed;
}

Так же нам нужен будет метод определяющий столкнулся ли астероид с кораблём.

public boolean isCollision(float shipX, float shipY, float shipSize) {
    return !(((x+size) < shipX)||(x > (shipX+shipSize))||((y+size) < shipY)||(y > (shipY+shipSize)));
}

Рассмотрим его поподробнее. Для простоты считаем корабль и астероид квадратами. Тут я пошёл от противного. То есть определяю когда квадраты НЕ пересекаются.

((x+size) < shipX) — корабль слева от астероида.
(x > (shipX+shipSize)) — корабль справа от астероида.
((y+size) < shipY) — корабль сверху астероида.
(y > (shipY+shipSize)) — корабль снизу астероида.

Между этими четырьмя выражениями стоит || (или). То есть если хоть одно выражение правдиво (а это значит что квадраты НЕ пересекаются) — результирующие тоже правдиво.

Всё это выражение я инвертирую знаком!. В результате метод возвращает true когда квадраты пересекаются. Что нам и надо.

Про определение пересечения более сложных фигур можно почитать тут.

Шаг 10. Добавляем астероиды в GameView

В GameView добавляем переменные:

private ArrayList asteroids = new ArrayList<>(); // тут будут харанится астероиды
private final int ASTEROID_INTERVAL = 50; // время через которое появляются астероиды (в итерациях)
private int currentTime = 0;

также добавляем 2 метода:

private void checkCollision(){ // перебираем все астероиды и проверяем не касается ли один из них корабля
    for (Asteroid asteroid : asteroids) {
        if(asteroid.isCollision(ship.x, ship.y, ship.size)){
            // игрок проиграл
            gameRunning = false; // останавливаем игру
            // TODO добавить анимацию взрыва
        }
    }
}

private void checkIfNewAsteroid(){ // каждые 50 итераций добавляем новый астероид
    if(currentTime >= ASTEROID_INTERVAL){
        Asteroid asteroid = new Asteroid(getContext());
        asteroids.add(asteroid);
        currentTime = 0;
    }else{
        currentTime ++;
    }
}

И в методе run() добавляем вызовы этих методов перед вызовоом control().

@Override
public void run() {
    while (gameRunning) {
        update();
        draw();
        checkCollision();
        checkIfNewAsteroid();
        control();
    }
}

Далее в методе update() добавляем цикл который перебирает все астероиды и вызывает у них метод update().

private void update() {
    if(!firstTime) {
        ship.update();
        for (Asteroid asteroid : asteroids) {
            asteroid.update();
        }
    }
}

Такой же цикл добавляем и в метод draw().

private void draw() {
    if (surfaceHolder.getSurface().isValid()) {  //проверяем валидный ли surface

        if(firstTime){ // инициализация при первом запуске
            firstTime = false;
            unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните
            unitH = surfaceHolder.getSurfaceFrame().height()/maxY;

            ship = new Ship(getContext()); // добавляем корабль
        }

        canvas = surfaceHolder.lockCanvas(); // закрываем canvas
        canvas.drawColor(Color.BLACK); // заполняем фон чёрным

        ship.drow(paint, canvas); // рисуем корабль

        for(Asteroid asteroid: asteroids){ // рисуем астероиды
            asteroid.drow(paint, canvas);
        }

        surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas
    }
}

Вот и всё! Простейшая 2D игра готова. Компилируем, запускаем и смотрим что получилось!
Если кто-то запутался или что-то не работает можно скачать исходник.

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

На этом всё. Пишите отзывы, вопросы, интересующие вас темы для продолжения.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330686/


Метки:  

Создаём динамическую обложку ВКонтакте

Воскресенье, 11 Июня 2017 г. 18:33 + в цитатник
В последнее время всё большую популярность обретают различные интерактивные способы завлечь аудиторию и привлечь к себе больше внимания. Тут и боты для социальных сетей и мессенджеров, и другие решения, придающие «уникальности».
Среди них можно выделить и динамические обложки для сообществ, официально поддерживать которые ВКонтакте начали в марте.
Почему я решил написать эту небольшую статью? Хотя ажиотаж вокруг этой темы и спал, всё равно она остаётся довольно популярной, находятся как «клиенты», готовые платить очень большие деньги, так и желающие научиться это делать самому.
Я работал с одной «студией», которая берет шестизначные суммы за эту работу, при этом кидая своих разработчиков и мелких клиентов.
Так вот, чтобы в этой сфере не было монополии, и все увидели, насколько легко это делается, я и решил написать статью.


На примере моего пустого сообщества-песочницы


Вступление


Я опишу, как сделать динамическую обложку, отображающую текущее время и последнего подписчика. Любой другой функционал делается не сложнее — чуть больше строк кода и доступ к API других сервисов.

Что нам понадобится? Всего лишь подготовленный фон для обложки и access_token для группы с правами доступа к фотографиям. Привожу пример на Java, но сделать это можно и любым другим способом.

Статья рассчитана на более-менее опытных читателей — расписывать, как отправить GET запрос или авторизоваться в ВК через API я не стану.

Приступаем


Для начала реализуем транслирование новых подписчиков на обложку.
Логика примерно следующая: получаем запрос от Callback API, берём оттуда id пользователя, по нему берём имя и аватар, накладываем на имеющийся фон и загружаем в ВК. Проще некуда.
Поехали.

Обрабатываем запрос от Callback API


Выглядеть этот запрос будет примерно так:
{
  "type": "group_join",
  "object": {
    "user_id": XXXXXXXX,
    "join_type": "join"
  },
  "group_id": XXXXXXXX
}

Парсить JSON, я думаю, все умеют. Отсюда нам нужен только id пользователя, и по нему мы уже получаем имя, фамилию и ссылку на фотографию.

Достаточно сделать такой GET запрос:
https://api.vk.com/method/users.get?user_ids=XXXXXXXX&fields=photo_max_orig&v=5.65

В ответ мы получим:
{
  "response": [
    {
      "id": XXXXXXXX,
      "first_name": "имя",
      "last_name": "фамилия",
      "photo_max_orig": "https:\/\/pp.userapi.com\/\/..."
    }
  ]
}

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

Обрабатываем изображение


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

// Фоновое изображение и аватар пользователя
BufferedImage background_image = ImageIO.read(new File("/some/folder/bg.png")),
              user_avatar = ImageIO.read(new URL("https://..."));

// Результат - отдельное изображение
BufferedImage result = new BufferedImage(background_image.getWidth(), background_image.getHeight(), BufferedImage.TYPE_INT_ARGB);

// В качестве "холста" берём наше новое изображение
Graphics2D g = (Graphics2D) result.getGraphics();

// Рисуем фон
g.drawImage(background_image, 0, 0, null);

// Рисуем аватар подписчика
g.drawImage(user_avatar, x_avatar, y_avatar, width, height, null);

// Подписываем имя подписчика
g.drawString(user_name, x_name, y_name);

// Записываем результат на диск
ImageIO.write(result, "PNG", new File("some/folder/result.png"));

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

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

Загружаем обложку


Всё делается очень просто и подробно описано в документации к API.

Получаем сервер для загрузки обложки, отправив GET-запрос:
https://api.vk.com/method/photos.getOwnerCoverPhotoUploadServer?group_id=XXXXXXXX&crop_x=0&crop_y=0&crop_x2=1590&crop_y2=400&access_token=ACCESS_TOKEN&v=5.64

Где ACCESS_TOKEN — токен с правами доступа к фотографиям группы.

Из ответа берём upload_url:
{
    "response": {
        "upload_url": "https://..."
    }
}

Теперь отправляем на наш upload_url отправляем POST-запрос с полем photo в формате multipart/form-data, точно также, как и с любыми документами. Я уже освещал этот вопрос в другой статье.

В ответ мы получим следующее:
{
  "hash": "...",
  "photo": "..."
}

Всё, осталось сделать один GET-запрос и обложка засияет в сообществе:
https://api.vk.com/method/photos.saveOwnerCoverPhoto?hash=HASH&photo=HASH&access_token=ACCESS_TOKEN&v=5.65

Где HASH и PHOTO получены из предыдущего пункта, а токен всё тот же.

Готово, динамическая обложка заработала.

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

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

Например, вот так можно транслировать текущее время на обложку:
while (true) {
    // Спим минуту
    Thread.sleep(1000*60);

    // Берём текущее время в формате "20:25"
    String current_tine = new SimpleDateFormat("H:m").format(new Date());

    // Рисуем!
    BufferedImage result = ImageIO.read(new File("some/folder/result.png"));
    Graphics2D g = (Graphics2D) result.getGraphics();

    g.drawString(current_tine, x_time, y_time);

    // Можно перезаписать изображение, или сразу его загружать
    // Или делать что душе угодно 
    ImageIO.write(result, "PNG", new File("some/folder/result.png");
}


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

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

https://habrahabr.ru/post/330682/


Метки:  

Применение национальных языков в программировании на SPL

Воскресенье, 11 Июня 2017 г. 17:23 + в цитатник

Метки:  

Оценка параметров старения с помощью носимой электроники. Лекция в Яндексе

Воскресенье, 11 Июня 2017 г. 15:36 + в цитатник
Носимые устройства сейчас в моде, но используются в основном для фитнеса и спорта. Как найти им другое применение? Что они могут рассказать о нашем здоровье и продолжительности жизни? А главное — как оценивать поступающие с них данные? Руководитель направления mHealth R&D в компании Gero Тимофей Пырков прочитал отличную лекцию, посвящённую локомоторной активности человека.




Под катом — расшифровка и большинство слайдов.



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

Наша компания Gero — небольшая. занимаемся разработкой лекарственных препаратов, используя компьютерные программы, а также поиском биомаркеров о состоянии здоровья. И в том числе пытаемся понять, как здоровье человека меняется в зависимости от наличия или отсутствия заболеваний и от старения организма в целом. Публикуем работы в научных журналах, а источник наших данных — это обычно открытые датасеты, содержащие в себе информацию от многих пользователей о геноме в виде транскриптомов, протемов, анализов крови. В том числе нас интересует двигательная активность. Оказалось, что довольно много такой информации известно. Собирается она с помощью акселерометров. Например, с браслетов, которые можно носить на запястье. И есть открытый датасет NHANES, где достаточно много такой информации для десятков тысяч пользователей. Еще сотрудничаем с UK Biobank, в котором такой информации намного больше, уже для сотни тысяч пользователей, но она не находится в открытом доступе.



Что такое двигательная активность? Собирается она очень простым способом. Пользователь носит какой-то девайс, в котором есть акселерометр, он записывает ускорение с какой-то частотой, обычно в несколько герц — 50 или 100. Это может быть просто шагомер. Шагомер обычно пользователю выдает информацию об общей активности — например, сколько шагов в день ты сделал. Но в том числе шагомер записывает ее с большей частотой именно поминутно. То есть выглядит такой трек записи примерно как изображено на середине слайда. Это сэмпл за два дня с одного пользователя. Видно, что есть доминирующий эффект от циркадного ритма. То есть ночью никакой активности нет — пользователь спит. Днем он как-то ходит, когда-то больше, когда-то меньше. Иногда может даже пойти на пробежку.

Что с такой информацией можно делать? Обычно, когда человек покупает фитнес-трекер, например, или ставит себе приложение на телефон для мониторинга фитнеса, оно выдает ему очень простую информацию в виде общего среднего пульса или числа шагов за день. Можно использовать такую информацию. Но кажется, если посмотреть на нее более детально, узнаешь больше о состоянии здоровья или о том, что происходит с организмом. И вообще, data выглядит примерно таким образом. Дальше надо понять, где тут science. Science, наверное, в том, чтобы сравнивать одних пользователей с другими. Пытаться понять, кто старше, кто моложе, кто здоровее, у кого какие-то проблемы со здоровьем. Может быть, надо уже идти к врачу. Вопрос, как сравнить трек, который изображен на слайде. Напрямую не сравнишь среди пользователей, потому что туда замешана большая составляющая индивидуального социального поведения. Кто-то на работу ходит раньше, кто-то позже, у каждого свое расписание. На выходных кто-то предпочитает отлежаться, поспать, кто-то — наоборот, поработать, кто-то — на фитнес пойти. Понятно, что эта информация — не совсем о физиологии, не совсем о состоянии здоровья, а больше о социальном поведении и социальном статусе. Поэтому, естественно, возникает желание получить какой-то дескриптор такой даты, провести feature engineering, который отбросит ненужные для нашего анализа социальные паттерны поведения и оставит существенную информацию о физиологии.

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

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

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



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

Про возрастные изменения в двигательной активности можно посмотреть в литературе. Там известны довольно простые вещи, известны давно. Это, например, падение полной активности, это видно по уровню. В том числе можно посмотреть на спектр такого временного ряда. Здесь приведены средние спектры для условно молодой и старой группы — 35 и 45 лет соответственно. То есть это люди в расцвете сил. И — уже пожилые, 70-80, синие и зеленые. Видно, что в целом спектральная мощность понижается при старении человека.

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



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

Дальше, получается, мы дату собрали, предобработали, получили какие-то дескрипторы, и хотим теперь что-то узнать о состоянии здоровья. Для этого существует, грубо говоря, две группы методов: supervised и unsupervised. Supervised — обучение моделей с контролем. То есть у вас обычно есть какая-то метка — например, возраст, диагнозы — и вы пытаетесь построить модель именно под нее. А unsupervised-методы позволяют что-то узнать о структуре данных. В нашем сигнале хорошо то, что это просто акселерометр — достаточно дешевый способ собирать данные. Такие данные можно собирать даже с помощью смартфонов, и они есть для большого количества людей. В итоге получаем датасет, где количество семплов достаточно большое. Здесь становится возможным применять unsupervised-методы, то есть не заставлять модель подстраиваться под какой-то диагноз, а просто посмотреть, как устроены данные.

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

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



И в этой связи возникает вопрос: как оценить его точность? Мы получили модельку, описыающую старение. Может как-то дать поправку на то, что больной человек — более старый, или наоборот, занимается фитнесом, ведет здоровый образ жизни и кажется моложе своих лет? Вопрос, какая точность у каждой модели? Смотрим на оценку точности ранжирования времен дожития: такие данные собираются параллельно с медицинскими опросниками, их часто сопровождает follow up, где за каждым пациентом проводится наблюдение в течение нескольких ближайших лет, обычно 5-10. Часто в таких датасетах есть информация о том, что пациент жив, с ним все хорошо. Или, наоборот — прошло столько-то лет, и он скончался от болезни или от старости.

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

Видим, что первый столбик — настоящий возраст пациента, хронологический возраст. Он представляет собой довольно высокую метрику, его значение достаточно высокое по сравнению со всеми остальными методами. Следующие два столбика — разные модели для возраста. Один из них — supervised-метод: с помощью регрессии мы пытались построить возраст. Другой — проекция на главную компоненту, которую мы нашли unsupervised-способом. Эту проекция мы обозвали просто биовозрастом. И видим довольно забавную картину. Действительно, в плане ранжирования по временам дожития эти две модели биовозраста примерно так же, если угодно, хороши или плохи, как если просто посмотреть на паспортный возраст пациента и сделать очевидный вывод — чем ты моложе, тем больше вероятность того, что ты проживешь еще дольше, и наоборот. Но следующие три столбика справа — разные модели, такие специальные, не для биовозраста, а модели предсказания рисков смертности. По-английски hazards model — модель рисков, угроз, опасности, но на самом деле речь идет именно о предсказании рисков смертности, или вероятности того, что каждый пациент умрет в течение какого-то ближайшего времени. Мы видим, что модели, построенные на дескрипторе, получаемом просто с локомоторного сенсора, позволяют в когорте людей до средней продолжительности жизни… Смотрим нижний рисунок. Видно, что три зеленых столбика справа — разные модели. Одна — просто на локомоторном дескрипторе, а две другие с подключением дополнительных параметров — таких, как анализ крови, — позволяют существенно улучшить качество предсказания, качество оценки. Это особенно хорошо для людей, которые еще моложе средней продолжительности жизни. Но эффект становится не таким заметным на верхнем графике, где есть все пациенты, в том числе и очень старые. Значит, границы применимости есть и на сегодняшний день хронологический возраст достаточно точно говорит нам: если ты уже очень старый человек, то способов как-то продлить себе жизнь — мало.

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



Здесь нам приходит на помощь анализ мета-даты и попытка разобраться, почему наша модель дает какое-то улучшение по сравнению с простой оценкой по возрасту, сравнить, посмотреть, в чем на самом деле разница, что такого локомоторный дескриптор, дескриптор о двигательной активности, может рассказать о состоянии здоровья, о физиологии организма — кроме оценки, что такой-то старше, а такой-то моложе. Мы взяли две возрастные группы и дополнительно посмотрели в каждой из них когорту, ранжированную по оценке рисков смертности по нашим дескрипторам, разделив их таким образом на старую и молодую. Это желтый и зеленый сверху. Но желтый и зеленый — так называемые успешно стареющие люди, successfully aging, есть такой термин в англоязычной литературе. И проплатили (нрзб. — прим. ред.) для них кривые дожития. Чем выше кривая, тем больше на каждое время follow up остается живых пациентов. Видим, что среди успешно стареющих людей потихоньку нарастает вероятность смерти. Другими словами, кривая дожития постепенно снижается от желтой для более молодых к зеленой. Но при этом модель, построенная по локомоторному дескриптору, позволяет также выделить когорту с повышенными рисками по ее оценке. И мы смотрим на такие кривые дожития и видим, что они существенно ниже и в них не так хорошо прослеживается зависимость от возраста. Это синий для молодых и высокий риск, и фиолетовые для чуть более старых, тоже с высоким риском. Отсюда мы понимаем, что есть какие-то характеристики физиологии, которые можно узнать, не отправляя человека в больницу, не беря у него каких-то анализов крови или генома, а просто наблюдая за тем, как он двигается, через сенсор в браслете или, может быть, даже в телефоне.

Хочется понять, какой же эффект видит локомоторный трек.



Мы посмотрели и выяснили, что в клинике часто используется индекс немощности — frailty index. Очень простая вещь. Существует список известных хронических заболеваний. Их, например, сотня, и каждый пациент может у доктора на анализе через опросник заполнить список. Доктор скажет, что у пациента из этого списка наблюдается 15 заболеваний, а у другого 30, а у третьего 50. И frailty index — доля заболеваний, наблюдаемых из списка. Оказалось, что, действительно, наша модель, построенная по локомоторной активности человека, довольно хорошо в каждой возрастной когорте коррелирует с этим индексом. Фактически, оказывается, что есть две независимых компоненты. Одна показывает, насколько в среднем организмы стареют. Можно замерить этот эффект через локомоторную активность. Но есть и другая компонента, которая оценивает общее состояние здоровья независимо от возраста. Мы эти две компоненты называем биовозрастом и frailty и видим, что наш довольно простой подход уже способен учитывать оба перечисленных эффекта.

Все-таки остается вопрос, что для каждого человека персонально?



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

Самый известный, самый большой эффект на продолжительность жизни оказывают два стиля жизни. Один из них — курение, второй — диабет. Диабет — не совсем стиль жизни, но часто это диабет второго типа, очень сильно связанный с ожирением. Поэтому условно мы можем его называть связанным с lifestyle. Мы смотрим на датасеты, с которыми работаем. Видим, что, действительно, есть такая информация в метадате. Видим, что наша модель может популярно отскорить популяцию некурильщиков. На верхнем графике она обозначена светлым. Она против курильщиков, для них она предсказывает повышенные риски, повышенное hazard ratio. Более того, она может предсказывать в некотором смысле в дозозависимой степени. То есть видно, что темным цветом показаны так называемые heavy smokers — те, которые выкуривают больше пачки в день. А те, у кого меньше пачки в день, находятся в промежуточном положении между некурящими и курильщиками.

Модель наша была построена на датасете NHANES, зеленым слева. Для диабета — то же самое. У диабетиков этот лайфстайл, повышенный hazard, тоже сенсится.

Очень хорошо было получить доступ к другим датасетам. То есть мы построили модель на одном датасете. NHANES — американский датасет, National Health And Nutrition Examination. Другими словами, там есть данные по крови, по локомоторике, и достаточно большое количество опросников, заполняемых в клинике. Мы посмотрели на другой датасет. Это английский датасет UK Biobank. В нем тоже есть локомоторные данные, тоже есть кровь, генетика и метаданные из медицинских опросников. И мы были очень рады увидеть, что без каких-либо дополнительных изменений, без тюнинга и без претрейнинга модель довольно легко переносима на другой датасет. Показательно здесь то, что в одном датасете был один девайс, а в другом другой. В NHANES это был одноосевой акселерометр, который носится на поясе, примерно как рация у меня здесь. А в UK Biobank это был совершенно другой девайс — трехосевой акселерометр, который пациенты носят на запястье как фитнес-трекер. И мы видим, что все трекеры одинаковые, и если мы окажемся в состоянии какую-то информацию извлекать из модели на датасете, то у нас будут хорошие шансы просто взять и перенести на кейсы из реальной жизни.

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



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

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



Почему курение, почему ожирение? На самом деле не потому, что нам интересно заниматься только ими, а потому что это образы жизни, для которых очень хорошо изучен эффект, оказываемый на состояние здоровья, на продолжительность жизни. Он известен и он достаточно велик. Он составляет, если смотреть на продолжительность жизни по популярности, до 10 лет. Это много. Про здоровье даже говорить нечего. Но на самом деле мы можем взять датасет NHANES — там довольно много информации. Я привел график о том, какие еще группы диагнозов, анализов или параметров стиля или образа жизни могут в принципе коррелировать с оценкой рисков из шагомера. Здесь, на графике, они проплачены, это так называемый volcano plot. По горизонтали — wellness score. На самом деле речь идет о том же самом hazard ratio, но с отрицательным знаком, потому что мы хотим всю историю давать в виде фидбека консьюмерам, пользователям девайсов, шагомеров и смартфонов, и не очень хотим сразу пугать. Ту же самую информацию мы им хотим преподнести в более user friendly-виде. Берем и просто проходимся по всем полям метаданных, делим пользователей на группы, смотрим, насколько велика разница в популярности с одной меткой и с другой, и насколько они значимы, оцениваем по p-value. Видим, что, действительно, у нас довольно большой список, включающий в себя вещи, связанные с лайфстайлом, употреблением алкоголя, курением, нарушением сна, физической активностью, даже связанные с occupation — родом занятости. Потом — медицинские анализы: биохимия, где много параметров оказались связанными с локомоторным дескриптором; body composition — в основном это состояние опорно-двигательного аппарата и скелета; анализы dietary, связанные с питанием, и также связанная с ним body weight — масса тела, диабет. Если захотеть, можно найти много параметров, которые будут коррелировать с тем, как ты двигаешься и как твое движение может засенсить обычный фитнес-трекер, используемый не для подсчета шагов или километров, которые ты пробежал, а для оценки состояния здоровья в целом.



На самом деле, число шагов в день, которое обычно показывают фитнес-приложения или фитнес-трекеры, — она не то чтобы совсем не связана со здоровьем. График показывает scatter plot пациентов в координатах количества шагов в день, среднее за неделю, — против оценки риска смертности. Здесь он показан с другим знаком. Другими словами, перед нами то, что мы называем wellness score уже для пользователей. И видим, что для тех, кто ходит очень мало, примерно до 7 тыс. шагов в день, количество пройденных шагов уже является очень неплохим показателем состояния здоровья. Корреляция между риском, который оценивается по модели, и количеством шагов — очень хорошая. Но все меняется для тех людей, которые ходят. Обычно если покупаешь такой фитнес-трекер, не каждый же день занимаешься фитнесом. Но он все равно должен какую-то информацию показывать. Он дает общую рекомендацию: «Ходи больше, будешь здоровее». Это оказывается не совсем так для тех, кто уже ходит много — от 12 до 16 тыс. шагов в день. Для них видно, что на правовой гистограмме мы уже не можем разделить курильщиков от некурильщиков по количеству шагов. А вот по локомоторному дескриптору, который мы используем на средней гистограмме, они по-прежнему разделяются. Иначе говоря, такой подход позволяет более точно оценить физиологию организма.



Как это может выглядеть для пользователя? Мы попросили одного из наших сотрудников попробовать проассоциировать изменения в его треке, который он записывал в течение нескольких месяцев, с какими-то событиями в жизни. И действительно, видим, что это неслучайные изменения. Здесь красным и зеленым подсвечены тренды в изменениях его wellness score, определенные алгоритмы. Они действительно ассоциируются с какими-то событиями. Понижение — это обычно усталость или болезнь. Повышается он, когда человек едет в отпуск.



У меня довольно академическая презентация, но на самом деле мы очень рассчитываем сделать приложение для пользователей девайсов. И у нас уже есть пилотное приложение, которое работает с трекерами Fitbit. Оно позволяет залогиниться, подписаться, чтобы получить оценку компонентов BIH и Wellness score, frailty-компоненту. К приложению будет прикручен API для других трекеров. Пока мы работаем только с Fitbit, то есть можно будет своими силами попробовать загрузить, не автоматически. И мы рассчитываем, что через месяц-другой оно уже будет доступно для iPhone, чтобы можно было использовать трек с шагомеров в телефоне.

У меня всё, спасибо.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330680/



Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1002 1001 [1000] 999 998 ..
.. 1 Календарь