...

суббота, 27 июня 2015 г.

Как же программа работает со всеми этими ошибками?

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

Введение


Пара вводных слов для читателей, ещё не знакомых с нашим инструментом. Мы разрабатываем анализатор PVS-Studio для нахождения ошибок в исходном коде приложений, написанных на C/C++. Самый лучший способ показать, что он умеет, это проверять открытые проекты и находить в них ошибки. Найденные ошибки мы собираем в базу. Если в проекте мы находим интересные на наш взгляд ошибки, то пишем статью. Всем желающим предлагаю взглянуть на обновляемый список статей.

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

Самое важное


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

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

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

Если ошибка зависит от входных данных, то ей уже легче спрятаться. Представим, что программист разрабатывает графический редактор. Он протестировал работу приложения на картинках с разрешением 100x100 и 300x400. И хотя он написал плохой код, у него всё работало. К счастью в компании есть отдел тестирования, в котором заметили, что программа не работает с вытянутыми картинками разрешением 100x10000. Обратите внимание, что ошибка прожила немного дольше.

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

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

Ответ на вопрос


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

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

QV4::ReturnedValue
QQuickJSContext2DPrototype::method_getImageData(....)
{
  ....
  qreal x = ctx->callData->args[0].toNumber();
  qreal y = ctx->callData->args[1].toNumber();
  qreal w = ctx->callData->args[2].toNumber();
  qreal h = ctx->callData->args[3].toNumber();
  if (!qIsFinite(x) || !qIsFinite(y) ||
      !qIsFinite(w) || !qIsFinite(w))
  ....
}

Функция содержит ошибку. Не проверяется значение 'h'. Вместо этого дважды проверяется переменная 'w'. Это ошибка? Да, это ошибка. Но вероятность, что она проявит себя весьма мала.

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

И поэтому она прожила в коде библиотеки Qt долгое время, прежде чем была обнаружена с помощью PVS-Studio. Кстати, если кто-то хочет прочитать подробнее о проверке библиотеки, то предлагаю взглянуть на нашу статью "Проверка фреймворка Qt 5".

Подведём итог.

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

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

То есть ошибки есть. Их даже много (проверка N1, N2, N3, N4). Но, запуская Chromium, вы вряд ли с ними столкнётесь. Нужно будет приложить усилие и, возможно, очень большое, чтобы суметь попасть в ветвь кода, содержащую ошибку.

PVS-Studio не нужен?


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

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

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

Самое плохое, что можно делать, это прогонять анализатор незадолго до релиза. В этом нет никакого прока. Огромное количество ошибок, которые мог бы найти анализатор, к этому моменту будут исправлены ценой пота и крови. Эти неэффективно (пример, как зря потратить 50 часов времени). А потом после бессонных ночей в отладчике, переписываний с тестерами, разработчики запускают анализатор и получают всего пару полезных сообщений. Ведь всё самое страшное они исправили самостоятельно, потратив массу времени и сил. Зачем? Это какой-то Epic Fail.

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

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

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

PVS-Studio нужен!


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

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

static void GetArrayOfSpeakers(....)
{
  Speakers.Reset();
  uint32 ChanCount = 0;
  // Build a flag field of the speaker outputs of this device
  for (uint32 SpeakerTypeIndex = 0;
       SpeakerTypeIndex < ESpeaker::SPEAKER_TYPE_COUNT,    //<==
       ChanCount < NumChannels; ++SpeakerTypeIndex)
  {
    ....
  }
  check(ChanCount == NumChannels);
}

Вместо оператора && случайно написали запятую. Незначительной такую ошибку назвать нельзя. Она попала в систему контроля версий и уверен доставила бы проблемы. К счастью, анализатор PVS-Studio оказался на страже.

Желающим поподробнее узнать историю нашей работы с компанией Epic Games предлагаю познакомиться со статьёй "Как команда PVS-Studio улучшила код Unreal Engine".

Заключение


Предлагаю не откладывать и попробовать наш анализатор кода PVS-Studio на своём проекте. Вы можете скачать его здесь. Интерфейс анализатора весьма прост, но предлагаю взглянуть на статью "PVS-Studio для Visual C++". Возможно, вы почерпнете для себя полезные советы по использованию анализатора. Например, многие не догадываются как легко и быстро можно исключить из результатов анализа сторонние библиотеки.

Если вы используете для сборки makefile или свою собственную сборочную систему, то проверить проект вам поможет PVS-Studio Standalone. В этом инструменте имеется механизм отслеживания запусков компилятора.

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

Желаю удачи!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] «pip -t» — простая альтернатива virtualenv

TL;DR


Чтобы просто установить и изолировать зависимости проекта, virtualenv часто оказывается слишком тяжелым решением. Предлагаем простую альтернативу:
  1. добавить ./.pip в переменную окружения PYTHONPATH,
  2. установить пакеты локально с помощью pip install -t .pip,
  3. запускать python из папки проекта.

Какую версию желаете сегодня?


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

Обычная практика — включать в проект файл requirements.txt. В этом файле перечислены все библиотеки, от которых зависит проект, с номерами версий. Если файл на месте, установить зависимости просто:

$ pip install -r requirements.txt


Пока все хорошо. Плохо станет, если у вас два и больше проектов с конфликтующими зависимостями. Допустим, проект A работает с библиотекой X только версии 0.1, а проект B — 0.2. По умолчанию pip устанавливает библиотеки глобально в папку интерпретатора Питона. Это означает, что команда $ pip install X==0.2 установит библиотеку X версии 0.2 для всех проектов и перезапишет версию 0.1, если та уже установлена. Переключение между A и B означает глобальную переустановку X каждый раз, что долго и неудобно.

Остров под солнцем


Популярное решение этой частой проблемы — виртуальные окружения. Фреймворк virtualenv создает изолированные Питон-окружения. Зависимости каждого проекта отделяются друг от друга. Так или иначе, некоторым пользователям virtualenv кажется слишком сложным. Поэтому существуют пакеты типа virtualenvwrapper и autoenv, которые расширяют функциональность virtualenv, чтобы им было проще пользоваться. Другие решения: Anaconda environments и pyvenv из стандартной библиотеки Питона (3.3+).

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

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

Чистая магия


Оказывается, есть простой способ повторить подход npm и Bower в Питоне:
  • добавить ./.pip в PYTHONPATH,
  • использовать pip с параметром -t .pip для локальной установки пакетов.

Затем просто запускаете код из папки проекта. Забудьте про source env/bin/activate и deactivate!

Фокус работает, потому что ./.pip — относительный путь. В результате, когда запускаете python из ~/dev/project_a, папка ~/dev/project_a/.pip включается в список папок библиотек для этого экземпляра Питона. Запускаете python в ~/dev/project_b — включается ~/dev/project_b/.pip. Прием работает на всех популярных платформах: Линуксе, Маке и Виндоузе.

Название папки .pip, конечно, может быть любым. Кому-то больше понравится pip_components или libs. Тем не менее, .pip быстро печатать, а точка в начале делает папку скрытой в Линуксе и Маке.

Шаг 1: Установить PYTHONPATH


  • MAC/LINUX
    Эта команда установит PYTHONPATH навсегда для стандартных терминальных сессий:
    $ echo 'export PYTHONPATH="./.pip:$PYTHONPATH"' >> ~/.bash_profile
    
    

    После этого перезапустите терминал или исполните $ source .bash_profile, чтобы PYTHONPATH загрузилась в активной сессии. В зависимости от платформы вам, возможно, надо будет заменить ~/.bash_profile на ~/.bashrc.
  • WINDOWS
    Окройте Панель упраления и перейдите в System and Security → System → Change Settings → Advanced → Environment Variables (у меня английская версия, поэтому не перевожу названия разделов, чтобы случайно не запутать читателей. — прим. пер.). Добавьте или отредактируйте переменную PYTHONPATH, чтобы ее значение стало таким: .\.pip или .\.pip;(...other paths...). Можно установить переменную как для пользователя, так и для всей системы.
  • TEMPORARY PYTHONPATH
    Если предпочитаете устанавливать PYTHONPATH только на время сессии, запустите $ export PYTHONPATH=./.pip в Маке и Линуксе или > set PYTHONPATH=.\.pip в Виндовсе.

    В Маке и Линуксе можно даже установить PYTHONPATH только на время Питон-сессии: $ PYTHONPATH=./.pip python main.py.

Шаг 2: Установить пакеты через pip -t


Теперь, когда PYTHONPATH установлена, осталось только установить пакеты в правильное место с помощью pip. Для этого используем ключ -t или --target:
$ cd project_a
project_a$ pip install requests==2.7.0 -t .pip

