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

Поиск сообщений в 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 ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

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

JetBrains MPS для интересующихся #1

Пятница, 28 Июля 2017 г. 00:50 + в цитатник

Введение


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


Зачем нам нужен язык Weather?


В комментариях к 1 посту было следующее высказывание


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

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


Синтаксис


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


Пример массива входных данных на JS
const weatherInput = [
  {
    time: 1501179579253,
    temperature: {
        unit: "celsius",
        value: 23.7
     }
  },
 {
    time: 1501185944759,
    temperature: {
        unit: "fahrenheit",
        value: 15.3
     }
  }
]

Думаю сойдет.


Реализация на Weather
Weather prediction rules for Saint Petersburg
data Today:
  [21:23]{
    temperature = 23.7 °C
  }
  [23:06]{
    temperature = 15.3 °F
  }

У нас очень простые данные — время + температура в единицах измерения. Создадим абстрактный концепт WeatherTimedData — он нам нужен для хранения времени измерения и самой температуры.
image
Теперь нужно определить, что такое Temperature и Time.
image
Time реализован очень просто — у нас есть время в часах и минутах, а отображается оно как hh : mm.
image
Если с Time все понятно, то с Temperature немного нет. Во-первых — value это какой-то _FPNumber_String. На самом деле это MPS'овский double, так что ничего страшного. Но вопрос — как из интерфейса Temperature сделать реализации температуры в разных единицах измерения, да так чтобы это еще и красиво было? И вообще, что такое интерфейс концепт?
У таких концептов не может быть реализации в AST. То есть, вообще никакой. Только если другой концепт расширяет его, и никак иначе. Делается это, как и в ООП, для того, чтобы обобщить несколько классов под одно общее начало.
Вот как я реализовал отображение в редакторе для Temperature:
image
Здесь у нас первая ячейка — double значение, величина температуры, а вторая — Read-Only model access. Здесь мы немного отдаляемся от практики и переходим к теории.


Теория


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


abstract class Temperature{
  abstract double value;
  public abstract String getUnit();

  override String toString(){
    return value + this.getUnit();
  }
}

Можно было бы задать unit как переменную, а не писать абстрактный метод, но…
Есть аспект, называется Behavior. Все, что он может делать — добавлять новые методы к концепту. То есть добавить переменную мы не можем, поэтому будем использовать абстрактный метод.
image
И вот после этого мы можем у каждой реализации концепта Temperature вызывать этот метод. Но где же его вызывать? Как вообще кодить в этом MPS?..


Снова практика


Мы остановились на том, что у нас есть непонятная ячейка в Editor аспекте — ReadOnly model access. Все очень просто — если нужно как-то логически обработать proeprty/children/reference перед тем, как его показывать, и на это не хватает встроенных приколов, то мы можем сами получить нужную строку из контекста редактора и реализации концепта. Если просто — нам дают текущий объект концепта, то есть реализованный, и мы можем из него получить все, что мы там понапихали. В данном случае мы хотим получить единицу измерения, поэтому мы нажимаем на ячейку R/O model access и пишем
image
Кстати, в любом месте кода вы можете тыкнуть на штучку, что Вас интересует и нажать Ctrl + Shift + Enter и получить информацию о типе этой штучки. Например, если мы нажмем на node в скрине выше и узнаем его тип, то мы получим
image
node<Название Концепта> = какая-то реализация концепта
concept<Название Концепта> = класс концепта
Так! Мы уже умеем составлять температуру по значению и единице измерения, но откуда мы возьмем, какая единица измерения нам нужна? Из дочерних реализаций, естественно.
Создаем пустой CelsiusTemperature концепт, расширяем Temperature и создаем для него behavior.
image


image
Как видно на последнем скрине, мы переопределяем метод getUnit(он имеется в зоне видимости из-за того, что мы наследовали концепт от Temperature) и возвраещем "°C". Все просто!
Остается только собрать все вместе в WeatherTimedData:
image
Собираем язык и смотрим на результат:
image
Вроде похоже на правду. Еще, конечно, нет самих предсказаний погоды, нет подсветки, к тому же часы у нас могут быть больше 24 и меньше нуля, минуты тоже не ограничены ничем, кроме размерности integer… В следующем посте ждите разъяснений по новому аспекту — constraints и еще чего-нибудь. А пока — пишите фидбек в комментариях, все как всегда, если вопрос простой — отвечаю там же, если он обширен и скорее как пожелание — то я постараюсь с каждым постом писать все качественнее. Спасибо за внимание!

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

https://habrahabr.ru/post/334308/


Метки:  

Перевод книги Appium Essentials. Глава 6

Четверг, 27 Июля 2017 г. 20:23 + в цитатник
Добрый вечер, Хабр. У нас тут подходит к концу перевод Appium Essentials. Что уже пройдено:



В этой главе:
  • Автоматизируем набор номера на устройстве Android
  • Автоматизируем форму регистрации на Android
  • Используя Chrome, залогинимся на Gmail
  • iOS. Автоматизируем Body Mass Index (BMI)
  • Автоматизация гибридных приложений на устройствах iOS
  • iOS. Автоматизация веб-приложений


Перед стартом


Перед началом работы, еще раз убедимся, что все настроено:
Требования для Android Требования для iOS
Java (версии 7 и выше) Mac OS (версии 10.7 и выше)
Android SDK API, версии 17 и выше Xcode (версии 4.6.3 и выше; рекомендуется 5.1)
Устройство с Android Профиль iOS [эти и другие непонятные слова объясняются ниже]
Браузер Chrome на устройстве Устройство iOS
Eclipse [или Idea] Приложение SafariLauncher
TestNG ios-webkit-debug-proxy
Appium сервер Java (версии 7 и выше)
Клиентская библиотека Appium (у нас все еще Java) Eclipse [или Idea]
Selenium Server и Java-библиотека WebDriver TestNG
Приложение Apk Info app Selenium Server и Java-библиотека WebDriver
Appium сервер
Клиентская библиотека Appium (у нас все еще Java)

Убедитесь, что на устройстве Android включен режим разработчика и разрешена отладка оп USB.
Чтобы проверить, что девайс подключен, введите в командную строку
adb devices

Вы получите список Android-устройств. Если нет, попробуйте перезапустить adb-сервер:

adb kill-server
adb start-server

Desired capabilities для Android для нативных и гибридных приложений


В главе 1 мы обсудили, какие есть опции и зачем они. Так что здесь, перейдем сразу к коду.
Сначала, импортируем пакеты:
import java.io.File;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.remote.MobileCapabilityType;

Теперь DC:
DesiredCapabilities caps = new DesiredCapabilities();
File app=new File("path of the apk");//чтобы определить путь до apk
caps.setCapability(MobileCapabilityType.APP,app);//если приложение уже установлено, эти две опции не нужны
caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");//версия Android
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");//Имя OS
caps.setCapability(MobileCapabilityType.DEVICE_NAME, "Moto X");//здесь указывается имя устройства 
caps.setCapability(MobileCapabilityType.APP_PACKAGE, "имя пакета вашего приложения(можете получить его, используя apk info app)"); 
caps.setCapability(MobileCapabilityType.APP_ACTIVITY, "активити, которую нужно будет запустить (получить так же через apk info app)");

Desired capabilities для Android для веб-приложений


При работе с веб-приложениями, некоторые опции, указанные выше, нам не понадобятся: APP, APP PACKAGE и APP ACTIVITY, поскольку мы работаем с браузером.
Сперва, импортируем пакеты:
import java.io.File;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.remote.MobileCapabilityType;

Теперь DC:
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");//версия Android
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");//Имя OS
caps.setCapability(MobileCapabilityType.DEVICE_NAME, "Moto X");//здесь указывается имя устройства 
caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Chrome"); //чтобы запустить Chrome

Мы закончили с конфигурацией, теперь можно инициализировать драйвер.
Импорт:
import io.appium.java_client.android.AndroidDriver;
import java.net.URL;

Инициализация:
AndroidDriver driver = new AndroidDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);//указываем адрес, где запущен Appium-сервер

Все готово для работы с устройством Android.

Разбираемся с provisional profile, SafariLauncher и ios-webkit-debug-proxy


Прежде чем настраивать драйвер для iOS, нужно выполнить несколько шагов.
Provisional profile
Профиль нужен для того, чтобы ставить свои приложения на устройства iOS. Для этого нужно присоединиться к программе iOS Developer Program
После регистрации, посетите эту страницу, чтобы сгенерировать профиль.
Этот профиль нужно будет установить и на девайс:
  1. Скачайте сгенерированный профиль
  2. Подключите iOS устройство к Mac
  3. Откройте Xcode (версии 6) и перейдите в меню Window -> Devices
  4. Вызовите контекстное меню для подключенного устройства и кликните на Show Provisional Profiles…
  5. Нажмите +, выберите скаченный профиль, нажмите Done

Приложение SafariLauncher и ios-webkit-debug-proxy
SafariLauncher используется для запуска браузера Safari на устройстве. Нужно собрать и задеплоить SafariLauncher на устройство iOS, чтобы работать с браузером Safari:
  1. Скачайте исходники
  2. Запустите Xcode и откройте проект SafariLauncher
  3. Выберите устройство для деплоя и нажмите кнопку build
  4. Затем нужно будет заменить SafariLauncher в Appium.dmg; для этого необходимо:
    1. Вызвать контекстное меню для Appium.dmg
    2. Кликнуть на Show Package Contents и перейти в Contents/Resources/node_modules/appium/build/SafariLauncher
    3. Разархивировать SafariLauncher.zip
    4. Перейти в submodules/SafariLauncher/build/Release-iphoneos и замените приложение SafariLauncher своим.
    5. Заархивируйте submodules и переименуйте в SafariLauncher.zip

Теперь нужно поставить ios-webkit-debug-proxy на Mac, чтобы установить соединение и получить доступ к web-view. Чтобы поставить прокси, можете использовать brew и выполнить команду
brew install ios-webkit-debug-proxy
.

Desired capabilities для iOS для нативных и гибридных приложений


В главе 1 мы обсудили, какие есть опции и зачем они. Так что здесь, перейдем сразу к коду.
Сначала, импортируем пакеты:
import java.io.File;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.remote.MobileCapabilityType;

Теперь DC:
DesiredCapabilities caps = new DesiredCapabilities();
File app=new File("path of the .app");
caps.setCapability(MobileCapabilityType.APP,app);
caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");//
caps.setCapability(MobileCapabilityType.DEVICE_NAME, "iPad");//
caps.setCapability("udid","Id реального устройства");//UDID

Desired capabilities для iOS для веб-приложений


Некоторые из опция нам не понадобятся. К этим опциям относятся: APP, APP PACKAGE и APP ACTIVITY. Теперь к делу.
Сначала, импортируем пакеты:
import java.io.File;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.remote.MobileCapabilityType;

Теперь DC:
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
caps.setCapability(MobileCapabilityType.DEVICE_NAME,"iPad");
caps.setCapability("udid","UDID вашего устройства");
caps.setCapability(MobileCapabilityType.BROWSER_NAME,"Safari"); //чтобы запустить Safari

Мы закончили с конфигурацией, теперь можно инициализировать драйвер.
Импорт:
import io.appium.java_client.ios.IOSDriver;
import java.net.URL;

Инициализация:
IOSDriver driver = new IOSDriver (new URL("http://127.0.0.1:4723/wd/hub"),caps); //Адрес, где Appium-сервер

Все готово для работы с устройством iOS.
Теперь можно добавить TestNG и попробовать запустить все вместе:
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class TestAppIication {
	IOSDriver driver;
	@BeforeClass //этот метод выполняется перед каждым тестом
	public void setUp() throws MalformedURLException{
		DesiredCapabilities caps = new DesiredCapabilities();
		File app=new File("path of the .app");
		caps.setCapability(MobileCapabilityType.APP,app);
		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
		caps.setCapability(MobileCapabilityType.DEVICE_NAME,"iPad");
		caps.setCapability("udid","UDID вашего устройства");
		caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Safari");//в случае, если работаем с веб-приложением
		driver = new IOSDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
	}
	@Test
	public void testExample(){
		//здесь шаги теста
	}
	@AfterClass
	public void tearDown(){
		driver.closeApp();
		//driver.quit(); //в случае, если работаем с веб-приложением
	}
}

Автоматизация нативных приложений


Нативные приложения на Android


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

  1. Установите следующие desired capabilities, для запуска приложения dialer
    caps.setCapability(MobileCapabilityType.APP_PACKAGE, "com.android.dialer");
    caps.setCapability(MobileCapabilityType.APP_ACTIVITY, "com.android.dialer.DialtactsActivity");
  2. Теперь нужно найти dial pad. Искать будем по AccessibityId
    WebElement dialPad= driver.findElementByAccessibilityId("dial pad"));
  3. Клик
    dialPad.click();
  4. Нужно найти клавиши с цифрами. Задействуем немного логики, чтобы найти клавиши 0-9 по name и кликнем каждую из них
    for(int n=0;n<10;n++){
    	driver.findElement(By.name(""+n+"")).click();
    }
  5. В данном случае, мы используем некорректный номер телефона, так что телефон никуда не позвонит
  6. Давайте найдем кнопку вызова. Искать будем по AccessibilityId:
    WebElement dial= driver.findElementByAccessibilityId("dial"));
  7. Клик:
    dial.click();
  8. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	AndroidDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException{
    		DesiredCapabilities caps = new DesiredCapabilities();
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME,"Moto X");//I am using Moto X as Real Device
    		caps.setCapability(MobileCapabilityType.APP_PACKAGE, "com.android.dialer");
    		caps.setCapability(MobileCapabilityType.APP_ACTIVITY, "com.android.dialer.DailtactsActivity");
    		driver = new AndroidDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	@Test
    	public void testExample(){
    		WebElement dialPad=driver.findElementByAccessibilityId("dial pad");
    		dialPad.click();
    		for(int n=0;n<10;n++){
    			driver.findElement(By.name(""+n+"")).click();
    		}
    		WebElement dial=driver.findElementByAccessibilityId("dial");
    		dial.click();
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.closeApp();
    	}
    }

Нативные приложения на iOS


Здесь мы поработаем с калькулятором BMI

Мы рассчитаем индекс массы тела по весу и росту. Для этого, делаем следующее:
  1. Установите следующие desired capabilities, для запуска приложения
    File app=new File("/Users/mhans/appium/ios/BmiCalc.app");//You can change it with your app address
    caps.setCapability(MobileCapabilityType.APP,app);//To set the app path
  2. Теперь нужно найти поля ввода роста и веса. Будем искать по Xpath
    WebElement height=driver.findElement(By.xpath("(//UIATextField)[2]"));
    WebElement weight=driver.findElement(By.xpath("(//UIATextField)[4]"));
    
  3. Ищем кнопку calculate. Используем name:
    WebElement calculateBMI=driver.findElement(By.name("Calculate BMI"));
  4. Указываем рост
    height.sendKeys("1.82");
  5. Указываем вес
    weight.sendKeys("75");
  6. Считаем индекс
    calculateBMI.click();
  7. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	IOSDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException{
    		File app=new File("/Users/mhans/appium/ios/BmiCalc.app");//You can change it with your app address
    		DesiredCapabilities caps = new DesiredCapabilities();
    		caps.setCapability(MobileCapabilityType.APP,app);
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME,"iPad");
    		caps.setCapability("udid","Real Device Id ");
    		driver = new IOSDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	@Test
    	public void testExample(){
    		WebElement height=driver.findElement(By.xpath("(//UIATextField)[2]"));
    		height.sendKeys("1.82");
    		WebElement weight=driver.findElement(By.xpath("(//UIATextField)[4]"));
    		weight.sendKeys("75");
    		WebElement calculateBMI=driver.findElement(By.name("Calculate BMI"));
    		calculateBMI.click();
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.closeApp();
    	}
    }
    


Автоматизация веб приложений


Веб приложения на Android


Для примера, рассмотрим страницу авторизации в Gmail. В этом разделе, мы посмотрим, как запустить Chrome на реальном устройстве, перейти по нужному адресу, передать логин/пароль и нажать SignIn

  1. Установите следующие desired capabilities, для запуска Chrome
    caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Chrome");
  2. Теперь нужно указать адрес
    driver.get("https://www.gmail.com");
  3. Нужно найти элемент username. Используем локатор name
    WebElement username=driver.findElement(By.name("Email"));
  4. Передаем значение
    username.sendKeys("test");
  5. Теперь, нужно найти элемент password. Используем локатор name
    WebElement password=driver.findElement(By.name("Passwd"));
  6. Передаем значение
    password.sendKeys("test");
  7. Ищем кнопку SignIn. Используем локатор name
    WebElement signIn=driver.findElement(By.name("signIn"));
  8. Клик
    signIn.click();
  9. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	AndroidDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException {
    		DesiredCapabilities caps = new DesiredCapabilities();
    		caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Chrome");
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME,"Moto X");
    		driver = new AndroidDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	@Test
    	public void testExample(){
    		driver.get("https://www.gmail.com");
    		WebElement username=driver.findElement(By.name("Email"));
    		username.sendKeys("test");
    		WebElement password=driver.findElement(By.name("Passwd"));
    		password.sendKeys("test");
    		WebElement signIn=driver.findElement(By.name("signIn"));
    		signIn.click(); 
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.quit();
    	}
    }
    

Веб приложения на iOS


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

  1. Установите следующие desired capabilities, для запуска Safari
    caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Safari");
  2. Теперь нужно указать адрес
    driver.get("https://www.google.com");
  3. Нужно найти элемент searchBox. Используем локатор name
    WebElement searchBox=driver.findElement(By.name("q"));
  4. Передаем значение
    searchBox.sendKeys("Appium for mobile automation");
  5. Перед запуском скрипта, нам нужно будет включить прокси командой:
    ios_webkit_debug_proxy -c 2e5n6f615z66e98c1d07d22ee09658130d345443:27753 –d

    Замените 2e5n6f615z66e98c1d07d22ee09658130d345443 на UDID вашего девайса. Порт должен быть 27753
  6. Убедитесь, что Web Inspector включен (Settings | Safari | Advanced) и SafariLauncher установлен на ваш девайс
  7. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	IOSDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException{
    		DesiredCapabilities caps = new DesiredCapabilities();
    		caps.setCapability(MobileCapabilityType.BROWSER_NAME, "Safari");
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME, "iPad");
    		caps.setCapability("udid","Real Device Identifier");
    		driver = new IOSDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	@Test
    	public void testExample(){
    		driver.get("https://www.google.com");
    		WebElement searchBox=driver.findElement(By.name("q"));
    		searchBox.sendKeys("Appium for mobile automation");
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.quit();
    	}
    }
    


Автоматизация гибридных приложений


Гибридные приложения на Android


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

В примере попробуем заполнить форму в приложении:
  1. Установите следующие desired capabilities, для запуска гибридного приложения
    File app=new File("C:\\Appium_test\\HybridtestApp.apk");// (On window platform)
    caps.setCapability(MobileCapabilityType.APP,app);
    caps.setCapability(MobileCapabilityType.APP_PACKAGE, "com.example.hybridtestapp");
    caps.setCapability(MobileCapabilityType.APP_ACTIVITY, "com.example.hybridtestapp.MainActivity");
  2. Теперь нужно сменить контекст, чтобы работать с WebView
    Set contexts = driver.getContextHandles();
    for (String context : contexts) {
    	System.out.println(context); //выводим список контекстов
    }
  3. Переключаем
    driver.context("WEBVIEW_com.example.hybridtestapp");

    или
    driver.context((String) contextNames.toArray()[1]);
  4. После перехода в WebView, нам нужно найти поле ввода First Name. Искать будем по локатору name
    WebElement firstName=driver.findElement(By.name("fname"));
    firstName.sendKeys("test");
  5. И Last Name
    WebElement lastName=driver.findElement(By.name("lname"));
    lastName.sendKeys("test");
    
  6. Таким образом заполняем остальные поля
    WebElement age=driver.findElement(By.name("age"));
    age.sendKeys("26");
    
    WebElement username=driver.findElement(By.name("username"));
    username.sendKeys("appiumTester");
    
    WebElement password=driver.findElement(By.id("psw"));
    password.sendKeys("appium@123");
    

  7. И кликаем на копку Register
    WebElement registerButton=driver.findElement(By.id("register"));
    registerButton.click();
    
  8. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	AndroidDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException{
    		File app=new File("C:\\Appium_test\\HybridtestApp.apk");
    		caps.setCapability(MobileCapabilityType.APP,app);
    		DesiredCapabilities caps = new DesiredCapabilities();
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "4.4");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME, "Moto X");
    		caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "Appium");//Используйте Selendroid если версия android ниже 4.4
    		caps.setCapability(MobileCapabilityType.APP_PACKAGE, "com.example.hybridtestapp");
    		caps.setCapability(MobileCapabilityType.APP_ACTIVITY, "com.example.hybridtestapp.MainActivity");
    		driver = new AndroidDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	
    	@Test
    	public void testExample(){
    		Set contexts = driver.getContextHandles();
    		for (String context : contexts) {
    			System.out.println(context);
    		}
    		driver.context((String) contexts.toArray()[1]);
    		WebElement firstName=driver.findElement(By.name("fname"));
    		firstName.sendKeys("test");
    		WebElement lastName=driver.findElement(By.name("lname"));
    		lastName.sendKeys("test");
    		WebElement age=driver.findElement(By.name("age"));
    		age.sendKeys("26");
    		WebElement username=driver.findElement(By.name("username"));
    		username.sendKeys("appiumTester");
    		WebElement password=driver.findElement(By.id("psw"));
    		password.sendKeys("appium@123");
    		WebElement registerButton=driver.findElement(By.id("register"));
    		registerButton.click();
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.closeApp();
    	}
    }
    

Гибридные приложения на iOS


Пример будем рассматривать на приложении WebViewApp

Пробуем работать с приложением:
  1. Установите следующие desired capabilities, для запуска гибридного приложения
    File app=new File("/Users/mhans/appium/ios/WebViewApp.app");
    caps.setCapability(MobileCapabilityType.APP,app);
  2. Теперь нужно найти поле ввода и указать нужный адрес
    WebElement editBox=driver.findElement(By.className("UIATextField"));
    editBox.sendKeys("www.google.com");
  3. Ищем и кликаем кнопку Go
    WebElement goButton=driver.findElement(By.name("Go"));
    goButton.click();
  4. Теперь нужно сменить контекст, чтобы работать с WebView
    Set contexts = driver.getContextHandles();
    for (String context : contexts) {
    	System.out.println(context);
    }
  5. Переключаем
    driver.context("WEBVIEW_com.example.testapp");

    или
    driver.context((String) contextNames.toArray()[1]);
  6. Все. Можем работать со страницей google. Пробуем открыть кладку Images:
    WebElement images=driver.findElement(By.linkText("Images"));
    images.click();
  7. Перед запуском всего теста, нужно включить прокси
    ios_webkit_debug_proxy -c :27753 –d

    Порт должен быть 27753
  8. Весь скрипт, с использованием TestNG, будет выглядеть вот так:
    public class TestAppIication {
    	IOSDriver driver;
    	@BeforeClass
    	public void setUp() throws MalformedURLException{
    		DesiredCapabilities caps = new DesiredCapabilities();
    		File app=new File("/Users/mhans/appium/ios/WebViewApp.app");
    		caps.setCapability(MobileCapabilityType.APP,app);
    		caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, "8.1");
    		caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
    		caps.setCapability(MobileCapabilityType.DEVICE_NAME, "iPad");
    		caps.setCapability("udid","Real Device Identifier");
    		driver = new IOSDriver (new URL("http://127.0.0.1:4723/wd/hub"), caps);
    		driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
    	}
    	@Test
    	public void testExample(){
    		WebElement editBox=driver.findElement(By.className("UIATextField"));
    		editBox.sendKeys("https://www.google.com");
    		WebElement goButton=driver.findElement(By.name("Go"));
    		goButton.click();
    		Set contexts = driver.getContextHandles();
    		for (String context : contexts) {
    			System.out.println(context);
    		}
    		driver.context((String) contexts.toArray()[1]);
    		WebElement images=driver.findElement(By.linkText("Images"));
    		images.click();
    	}
    	@AfterClass
    	public void tearDown(){
    		driver.closeApp();
    	}
    }
    