project_a$ python
>>> import requests
>>> requests.__version__
'2.7.0'


Теперь то же самое в другом проекте с другой версией:
$ cd project_b
project_b$ pip install requests==2.6.0 -t .pip

project_b$ python
>>> import requests
>>> requests.__version__
'2.6.0'


С файлом requirements.txt все работает так же:
$ pip install -r requirements.txt -t .pip

Потенциальные засады


Разные интерпретаторы Питона


Можно легко запустить программу другим интерпретатором Питона:
$ /path/to/python main.py


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

В этом случае создайте папку типа .pip3 и добавьте ее в начало PYTHONPATH, когда запускаете код Питоном 3.

easy_install


Если у вас есть пакеты, установленные глобально через easy_install, столкнетесь с проблемой: easy_install дописывает путь к таким пакетам в начало sys.path, поэтому у них приоритет над пакетами из .pip.

Решение — избавиться от пакетов, установленных глобально через easy_install.

Чтобы проверить, что ничто не мешает использовать .pip, запустите import sys;sys.path в Питоне. Если перед ./.pip будут другие пути, возможно, придется сначала почистить систему от глобальных easy_install-пакетов.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Настройка MySQL + octopus

В данной статье я бы хотел показать как настроить MySQL для дальнейшего использования gem'a octopus, который используется для шардинга и репликации в Rails — приложениях.
Итак, представим, что перед нами стоит задача развернуть три сервера(на первом крутится Rails — приложение, второй нужен для Master'a, третий будет выступать в качестве Slave'a), настроить репликацию между серверами и сделать так, чтобы octopus работал.
Шаг первый — предварительная настройка серверов
Действия, описанные в данном шаге, необходимо проделать как на Master — машине, так и на Slave.

Установим MySQL.

sudo apt-get install mysql-server

Теперь зайдем в MySQL консоль:
mysql -u root -p

Создадим базу данных, а также пользователя, у которого будут все права на работу с данной базой:
create database rails_myapp;
GRANT ALL ON rails_myapp.* TO 'rails_myapp_user'@'localhost';
GRANT ALL ON rails_myapp.* TO 'rails_myapp_user'@'%';
FLUSH PRIVILEGES;
EXIT;

Шаг второй — настроим Master
Откроем файл конфигурации MySQL на Master — сервере.
sudo nano /etc/mysql/my.cnf

Внутри этого файла мы сделаем несколько изменений. Во — первых, найдем следующую строчку:
bind-address            = 127.0.0.1

Заменим стандартный IP адрес на 0.0.0.0, для того, чтобы Rails — приложение могло достучаться до сервера:
bind-address            = 0.0.0.0

Следующим нашим шагом будет изменение значения server-id, ищем server-id в [mysqld] секции файла конфига. Вы можете выбрать любое число в качестве server-id, но для простоты лучше указать 1, нужно лишь помнить, что данное число должно быть уникально для группы серверов, которые будут участвовать в репликации.

Еще раз убедитесь, что данная строка откомментирована.

server-id               = 1

Теперь перейдите на строку со значением log_bin. Slave будет копировать все изменения, которые будут регистрироваться в логе. Опять же, лучше просто откомментировать строчку с log_bin:
log_bin                 = /var/log/mysql/mysql-bin.log

В конце Вы должны указать имя базы данных, которая будет реплицирована Slave — сервером. Вы можете указать несколько баз, повторяя данную строку для всех баз, которые хотите репллицировать.
binlog_do_db            = rails_myapp

Все изменения сделаны! Можно сохранить и закрыть файл.

Перезапустим MySQL.

sudo service mysql restart

Откроем MySQL — шелл.
mysql -u root -p

Вы должны предоставить привилегии для Slave. Для этого используем следующую команду:
GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'%' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;

Далее Вы должны переключиться на свою базу:
USE  rails_myapp;

Заблокируем базу данных, чтобы посмотреть данные, которые пригодятся нам на следующих этапах:
FLUSH TABLES WITH READ LOCK;

Далее наберем:
SHOW MASTER STATUS;

Вы увидите примерно такую табличку, если наберете описанную ниже команду:
mysql> SHOW MASTER STATUS;

+------------------+----------+--------------+-------------------+
| File | Position | Binlog_Do_DB|
+------------------+----------+--------------+-------------------+
| mysql-bin.000001 | 107 | rails_myapp |
+------------------+----------+--------------+-------------------+
1 row in set (0.00 sec)

Важно! Запишите или запомните название файла и номер позиции, данные значения будут использоваться позже.
Пока ваши базы данных заблокированы, нужно экспортировать базу rails_myapp. Откройте второй терминал в новом окне, убедитесь, что набираете данную команду в bash — шеле, а не в консоле MySQL.

mysqldump -u root -p --opt rails_myapp > rails_myapp.sql

Перейдите обратно в MySQL консоль, разблокируйте базы данных(разрешим запись).
UNLOCK TABLES;
QUIT;

Все! Мы закончили настройку Master'a.

Шаг третий — настроим Slave.
Зайдите на MySQL сервер, откройте MySQL и создайте базу данных(имя точно такое, как и на мастере):

CREATE DATABASE rails_myapp;
EXIT;

Перенесите файл с SQL — командами, который вы экспортировали на мастере и сделайте импорт.
mysql -u root -p rails_myapp< /path/to/rails_myapp.sql

Затем необходимо немного поправить файл конфигурации MySQL:
sudo nano /etc/mysql/my.cnf

Первое, что изменим будет server-id. Как Вы помните, номер должен быть уникальным в пределах группы(в конфиге Master'a мы указали 1).
server-id               = 2

Теперь допишем(или откомментируем) следующие три строки:
relay-log               = /var/log/mysql/mysql-relay-bin.log
log_bin                 = /var/log/mysql/mysql-bin.log
binlog_do_db            = newdatabase

Зададим новое значение bind-address:
bind-address            = 0.0.0.0 

Перезапустим MySQL:
sudo service mysql restart

Следующим шагом будет непосредственно запуск репликации. Помните! Вместо MASTER_LOG_FILE и MASTER_LOG_POS Вы должны вписать те значения, которые записали до этого.
CHANGE MASTER TO MASTER_HOST='12.34.56.789',MASTER_USER='slave_user', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=  107;
START SLAVE;


Как проверить, удачно ли прошел запуск Slave'a? Необходимо ввести комманду, описанную ниже и просмотреть лог:
SHOW SLAVE STATUS\G

Шаг четвертый — настроим octopus.
В Rails — приложении нужно добавить следующую строчку в Gemfile:

gem 'ar-octopus'

Внутри директории config/ нужно создать файл shards.yml, который будет отвечать за конфигурирование Slave — сервера. Помните! Файл database.yml отвечает за конфигурацию Master'a, а только что созданный shards.yml за конфигурацию Slave'ов,  не забываем, что в YML пробелы и табуляции имеют значение.

Файл shards.yml, после заполнения должен выглядеть примерно так:

 octopus:
  replicated: true
  fully_replicated: true
  environments:
    - development
    - test
  development:
    slave:
      adapter: mysql2
      encoding: utf8
      database: rails_myapp
      username: rails_myapp_user
      password: qqdq
      host: 130.111.11.111
      port: 3306
      pool: 10
  test:
    slave:
      adapter: mysql2
      encoding: utf8
      database: rails_myapp
      username: rails_myapp_user
      password: qqdq
      host: 130.111.111.111
      port: 3306
      pool: 10

Все! Осталось прописать в Ваших моделях, что они являются replicated_model(), и запустить Rails — приложение. Отправив POST — запрос, в консоле Вы должны увидеть примерно следующее:
 [Shard: slave]  OurModel Load (1.0ms)  SELECT  `model`.* FROM `model` WHERE `chats`.`id` = 'gkjhgfhd' LIMIT 1

Удачной настройки!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Подробнее о 3D навигации в nanoCAD Plus 7

В nanoCAD Plus 7, который вышел 18 мая 2015 года, появилось ряд функций, существенно расширяющих область применения и позиционирование платформы nanoCAD. Мы предлагаем вниманию наших читателей ряд технических статей, в которых подробнее погружаемся в эти функции, рассматриваем «какими возможностями они обладают» и «как их можно применить на практике». Данная статья посвящена функции «Перспективной навигации».

Введение

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

Тонкость в том, что AutoCAD позволяет удобно смотреть на модель только в режиме Параллельной (ортогональной) проекции, т.е. модель лежит у вас на ладони, и вы ее осматриваете со всех сторон – за это отвечают команды Свободной и Зависимой орбиты. Это вполне достаточный режим для визуализации машиностроительных деталей, но совершенно неудобно для работы с архитектурно-строительными объектами, когда необходимо ощутить себя внутри модели. Да, в AutoCAD можно поставить камеру, перемещать ее по пути, но это все очень сложные с точки зрения пользователя режимы, которыми практически никто не пользуется.

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

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


Рис. 1. Удобная навигация в среде DWG-формата дает возможность фактически пройтись по смоделированному трехмерному объекту, в том числе и собранному из внешних ссылок.

Подготовка модели

С какими данными работает навигация в 3D? C любыми. Все, что сохранено в формат DWG, смоделировано в среде nanoCAD или импортировано в формат DWG, будет отображаться в 3D пространстве nanoCAD Plus. А затем по этой модели можно будет походить.

Это означает, что данная функция будет интересна как приложениям под nanoCAD (которые могут использовать ее для понимания и анализа модели в процессе проектирования), так и для тех специалистов, которые собирают трехмерные модели в среде nanoCAD посредством внешних ссылок. Появляется уникальная возможность интегрировать модели разрозненных решений в одно целое и проводить визуальный анализ модели. Например, вы можете создать трехмерную модель в ArchiCAD, потом выполнить конструкторскую часть в Revit, вентиляцию в MagiCAD, а электрику, водоснабжение и отопление в решениях nanoCAD Электро, ВК, Отоплении, а затем выгрузить из этих решений модели в формат DWG и отобразить их как единое целое в платформе nanoCAD Plus 7.

Рис. 2. Модель, собранная из ArchiCAD, nanoCAD Электро, СКС, ОПС, ВК и Отопления, отображается в среде nanoCAD Plus.

Так же эта функция будет полезна при работе с облаками точек, которые являются в nanoCAD полноценными трехмерными объектами. Представляете, что вы можете ходить по виртуальной модели через 10 минут, после того, как 3D сканер обработал объект? Это же фантастика!

Рис. 3. Навигация по трехмерной модели, полученной с 3D сканера.

Навигация в nanoCAD Plus 7

Если у вас установлен nanoCAD Plus, то откройте в программе файл-пример из папки Samples\Облака точек – давайте на нем будем практически изучать возможности навигации в nanoCAD:

Рис. 4. Навигация по трехмерной модели, полученной с 3D сканера.

Навигация осуществляется двумя методами: посредством команды «Обход» (3DWALK, 3DОБХОД) либо команды «Облет» (3DFLY,3DОБЛЕТ). Обе эти команды расположены на панели «Виды и проекции» или в меню Вид\Обход и облет. Отличие между командами заключается «свободе» навигации: во время обхода вы перемещаетесь в одной плоскости, во время облета свободно парите по всей модели.

Когда запускается команда, меняется способ проекции модели на экран (поэтому происходит небольшое искажение), а курсор превращается в зеленый крест, нацеленный на центр экрана (см. рис. 3). Попробуйте теперь подвигать мышкой – изображение на экране начнет перемещаться. Фактически мышка сейчас управляет вашим взглядом – вверх, вниз, влево-вправо. Осмотритесь вокруг – вы погрузились в среду nanoCAD! :-)

Теперь попробуйте нажать на клавишу «W» или стрелку вперед и удерживайте ее. Вы начнете движение вперед и, если будете держать кнопку долго, рано или поздно войдете внутрь модели! Движение назад – клавиша «S» или стрелка вниз. Есть еще возможность уходить влево и вправо – это клавиши «A» и «D», соответственно. Причем все эти клавиши можно нажимать и удерживать в комбинациях и таким образом идти вперед и одновременно чуть в сторону. В итоге у вас есть полная возможность контролировать перемещение по трехмерной модели nanoCAD – влево-вправо-вперед-назад и мышка указывает, куда вы смотрите!

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

Есть еще клавиша «SHIFT», которая включает временное ускорение, клавиши «+» и «-», которые соответственно увеличивают и уменьшают шаг перемещения. Добро пожаловать в мир трехмерной навигации nanoCAD! :-)

Преимущества 3D навигации в nanoCAD

  1. Просто и удобно. Если вы когда-либо играли в шутеры подобные DOOM, то вы легко освоитесь в среде nanoCAD. Если же вы далеки от игр, то после прочтения этой статьи, думаю, будете чувствовать себя внутри модели nanoCAD достаточно свободно.
  2. Полезно. Вы в любой момент можете заглянуть в труднодоступную часть проекта, посмотреть как одно состыковалось с другим, понять сколько места осталось между объектами, проанализировать пересечения объектов друг с другом. Отличный инструмент для анализа ваших проектов.
  3. Комплексно. nanoCAD может собирать в одном проекте различные данные – облака точек, твердотельные (solid) и поверхностные (mesh) модели, модели любых САПР, совместимых с форматом DWG (вертикальные приложения Нанософт, AutoCAD Architectural, Civil, Revit, Tekla, ArchiCAD и других). Самое главное – сохраните трехмерную модель в формате DWG.
  4. Быстро. Скорость обработки трехмерных данных в nanoCAD достаточно высокая. Это позволяет открывать крупные сборки внутри продукта и осуществлять навигацию по ней. Но, конечно, все должно быть в рамках разумного.
  5. Недорого. Используя nanoCAD как инструмент для интеграции разрозненных моделей в одно целой, можно выстроить недорогое рабочее место для анализа трехмерного проекта.

Особенности 3D навигации в nanoCAD

  1. Требовательность к ресурсам. Трехмерная навигация – не самый «легкий» инструмент, может загрузить ресурсы любой современной машины. Чем более сложные модели вы тащите в объем, тем более производительную систему вам надо иметь. Поэтому подходите к процессу с умом, ограничивая данные так, чтобы успешно решить вашу задачу.
  2. Работа с разномасштабными моделями. Т.к. nanoCAD не привязан к размерным единицам и может работать с совершенно разными моделями по масштабу, то становится сложно оценить размерность модели и настроить параметры по умолчанию. Бывает несколько трудных для пользователя ситуаций:
    • Клавиши «W» и «S» не перемещают по модели. В этом случае, проверьте шаг перемещения – скорее всего он настолько мал, что вы просто не замечаете движения. Используйте клавиши «+» и «-» для того, чтобы изменить шаг движения.
    • При перемещении модель обрезается на некотором расстоянии перед вами. Отмените команду и вернитесь в штатный режим nanoCAD, колесом мышки приблизьте модель ближе и опять перейдите в режим перспективы – nanoCAD сократит область обрезки до более приемлемого уровня.
  3. Нет возможности редактировать модель в режиме перспективы – это одно из текущих ограничений nanoCAD и нам есть куда развиваться.

Заключение

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

Рассчитываем на обратную связь и хороших вам проектов.

dows aka Денис Ожигин

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Мои маленькие реле: Автополив это магия

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

Дано:
Скважина с насосом, система труб с электромагнитными клапанами и поливочными насадками, установленные в десяти различных зонах сада.
Требуется:
Обеспечить последовательный запуск полива каждой зоны на заранее установленный период времени от 5 до 30 минут. Последовательность требуется в связи с тем, что скважина не может обеспечить необходимое давление сразу на двух зонах. Предусмотреть возможность применения внешнего блока управления для того, чтобы имелась возможность включать по таймеру, учитывать влажность почвы и т.п.
Подкатом вы найдете мегабайты мяса, а также процесс самой медленной прошивки контроллера. Это вам не ПЛИСину по битбангу программировать!
Все началось с того что однажды утром раздался телефонный звонок, а уже на следующий вечер в гараже лежало почти пол-тонны «новых» советских релюх в родных коробках, спасенных мной от свалки. Ненужное разобрал, что-то ушло в другие руки. С оставшимися парой сотен килограмм надо было что-то делать :)
Данный проект немного разгрузил полки моих шкафов. Единственное, что мне пришлось купить — это 15 метров тонкого многожильного провода, да и то на большую часть соединений ушло 6 комплектов проводов от компьютерных БП. На весь проект ушло около месяца. Активная фаза сборки — три дня.

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

У нас 10-канальная система, предполагаем что одно реле будет отвечать за включение канала, еще одно за отключение, а на третьем реализуем различные блокировки и управления.
Первые два реле — РН-53 и РН-54, на различное номинальное напряжение — что было то было. У этих реле один замыкающий и один размыкающий контакт. Нужно уложиться.
Третье реле — РПУ-1. 6 НО и 2 НЗ контактов. Должно хватить.
Некоторое количество этих реле отложено для общего управления.
Делаем ночные наброски схемы:

По принципу приоритета блокируем следующий канал предыдущим, обеспечив последовательность. т.е. пока 1 канал не закончит работу, 2 канал не запустится. Не забудем включить последовательную блокировку, дабы пропустив один из каналов не нарушить эту логику.
Уставка времени определяется выбором одного из каналов программируемого реле времени.

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