Вот и все. У нас осталась последняя глава, в которой мы посмотрим, как имитировать специфичные, для мобильной платформы, пользовательские действия.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333546/


Метки:  

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

Четверг, 27 Июля 2017 г. 19:53 + в цитатник
Недавно решил проверить на уязвимости сайты платежных систем (ua,ru). Нашёл топ такого рода сервисов, на множестве из которых были обнаружены xss, csrf и другие популярные уязвимости. Были компании, которые оперативно устраняли уязвимости, благодарили и договаривались о сотрудничестве, были, которые молча фиксили, и самый неприятный момент — компании, которые не верили в опасность проблемы, я пытался доказать им обратное, что дело обстоит серьезно, предлагал показать уязвимость на их тестовом аккаунте, говорили, что исправят, но до сих пор и не исправили (maxkassa.ru).

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

Сайт компании https://www.plategka.com, это 7900 alexarank в Украине. Я обычно не трачу много времени на каждый сайт, потому что ещё не известно, как относится к уязвимостям на своём сайте владелец, возможно он проигнорирует уязвимость и моё время будет потрачено зря. Если нашёл первую стоящую уязвимость на сайте, то я её сразу репорчу, смотрю на реакцию владельца, и если он положительно относится к такого рода мероприятиям и хочет чтобы я сотрудничал с ним дальше, то продолжаю свои поиски.

Первое, что я сделал — это проверил поддомены и директории по моему словарю, поддомены были выявлены следующие
mail.plategka.com
test.plategka.com
, в целом брут ничего не дал.
Далее была просмотрена выдача по google доркам, увы, поиск не увенчался успехом, и единственное что было найдено — статья Gorodnya на Хабре о том, как он нашёл уязвимость на plategka https://habrahabr.ru/post/269663/, и о том как ему заплатили очень много денег — всего $8 :)

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



Одна вещь, на которую я обратил внимание — это сервис pay2me. Его работа заключается в обработке данных о кредитных картах, я посчитал это интересным предметом для изучения.
Раньше натыкался на новости с этим сервисом https://ain.ua/2015/10/15/novyj-servis-po-priemu-platezhej-pay2me-ot-plategka, но не обращал на них особого внимания. Инструкция к созданию:

В личном кабинете есть соответствующая вкладка — «Настройки Pay2me». Для создания уникальной ссылки необходимо ввести ее название, 16-значный номер карты и нажать клавишу «Добавить».


Я создал свою ссылку https://www.plategka.com/gateway/pay2me/qwqwqw/


Данные о карте были скрыты, xss уязвимости отсутствовали. Внимание привлек ответ бурпа. В исходном коде страницы с оплатой в javascript выдавался логин юзера, md5 хэш его пароля, ФИО, email и номер телефона:
{“user_id”:”dd660731660eb4bba1f62e44e7'',”user_role_id”:null,”org_id”:null,”merchant_id”:null,”partner_id”:null,”login”:”berest****”,”passwd”:”f1957496d2f5c7cb3caa73c2e*******”,”first_name”:”\u0418\u043b\u044c\u044f”,”second_name”:”\u0418\u0433\u043e\u0440\u0435\u0432\u0438\u0447'',”last_name”:”\u0412\u044f\u0437\u043c\u0435\u043d\u0442\u0438\u043d\u043e\u0432'',”email”:”berest****@ya.ru”,”phone”:”+38097360'',”key_private”:”inNFD\/aJyC2\/KWsuQ9vAs9FGg6\/\/4YnGjQuHUX7tu0i5GMY7weB3EddH815ytvkaUfS8F0KuxWMgAzDBAKv+Rv6Uzqgtn6qMaHH8N6v5KFWFeeRncGQ6XPQwo4kboNJIf8jidlFj\/Xi8+5BqRM\/cUfDt+P3ODaql5dImLehS36jzBwsQq6NjKZFYVkwhZ1mKbcu4H832JA32mHaHmTLlb\/HAocxg6ws1EDKRyJoNL3S8P”,”key_public”:”/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/+6MXVFgAmowKck8C+Bqoofy6+A2Qx6lREFBM8ENpCZGo6QAkjv2uYcw+JwXzBglqyomyBGp6kCR3G4KwWuFNQG+dNGA+uRC3wCvbHyPPRZKpuNIwM3AFGXdWWs3yZok32NbMkFRRcFa3raJ1QFlk1usJ6D3tnGiSrPL4NM+kB\/S6ezQHZzepScfD”,”secret”:”d7ceeb0a873ad2d29de12c45b******”,”secret_key”:null,”date_created”:”2017-05-11''}