Пульт


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

и монтируем внутренности:

Реле нужны для кнопки общего запуска. Если канал выбран с помощью тумблера, то при нажатии общей кнопки включения произойдет включение канала. Реле оказались постоянного тока на 110В, поэтому монтируются по два последовательно и питаются через диодный мост. Не помню где я их взял. Разъемы на 14 контактов взял из вольтметров — самописцев Н392. Толку от последних мало, а вот комплектующие и корпус очень даже пригодятся. Из них же были взяты и тумблеры.
Распиновка:
1 — 10 — запуск каналов
11 — внешнее управление каналами, замыкание на 220в-линия включаем самоблокировку и времязадающие цепи.
12 — сигнал стоп.
13 — 220В — нейтраль (правая на схемах)
14 — 220В — линия (левая на схемах)
Блок управления готов. Сверху установлен разъем типа мама, аналогичный по распиновке нижнему. В него можно подключить внешний блок.

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

Контроллер


Анекдот. Шел я как-то по подъезду. Смотрю — пианино стоит. Ну я взял его и забрал.
Музыкантам не смотреть
Кстати без шуток. Пианино оказалось так себе — с трещиной в футоре о_О (знающие люди поймут).
Разобрал пианино пока оно спало…

Зато у меня теперь есть отличная рама и клавиши для синтезатора. Когда-нибудь у меня и до него руки дойдут. Рама пошла в металлолом, ну а треснутый футор — на дрова :)

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

Устанавливаем все реле заднего присоединения и пульт управления:

И прошиваем логику включения:

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

Момент где эта бандура лежит плашмя и из под нее торчат мои ноги в кадр не попал. А жаль.
Установил переключатели. Они винтовые, на 7 позиций, из реле РТ-81. Так как они металлические открытого типа, подключим эту цепь через трансформатор на 24В, чтобы было безопасно хвататься голыми руками. На схеме выше он, в порыве ночного озарения, нарисован черным. В качестве такого трансформатора применен базовый блок реле РФ8300. Туда же встроено и реле на 24В.

Подключаем остатки, устанавливаем реле времени и настраиваем их уставки.

Запускаем еще раз, меняем пробки и убираем еще пару багов. Работает!

В итоге у нас получился Программируемый логический контроллер со следующими характеристиками:

  • Количество логических ячеек — 33
  • Тип программируемой памяти — SDEPROM (ScrewDriven Erasable Programmable Read-Only Memory)
  • Два таймера от 1 до 30 минут — один на 6 каналов и один на 3 канала
  • Один таймер от 1 до 21 секунды на 2 канала.
  • Максимальная скорость программирования на практике — 20 перемычек/час.
  • Средняя скорость программирования — 6 перемычек/час
  • Размеры контроллера — 1333х500х200мм
  • Масса контроллера — 45 кг

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

Актуальная схема сделана в DipTrace и присутствует на GitHub: http://ift.tt/1HlgFXg
Внес в нее все структурные изменения, произведенные во время монтажа.

Ну и последний вопрос — в свете существования огромного количества промавтоматики на ПЛК, программы для которых составляются в тех же релейно-контакторных схемах (ladder diagrams), есть ли потребность у сообщества в обучающих материалах по релейно-контакторной логике? Как дискретной, так и в составе ПЛК.

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 26 июня 2015 г.

[Из песочницы] Плюсы микросервисной архитектуры

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

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

При классическом подходе для построения чаще всего выбирается фреймворк и внутри него реализуются компоненты. В случае с микросервисами для каждого компонента строится отдельное приложение и подбирается свой набор инструментов. Компоненты чаще всего взаимодействуют через REST API.
image
Компоненты: C — данные по квартирам (ядро), 1 — бронирование, 2 — оплата, 3 — логирование броней, 4 — размещение квартир, 5 — администрирование контента.

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

Каждой задаче свой инструмент


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

image

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

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

Незаменимых нет


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

image

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

Каждому свое


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

image

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

Вы маленькие, даже когда вы большие


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

image

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

В итоге каждый из 6 компонентов будет обслуживаться 1-3 разработчиками. И в своей работе они не будут пересекаться с коллегами из параллельных микросервисов. Также стоит обратить внимание, что с микросервисами удобно будет работать в рамках версионности, которая легко реализуется в классическом API.

Итоги


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

А еще базовые функции каждого нового продукта можно будет собрать из уже готовых микросервисов. И это просто великолепно!

image

Для углубленного изучения концепции рекомендую прочитать вот эту публикацию: http://ift.tt/1BRZfPZ.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

XMPP отстой

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

— Паша, нам нужно сделать чат.
— Да всё просто, у меня тут знакомые использовали XMPP для чата в своём приложении.

Какие у нас были требования? Да ничего особенного, простой обмен сообщениями между пользователями, без групповых разговоров. Платформы: веб (с поддержкой работы через вебсокеты), Android, iOS. Создание пользователей должно автоматически производится только нашим серверным приложением. Конечно неплохо было бы иметь отметки о том прочитано сообщение или нет(предполагается, что приложение может быть использовано с разных девайсов), и иметь возможность просмотреть лог чата. В общем стандартный функционал для мгновенного обмена сообщениями в 2015 году. Бонусные баллы начисляются если сервер умеет горизонтально масштабироваться.
Хм, звучит заманчиво. Гуглим сравнение с другими стандартами, открываем ссылку на статью в википедии. Первая мысль: ого, круто, тут есть всё что нам нужно.

Почему же мы выбрали именно XMPP?

  • XMPP — это открытый протокол, разрабатываемый XSF (XMPP Standards Foundation), независимой некоммерческой организацией. Эти ребята должно быть довольно умны и предусмотрели всё, что нужно протоколу обмена сообщениями (ага, конечно).
  • Поскольку протокол открытый должно быть довольно много реализаций сервера, и хотя бы одна из них будет достаточно хороша чтобы её использовать.
  • По этой же причине для каждого из языков программирования наверняка найдётся 1-2 библиотеки.
  • Первая буква X в аббревиатуре означает extensible, расширяемый. Даже если что-то нам не понравится в протоколе мы всегда сможем расширить своими методами.

Протокол


Ребята разрабатывавшие протокол действительно многое предусмотрели. Например, взглянем на дизайн архивирования сообщений. Индивидуальные настройки архивирования для сессий. Описание что делать в случае если один из пользователей хочет чтобы его сообщения не сохранялись на сервере, а второй хочет этого. При этом нормального механизма получения архива сообщений по страницам нет. Под нормальным я подразумеваю выставление offset и limit. Здесь вы можете установить только параметр max, который выставит максимальное количество сообщений и параметр start, который означает время, начиная с которого вы будете получать сообщения.

Или вот например: протокол не определяет что должно быть сделано с offline сообщениями (сообщения, посланные пользователю не в сети). Поэтому большинство серверов просто вышлют их пользователю при следующем логине. Помните требование про то, что пользователь может пользоваться приложением с нескольких устройств? Так вот, если вы запустите приложение на смартфоне, а потом войдёте в веб-приложение, то все offline сообщения придут на ваш смартфон и… всё. То есть в веб-приложении вы об этом никак не сможете узнать. Стоит отметить что некоторые сервера ведут себя по-другому, то есть реализуют XEP-0013.

Ах да, протокол для IM (instant messaging) в 2015 году не имеет спецификации, которая позволяла бы получить список непрочитанных сообщений и отметить прочитанными какие-то из них. Совсем.

Реализации


В самом начале я ещё не знал с какими проблемами из предыдущего пункта мне придётся столкнуться и поэтому в качестве реализации сервера был выбран MongooseIM. Масштабируемый, умеет запрещать разрешать регистрацию только с определённых ip, поддерживает вебсокеты. Для веб front-end была выбрана библиотека JSJaC, потому что по сравнению с strophe.js предоставлял более удобное API. Простая страничка из примеров JSJaC подключилась к серверу с пол-пинка и радости моей не было предела. Казалось бы, работа закончена. Ну почти.

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

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

Единственным достойным упоминания из проектов на JavaScript оказался xmpp-ftw. Проект реализует много расширений из XMPP. Однако от его использования пришлось отказаться, поскольку мы бы не смогли использовать существующие клиентские библиотеки. Возможно всё было бы не так хорошо и с ним.

Tigase поначалу казался спасением. Быстрый, масштабируемый сервер на Java, с возможностью написать плагин и довольно просто его подключить. И отсутствием документации. Вместо неё рекомендовалось читать исходный код. Но это ничего, я и так чаще всего так и делаю. Я написал плагин, который помечал новые сообщения непрочитанными. Чтобы регистрировать пользователей пришлось напрямую писать их в базу, поскольку API для администрирования так и не удалось нормально использовать. К счастью Tigase имеет драйвер для подключения к Mongodb. Получение непрочитанных сообщений сделано отдельным методом API приложения(костыль, можно было сделать и плагином внутри Tigase, но отняло бы намного больше времени, потому что для общения с СУБД внутри Tigase используется довольно низкоуровневое API). Подключив всё я проверил — пока сообщение не помечено прочитанным (кстати, сообщения не имеют id в xmpp, поэтому помечать было решено прочитанной всю переписку с конкретным пользователем) оно возвращается в списке непрочитанных. Всё работает. Осталось проверить только случай с offline сообщением. Да, оно не помечалось непрочитанным, потому что плагин который обрабатывает offline сообщения срабатывает раньше, чем архивирование. На форуме Tigase разработчик ответил что нужное мне поведение реализовано в коммерческом проекте Tigase Unified Archive. Гугление по его названию ни к чему не привело, разработчик на форуме сказал что проект пока нестабилен и нет релизной версии. Покопавшись в исходниках сервера нашёл что можно получить нужное поведение выставив всем сообщениям тип chat.

Теперь пройдёмся по реализациям клиентов. Начнём с JavaScript реализаций. Пробовали мы только JSJaC, который в итоге поменяли на strophe.js. Первый имеет более приятное API, но он имеет только базовую функциональность, не поддерживая работу с расширениями, например с архивом. В то же время strophe вместо этого предлагает довольно удобный xml-билдер. Ну что ж, абстрагироваться от внутренностей XMPP не получилось. Кстати, вебсокет-коннектор в JSJaC работает только в Webkit.

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

Вывод


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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Продолжаем разбираться с «историческими причинами» в cmd.exe

image

В предыдущей статье мы поговорили о возможном варианте решения ситуации с необходимостью указания ключа "/D" для команды CD, входящей в поставку стандартного для операционных систем семейства Windows интерпретатора командной строки cmd.exe. Пришла пора поговорить о ещё одном поведении, которое тянется с незапамятных времён без особой на то причины.

На этот раз речь пойдёт об автодополнении путей, которое в большинстве сред и программных продуктов (и cmd.exe не является в данном случае исключением) осуществляется при помощи нажатия клавиш Tab / Shift-Tab. Думаю, никто не станет спорить с тем, что фича это довольно полезная и зачастую экономит до нескольких секунд времени, которое было бы потрачено на ручной ввод полного пути до интересущего пользователя файла или директории. Здорово, что она присутствует и в cmd.exe, однако…

Давайте поэкспериментируем. Запустим cmd.exe (Win-R -> cmd), начнём вводить команду «CD C:/», нажмём Tab, и… Вместо ожидаемых директорий наподобие «Program Files» и «Windows» получим первый по алфавиту объект из %HOMEPATH%, «слепленный» воедино с «C:/» (в моём случае это дало результат в виде «C:\.vim»). Почему? Думаю, те, кому по своему роду деятельности приходилось часто сталкиваться с cmd.exe, уже поняли, в чём тут дело — вместо forward slash'а для корректного автодополнения следовало использовать backslash (кстати, есть и другие исключения в этом плане). Особенно это непривычно для тех, кто большую часть своего времени проводит в других системах (например, *nix-like), где в качестве path separator'а используется как раз прямой слеш, а не обратный. Почему Microsoft решили использовать именно этот символ вместо уже ставшего на тот момент привычным для многих пользователей forward slash'а, объясняется, например, тут. Ну, а нам остаётся либо смириться с этим, либо взять в руки напильник отладчик и заняться исследованием cmd.exe. Если бы мы выбрали первый путь, то никакой статьи и не было, так что Вы уже должны были догадаться, к чему всё идёт.

Как протекал процесс, и что из этого вышло, читайте под катом (осторожно, много скриншотов).
Для начала нам надо посмотреть, с чего это вдруг cmd.exe решил искать объекты не в указанной пользователем директории, а в %HOMEPATH%.

Итерирование по объектам в директории при помощи WinAPI обычно осуществляется посредством функций FindFirstFile и FindNextFile, а также их вариаций в виде FindFirstFileEx, FindFirstFileTransacted и т.д. Запускаем OllyDbg, загружаем в него cmd.exe (разумеется, заранее скопированный в любую отличную от "%WINDIR%\system32" директорию), открываем окно со списком межмодульных вызовов (right-click по окну CPU -> Search for -> All intermodular calls), пишем «FindFirstFile» и ставим бряки на все вызовы при помощи клавиши F2:

image

Вводим исследуемую нами команду «CD C:/», нажимаем Tab и видим перед собой следующую картину:

image

Обратите внимание на первый из переданных функции FindFirstFileEx аргумент — именно он, согласно документации, задаёт критерий, по которому будет осуществляться поиск:

lpFileName [in]
The directory or path, and the file name, which can include wildcard characters, for example, an asterisk (*) or a question mark (?)


В моём случае он указывал на адрес 0x0030F660, где хранилась юникодовая строка «C:\Program Files\*». Почему именно она? Да потому что именно там я находился в момент ввода команды CD.

Давайте проделаем то же самое, используя backslash вместо forward slash'а. Нажимаем F9, вводим команду «CD C:\» с последующим нажатием Tab'а и видим:

image

Да, теперь этот аргумент указывает на строку «C:\*», как и предполагалось. Следовательно, в случае использования прямого слеша в качестве path separator'а cmd.exe пробегается по объектам, подходящим для автодополнения, в текущей директории.

Бегаем по вызовам всех процедур из call stack'а, открывающегося по нажатию клавиш Alt-K, и видим возле одного из них нечто похожее на парсинг пришедшей от пользователя команды:

image

Ставим бряк на начало данной процедуры (в моём случае это 0x4ACE1877), нажимаем F9, вводим нашу команду с обратным слешем и Tab'ом ещё раз и начинаем пошаговую отладку. Вскоре после усиленных нажатий клавиши F7 мы понимаем, что оказались в цикле, который пробегается по всем имеющимся во введённой пользователем команде символам:

image

EBP+8 указывает на юникодовую строку с командой, в EBP+10 содержится длина команды, а EDI является счётчиком цикла.

Практически сразу после этого цикла находится вызов функции std::memcpy, в результате которого в случае использования backslash'а в dest попадёт «C:\»

image

, а в случае forward slash'а — пустая строка:

image

Что ж, попробуем разобраться, что происходит в этом цикле, переведя алгоритм его работы на какой-нибудь высокоуровневый язык программирования. IDA Pro может декомпилировать код за вас, но, к сожалению, за это она просит довольно много денег, так что попробуем перевести его самостоятельно на C++:

#include <cstddef>
#include <cstring>
#include <cwchar>
#include <iostream>
#include <string>