(это данные, взятые из url https://www.plategka.com/gateway/pay2me/ilia/ ).



Уязвимость найдена, но одной моей ссылки для доказательства будет маловато, было принято решение о нахождении других подобных ссылок.
Были попытки их нахождения с помощью дорков вида site:plategka.com inurl:pay2me/, выдало только одну ссылку https://vk.com/thewayua?w=wall63064560_1589. Я знал, что ссылок должно быть намного больше, потому что сервис пользуется хорошей популярностью.
Последним шансом получить заветные ссылки стал брутфорс в интрудере, было загружено более 100 000 популярных английских слов и начат перебор.



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


Я зашёл на пару аккаунтов проверить, шифруются ли cvv2 и др данные карты.
На первом аккаунте обнаружил 3 действительных карты



На втором было 20



cvv в аккаунте скрыт, раскрывается информация только о 10 цифрах номера карты и срок её действия. Чтобы обналичить карты, можно, например, пополнить webmoney, или другой электронный кошелёк. Но для того, чтобы это сделать, придется ввести cvv2 код в подтверждении платежа, даже если карта была верифицирована.

Первое, что мы можем сделать – это ввести рандомный cvv2 код, например 111. Часто нас редиректит на ввод otp от банка, и тогда платёж не проходит из-за неверного cvv2.



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

Если у нас будет дата и частичный номер карты, то обычная фишинг атака может заставить юзера рассказать о номере своей карты (мы получили email, номер телефона, ФИО, я даже смог получить несколько ссылок на страницы пользователей в соцсетях), даже обычное письмо вида: “Вы выиграли денежный приз, для его получения Вам необходимо прислать нам номер Вашей карты” может сработать, действие не требует предоставления критических конфиденциальных данных.

Чтобы подобрать cvv2, можно использовать какой-то онлайн магазин, всего 1000 запросов и мы подобрали код (вот статья об этом https://xakep.ru/2016/12/05/visa-brutforce/ ). Конечно, некоторые магазины делают редирект на ввод otp от банка, но многие “забугорные” магазины сразу списывают деньги без необходимости редиректа.

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

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

Хронология:
13.07 в 17:00 — отправлен репорт.
13.07 — уязвимость исправлена.
20.07 — выплачена награда $100.
24.07 — статья была согласована с тех поддержкой plategka.com.
Вывод из статьи: Не смотря на заявленное прохождение PCI DSS, я бы рекомендовал сайтам периодически проверять уязвимости, а клиентам — заводить виртуальные карты для использования в Интернет.

P.S: Если Вам нужен аудит безопасности, то пишите сюда.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333930/


[Из песочницы] Мониторинг акторов в Akka.Net, но на F#

Четверг, 27 Июля 2017 г. 19:51 + в цитатник
Сразу скажу, хаба для F# на хабре нет, поэтому пишу в C#.

Для тех кто не знаком с F#, но знаком с C#, рекомендую наисвежайшую статью от Microsoft.
Она поможет Вам испытывать меньше WTF моментов при прочтении, т.к. моя статья не туториал к синтаксису.


Контекст задачи


Есть сервис, написанный на Akka.NET, он вываливает в разные текстовые логи кучу инфы. Отдел эксплуатации грепает эти логи, жарит по ним регекспами, чтобы узнать о кол-ве ошибок (бизнесовых и не очень), о кол-ве входящих в сервис сообщений и кол-ве исходящих. Далее эта информация заливается в ElasticDB, InfluxDB и показывается в Grafana и Kibana в разных срезах и агрегациях.

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

Решать задачу будем так:

  1. Напишем доменную модель для метрик
  2. Замапим доменную модель метрик на реализацию App.Metrics и поднимем апишечку
  3. Сделаем структурированный доменный логгер, который натянем на внутренний логгер Akka
  4. Сделаем обёртку для функциональных акторов, которая спрячет работу с метриками и логгером
  5. Соберём всё вместе и запустим

Доменная модель для метрик


В App.Metrics есть 6 типов представлений:

  • Counters
  • Apdex
  • Gauges
  • Histograms
  • Meters
  • Timers

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

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

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

    type IMetricsTimer = 
        abstract member Measure : Amount        -> unit
        abstract member Measure : Amount * Item -> unit

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

    type IMetricsCounter = 
        abstract member Decrement : unit          -> unit
        abstract member Decrement : Amount        -> unit
        abstract member Decrement : Amount * Item -> unit
        abstract member Increment : unit          -> unit
        abstract member Increment : Amount        -> unit
        abstract member Increment : Amount * Item -> unit

И пара примеров команд для шины:

    type DecrementCounterCommand = 
        { CounterId       : CounterId
          DecrementAmount : Amount
          Item            : Item }

    type CreateCounterCommand = 
        { CounterId             : CounterId
          Context               : ContextName
          Name                  : MetricName
          MeasurementUnit       : MeasurementUnit
          ReportItemPercentages : bool
          ReportSetItems        : bool
          ResetOnReporting      : bool }

Самое главное — определим возможные сообщения, которые могут ходить по шине, и на которые будет реагировать наш метрик-актор. Для этого воспользуемся Discriminated Union:

    type MetricsMessage =
        | DecrementCounter of DecrementCounterCommand
        | IncrementCounter of IncrementCounterCommand
        | MarkMeter        of MarkMeterCommand
        | MeasureTime      of MeasureTimeCommand
        | CreateCounter    of CreateCounterCommand
        | CreateMeter      of CreateMeterCommand
        | CreateTimer      of CreateTimerCommand

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

Пример создания метра:

    let private createMeter (evtStream: EventStream) meterId = 
        { new IMetricsMeter with

              member this.Mark amount = 
                  this.Mark (amount, Item None)

              member this.Mark item = 
                  this.Mark (Amount 1L, item)

              member this.Mark (amount, item) = 
                  evtStream.Publish <| MarkMeter { MeterId = meterId; Amount = amount; Item = item }

Для людей из мира C# даю аналог:

        private IMetricsMeter createMeter(EventStream evtStream, MeterId meterId)
        {
            private class TempClass : IMetricsMeter
            {
                public void Mark(long amount)
                {
                    Mark(amount, "");
                }

                public void Mark(string item)
                {
                    Mark(1, item);
                }

                public void Mark(long amount, string item)
                {
                    evtStream.Publish(new MarkMeter {...});//omitted
                }
            }
            return new TempClass();
        }

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

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

Аналогично поступаем с IMetricsAdapter, но т.к. методов у него много приведу один:

            member this.CreateMeter (name, measureUnit, rateUnit) = 
                let cmd = 
                    { MeterId         = MeterId (toId name)
                      Context         = context
                      Name            = name
                      MeasurementUnit = measureUnit
                      RateUnit        = rateUnit }
                evtStream.Publish <| CreateMeter cmd
                createMeter evtStream cmd.MeterId

При запросе на создание таймера мы отправляем в шину сообщение о создании, а вызывающему возвращаем результат метода createMeter с аргументами evtStream и cmd.MeterId.
Результат её, как видно выше — IMetricsMeter.

После этого создадим расширение для ActorSystem, чтобы можно было вызывать наш IMetricsAdapter откуда угодно:

    type IActorContext with
        member x.GetMetricsProducer context = 
            createAdapter x.System.EventStream context

Акторы для метрик и апишечка


Нам понадобятся два актора:

  • Первый будет слушать шину на наличие в ней MetricsMessage и создавать/писать в метрики.
  • Второй актор будет держать WebApi с одним методом, который будет отгружать по GET запросу всю собранную инфу.

Сразу сообразим ApiController, он тривиален:

    type public MetricController(metrics: IMetrics) = 
        inherit ApiController()

        []
        []
        member __.GetMetrics() =
            __.Ok(metrics.Snapshot.Get())

Далее объявим функцию актора, который будет считывать все MetricsMessage из EventStream и что-то с ними делать. В функцию внедрим зависимость IMetrics через аргументы, внутри создадим кэши для всех метрик через обычные Dictionary.

Почему не ConcurrentDictionary, спросите Вы? А потому что актор обрабатывает сообщения по очереди. Чтобы словить внутри актора race condition, надо целенаправленно стрелять себе в ногу.

    let createRecorder (metrics: IMetrics) (mailbox: Actor<_>) = 
        let self = mailbox.Self

        let counters = new Dictionary()
        let meters   = new Dictionary()
        let timers   = new Dictionary()
        //Часть кода для мапинга пропущена...

        let handle = function
            | DecrementCounter evt ->
                match counters.TryGetValue evt.CounterId with
                | (false, _) -> ()
                | (true,  c) ->
                    let (Amount am) = evt.DecrementAmount
                    match evt.Item with
                    | Item (Some i) -> c.Decrement (i, am)
                    | Item None     -> c.Decrement (am)
            | CreateMeter cmd ->
                match meters.TryGetValue cmd.MeterId with
                | (false, _) ->
                    let (ContextName ctxName) = cmd.Context
                    let (MetricName name)     = cmd.Name
                    let options = new MeterOptions(
                                        Context         = ctxName, 
                                        MeasurementUnit = toUnit cmd.MeasurementUnit, 
                                        Name            = name,
                                        RateUnit        = toTimeUnit cmd.RateUnit)
                    let m = metrics.Provider.Meter.Instance options
                    meters.Add(cmd.MeterId, m)
                | _ -> ()
           //Остальные случае в этом match пропущены

        subscribe typedefof self mailbox.Context.System.EventStream |> ignore

        let rec loop() = actor {
            let! msg = mailbox.Receive()
            handle msg
            return! loop()
        }
        loop()


Краткий смысл — объявили внутреннее состояние в виде словарей разных метрик, объявили функцию обработки сообщения MetricsMessage, подписались на MetricsMessage и вернули рекурсивную функцию обработки сообщения из мейлбокса.

Сообщения для работы с метриками обрабатывается так:

  1. Смотрим какое именно сообщение (через паттерн матчинг)
  2. Ищем в соответствующем словаре метрику с этим Id (для этого есть прекрасный паттерн через пару (bool, obj), который возвращает TryGetValue в F#
  3. Если это запрос на создание метрики и её нет — создаём, добавляем в словарь
  4. Если это запрос на использование метрики и она есть — используем

Так же нам понадобится актор, который поднимает Owin хост с контроллером выше.
Для этого напишем функцию, которая принимает зависимость в виде конфига и IDependencyResolver. Чтобы не завалиться на старте, актор сам себе посылает сообщение, которое инициирует возможный Dispose() старого API и создание нового. И опять таки, т.к. актор внутри себя синхронен, мы можем использовать mutable state.

    type IMetricApiConfig = 
        abstract member Host: string
        abstract member Port: int
    
    type ApiMessage = ReStartApiMessage

    let createReader (config: IMetricApiConfig) resolver (mailbox: Actor<_>) =
        let startUp (app: IAppBuilder) = 
            let httpConfig = new HttpConfiguration(DependencyResolver = resolver)
            httpConfig.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MetricDataConverter())
            httpConfig.Formatters.JsonFormatter.Indent <- true
            httpConfig.MapHttpAttributeRoutes()
            httpConfig.EnsureInitialized()
            app.UseWebApi(httpConfig) |> ignore

        let uri = sprintf "http://%s:%d" config.Host config.Port
        let mutable api = {new IDisposable with member this.Dispose() = ()}

        let handleMsg (ReStartApiMessage) = 
            api.Dispose()
            api <- WebApp.Start(uri, startUp)

        mailbox.Defer api.Dispose
        mailbox.Self 

Так же мы кидаем метод api.Dispose в отложенные задачи при окончательной остановке актора с помощью mailbox.Defer. А для начального состояния переменной api используем заглушку через object expression, которое конструирует пустой IDisposable объект.

Делаем структурированный логгер


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

Вся типизация логгера заключена в одном union.

type Fragment =
    | OperationName     of string
    | OperationDuration of TimeSpan
    | TotalDuration     of TimeSpan
    | ReceivedOn        of DateTimeOffset
    | MessageType       of Type
    | Exception         of exn

А сам логгер будет работать по такому интерфейсу:

type ILogBuilder = 
    abstract OnOperationBegin:     unit     -> unit
    abstract OnOperationCompleted: unit     -> unit
    abstract Set:                  LogLevel -> unit
    abstract Set:                  Fragment -> unit
    abstract Fail:                 exn      -> unit
    abstract Supress:              unit     -> unit
    abstract TryGet:               Fragment -> Fragment option

Создавать его будем через обычный класс:

type LogBuilder(logger: ILoggingAdapter) = 
    let logFragments = new Dictionary()
    let stopwatch    = new Stopwatch()
    let mutable logLevel = LogLevel.DebugLevel
    interface ILogBuilder with
        //Реализация интерфейса

Возможно Вы спросите, почему обычный Dictionary? Как было сказано выше, данный LogBuilder предназначен для использования внутри актора при обработке одной операции. Нет смысла использовать конкурентную структуру данных.

Приведу пример методов реализации интерфейса:

        let set fragment = 
            logFragments.[fragment.GetType()] <- fragment

        member x.OnOperationBegin() =   
            stopwatch.Start()

        member this.Fail e = 
            logLevel <- LogLevel.ErrorLevel
            set <| Exception e

        member this.OnOperationCompleted() = 
            stopwatch.Stop()
            set <| OperationDuration stopwatch.Elapsed

            match tryGet <| ReceivedOn DateTimeOffset.MinValue with
            | Some (ReceivedOn date) -> set <| TotalDuration (DateTimeOffset.UtcNow - date)
            | _ -> ()

            match status with
            | Active ->
                match (logLevel) with
                | LogLevel.DebugLevel   -> logger.Debug(message())
                | LogLevel.InfoLevel    -> logger.Info(message())
                | LogLevel.WarningLevel -> logger.Warning(message())
                | LogLevel.ErrorLevel   -> logger.Error(message())
                | x                     -> failwith(sprintf "Log level %s is not supported" <| string x)
            | Supressed -> ()

Самое интересное это логика работы OnOperationCompleted():

  • Останавливаем таймер и пишем прошедшее время в логгер через фрагмент OperationDuration
  • Если у нас есть в логе фрагмент ReceivedOn (который в моей модели означает время прихода сообщения в сервис ВООБЩЕ), то пишем в лог общее время нахождения сообщения в сервисе через TotalDuration
  • Если логгер не был выключен (через метод Supress()), то пишем инфу в Akka логгер через метод message(), который я не привёл, но он просто как-то собирает все Fragments в строку с учётом типов сообщений

Создаём обёртку для функциональных акторов


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

Чего мы хотим добиться? Для начала мы хотим залогировать:

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

Сделать всё выше перечисленное нам помогут Linq.Expressions. Как сделать это через QuotationExpressions из F# я не знаю, т.к. не нашёл простого способа скомпилировать их. Буду рад, если кто предложит варианты.

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

type Expr<'T,'TLog when 'TLog :> ILogBuilder> = Expression, 'T, 'TLog>>

type Wrap =
    static member Handler(e:  Expression, 'T, #ILogBuilder>>) = e

let toExprName (expr: Expr<_,_>) = 
    match expr.Body with
    | :? MethodCallExpression as methodCall -> methodCall.Method.Name
    | x -> x.ToString()

Expr — это выражение, которое содержит Action от мейлбокса (на случай если надо наплодить детей, остановить себя или детей и вообще), обрабатываемого сообщения и логгера (если надо делать с ним какие-то особые действия).

Wrap.Handler(Expr) — позволит нам писать в него обычные F# выражения вида «fun mb msg log -> ()», а на выходе получать Linq.Expressions.

toExprName — метод, который получает название метода, если выражение является вызовом метода (MethodCallExpression) или просто пытается привести наше выражение к строке.
Для выражения вида «fun mb msg log -> handleMsg msg» — toExprName вернёт «handleMsg».

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

let loggerActor<'TMsg> (handler: Expr<'TMsg,_>) (mailbox: Actor<'TMsg>) =
    let exprName = handler |> toExprName
    let metrics  = mailbox.Context.GetMetricsProducer (ContextName exprName)
    let logger   = mailbox.Log.Value

На вход мы будем подавать только handler, т.к. mailbox потом докинет сама Akka (partial application).

С помощью написанного нами расширения к ActorSystem получим экземпляр IMetricsAdapter в значение metrics. Так же получим логгер Akka в значение logger.

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

    let errorMeter      = metrics.CreateMeter   (MetricName "Error Rate",              Errors)
    let instanceCounter = metrics.CreateCounter (MetricName "Instances Counter",       Items)
    let messagesMeter   = metrics.CreateMeter   (MetricName "Message Processing Rate", Items)
    let operationsTimer = metrics.CreateTimer   (MetricName "Operation Durations",     Requests, MilliSeconds, MilliSeconds)

    instanceCounter.Increment()
    mailbox.Defer instanceCounter.Decrement

Как видите, мы увеличиваем значение instanceCounter и закладываем уменьшение этого счётчика на остановке актора.

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

В этом куске кода мы кидаем в логгер название операции, вызываем завершение её логирования, закидываем в метрику таймеров время операции, а в метрику мессаджей — тип сообщения:

    let completeOperation (msgType: Type) (logger: #ILogBuilder) =
        logger.Set (OperationName exprName)
        logger.OnOperationCompleted()

        match logger.TryGet(OperationDuration TimeSpan.Zero) with
        | Some(OperationDuration dur) -> 
            operationsTimer.Measure(Amount (int64 dur.TotalMilliseconds), Item (Some exprName))
        | _ -> ()

        messagesMeter.Mark(Item (Some msgType.Name))

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

    let registerExn (msgType: Type) e (logger: #ILogBuilder) = 
        errorMeter.Mark(Item (Some msgType.Name))
        logger.Fail e

Осталось немного чтобы всё заработало. Свяжем всё вместе через обёртку над обработчиком:

    let wrapHandler handler mb (logBuilder: unit -> #ILogBuilder) =
        let innherHandler mb msg  =
            let logger = logBuilder()
            let msgType = msg.GetType()
            logger.Set (MessageType msgType)
            try
                try
                    logger.OnOperationBegin()
                    handler mb msg logger
                with
                | e -> registerExn msgType e logger; reraise()
            finally
                completeOperation msgType logger
        innherHandler mb

wrapHandler обладает сложной сигнатурой. На языке C# это выглядело бы так:

Func wrapHandler(
    Func handler, 
    TMailbox mb, 
    Func logBuilder) 
where TLogBuilder: ILogBuilder

При этом на все остальные типы никаких ограничений нет.

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

  • Начинаем логирование операции
  • Выполняем операцию
  • В случае возникновения необработанного в handler исключения, логируем его и пробрасываем выше (родителю данного актора)
  • Завершаем логирование

Для преобразования Expression в Action и подачи в каждое действие актора нового экземпляра логгера сделаем ещё одну вспомогательную функцию:

    let wrapExpr (expr: Expr<_,_>) mailbox logger = 
        let action = expr.Compile()
        wrapHandler 
            (fun mb msg log -> action.Invoke(mailbox, msg, log))
            mailbox
            (fun () -> new LogBuilder(logger))

В ней мы как раз получаем наш Expression, компилим его и подаём в wrapHandler выше, вместе с мейлбоксом и функцией на получение нового LogBuilder().

Сигнатура данного метода так же непростая. На C# это выглядело бы так:

Action wrapExpr(
    Expr expr, 
    Actor mb, 
    ILoggingAdapterlogger)

Ограничений на TMsg всё ещё нет.

Осталось только создать рекурсивную функцию :)
    let rec loop() = 
        actor {
            let! msg = mailbox.Receive()
            wrapExpr handler mailbox akkaLogger msg
            return! loop()
        }
    loop()

Вот это выражение «wrapExpr handler mailbox akkaLogger», как видно из объяснения выше, возвращает Action, т.е. метод, в который можно подать любой тип на вход и получить unit (void в c#).

Дописав в конце выражения «msg» мы кидаем в эту функцию аргумент msg и выполняем наше действие над полученным сообщением.

На этом мы закончили с кодированием нашей задачи и перейдём к примерам!

Как всё это запустить?


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

Простой случай может выглядеть так:

type ActorMessages =
    | Wait of int
    | Stop

let waitProcess = function
    | Wait d -> Async.Sleep d |> Async.RunSynchronously
    | Stop   -> ()


А чтобы завернуть эту функцию в loggerActor и получить все плюшки ради которых мы так старались можно написать так:

let spawnWaitWorker() =
    loggerActor <| Wrap.Handler(fun mb msg log -> waitProcess msg)

let waitWorker = spawn system "worker-wait"  <| spawnWaitWorker()
waitWorker 

Если у вас сложная логика и нужен доступ к мейлбоксу и логеру:

let failOrStopProcess (mailbox: Actor<_>) msg (log: ILogBuilder) =
    try
        match msg with
        | Wait d -> failwith "can't wait!"
        | Stop   -> mailbox.Context.Stop mailbox.Self
    with
        | e -> log.Fail e

let spawnFailOrStopWorker() =
    loggerActor <| Wrap.Handler(fun mb msg log -> failOrStopProcess mb msg log)

let failOrStopWorker = spawn system "worker-vocal"  <| spawnFailOrStopWorker()
failOrStopWorker 

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

Program.fs
open Akka.FSharp
open SimpleInjector
open App.Metrics;
open Microsoft.Extensions.DependencyInjection
open SimpleInjector.Integration.WebApi
open System.Reflection
open System
open Metrics.MetricActors
open ExampleActors

let createSystem = 
    let configStr = System.IO.File.ReadAllText("system.json")
    System.create "system-for-metrics" (Configuration.parse(configStr))

let createMetricActors system container = 
    let dependencyResolver = new SimpleInjectorWebApiDependencyResolver(container)
    let apiConfig = 
        { new IMetricApiConfig with
            member x.Host = "localhost"
            member x.Port = 10001 }
    
    let metricsReaderSpawner = createReader apiConfig dependencyResolver
    let metricsReader = spawn system "metrics-reader" metricsReaderSpawner

    let metricsRecorderSpawner = createRecorder (container.GetInstance())
    let metricsRecorder = spawn system "metrics-recorder" metricsRecorderSpawner
    ()

type Container with
    member x.AddMetrics() = 
        let serviceCollection  = new ServiceCollection()
        let entryAssemblyName  = Assembly.GetEntryAssembly().GetName()
        let metricsHostBuilder = serviceCollection.AddMetrics(entryAssemblyName)

        serviceCollection.AddLogging() |> ignore
        let provider = serviceCollection.BuildServiceProvider()

        x.Register(fun () -> provider.GetRequiredService())

[]
let main argv = 
    let container = new Container()
    let system = createSystem

    container.RegisterSingleton system
    container.AddMetrics()
    container.Verify()

    createMetricActors system container

    let waitWorker1      = spawn system "worker-wait1"  <| spawnWaitWorker()
    let waitWorker2      = spawn system "worker-wait2"  <| spawnWaitWorker()
    let waitWorker3      = spawn system "worker-wait3"  <| spawnWaitWorker()
    let waitWorker4      = spawn system "worker-wait4"  <| spawnWaitWorker()

    let failWorker       = spawn system "worker-fail"   <| spawnFailWorker()
    let waitOrStopWorker = spawn system "worker-silent" <| spawnWaitOrStopWorker()
    let failOrStopWorker = spawn system "worker-vocal"  <| spawnFailOrStopWorker()

    waitWorker1  ignore

    0


Самое главное — метрики!

Если во время работы зайти по ссылке localhost:10001/metrics, увидим достаточно большой json, в котором будет много информации. Приведу кусок для функции waitProcess:

Скрытый текст
{
      "Context": "waitProcess",
      "Counters": [
        {
          "Name": "Instances Counter",
          "Unit": "items",
          "Count": 4
        }
      ],
      "Meters": [
        {
          "Name": "Message Processing Rate",
          "Unit": "items",
          "Count": 4,
          "FifteenMinuteRate": 35.668327519112893,
          "FiveMinuteRate": 35.01484385742755,
          "Items": [
            {
              "Count": 4,
              "FifteenMinuteRate": 0.0,
              "FiveMinuteRate": 0.0,
              "Item": "Wait",
              "MeanRate": 13.082620551464204,
              "OneMinuteRate": 0.0,
              "Percent": 100.0
            }
          ],
          "MeanRate": 13.082613248856632,
          "OneMinuteRate": 31.356094372926623,
          "RateUnit": "min"
        }
      ],
      "Timers": [
        {
          "Name": "Operation Durations",
          "Unit": "req",
          "ActiveSessions": 0,
          "Count": 4,
          "DurationUnit": "ms",
          "Histogram": {
            "LastUserValue": "waitProcess",
            "LastValue": 8001.0,
            "Max": 8001.0,
            "MaxUserValue": "waitProcess",
            "Mean": 3927.1639786164278,
            "Median": 5021.0,
            "Min": 1078.0,
            "MinUserValue": "waitProcess",
            "Percentile75": 8001.0,
            "Percentile95": 8001.0,
            "Percentile98": 8001.0,
            "Percentile99": 8001.0,
            "Percentile999": 8001.0,
            "SampleSize": 4,
            "StdDev": 2932.0567172627871,
            "Sum": 15190.0
          },
          "Rate": {
            "FifteenMinuteRate": 0.00059447212531854826,
            "FiveMinuteRate": 0.00058358073095712587,
            "MeanRate": 0.00021824579927905906,
            "OneMinuteRate": 0.00052260157288211038
          }
        }
      ]
    }


Из него можно узнать, что:

  • У нас сейчас активно 4 инстанса workProcess
  • Они обработали 4 сообщения типа Wait
  • Медианное время обработки сообщений 5021 мс

В консоли будет примерно следующее.

Заключение


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

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

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

Репозиторий с примером лежит тут.

Спасибо за внимание!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334296/


Метки:  

Интернет в поездках. Анализ зарубежных SIM-карт

Четверг, 27 Июля 2017 г. 18:46 + в цитатник

[Из песочницы] SOAP и REST сервисы с помощью Python-библиотеки Spyne

Четверг, 27 Июля 2017 г. 18:13 + в цитатник

Знакомство с библиотекой Spyne


В данной статье я хочу рассказать о замечательной Python-библиотеке Spyne.
Мое знакомство с Spyne началось в тот момент, когда передо мной поставили задачу написать Веб-сервис, который будет принимать и отдавать запросы через SOAP-протокол. Немного погуглив я наткнулся на Spyne, которая является форком библиотеки soaplib. А еще я был удивлен, насколько мало русскоязычной информации встречается о данной библиотеке.

С помощью Spyne можно писать веб-сервисы, которые умеют работать с SOAP, JSON, YAML, а написанный скрипт можно запустить через mod_wsgi Apache. Итак, давайте рассмотрим несколько примеров, напишем работающие скрипты и настроим так, чтобы скрипты работали через apache.

1. SOAP-сервис


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

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

Теперь переходим непосредственно к разработке.

Устанавливаем необходимые библиотеки: «pytz», «spyne», а также «yandex_translate». Библиотеки ставятся очень легко через pip.

Код приложения выглядит следующим образом:

from spyne import Application, rpc, ServiceBase, Unicode
from lxml import etree
from spyne.protocol.soap import Soap11
from spyne.protocol.json import JsonDocument
from spyne.server.wsgi import WsgiApplication
from yandex_translate import YandexTranslate

class Soap(ServiceBase):
    @rpc(Unicode, _returns=Unicode)
    def Insoap(ctx, words):
        print(etree.tostring(ctx.in_document))
        translate = YandexTranslate('trnsl.1.1.201somesymbols')
        tr = translate.translate(words, 'en')
        tr_answer = tr['text'][0]
        return tr_answer
app = Application([Soap], tns='Translator',
                          in_protocol=Soap11(validator='lxml'),
                         out_protocol=Soap11()
application = WsgiApplication(app)
if __name__ == '__main__':
     from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8000, application)
    server.serve_forever()

Разберем код:

После импортирования необходимых библиотек, мы создали класс «Soap» с аргументом «ServiceBase». Декоратор "@rpc(Unicode, _returns=Unicode)" определяет тип входящих аргументов («Unicode») и исходящих ответов ("_returns=Unicode"). Список доступных типов аргументов можно посмотреть в официальной документации.. Далее создается метод «Insoap» с аргументами «ctx» и «words». Аргумент «ctx» очень важен, так как в нем содержится много информации о входящих запросах. Строка «print(etree.tostring(ctx.in_document))» выводит на экран входящий xml-запрос, в таком виде, в каком нам его отправил пользователь. В некоторых моментах это может быть важно. Например, мне в ходе написания веб-сервиса нужно было вытащить входящий xml-запрос и записать в базу данных. Но как вытащить этот xml-запрос не упомянуто в официальной документации Spyne. Burak Arslan (автор Spyne) порекомендовал смотреть в сторону библиотеки lxml. Только после этого я нашел ответ и результат видите в данноме скрипте. Далее наш метод обращается в Яндекс-переводчик и возвращает клиенту полученный от Яндекс-переводчика результат.

Переменная «app» определяет настройки нашего веб-сервиса: «Application([Soap]» — указывается, какой класс инициализируется (их может быть несколько), параметры «in_protocol» и «out_protocol» определяет тип входящих и исходящих запросов, в нашем случае это SOAP v1.1.

Строкой «application = WsgiApplication(app)» определяется, чтобы наш скрипт мог работать через wsgi.

Важно! имя переменного обязательно должен быть «application», чтобы наше приложение мог работать через apache с помощью mod_wsgi.
Последующие строки кода инициализирует и запускает Веб-сервер по порту 8000.

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

Тело xml-запроса
schemas.xmlsoap.org/soap/envelope» xmlns:tran=«Translator»>



Тестируем наше приложение



Наш веб-сервис дал следующий ответ:

Ответ от сервера
schemas.xmlsoap.org/soap/envelope» xmlns:tns=«Translator»>


Test our app



Все просто, не правда ли?

2. REST-сервис


Предположим, что теперь у нас поменялось тех. задание, и нужно сделать веб-сервис, который работает через JSON. Что делать? Переписывать наш сервис на другом фреймворке, например Django Rest Framework или Flask? или можно обойтись меньшими усилиями? Да, можно! И нужно!

Библиотека Spyne нам в помошь.

Все что потребуется поменять в нашем приложении, это переменную «app» привести к следующему виду:

app = Application([Soap], tns='Translator',
                          in_protocol=JsonDocument(validator='soft'),
                          out_protocol=JsonDocument())

Запускаем наш веб-сервис и тестируемся.

Наш JSON-запрос выглядит так:

Тело JSON-запроса
{«Insoap»: {«words»:«тестируем наш веб-сервис. Используем JSON»}}

Веб сервер вернул следующий ответ:

Ответ веб-сервера
«test our web service. Use JSON»

3. Вывод в продакшн


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

Строки для удаления
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('0.0.0.0', 8000, application)
server.serve_forever()

Ура! Наш веб-сервис готов к использованию в эксплуатации.

P.S. о дополнительных возможностях Spyne (а их немало) всегда можно ознакомиться на официальном сайте, чего я вам очень рекомендую.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334290/


Метки:  

[recovery mode] Призрак локомотива или биржевой рынок через призму корреляций

Четверг, 27 Июля 2017 г. 18:08 + в цитатник
В этой статье будет продемонстрирована техника обработки информации по биржевым котировкам с помощью пакета pandas (python), а также изучены некоторые «мифы и легенды» биржевой торговли посредством применения методов математической статистики. Попутно кратко рассмотрим особенности использования библиотеки plotly.
Одной из легенд трейдеров является понятие «локомотива». Описать ее можно следующим образом: есть бумаги «ведущие» и есть бумаги «ведомые». Если поверить в существование подобной закономерности, то можно «предсказывать» будущие движения финансового инструмента по движению «локомотивов» («ведущих» бумаг). Так ли это? Есть ли под этим основания?
image

Сформулируем задачу. Есть финансовые инструменты: A, B, C, D; есть характеристика времени — t. Существуют ли связи между движениями этих инструментов:

At и Bt-1; At и Ct-1; At и Dt-1
Вt и Сt-1; Bt и Dt-1; Bt и At-1
Ct и Dt-1; Ct и At-1; Ct и Bt-1
Dt и At-1; Dt и Bt-1; Dt и Bt-1

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

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

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

Предварительно, получим котировки с сервера компании «ФИНАМ». Будем брать «часовики» за период с 01.01.2017 по 13.07.2017. Немного модифицировав функцию, упомянутую здесь, получим:

# -*- coding: utf-8 -*-
"""
@author: optimusqp
"""
import os
import urllib
import pandas as pd
import time 
import codecs
from datetime import datetime, date
from pandas.io.common import EmptyDataError

e='.csv';
p='7';
yf='2017';
yt='2017';
month_start='01';
day_start='01';
month_end='07';
day_end='13';

year_start=yf[2:];
year_end=yt[2:];

mf=(int(month_start.replace('0','')))-1;
mt=(int(month_end.replace('0','')))-1;

df=(int(day_start.replace('0','')));
dt=(int(day_end.replace('0','')));

dtf='1';
tmf='1';
MSOR='1';
mstimever='0'
sep='1';
sep2='1';
datf='5';
at='1';


def quotes_finam_optimusqp(data,year_start,month_start,day_start,year_end,month_end,day_end,e,df,mf,yf,dt,mt,yt,p,dtf,tmf,MSOR,mstimever,sep,sep2,datf,at):
    
    temp_name_file='id,company\n';
    incrim=1;
    
    for index, row in data.iterrows():    
        page = urllib.urlopen('http://export.finam.ru/'+str(row['code'])+'_'+str(year_start)+str(month_start)+str(day_start)+'_'+str(year_end)+str(month_end)+str(day_end)+str(e)+'?market='+str(row['id_exchange_2'])+'&em='+str(row['em'])+'&code='+str(row['code'])+'&apply=0&df='+str(df)+'&mf='+str(mf)+'&yf='+str(yf)+'&from='+str(day_start)+'.'+str(month_start)+'.'+str(yf)+'&dt='+str(dt)+'&mt='+str(mt)+'&yt='+str(yt)+'&to='+str(day_end)+'.'+str(month_end)+'.'+str(yt)+'&p='+str(p)+'&f='+str(row['code'])+'_'+str(year_start)+str(month_start)+str(day_start)+'_'+str(year_end)+str(month_end)+str(day_end)+'&e='+str(e)+'&cn='+str(row['code'])+'&dtf='+str(dtf)+'&tmf='+str(tmf)+'&MSOR='+str(MSOR)+'&mstimever='+str(mstimever)+'&sep='+str(sep)+'&sep2='+str(sep2)+'&datf='+str(datf)+'&at='+str(at))
        print('http://export.finam.ru/'+str(row['code'])+'_'+str(year_start)+str(month_start)+str(day_start)+'_'+str(year_end)+str(month_end)+str(day_end)+str(e)+'?market='+str(row['id_exchange_2'])+'&em='+str(row['em'])+'&code='+str(row['code'])+'&apply=0&df='+str(df)+'&mf='+str(mf)+'&yf='+str(yf)+'&from='+str(day_start)+'.'+str(month_start)+'.'+str(yf)+'&dt='+str(dt)+'&mt='+str(mt)+'&yt='+str(yt)+'&to='+str(day_end)+'.'+str(month_end)+'.'+str(yt)+'&p='+str(p)+'&f='+str(row['code'])+'_'+str(year_start)+str(month_start)+str(day_start)+'_'+str(year_end)+str(month_end)+str(day_end)+'&e='+str(e)+'&cn='+str(row['code'])+'&dtf='+str(dtf)+'&tmf='+str(tmf)+'&MSOR='+str(MSOR)+'&mstimever='+str(mstimever)+'&sep='+str(sep)+'&sep2='+str(sep2)+'&datf='+str(datf)+'&at='+str(at))
        print('code: '+str(row['code']))
        #Формируем перечень файлов в которых будут содержаться котировки. 
	#Один файл - один торгуемый инструмент 
        file = codecs.open(str(row['code'])+"_"+"0"+".csv", "w", "utf-8")

        content = page.read()
        file.write(content)
        file.close()
        
        temp_name_file = temp_name_file + (str(incrim) + "," + str(row['code'])+"\n")
        incrim+=1
        
        time.sleep(2)
    #Формируем файл в котором содержатся code заголовки торгуемых инструментов, 
    #из расчета одна строка - один заголовок.
    write_file = "name_file_data.csv"
    with open(write_file, "w") as output:
        for line in temp_name_file:
            output.write(line)

#Перед запуском quotes_finam_optimusqp в распоряжении должен быть 
#файл параметров function_parameters.csv
#___http://optimusqp.ru/articles/articles_1/function_parameters.csv
data_all = pd.read_csv('function_parameters.csv', index_col='id')
#Сузим область нашей выборки до тех инструментов, которые торгуются 
#исключительно на id_exchange_2 == 1, т.е. МосБиржа акции
data = data_all[data_all['id_exchange_2']==1]
quotes_finam_optimusqp(data,year_start,month_start,day_start,year_end,month_end,day_end,e,df,mf,yf,dt,mt,yt,p,dtf,tmf,MSOR,mstimever,sep,sep2,datf,at)



В результате имеем перечень файлов типа A_0.csv:

image

Далее определяем движения финансовых инструментов At-At-1, удаляем столбцы OPEN, HIGH, LOW, VOL, формируем единый столбец DATETIME. Произведем отсев тех финансовых инструментов, которые имеют слишком мало данных для анализа (торгуются недавно, нестабильно либо обладают малой ликвидностью).

#Зачем мы записываем файлы, и потом их считываем тут же? 
#Все просто - ради наглядности процесса.
name_file_data = pd.read_csv('name_file_data.csv', index_col='id')

incrim=1;
#Введем показатель how_work_days - он нужен нам затем, чтобы не рассматривать 
#неликвидные инструменты, либо инструменты с малой продолжительностью торговли
#на рынке
temp_string_in_file='id,how_work_days\n';    
for index, row1 in name_file_data.iterrows():  
    
    how_string_in_file = 0
    #открываем файл с котировкой по инструменту, в соответствие с имеющейся маской
    name_file=row1['company']+"_"+"0"+".csv"
    #а существует ли файл котировок? проверка файла на существование
    if os.path.exists(name_file):

        folder_size = os.path.getsize(name_file)
        #если файл котировок имеет нулевой вес - следовательно он пуст, и мы можем его просто удалить
        if folder_size>0:
            
            temp_quotes_data=pd.read_csv(name_file, delimiter=',')
            #если файл котировок пуст, в соответствие с исключением типа EmptyDataError 
	    #его также удаляем
            try:
                #здесь будем рассматривать цены закрытия (CLOSE); 
		#остальные столбцы можем просто удалить
                quotes_data = temp_quotes_data.drop(['', '', '', ''], axis=1)
                
                #Определяем - какое количество строк в файле котировок
                how_string_in_file = len(quotes_data.index)
                #если файл котировок имеет количество строк менее чем 1 100, 
		#удаляем его; причина отсев неликвидных инструментов 
                if how_string_in_file>1100:
                    #формируем построчные записи для файла days_data.csv, в котором 
		    #определяется количество периодов в течение которых  торговался 
		    #данный инструмент
                    temp_string_in_file = temp_string_in_file + (str(incrim) + "," + str(how_string_in_file)+"\n")
                    incrim+=1
                    
                    quotes_data['DATE_str']=quotes_data[''].astype(basestring)
                    quotes_data['TIME_str']=quotes_data['


В результате получим перечень файлов типа A_1.csv. Всего 91 файл:

image

«Сливаем» в один файл securities.csv все движения всех финансовых инструментов, удалив первую пустую строку.

import glob

allFiles = glob.glob("*_1.csv")
frame = pd.DataFrame()
list_ = []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    list_.append(df)

dfff = reduce(lambda df1,df2: pd.merge(df1,df2,on='DATETIME'), list_)

quotes_data = dfff.drop(['Unnamed: 0_x', 'Unnamed: 0_y', 'Unnamed: 0'], axis=1)

quotes_data.to_csv("securities.csv", sep=',', encoding='utf-8')

quotes_data = quotes_data.drop(['DATETIME'], axis=1)
number_columns=len(quotes_data.columns)
columns_name_0 = quotes_data.columns
columns_name_1 = quotes_data.columns


На данном этапе происходит довольно интересная операция объединения записей по столбцу DATETIME (pd.merge). Этот порядок объединения отбрасывает те даты, в которые не торговалась хотя бы одна из 91 ценной бумаги. То есть объединение основано на полном исключении пустых данных. В результате:

image

В файле securities.csv, оперируя данными в цикле сдвигаем все строки, кроме текущей. Таким образом, напротив At оказываются значения Bt-1, Ct-1, Dt-1.


incrim=0
quotes_data_w=quotes_data.shift(1)
for column in columns_name_0:
    
    quotes_data_w[column]=quotes_data_w[column].shift(-1)
    quotes_data_w.to_csv("securities_"+column+".csv", sep=',', encoding='utf-8')  
    #Вернем на место сдвинутые строки
    quotes_data_w[column]=quotes_data_w[column].shift(1)
    incrim+=1


Данные будут выглядеть так:

image

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

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

image
Чем интересен данный сервис? Во-первых, возможностью графической интерпретации данных. Во-вторых, набором статистических методов-тестов; в частности, возможностью проведения тестов на соответствие выборки нормальному (Гаусса) распределению. Будем использовать следующие тесты: критерий Шапиро-Уилка (Shapiro-Wilk), критерий Колмогорова-Смирнова (Kolmogorov-Smirnov) см. правила работы здесь .

Сервис, связанный с plotly, достоин самых высоких похвал. Тутор по настройке работы plotly на Linux можно посмотреть, на plot.ly, а под Windows, например, здесь. Но на plotly есть и странности. И вопрос здесь не много ни мало в описании логики работы теста. В примерах к применению дается таблица:

image

Разработчик дает следующий комментарий:

Since our p-value is much less than our Test Statistic, we have good evidence to not reject the null hypothesis at the 0.05 significance level.

Перевод:

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

Таким образом, согласно данной рекомендации мы не вправе отказаться от гипотезы о нормальности распределения по рассматриваемой выборке! Но… данный совет не является верным.

Итак, вспомним — что же такое p-value? Эта величина необходима для проведения тестирования статистических гипотез. Ее можно понимать как вероятность ошибки если мы отклоним нулевую гипотезу. Под нулевой гипотезой в критерии Шапиро-Уилка H0, напомню, имеется ввиду то, что «случайная величина X распределена нормально». Если мы отклоним H0 при чрезвычайно малом значении p-value (близком к нулю), то мы не ошибемся. Не ошибемся, исключив предположение о нормальности распределения. Вообще уровень значимости в тестах plotly на нормальность составляет 0.05 и принятие либо не принятие нулевой гипотезы должно основываться на сопоставлении данного значения p-значению. Превышение порога уровня значимости величиной p-value говорит о том, что нельзя отклонять гипотезу о нормальности распределения тестируемой выборки.

image

А… вдруг и… сами тесты на нормальность распределения на plotly не корректны? Забегая вперед скажу — все в порядке. Мною были сгенерирированы два вида рандомных выборок – гауссовская и парето; эти массивы данных последовательно отправляем на plot.ly. Тестируем. Характер распределений, сильно отличается и очевидно, что Парето выборки не должны пройти тест на «нормальность».

image

Код тестов:

import pandas as pd
import matplotlib.pyplot as plt

import plotly.plotly as py
import plotly.graph_objs as go
from plotly.tools import FigureFactory as FF

import numpy as np
from scipy import stats, optimize, interpolate


def Normality_Test(L):   
    
    x = L
    
    shapiro_results = scipy.stats.shapiro(x)
    
    matrix_sw = [
        ['', 'DF', 'Test Statistic', 'p-value'],
        ['Sample Data', len(x) - 1, shapiro_results[0], shapiro_results[1]]
    ]
    
    shapiro_table = FF.create_table(matrix_sw, index=True)
    
    py.iplot(shapiro_table, filename='pareto_file')   
    #py.iplot(shapiro_table, filename='normal_file')   

#L =np.random.normal(115.0, 10, 860)
L =np.random.pareto(3,50)
Normality_Test(L)


Результаты обработки можно посмотреть в своем профайле на plot.ly/organize/home
Итак, вот некоторые результаты тестов Шапиро-Уилка:

Для Парето распределения

Первый тест

image

Второй тест

image

Для нормального (Гаусса) распределения

Первый тест

image

Второй тест

image

Итак, алгоритм теста работает корректно. Однако советы по использованию теста не совсем, мягко говоря, верны. Мораль в следующем: будьте бдительны! Возле правильно написанного инструмента не всегда лежит правильно написанная инструкция!
Перейдем к тестированию движения финансовых инструментов на нормальность (Гаусса) распределения с применением библиотеки plotly. Мною были получены следующие результаты:

image

По остальным финансовым инструментам схожая картина. Следовательно – исключаем предположение о нормальности распределения в движении рассматриваемых финансовых инструментов. Код самого теста:


allFiles = glob.glob("*_1.csv")

    
def Shapiro(df,temp_header):    

    df=df.drop(df.index[0])
    x = df[temp_header].tolist()
    
    shapiro_results = scipy.stats.shapiro(x)
    
    matrix_sw = [
        ['', 'DF', 'Test Statistic', 'p-value'],
        ['Sample Data', len(x) - 1, shapiro_results[0], shapiro_results[1]]
    ]
    
    shapiro_table = FF.create_table(matrix_sw, index=True)
    py.iplot(shapiro_table, filename='shapiro-table_'+temp_header)   


def Kolmogorov_Smirnov(df,temp_header):    

    df=df.drop(df.index[0])
    x = df[temp_header].tolist()

    ks_results = scipy.stats.kstest(x, cdf='norm')
    
    matrix_ks = [
        ['', 'DF', 'Test Statistic', 'p-value'],
        ['Sample Data', len(x) - 1, ks_results[0], ks_results[1]]
    ]
    
    ks_table = FF.create_table(matrix_ks, index=True)
    py.iplot(ks_table, filename='ks-table_'+temp_header)


frame = pd.DataFrame()
list_ = []

for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    print(file_)
    columns = df.columns

    temp_header = columns[2]
    Shapiro(df,temp_header)
    time.sleep(3)
    Kolmogorov_Smirnov(df,temp_header)
    time.sleep(3)



Поскольку мы не можем полагаться на нормальность (Гаусса) распределения – следовательно, при расчете корреляций необходимо выбрать непараметрический инструмент, а именно корреляцию Спирмена (Spearman rank correlation coefficient). После того как определились с видом корреляции можно перейти непосредственно к ее расчетам:


incrim=0
for column0 in columns_name_1:
    
    df000 = pd.read_csv('securities_'+column0+".csv",index_col=None, header=0)
    #Удаляем первую строку с пустотами
    df000=df000.drop(df000.index[0])
    df000 = df000.drop(['Unnamed: 0'], axis=1)
    #Поочередно рассчитываем корреляцию Спирмена для каждого 
    #инструмента по отношению к прошлым периодам других ценных бумаг
    corr_spr=df000.corr('spearman')
    #Отсортируем строки в полученном файле корреляций от 
	#больших значений к меньшим
    corr_spr=corr_spr.sort_values([column0], ascending=False)
    #Сохраняем как отдельный DataFrame
    corr_spr_temp=corr_spr[column0]
    corr_spr_temp.to_csv("corr_"+column0+".csv", sep=',', encoding='utf-8')  
    incrim+=1



Получаем файл с корреляциями по текущей бумаге (типа corr_A.csv) и прошлым периодом по иным ценным бумагам (B, C, D их всего 90), для этого удаляем первую строку с пустыми значениями в файле типа securities_A.csv; Рассчитываем корреляции других ценных бумаг по отношению к текущей. Сортируем столбец корреляций и именований к ним. Сохраняем столбец корреляций по текущей ценной бумаге как отдельный DataFrame.

image

Поочередно каждый из файлов с корреляциями типа corr_A.csv «сливаем» в один общий файл – _quotes_data_end.csv.csv. Строки в данном файле обезличены. Можно наблюдать лишь величины отсортированных корреляций.


incrim=0
all_corr_Files = glob.glob("corr_*.csv")
list_corr = []
quotes_data_end = pd.DataFrame()
for file_corr in all_corr_Files:
    df_corr = pd.read_csv(file_corr,index_col=None, header=0)
    columns_corr = df_corr.columns
    temp_header = columns_corr[0]
    quotes_data_end[str(temp_header)]=df_corr.iloc[:,1]
    incrim+=1
    
quotes_data_end.to_csv("_quotes_data_end.csv", sep=',', encoding='utf-8')  
plt.figure();
quotes_data_end.plot(); 



image

По полученным данным _quotes_data_end.csv строим график:

image

Уровень корреляций даже на крайних областях не высок. Основная масса корреляционных значений находится в пределах -0.15;0.15. Как таковых ценных бумаг, которые бы «вели» какие-либо другие финансовые инструменты в рамках рассматриваемого периода (7,5 мес) и на данном таймфрейме («часовиках») нет. Напомню, что в нашем распоряжении данные по 91 ценной бумаге. Но… если попытаться провести обработку тех же «часовиков» за более короткий период? По выборке длительностью в 1 месяц получим следующий график:

image

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

Каков же характер динамики изменения корреляций; того как это происходит и чем сопровождается? Но… это тема для продолжения.

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

https://habrahabr.ru/post/334288/


Метки:  

Метаклассы в C++

Четверг, 27 Июля 2017 г. 17:38 + в цитатник
В этой статье мы поговорим о новом предложенном расширении языка С++ — метаклассах. Герб Саттер с коллегами работал над этим предложением около 2 лет и, наконец, этим летом представил его общественности.

Итак, что же такое «метакласс» с точки зрения Герба Саттера? Давайте вспомним наш С++ — самый прекрасный в мире язык программирования, в котором, однако, веками десятилетиями существуют примерно одни и те же сущности: переменные, функции, классы. Добавление чего-то фундаментально нового (вроде enum classes) занимает очень много времени и рассчитывать дождаться включения чего-то нужного вам здесь и сейчас в стандарт — не приходится. А ведь кое-чего и правда не хватает. Например, у нас всё ещё нет (да, наверное, и не будет) интерфейсов как таковых (приходится эмулировать их абстрактными классами с чисто виртуальными методами). Нет properties в полном их понимании, нет даже value-типов (чего-то такого, что можно было бы определить как набор переменных простых типов и сразу использовать во всяких там контейнерах/сортировках/словарях без определения для них разных там операций сравнения, копирования и хеширования). Да и вообще постоянно чего-то кому-то не хватает. Разработчикам Qt вот не хватает метаданных и кодогенерации, что заставляет их использовать moc. Разработчикам C++/CLI и C++/CX не хватило способов взаимодействия со сборщиком мусора и своими системами типов. Ну и т.д.

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

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

Например, нам хочется иметь в языке классический интерфейс. Что такое «интерфейс»? Например, стандарт языка С# отвечает на этот вопрос на 18 страницах. И с этим есть целый ряд проблем:

  1. Никто их не читает
  2. Компилятор совершенно не гарантированно реализует именно то, что написано в тех 18 страницах текста
  3. У нас нет возможности проверить соответствие работы компилятора и текста на английском языке
  4. Для С++ пришлось бы написать такую же спецификацию и её реализацию в компиляторах (а зная С++ — так ещё и намного более сложную). А дальше см. пункты 1, 2 и 3.

Но, давайте скажем простыми словами, что такое «интерфейс» — это такой именованный набор публичных чисто-виртуальных методов, к которому в то же время не привязаны никакие приватные методы или члены данных. Всё! Да, может я сейчас упустил какую-то мелкую деталь из тех 18 страниц спецификации, но для 99.99% практического кода этого определения хватит. И вот для возможности описания в коде подобных определений и придуманы метаклассы.

Синтаксис ещё на этапе обсуждения, но вот примерно как может быть реализован метакласс «интерфейс»:

$class interface {
  constexpr 
  {
    compiler.require($interface.variables().empty(),
      "Никаких данных-членов в интерфейсах!");
  
    for (auto f : $interface.functions()) 
    {
      compiler.require(!f.is_copy() && !f.is_move(),
        "Интерфейсы нельзя копировать или перемещать; используйте"
        "virtual clone() вместо этого");

      if (!f.has_access()) 
        f.make_public(); // сделать все методы публичными!

      compiler.require(f.is_public(), // проверить, что удалось
        "interface functions must be public");

    f.make_pure_virtual();  // сделать метод чисто виртуальным
    }
  }

  // наш интерфейс в терминах С++ будет просто базовым классом, 
  // а значит ему нужен виртуальный деструктор
  virtual ~interface() noexcept { } 
};

Код интуитивно понятен — мы объявляем метакласс interface, в котором на этапе компиляции кода (блок constexpr) будут проведены определённые проверки и модификации конечного класса, который будет претендовать на то, чтобы считаться интерфейсом.

Применять это дело теперь можно вот так:

interface Shape 
{
  int area() const;
  void scale_by(double factor);
};

Правда, очень похоже на C# или Java? При компиляции компилятор применит к Shape метакласс interface, что на выходе даст нам класс:

class Shape 
{
public:
  virtual int area() const =0;
  virtual void scale_by(double factor) =0;
  virtual ~Shape() noexcept { };
};

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

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

class Point 
{
  int x = 0;
  int y = 0;
public:
  Point() = default;
  friend bool operator==(const Point& a, const Point& b)
   { return a.x == b.x && a.y == b.y; }
  friend bool operator< (const Point& a, const Point& b)
   { return a.x < b.x || (a.x == b.x && a.y < b.y); }
  friend bool operator!=(const Point& a, const Point& b) { return !(a == b); }
  friend bool operator> (const Point& a, const Point& b) { return b < a; }
  friend bool operator>=(const Point& a, const Point& b) { return !(a < b); }
  friend bool operator<=(const Point& a, const Point& b) { return !(b < a); }
};

Но если на этапе компиляции у нас есть рефлексия, позволяющая перечислять данные-члены и добавлять в класс новые методы — мы можем вынести все эти сравнения в метакласс:

$class ordered {
  constexpr {
    if (! requires(ordered a) { a == a; }) -> 
    {
      friend bool operator == (const ordered& a, const ordered& b) 
      {
        constexpr 
        {
          for (auto o : ordered.variables()) // for each member
            -> { if (!(a.o.name$ == b.(o.name)$)) return false; }
        }
        return true;
      }
    }
    if (! requires(ordered a) { a < a; }) -> 
    {
      friend bool operator < (const ordered& a, const ordered& b) 
      {
        for (auto o : ordered.variables()) -> 
        {
          if (a.o.name$ < b.(o.name)$) return true; 
          if (b.(o.name$) < a.o.name$) return false; )
        }
        return false;
      }
    }
    if (! requires(ordered a) { a != a; })
      -> { friend bool operator != (const ordered& a, const ordered& b) { return !(a == b); } }
    if (! requires(ordered a) { a > a; })
      -> { friend bool operator > (const ordered& a, const ordered& b) { return b < a ; } }
    if (! requires(ordered a) { a <= a; })
      -> { friend bool operator <= (const ordered& a, const ordered& b) { return !(b < a); } }
    if (! requires(ordered a) { a >= a; })
      -> { friend bool operator >= (const ordered& a, const ordered& b) { return !(a < b); } }
  }
};

Что? Выглядит сложно? Да, но вы не будете писать такой метакласс — он будет в стандартной библиотеке или в чём-то типа Boost. У себя в коде вы лишь определите точку, вот так:

ordered Point 
{ 
  int x; 
  int y; 
};

И всё заработает!

Точно так же мы, наконец, сможем добиться того, чтобы вещи типа pair или tuple определялись тривиально:

template
literal_value pair 
{
  T1 first;
  T2 second;
};

Посмотрите, ради интереса, как банальная пара определена сейчас.

От открывающихся возможностей разбегаются глаза:

  • Мы сможем явно определять в коде гайдлайны вроде «базовый класс должен всегда иметь чисто виртуальный деструктор» или "правило трёх"
  • Мы сможем реализовать интерфейсы, value-типы, properties
  • Мы сможем отказаться от Moc в Qt и от кастомных компиляторов для С++/CLI и C++/CX, поскольку все эти вещи можно будет описать метаклассами
  • Мы сможем генерировать код не внешними кодогенераторами и не тупыми дефайнами, а встроенным мощным фреймворком
  • Мы сможем реализовывать на этапе компиляции даже такие сложные проверки, как «во всех ли методах класса, обращающихся к некоторой переменной мы используем критическую секцию, контролирующую доступ к ней?»

Мета-уровень — это очень круто! Правда?

Вот вам ещё на закуску видео, где Герб об этом рассказывает детальнее:




А вот онлайн-компилятор, в котором это всё даже можно попробовать.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334284/


Метки:  

Что делают химики и биологи в ЕРАМ?

Четверг, 27 Июля 2017 г. 17:28 + в цитатник
В ЕРАМ работают не только бизнес-аналитики, разработчики, дизайнеры, инженеры по качеству, но и настоящие ученые: химики и биологи. Они не носят белые халаты и не устраивают зрелищные эксперименты, но делают сложную и важную работу.

Чем занимаются химики и биологи в IT-компании и почему заказчики высоко ценят их работу?



Три года назад в ЕРАМ появилось подразделение Life Sciences, где среди прочих работают химики и биологи. Мы сотрудничаем с 9 из 10 крупнейших фармацевтических компаний в мире. Часть работы по созданию продуктов для них – проведение экзерпции (от англ. to excerpt – делать выдержки): исследователи находят в статьях научные факты и заносят их в специальную базу данных.

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

1. Узнать, из каких веществ можно сделать лекарство


Каждый год в мире проводят тысячи научных исследований разной степени актуальности и полезности. Экзерпторы систематизируют их результаты и заносят информацию в специальные базы данных. Это нужно, чтобы отделить полезные открытия от тех, которыми наука воспользоваться не может – по крайней мере пока. Фармацевтические компании обращаются к таким базам данных, когда отбирают вещества для создания новых лекарств. Иногда открытие «выстреливает» очень нескоро: результаты сегодняшних исследований могут пригодиться фармацевтике и через 10, и через 20 лет.

2. Застраховаться от производства некачественных лекарств


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

3. Правильно подобрать добровольцев для клинических исследований


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

На рынке экзерпции научной литературы конкуренция между заказчиками (в основном фармкомпаниями) и подрядчиками почти одинаково высока. Самые серьезные конкуренты для наших ученых-экзерпторов – специалисты из Филиппин, стран Восточной Европы и Индии.

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

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

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



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

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



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

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

В ЕРАМ разработали онлайн-инструмент для химиков-экзерпторов. Его цель – не заменить ученых, а освободить от рутинных операций. Приложение помогает отделять релевантные статьи от неактуальных для конкретной задачи, определять нужную информацию в релевантной статье, автоматически экзерпировать нужную химическую информацию, исправлять ошибки и опечатки в PDF-документе и оптически распознавать химические структуры. Проект был основан на использовании технологий машинного обучения. Специалисты ЕРАМ разработали алгоритмы, которые ищут и выделяют в текстах химические факты. Их хорошо удается находить в конкретном окружении и в стандартном виде (например, точка плавления всегда обозначается числом или числовым диапазоном). Однако обнаружить сложные факты приложению удается не всегда: статьи пишут живые люди без опоры на конкретный образец.

Всё началось со словаря, созданного в помощь экзерпторам ЕРАМ. Ученые не всегда находят в статьях исчерпывающую информацию и вынуждены дополнять ее фактами из регламентированных заказчиком источников (раньше это были книги и сканы с них). Их объединили в общую базу данных, сделали по ней удобный поиск и добавили другой необходимый экзерпторам функционал – получился словарь. Уже он один ускорил обработку 20% статей в десять раз.

Один из пользователей словаря поделился статистикой. Раньше на обработку статьи, содержащей 200 соединений, у него уходило 5 дней, а со словарем это время сократилось до 4 часов.

В подразделении Life Sciences много людей, средний возраст которых выше, чем у других сотрудников компании. Есть те, кому за 60 или 70, и на первый взгляд их работа может показаться скучной. Но именно благодаря специалистам, которые классифицируют результаты научных исследований, появляются новые лекарства, прививки и иммуностимуляторы. А это, если и не спасает весь мир, то дает многим людям шанс жить дольше.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334282/


Метки:  

[Из песочницы] Криптовалюта с точки зрения гражданского права

Четверг, 27 Июля 2017 г. 17:15 + в цитатник
Все ближе и ближе тот день, когда можно будет однозначно сказать, что криптовалюта заняла прочные позиции во всём мире, и не только в качестве интересной штуковины для криптоманов или очередного высоковолатильного составляющего биржевого портфеля, а действительно как средство расчёта платежа. Вон, Россия уже вовсю собирается свою выпускать.

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

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

Итак, наличные деньги — минус, потому что не наличные, тут всё ясно.

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

ВНИМАНИЕ! ICO – отдельная тема для разговора, в данной статье она не будет подробно разбираться.

Лишь кратко: ICO по своей сути близко к IPO, но в том и отличие, что обязательственные права между организацией/множественными эмитентами, выпустившими криптоединицу, и её условным приобретателем не возникают: не можем же мы, приобретая криптовалюту, требовать от лиц, выпускающих токены, коины и иже с ними, например, обмена её на фиатные деньги в будущем. Никто нам ничего не должен. Конечно, отличие достаточно зыбкое, ситуация в России может измениться в будущем, когда в игру вступят регуляторы (пока лишь в США, на момент написания статьи новости еще не было), но пока что у нас разница присутствует и она довольно существенна с точки зрения права.

Дальше — интереснее: безналичные денежные средства.

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

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

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

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

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

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

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

2) Кроме того золото не чересчур тяжело найти, но всё же его количество в недрах Земли изначально серьёзно ограничено (ограниченость).

3) Наконец, золото стало первым металлом, открытым человеком (новизна).

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

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

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

Хорошо, с категориями плюс-минус ясно, а что насчёт сделок с криптовалютой? Да, я знаю, что де-факто биткоин не является анонимным в чистом виде. Но даже псевдоанонимности для гражданского права достаточно, чтобы по сделке возникли определённые вопросы. С одной стороны, ст.19 ГК гласит, что гражданин приобретает права и обязанности под своим именем, а в случае и порядке, установленном законом, может использовать псевдоним. С другой стороны, это не означает недействительности всех анонимных сделок. Например, для сделок розничной купли-продажи контрагентом является любое лицо, предоставившее документ об уплате покупной цены. Не ходим же мы в магазины, рестораны, музеи по паспорту! Если анонимность не мешает надлежащему исполнению обязательства, то сделка может быть совершена и без раскрытия имён. Конечно же, ни о каких нотариальных сделках в случаях использования криптовалюты речи пока не идёт.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334280/


Метки:  

Немного о SSL-сертификатах: Какой выбрать и как получить

Четверг, 27 Июля 2017 г. 16:56 + в цитатник
20 июля компания Google объявила о том, что браузер Chrome перестает считать доверенными SSL-сертификаты, выданные центром сертификации (CA) WoSign и его дочерним предприятием StartCom. Как пояснили в компании, решение связано с рядом инцидентов, не соответствующим высоким стандартам CA, — в частности, выдаче сертификатов без авторизации со стороны ИТ-гиганта.

Чуть ранее в этом году также стало известно, что организации, ответственные за выдачу сертификатов, должны будут начать учитывать специальные DNS-записи. Эти записи позволят владельцам доменов определять «круг лиц», которым будет дозволено выдавать SSL/TLS-сертификаты для их домена.

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

/ Flickr / montillon.a / CC

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

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

Для проверки лица запросившего сертификат центр сертификации высылает письмо на электронный адрес, связанный с доменным именем (например, admin@yourdomainname.com). Это делается для того, чтобы удостовериться, что лицо, запросившее сертификат, действительно является владельцем доменного имени. Другие варианты верификации — добавление TXT-записи в DNS или размещение на сервере специального файла, который может быть прочитан CA.

Этот вид сертификата самый дешевый и популярный, но не считается полностью безопасным, поскольку содержит информацию лишь о зарегистрированном доменном имени. Поэтому они часто используются для защиты во внутренних сетях или на небольших веб-сайтах. Однако есть и исключения. Например, компании Google не нужно доказывать общественности, что www.google.com принадлежит ИТ-гиганту. Поэтому они используют простые сертификаты с проверкой домена (как и amazon.com).

Второй тип сертификатов носит название Organization Validated, или сертификаты с проверкой организации. Они более надежны, по сравнению с DV, поскольку дополнительно подтверждают регистрационные данные компании-владельца онлайн-ресурса. Всю необходимую информацию компания предоставляет при покупке сертификата, а CA затем напрямую связывается с представителями организации для её подтверждения.

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


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

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

EV-сертификаты полезны, если необходимо «жестко» связать домен с физической организацией. Например, Bank of America и домен bankofamerica.com. В этом случае сертификат с проверкой организации гарантирует, что ресурс действительно принадлежит банку, куда пользователь может физически занести свои деньги — это как минимум удобно для пользователей.

Более того, EV-сертификаты защищают от атак с использованием фишинговых сайтов, как это было в случае с Mountain America Credit Union. Злоумышленники сумели получить легальный SSL-сертификат для копии сайта кредитной организации. Дело в том, что банк использовал доменное имя macu.com, а атакующие использовали имя mountain-america.net и при подаче заявки «вывесили» невинно выглядящий сайт. После получения сертификата сайт был заменен на фишинговый ресурс. EV-сертификаты серьезно затрудняют выполнение подобного «фокуса» — как минимум адрес виновника становится сразу известен.

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

После получения заявки на сертификат, CA присылает бланки с вопросами об организации, которые нужно заполнить и подписать. Свои подписи и печати ставят руководитель компании и главный бухгалтер. После чего отсканированные документы отправляются обратно в центр сертификации, где их проверяют по идентификаторам ЕГРЮЛ и ИНН.

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

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

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

При этом отметим, что также существуют международные агентства, которые могут проверять официальные документы компании и выступать удостоверителем её законного существования. Наиболее известным из таких агентств является Dun & Bradstreet. После проверки организации D&B выдаёт цифровой идентификатор — DUNS (Digital Universal Numbering System) — на который можно ссылаться для подтверждения легальности организации.

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

Цепочки сертификатов


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


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

Если некто «Б» удостоверил личность «А», и вы доверяете «Б», то проблема решена.


Если же вы не знаете «Б», то он может сообщить, что его знает «В».


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

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

Другие виды сертификатов


Напоследок хотелось бы сказать, что помимо обозначенной градации сертификатов — DV, OV, EV — существуют и другие типы сертификатов. Например, сертификаты могут отличаться по количеству доменов, на которые они выдаются. Однодоменные сертификаты (Single Certificate) привязываются к одному домену, указываемому при покупке. Мультидоменные сертификаты (типа Subject Alternative Name, Unified Communications Certificate, Multi Domain Certificate) будут действовать уже для большего числа доменных имен и серверов, но за каждое наименование, включаемое в список сверх обозначенного количества, придется доплачивать отдельно.

Еще существуют поддоменные сертификаты (типа WildCard), которые охватывают все поддомены указанного при регистрации доменного имени. Иногда могут потребоваться сертификаты, которые будут одновременно включать помимо доменов несколько поддоменов. В таких случаях стоит приобретать сертификаты типа Comodo PositiveSSL Multi-Domain Wildcard и Comodo Multi-Domain Wildcard SSL. Отметим, что в этом случае можно также приобрести обычный мультидоменный сертификат, в котором просто указать необходимые поддомены.

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

P.S. Несколько материалов по теме из нашего блога 1cloud:

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

https://habrahabr.ru/post/334278/


Метки:  

[Перевод] Если нет разницы между двумя вариантами кода, выбирай тот, который проще отладить

Четверг, 27 Июля 2017 г. 16:37 + в цитатник
В С# существует два способа преобразования объектов: использовать оператор as, который пытается преобразовать объект и в случае успеха возвращает результат, в случае неудачи null; или использовать оператор преобразования.



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

// вариант 1
var thing = GetCurrentItem();
var foo = thing as Foo;
foo.DoSomething();

// вариант 2
var thing = GetCurrentItem();
var foo = (Foo)thing;
foo.DoSomething();

Теперь предположим, что объект thing не является типом Foo. Оба варианта отработают некорректно, однако сделают они это по-разному.

В первой варианте отладчик выдаст исключение NullReferenceException в методе foo.Do­Something(), и дамп сбоя подтвердит, что переменная foo является null. Однако этого может и не быть в дампе сбоя. Возможно, дамп сбоя захватывает лишь параметры, которые участвовали в выражении, которое в свою очередь привело к исключению. Или может быть переменная thing попадёт в сборщик мусора. Вы не можете определить где проблема когда GetCurrentItem возвращает null или GetCurrentItem возвращает объект другого типа, отлично от типа Foo. И что это если не Foo?

Во втором варианте также могут возникнуть ошибки. Если объект thing является null, при вызове метода foo.Do­Something() ты получишь исключение NullReferenceException. Однако, если объект thing имеет другой тип, сбой произойдет в точке преобразования типов и выдаст исключение InvalidCastException. Если повезёт, отладчик покажет что именно нельзя преобразовать. Если не сильно повезёт, можно будет определить где произошел сбой по типу выданного исключения.

Задание: Оба варианта ниже функционально эквивалентны. Какой будет проще отладить?

// вариант 1
collection.FirstOrDefault().DoSomething();

// вариант 2
collection.First().DoSomething();
Переводить ли в дальнейшем подобные заметки Рэймонда Чена из блога The Old New Thing?

Проголосовало 30 человек. Воздержалось 14 человек.

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

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

https://habrahabr.ru/post/334274/


Метки:  

Карты, счета, 2 баланса

Четверг, 27 Июля 2017 г. 16:33 + в цитатник
Как знают держатели дебетовых банковских карт Райффайзенбанка, карта является лишь инструментом доступа к счету: она не имеет собственного баланса, по ней доступны только те средства, которые есть на текущем счете клиента. В этой статье мы расскажем об архитектуре технического решения, которое позволяет картам и счетам в нашем банке использовать единый баланс. И также вы узнаете о том, как был организован проект, который позволил нам эту архитектуру реализовать.

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

Такая, казалось бы, элементарная и логичная функциональность обеспечивается довольно сложным техническим решением, которое удачно реализовано далеко не в каждом банке. Например, до 2014 года в нашем банке балансы дебетовых карт жили отдельно от балансов счетов: карты в процессинге, счета около АБС. Балансы синхронизировались по утрам рабочих дней. Излишне говорить, что такая схема приводила к проблемам у клиентов: если человек досрочно расторгал депозит, то потратить/снять деньги по карте мог только завтра. А если сегодня суббота, то «завтра» превращалось в понедельник. В те времена добрая треть жалоб на banki.ru была так или иначе связана с этой темой. Внутри банка эта реализация также приводила к сложностям: при любой расходной операции по счету нужно было проверить, а не потратил ли клиент те же самые деньги по карте. В итоге большинство систем использовало программный эмулятор POS-терминала, через который проверялся баланс по карте и ставилась специальная блокировка в процессинге. Эта проверка не во всех случаях работала корректно, часто требовалось ручное вмешательство операциониста. Например, карта временно заблокирована, на ней 100 рублей, клиент снимает со счета 100, эмулятор POS-терминала на такую блокировку выдаёт ошибку, сумму не блокирует. Если эту проблему проигнорировать, то после совершения расходной операции по счету клиент разблокирует карту и снимет еще 100 рублей.

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

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

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

Во-вторых, мы хотели сделать минимум доработок в процессинге и АБС силами поставщиков этих систем.

В-третьих, для контроля баланса по расходным операциям со счетом этими внешними системами не использовался механизм самой АБС.

Исторически операционный день в банке был устроен так, что текущее значение баланса по счету вычислялось специальной системой на основании остатка на начало операционного дня в АБС, уже подтвержденных в АБС транзакций/проводок, и промежуточных операционных данных в некоторых транзакционных системах (всего было три таких системы). А как же устроен контроль баланса по операциям, которые инициируются внутри АБС? Ведь не может же она для проверки баланса обращаться в третью систему?! Для кредитных продуктов (плановые списания по графику платежей, работа с просрочкой) АБС использует свой баланс на конец дня, так как по процессу все промежуточные движения из третьих транзакционных систем записываются в АБС до завершения операционного дня. Для транзакционных продуктов (размещение депозитов, отправка валютных платежей и так далее) контроль баланса происходит либо в ручном режиме (если операция вручную вводится в АБС), либо во внешней системе, которая после всех проверок через API формирует операцию в АБС.

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

image

Итак, мы определились с целевой системой, которая будет является источником истины по доступному балансу — это будет ни АБС, ни процессинг, а некая новая система. Назовем её кодовыми буквами NI. Исходя их обозначенных выше особенностей работы систем банка, для корректной реализации системы NI нужно выполнить следующие действия:
  • Отказаться при расчете баланса от анализа данных транзакционных систем (мы упоминали о трех таких системах — это касса, рублевые платежи, конверсии юридических лиц). Это нужно для надежной и быстрой работы авторизаций по картам, чтобы не ходить на сторонние СУБД.
  • Переключить на NI все системы, использующие текущую специализированную систему для расчета баланса. Или подключить старую систему к NI, что проще; это мы в итоге и выбрали.
  • Научить NI как-то работать в процессе закрытия операционного дня АБС.
  • Интегрировать NI с карточным процессингом.
  • Посмотреть, как все эти изменения отразятся на интернет-банкинге.


Отказ от данных третьих систем при расчете баланса по счету


Как это работало? Допустим, на счете клиента на начало дня в АБС баланс составил 100 рублей. Клиент снял через кассу 50 рублей в 10 часов утра. В кассовой системе есть подтвержденная кассиром операция, но проводки по ней в АБС еще не созданы (вспоминаем, что АБС из прошлого века, проводки она любит принимать крупными пачками, а не по одной штучке). Кассовая система по своему плану эту и прочие подтвержденные операции планирует выгрузить проводками в АБС в 12 часов дня единой пачкой. Как учесть в балансе по счету, что 50 рублей уже нет? В начале 2000-х годов в банке этому нашли «элегантное» решение — система расчета баланса откроет соединение с базой кассовой системы и в ней отберет все операции, подтвержденные кассиром, но еще не выгруженные в АБС. И так по трем разным системам.

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

Более правильным нам показалось решение, когда создается единое хранилище всех подтвержденных, но еще не выгруженных в АБС движений по счету. А все системы, которым нужно учитывать свои операции в доступном балансе в реальном времени, должны подключаться к этому хранилищу через набор API, которые должны позволят как рассчитывать текущее значение доступного баланса, так и менять его посредством создания движений по счету. Поскольку новую систему решили обозвать NI, то её API будем называть “NI API”. При этом наша старая система расчета доступного баланса должна научиться смотреть в это новое хранилище NI, а само хранилище, в целях быстродействия и надежности, решили разместить на той же системе, где живет АБС. В нашем случае это платформа IBM i.

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

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

Представим, как в нашем примере кассовая система должна работать с NI API. Клиент снимает через кассу 50 рублей в 10 часов утра. Кассовая система обращается в NI API с просьбой: «Создай расходную операцию по счету клиента на 50 рублей, если текущее значение его баланса позволяет это сделать». NI рассчитывает баланс, он позволяет совершить операцию, NI создает движение на 50 рублей в своем промежуточном хранилище и отвечает кассовой системе: «Всё хорошо, выдавай». Если бы баланс по счету клиента был меньше 50 рублей, NI ответил бы: «Дело плохо, текущее значение баланса 40 рублей».

Наступает 12 часов, кассовой системе пора выгружать свою пачку совершенных операций проводками в АБС. По идее, в момент выгрузки операций она должна сообщить NI API, что эти движения уже доехали до АБС, и пора бы их убрать из промежуточного хранилища движений. И тут начинаются сложности. По логике процесса выгрузки пачки проводок в АБС, на всю пачку делается один коммит. Значит, надо также пачкой и с одним коммитом убирать эти движения из хранилища NI. Пачки проводок могут быть достаточно большими, промежуток времени между коммитами может быть довольно большим (десятки секунд максимум, но для нас и это много), в течение которого у клиента на счете будет некорректный баланс. Также возможны технические сбои, когда коммит пачки в АБС прошел успешно, а в NI API по какой-то причине — нет. Из-за этого мы рискуем получить неверные значения текущих балансов по счетам клиентов, а в банке такое неуместно.

К счастью, мы идентифицировали эти риски на этапе проектирования системы NI. Для их нивелирования решили «протянуть» функционал NI до АБС. В API по созданию проводок добавили обязательное поле с идентификатором пачки проводок, в которую внешняя система хочет объединить эти проводки. Также была добавлена отдельная функция «Запиши пачку проводок в АБС», по вызову которой NI сам записывал проводки в АБС, с единым коммитом в своем хранилище и в АБС. В этом сильно помогло то, что базы NI и АБС были размещены, по сути, в одной СУБД. Таким образом, кассовая система в 12 часов просто говорила NI API: «Выгрузи пачку проводок NI в АБС и сообщи мне о возникших проблемах».

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

Был разработан NI API, который предоставлял внешним системам несколько логически сгруппированных API:
  • Получение информации по счету (баланс, статусы и так далее).
  • Работа с пачками (создание, удаление, выгрузка в АБС).
  • Работа с отдельными проводками в пачках.
  • Работа с холдами.


Технически, API представлял собой набор программ, написанных на языке программирования Free format ILE RPG с использованием SQL. Для использования API вне платформы IBM I, программы были обернуты в хранимые процедуры на базе DB2 for i, с которыми внешние системы могли работать по стандартным протоколам ODBC, JDBC и так далее. Также доступ к NI API был реализован с корпоративной интеграционной шины ESB, которая, в свою очередь, также обращалась к API по ODBC/JDBC.

Переключить некоторые системы на NI


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

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

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

Внедрение механизма создания уже акцептованных пакетов проводок технически оказалось довольно сложной, но интересной задачей. Мы анализировали стек вызовов штатных программ АБС по акцепту проводок, отслеживали по журналам транзакций СУБД её манипуляции с данными. В итоге мы научились обманывать АБС и создавать проводки акцептованными. Кассовый модуль по внебалансу запустился на NI API, проводки потекли в АБС без акцепта оператором, а мы получили понимание, как стимулировать бизнес переходить на NI. Секрет оказался прост — надо предлагать бизнесу при миграции дополнительную функциональность, которая упростит их процессы или каким-то иным образом улучшит продукт. И тут-то нам карта и пошла…
В результате NI оброс функциональностью по созданию проводок по заблокированным счетам (актуально для списаний по решению суда и так далее, в старом процессе счета временно разблокировали вручную), замороченными вариантами расчетами баланса под приоритет, овердрафт и прочими хитростями. Это позволило бизнесу при миграции функциональности получить свои плюшки и существенно оптимизировать процессы. Итак, касса и рублевые платежи — done, остались валютообменные операции корпоративных клиентов.

А с этой системой у нас дело не ладилось. В ней проходило очень мало операций, потребностей в развитии функциональности не было, оптимизация не сулила существенных выгод. В итоге нам так и не удалось убедить партнеров в необходимости доработок, и мы махнули на нее рукой. До сих при расчете баланса по счетам корпоративных клиентов открывается соединение с ее СУБД. Но тогда нас это не расстроило, потому что мы добились своей главной цели — по счетам физических лиц все данные для расчета баланса были сконцентрированы в единой СУБД IBM i, расчет работал быстро (порядка 5 миллисекунд на счет) и надежно.

Научить NI работать в процессе закрытия операционного дня


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

Программа «день->ночь» просто копировала нужные для расчета баланса таблицы АБС в отдельную библиотеку в той же СУБД. Использовалась примитивная конструкция INSERT SELECT. Всё копирование занимало порядка 20 минут. После завершения копирования NI понимал, что теперь необходимо ходить не в таблицу АБС, а в её копию.

Программа «ночь->день» отрабатывала мгновенно, просто переключая NI на работу с основной базой АБС.

Это решение позволило переходить между фазами дня без прерывания сервиса. Все движения по счетам, которые внешние системы хотели совершить во время закрытия дня АБС, накапливались в NI. Далее, после открытия дня АБС, внешним системам надо было дать команду на выгрузку проводок в АБС через NI API. Это не очень удобно для внешних систем, поэтому в дальнейшем был создан механизм STP (от straight through processing), при котором внешняя система только создает проводку в NI, а дальше он уже сам группирует их в пакеты и выгружает в АБС, когда та для этого доступна.

Реализация схемы день-ночь не лишена изящества. Мы задействовали штатный механизм IBM i по работе с контекстом — список библиотек. Были созданы две библиотеки — дневная и ночная. В них были созданы алиасы на дневные и ночные копии таблиц АБС соответственно. Для каждой таблицы и её ночной копии имя алиаса совпадало. Во всех SQL-выражениях для доступа к данным АБС использовались исключительно алиасы.

Перед обработкой каждого вызова NI API смотрит в специальный объект Data Area (очень быстрый на чтение-запись) системы IBM i чтобы понять, не изменился ли режим с прошлого вызова API в этом процессе. Как нетрудно догадаться, режим в Data Area меняет программы день->ночь и ночь->день. Если режим изменился, то программа просто заменяет библиотеку с алиасами в списке библиотек процесса, это крайне быстрая операция.

В результате, все системы-клиенты NI API смогли рассчитывать баланс и проводить операции 24 часа в сутки без перерывов, не особо заботясь об операционном дне в АБС. Для нас это была революция.

На этом мы не остановили развитие логики перехода ночь/день. Те 20 минут на копирование таблиц силами SQL не давали нам покоя. Была придумана и внедрена схема с репликацией с помощью решения высокой доступности MIMIX. Идея в том, что ночная копия создается не в момент начала закрытия дня АБС, а при открытии дня. В течение дня эта копия в режиме, близком к реальному времени, синхронизируется с данными АБС. А чтобы быть готовыми к любым ударам судьбы, мы делаем репликацию сразу на двух системах IBM i: основной и резервной.

image
В данный момент мы переходим день->ночь и обратно практически мгновенно, сервис не прерывается ни на секунду.

Интеграция с процессингом банковских карт


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

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

Наша процессинговая система на момент начала проекта умела при авторизации обращаться за проверкой доступного баланса во внешние системы. Эта функциональность была создана ранее, при объединении с Импэксбанком, так как Импэкс имел продукт «карта к счету». В процессе авторизации операции, после успешного завершения чисто карточных проверок (криптография, PIN-код и так далее) авторизационная система посылает ISO-8583-сообщение во внешнюю систему для финансовой авторизации и ждет ответа определенное количество секунд. От нас требовалось создать специальный модуль NI, который умел бы общаться с процессинговой системой по этому протоколу. Принять сообщение, разобрать на поля, вызвать NI API, сформировать ответ. Достаточно простая задача, но только на первый взгляд.

Первая сложность подстерегала нас с онлайновыми операциями пополнения счета клиента с помощью банковской карты. Это взносы в банкоматы и входящие переводы с карты на карту. По логике работы этих операций, авторизация и клиринг могут пройти в разные банковские дни. Допустим, клиенту с нулевым балансом на счете поступает перевод из стороннего банка 1 апреля в 10:00. В 10:05 клиент эти средства пускает на досрочное погашение кредита. Клиринг по этой операции придет 2 апреля. Если мы 1 апреля (при авторизаици) не сделаем бухгалтерскую проводку с пополнением счета, а просто поднимем баланс «положительным холдом», то после погашения кредита на счете в бухгалтерском учете возникнет отрицательный остаток, технический овердрафт, который закроется только после получения клиринга. В банковской бухгалтерии технических овердрафтов всячески избегают, и выход у нас один — по операциям пополнения делать проводки при авторизации.

Из первой сложности вытекает вторая — курсовые разницы. Когда перевод приходит из иностранного банка, суммы при авторизации и клиринге могут различаться из-за колебания курсов валют. Проиллюстрируем на реальном примере. Наш клиент 11 июня получил перевод на карту из белорусского банка, сумма перевода 15,00 белорусских рублей. Банк физически не может поддерживать курсы конвертации ко всем валютам мира по карточным операциям, мы ведем курсы по трем валютам — рубли, доллары, евро. Платежная система для авторизации пересчитывает сумму перевода по собственному курсу в валюту эмитента, в данном случае это были рубли РФ, сумма составила 455,50. Счет клиента открыт в рублях, поэтому мы ему провели приход на 455,50 рублей. С этого момента клиент может распоряжаться этими деньгами как угодно.

14 июня к нам пришел клиринг из платежной системы, и по этой операции нашему банку перечислили 7,16 Евро. С точки зрения учета выходит, что мы клиенту провели распределенную во времени конверсионную операцию: за 455,50 рублей продали 7,16 Евро. Неплохо бы понять, заработал банк на такой операции или потерял. По нашим курсам на 14 июня выходит, что 7,16 евро мы продали бы клиенту за 445,35 рублей. Значит, банку повезло и он заработал 10,15 рублей. Бывает, что не везет, и на колебаниях банк теряет. Техническая реализация такой схемы расчетов очень сложна. Зачем морочиться, спросите вы, ведь такие операции с валютным риском можно просто не показывать клиенту в балансе до клиринга. К тому же таких операций сравнительно немного. Что греха таить, некоторые банки так и поступают. Но пойти на это нам не позволяет наш девиз — «разница в отношении». Мы считаем, что клиенту важно иметь возможность распоряжаться деньгами в момент совершения перевода, а бухгалтерские и технические заморочки не должны тому препятствовать.

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

С авторизацией разобрались, переходим к клирингу. В прежней схеме работы специальная система-посредник брала клиринговые операции по счетам клиентов из процессинговой системы, превращала их в бухгалтерские проводки и складывала в АБС. Причем на момент, когда наш проект дошел до процессинга, эта система-посредник уже работала с NI API (бонус NI в виде автоакцепта пакетов проводок стимулировал бизнес-владельцев систем на подобный переход). На момент клиринга наша основная задача — найти в NI авторизацию, убрать по ней холд и выполнить проводку. Но данных бухгалтерской проводки недостаточно для поиска авторизации. Мы видели два варианта реализации:
  • Создать отдельный API для поиска авторизации и снятия холда.
  • Встроить в «проводочный» API данные карточной транзакции, чтобы за один вызов и проводку добавить, и холд снять.

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

image

Изменения в интернет-банкинге


По теме интернет-банка интересны две вещи: посмотреть баланс карты/счета и выписку. До реализации проекта эти сущности для дебетовых карт и их счетов жили в инернет-банке раздельно. Невозможно было посмотреть текущее значение баланса счетов, отображался лишь баланс на начало дня из АБС. Выписка по счетам также бралась из АБС за прошедшие дни. Сегодня пришел платеж на ваш счет? Увидите в интернет-банке завтра!

По картам клиенты видели реальный баланс и реальные списки операций. Но какая техническая реализация за этим стояла! Интернет-банк постоянно, каждые две минуты обращался в базу процессинга, забирая из нее свежие авторизации по картам и изменения балансов карт. Всё это он складывал в своей базе. Так как балансы из АБС загружались в процессинг раз в день, это событие интернет-банк ловил в логах процессинга и начинал массовую перегрузку балансов всех карт и активных холдов. Эта процедура занимала продолжительное время; пока она шла, клиенты могли увидеть некорректную информацию.

image

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

Отдельно мы обещали рассказать про SMS-информирование по факту авторизации по карте. До реализации NI SMS генерировались в системах интернет-банкинга по факту получения свежих авторизаций из БД процессинга. В этом проекте мы попробовали реализовать событийно-управляемую архитектуру. Специально для NI был создан топик в системе управления очередями IBM WebSphere MQ. NI публикует в этот топик все события, связанные с изменением баланса: появился холд, появилась проводка, удалился холд и так далее. При этом атрибуты событий выведены в заголовки сообщений, что позволяет подписчикам гибко выбирать только те события, которые им нужны. Система SMS-информирования клиентов первой подключилась к этому топику, клиенты стали получать сообщения быстрее. Наши сотрудники, участвующие в пилоте, даже жаловались, что в новой схеме SMS приходит быстрее, чем банкомат успевает набрать пачку и выдать наличность.

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

image

Заключение


Реализовав столь масштабный проект, банк сумел устранить ряд архитектурных огрехов в процессах обработки транзакций и отображения информации для клиентов, что создало мощную базу для развития продуктов. Вслед за физическими лицами эта функциональность была востребована корпоративными клиентами. Были выпущены специальные карты «Business 24x7», в которых акцент делается на общем балансе счета/карта. Интернет-банк для юридических лиц также оброс онлайновыми сервисами.

Ну, а вы теперь знаете, что стоит за вашей картой Райффайзенбанка, и как мы относимся к надежности и клиентскому опыту. И что у нас переводы на карты зачисляются на счет мгновенно!
Немного сухой статистики:
  • Порядка 70% проводок в банке формируется через NI.
  • API по запросу баланса вызывается в среднем 5 млн раз в сутки, в пиковые дни доходит до 8. Примерно половина всех запросов приходится на ДБО (отображение информации клиентам).
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334242/


Метки:  

[Перевод] Разбираемся с копированием и клонированием

Четверг, 27 Июля 2017 г. 16:21 + в цитатник


Я наткнулся на статью Нареша Джоши о копировании и клонировании и был удивлён ситуацией с производительностью. У клонирования есть проблемы с финальными полями. А учитывая тот факт, что интерфейс Cloneable не предоставляет метод clone, то для вызова clone вам необходимо будет знать конкретный тип класса.


Вы можете написать такой код:


    ((Cloneable)o).clone(); // не работает

Если интерфейс Cloneable сломан, то у механизма клонирования могут быть некоторые преимущества. При копировании памяти он может оказаться эффективнее, чем копирование поля за полем. Это подчёркивает Джош Блох, автор Effective Java:


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

Но это было в 2002-м, разве ситуация не изменилась? Со времён Java 6 у нас есть Arrays.copyOf, что насчёт него? Какова производительность копирования объекта?
Есть только один способ выяснить: прогнать бенчмарки.


TL;DR


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

image


Массивы


Давайте быстро рассмотрим clone и Arrays.copyOf применительно к массивам.


Бенчмарк int array выглядит так:


    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public int[] testCopy() {
        return Arrays.copyOf(original, size);
    }

    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public int[] testClone() {
        return original.clone();
    }

Мы создали массив из случайных числовых значений, затем выполнили clone или Arrays.copyOf. Обратите внимание: мы вернули результат копирования, чтобы гарантировать, что код будет выполнен. В главе про escape analysis мы увидим, как невозвращение массива может радикально повлиять на бенчмарк.


Наряду с int array есть версия для byte array, long array и Object array. Я использую флаг DONT_INLINE, чтобы при необходимости легче было анализировать сгенерированный asm.


mvn clean install
java -jar target/benchmark.jar -bm avgt -tu ns -rf csv

Это даёт нам следующие значения средней продолжительности в наносекундах.


На графиках я отобразил одновременно total time/array size. Чем меньше, тем лучше.


image image


image image


Как видите, clone по сравнению с Arrays.copyOf обходится примерно на 10 % дешевле при маленьких массивах, так что это всё ещё хороший вариант. Несколько удивительно, что оба они используют один и тот же механизм копирования.


Давайте рассмотрим сгенерированный asm.


asm клонирование


Для testClone есть код выделения памяти, идущий за копированием массива со строки 41 по 47.


0x0000000116972e4c: mov 0x10(%rsi),%r9d  ;*getfield original
                                         ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testClone@1 (line 68)
0x0000000116972e50: mov 0xc(%r12,%r9,8),%r8d  ;*invokevirtual clone
                                              ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testClone@4 (line 68)
                                              ; implicit exception: dispatches to 0x0000000116972f0e
0x0000000116972e55: lea (%r12,%r9,8),%rbp  ;*getfield original
                                           ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testClone@1 (line 68)
0x0000000116972e59: movslq %r8d,%rdx
0x0000000116972e5c: add $0x17,%rdx
0x0000000116972e60: and $0xfffffffffffffff8,%rdx
0x0000000116972e64: cmp $0x100000,%r8d
0x0000000116972e6b: ja L0001
0x0000000116972e6d: mov 0x60(%r15),%rbx
0x0000000116972e71: mov %rbx,%r10
0x0000000116972e74: add %rdx,%r10
0x0000000116972e77: cmp 0x70(%r15),%r10
0x0000000116972e7b: jae L0001
0x0000000116972e7d: mov %r10,0x60(%r15)
0x0000000116972e81: prefetchnta 0xc0(%r10)
0x0000000116972e89: movq $0x1,(%rbx)
0x0000000116972e90: prefetchnta 0x100(%r10)
0x0000000116972e98: movl $0xf80000f5,0x8(%rbx)  ;   {metadata({type array byte})}
0x0000000116972e9f: mov %r8d,0xc(%rbx)
0x0000000116972ea3: prefetchnta 0x140(%r10)
0x0000000116972eab: prefetchnta 0x180(%r10)
             L0000: lea 0x10(%r12,%r9,8),%rdi
0x0000000116972eb8: mov %rbx,%rsi
0x0000000116972ebb: add $0x10,%rsi
0x0000000116972ebf: add $0xfffffffffffffff0,%rdx
0x0000000116972ec3: shr $0x3,%rdx
0x0000000116972ec7: movabs $0x1167e5780,%r10
0x0000000116972ed1: callq *%r10  ;*invokevirtual clone
                                 ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testClone@4 (line 68)
0x0000000116972ed4: mov %rbx,%rax
0x0000000116972ed7: add $0x20,%rsp
0x0000000116972edb: pop %rbp
0x0000000116972edc: test %eax,-0xdf73ee2(%rip)  # 0x00000001089ff000
                                                ;   {poll_return} *** SAFEPOINT POLL ***
0x0000000116972ee2: retq

asm копирование


В testCopy есть код выделения памяти, но со строки 47 идёт гораздо больше кода для работы с длиной копии. Реальное копирование выполняется в строках 79—80.


0x000000010b1639cc: mov 0xc(%rsi),%r10d  ;*getfield size
                                         ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@5 (line 62)
0x000000010b1639d0: cmp $0x100000,%r10d
0x000000010b1639d7: ja L0005
0x000000010b1639dd: movslq %r10d,%r8  ;*newarray
                                      ; - java.util.Arrays::copyOf@1 (line 3236)
                                      ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@8 (line 62)
             L0000: mov 0x10(%rsi),%r9d  ;*getfield original
                                         ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@1 (line 62)
0x000000010b1639e4: mov %r9d,0x10(%rsp)
0x000000010b1639e9: add $0x17,%r8
0x000000010b1639ed: mov %r8,%rdx
0x000000010b1639f0: and $0xfffffffffffffff8,%rdx
0x000000010b1639f4: cmp $0x100000,%r10d
0x000000010b1639fb: ja L0004
0x000000010b163a01: mov 0x60(%r15),%rbp
0x000000010b163a05: mov %rbp,%r11
0x000000010b163a08: add %rdx,%r11
0x000000010b163a0b: cmp 0x70(%r15),%r11
0x000000010b163a0f: jae L0004
0x000000010b163a15: mov %r11,0x60(%r15)
0x000000010b163a19: prefetchnta 0xc0(%r11)
0x000000010b163a21: movq $0x1,0x0(%rbp)
0x000000010b163a29: prefetchnta 0x100(%r11)
0x000000010b163a31: movl $0xf80000f5,0x8(%rbp)  ;   {metadata({type array byte})}
0x000000010b163a38: mov %r10d,0xc(%rbp)
0x000000010b163a3c: prefetchnta 0x140(%r11)
0x000000010b163a44: prefetchnta 0x180(%r11)  ;*newarray
                                             ; - java.util.Arrays::copyOf@1 (line 3236)
                                             ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@8 (line 62)
             L0001: mov 0x10(%rsp),%r11d
0x000000010b163a51: mov 0xc(%r12,%r11,8),%r11d  ;*arraylength
                                                ; - java.util.Arrays::copyOf@9 (line 3237)
                                                ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@8 (line 62)
                                                ; implicit exception: dispatches to 0x000000010b163b77
0x000000010b163a56: cmp %r10d,%r11d
0x000000010b163a59: mov %r10d,%r9d
0x000000010b163a5c: cmovl %r11d,%r9d  ;*invokestatic min
                                      ; - java.util.Arrays::copyOf@11 (line 3238)
                                      ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@8 (line 62)
0x000000010b163a60: mov %rbp,%rbx
0x000000010b163a63: add $0x10,%rbx
0x000000010b163a67: shr $0x3,%r8  ;*invokestatic arraycopy
                                  ; - java.util.Arrays::copyOf@14 (line 3237)
                                  ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@8 (line 62)
0x000000010b163a6b: mov 0x10(%rsp),%edi
0x000000010b163a6f: lea (%r12,%rdi,8),%rsi  ;*getfield original
                                            ; - com.github.arnaudroger.ArrayByteCopyVsCloneBenchmark::testCopy@1 (line 62)
0x000000010b163a73: mov %r8,%rcx
0x000000010b163a76: add $0xfffffffffffffffe,%rcx
0x000000010b163a7a: cmp %r9d,%r11d
0x000000010b163a7d: jb L0006
0x000000010b163a83: cmp %r9d,%r10d
0x000000010b163a86: jb L0006
0x000000010b163a8c: test %r9d,%r9d
0x000000010b163a8f: jle L0007
0x000000010b163a95: lea 0x10(%r12,%rdi,8),%r11
0x000000010b163a9a: cmp %r10d,%r9d
0x000000010b163a9d: jl L0003
0x000000010b163a9f: add $0xfffffffffffffff0,%rdx
0x000000010b163aa3: shr $0x3,%rdx
0x000000010b163aa7: mov %r11,%rdi
0x000000010b163aaa: mov %rbx,%rsi
0x000000010b163aad: movabs $0x10afd5780,%r10
0x000000010b163ab7: callq *%r10
             L0002: mov %rbp,%rax
0x000000010b163abd: add $0x30,%rsp
0x000000010b163ac1: pop %rbp
0x000000010b163ac2: test %eax,-0x5b6aac8(%rip)  # 0x00000001055f9000
                                                ;   {poll_return} *** SAFEPOINT POLL ***
0x000000010b163ac8: retq

clone сделает копию точно такой же длины, но Arrays.copyOf позволяет нам копировать массив в массив другой длины, что усложняет обработку разных ситуаций и увеличивает стоимость, особенно на маленьких массивах. Похоже, jit никак не смирится с тем фактом, что мы передаём original.length как newLength. Будь это не так, он мог бы упростить код и производительность стала бы на уровне.


Объекты


Теперь разберёмся с клонированием объектов с 4, 8, 16 и 32 полями. Бенчмарки ищут объекты с 4 полями:


    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public Object4 testCopy4() {
        return new Object4(original);
    }

    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public Object4 testClone4() {
        return original.clone();
    }

С 8, 16 и 32 полями.


Нормализованные результаты:


image


Как видите, для маленьких/средних объектов — меньше 8 полей — клонирование не столь эффективно, как копирование, но его преимущества раскрываются на более крупных объектах.
Это неудивительно и следует из комментария к коду JVM:


// TODO: вместо этого сгенерировать копии полей для маленьких объектов.

Кто-то должен был отработать этот комментарий, но так и не сделал этого.


Давайте внимательнее проанализируем asm применительно к копированию и клонированию объектов с 4 полями.


Asm и 4 поля


java -jar target/benchmarks.jar -jvmArgs "-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly "  -f 1 "Object4"

Копирование asm


В testCopy asm с 17-й по 32-ю строки мы видим код выделения памяти.


Я добавил в asm кое-какую аннотацию, начинающуюся с **.


0x000000010593d28f: mov 0x60(%r15),%rax
0x000000010593d293: mov %rax,%r10
0x000000010593d296: add $0x20,%r10         ;** allocation size
0x000000010593d29a: cmp 0x70(%r15),%r10
0x000000010593d29e: jae L0001
0x000000010593d2a0: mov %r10,0x60(%r15)
0x000000010593d2a4: prefetchnta 0xc0(%r10)
0x000000010593d2ac: mov $0xf8015eab,%r11d  ;   {metadata('com/github/arnaudroger/beans/Object4')}
0x000000010593d2b2: movabs $0x0,%r10
0x000000010593d2bc: lea (%r10,%r11,8),%r10
0x000000010593d2c0: mov 0xa8(%r10),%r10
0x000000010593d2c7: mov %r10,(%rax)
0x000000010593d2ca: movl $0xf8015eab,0x8(%rax)  ;   {metadata('com/github/arnaudroger/beans/Object4')}
0x000000010593d2d1: mov %r12d,0xc(%rax)
0x000000010593d2d5: mov %r12,0x10(%rax)
0x000000010593d2d9: mov %r12,0x18(%rax)  ;*new  ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testCopy4@0 (line 60)

Строка 19 — это размер выделяемой памяти, 32 байта. Из них 16 байтов для свойств, 12 — для заголовков, 4 — для выравнивания (alignment). Проверить это можно с помощью jol.


com.github.arnaudroger.beans.Object4 object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4    int Object4.f1                                N/A
     16     4    int Object4.f2                                N/A
     20     4    int Object4.f3                                N/A
     24     4    int Object4.f4                                N/A
     28     4        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Копирование поля за полем выполняется в строках с 33-й по 48-ю.


             L0000: mov 0xc(%rbp),%r11d  ;*getfield original4
                                         ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testCopy4@5 (line 60)
0x000000010593d2e1: mov 0xc(%r12,%r11,8),%r10d  ; implicit exception: dispatches to 0x000000010593d322
0x000000010593d2e6: mov %r10d,0xc(%rax)  ;*putfield f1
                                         ; - com.github.arnaudroger.beans.Object4::@9 (line 12)
                                         ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testCopy4@8 (line 60)
0x000000010593d2ea: mov 0x10(%r12,%r11,8),%r8d
0x000000010593d2ef: mov %r8d,0x10(%rax)  ;*putfield f2
                                         ; - com.github.arnaudroger.beans.Object4::@17 (line 13)
                                         ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testCopy4@8 (line 60)
0x000000010593d2f3: mov 0x14(%r12,%r11,8),%r10d
0x000000010593d2f8: mov %r10d,0x14(%rax)  ;*putfield f3
                                          ; - com.github.arnaudroger.beans.Object4::@25 (line 14)
                                          ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testCopy4@8 (line 60)
0x000000010593d2fc: mov 0x18(%r12,%r11,8),%r11d
0x000000010593d301: mov %r11d,0x18(%rax)

Клонирование asm


Для testClone asm можно также посмотреть код выделения памяти с 24-й по 37-ю строку.


0x000000010b17da9d: mov 0x60(%r15),%rbx
0x000000010b17daa1: mov %rbx,%r10
0x000000010b17daa4: add $0x20,%r10         ;** allocation size
0x000000010b17daa8: cmp 0x70(%r15),%r10
0x000000010b17daac: jae L0001
0x000000010b17daae: mov %r10,0x60(%r15)
0x000000010b17dab2: prefetchnta 0xc0(%r10)
0x000000010b17daba: mov $0xf8015eab,%r11d  ;   {metadata('com/github/arnaudroger/beans/Object4')}
0x000000010b17dac0: movabs $0x0,%r10
0x000000010b17daca: lea (%r10,%r11,8),%r10
0x000000010b17dace: mov 0xa8(%r10),%r10
0x000000010b17dad5: mov %r10,(%rbx)
0x000000010b17dad8: movl $0xf8015eab,0x8(%rbx)  ;   {metadata('com/github/arnaudroger/beans/Object4')}

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


image


        
        
        
        
        
        
        

Применительно к Object.clone указан сбой инлайнинга, потому что это «нативный метод».
clone является внутренним (intrinsic), он инлайнится с помощью inline_native_clone и copy_to_clone.


copy_to_clone генерирует выделение памяти (allocation), а затем копирование long array. Оно возможно, потому что объекты выравнены в памяти по 8 байтов.


             L0000: lea 0x8(%r12,%r8,8),%rdi ;** src 
0x000000010b17dae4: mov %rbx,%rsi ;** dst
0x000000010b17dae7: add $0x8,%rsi ;** add offset
0x000000010b17daeb: mov $0x3,%edx ;** length
0x000000010b17daf0: movabs $0x10aff4780,%r10
0x000000010b17dafa: callq *%r10  ;*invokespecial clone
                                 ; - com.github.arnaudroger.beans.Object4::clone@1 (line 28)
                                 ; - com.github.arnaudroger.Object4CopyVsCloneBenchmark::testClone4@4 (line 66)

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


Влияние escape analysis


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


     @Benchmark
     @CompilerControl(CompilerControl.Mode.DONT_INLINE)
     public int testCopy() {
         return new Object32(original).f29;
     }

     @Benchmark
     @CompilerControl(CompilerControl.Mode.DONT_INLINE)
     public int testClone() {
         return original.clone().f29;
     }

Результаты таковы, что, даже хотя clone более эффективно для объектов с 32 полями...


image


… бенчмарк клонирования работает более чем вчетверо медленнее! Что произошло?


Посмотрим, что находится под капотом asm.


asm клонирование


В asm для testClone всё аналогично варианту для Object4CopyVsCloneBenchmark.testClone. В строке 26 выделяется 144 байта — 90 в шестнадцатеричном виде, — из которых 12 байтов на заголовок, 32 x 4 = 128 байтов на поля, 4 байта потеряно на выравнивание.


0x000000010ceebe8c: mov 0xc(%rsi),%r9d  ;*getfield original
                                        ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testClone@1 (line 69)
0x000000010ceebe90: test %r9d,%r9d
0x000000010ceebe93: je L0002  ;*invokespecial clone
                              ; - com.github.arnaudroger.beans.Object32::clone@1 (line 111)
                              ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testClone@4 (line 69)
0x000000010ceebe99: lea (%r12,%r9,8),%rbp  ;*getfield original
                                           ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testClone@1 (line 69)
0x000000010ceebe9d: mov 0x60(%r15),%rbx
0x000000010ceebea1: mov %rbx,%r10
0x000000010ceebea4: add $0x90,%r10 ;** object length
0x000000010ceebeab: cmp 0x70(%r15),%r10
0x000000010ceebeaf: jae L0001
0x000000010ceebeb1: mov %r10,0x60(%r15)
0x000000010ceebeb5: prefetchnta 0xc0(%r10)
0x000000010ceebebd: mov $0xf8015eab,%r11d  ;   {metadata('com/github/arnaudroger/beans/Object32')}
0x000000010ceebec3: movabs $0x0,%r10
0x000000010ceebecd: lea (%r10,%r11,8),%r10
0x000000010ceebed1: mov 0xa8(%r10),%r10
0x000000010ceebed8: mov %r10,(%rbx)
0x000000010ceebedb: movl $0xf8015eab,0x8(%rbx)  ;   {metadata('com/github/arnaudroger/beans/Object32')}
             L0000: lea 0x8(%r12,%r9,8),%rdi ;** src
0x000000010ceebee7: mov %rbx,%rsi ;** dest
0x000000010ceebeea: add $0x8,%rsi ;** add offset of 8
0x000000010ceebeee: mov $0x11,%edx ;** length to copy 0x11 * 8 = 136 bytes 
0x000000010ceebef3: movabs $0x10cd5d780,%r10
0x000000010ceebefd: callq *%r10  ;*invokespecial clone
                                 ; - com.github.arnaudroger.beans.Object32::clone@1 (line 111)
                                 ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testClone@4 (line 69)
0x000000010ceebf00: mov 0x7c(%rbx),%eax  ;*getfield f29 ** 7c is 124 bytes, minus the headers 112 that offset 28 ints 
                                         ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testClone@7 (line 69)
0x000000010ceebf03: add $0x20,%rsp
0x000000010ceebf07: pop %rbp
0x000000010ceebf08: test %eax,-0xb154f0e(%rip)  # 0x0000000101d97000
                                                ;   {poll_return} *** SAFEPOINT POLL ***
0x000000010ceebf0e: retq

asm копирование


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


Строки с 16-й по 26-ю.


0x0000000109c7b1cc: mov 0xc(%rsi),%r11d  ;*getfield original
                                         ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testCopy@5 (line 63)
0x0000000109c7b1d0: mov 0x7c(%r12,%r11,8),%eax  ;*getfield f29 ** 7c is 124 bytes, minus the headers 112 that offset 28 ints
                                                ; - com.github.arnaudroger.beans.Object32::@230 (line 67)
                                                ; - com.github.arnaudroger.Object32CopyVsCloneEABenchmark::testCopy@8 (line 63)
                                                ; implicit exception: dispatches to 0x0000000109c7b1e1
0x0000000109c7b1d5: add $0x10,%rsp
0x0000000109c7b1d9: pop %rbp
0x0000000109c7b1da: test %eax,-0x47b81e0(%rip)  # 0x00000001054c3000
                                                ;   {poll_return} *** SAFEPOINT POLL ***
0x0000000109c7b1e0: retq

Итог


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

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

https://habrahabr.ru/post/334176/


Метки:  

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

Четверг, 27 Июля 2017 г. 15:38 + в цитатник

Предисловие


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

Решаемые задачи


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

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

Состав оборудования


В качестве сердца системы был выбран выпускаемый малой серией программируемый логический контроллер «iТеплица -малый контроллер». Фото контроллера со снятой крышкой под спойлером.

Фото контроллера
image

И это именно программируемый логический контроллер — для него есть среда разработки, которая позволяет не только написать программу на промышленных языках стандарта IEC 61131-3, но и произвести онлайн отладку с режимом мониторинга. В качестве среды программирования используется демонстрационная версия программы GX Developer-FX. Сам контроллер полностью совместим с серийным контроллером Mitsubishi FX2N. Как видно на фото, система построена на микроконтроллере STM32F103C8T6.

Немного о его возможностях:

1. Количество шагов выполнения программы -2000. О шагах более подробно расскажу немного ниже.
2. Гальванически изолированная шина интерфейса 1-wire. Позволяет работать со 128 датчиками. При помощи утилиты настройки производит поиск датчиков и сохранение в энергонезависимой памяти контроллера.
3. Гальванически изолированная шина интерфейса RS-485 с поддержкой протокола обмена modbus RTU. При помощи утилиты настройки может работать как в режиме мастера, так и в режиме слейва.Всего может быть поддержано до 64 слейвов( при работе контроллера в режиме мастера).
4. Программирование и отладка производятся при помощи micro -USB кабеля.
5. Имеет 8 дискретных входов и 8 дискретных выходов, из которых 2 выхода снабжены реле с нагрузочной способностью 5A 250V AC. Также имеет 2 аналоговых входа.
6. Имеется 2-й порт протокола modbus RTU — но он имеет TTL интерфейс и предназначен для подключения к системам сбора данных. Может работать тольков режиме слейва.
7. Используется операционная система реального времени.

Следующее действующее лицо — это датчик освещённости. Он построен на основе микроконтроллера STM32F030 с использованием операционной системы реального времени. Имеет последовательный интерфейс стандарта RS-485 с поддержкой протокола обмена Modbus RTU для обмена данными и настройки параметров. Корпус исполнения IP67 позволяет производить установку под открытым небом. Фотография под спойлером.

Датчик освещённости в сборе
image

Для любопытных читателей сразу скажу — сенсор BH1750 позволяет произвести замеры освещённости больше 100 тыс. люкс за счёт изменения ширины окна измерения.
И ещё есть одна отличительная особенность данного датчика от тысяч других — он сам считает накопленную мощность в Дж/см2/час и по запросу передаёт мастеру сети. При поставке каждый сенсор имеет собственный сертификат калибровки на мощность излучения 1000 Дж/см2/час и сразу готов к применению.

Сам объект управления


В качестве объекта используется небольшой участок земли размерами 5,5м х 25 м, оборудованный 6 линиями капельного полива. Капельные трубки 16 мм с капельницами через каждые 30 см с водовыливом 1,6л/час. То есть в теории за один час такая система может израсходовать 800 литров воды. Но так как мы не используем бустерный насос для поднятия давления воды, то под давлением самотёка значения расхода оказываются значительно ниже.

Ход работ: монтаж на объекте


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

Датчики уровня в бочке
image

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

Датчик освещённости - вид сверху
image

И для наглядности вид снизу:

Датчик освещённости - вид снизу
image

А теперь монтаж контроллера и блока питания для клапана — уж не судите строго, монтаж сделан «как есть». Всегда можно сделать намного более аккуратно — но мы тут рассматриваем не качество монтажа, а работу самой системы.

Контроллер на окне
image

А теперь фотография монтажа блока механического дискового фильтра и клапана полива.

Клапан с фильтром
image


Работа программы


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

Итак — наполнение бочки. При этом контролируется тай-аут времени работы насоса.Если наполнение будет длиться больше, чем 30 минут — то отключаем насос и показываем сигнал аварии. Если бочка наполнена в отведённое время — то ставим флаг готовности к поливу. Полив возможен только между 5:00 и 17:35. Время может быть очень легко изменено. Первый полив будет включен, как только утренняя доза поглощённой солнечной энергии будет больше 180 Дж/см2/час. После этого каждый следующий полив будет включен через 300 Дж/см2/час. Если солнечная активность низка и мы до 10:35 не набрали утренней дозы, то будет один раз включен полив и система будет ждать увеличения солнечной активности. Для опустошения бочки отводится тайм-аут 50 минут. Если время превышено — то значит проблемы с фильтром или клапаном. В этом случае выдаём предупреждающий сигнал и отключаем полив. Также контролируется количество воды, израсходованной на полив — если было использовано больше 8 бочек, то полив останавливается и выдаётся сигнал предупреждения. Сигнал не квитируемый — он будет сброшен утром следующего дня. Время выполнения такой программы в контроллере составляет 2 мсек.
Ниже под спойлером показан процесс отладки — онлайн монитор программы в режиме исполнения.

Онлайн-отладка программы
image

Я не буду тут описывать все временные защитные задержки и логику программы — вы можете посмотреть это всё сами в программе. Вот тут находится архив с программой и распечатанная версия в формате pdf.

Результаты работы и заключение


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

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

Дальнейший путь


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

https://habrahabr.ru/post/334192/


Метки:  

[Перевод] Методология и руководство по стилю кода компании Ronimo

Четверг, 27 Июля 2017 г. 15:17 + в цитатник

Метки:  

Разбор задач финала Яндекс.Алгоритма 2017

Четверг, 27 Июля 2017 г. 14:45 + в цитатник

На днях завершился Яндекс.Алгоритм 2017 — наш чемпионат по спортивному программированию. В финальном раунде 25 финалистам нужно было за два с половиной часа решить шесть задач. Первое место вновь завоевал Геннадий Короткевич из питерского ИТМО — это уже четвёртая его победа после состязаний 2013, 2014 и 2015 года. Никола Йокич из Швейцарской высшей технической школы Цюриха и выпускник Университета Токио Макото Соэдзима стали вторым и третьим, повторив свои прошлогодние результаты. Вот как распределились денежные призы: победа — 300 тысяч рублей, второе место — 150 тысяч, третье — 90 тысяч.




Заявки на участие в Алгоритме 2017 подали 4840 человек. Более 60% из них — россияне. На втором месте по количеству заявок — Беларусь, далее следуют Украина, Индия и Китай. В общей сложности на чемпионат зарегистрировались жители нескольких десятков стран, включая Сингапур, Камерун, Венесуэлу и Перу.


Мы по традиции публикуем формулировки и разобранные решения задач финала.


Задача A. Горная задача


Автор задачи: Глеб Евстропов (Яндекс, НИУ ВШЭ).


Имя входного файла: Имя выходного файла: Ограничение по времени: Ограничение по памяти:
стандартный ввод стандартный вывод 5 секунд (13 для Java) 512 мегабайт

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


Единственная имеющаяся трасса состоит из $n$ контрольных пунктов, пронумерованных от $1$ до $n$. Контрольный пункт $i$ находится на высоте $h_i$, причём никакие два пункта не находятся на одной высоте. Поскольку на трассе только один подъёмник, то спуск всегда начинается в контрольном пункте номер $s$ и заканчивается в контрольном пункте номер $t$. Некоторые $m$ пар контрольных пунктов соединены участками трассы, которые ведут от более высокого контрольного пункта к более низкому.


Привлекательность участка трассы, непосредственно соединяющего контрольный пункт $u$ с контрольным пунктом $v$, равна разности высот пунктов, то есть $h_u - h_v$. При этом привлекательностью маршрута, последовательно посещающего контрольные пункты $v_1, v_2, \ldots, v_k$, называется минимальная из привлекательностей его участков, то есть минимум среди величин $h_{v_1} - h_{v_2}, h_{v_2} - h_{v_3}, \ldots, h_{v_k} - h_{v_{k - 1}}$.


Туристов, посещающих курорт Аркадия, с одной стороны волнует привлекательность маршрута, а с другой — его длина, которая определяется как количество участков трассы в маршруте. Поскольку Аркадий уже не силён в решении подобного рода задач, то именно вам придётся вычислить для каждой возможной длины маршрута $x$ от $1$ до $n - 1$ максимально возможную привлекательность маршрута из $s$ в $t$, имеющего длину не менее $x$.


Формат входных данных


В первой строке входных данных записаны четыре целых числа $n$, $m$, $s$ и $t$ ($2 \leq n \leq 100\,000$, $1 \leq m \leq 200\,000$, $1 \leq s, t \leq n$, $s \ne t$) — количество контрольных пунктов на трассе, количество участков трассы, номер стартового контрольного пункта и номер финишного контрольного пункта соответственно.


Во второй строке записаны $n$ различных целых чисел $h_1, h_2, \ldots, h_n$ ($0 \leq h_i \leq 100\,000$) — высоты, на которых расположены контрольные пункты.


Далее следуют $m$ строк, описывающих участки трассы. В $i$-й из них записаны два числа $u_i$ и $v_i$ ($1 \leq u_i, v_i \leq n$, $u_i \ne v_i$), означающих, что $i$-й участок трассы идёт от контрольного пункта $u_i$ к контрольному пункту $v_i$. Гарантируется, что никакие два участка трассы не соединяют напрямую одну и ту же пару контрольных пунктов, что высота контрольного пункта $u_i$ больше высоты контрольного пункта $v_i$, и что существует хотя бы один маршрут от контрольного пункта $s$ до контрольного пункта $t$.


Формат выходных данных


Выведите $n - 1$ чисел по одному в строке, $i$-е из которых должно равняться максимально возможной привлекательности маршрута из $s$ в $t$, имеющего длину не меньше $i$. Если для некоторого $i$ не существует маршрута длиной не меньше $i$, то выведите в соответствующей строке $-1$.


Примеры


стандартный ввод стандартный вывод
4 4 2 4
3 4 2 1
2 4
2 1
1 3
3 4
3
1
1
3 2 1 3
3 2 1
1 2
1 3
2
-1
5 10 1 5
8 6 4 3 1
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
7
3
2
1

Замечание


В первом примере существует прямой участок трассы из стартового контрольного пункта в конечный. Привлекательность такого маршрута равна $3$, а длина $1$. Также существует путь длины $3$, проходящий через контрольные пункты $2$, $1$, $3$ и $4$, c привлекательностью равной $1$. Для $x = 2$ ответом будет являться данный путь длины $3$, поскольку это самый привлекательный путь длиной не менее $2$.


Разбор задачи A


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


Опишем два способа решить задачу за квадратичное время. Способ первый: перебрать все возможные значения $x$ увлекательности маршрута, после чего оставить в графе только рёбра $(u, v)$, такие что $h_u - h_v \geq x$ и найти самый длинный путь из $s$ в $t$, использующий только такие рёбра. Время работы такого решения — $O(mC)$, где $C = h_s - h_t + 1$. Второй способ: динамическое программирование $d(v, len)$ — максимально возможная увлекательность маршрута длины $len$ из $v$ в $t$. Чтобы вычислить значение $d(v, len)$ рассмотрим все выходящие рёбра $(v, u)$ и используем релаксацию $d(v, len) = max(d(v, len), min(d(u, len - 1), h_v - h_u))$. Время работы такого решения — $O(nm)$.


Заметим, что первое из описанных квадратичных решений быстро найдёт ответ для путей с маленькой увлекательностью, а второе решение лучше подойдёт для коротких путей, и попробуем скомбинировать эти два подхода. Действительно, для пути длины $x$ верно, что его увлекательность не превосходит $C / x$, и наоборот, путь увлекательностью $y$ не может иметь длину, больше чем $C / y$. Введём параметр $k = \sqrt{C}$. Для любого пути из $s$ в $t$ справедливо, что либо его длина, либо его увлекательность не превосходят $k$. Применим оба решения из второго абзаца, запуская первое только для $c \leq k$, а во втором используя только значения $len \leq k$. Итоговая сложность полученного решения: $O(m\sqrt{C})$. Отметим, что при желаниии такое решение можно реализовать с использованием $O(m)$ дополнительной памяти.




Задача B. Беспилотный автомобиль


Автор задачи: Максим Ахмедов (Яндекс, МГУ, НИУ ВШЭ).


Имя входного файла: Имя выходного файла: Ограничение по времени: Ограничение по памяти:
стандартный ввод стандартный вывод 4 секунды 512 мегабайт

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


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


Полигон состоит из $n$ площадок, соединённых $n - 1$ дорогами. Площадки пронумерованы числами от $1$ до $n$, а беспилотный автомобиль исходно находится на $1$-й площадке. Цель тестирования состоит в том, чтобы провести за минимальное время автомобиль по маршруту, проходящему по каждой из площадок хотя бы раз, если известно, что автомобиль проезжает одну дорогу за одну минуту, а временем на повороты и развороты можно пренебречь.


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


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


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


Формат входных данных


В первой строке находится целое число $n$ ($1 \leq n \leq 200\,000$) — количество площадок.


Во второй строке находятся целые числа $a_1, a_2, \ldots, a_n$ ($0 \leq a_i \leq 10^6$), где $a_i$ — количество минут, на которое хватает заряда в радиомаяке на площадке $i$.


В последующих $n - 1$ строках находятся описания дорог, каждое из которых состоит из двух целых чисел $u_i$ и $v_i$ ($1 \leq u_i, v_i \leq n$, $u_i \neq v_i$) — номеров соединяемых площадок.


Формат выходных данных


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


Примеры


стандартный ввод стандартный вывод
7
0 3 0 2 4 3 3
1 2
1 3
3 4
1 5
5 6
6 7
9
4
0 1 1 2
1 2
2 3
1 4
-1

Замечание


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


  • В течение 2 минут держим включённым радиомаяк на площадке 4, в результате чего машина оказывается на площадке 4, а маяк разряжается.
  • В течение 3 минут держим включённым радиомаяк на площадке 2, в результате чего машина оказывается на площадке 2, а маяк разряжается.
  • В течение 2 минут держим включённым радиомаяк на площадке 5, в результате чего машина оказывается на площадке 5, а у маяка остаётся 2 минуты заряда.
  • В течение 2 минут держим включённым радиомаяк на площадке 7, в результате чего машина оказывается на площадке 7, а у маяка остаётся 1 минута заряда.

Таким образом за 9 минут можно посетить все площадки.


Разбор задачи B


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


Таким образом, у нас имеется дерево, в вершине 1 которого находится фишка, в котором также имеется набор кнопок: в вершине $i$ находится $a_i$ кнопок, нажимая на которые, надо провести автомобиль по всему дереву.


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


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


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


Значит, ответ на нашу задачу (в упрощённой постановке) всегда $2n - 2$ (такова длина любого эйлерова обхода дерева), и единственное, что надо проверить — что существует сопоставление кнопок рёбрам, удовлетворяющее условию, что ребру соответствует кнопка в поддереве, на которое указывает данное ребро. Данная задача является, на самом деле, задачей о паросочетании в двудольном графе (одной долей которого является множество ориентированных рёбер, а другой — множество кнопок), насыщающего одну из долей. На вопрос о наличии подобного паросочетания даёт ответ классический факт теории паросочетаний, известный как лемма Холла.


Введём обозначения — пусть $E$ это множество ориентированных рёбер дерева, $B$ — множество кнопок, а $s(e)$ для $e \in E$ это множество кнопок, которые находятся в поддереве, на которое указывает ребро $e$ (формально говоря, кнопка $b \in s(e)$ тогда и только тогда, когда конец ребра $e$ лежит между началом ребра $e$ и кнопкой $b$). Тогда лемма Холла гласит, что искомое нами паросочетание существует тогда и только тогда, когда для любого набора ориентированных рёбер $A \subseteq E$ в объединении поддеревьев всех рёбер в $A$ присутствует не меньше кнопок, чем рёбер в множестве $E$:

$|A| \leq \left| \bigcup\limits_{e \in A} s(e)\right|$


Будем для краткости обозначать объединение множеств кнопок в правой части этого неравенства за $s(A)$. Пока неясно, как этот критерий можно эффективно проверить, потому что он состоит из экспоненциального количества условий, соответствующих вариантам выбора подмножества $A$. Однако, как мы увидим, большая часть проверяемых условий является излишней.

Во-первых, если множество $A$ таково, что в $A$ есть одно и то же ребро в двух направлениях, либо есть два ребра $e_1$ и $e_2$, "смотрящих" друг на друга, то тогда $s(A)$ совпадает с множеством всех кнопок в дереве, а значит, вне зависимости от выбора такого множества $A$, в правой части неравенства будет стоять одно и то же ребро. Значит, из всех данных неравенств достаточно проверить одно наиболее сильное, а именно то, в котором $A$ совпадает с $E$, причём эта проверка имеет наглядный смысл — фактически мы проверяем, что ориентированных рёбер не больше, чем всего кнопок имеется в дереве. Обозначим это условие за $(1)$.

В противном случае, поступим похожим образом. Если $e \in A$, то аналогичным образом можно добавить в $E$ все рёбра, которые лежат в поддереве $e$ и смотрят в ту же сторону, что и $e$ (то есть, добавление которых не приводит к случаю, описанному выше). Действительно, добавление таких рёбер не изменяет множества покрываемых поддеревьями кнопок, а значит, это только усиливает проверяемое нами условие.


Теперь понятно, какую структуру имеет множество $A$ и соответствующее ему множество кнопок $s(A)$. Множество $A$ состоит из набора непересекающихся поддеревьев, в каждом из которых взяты все рёбра, смотрящие в направлении от корня поддерева, а $s(A)$ является объединением кнопок по всем этим поддеревьям. Заметим, что если всё множество $A$ не удовлетворено (то есть, $|A| > |s(A)|$), то тогда хотя бы в одно из поддеревьев в составе $A$ тоже не удовлетворено, иначе можно суммированием соответствующих неравенств для всех поддеревьев показать, что множество $A$ тоже должно быть удовлетворено.


Значит, мы окончательно поняли, для каких подмножеств $A$ достаточно проверять критерий из леммы Холла: достаточно рассмотреть $2n - 2$ подмножества, каждое из которых задаётся "корневым" ориентированным ребром $e$, и содержит все рёбра, смотрящие в направлении $e$ в поддереве $e$. Опять же, у неравенства для данного подмножества есть понятный физический смысл — необходимо, чтобы для любого поддерева кнопок в нём было не меньше, чем неориентированных рёбер в нём плюс один (здесь плюс один берётся от самого корневого ребра). Обозначим это условие для ориентированного ребра $e$ за $(2_e)$.


Условие $(1)$ очевидно можно проверить за линейное время. Условий $(2_e)$ самих по себе линейное количество, но несложным предподсчётом размеров поддеревьев дерева и количеств кнопок в них все условия можно проверить за линейное время в совокупности.


Таким образом, мы научились решать упрощённую версию задачи. Вернёмся к исходной — её отличает от нашей то, что теперь не по каждому ребру мы пройдём в обоих направлениях. Легко видеть, что наш путь теперь характеризуется финальной вершиной $t$, которая обязательно будет листовой, и тогда из множества посещаемых нами ореинтированных рёбер исчезнут рёбра, лежащие на пути от $t$ до стартовой вершины $1$. Подвесим дерево за вершину $1$, тогда по-другому можно сказать, что исчезнут все восходящие рёбра от $t$ до $1$.


Аналогичным образом применим критерий Холла и посмотрим, что изменится. Рассуждение про рёбра, смотрящие друг на друга, по-прежнему верно, и оно даёт нам условие $(1')$: ориентированных рёбер, которые мы пройдём, должно быть не больше, чем кнопок всего. Отметим здесь, что мы пройдём ровно $2n - 2 - depth(t)$ рёбер, таким образом, это условие задаёт нижнюю границу на глубину искомой терминальной вершины $t$: $depth(t) \geq 2n - 2 - |B|$ (заметим, что это условие может быть выполнено автоматически, если $2n - 2 \leq |B|$).


Рассуждение про независимую проверку критерия для подномжеств, задаваемых ориентированным ребром и всеми, которые лежат в его поддереве, по-прежнему верно, но теперь чуть-чуть изменится форма этих подмножеств. А именно, если $e$ — ребро, смотрящее вниз, то для него ничего не поменяется (потому как у нас по сравнению с упрощённой задачей исчезли только рёбра, смотрящие вверх). Если же $e$ — ребро, смотрящее вверх, то в зависимости от выборе вершины $t$ ребро $e$ может либо исчезнуть из двудольного графа (тогда соответствующее ему условие просто проверять не надо), либо же, левая часть соответствующего ему условия уменьшится на количество выпавших рёбер, которые лежат в поддереве $e$ и смотрят в ту же сторону, что и $e$. Нетрудно понять, что величина уменьшения левой части составляет $depth(lca(e, t))$, где $lca(e, t)$ — наименьший общий предок ребра $e$ и терминальной вершины $t$ в подвешенном дереве с корнем в $1$.


Таким образом, мы имеем набор условий следующего вида:


$ 2n - 2 - depth(t) \leq |B| \quad (1') $


Для рёбер, смотрящих вниз:


$ |edgesBelow(e)| + 1 \leq |buttonsBelow(e)| \quad (2_{e\downarrow}') $


Для рёбер, смотрящих вверх:


$ |edgesAbove(e)| + 1 - depth(lca(e, t)) \leq |buttonsAbove(e)|~\text{или}~t~\text{в поддереве}~e \quad (2_{e\uparrow}') $


Условия $(2_{e\downarrow}')$ проверим за линейное время сразу, так как они не зависят от выбора $t$. Оставшиеся два вида условий переформулируем в виде условия на положение вершины $t$. $(1')$, как уже было отмечено, эквивалентно тому, что вершина $t$ достаточно низко, то есть, находится на глубине минимум $2n - 2 - |B|$. Условие $(2_e')$, оказывается, эквивалентно принадлежности $t$ некоторому поддереву нашего дерева: действительно, перенеся в левую часть неравенства $(2_{e\uparrow}')$ слагаемое $depth(lca(e, t))$, получим ограничение снизу на глубину вершины, в которой путь до $t$ ответвляется от пути до $e$. Это означает, что $t$ должно быть ниже ($|edgesAbove(e)| + 1 - |buttonsAbove(e)|$)-й вершины на пути от $s$ до $e$, что тоже является условием принадллежности некоторому поддереву.


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


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




Задача C. Не менее плотный, чем требуется


Автор задачи: Максим Ахмедов (Яндекс, МГУ, НИУ ВШЭ).


Имя входного файла: Имя выходного файла: Ограничение по времени: Ограничение по памяти:
стандартный ввод стандартный вывод 3 секунда 512 мегабайт

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


Рассмотрим неориентированный граф, состоящий из $n$ вершин, пронумерованных целыми числами от $1$ до $n$, и $m$ рёбер $(u_{1}, v_{1}), (u_{2}, v_{2}), \ldots, (u_{m}, v_{m})$. Пусть задан набор из $n$ неотрицательных вещественных чисел $x_1, x_2, \ldots, x_n$, удовлетворяющий условию $x_1 + \ldots + x_n = 1$. Положим:


$\lambda = \sum \limits_{i=1}^{m} \min\{x_{u_i}, x_{v_i}\}$


Требуется найти подграф данного графа, имеющий плотность по меньшей мере $\lambda$. Формально, требуется найти такое непустое множество вершин $A \subseteq \{1, 2, \ldots, n\}$, что $\frac{|E(A)|}{|A|} \geq \lambda$, где $E(A) = \{(u_i, v_i)~\mid~u_i, v_i \in A\}$.


Формат входных данных


В первой строке находятся два целых числа $n$ и $m$ ($1 \leq n \leq 200\,000$, $0 \leq m \leq 200\,000$), количество вершин и рёбер в графе.


Во второй строке находятся $n$ вещественных чисел $x_1, x_2, \ldots, x_n$ ($x_i \geq 0$, $x_1 + \ldots + x_n = 1$), заданных с не более чем шестью знаками после десятичной точки.


В $i$-й из последующих $m$ строк находятся два целых числа $u_i, v_i$ ($1 \leq u_i, v_i \leq n$, $u_i \neq v_i$), задающих вершины, соединённые $i$-м ребром графа. Гарантируется, что в графе отсутствуют кратные рёбра и петли.


Гарантируется, что в графе существует подграф, удовлетворяющий требованию задачи.


Формат выходных данных


В первой строке выведите целое число $k$ ($1 \leq k \leq n$) — количество вершин в искомом множестве $A$.


Во второй строке выведите $k$ целых чисел $d_1, d_2, \ldots, d_k$ — номера вершин, образующих подграф плотности хотя бы $\lambda$.


Ваш ответ будет признан корректным, если плотность выведенного вами подграфа не меньше, чем $\lambda - 10^{-7}$. Вершины разрешается выводить в любом порядке. Если возможных ответов несколько, разрешается вывести любой правильный.


Примеры


стандартный ввод стандартный вывод
4 4
0.2 0.1 0 0.7
1 2
2 3
3 1
3 4
3
1 2 4
5 6
0.25 0 0.25 0.25 0.25
2 1
1 3
3 5
5 4
4 1
1 5
4
1 5 3 4

Замечание


В первом тесте из условия: $\lambda = \min\{0.2, 0.1\} + \min\{0.1, 0\} + \min\{0, 0.2\} + \min\{0, 0.7\} = 0.1 + 0 + 0 + 0 = 0.1$


В подграфе, состоящем из вершин $1$, $2$ и $4$, присутствует единственное ребро $(1, 2)$, поэтому его плотность равна $1/3 > 0.1$.


Во втором тесте из условия:


$\begin{eqnarray} \lambda = \min\{0, 0.25\} + \min\{0.25, 0.25\} + \min\{0.25, 0.25\} + \min\{0.25, 0.25\} + \min\{0.25, 0.25\} + \min\{0.25, 0.25\} \nonumber \\ = 0 + 0.25 + 0.25 + 0.25 + 0.25 + 0.25 = 1.25 \nonumber \end{eqnarray}$


В подграфе, состоящем из вершин $1, 5, 3, 4$, присутствует 5 рёбер $(1, 3), (3, 5), (5, 4), (4, 1), (1, 5)$, поэтому его плотность равна $5/4 = 1.25$.


Разбор задачи C


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


Верен следующий факт. Положим $A(t) = \{v~\mid~x_v \leq t\}$. Утверждается, что ответ всегда можно найти среди всевозможных $A(t)$, то есть, среди подграфов, образованных каким-то суффиксом вершин в порядке сортировки по $x_i$ всегда есть хотя бы один подграф плотности не меньше $\lambda$. Покажем этот факт.


Положим, аналогично, $E(t) = E(A(t)) = \{(u, v)~\mid~x_u \leq t, x_v \leq t\}$. Рассмотрим функцию $f(t) = |E(t)| - \lambda |A(t)|$. Будем рассуждать от противного — предположим, что она строго меньше нуля в каждой точке полуинтервала $[0, M)$ (что как раз значит, что для любого $t$ плотность подграфа $A(t)$, равная $\frac{|E(t)|}{|A(t)|}$ строго меньше $\lambda$).


Обозначим за $M$ максимальное из чисел $x_i$. Тогда $\int\limits_{0}^{M} f(t) dt < 0$. Однако верна следующая цепочка равенств:


$\begin{eqnarray} \int\limits_{0}^{M} f(t) dt = & \int\limits_{0}^{M} |E(t)| - \lambda \int\limits_{0}^{M} |A(t)| \nonumber \\ = & \int\limits_{0}^{M} \sum\limits_{(u, v) \in E} \mathbf{1}\{x_u \leq t, x_v \leq t\}~dt - \lambda \int\limits_{0}^{M} \sum\limits_{v=1}^{n} \mathbf{1}\{x_v \leq t\}~dt \nonumber \\ = & \sum\limits_{(u, v) \in E} \int\limits_{0}^{M} \mathbf{1}\{x_u \leq t, x_v \leq t\}~dt - \lambda \sum\limits_{v=1}^{n} \int\limits_{0}^{M} \mathbf{1}\{x_v \leq t\}~dt \nonumber \\ = & \sum\limits_{(u,v) \in E} \min\{x_u, x_v\} - \lambda \sum\limits_{v=1}^n x_v \nonumber \\ = & \lambda - \lambda = 0 \nonumber \end{eqnarray}$


Таким образом, мы получаем противоречие, значит, хотя бы в одной точке полуинтвервала $[0, M)$ верно неравенство $f(t) > 0$, что доказывает наше утверждение.


Таким образом, решение принимает следующий вид: упорядочиваем вершины по убыванию $x_i$, добавляем вершины одну за одной и поддерживаем количество вершин в текущем подграфе. Если плотность стала не меньше $\lambda$, выводим ответ. Сложность получившегося решения — $O(n \log n + m)$.




Задача D. Магазин шляп


Автор задачи: Михаил Тихомиров (МФТИ)


Имя входного файла: Имя выходного файла: Ограничение по времени: Ограничение по памяти:
стандартный ввод стандартный вывод 2 секунды 512 мегабайт

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


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


К концу дня


Метки:  

Outdoor Wi-Fi: уличные Wi-Fi сети и мосты на оборудовании TP-Link

Четверг, 27 Июля 2017 г. 14:44 + в цитатник

«Рынок становится агрессивнее и хайповее» — Александр Зимин о тенденциях iOS-разработки

Четверг, 27 Июля 2017 г. 14:36 + в цитатник


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

В случае с iOS-разработкой эти усилия прикладывает Александр Зимин. С одной стороны, он настолько активно смотрел видеозаписи сессий WWDC 2017, что недавно даже составил для всех гид по ним. С другой, он организует встречи CocoaHeads, выступает на различных конференциях, а на приближающейся московской Mobius ещё и входит в программный комитет — то есть очень много контактирует с сообществом и видит, чем оно живёт.

Поэтому сейчас, когда неумолимо приближаются релизы iOS 11 и Swift 4, мы решили расспросить Александра о текущем состоянии iOS-разработки сразу с двух ракурсов: и «что происходит с технологиями», и «что происходит с людьми».

— Вступительный вопрос: сколько всего приложений установлено на твоём айфоне?

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

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

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

Вторая группа — те, кто следуют указаниям Apple и поддерживают последние две операционные системы. Как я указал в конце своей статьи, они сейчас смотрят WWDC предыдущего года. Потому что если при выходе iOS 11 они выкинут поддержку iOS 9, будут поддерживать iOS 10 и выше, значит, смогут использовать во всей красе те нововведения, которые показали год назад.

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

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

— А насколько этот WWDC своими анонсами впечатлил iOS-сообщество?

— Он довольно сильно удивил многих разработчиков. Потому что было мнение, что в сфере машинного обучения или дополненной реальности Apple работает исключительно в экспериментальных лабораториях, да еще и в маленьком объёме. И если они их и представят, то это будет с большим запозданием. А тут в Apple взяли и представили множество новых технологий (причём их определение объектов в AR без датчиков одно из самых точных на рынке), в iOS 11 на этом акцент куда больший, чем был в iOS 10.

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

— А насколько изменилось что-то для тех разработчиков, которые ни в какое машинное обучение лезть не собираются, а делают какое-то типовое приложение?

— В iOS есть свои изменения и для них. Одно из явных — это очень быстрое заполнения пароля, и очень многим разработчикам с авторизацией в приложениях стоит это поддержать. Под это на WWDC была целая сессия выделена. Или, например, Drag and Drop — если приложение поддерживает iPad и работу с данными, то скорее всего, есть смысл попробовать.

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

Но если с iOS все понятно, то в Xcode в этот раз очень много нововведений. На мой взгляд, это один из самых больших апдейтов за последние годы.

— И если в случае с iOS некоторые только через год посмотрят на новинки 11-й версии, то вот Xcode обновить стоит всем прямо сейчас, и любой почувствует улучшения?

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

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

И самым интересным, на мой взгляд, стало то, что теперь на Xcode можно писать свои расширения, которые будут поддерживать дополнительные действия с вашим кодом. И это открывает сразу простор для кастомных рефакторингов (вроде упрощения большого if-else в тернарный оператор, если там мало кода). Теперь у разработчиков может быть меньше поводов перейти на AppCode.

— Про AppCode, кстати, любопытно, что сейчас в сообществе с его использованием. Сложно конкурировать с IDE от главного вендора — насколько у JetBrains получается это сделать, и чем именно удаётся привлечь разработчиков?

— Я сам с AppCode работал довольно мало, но вижу, что есть своя каста разработчиков, которые его используют. Обычно это advanced-разработчики, которым не хватает каких-то функций в Xcode: сложных кастомных скриптов, или того же рефакторинга, или, что интересно, в AppCode (с помощью Upsource) встроены pull request-ревью, да и в целом контроль версий хорошо устроен, поэтому можно прям из IDE проверять чужие ветки, чужие PR.

Конечно, многим разработчикам не хочется переходить на новые инструменты, потому что Apple для них уже сделали Xcode, он бесплатный, везде разрекламирован, сразу беты поддерживает. Но, в моём представлении, идеальный вариант — это когда ты комбинируешь два инструмента. Это сложно, но, например, используешь Xcode, когда нужно протестировать будущие беты, а AppCode — когда проверяешь чужие ветки.

— Приближается Swift 4. В чём его технические изменения, несложно нагуглить, а спросить хочется о другом: что сообщество ощущает по его поводу? Жаждет поскорее перейти, или, наоборот, после болезненного перехода к третьему теперь «дует на воду»?

— Ну, переход на 4.0 должен быть безболезненным. Появляется Swift 3.2, который почти не отличается от текущего Swift 3.1.1, и в то же время работает со Swift 4.0. То есть теперь не должно возникнуть знаменитой проблемы, когда какая-то сторонняя библиотека, написанная на Swift, которую вы держите в проекте, не поддерживает последнюю версию, и вы не знаете, как вам поступить — не дописывать же за автора. В моём представлении, переход на Swift 4.0 не займёт больше часа или двух.

Так что особых опасений нет, но при этом ситуация вокруг нового Swift в сообществе такая: о нём попросту почти не говорят. Из-за того, что его изменения «под капотом», незаметные для тех, кто пользуется Swift. Они связаны с производительностью, с исправлением багов и тому подобным. И если о 3.0 разработчики говорили в ключе «всё сломается», то о 4.0 просто ничего не говорят. Получается ситуация «либо плохо, либо никак».

— Год назад компания Apple, агитируя всех активно использовать Swift, при этом сама почти не использовала его в продакшене. Что с этим изменилось за год?

— Год назад ситуация была такой: на Swift был написан калькулятор в iOS, приложение WWDC, и, если не ошибаюсь, один из их новых фреймворков — действительно, довольно мало. А теперь на Swift написан, например, обновлённый App Store — понятно, что это довольно гигантская вещь, полная сложных кастомных UI-компонентов, и, наверное, превосходящая многие другие эппловские приложения по сложности. И в Xcode теперь две системы на Swift. Во-первых, новая билд-система, призванная ускорить сборку проекта, а во-вторых, source editor — эта не написана с нуля, а портирована из Swift Playgrounds для iPad. Так что, как видим, Apple сделал очень много шагов в сторону Swift за последний год.

— К вопросу о том, что source editor портирован с iPad. Приложение Swift Playgrounds ощущается как баловство — «дать детям бесплатно первую дозу Swift на планшете, чтобы потом они продолжили всерьёз уже на Mac». А слова о том, что оттуда перетащили что-то в десктопный Xcode, звучат так, как будто Apple подошли к айпэду очень серьёзно, поставив в некоторых вопросах вперёд десктопа. Где правда?

— Ну, в данном случае iPad выступал скорее как тестовое устройство, на котором они проверили работу нового source editor, и, когда он работал достаточно хорошо, смогли перенести в Xcode. Та же новая билд-система является не базовой, а дополнительной опцией, можно при желании собирать проект со старой. А вот source editor уже достаточно обкатан, чтобы его вставили единственным вариантом.

Но я так понимаю, что на Swift Playgrounds для iPad у них действительно амбициозные планы, а не просто «выпустили и забыли». Буквально пару месяцев назад они заключили контракт, благодаря которому теперь именно с iPad на Swift можно писать под многих роботов и дронов. Мне кажется, это мечта многих детей.

— На WWDC в числе прочего Apple озвучили, сколько суммарно денег получили все iOS-разработчики, и по сравнению с прошлым годом сумма резко увеличилась. А насколько то, что ты видишь в сообществе, подтверждает эти красивые числа? По ощущениям, стало ли в iOS-разработке больше денег?

— Смотря о чём говорить. Если про личные проекты — в моём представлении, действительно стало больше. Мой знакомый выпустил текстовый квест с двумя экранами. Текстовый квест — это когда на экране написан какой-то текст, у игрока есть несколько вариантов выбора, он выбирает и видишь следующий текст. Такое было популярно в 90-х, ну и сейчас среди видных представителей Lifeline…. Сюжет был написан непрофессионалом и довольно быстро. Понятно, что технически такое приложение пишется за пару часов. И в итоге при затратах на рекламу примерно в $600 была прибыль $10 000 за месяц.

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

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

— По твоим ощущениям, в какую сторону сейчас меняется сообщество iOS-разработчиков — и вообще в мире, и конкретно в Москве?

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

Но в то же время рынок становится консервативнее. Пять лет назад он был относительно новым, сюда приходили люди, которые хотели себя попробовать в чём-то новом, готовые ввязаться в авантюру. Сейчас многие из них находятся на рынке уже 5-6 лет и не очень принимают какие-то новинки. Для них те же машинное обучение или дополненная реальность выглядят чем-то рискованным и «накрученным». Хотя в своё время, когда они пришли на мобильный рынок, он был таким же для предыдущих, как сейчас ML. Это конфликт поколений, можно сказать.

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

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

— К тому, что московский рынок вырос: а как на этом фоне развиваются встречи CocoaHeads?

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

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

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

— Поскольку последний петербургский Mobius был двухдневным, а в Москве будет один день, тут будет несколько другой формат. С одной стороны, докладов будет количественно меньше. А с другой, это позволяет сделать отбор максимально строгим и показать «лучших из лучших».

И при этом, поскольку в Москве конференция пройдёт впервые, тут есть ощущение эксперимента: посмотрим, как поведёт себя московская аудитория.

— Тебе как раз знакомо и московское iOS-сообщество, и петербургское — а чем, на твой взгляд, они различаются? По-твоему, есть ли разница между тем, как пишут под iOS в двух городах?

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

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




Mobius пройдёт в Москве 11 ноября. Тема доклада Александра сейчас ещё не сформулирована окончательно, но очевидно, что она будет связана с iOS. Сейчас на сайте конференции уже можно увидеть имена и нескольких других спикеров (как по iOS, так и по Android), а также приобрести билеты. И со временем они дорожают — так что выгоднее не ждать, пока станет известно больше подробностей.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334244/


Метки:  

[Перевод] JavaScript без this

Четверг, 27 Июля 2017 г. 14:26 + в цитатник
Ключевое слово this в JavaScript можно назвать одной из наиболее обсуждаемых и неоднозначных особенностей языка. Всё дело в том, что то, на что оно указывает, выглядит по-разному в зависимости от того, где обращаются к this. Дело усугубляется тем, что на this влияет и то, включён или нет строгий режим.



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

Означает ли это, что «борьба с this» окончена? Нет конечно. Если поразмыслить, можно обнаружить, что задачи, которые традиционно решают с привлечением функций-конструкторов и this, можно решить и другим способом. Этот материал посвящён описанию именно такого подхода к программированию: никаких new и никаких this на пути к более простому, понятному и предсказуемому коду.

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

Есть идея


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

function makeAdder(base) {
  let current = base;
  return function(addition) {
    current += addition;
    return current;    
  }
}

Функция makeAdder() принимает параметр base и возвращает другую функцию. Эта функция принимает числовой параметр. Кроме того, у неё есть доступ к переменной current. Когда её вызывают, она прибавляет переданное ей число к current и возвращает результат. Между вызовами значение переменной current сохраняется.

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

Замыкания — весьма мощная возможность JavaScript. Их корректное использование позволяет создавать сложные программные системы с надёжно разделёнными уровнями абстракции.

Выше мы возвращали из функции другую функцию. С тем же успехом, вооружившись имеющимися у нас знаниями о замыканиях, из функции можно возвратить объект. Этот объект будет иметь доступ к локальному окружению. Фактически, его можно воспринимать как открытое API, которое даёт доступ к функциям и переменным, хранящимся в замыкании. То, что мы только что описали, называется «шаблон revealing module».

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

Вот пример:

let counter = (function() {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();
counter.increment();
counter.increment();
console.log(counter.value()); // выводит в лог 2

Как видите, переменная privateCounter — это данные, с которыми нам надо работать, скрытые в замыкании и недоступные напрямую извне. Общедоступные методы increment(), decrement() и value() описывают операции, которые можно выполнять с privateCounter.

Теперь у нас есть всё необходимое для программирования на JavaScript без использования this. Рассмотрим пример.

Двухсторонняя очередь без this


Вот простой пример использования замыканий и функций без this. Это — реализация известной структуры данных, которая называется двухсторонняя очередь (deque, double-ended queue ). Это — абстрактный тип данных, который работает как очередь, однако, расти и уменьшаться наша очередь может в двух направлениях.

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

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

  • create: Создание нового объекта двухсторонней очереди
  • isEmpty: Проверка, пуста ли очередь
  • pushBack: Добавление нового элемента в конец очереди
  • pushFront: Добавление нового элемента в начало очереди
  • popBack: Удаление и возврат последнего элемента очереди
  • popFront: Удаление и возврат первого элемента очереди

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

Итак, для начала нужна переменная, назовём её data, которая будет хранить данные каждого элемента очереди. Кроме того, нам нужны указатели на первый и последний элементы, на голову и хвост очереди. Назовём их, соответственно, head и tail. Так как очередь мы создаём на основе связного списка, нам нужен способ связи элементов, поэтому для каждого элемента требуются указатели на следующий и предыдущий элементы. Назовём эти указатели next и prev. И, наконец, требуется отслеживать количество элементов в очереди. Воспользуемся для этого переменной length.

Теперь поговорим о группировке вышеописанных переменных. Каждому элементу очереди, узлу, нужна переменная с его данными — data, а также указатели на следующий и предыдущий узлы — next и prev. Исходя из этих соображений создадим объект Node, представляющий собой элемент очереди:

let Node = {
  next: null,
  prev: null,
  data: null
};

Каждая очередь должна хранить указатели на собственную голову и хвост (переменные head и tail), а также сведения о собственной длине (переменная length). Исходя из этого, определим объект Deque следующим образом:

let Deque = {
  head: null,
  tail: null,
  length: 0
};

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

module.exports = LinkedListDeque = (function() {
  let Node = {
    next: null,
    prev: null,
    data: null
  };
  let Deque = {
    head: null,
    tail: null,
    length: 0
  };
 // тут нужно вернуть общедоступное API
})();

Теперь, после того, как переменные помещены в замыкание, можно описать метод create(). Он устроен довольно просто:

function create() {
  return Object.create(Deque);
}

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

function isEmpty(deque) {
  return deque.length === 0
}

Этому методу мы передаём объект двухсторонней очереди, deque, и проверяем равняется ли его свойство length нулю.

Теперь пришло время метода pushFront(). Для того, чтобы его реализовать, надо выполнить следующие операции:

  1. Создать новый объект Node.
  2. Если очередь пуста, нужно установить указатели головы и хвоста очереди на новый объект Node.
  3. Если очередь не пуста, нужно взять текущий элемент очереди head и установить его указатель prev на новый элемент, а указатель next нового элемента установить на тот элемент, который записан в переменную head. В результате первым элементом очереди станет новый объект Node, за которым будет следовать тот элемент, который был первым до выполнения операции. Кроме того, надо не забыть обновить указатель очереди head таким образом, чтобы он ссылался на её новый элемент.
  4. Увеличить длину очереди, инкрементировав её свойство length.

Вот как выглядит код метода pushFront():

function pushFront(deque, item) {
  // Создадим новый объект Node
  const newNode = Object.create(Node);
  newNode.data = item;
  
  // Сохраним текущий элемент head
  let oldHead = deque.head;
  deque.head = newNode;
  if (oldHead) {
    // В этом случае в очереди есть хотя бы один элемент, поэтому присоединим новый элемент к началу очереди
    oldHead.prev = newNode;
    newNode.next = oldHead;
  } else {// Если попадаем сюда — очередь пуста, поэтому просто запишем новый элемент в tail.
    deque.tail = newNode;
  }
  // Обновим переменную length
  deque.length += 1;
  
  return deque;
}

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

function pushBack(deque, item) {
  // Создадим новый объект Node
  const newNode = Object.create(Node);
  newNode.data = item;
  
  // Сохраним текущий элемент tail
  let oldTail = deque.tail;
  deque.tail = newNode;
if (oldTail) {
    // В этом случае в очереди есть хотя бы один элемент, поэтому присоединим новый элемент к концу очереди
    oldTail.next = newNode;
    newNode.prev = oldTail;
  } else {// Если попадаем сюда — очередь пуста, поэтому просто запишем новый элемент в head.
    deque.head = newNode;
  }
  // Обновим переменную length
  deque.length += 1;
  
  return deque;
}

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

return {
 create: create,
 isEmpty: isEmpty,
 pushFront: pushFront,
 pushBack: pushBack,
 popFront: popFront,
 popBack: popBack
}

Здесь, помимо тех методов, которых мы описали выше, есть и те, которые пока не созданы. Ниже мы к ним вернёмся.

Как же всем этим пользоваться? Например, так:

const LinkedListDeque = require('./lib/deque');
d = LinkedListDeque.create();
LinkedListDeque.pushFront(d, '1'); // [1]
LinkedListDeque.popFront(d); // []
LinkedListDeque.pushFront(d, '2'); // [2]
LinkedListDeque.pushFront(d, '3'); // [3]<=>[2]
LinkedListDeque.pushBack(d, '4'); // [3]<=>[2]<=>[4]
LinkedListDeque.isEmpty(d); // false

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

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


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

  1. Основываясь на рассмотренных выше примерах реализации методов, создайте остальные. А именно, напишите функции popBack() и popFront(), которые, соответственно, удаляют и возвращают первый и последний элементы очереди.

  2. В этой реализации двухсторонней очереди используется связный список. Ещё один вариант основан на обычных массивах JavaScript. Создайте все необходимые для двухсторонней очереди операции, используя массив. Назовите эту реализацию ArrayDeque. И помните — никаких this и new.

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

  4. Ещё один способ реализации двухсторонних очередей заключается в одновременном использовании массивов и связных списков. Такую реализацию можно назвать MixedQueue. При таком подходе сначала создают массив фиксированного размера. Назовём его block. Пусть его размер будет 64 элемента. В нём будут храниться элементы очереди. При попытке добавить в очередь больше 64-х элементов создают новый блок данных, который соединяют с предыдущим с помощью связного списка по модели FIFO. Реализуйте методы двухсторонней очереди, используя этот подход. Каковы преимущества и недостатки такой структуры? Запишите свои выводы.

  5. Эдди Османи написал книгу «Шаблоны проектирования в JavaScript». Там он говорит о недостатках шаблона revealing module. Один из них заключается в следующем. Если приватная функция модуля использует общедоступную функцию того же модуля, эту общедоступную функцию нельзя переопределить извне, пропатчить. Даже если попытаться это сделать, приватная функция всё равно будет обращаться к исходной приватной реализации общедоступной функции. То же самое касается и попытки изменения извне общедоступной переменной, доступ к которой даёт API модуля. Разработайте способ обхода этого недостатка. Подумайте о том, что такое зависимости, как инвертировать управление. Как обеспечить то, чтобы все приватные функции модуля работали с его общедоступными функциями так, чтобы у нас была возможность контролировать общедоступные функции. Запишите свои идеи.

  6. Напишите метод, join, который позволяет соединять две двухсторонних очереди. Например, вызов LinkedListDeque.join(first, second) присоединит вторую очередь к концу первой и вернёт новую двухстороннюю очередь.

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

  8. Разработайте неразрушающий механизм обхода очереди в обратном порядке.

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

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

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

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

  3. Полином — это выражение, которое может быть записано в виде an * x^n + an-1*x^n-1 + ... + a1x^1 + a0. Здесь an..a0 — это коэффициенты полинома, а n…1 — показатели степени. Создайте реализацию структуры данных для работы с полиномами, разработайте методы для сложения, вычитания, умножения и деления полиномов. Ограничьтесь только упрощёнными полиномами. Добавьте тесты для проверки правильности решения. Обеспечьте, чтобы все методы, возвращающие результат, возвращали бы его в виде двухсторонней очереди.

  4. До сих пор предполагалось, что вы используете JavaScript. Выберите какой-нибудь другой язык программирования и выполните все предыдущие упражнения на нём. Это может быть Python, Go, C++, или что угодно другое.

Итоги


Надеюсь, упражнения вы выполнили и узнали с их помощью что-нибудь полезное. Если вы думаете, что преимущества отказа от использования this стоят усилий по переходу на новую модель программирования, взгляните на eslint-plugin-fp. С помощью этого плагина можно автоматизировать проверки кода. И, если вы работаете в команде, прежде чем отказываться от this, договоритесь с коллегами, иначе, при встрече с ними, не удивляйтесь их угрюмым лицам. Хорошего вам кода!

Уважаемые читатели! Как вы относитесь к this в JavaScript?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334222/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1068 1067 [1066] 1065 1064 ..
.. 1 Календарь