int main()
{
  std::wstring command;
  std::getline(std::wcin, command);
  auto command_size = command.size();

  int ebx = -1;
  int esi = 0;
  int edx = 0;

  const int ebp_24 = 0; // Always 0 in our case cause it changes in the '"' branch

  // Not actually used in our case
  int ebp_1c = 0;
  int ebp_28 = 0;
  int ebp_2c = 0;

  /**
   * 4ACE18C7 | > / 897D D0 / MOV DWORD PTR SS : [EBP - 30], EDI
   * 4ACE18CA | . | 8B45 10 | MOV EAX, DWORD PTR SS : [EBP + 10]
   * 4ACE18CD | . | 3BF8 | CMP EDI, EAX
   * 4ACE18CF | . | 0F8D 90000000 | JGE cmd.4ACE1965
   */
  for (std::wstring::size_type i = 0; i < command_size; ++i)
  {
    /**
     * 4ACE18D5 | .  8B45 08 | MOV EAX, DWORD PTR SS : [EBP + 8]
     * 4ACE18D8 | .  0FB70478 | MOVZX EAX, WORD PTR DS : [EAX + EDI * 2]
     */
    const wchar_t cur_symbol = command[i];

    // 4ACE18DC | .  66:83F8 2F | CMP AX, 2F
    if (cur_symbol == L'/')
    {
      /**
       * 4ACE18E2 | .  8D77 01 | LEA ESI, DWORD PTR DS : [EDI + 1]
       * 4ACE18E5 | .  8975 D8 | MOV DWORD PTR SS : [EBP - 28], ESI
       */
      esi = i + 1;
      ebp_28 = esi;
    }
    else if (cur_symbol == L'"')
    {
      // ...
    }

    // 4ACE18F0 | .  3955 DC | CMP DWORD PTR SS : [EBP - 24], EDX
    if (ebp_24 == edx)
    {
      /**
       * 4ACE190C | .  50 | PUSH EAX; / w
       * 4ACE190D | .  68 E008D04A | PUSH cmd.4AD008E0; | wstr = " &()[]{}^=;!%'+,`~"
       * 4ACE1912 | .FF15 F010CC4A | CALL DWORD PTR DS : [<&msvcrt.wcschr>]; \wcschr
       * 4ACE1918 | .  59 | POP ECX
       * 4ACE1919 | .  59 | POP ECX
       * 4ACE191A | .  85C0 | TEST EAX, EAX
       */
      if (std::wcschr(L" &()[]{}^=;!%'+,`~", cur_symbol) != NULL)
      {
        esi = i + 1;
        ebp_28 = esi;
        ebp_1c = 0;
        edx = 0;
      }
      else
      {
        // 4ACE192C | > \33D2 | XOR EDX, EDX
        edx = 0;
        /**
         * 4ACE1935 | .  66:83F8 3A | CMP AX, 3A
         * 4ACE1939 | .  74 1B | JE SHORT cmd.4ACE1956
         * 4ACE193B | .  66 : 83F8 5C | CMP AX, 5C
         * 4ACE193F | .  74 15 | JE SHORT cmd.4ACE1956
         */
        if (cur_symbol == L':' || cur_symbol == L'\\')
        {
          /**
           * 4ACE1956 | > \8D5F 01 | LEA EBX, DWORD PTR DS : [EDI + 1]
           * 4ACE1959 | .  895D D4 | MOV DWORD PTR SS : [EBP - 2C], EBX
           * 4ACE195C | > 8955 E4 | MOV DWORD PTR SS : [EBP - 1C], EDX
           */
          ebx = i + 1;
          ebp_2c = ebx;
          ebp_1c = edx;
        }
        else if (cur_symbol == L'*' || cur_symbol == L'?')
        {
          // ...
        }
      }
    }
  }

  /**
   * 4ACE1965  |> \83FB FF       CMP EBX,-1
   * 4ACE1968  |.  74 04         JE SHORT cmd.4ACE196E
   * 4ACE196A  |.  3BDE          CMP EBX,ESI
   * 4ACE196C  |.  7D 05         JGE SHORT cmd.4ACE1973
   */
  if (ebx == -1 || ebx < esi)
  {
    /**
     * 4ACE196E | > \8BDE          MOV EBX, ESI
     * 4ACE1970 | .  895D D4       MOV DWORD PTR SS : [EBP - 2C], EBX
     */
    ebx = esi;
    ebp_2c = ebx;
  }

  /**
   * 4ACE1973 | > \2BC6          SUB EAX, ESI
   * 4ACE1975 | .  03C0          ADD EAX, EAX
   * 4ACE1977 | .  8BF8          MOV EDI, EAX
   * 4ACE1979 | .  57            PUSH EDI; / n
   * 4ACE197A | .  8B45 08       MOV EAX, DWORD PTR SS : [EBP + 8]; |
   * 4ACE197D | .  8D0470        LEA EAX, DWORD PTR DS : [EAX + ESI * 2]; |
   * 4ACE1980 | .  50            PUSH EAX; | src
   * 4ACE1981 | .FF75 E0       PUSH DWORD PTR SS : [EBP - 20]; | dest
   * 4ACE1984 | .E8 52FAFDFF   CALL <JMP.&msvcrt.memcpy>; \memcpy
   */
  const std::size_t count = (command_size - esi) * 2;
  wchar_t dest[1024] = { 0 };
  std::memcpy(dest, command.substr(esi).c_str(), count);

  std::wcout << "Result: " << dest << std::endl;
}


Места, помеченные комментарием "// ...", в рассматриваемых нами случаях не затрагиваются.

Символы наподобие '*' и '\\' были определены по таблице ASCII-кодов:

image

Поэкспериментировав со входными данными, можно увидеть следующее:

CD C:\
Result: C:\

CD C:/
Result:

CD C:\Windows\
Result: C:\Windows\

CD C:/Windows\
Result: Windows\


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

Решением может являться замена всех forward slash'ей на backslash'и сразу после того, как cmd.exe понял, что необходимо выполнять автодополнение. Для этого предлагаю подойти с другой стороны — провести пошаговую отладку сразу после ввода пользователем данных из стандартного потока ввода.

Однако считывание данных из stdin может осуществляться самыми разными способами. Как же понять, что именно используется в cmd.exe? Довольно просто — нажимаем F9, затем F12 (Pause), смотрим на call stack и видим среди вызовов WinAPI-функцию под названием ReadConsole:

image

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

Ставим софтварный бряк на её вызов и добиваемся его срабатывания:

image

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

pInputControl [in, optional]
A pointer to a CONSOLE_READCONSOLE_CONTROL structure that specifies a control character to signal the end of the read operation. This parameter can be NULL


В нашем случае он вовсе не NULL, так что давайте посмотрим, как выглядит структура CONSOLE_READCONSOLE_CONTROL:
typedef struct _CONSOLE_READCONSOLE_CONTROL {
  ULONG nLength;
  ULONG nInitialChars;
  ULONG dwCtrlWakeupMask;
  ULONG dwControlKeyState;
} CONSOLE_READCONSOLE_CONTROL, *PCONSOLE_READCONSOLE_CONTROL;


Смотреть на «сырые» байты не очень удобно, так что давайте воспользуемся специальным плагином для OllyDbg под названием StollyStructs, который как раз и предназначен для визуализации структур. Качаем, разархивируем .dll и .ini в директорию, где лежит исполняемый файл OllyDbg (разумеется, если он указан в качестве пути для плагинов, что и сделано по дефолту) и перезапускаем отладчик. После перезапуска cmd.exe адреса могут смениться, но, вероятнее всего, «окончание» адресов останется прежним. Например, если раньше интересующий нас вызов ReadConsole находился по адресу 0x4ACD3589, то теперь он, возможно, будет находиться на адресе вида 0xXXXXX589:

image

Ставим бряк, останавливаемся на нём, нажимаем Plugins -> StollyStruct -> Select structure, вписываем адрес, передаваемый в качестве аргумента pInputControl, в поле «Address», и… Не находим структуру CONSOLE_READCONSOLE_CONTROL в выпадающем списке. Что ж, автор и не обещал, что заранее будут заданы все структуры из WinAPI. Варианта два — либо добавить описание данной структуры в конфигурационный файл плагина, либо воспользоваться другой структурой, которая будет аналогична интересующей нас. Первое, что мне пришло на ум — это структура RECT, которая так же содержит 4 поля с той лишь разницей, что в ней используются LONG'и вместо ULONG'ов, что нас, в принципе, в данном случае вряд ли будет беспокоить:

typedef struct _RECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT, *PRECT;


В результате получаем следующее:

image

Указание символов для остановки ввода происходит при помощи поля dwCtrlWakeupMask:

dwCtrlWakeupMask
A user-defined control character used to signal that the read is complete


Как видите, в нашем случае он содержит значение 0x200, которое получилось в результате выполнения операции битового сдвига 1

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

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

image

Здесь EDI указывает на юникодовую строку с командой, а EAX является счётчиком цикла.

Как видите, каждый символ сравнивается сначала с 0x0D, потом со значением по адресу 0x4A1640A0, а затем с содержимым по адресу 0x4A1640A4. Если взглянуть на таблицу ASCII-кодов, то мы увидим, что 0x0D — это ни что иное, как carriage return. По указанным же ранее адресам хранится одно и то же значение 0x9, которое, как уже упоминалось ранее, является ASCII-кодом Tab'а:

image

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

Что мы будем в нём делать? Предлагаю поступить следующим образом — пробегаться с конца строки до её начала, проверяя каждый символ до первого встреченного символа пробела на равенство с forward slash'ем и заменяя его на backslash. Выглядеть это будет примерно следующим образом:

PUSHFD
PUSHAD

; Если строка пустая, то ничего не делаем
TEST EAX,EAX
JZ l1

; Декрементируем счётчик цикла (необходимо выполнять даже на
; первой итерации, чтобы пропустить символ Tab'а)
l4:
DEC EAX

; Помещаем в ECX текущий символ
MOVZX ECX,WORD PTR DS:[EDI+EAX*2]
CMP CX,2F ; Если это forward slash
JE l2
CMP CX,20 ; Если это пробел
JE l1
JMP l3

l2:
; Заменяем forward slash на backslash
MOV WORD PTR DS:[EDI+EAX*2],5C

l3:
; Если счётчик цикла равен нулю, то выходим из цикла
TEST EAX,EAX
; В противном случае прыгаем на начало цикла
JNZ l4

l1:
POPAD
POPFD

; Осуществляем прыжок на адрес, по которому
; должны были перейти изначально
JMP 4ACD42CD


Находим место для нашего code cave'а (можно сделать это при помощи Ctrl-B -> много нулей в поле «HEX +0C») и пишем туда следующий код (адреса, разумеется, могут отличаться):
4A163CC5      9C            PUSHFD
4A163CC6      60            PUSHAD
4A163CC7      85C0          TEST EAX,EAX
4A163CC9      74 1D         JE SHORT cmd.4A163CE8
4A163CCB      48            DEC EAX
4A163CCC      0FB70C47      MOVZX ECX,WORD PTR DS:[EDI+EAX*2]
4A163CD0      66:83F9 2F    CMP CX,2F
4A163CD4      74 08         JE SHORT cmd.4A163CDE
4A163CD6      66:83F9 20    CMP CX,20
4A163CDA      74 0C         JE SHORT cmd.4A163CE8
4A163CDC      EB 06         JMP SHORT cmd.4A163CE4
4A163CDE      66:C70447 5C0>MOV WORD PTR DS:[EDI+EAX*2],5C
4A163CE4      85C0          TEST EAX,EAX
4A163CE6    ^ 75 E3         JNZ SHORT cmd.4A163CCB
4A163CE8      61            POPAD
4A163CE9      9D            POPFD
4A163CEA    ^ E9 DE05FFFF   JMP cmd.4A1542CD


, где 0x4A1542CD — это адрес, на который мы должны были перейти в результате условного перехода, находящегося по адресу 0x4A154299 и осуществляющего проверку на равенство текущего символа в команде на Tab. Тот переход, соответственно, заменяем на прыжок на наш code cave:

image

Я думаю, Вы уже заметили, что он затёр следующую инструкцию. Ничего страшного, т.к., по сути, ею являлась аналогичная проверка на равенство текущего символа на всё тот же Tab, а другими способами попасть на неё было нельзя. Чтобы убедиться в этом, можно выделить наши изменения, вернуть всё, как было, при помощи Alt-Backspace, выделить строчку с данной инструкцией и нажать Ctrl-R, где будет одна-единственная строчка с этим же адресом:

image

Проверяем работоспобность, и… По нажатию Tab'а forward slash'и действительно заменяются на backslash'и, в результате чего автодополнение выполняется по указанной пользователем директории, независимо от того, какие именно слеши он использовал изначально.

Послесловие


Кто-то может сказать, что это всё мелочи. Кому-то может не понравиться то, что эту задачу решаем мы, а не разработчики из Microsoft. Кому-то может вообще ничего не понравиться. Но факт остаётся фактом — свою проблему мы решили, и теперь cmd.exe работает так, как мы того хотели в самом начале статьи. А заниматься подобным или нет, решать уже Вам.

Справедливости ради стоит отметить, что в PowerShell эту «проблему», равно как и ситуацию с ключом "/D" для команды CD, всё же исправили.

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] WebAssembly: начало новой эры

Веб ожидает большое будущее.

Вчера Брендан Айк “взорвал” сообщество веб-разработки: веб получит новый низкоуровневый бинарный компилируемый формат, который будет работать гораздо лучше, чем JavaScript.

Google, Microsoft, Mozilla, а также несколько независимых специалистов работают над новым проектом в W3C WebAssembly Community Group, и то, над чем они работают, совсем не маленькая вещь.
WebAssembly — это:

  • Улучшение JavaScript: Реализуйте все критичные вещи на wasm (сокращение от WebAssembly — прим. переводчика) и импортируйте его как стандартный JavaScript модуль.
  • Новый язык программирования: WebAssembly определяет абстрактное синтаксическое дерево (как и JavaScript) в бинарном формате. Вы можете писать код и чистить его от ошибок в текстовом формате. WebAssembly легко читаем.
  • Улучшение для браузеров: Браузеры будут понимать бинарный формат, а это значит, что разработчики смогут компилировать бинарники, которые можно сжать гораздо больше, чем используемые сегодня текстовые файлы с JavaScript. Чем меньше файл, тем быстрее загрузка. В зависимости от возможностей оптимизации времени компиляции, код на WebAssembly может передаваться и запускаться быстрее, чем на JavaScript!
  • Целевая компиляций: Возможность другим языкам, получить первоклассную двоичную поддержку через весь стек веб-платформы.

Что это значит для JavaScript?

Прежде, чем ответить на этот вопрос, давайте отмотаем время назад. Вернемся во времена до React, Angular, Backbone и jQuery.

Вот мы и на месте.

Сеть — это набор гипертекстовых документов, отображаемых с помощью систем доставки сообщений, которые, однако, пока еще не связаны между собой. Первый веб-сервер был запущен на компьютере NeXT (см. ниже) в ЦЕРНе…


image

На дворе 1991 год. Я еще не успел поседеть. Я пытаюсь взломать десятитысячную текстовую игру-квест (я их все не считал).

Я выбрал своеобразный язык программирования для этой цели. На тот момент мне уже успел надоесть и BASIC, и Pascal. Я хотел использовать С, но не мог: я все еще копил на коробочную версию Borland Turbo C++ (они буквально приходят в упакованных коробках с инструкцией и установочным диском). Тогда у меня не было даже простейшего Turbo Assembler.

Я писал на языке ассемблера и “компилировал” в исполняемую программу с помощью командной строки в ДОСе и “debug”. Если это звучит безумно, поверьте мне, так оно и было. Держу пари, что даже те из Вас, которым посчастливилось работать с ДОС, не знают, что можно использовать “debug” для отладки ассемблера и диссасемблеризации (обратный инжиринг) существующего кода.

Звучит круто? Нет. Я ненавидел это. Я не мог дождаться момента, когда смогу сесть за новенький Borland Turbo C++ и программировать по-человечески. В общем, мне его подарили. Ура!

Мне нравился Borland Turbo C++ потому, что на нем был предустановлен Borland Turbo Assembler. Что?! Зачем программировать на языке ассемблера, когда у тебя есть замечательный высокоуровневый, объектно-ориентированный инструмент C++?

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

Я сошел с ума.

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

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

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

Чем он отличается от JavaScript? Ключевое слово — “низкоуровневый”. Он определяет примитивы, включая ряд типов и операций над этими типами, литералы для них, control-flow, вызовы, кучи и т.д.

Это очень простые примитивы. Ничего сложного. Нет сложной объектной системы (прототипной или какой-то другой). Нет встроенной, автоматической “сборки мусора” следующей за Вами и периодически Вас останавливающей, когда ей нужно собрать мусор.

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

Что же такое WebAssembly?

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

К счастью, уже сейчас активно идет разработка отладчика, который будет работать в браузерах, а абстрактное синтаксическое дерево будет представлено в (относительно) легко читаемом текстовом формате. Я бы хотел показать примеры, но их пока не особо много. Пожалуй, код на WebAssembly будет все же прочитать сложнее, чем аналогичный код, написанный руками на JavaScript, но, его как минимум будет так же легко читать, как ASM.js. Может даже и легче. Посмотрим.

Для чего будет использоваться WebAssembly?

Среди всего прочего, его можно будет использовать для простой работы с тредами и SIMD (single instruction, multiple data) — проще говоря, с одним потоком команд и несколькими потоками данных. Вы можете поставить в очередь множество блоков данных, а затем прописать одну команду для одновременной работы с ними.

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

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

Как насчет приложений?

На настоящий момент, такие приложения, как Ableton Live (написание музыки) и Adobe Premiere Pro (создание видео), не слишком подходят для портирования в Веб. Замечу, что это возможно, но все же пока затруднительно. Надо решить еще много проблем. Например, надо решить, как лучше синхронизировать данные для real-time приложений.

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

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

WebAssembly органично заполнит пробелы, которые есть в функциональности JavaScript.

Пробелы в функциональности JavaScript есть, и это далеко не секрет. Даже самые преданные его фанаты вряд будут спорить с утверждением, что порой язык пытается “проглотить” слишком много, теряя гибкость и эффективность. Еще вчера я думал, что эти пробелы можно устранить, просто добавив больше функциональности в сам JavaScript. Брендан Айк предложил тот же путь на конференции Fluent. Я аплодировал.

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

WebAssembly может увеличить скорость JavaScript в разы!

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

Конечно, на самом деле нам не нужен WebAssembly, чтобы использовать в веб-разработке другие языки программирования. Например, у нас уже есть отличные игровые AAA-движки, которые прекрасно работают с компиляцией из JavaScript.

Если вы слышите, что JavaScript работает медленно… Это не так.

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

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

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

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

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

Сеть ожидает большое будущее. Так что лучше выходить из тени и браться за дело, пока не поздно.
Дополнения + FAQ

В: Что такое wasm?
О: Сокращение от WebAssembly.

В: Почему бы не использовать JVM?
О: Попытки добавить JVM в браузеры с помощью плагинов были и не раз. К сожалению, ничего хорошего из этого не вышло. В JavaScript есть встроенная виртуальная машина, поэтому добавление еще одной приводит к появлению второго набора API подключений, чтобы дать виртуальной машине доступ к DOM, сетям, сенсорам, устройствам ввода и т.п. За это придется кое чем пожертвовать. Например, как будут процессы в виртуальной машине распределять между собой имеющиеся ресурсы? Ответить на этот вопрос сложнее, чем кажется.

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

В: Означает ли появление WebAssembly, что в будущем появится много новых языков программирования? Не приведет ли это к фрагментации?

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

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

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

JavaScript все чаще используется в разработке игр, в программировании роботов и IoT устройств. Хотя в этих областях есть значительная конкуренция со стороны C, C++ и Java, на положение JavaScript как для главного языка веб-программирования это никак не влияет. У всех разработчиков есть выбор, и они используют JavaScript не просто так, а потому, что он им нравится.

JavaScript выживет. “Есть только два вида языков программирования: те, на которые люди постоянно жалуются, и те, которые никто не использует”. Бьёрн Страуструп


Полезные материалы

W3C WebAssembly Community Group
Mailing List
IRC: irc://irc.w3.org:6667/#webassembly
GitHub
Who’s involved?
Об авторе

Эрик Эллиот (Eric Elliott) — автор книг “Programming JavaScript Applications” и “Learn JavaScript Universal Development with Node, ES6, & React”. Он участвовал в разработке программных продуктов для Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, а также для таких исполнителей, как Usher, Frank Ocean, Metallica и многих других. Эрик проводит большую часть времени в Кремниевой долине вместе с самой красивой женщиной в мире.

Над переводом работали: greebn9k(Сергей Грибняк), seninrom(Роман Сенин), silmarilion(Андрей Хахарев)
Singree

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] Использование монад в С++. Часть 2: монада состояния

Часть 1
Часть 2

Что вы сделаете, если завтра выиграете в лотерею? Купите спортивную машину, бросите работу и поедете в турне по США? А может быть станете основателем собственной компании, приумножите состояние и купите личный самолёт?

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

pair<Car, Cash> buyCar(Cash cashIn)

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

template<class A>
using Plan = function<pair<A, Cash>(Cash)>;

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

template<class A>
Plan<A> got_it(A a)
{
    return [a](Cash s) { return make_pair(a, s); };
}

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

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

Монада состояния

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

using State = List<int>;

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

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

template<class A>
using Plan = function<pair<A, State>(State)>;

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

template<class A>
pair<A, State> runPlan(Plan<A> pl, State s)
{
    return pl(s);
}

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

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

Обратите внимание на две компоненты: одна из них это план покупки автомобиля: Plan<Car>. Вторая компонента это функция, которая получает автомобиль и генерироует план поездки, Plan<Trip>. Эта функция является «продолжением», которое ведёт вас к конечной цели: функции типа Plan<Trip>(Car). И «продолжение» само по себе может состоять из болього количества маленьких планов.

А вот и функция mbind, которая привязывает план pl к «продолжению» k. «Продолжение» использует выходное значение плана pl для генерации нового плана. Функция mbind должна вернуть новый составной план, то есть она должна вернуть лямбду. Как и любой другой план, эта лямбда принимает состояние и возвращает пару: значение и состояние. Мы реализуем эту лямбду наиболее общным способом.

Логика проста. Внутри лямбды нам доступно состояние, а значит мы можем запустить план pl. На его выходе мы получаем пару: значение типа A и новое состояние. Мы передаём это значение в «продолжение» k и получаем новый план. В конце концов мы запускаем этот план с новым состоянимем и на этом всё.

template<class A, class F>
auto mbind(Plan<A> pl, F k) -> decltype(k(pl(State()).first))
{
    using B = decltype(k(pl(State()).first)(State()).first);

    // это должно было бы быть выражено с помощью концептов, если 
    // бы их поддержка была встроена в С++
    static_assert(std::is_convertible<
        F, std::function<Plan<B>(A)>> ::value,
        "mbind requires a function type Plan<B>(A)");

    return [pl, k](State s) {
        pair<A, State> ps = runPlan(pl, s);
        Plan<B> plB = k(ps.first);
        return runPlan(plB, ps.second); // new state!
    };
}

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

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

template<class A>
Plan<A> mreturn(A a)
{
    return [a](State s) { return make_pair(a, s); };
}

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

Plan<State> getState()
{
    return [](State s) { return make_pair(s, s); };
}

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

Вторая вспомогательная функция используется для модификации (полного замещения) состояния.

Plan<void*> putState(State newState)
{
    return [=](State s) { return make_pair(nullptr, newState); };
}

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

Пример

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

pair<int, List<int>> select(List<int> lst)
{
    int i = lst.front();
    return make_pair(i, lst.popped_front());
}

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

Вот наш первый план:

Plan<int> sel = &select;

Теперь мы создадим более сложный план для генерации пар целых чисел:

Plan<pair<int, int>> pl = 
    mbind(sel, [=](int i) { return
    mbind(sel, [=](int j) { return
    mreturn(make_pair(i, j));
}); });

Давайте проанализируем этот код. Первый mbind принимает план sel, который выбирает первый элемент из списка (список будет предоставлен позже, во время выполнения плана). Он привязывается к «продолжению», которое принимает выбранное целое число i и генерирует план, создающий пару целых чисел. Вот это «продолжение»:

[=](int i) { return
    mbind(sel, [=](int j) { return
    mreturn(make_pair(i, j));
}); });

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

[=](int j) { return
    mreturn(make_pair(i, j));
});

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

mreturn(make_pair(i, j));

Обратите внимание, что мы используем один и тот же план sel дважды. Но когда этот план выполняется внутри нашего финального плана, он вернёт два различных элемента начального списка. Когда выполняется mbind, она сначала передаёт состояние (список целых чисел) в первый sel. Назад она получает модифицированное состояние — список без первого элемента. Затем она использует этот укороченный список для выполнения плана, созданного «продолжением». Таким образом второй sel выбирает первый элемент из уже укороченного списка (второй элемент оригинального списка). Здесь список снова укорачивается и передаётся mreturn, которая больше его не модифицирует.

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

List<int> st{ 1, 2, 3 };
cout << runPlan(pl, st) << endl;

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

StateL<tuple<int, int, int>> solve()
{
    StateL<int> sel = &select<int>;

    return mbind(sel, [=](int s) {
    return mbind(sel, [=](int e) {
    return mbind(sel, [=](int n) {
    return mbind(sel, [=](int d) {
    return mbind(sel, [=](int m) {
    return mbind(sel, [=](int o) {
    return mbind(sel, [=](int r) {
    return mbind(sel, [=](int y) {
        return mthen(guard(s != 0 && m != 0), [=]() {
            int send  = asNumber(vector<int>{s, e, n, d});
            int more  = asNumber(vector<int>{m, o, r, e});
            int money = asNumber(vector<int>{m, o, n, e, y});
            return mthen(guard(send + more == money), [=]() {
                return mreturn(make_tuple(send, more, money));
            });
        });
    });});});});});});});});
}

В этот раз я не переименовывал mbind в for_each, а mreturn в yield.

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

Перед тем как вы напишете комментарий

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

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

Задачи на дом
  • Реализуйте select из примера в тексте, используя getState и putState
  • Реализуйте evalPlan, версию runPlan, которая возвращает только финальное значение, без состояния
  • Реализуйте mthen — версию mbind, где «продолжение» не принимает никаких аргументов. Оно игнорирует результат плана, который является первым аргументов mthen (но всё же запускает его и использует модифицированное состояние)
  • Используйте монаду состояния для написания простого калькулятора выражений в обратной польской записи. Состояние в этом случае будет стеком (списком) элементов
    enum ItemType { Plus, Minus, Num };
    
    struct Item
    {
        ItemType _type;
        int _num;
        Item(int i) : _type(Num), _num(i) {}
        Item(ItemType t) : _type(t), _num(-1) {}
    };
    
    

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

    List<int> stack{ Item(Plus)
                   , Item(Minus)
                   , Item(4)
                   , Item(8)
                   , Item(3) };
    cout << evalPlan(calc(), stack) << endl;
    
    

    (Решение доступно на Гитхабе)

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.