Русский
Русский
English
Статистика
Реклама

Programming

С безопасность для новичков

05.03.2021 18:07:04 | Автор: admin

Привет, хабровчане. Для будущих студентов курса "C++ Developer. Professional" Александр Колесников подготовил статью.

Приглашаем также посмотреть открытый вебинар на тему
Области видимости и невидимости. За 1,5 часа участники вместе с экспертом успеют реализовать класс общего назначения и запустить несколько unit-тестов с использованием googletest. Присоединяйтесь.


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

Сегодня язык программирования С++ существует в нескольких параллельных реальностях: C++98, C++11, C++14, C++17, C++20. Существует как минимум один источник, где можно немного разобраться со всем этим набором мультивселенных. Однако, когда дело дойдет до написания кода использования stackOverflow, вопрос а точно эта строка написана безопасно", будет мучать разработчика из релиза в релиз. Кстати, на момент написания статьи готовится новый стандарт С++23 =).

Откуда проблемы

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

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

  • неверное объявление типов данных;

  • неверное использование выражений;

  • неправильная обработка целочисленных данных;

  • неправильная работа с контейнерами;

  • неправильная работа со строками;

  • неправильная работа с памятью;

  • неверная обработка exception;

  • пренебрежение ограничениями OOP;

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

  • прочие проблемы, о которых мало, где сказано.

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

Разберем несколько примеров.

String Format

void check_password(const char *user) {  int ret;  //  static const char format[] = "%s wrong pass.\n";  size_t messageLength = strlen(user) + sizeof(msg_format);  char *data = (char *)malloc(messageLength); // <- так же не очень безопасный вариант  if (data == NULL) {    //Код для ошибки  }  ret = snprintf(data, messageLength, format, user);  if (ret < 0) {     //Код для ошибки  } else if (ret >= messageLength) {     //Последний шанс обработать некорректные данные  }  syslog(LOG_INFO, msg);  free(msg);}

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

Integer overflow

Данная проблема головная боль любого ПО, которое работает с накапливаемыми данными. Какого размера переменные использовать, чтобы оптимально хранить данные и одновременно сделать запас для приложения, если если оно будет использоваться месяцами без перезапуска? В некоторых случаях ответить однозначно на этот вопрос нельзя, поэтому программист, ориентируясь на собственный опыт, волевым решением пишет uint16_t. Но данных оказывается больше 65,535 и тут случается чудо значение переменной становится равно нулю и отсчет идёт заново. Пример кода:

...user->nameLength = getUserNameLength(&user->name) ;user->newDbCellLen = malloc(user->nameLength * sizeof(uint8_t))...

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

...int16_t checkLen(uint16_t firstNumber, uint16_t secondNumber){    uint16_t resultLength;    if (UINT_MAX - firstNumber < secondNumber)     {        //ошибка        return -1;    }    else    {        resultLength = firstNumber + secondNumber;    }    return resultLength;}

Преобразование типов

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

...unsigned int number = (unsigned int)ptr;number = (number & 0x7fffff) | (flag << 23);ptr = (char *)number;...

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

Выводы

Как видно из примеров в статье, С++ это язык, в котором нужно внимательно относиться к кажущимся мелочам, начиная от форматов данных и заканчивая типами переменных. Где брать примеры? Что делать с уязвимостями? К сожалению, универсального ответа нет, но можно постоянно работать и накапливать знания о языке и его особенностях. Начать можно здесь или здесь. Так же нужно использовать плагины и приложения, которые позволяют анализировать код на этапе сборки. Можно в этом случае ориентироваться на продукты вроде этого или этого.


Узнать подробнее о курсе "C++ Developer. Professional".

Смотреть открытый вебинар на тему Области видимости и невидимости.

Подробнее..

Часть 3 ESPboy2 гаджет для ретро игр и экспериментов с IoT, новости проекта 2021

24.05.2021 16:18:49 | Автор: admin

Предыдущие статьи:

В основе лежит старенький уже на сегодня чип ESP8266 c WiFi с подключенными к нему цветным экраном 128х128, кнопками, динамиком, RGB светодиодом и еще парой удобных дополнений.

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

В ESPboy есть слот расширения, на который выведены интерфейсы I2C, SPI, UART, I2S куда втыкаются штатные модули, которые при загрузке соответствующего софта превращают устройство в ретро игровую консоль, GSM телефон, GPS навигатор, FM радио, перехватчик радиопакетов, читалку/писалку rfid/nfc, MP3 плеер, погодную станцию, универсальный ИК пульт, LORA мессенджер и много чего еще.

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

Список опробованных модулей можно увидеть на сайте проекта www.espboy.com, весь софт, как уже говорилось, на GitHub для изучения и экспериментов. Много чего в работе и еще больше в планах. К сожалению не удалось поспеть в прошлом году довести модули до промышленного изготовления, все они в виде прототипов, но энтузиасты с помощью сообщества в проектном форуме и Discord чате собирают при желании любой без сложностей. Думаю, что в этом году часть модулей все же получится выпустить в заводском исполнении.

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

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

Уже есть задумки по ESPboy3.

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

Заглядываясь на кикстартер успех FLIPPER ZERO, тоже посещают мысли о запуске компании на Kickstarter. Но такой разворот требует значительных усилий в сторону маркетинга, продаж, что далее неизбежно повлечет за собой масштабирование производства, логистики, поддержки, переход на новый уровень R&D и управление процессами в целом. Бросаться в такой омут очертя голову не охота. Хочется делать основательно, а в этом случае уже нужны заинтересованные люди, понимающие в бизнесе и инвестиции.

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

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

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

Всем добра!

С уважением, РоманС

mailto: espboy.edu@gmail.com

Подробнее..

Перевод Почему вы можете обойтись без Babel

01.03.2021 20:22:05 | Автор: admin

Для будущих студентов курса "JavaScript Developer. Basic" подготовили перевод материала.

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


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

Ознакомившись с этой статьей, вы поймете:

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

  • как использовать редактор Visual Studio Code, чтобы обойтись без Babel.

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

Что такое Babel и какую проблему он решает?

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

По мере развития браузеров добавляются новые функции API и ECMAScript. Различные браузеры развиваются с разной скоростью и расставляют акценты в качестве приоритетных для разных задач. Это ставит нас перед непростым выбором: как мы можем их все поддерживать и при этом использовать современные функции? Некоторые из них будут несовместимы.

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

Разница между транспилированием (transpiling) и полифилингом (polyfilling)

Транспилирование (Transpiling) это процесс преобразования синтаксиса нового языка, который старые браузеры не могут понять, в старый синтаксис, который они распознают.

Приведем пример переноса оператора let:

// the new syntax `let` was added in ECMAScript 2015 aka ES6let x = 11;// `let` transpiles to the old syntax `var` if your transpiler target was ES5var x = 11;

Полифилинг (Polyfilling) это процесс добавления недостающих методов, свойств или API к старым браузерам путем предоставления собственной версии недостающего родного кода.

Это можно рассматривать как дополнение недостающих элементов. Например, вот полифил (polyfill) для isNaN:

// check if the method `isNaN` exists on the standard built-in `Number` objectif (!Number.isNaN) {  // if not we add our own version of the native method newer browsers provide  Number.isNaN = function isNaN(x) {    return x !== x;  };}

Наилучшим способом для получения полифилов является использование core-js.

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

Альтернатива 1: не поддерживать древние браузеры

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

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

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

Чтобы определить, нужно ли поддерживать определенный браузер, задайте себе следующие вопросы.

1. Какие браузеры в настоящее время используют Ваши клиенты?

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

Если у вас не установлено аналитическое программное обеспечение, вы не будете знать, какие браузеры вам нужно поддерживать. Вы должны будете сделать обоснованное предположение. Если у вас есть корпоративные клиенты, гораздо больше шансов, что вам понадобится поддержка IE11 (Internet Explorer 11), чем если бы вы занимались маркетингом для фанатов web-literate (грамотное программирование) технологий.

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

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

2. Какие современные функции браузера вы хотите использовать?

Использование современных функций языка и API (Application Programming Interfaces) браузера делает написание кода проще, быстрее и интереснее. Это также делает ваш код более удобным в обслуживании.

Если вам нравиться писать ES5 (ECMAScript) и использовать XMLHttpRequest(), тогда определенно не нужен Babel, но может потребоваться какая-нибудь специальная процедура.

3. Какие современные функции браузера поддерживают браузеры ваших клиентов?

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

Альтернатива 2: Используйте eslint-plugin-compat

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

  • исключает любую зависимость от транспилиров (transpilers). Возвращает вам практический контроль над рабочим кодом.

  • если имеется современная функция, без которой вы не можете жить, то ее можно использовать применив полифил (polyfill).

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

Создать тест

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

Ниже приведен современный код, который должна поддерживать наша целевая среда после переноса (transpiled).

После переноса (transportation) для каждой функции есть console.assert (метод записи сообщений на консоли для пользователя), чтобы убедиться, что она работает, как положено. В случае eslint-plugin-compat вместо этого проверим, что несовместимый код помечен в linting (Linting это процесс, выполняемый программой linter, которая анализирует исходный код на определенном языке программирования и отмечает потенциальные проблемы, такие как синтаксические ошибки, отклонения от предписанного стиля кодирования или использование конструкций, о которых известно, что они небезопасны).

test.js

// test nullish coalescing - return right side when left side null or undefinedconst x = null ?? "default string";console.assert(x === "default string");const y = 0 ?? 42;console.assert(y === 0);// test optional chaining - return undefined on non existent property or methodconst adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};const dogName = adventurer.dog?.name;console.assert(dogName === undefined);console.assert(adventurer.someNonExistentMethod?.() === undefined);// use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then((response) => response.json())  .then((json) => console.log(json));

Использование eslint env свойства с помощью eslint-plugin-compat

Нам нужен обходной путь для объединения функций языка и API браузера.

Вы можете использовать eslint (Eslint это утилита, проверяющая стандарты кодирования на JavaScript) для проверки синтаксиса языка. Для этого измените свойство env наes2020.

Для проверки совместимости API браузера используйте eslint-plugin-compat. Он использует ту же самую конфигурацию Browserlist и остальные инструменты, что и Babel.

Полную инструкцию можно найти в eslint-plugin-compat repo. Мы воспользуемся browserlist defaults как предустановками по умолчанию. Замените их по своему выбору, основанному на аналитике.

Что такое browserlist?

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

Посмотрите список браузеров, поддерживаемых defaults для browserlist.

defaults использует быстрый доступ к таким версиям браузеров:

  • > 0,5 процента (версии браузеров, выбранные по глобальной статистике использования)

  • Последние две версии (каждого "живого (not dead)" браузера)

  • Firefox ESR (Extended Support Release)

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

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

Настройка eslint-plugin-compat для Visual Studio Code

Добавьте следующие пакеты в свой проект.

npm install --save-dev eslint eslint-plugin-compat

Добавьте следующее в package.json.

"browserslist": [    "defaults"  ]

Создайте следующий файл .eslintrc.json или добавьте эти настройки к существующим.

{  "extends": ["plugin:compat/recommended"],  "env": {    "browser": true,    "es2020": true  }}

Убедитесь, что у вас установлено расширение VS Code ESLint.

Теперь любой API браузера, несовместимый с конфигурацией browserlistв вашем package.json, отображается как ошибка linting. Вы можете отдельно контролировать, какую версию ECMAScript вы хотите поддержать, используя свойство env в файле .eslintrc.json.

Было бы неплохо, если бы eslint-plugin-compat автоматически добавил и возможности языка, но на данный момент это является нерешённой задачей.

IE 11 с выбранной настройкой

наш API fetch() помечен.

Поменяйте объект env на es6.

Вы сразу же увидите ошибку при попытке использовать nullish coalescing, который был запущен в составе Es2020.

Альтернатива 3: Используйте другое программное обеспечение для замены Babel

Прежде чем рассматривать альтернативы, давайте быстро рассмотрим, как использовать Babel.

Использование Babel для транспилирования (transpile) и полифилинга (polyfill)

Сначала создайте директорию мини-проекта и установите нужные нам взаимосвязи.

mkdir babel-testcd babel-testnpm init -ymkdir src distnpm install --save-dev @babel/core @babel/cli @babel/preset-envnpm install --save @babel/polyfill

Добавьте следующее в свой package.json.

"browserslist": "defaults",

Запишите файл test.js вsrc, а затем выполните следующую команду.

npx babel src --out-dir dist --presets=@babel/env

Наконец, запустите файл, чтобы проверить, что тесты все еще работают.

node dist/test.js

Ошибок ввода не должно быть, но будет сказано, что fetch is not defined, так как в Node.js нет метода fetch().

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

"use strict";var _ref, _, _adventurer$dog, _adventurer$someNonEx;// test nullish coalescing - return right side when left side null or undefinedvar x = (_ref = null) !== null && _ref !== void 0 ? _ref : "default string";console.assert(x === "default string");var y = (_ = 0) !== null && _ !== void 0 ? _ : 42;console.assert(y === 0); // test optional chaining - return undefined on non existent property or methodvar adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};var dogName =  (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0    ? void 0    : _adventurer$dog.name;console.assert(dogName === undefined);console.assert(  ((_adventurer$someNonEx = adventurer.someNonExistentMethod) === null ||  _adventurer$someNonEx === void 0    ? void 0    : _adventurer$someNonEx.call(adventurer)) === undefined,); // use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then(function (response) {    return response.json();  })  .then(function (json) {    return console.log(json);  });

Преимущества и недостатки использования Babel

Преимущества:

  • Эта базовая установка была относительно несложной.

  • У Babel есть большое сообщество для поддержки и постоянных обновлений с 36.8k GitHub звездами на момент написания статьи.

Недостатки:

  • Медленное время компиляции

  • Множество зависимостей (dependencies), даже если они являются зависимостями (dev-dependencies). (установлено 269 пакетов)

  • 39М использованного дискового пространства, как сообщает du -sh

  • 5728 установленных файлов, о чем сообщает find . -тип f | wc -l

Использование swc для транспилирования (transpile) и полифилинга (polyfill)


Swc новый конкурент Babel. Он написан на языке программирования Rust и в 20 раз быстрее. Это может быть очень важно, если вы долго ждете, чтобы построить свой проект.

Чтобы все устроить:

mkdir swc-testcd swc-testnpm init -ymkdir src distnpm install --save-dev @swc/cli @swc/core browserslist

Добавьте следующее в свой package.json.

"browserslist": "defaults",

Запишите конфигурационный файл .swcrc в корневую директорию проекта.

{  "env": {    "coreJs": 3  },  "jsc": {    "parser": {      "syntax": "ecmascript"    }  }}

Запишите ваш тестовый файл в src, затем выполните следующую команду для переноса (transpile).

npx swc src -d dist

Запустите полученный файл, чтобы проверить, что тесты все еще работают.

node dist/test.js

В итоге swc-transpiled (транспилированный) файл, выглядит вот так:

var ref, ref1;var ref2;// test nullish coalescing - return right side when left side null or undefinedvar x = (ref2 = null) !== null && ref2 !== void 0 ? ref2 : "default string";console.assert(x === "default string");var ref3;var y = (ref3 = 0) !== null && ref3 !== void 0 ? ref3 : 42;console.assert(y === 0);// test optional chaining - return undefined on non existent property or methodvar adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};var dogName =  (ref = adventurer.dog) === null || ref === void 0 ? void 0 : ref.name;console.assert(dogName === undefined);console.assert(  ((ref1 = adventurer.someNonExistentMethod) === null || ref1 === void 0    ? void 0    : ref1.call(ref1)) === undefined,);// use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then(function (response) {    return response.json();  })  .then(function (json) {    return console.log(json);  });

Преимущества и недостатки использования swc

Преимущества:

  • Гораздо меньше зависимостей (установлено 43 пакета)

Недостатки:

  • Меньшая пользовательская база и количество постоянных участников

Другие альтернативы: Google Closure Compiler и TypeScript

Я не включил Google Closure Compiler в качестве опции, потому что он, как это ни печально, сложен в использовании. Тем не менее, он может сделать хорошую работу по транспилированию (transpile) и полифилингу (polyfill). Если у вас есть свободное время, я рекомендую вам проверить его особенно если вы цените небольшой размер файла, так как встроенная функция минификации демонстрирует отличные результаты.

Вы также можете использовать TypeScript для переноса (transpile) и core-js для ручной полифил (polyfill) обработки, но это неуклюжее решение, которое может с легкостью создать больше проблем, чем решить их.

Заключение

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

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

Если вы выберете автоматический перевод, то SWC будет намного быстрее, чем Babel, и будет содержать гораздо меньше зависимостей. Также есть возможность использовать Google Closure Compiler или TypeScript, но для их настройки потребуется немного больше усилий.

LogRocket: Полная видимость ваших веб-приложений

LogRocket это передовое решение для мониторинга приложений, позволяющее воспроизводить проблемы так, как если бы они возникали в вашем собственном браузере. Вместо того, чтобы гадать, почему происходят ошибки, или спрашивать пользователей на скриншотах и дампах логов, LogRocket позволяет воспроизводить сеанс, чтобы быстро понять, что пошло не так. Он отлично работает с любым приложением, независимо от фреймворка, и имеет плагины для записи дополнительного контекста из Redux, Vuex и @ngrx/store.

В дополнение к регистрации действий и состояния Redux, LogRocket записывает журналы консольных сообщений, ошибки JavaScript, следы стеков, сетевые запросы/ответы в формате заголовок + тело, метаданные браузера и пользовательские журналы. Кроме того при помощи DOM (Document Object Model) позволяет записывать страницы HTML и CSS, воссоздавая превосходные в пиксельном отношении видео даже для самых сложных одностраничных приложений.


Узнать подробнее о курсе "JavaScript Developer. Basic".

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

Подробнее..

Начать заниматься роботами должно быть просто

09.11.2020 08:05:16 | Автор: admin

Введение


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

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



Интересно? Тогда начнём.


Возраст


Изначальная предполагаемая целевая аудитория дети примерно 7-10 лет, но уверен, что это может быть интересно и детям в возрасте до 99 лет и старше.

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

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

Полностью с нуля со всеми деталями можно собрать это примерно за 2-3 часа. Или чуть дольше если торопиться и что-то пойдёт не так.

Идея


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

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

Теория


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

Устройство робота можно свести к трём основным узлам:
Сенсоры (Sense) это сенсоры, которыми робот видит окружающий мир. В нашем случае это глаза робота, которые смотрят на дорогу.
Мозг (Plan) это та часть робота, которая получает информацию от сенсоров, обрабатывает и передаёт команды на исполняемые части.
Исполнители (Act) эти части робота непосредственно выполняют действия по командам, которые были получены от мозга.

Sense-Plan-Act
Sense-Plan-Act является уже устаревшей формулировкой, но в данном упрощённом варианте она вполне неплохо объясняет эти простые принципы.


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

Упрощённо полный путь сигнала можно представить в виде: глаза -> мозг -> мышцы -> ноги.

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

Реализация


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

Программная часть:


Алгоритм


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

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

Сам код можно посмотреть на github.com/nochkin/esp-line-follower.

Закачивание программы в мозг


Когда я вёл класс с 5-6-летками, то на все мозги предварительно была загружена нужная программа, что бы не тратить на это время во время класса.
Сам процесс закачивания программы в микроконтроллер не отличается от других Arduino-совместимых плат: установить плагин для ESP8266 (если ещё не установлен), подключить ESP8266 модуль по USB, открыть или скопировать скетч (файл .ino) и нажать Upload.
Тут есть более подробная инструкция установки поддержки ESP8266 в Arduino для тех, кто с этим пока не знаком:
github.com/esp8266/Arduino#installing-with-boards-manager

Железная часть:


Как основа для шасси, использована двух-колёсная платформа. Она легко доступна, у неё простая конструкция и с ней легко работать.
Центром управления был выбран популярный микроконтроллер Espressif ESP8266, реализованный в виде модуля NodeMCU.
Так же для этой платы есть удобный модуль для драйверов двигателя на L293DD. Как раз достаточно для раскачивания двух небольших моторов. Только благодаря наличию этого модуля для моторов количество соединительных проводов заметно снижается.

Схема соединений


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


Список основных компонентов:


  1. ESP8266 NodeMCU v2
  2. Motor Shield для NodeMCU v2
  3. Пара инфра-красных сенсоров
  4. Двух-колёсное шасси с моторами и колёсами
  5. Соединительные провода (Dupont wires) для сенсоров
  6. Дополнительные винты/гайки/крепления для установки сенсоров и контроллера на шасси
  7. Чёрная изолента для дороги (если поверхность достаточно светлая, то можно и синюю)

Общая стоимость всех деталей обычно не превышает $20.

Немного подробнее о компонентах:
  1. ESP8266 NodeMCU v2:

    Существует несколько вариантов ESP8266 NodeMCU модулей. Они не все совместимы друг с другом как электрически, так и механически.
    В данном проекте используется ESP8266 NodeMCU v2. Самый простой признак этот модуль использует CP2102 для USB интерфейса. Вариант NodeMCU на базе CH340G обычно шире физически и поэтому не подойдёт для драйвера моторов.
  2. Motor Shield для NodeMCU v2:

    Прелесть этого модуля тем, что NodeMCU плата вставляется в него и поэтому количество проводов в проекте сильно уменьшается.
    Существует только один вариант этого модуля на базе L293DD. Этой микросхемы достаточно как раз что бы раскачать двигатели на нашем шасси.
  3. Инфракрасные сенсоры:

    Данные модули представляют из себя светодиод (излучатель) и фотодиод (приёмник). По отражению робот может определить что он видит перед собой тёмную полосу или светлый пол.
    Я советую брать модули с подстройкой, что бы можно было отрегулировать чувствительность и уменьшить ложные срабатывания при определении светлого и тёмного.
    Советую заказать больше двух стоят они не много, но у них есть высокий риск сломаться при неосторожном обращении.
  4. Для шасси был выбран один из самых популярных и доступных вариантов на интернет просторах.
    В этом комплекте уже есть моторы со встроенными редукторами, колёса, держатель для трёх AA элементов и минимальный набор винтов и гаек что бы это всё собрать вместе.
    Как альтернативный вариант для питания, можно заменить держатель 3 * AA на держатель для одного литий-йоного элемента в формате 18650.
  5. Dupont провода.
    Провода надо с разъёмами мама-мама (female-female). Оптимальная длина примерно 20 см. Желательно что бы провода были разных цветов во избежании путаницы при подключении.
  6. Дорога

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


Сборка


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

  1. Небольшая подготовительная операция.
    a) Часто провода идут не припаянными к моторам. В этом случае надо их припаять. Это единственный момент где нужна пайка. Если нет вариантов, то можно кого-то заранее попросить припаять или попробовать самому прикрутить провода, но в случае прикручивания сложно сделать хороший контакт. Надо иметь это в виду.
    b) Модули драйвера двигателей могут идти с джампером на гребёнке с питанием (VIN-VM-NC) или без оного. В последнее время джампер не ставят, но сама гребёнка есть.
    Если есть джампер в закромах, то надо его поставить в позицию VIN-VM. У меня не было такого количества джамперов для всего класса и я просто припаял перемычку снизу платы.
    Конфигурация VIN-VM позволяет использовать один и тот же источник питания для моторов и для мозгов.

  2. Начнём со сборки самой платформы.
    Перед установкой компонентов я советую установить шестигранные крепления для модуля драйвера двигателей и инфракрасных сенсоров.

    Теперь установим держатели для моторов и сами моторы. Колёса лучше ставить в самом конце.
    Потом надо установить держатель для AA элементов. Провода пока соединять не надо. Выключатель я не устанавливал, так как на модуле драйвера двигателя уже есть выключатель.
    Часто в комплекте есть энкодеры (круглые диски с поперечными отверстиями по кругу), но в данном проекте они не используются, поэтому устанавливать не обязательно.
  3. Вставляем мозг робота на мышцы, то есть ставим модуль микроконтроллера на модуль драйвера двигателей.
    Важно соблюдать правильное направление, иначе будет взрыв мозга в виде белого дыма, на котором работает вся электроника в мире. Антенна на модуле микроконтроллера должна совпадать с нарисованной антенной на модуле драйвера.
  4. Привинчиваем бутерброд с мозгами и мышцами на шасси.


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

  6. Соединяем провода.
    На модуле драйвера есть восемь синих клемм. Достаточно ослабить их отвёрткой, что бы внутрь пролез провод и потом закрутить винт, что бы провод не выпадал и имел хороший контакт.
    Подключим питание на VIN и GND (VIN-красный, GND-чёрный). Важно соблюдать полярность и не перепутать плюс и минус.
    Подсоединим оба мотора на A-/A+ для левого и b+/B+ для правого. Тут тоже надо соблюдать полярность, но при ошибке мотор будет крутиться в другую сторону и в этом случае достаточно провода поменять местами в клеммах.
    Теперь соединим глаза. Тут я использую Dupont wire для удобства. Пожалуй, для детей (да и для некоторых взрослых) это самый сложный шаг. У каждого модуля сенсора есть три контакта плюс (VCC), минус (GND) и сигнал (OUT). Сложность в том, что на модуле драйвера эти сигналы стоят в другом порядке и важно проверить правильное соединение.
    Левый глаз робота идёт на группу 5, а правый глаз идёт на группу 6 (группы заданы в программе робота).


Первый пуск


Ставим наше творение на относительно светлый пол (главное, не на стол или куда-то ещё откуда он может упасть) и включаем кнопкой на модуле драйвера. Кнопка находится ближе к синим клеммам и рядом с VIN-VM-NC джампером.
На пустом относительно светлом полу робот должен ехать вперёд. Он может немного заворачивать вбок из-за асимметричности моторов или сборки, но это не так важно.
Если робот крутиться на месте как кот, играющийся со своим хвостом, то это означает то, что один из моторов с перевёрнутой полярностью. Провода этого мотора надо поменять местами на синих клеммах.
На случай если робот едет назад, то полярность надо поменять на двух моторах.
Бывает что один из моторов не крутиться вообще, обычно достаточно проверить соединение, так как бывает что провод слишком глубоко закручен в синюю клемму и прижим приходится на изоляцию от провода, а не на сам провод.

Проверка зрения


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

Дорога


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

Итог


Он видит. Он едет. Сам.

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


А тут самое интересное запуск и проверка:




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


Подробнее..

Проверка QEMU с помощью PVS-Studio

04.09.2020 10:14:53 | Автор: admin
image1.png

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

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

Об анализе


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

После проверки анализатор обнаружил множество потенциальных проблем. Для диагностик общего назначения (General Analysis) было получено: 1940 уровня High, 1996 уровня Medium, 9596 уровня Low. После просмотра всех предупреждений было решено остановиться на диагностиках для первого уровня достоверности (High). Таких предупреждений нашлось достаточно много (1940), но большая часть предупреждений либо однотипна, либо связана с многократным использованием подозрительного макроса. Для примера рассмотрим макрос g_new.

#define g_new(struct_type, n_structs)                        _G_NEW (struct_type, n_structs, malloc)#define _G_NEW(struct_type, n_structs, func)       \  (struct_type *) (G_GNUC_EXTENSION ({             \    gsize __n = (gsize) (n_structs);               \    gsize __s = sizeof (struct_type);              \    gpointer __p;                                  \    if (__s == 1)                                  \      __p = g_##func (__n);                        \    else if (__builtin_constant_p (__n) &&         \             (__s == 0 || __n <= G_MAXSIZE / __s)) \      __p = g_##func (__n * __s);                  \    else                                           \      __p = g_##func##_n (__n, __s);               \    __p;                                           \  }))

На каждое использование этого макроса анализатор выдает предупреждение V773 (Visibility scope of the '__p' pointer was exited without releasing the memory. A memory leak is possible). Макрос g_new определен в библиотеке glib, он использует макрос _G_NEW, а этот макрос в свою очередь использует другой макрос G_GNUC_EXTENSION, говорящий компилятору GCC пропускать предупреждения о нестандартном коде. Именно этот нестандартный код и вызывает предупреждение анализатора, обратите внимание на предпоследнюю строку. В целом же макрос является рабочим. Предупреждений этого типа нашлось 848 штук, то есть почти половина срабатываний приходится всего лишь на одно единственное место в коде.

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

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

Предупреждение N1

V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 2395, 2397. megasas.c 2395

#define MEGASAS_MAX_SGE 128             /* Firmware limit */....static void megasas_scsi_realize(PCIDevice *dev, Error **errp){  ....  if (s->fw_sge >= MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE) {    ....  } else if (s->fw_sge >= 128 - MFI_PASS_FRAME_SIZE) {    ....  }  ....}

Любые использования "магических" чисел в коде всегда вызывают подозрение. Здесь два условия, и на первый взгляд они кажутся разными, но, если посмотреть значение макроса MEGASAS_MAX_SGE, то окажется, что условия дублируют друг друга. Скорее всего, здесь опечатка и вместо 128 должно стоять другое число. Конечно, это проблема всех "магических" чисел, достаточно просто опечататься при их использовании. Применение макросов и констант сильно помогает разработчику в этом случае.

Предупреждение N2

V523 The 'then' statement is equivalent to the 'else' statement. cp0_helper.c 383

target_ulong helper_mftc0_cause(CPUMIPSState *env){  ....  CPUMIPSState *other = mips_cpu_map_tc(env, &other_tc);  if (other_tc == other->current_tc) {    tccause = other->CP0_Cause;  } else {    tccause = other->CP0_Cause;  }  ....}

В рассматриваемом коде тела then и else условного оператора идентичны. Здесь, скорее всего, copy-paste. Просто скопировали тело then ветвления, а исправить забыли. Можно предположить, что вместо объекта other необходимо было использовать env. Исправление этого подозрительного места могло бы выглядеть следующим образом:

if (other_tc == other->current_tc) {  tccause = other->CP0_Cause;} else {  tccause = env->CP0_Cause;}

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

  • V523 The 'then' statement is equivalent to the 'else' statement. translate.c 641

Предупреждение N3

V547 Expression 'ret < 0' is always false. qcow2-cluster.c 1557

static int handle_dependencies(....){  ....  if (end <= old_start || start >= old_end) {    ....  } else {    if (bytes == 0 && *m) {      ....      return 0;           // <= 3    }    if (bytes == 0) {      ....      return -EAGAIN;     // <= 4    }  ....  }  return 0;               // <= 5}int qcow2_alloc_cluster_offset(BlockDriverState *bs, ....){  ....  ret = handle_dependencies(bs, start, &cur_bytes, m);  if (ret == -EAGAIN) {   // <= 2    ....  } else if (ret < 0) {   // <= 1    ....  }}

Здесь анализатор обнаружил, что условие (комментарий 1) никогда не выполнится. Значение переменной ret инициализируется результатом выполнения функции handle_dependencies, эта функция возвращает только 0 или -EAGAIN (комментарии 3, 4, 5). Чуть выше, в первом условии, мы проверили значение ret на -EAGAIN (комментарий 2), поэтому результат выполнения выражения ret < 0 будет всегда ложным. Возможно, раньше функция handle_dependencies и возвращала другие значения, но потом в результате, например, рефакторинга поведение поменялось. Здесь надо просто завершить рефакторинг. Похожие срабатывания:

  • V547 Expression is always false. qcow2.c 1070
  • V547 Expression 's->state != MIGRATION_STATUS_COLO' is always false. colo.c 595
  • V547 Expression 's->metadata_entries.present & 0x20' is always false. vhdx.c 769

Предупреждение N4

V557 Array overrun is possible. The 'dwc2_glbreg_read' function processes value '[0..63]'. Inspect the third argument. Check lines: 667, 1040. hcd-dwc2.c 667

#define HSOTG_REG(x) (x)                                             // <= 5....struct DWC2State {  ....#define DWC2_GLBREG_SIZE    0x70  uint32_t glbreg[DWC2_GLBREG_SIZE / sizeof(uint32_t)];              // <= 1  ....}....static uint64_t dwc2_glbreg_read(void *ptr, hwaddr addr, int index,                                 unsigned size){  ....  val = s->glbreg[index];                                            // <= 2  ....}static uint64_t dwc2_hsotg_read(void *ptr, hwaddr addr, unsigned size){  ....  switch (addr) {    case HSOTG_REG(0x000) ... HSOTG_REG(0x0fc):                      // <= 4        val = dwc2_glbreg_read(ptr, addr,                              (addr - HSOTG_REG(0x000)) >> 2, size); // <= 3    ....  }  ....}

В этом коде есть потенциальная проблема с выходом за границу массива. В структуре DWC2State определен массив glbreg,состоящий из 28 элементов (комментарий 1). В функции dwc2_glbreg_read по индексу обращаются к нашему массиву (комментарий 2). Теперь обратите внимание, что в функцию dwc2_glbreg_read в качестве индекса передают выражение (addr HSOTG_REG(0x000)) >> 2 (комментарий 3), которое может принимать значение в диапазоне [0..63]. Для того чтобы в этом убедиться, обратите внимание на комментарии 4 и 5. Возможно, тут надо скорректировать диапазон значений из комментария 4.

Еще похожие срабатывания:

  • V557 Array overrun is possible. The 'dwc2_hreg0_read' function processes value '[0..63]'. Inspect the third argument. Check lines: 814, 1050. hcd-dwc2.c 814
  • V557 Array overrun is possible. The 'dwc2_hreg1_read' function processes value '[0..191]'. Inspect the third argument. Check lines: 927, 1053. hcd-dwc2.c 927
  • V557 Array overrun is possible. The 'dwc2_pcgreg_read' function processes value '[0..127]'. Inspect the third argument. Check lines: 1012, 1060. hcd-dwc2.c 1012

Предупреждение N5

V575 The 'strerror_s' function processes '0' elements. Inspect the second argument. commands-win32.c 1642

void qmp_guest_set_time(bool has_time, int64_t time_ns,                         Error **errp){  ....  if (GetLastError() != 0) {    strerror_s((LPTSTR) & msg_buffer, 0, errno);    ....  }}

Функция strerror_s возвращает текстовое описание кода системной ошибки. Её сигнатура выглядит так:

errno_t strerror_s( char *buf, rsize_t bufsz, errno_t errnum );

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

Предупреждение N6

V595 The 'blen2p' pointer was utilized before it was verified against nullptr. Check lines: 103, 106. dsound_template.h 103

static int glue (    ....    DWORD *blen1p,    DWORD *blen2p,    int entire,    dsound *s    ){  ....  dolog("DirectSound returned misaligned buffer %ld %ld\n",        *blen1p, *blen2p);                         // <= 1  glue(.... p2p ? *p2p : NULL, *blen1p,                            blen2p ? *blen2p : 0); // <= 2....}

В этом коде значение аргумента blen2p сначала используется (комментарий 1), а потом проверяется на nullptr (комментарий 2). Это крайне подозрительное место выглядит так, как будто просто забыли вставить проверку перед первым использованием (комментарий 1). Как вариант исправления просто добавить проверку:

dolog("DirectSound returned misaligned buffer %ld %ld\n",      *blen1p, blen2p ? *blen2p : 0);

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

  • V595 The 'ref' pointer was utilized before it was verified against nullptr. Check lines: 2191, 2193. uri.c 2191
  • V595 The 'cmdline' pointer was utilized before it was verified against nullptr. Check lines: 420, 425. qemu-io.c 420
  • V595 The 'dp' pointer was utilized before it was verified against nullptr. Check lines: 288, 294. onenand.c 288
  • V595 The 'omap_lcd' pointer was utilized before it was verified against nullptr. Check lines: 81, 87. omap_lcdc.c 81

Предупреждение N7

V597 The compiler could delete the 'memset' function call, which is used to flush 'op_info' object. The RtlSecureZeroMemory() function should be used to erase the private data. virtio-crypto.c 354

static void virtio_crypto_free_request(VirtIOCryptoReq *req){  if (req) {    if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {      ....      /* Zeroize and free request data structure */      memset(op_info, 0, sizeof(*op_info) + max_len); // <= 1      g_free(op_info);    }    g_free(req);  }}

В этом фрагменте кода вызывается функция memset для объекта op_info (комментарий 1), после этого op_info сразу удаляется, то есть, другими словами, после очистки этот объект нигде больше не модифицируется. Это как раз тот самый случай, когда в процессе оптимизации компилятор может удалить вызов memset. Чтобы исключить подобное потенциальное поведение, можно воспользоваться специальными функциями, которые компилятор никогда не удаляет. См. также статью "Безопасная очистка приватных данных".

Предупреждение N8

V610 Unspecified behavior. Check the shift operator '>>'. The left operand is negative ('number' = [-32768..2147483647]). cris.c 2111

static voidprint_with_operands (const struct cris_opcode *opcodep,         unsigned int insn,         unsigned char *buffer,         bfd_vma addr,         disassemble_info *info,         const struct cris_opcode *prefix_opcodep,         unsigned int prefix_insn,         unsigned char *prefix_buffer,         bfd_boolean with_reg_prefix){  ....  int32_t number;  ....  if (signedp && number > 127)    number -= 256;            // <= 1  ....  if (signedp && number > 32767)    number -= 65536;          // <= 2  ....  unsigned int highbyte = (number >> 24) & 0xff;  ....}

Так как переменная number может иметь отрицательное значение, побитовый сдвиг вправо является неуточнённым поведением (unspecified behavior). Чтобы убедиться в том, что рассматриваемая переменная может принять отрицательное значение, обратите внимание на комментарии 1 и 2. Для устранения различий поведения вашего кода на различных платформах, таких случаев нужно не допускать.

Еще предупреждения:

  • V610 Undefined behavior. Check the shift operator '<<'. The left operand is negative ('(hclk_div 1)' = [-1..15]). aspeed_smc.c 1041
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(target_long) 1' is negative. exec-vary.c 99
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand is negative ('hex2nib(words[3][i * 2 + 2])' = [-1..15]). qtest.c 561

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

V610 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. hppa.c 2702

int print_insn_hppa (bfd_vma memaddr, disassemble_info *info){  ....  disp = (-1 << 10) | imm10;  ....}

Другие подобные предупреждения:

  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. hppa.c 2718
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '-0x8000' is negative. fmopl.c 1022
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(intptr_t) 1' is negative. sve_helper.c 889

Предупреждение N9

V616 The 'TIMER_NONE' named constant with the value of 0 is used in the bitwise operation. sys_helper.c 179

#define HELPER(name) ....enum {  TIMER_NONE = (0 << 30),        // <= 1  ....}void HELPER(mtspr)(CPUOpenRISCState *env, ....){  ....  if (env->ttmr & TIMER_NONE) {  // <= 2    ....  }}

Можно легко убедиться, что значение макроса TIMER_NONE равно нулю (комментарий 1). Далее этот макрос используется в побитовой операции, результат которой всегда будет 0. Как итог, тело условного оператора if (env->ttmr & TIMER_NONE) никогда не выполнится.

Предупреждение N10

V629 Consider inspecting the 'n << 9' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qemu-img.c 1839

#define BDRV_SECTOR_BITS   9static int coroutine_fn convert_co_read(ImgConvertState *s,                   int64_t sector_num, int nb_sectors, uint8_t *buf){  uint64_t single_read_until = 0;  int n;  ....  while (nb_sectors > 0) {    ....    uint64_t offset;    ....    single_read_until = offset + (n << BDRV_SECTOR_BITS);    ....  }  ....}

В этом фрагменте кода над переменной n, имеющей 32-битный знаковый тип, выполняется операция сдвига, потом этот 32-битный знаковый результат расширяется до 64-битного знакового типа, и далее, как беззнаковый тип, складывается с беззнаковой 64-битной переменной offset. Предположим, что на момент выполнения выражения переменная n имеет некоторые значимые старшие 9 бит. Мы выполняем операцию сдвига на 9 разрядов (BDRV_SECTOR_BITS), а это, в свою очередь, является неопределенным поведением, тогда в качестве результата мы можем получить выставленный бит в старшем разряде. Напомним, что этот бит в знаковом типе отвечает за знак, то есть результат может стать отрицательным. Так как переменная n знакового типа, то при расширении будет учтен знак. Далее результат складывается с переменной offset. Из этих рассуждений нетрудно увидеть, что результат выполнения выражения может отличаться от предполагаемого. Одним из возможных вариантов решения является замена типа переменной n на 64-битный беззнаковый тип, то есть на uint64_t.

Вот еще похожие срабатывания:

  • V629 Consider inspecting the '1 << refcount_order' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2.c 3204
  • V629 Consider inspecting the 's->cluster_size << 3' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2-bitmap.c 283
  • V629 Consider inspecting the 'i << s->cluster_bits' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. qcow2-cluster.c 983
  • V629 Consider inspecting the expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. vhdx.c 1145
  • V629 Consider inspecting the 'delta << 2' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type. mips.c 4341

Предупреждение N11

V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. nand.c 310

static void nand_command(NANDFlashState *s){  ....  s->addr &= (1ull << s->addrlen * 8) - 1;  ....}

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

  • V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 449
  • V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 1235
  • V634 The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. exynos4210_mct.c 1264

Предупреждение N12

V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. pl181.c 400

static void pl181_write(void *opaque, hwaddr offset,                        uint64_t value, unsigned size){  ....  if (s->cmd & PL181_CMD_ENABLE) {    if (s->cmd & PL181_CMD_INTERRUPT) {      ....    } if (s->cmd & PL181_CMD_PENDING) { // <= else if      ....    } else {      ....    }    ....  }  ....}

В этом коде, судя по форматированию, прямо напрашивается использование else if вместо if. Возможно, здесь забыли дописать else. Тогда вариант исправления может быть такой:

} else if (s->cmd & PL181_CMD_PENDING) { // <= else if

Однако есть вероятность, что с этим кодом все в порядке, и тут некорректное форматирование текста программы, которое сбивает с толку. Тогда код мог бы выглядеть так:

if (s->cmd & PL181_CMD_INTERRUPT) {  ....}if (s->cmd & PL181_CMD_PENDING) { // <= if  ....} else {  ....}

Предупреждение N13

V773 The function was exited without releasing the 'rule' pointer. A memory leak is possible. blkdebug.c 218

static int add_rule(void *opaque, QemuOpts *opts, Error **errp){  ....  struct BlkdebugRule *rule;  ....  rule = g_malloc0(sizeof(*rule));                   // <= 1  ....  if (local_error) {    error_propagate(errp, local_error);    return -1;                                       // <= 2  }  ....  /* Add the rule */  QLIST_INSERT_HEAD(&s->rules[event], rule, next);   // <= 3  ....}

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

Предупреждение N14

V781 The value of the 'ix' index is checked after it was used. Perhaps there is a mistake in program logic. uri.c 2110

char *uri_resolve_relative(const char *uri, const char *base){  ....  ix = pos;  if ((ref->path[ix] == '/') && (ix > 0)) {  ....}

Здесь анализатор обнаружил потенциальный выход за границу массива. Сначала читается элемент массива ref->path по индексу ix, а потом ix проверяется на корректность (ix > 0). Правильным решением тут будет поменять эти действия местами:

if ((ix > 0) && (ref->path[ix] == '/')) {

Таких мест нашлось несколько:

  • V781 The value of the 'ix' index is checked after it was used. Perhaps there is a mistake in program logic. uri.c 2112
  • V781 The value of the 'offset' index is checked after it was used. Perhaps there is a mistake in program logic. keymaps.c 125
  • V781 The value of the 'quality' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 326, 335. vnc-enc-tight.c 326
  • V781 The value of the 'i' index is checked after it was used. Perhaps there is a mistake in program logic. mem_helper.c 1929

Предупреждение N15

V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. cadence_gem.c 1486

typedef struct CadenceGEMState {  ....  uint32_t regs_ro[CADENCE_GEM_MAXREG];}....static void gem_write(void *opaque, hwaddr offset, uint64_t val,        unsigned size){  ....  val &= ~(s->regs_ro[offset]);  ....}

В этом коде выполняется побитовая операция с объектами разных типов. Левый операнд это аргумент val, имеющий 64-битный беззнаковый тип. В качестве правого операнда выступает полученное значение элемента массива s->regs_ro по индексу offset, имеющий 32-битный беззнаковый тип. Результат операции в правой части (~(s->regs_ro[offset])) является 32-битным беззнаковым типом, и перед побитовым умножением он расширится до 64-битного типа нулями, то есть после вычисления всего выражения обнулятся все старшие биты переменной val. Такие места всегда выглядят подозрительными. Тут можно только порекомендовать разработчикам еще раз пересмотреть этот код. Еще похожее:

  • V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. xlnx-zynq-devcfg.c 199
  • V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. soc_dma.c 214
  • V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. fpu_helper.c 418

Предупреждение N16

V1046 Unsafe usage of the 'bool' and 'unsigned int' types together in the operation '&='. helper.c 10821

static inline uint32_t extract32(uint32_t value, int start, int length);....static ARMVAParameters aa32_va_parameters(CPUARMState *env, uint32_t va,                                          ARMMMUIdx mmu_idx){  ....  bool epd, hpd;  ....  hpd &= extract32(tcr, 6, 1);}

В этом фрагменте кода происходит операция побитового И над переменной hpd, имеющей тип bool, и результатом выполнения функции extract32, имеющим тип uint32_t. Так как битовое значение булевой переменной может быть только 0 или 1, то результат выражения будет всегда false, если младший бит, возвращаемый функцийе extract32, равен нулю. Давайте рассмотрим это на примере. Предположим, что значение hpd равно true, а функция вернула значение 2, то есть в двоичном представлении операция будет выглядеть так 01 & 10 = 0, а результат выражения будет равен false. Скорее всего, программист хотел выставлять значение true, если функция возвращает что-то отличное от нуля. По всей видимости, надо исправить код так, чтобы результат выполнения функции приводился к типу bool, например, так:

hpd = hpd && (bool)extract32(tcr, 6, 1);

Заключение


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


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Evgeniy Ovsannikov. Checking QEMU using PVS-Studio.
Подробнее..

Из песочницы Enum и switch, и что с ними не так

04.09.2020 16:11:02 | Автор: admin

image


Часто ли у вас было такое, что вы добавляли новое значение в enum и потом тратили часы на то, чтобы найти все места его использования, а затем добавить новый case, чтобы не получить ArgumentOutOfRangeException во время исполнения?


Идея


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


Идея состоит в том, чтобы заменить использование switch паттерном visitor.


Пример 1


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


Определим файл DocumentType.cs:


public enum DocumentType{    Invoice,    PrepaymentAccount}public interface IDocumentVisitor<out T>{    T VisitInvoice();    T VisitPrepaymentAccount();}public static class DocumentTypeExt{    public static T Accept<T>(this DocumentType self, IDocumentVisitor<T> visitor)    {        switch (self)        {            case DocumentType.Invoice:                return visitor.VisitInvoice();            case DocumentType.PrepaymentAccount:                return visitor.VisitPrepaymentAccount();            default:                throw new ArgumentOutOfRangeException(nameof(self), self, null);        }    }}

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


Опишем visitor который будет искать в базе нужный документ DatabaseSearchVisitor.cs:


public class DatabaseSearchVisitor : IDocumentVisitor<IDocument>{    private ApiId _id;    private Database _db;    public DatabaseSearchVisitor(ApiId id, Database db)    {        _id = id;        _db = db;    }    public IDocument VisitInvoice() => _db.SearchInvoice(_id);    public IDocument VisitPrepaymentAccount() => _db.SearchPrepaymentAccount(_id);}

И потом его использование:


public void UpdateStatus(ApiDoc doc){    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);    var databaseDocument = doc.Type.Accept(searchVisitor);    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

Пример 2


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


public enum PurseEventType{    Increase,    Decrease,    Block,    Unlock}public sealed class PurseEvent{    public PurseEventType Type { get; }    public string Json { get; }    public PurseEvent(PurseEventType type, string json)    {        Type = type;        Json = json;    }}

Мы хотим отправлять уведомления пользователю на определенный тип событий. Тогда реализуем visitor:


public interface IPurseEventTypeVisitor<out T>{    T VisitIncrease();    T VisitDecrease();    T VisitBlock();    T VisitUnlock();}public sealed class PurseEventTypeNotificationVisitor : IPurseEventTypeVisitor<Missing>{    private readonly INotificationManager _notificationManager;    private readonly PurseEventParser _eventParser;    private readonly PurseEvent _event;    public PurseEventTypeNotificationVisitor(PurseEvent @event, PurseEventParser eventParser, INotificationManager notificationManager)    {        _notificationManager = notificationManager;        _event = @event;        _eventParser = eventParser;    }    public Missing VisitIncrease() => Missing.Value;    public Missing VisitDecrease() => Missing.Value;    public Missing VisitBlock()    {        var blockEvent = _eventParser.ParseBlock(_event);        _notificationManager.NotifyBlockPurseEvent(blockEvent);        return Missing.Value;    }    public Missing VisitUnlock()    {        var blockEvent = _eventParser.ParseUnlock(_event);        _notificationManager.NotifyUnlockPurseEvent(blockEvent);        return Missing.Value;    }}

Для примера не будем ничего возвращать. Для этого можно воспользоваться типом Missing из System.Reflection или же написать тип Unit. В реальном проекте возвращался бы Result, например, с информацией об ошибке, если такие имеются.


И пример использования:


public void SendNotification(PurseEvent @event){    var notificationVisitor = new PurseEventTypeNotificationVisitor(@event, _eventParser, _notificationManager);    @event.Type.Accept(notificationVisitor);}

Дополнение


Если нужно быстрее


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


Метод расширение:


public static T Accept<TVisitor, T>(this DocumentType self, in TVisitor visitor)    where TVisitor : IDocumentVisitor<T>    {        switch (self)        {            case DocumentType.Invoice:                return visitor.VisitInvoice();            case DocumentType.PrepaymentAccount:                return visitor.VisitPrepaymentAccount();            default:                throw new ArgumentOutOfRangeException(nameof(self), self, null);        }    }

Сам visitor остаётся прежним, только меняем class на struct.


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


public void UpdateStatus(ApiDoc doc){    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);    var databaseDocument = doc.Type.Accept<DatabaseSearchVisitor, IDocument>(searchVisitor);    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

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


Читабельность и in-place реализация


Если нужно реализовать логику только в одном месте, то часто visitor громоздко и не удобно. Поэтому есть альтернативное решение match.


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


public static T Match<T>(this DocumentType self, Func<T> invoiceCase, Func<T> prepaymentAccountCase){    var visitor = new FuncVisitor<T>(invoiceCase, prepaymentCase);    return self.Accept<FuncVisitor<T>, T>(visitor);}

Сам FuncVisitor:


public readonly struct FuncVisitor<T> : IDocumentVisitor<T>{    private readonly Func<T> _invoiceCase;    private readonly Func<T> _prepaymentAccountCase;    public FuncVisitor(Func<T> invoiceCase, Func<T> prepaymentAccountCase)    {        _invoiceCase = invoiceCase;        _prepaymentAccountCase = prepaymentAccountCase;    }    public T VisitInvoice() => _invoiceCase();    public T VisitPrepaymentAccount() => _prepaymentAccountCase();}

Использование match:


public void UpdateStatus(ApiDoc doc){    var databaseDocument = doc.Type.Match(        () => _db.SearchInvoice(doc.Id),        () => _db.SearchPrepaymentAccount(doc.Id)    );    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

Итог


При добавлении нового значения в enum необходимо:


  1. Добавить метод в интерфейс.
  2. Добавить его использование в метод расширение.

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


Это все еще не серебряная пуля, но может здорово помочь в работе с enum.


Ссылки


Подробнее..

Из песочницы Hi Programming Language

21.09.2020 14:20:44 | Автор: admin
Начиная с этой статьи мы приступаем к публикации концепта реализации нового языка программирования Hi.

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

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

Don't bite my finger, look where I am pointing
Warren S. McCulloch, 1960s

Исходная постановка задачи


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

В целом, при некоторых ограничениях, эта задача была решена еще в 2018 году приложением Helius' full of life, которое можно найти в App Store.

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

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

Так как хорошо спроектированной интеллектуальной системе одинаково удобно сгенерировать синтаксис любого непротиворечивого языка в любой форме, то для того, чтобы использовать язык коммуникаций между искусственным и естественным интеллектом логично использовать традиционный алгоритмический код, сделав его максимально удобным для паттернов мышления человека, знакомого с мейнстримовыми языками, предком которых был Algol. Это потомки по линии Pascal (Ada, Modula) и C (C++, Java, Swift). Впрочем, наш концепт построения абстракций и внимательное отношение к скобкам близки к духу Scheme (Lisp), а интеграция команд программного окружения в выразительные средства языка соответствует идеям скриптовых языков, винтажному BASIC для первых микрокомпьютеров и проекту Oberon системы Никлауса Вирта.

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

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

Hi World


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

PRINT Hello world!

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

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

FUN gcd   INPUT a: INT   INPUT b: INT   WHILE a ~= b LOOP      IF a > b THEN a -= b ELSE b -= a ENDIF   REPEAT   PRINT gcd = , aRETURNFUN gcd _ a: INT, _ b: INT -> INT   IF b == 0 THEN RETURN a ENDIFRETURN gcd b, (a % b)PRINT gcd 6, 9 # печатает 3

Требования для языка Hi


Каждый удачный язык проектируется с конкретным назначением, которое определяет его синтаксические особенности и семантику. Например, ASSEMBLER был предназначен для прямого кодирования команд процессора в мнемонической форме, удобной человеку; BASIC (тот который с номерами строк и оператором GOTO) удачно продолжил идею прямой трансляции команд высокоуровневому интерпретатору. Hi programming language предназначен стать языком команд и алгоритмов для коммуникаций между Human и абстрактным интеллектом некоторой спроектированной системы.

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

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

  1. Язык должен быть легким в освоении человеком
  2. Язык должен быть надежным в использовании
  3. Язык должен быть способен к организации очень сложных программных систем.

Давайте подробнее исследуем эти базовые требования.

Язык должен быть легким в освоении


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

Следовательно, мы используем конструкции вида LOOPREPEAT, а не {}. Фигурные скобки в качестве приятного бонуса будем использовать вот так:
s = {1, 2, 3} у нас будет обозначать присвоение переменной s множества из трех целых чисел.
a = [1, 2, 3] у нас будет обозначать присвоение переменной a массива из трех целых чисел.

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

2) Для арифметических выражений используем запись вида: a + b + c, а не (+ a b c), как в семействе LISP.

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

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

5) Следим за отсутствием избыточности синтаксических конструкций и эргономичностью ввода символов кода. Мы используем новую строку в качестве разделителя. Впрочем, как и в языке Swift, можно использовать ; в одной строке в качестве разделителя для нескольких выражений. При этом наше инженерное образование категорически протестует против придания синтаксической значимости отступам в тексте программы, как это ранее использовалось в Fortran, а в настоящее время в Python.

Надежный язык


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

foo, Foo, f_001, F1, F, for

Примеры идентификаторов, которые зарезервированы языком:

FOO, FOR, HI, YES, EVERYRESTRICTIONMATTER

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

LET x = 6  # константа x имеет тип INTVAR boolean = TRUE  # объявление переменной boolean типа BOOL

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

Язык для построения очень сложных программных систем


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

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

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

Ограничения


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

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

В завершение скажем о происхождения приветливого наименования языка программирования HI, Hi или hi. Допустим это будет Helius interactive Programming Language или Human Intelligence Programming Language. В отличие от всех конструкций нашего языка это единственный мета идентификатор, который не имеет однозначной семантики.

В следующей статье представим описание Hi Basic Programming Language на одной странице и затем разберем логику конструирования синтаксиса, следуя требованиям тезисов, представленных выше.
Подробнее..

Перевод Эволюция PHP от 5.6 до 8.0 (Часть 1)

20.10.2020 14:07:37 | Автор: admin

Перевод статьи подготовлен в преддверии старта курсаBackend-разработчик на PHP.

Шпаргалка по изменениям в PHP v7.x

PHP_v8.0PHP_v8.0

После релиза PHP версии 7.3 я решил уделить больше внимания развитию PHP: что собственно развивается и в каком направлении искать понимание потенциала и оптимизации этого невероятно популярного языка программирования.

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

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

PHP 7.0

Поддержка анонимных классов

Анонимный класс может использоваться вместо именованного класса:

  • Когда класс не нужно документировать

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

new class($i) {   public function __construct($i) {       $this->i = $i;   }}

Функция целочисленного деления - безопасный способ деления (даже на 0).

Возвращает целочисленный результат деления первого операнда на второй. Если делитель (второй операнд) равен нулю, функция пробрасывает EWARNING и возвращает FALSE.

intdiv(int $numerator, int $divisor)

Добавлен новый оператор объединения с null - ??

$x = NULL;$y = NULL;$z = 3;vardump($x ?? $y ?? $z); // int(3)$x = ["c" => "meaningfulvalue"];vardump($x["a"] ?? $x["b"] ?? $x["c"]); // string(16) "meaningfulvalue"

Добавлен новый оператор - spaceship (космический корабль)(<=>)

Используется для оптимизации и упрощения операций сравнения.

// Преждеf unction orderfunc($a, $b) {return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);}// Используя оператор <=>function orderfunc($a, $b) {return $a <=> $b;}

Объявления скалярных типов

Это всего лишь первый шаг к реализации преимуществ более строго типизированного языка программирования в PHP - v0.5.

function add(float $a, float $b): float {return $a + $b;}add(1, 2); // float(3)

Объявления типов возвращаемых значений

Добавлена возможность возвращать типы помимо скалярных - классы, включая наследование. Хех, упустив при этом возможность сделать это необязательным (что будет введено в v7.1 :))

interface A {static function make(): A;}class B implements A {static function make(): A {return new B();}}

Групповые объявления use

// Явный use синтаксис:use FooLibrary\Bar\Baz\ClassA;use FooLibrary\Bar\Baz\ClassB;use FooLibrary\Bar\Baz\ClassC;use FooLibrary\Bar\Baz\ClassD as Fizbo;// Групповой use синтаксис:use FooLibrary\Bar\Baz{ ClassA, ClassB, ClassC, ClassD as Fizbo };

Делегация генератора

В теле функций генераторов разрешен следующий новый синтаксис:

yield from <expr>

Повышение производительности

PHP 7 почти вдвое быстрее, чем PHP 5.6.

Значительное сокращение использования памяти

Как видно из диаграмм, PHP 7.0 стал громадным шагом вперед с точки зрения производительности и использования памяти. Для страницы с запросами к базе данных версия 7.0.0 более чем в 3 раза быстрее, чем 5.6 с включенным opcache в 2.7 раза быстрее без opcache! С точки зрения использования памяти разница тоже существенная!

Интерфейс Throwable

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

Error и Exception теперь реализуют Throwable.

Иерархия Throwable:

interface Throwable|- Error implements Throwable|- ArithmeticError extends Error|- DivisionByZeroError extends ArithmeticError|- AssertionError extends Error|- ParseError extends Error|- TypeError extends Error|- ArgumentCountError extends TypeError|- Exception implements Throwable|- ClosedGeneratorException extends Exception|- DOMException extends Exception|- ErrorException extends Exception|- IntlException extends Exception|- LogicException extends Exception|- BadFunctionCallException extends LogicException|- BadMethodCallException extends BadFunctionCallException|- DomainException extends LogicException|- InvalidArgumentException extends LogicException|- LengthException extends LogicException|- OutOfRangeException extends LogicException|- PharException extends Exception|- ReflectionException extends Exception|- RuntimeException extends Exception|- OutOfBoundsException extends RuntimeException|- OverflowException extends RuntimeException|- PDOException extends RuntimeException|- RangeException extends RuntimeException|- UnderflowException extends RuntimeException|- UnexpectedValueException extends RuntimeException

Внимание! Вы можете реализовать Throwable только через Error и Exception.

Синтаксис кодирования Unicode \u{xxxxx}

echo "\u{202E}Reversed text"; // выводит Reversed textecho "maana"; // "ma\u{00F1}ana"echo "maana"; // "man\u{0303}ana" "n" комбинирована с символом ~ (U+0303)

Чувствительный к контексту лексер

С этим нововведением глобально зарезервированные словами стала полу-зарезервированными:

callable class trait extends implements static abstract final public protected private constenddeclare endfor endforeach endif endwhile and global goto instanceof insteadof interfacenamespace new or xor try use var exit list clone include includeonce throw arrayprint echo require requireonce return else elseif default break continue switch yieldfunction if endswitch finally for foreach declare case do while as catch die self parent

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

Выражения return в генераторах

Единый синтаксис переменных

Поддержка уровня вложенности для функции dirname()

PHP 7.1

Обнуляемые типы

function answer(): ?int {return null; // ок}function answer(): ?int {return 42; // ок}function answer(): ?int {return new stdclass(); // ошибка}function say(?string $msg) {if ($msg) {echo $msg;}}say('hello'); // ок - выводит hellosay(null); // ок - ничего не выводитsay(); // ошибка - отсутствует параметрsay(new stdclass); // ошибка - не подходящий тип

Ничего не возвращающие функции

function shouldreturnnothing(): void {return 1; // Fatal error: A void function must not return a value}

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

Функция с типом возврата void или void функция может либо возвращать неявно, либо иметь оператор возврата без значения:

function lacksreturn(): void {// валидно}

Псевдотип Iterable

Обычно функция принимает или возвращает array или объект, реализующий Traversable, который используется с foreach. Однако, поскольку array является примитивным типом, а Traversable - интерфейсом, в настоящее время нет способа использовать объявление типа для параметра или возвращаемого типа, чтобы указать, что значение является итерируемым.

function foo(iterable $iterable) {foreach ($iterable as $value) {// }}

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

function bar(): iterable {return [1, 2, 3];}

Параметры, объявленные как iterable могут использовать null или массив в качестве значения по умолчанию.

function foo(iterable $iterable = []) {// }

Closure из callable

class Closure {public static function fromCallable(callable $callable) : Closure {}}

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

$array = [1, 2, 3];// Присваивает $a, $b и $c значения соответствующих элементов массива $array с ключами, пронумерованными от нуля[$a, $b, $c] = $array;// Присваивает $a, $b и $c значения элементов массива $array с ключами "a", "b" и "c" соответственно["a" => $a, "b" => $b, "c" => $c] = $array;

Синтаксис квадратных скобок для list()

$powersOfTwo = [1 => 2, 2 => 4, 3 => 8];list(1 => $oneBit, 2 => $twoBit, 3 => $threeBit) = $powersOfTwo;

Видимость констант класса

class Token {    // Константы по умолчанию public   const PUBLICCONST = 0;// Константы также могут иметь определенную пользователем видимостьprivate const PRIVATECONST = 0;protected const PROTECTEDCONST = 0;public const PUBLICCONSTTWO = 0;// Константы могут иметь только один список объявлений видимостиprivate const FOO = 1, BAR = 2;}

Перехват нескольких типов исключений

try {// Какой-то код} catch (ExceptionType1 | ExceptionType2 $e) {// Код обработки исключения} catch (\Exception $e) {// }

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

Читать ещё:

Подробнее..

Перевод Эволюция PHP от 5.6 до 8.0 (Часть 2)

25.10.2020 22:04:29 | Автор: admin

Перевод статьи подготовлен в преддверии старта курсаBackend-разработчик на PHP

(Читать первую часть)


PHP 7.2

Расширение типа параметра

<?phpclass ArrayClass {public function foo(array $foo) { /*  / }}// Этот RFC предлагает разрешить расширение типа до нетипизированного, т.е. в качестве параметра может быть передан любой тип.// Любые ограничения типа могут быть отработаны с помощью пользовательского кода в теле метода.class EverythingClass extends ArrayClass {public function foo($foo) { /  / }}

Подсчет неисчислимых объектов

Вызов функции count() для скаляра или объекта, который не реализует интерфейс Countable возвращает 1 (что нелогично).

В этой версии добавлено предупреждение при вызове count() с параметром, который является скаляром, null или объектом, который не реализует Countable.

Завершающие запятые в групповом use синтаксисе

use Foo\Bar{ Foo, Bar, Baz, };

Хеширование пароля с помощью Argon2

Существующие функции password_* обеспечивают упрощенный интерфейс с прямой совместимостью для хеширования паролей. Этот RFC предлагает для использования в функциях password_* реализацию Argon2i (v1.3) в качестве безопасной альтернативы.

Отладка эмуляции подготовленного запроса PDO

$db = new PDO();// работает с запросами без связанных значений$stmt = $db->query('SELECT 1');vardump($stmt->activeQueryString()); // => string(8) "SELECT 1"$stmt = $db->prepare('SELECT :string');$stmt->bindValue(':string', 'foo');// возвращает не распаршенный запрос до выполненияvardump($stmt->activeQueryString()); // => string(14) "SELECT :string"// возвращает распаршенный запрос после выполнения$stmt->execute();vardump($stmt->activeQueryString()); // => string(11) "SELECT 'foo'"

PHP 7.3

JSON_THROW_ON_ERROR

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

До PHP v7.2 нам нужно было использовать обходное решение, чтобы получать ошибки из JSON, и оно не отличалось ни надежностью, ни изяществом в своей работе;

Вот пример:

jsondecode("{");jsonlasterror() === JSONERRORNONE // результат falsejsonlasterrormsg() // результат "Syntax error"

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

use JsonException;try {   $json = json_encode("{", JSON_THROW_ON_ERROR);   return base64_encode($json);} catch (JsonException $e) {   throw new EncryptException('Could not encrypt the data.', 0, $e);}

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

$e->getMessage(); // как jsonlasterrormsg()

$e->getCode(); // как jsonlasterror()

Добавлена функция iscountable

// До:if (isarray($foo) || $foo instanceof Countable) {// $foo является countable}// Послеif (iscountable($foo)) {// $foo является countable}

Добавлены функции массива arraykeyfirst (), arraykeylast ()

$firstKey = arraykeyfirst($array);$lastKey = arraykeylast($array);

Добавлена встроенная поддержка samesite cookie

Теперь есть две опции для cookie, использующего флаг samesite: Lax (нестрогий) и Strict (строгий). Разница между Lax и Strict заключается в доступности файла cookie в запросах, исходящих из другого регистрируемого домена с использованием метода HTTP GET. Файлы cookie, использующие Lax, будут доступны по GET, исходящему из другого регистрируемого домена, тогда как файлы cookie, использующие Strict, не будут доступны в GET запросе. Для POST методов разницы нет: браузер не должен разрешать доступ к cookie в POST запросе, исходящем из другого регистрируемого домена.

Set-Cookie: key=value; path=/; domain=example.org; HttpOnly; SameSite=Lax|Strict

Обновление PCRE до PCRE2

Улучшение хеширования паролей с помощью Argon2

Существующие password_* функции обеспечивают упрощенный интерфейс с прямой совместимостью для хеширования паролей. Этот RFC предлагает для использования в password_* функциях реализацию Argon2id в качестве безопасной альтернативы первоначально предложенному Argon2i.

Завершающая запятая в вызовах функций

$newArray = array_merge($arrayOne,$arrayTwo,['foo', 'bar'],// в вызовах функций можно использовать запятую);

Присвоение по ссылкам в list()

$array = [1, 2];list($a, &$b) = $array;

Это эквивалентно следующему:

$array = [1, 2];$a = $array[0];$b =& $array[1];

Устарели не чувствительные к регистру константы

PHP 7.4

Типизированные свойства

class User {   public int $id;   public string $name;   public function __construct(int $id, string $name) {       $this->id = $id;       $this->name = $name;   }}

FFI (Foreign Function Interface)

FFI - одна из фич, которые сделали Python и LuaJIT очень полезными для быстрого прототипирования. Он позволяет вызывать функции C и использовать типы данных C из чисто скриптового языка и, следовательно, более продуктивно разрабатывать системный код. Для PHP FFI открывает способ писать расширения PHP и биндинги к библиотекам C на чистом PHP.

Присваивающий оператор объединения с Null

// Следующие строчки делают одно и то же$this->request->data['comments']['userid'] = $this->request->data['comments']['userid'] ?? 'value';// Вместо повторения переменных с длинными именами используется присваивающий оператор объединения$this->request->data['comments']['user_id'] ??= 'value';

Предварительная загрузка

PHP использует кеши опкодов уже много лет (APC, Turck MMCache, Zend OpCache). Они дают значительный прирост производительности, ПОЧТИ полностью устраняя накладные расходы на перекомпиляцию PHP кода. Предварительная загрузка будет контролироваться только одной новой директивой php.ini - opcache.preload. Используя эту директиву, мы определяем PHP файл, который возьмет на себя задачу предварительной загрузки. После загрузки этот файл полностью выполняется - и может предварительно загружать другие файлы, инклюдя их или используя функцию opcachecompilefile() .

Всегда доступное хеш расширение

Это делает хеш расширение (ext/hash) всегда доступным, схожим образом с date. Хеш расширение предоставляет очень ценную утилиту со множеством алгоритмов хеширования, которая чрезвычайно полезна в современных приложениях, причем не только в коде юзерленда, но и во многом случаях во внутреннем коде.

На пути к PHP 8.0

JIT

В двух словах, когда вы запускаете PHP программу, Zend Engine парсит код в абстрактное синтаксическое дерево (abstract syntax tree - AST) и преобразует его в опкоды. Опкоды - это единицы выполнения для виртуальной машины Zend (Zend VM). Опкоды довольно низкоуровневые, поэтому их гораздо быстрее переводить в машинный код, чем исходный код PHP. В ядре PHP есть расширение OPcache для кеширования этих опкодов.

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

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

Стабильные typeErrors для внутренних функций

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

Сравнение производительности

Я составил простой тест, который поможет легко сравнить производительность различных версий PHP (с использованием Docker). Он даже позволил бы легко проверять производительность новых версий PHP, просто добавляя новые имена контейнеров.

Тестировалось на Macbook pro, Intel Core i7 2,5 ГГц.

PHP version : 5.6.40--------------------------------------test_math                 : 1.101 sec.test_stringmanipulation   : 1.144 sec.test_loops                : 1.736 sec.test_ifelse               : 1.122 sec.Mem: 429.4609375 kb Peak mem: 687.65625 kb--------------------------------------Total time:               : 5.103PHP version : 7.0.33--------------------------------------test_math                 : 0.344 sec.test_stringmanipulation   : 0.516 sec.test_loops                : 0.477 sec.test_ifelse               : 0.373 sec.Mem: 421.0859375 kb Peak mem: 422.2109375 kb--------------------------------------Total time:               : 1.71PHP version : 7.1.28--------------------------------------test_math                 : 0.389 sec.test_stringmanipulation   : 0.514 sec.test_loops                : 0.501 sec.test_ifelse               : 0.464 sec.Mem: 420.9375 kb Peak mem: 421.3828125 kb--------------------------------------Total time:               : 1.868PHP version : 7.2.17--------------------------------------test_math                 : 0.264 sec.test_stringmanipulation   : 0.391 sec.test_loops                : 0.182 sec.test_ifelse               : 0.252 sec.Mem: 456.578125 kb Peak mem: 457.0234375 kb--------------------------------------Total time:               : 1.089PHP version : 7.3.4--------------------------------------test_math                 : 0.233 sec.test_stringmanipulation   : 0.317 sec.test_loops                : 0.171 sec.test_ifelse               : 0.263 sec.Mem: 459.953125 kb Peak mem: 460.3984375 kb--------------------------------------Total time:               : 0.984PHP version : 7.4.0-dev--------------------------------------test_math                 : 0.212 sec.test_stringmanipulation   : 0.358 sec.test_loops                : 0.205 sec.test_ifelse               : 0.228 sec.Mem: 459.6640625 kb Peak mem: 460.109375 kb--------------------------------------Total time:               : 1.003

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

meskis/php-bench

Бенчмарки от PHP 5.6 и выше

Мне очень понравилась компиляция визуальной производительности всех основных версий от 5.6 и выше от servebolt.com. См. результаты в таблицах ниже.

Сводка по производительности

PHP 7.0.0 стал важной вехой со значительно улучшенной производительностью и меньшим использованием памяти, но у специалистов по сопровождению уже PHP попросту заканчиваются идеи по его улучшению. Один из оставшихся моментов - JIT (Just in time) компиляция. И она придет с PHP 8.0.

Направление развития

В версиях PHP 7.x прослеживается путь к более типизированному (и немного более объектному) и современному языку программирования. PHP любит перенимать изящные и полезные функции других языков программирования.

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

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

TL;DR

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

Ссылки

https://wiki.php.net/rfc

https://www.cloudways.com/blog/php-5-6-vs-php-7-symfony-benchmarks/

https://servebolt.com/articles/wordpress-5-0-php-7-2-vs-php-7-3-performance-and-speed-benchmark/


Узнать подробнее о курсе.


Подробнее..

Перевод 5 уроков, которые я извлек для себя, продолжая осваивать ZIO

29.10.2020 16:15:50 | Автор: admin

Всем привет. В преддверии старта курса "Scala-разработчик" подготовили для вас полезный перевод.


Еще в январе 2020 года я написал два поста (I, II) о подводных камнях, с которыми могут столкнуться новички в начале работы с ZIO. Прошло 9 месяцев. На этот период пришелся релиз ZIO 1.0, и среда ZIO значительно улучшилась с внедрением ZLayer.

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

С января я написал порядочное количество кода с использованием ZIO и за это время успел совершить и исправить несколько ошибок. Ниже я расскажу еще о 5 уроках, которые я усвоил, работая с ZIO. Темы будут затронуты очень разные: от оптимальных методов написания рекурсивного кода с помощью ZIO до правильного формулирования тестовых assert-проверок при использовании TestClock.

ФотографияIl VagabiondoнаUnsplash

1. Выполнение рекурсии, небезопасной для кучи

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

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

Рассмотрим упрощенный пример кода нашей реализации опроса и обработки сообщений потребителей в Kafka.

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

def pollLoop(running: Ref[Boolean],             consumer: Consumer            ): UIO[Unit] =  running.get.flatMap {    case true => for {      _ <- pollAndHandle(consumer)      result <- pollLoop(running, consumer)    } yield result    case false => ZIO.unit  }

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

def pollLoop(running: Ref[Boolean],                 consumer: Consumer                 ): UIO[Unit] =  running.get.flatMap {    case true =>       pollAndHandle(consumer)        .flatMap(_ => pollLoop(running, consumer))        .map(result => result)    case false => ZIO.unit  }

Мы решили полностью отказаться от использования рекурсивных методов в нашей кодовой базеGreyhound и вместо них применять оператор ZIOdoWhile, который гарантированно обеспечивает хвостовую рекурсию эффекта, безопасную для кучи. Для вышеприведенного случая мы изменили рекурсивный метод, чтобы он ограничивался только одной операцией опроса (pollOnce), а рекурсией управлял оператор doWhile:

pollOnce(running, consumer).doWhile(_ == true).forkDaemon

Таким образом, данная реализация pollOnce должна возвращать значение UIO[boolean], от которого зависит, будет ли рекурсия выполняться дальше:

def pollOnce(running: Ref[Boolean],                 consumer: Consumer                 ): UIO[Unit] =  running.get.flatMap {    case true => for {      _ <- pollAndHandle(consumer)    } yield true    case false => UIO(false)  }

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

2. Использование побочного эффекта одновременно с повторяющимся эффектом

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

def repeatedlyPublishQuote(stock: Stock) = {  publishQuote(stock).repeat(Schedule.fixed(1.second))}def publishQuote(stock: Stock) = {  println(s"getLatestQuote for $stock")  for {    quote <- grpcClient.getLatestQuote(stock)    _ <- sendToWebsocket(quote)  } yield ()}

Любой побочный эффект не из библиотеки ZIO, выполняемый в методе publishQuote, не будет повторен оператором repeat в repeatedlyPublishQuote. Оператор repeat повторяет только функциональные эффекты.

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

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

def publishQuote(stock: Stock) = {  val sendQuote = for {    _ <- console.putStrLn(s"getLatestQuote for $stock")    quote <- grpcClient.getLatestQuote(stock)    _ <- sendToWebsocket(quote)  } yield ()    sendQuote.catchAll(_ => UIO.unit)}

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

3. Неумышленное использование TestClock в коде периодической assert-проверки

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

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

Примечание. Этот пример написан с использованиемspecs2, однако тот же принцип работает и дляScalaTest.

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

Рассмотрим реализацию eventuallyZ:

def eventuallyZ[T](f: UIO[T])(predicate: T => Boolean): ZIO[Clock, Throwable, Unit] = {  f.repeat(Schedule.spaced(100.milliseconds) && Schedule.doUntil(predicate))    .timeoutFail(new RuntimeException)(4.seconds)    .unit}

С помощью Schedule из библиотеки ZIO заданное условие вызывается каждые 100 миллисекунд до тех пор, пока оно не станет истинным или не наступит тайм-аут спустя 4 секунды.

Но здесь и кроется проблема. eventuallyZ использует часы Clock в среде, но без уточнения, какие именно. Необходимо использовать Live Clock, но в нашем примере, поскольку в тесте используется TestClock, eventuallyZ также будет использовать TestClock, а это означает, что фактически циклическая проверка происходить не будет, так как TestClock.adjust не вызывается.

Решением данной проблемы будет указание правильных часов с помощью оператораZIO provideSomeLayer:

def eventuallyZ[T](f: UIO[T])(predicate: T => Boolean): ZIO[Clock, Throwable, Unit] = {  f.repeat(Schedule.spaced(100.milliseconds) && Schedule.doUntil(predicate))    .timeoutFail(new RuntimeException)(4.seconds)    .provideSomeLayer(Clock.live)    .unit}

Теперь эффекты ZIO, выполняемые в областиeventuallyZ,используютLiveClock и корректно проверяют условия каждые 100 миллисекунд. Это не влияет на остальной код теста, в котором можно и далее использоватьTestClock.

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

4. Не забываем связывать assert-проверки ZIO Test между собой

К вопросу о тестах: у меня также была возможность поработать с замечательной библиотекой ZIO Test. Рассмотрим простой пример: проверка, является ли число положительным и четным:

object NumbersTest extends DefaultRunnableSpec {  override def spec =      testM("positive and even") {        checkAll(Gen.fromIterable(Seq(0, 2, 4, 6))) { number =>            assert(number)(isPositive)            assert(number % 2 == 0)(Assertion.isTrue)          }      }}

Как видите, ее можно легко провести на генерируемом потоке значений, который, разумеется, может быть потоком случайных значений для тестирования свойств (например, положительных целых чисел:Gen.anyInt.filter(_ > 0)). Однако в приведенном выше коде есть небольшая проблема. В действительности будет выполняться только проверка isEven, поскольку первый assert не связан со вторым. Таким образом, тест будет пройден, несмотря на то что 0 не является положительным числом.

+ positive and even after additionRan 1 test in 660 ms: 1 succeeded, 0 ignored, 0 failed

Чтобы это исправить, достаточно связать две assert-проверки с помощью оператора&&:

assert(number)(isPositive) && assert(number % 2 == 0 (Assertion.isTrue)

Теперь тест завершается неудачей:

Ran 1 test in 685 ms: 0 succeeded, 0 ignored, 1 failed- positive and even after additionTest failed after 1 iteration with input: 00 did not satisfy isGreaterThan(0)

5. Прерывание волокна, исходящего из области Managed#Acquire

Волокна (fibers) ZIO это своего рода строительные блоки, из которых создаются функции для одновременного/асинхронного выполнения, предлагаемые ZIO.

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

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

И вот здесь важно помнить, что иногда волокна нельзя прервать! Примерами тому являются области ZManaged acquire и release (необходимо контролировать, что ресурсы приобретаются и высвобождаются безопасным образом), а также те случаи, когда вы указываете свойство uninterruptible:

criticalEffect.uninterruptible

В примере ниже область действия Managed.acquire используется для создания нового волокна, которое периодически посылает сигнальное сообщение heart-beat некому серверу в целях поддержания связи. ОбластьManaged.releaseиспользуется для прерывания (по завершении работы приложения).

object Server extends zio.ManagedApp {  def sendHeartBeat(): URIO[Console with Clock, Unit] =    console.putStrLn("heart-beat").repeat(Schedule.fixed(1.second)).ignore  override def run(args: List[String]): ZManaged[zio.ZEnv, Nothing, ExitCode] =    Managed      .make(sendHeartBeat().fork)(_.interrupt)      .map(_ => ExitCode(0))}

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

Решить эту проблему можно, явно указав интерпретатору ZIO, что это волокно должно быть прерываемым:

Managed.make(sendHeartBeat().fork.interruptible)(.interrupt)

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

sendHeartBeat().toManaged.fork

toManaged_ создает прерываемое волокно, в отличие от Managed.make.

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

Предыдущие посты на эту тему:

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

Узнать подробнее о курсе.

Читать ещё:

Подробнее..

Перевод Модифицируем паттерн Filter с помощью обобщенных лямбда-выражений

19.01.2021 02:08:11 | Автор: admin

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

Паттерн Pipeline & Filter

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

ConditionAggregator - это конвейерный класс, в котором хранится коллекция Condition<T>. Filter<T> владеет ConditionAggregator или Condition<T> для применения условий (condition) фильтрации к набору данных. Когда вызывается функция apply (применить) Filter<T>, выполняется метод check (проверить) ICondition<T>. ConditionAggregator<T> имеет событие OnFilterChanged. Оно срабатывает, когда в классах модели представления изменяется значение коллекции или условия. В следующем разделе будет описано использование паттерна Filter моделью представления.

Код

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

Объяснение паттерна MVVM можно найти по этой ссылке. Одна из обязанностей модели представления (View Model) в MVVM - обрабатывать взаимодействие с пользователем и изменения данных. В нашем случае изменения значений условий фильтрации должны быть переданы на бизнес-уровень, чтобы применить фильтры к определенной коллекции данных. Изменение значения условия в модели представления стригерит событие ConditionAggregator<T> OnFilterChanged, на которое подписан метод фильтра apply. Ниже приведена диаграмма классов модели представления.

Класс сущности Employee создан для хранения информации о сотрудниках. Generic тип T паттерна проектирования Filter будет заменен классом Employee. EmployeeList содержит список данных о сотрудниках и применяемые фильтры. Конструктор класса получает список условий и переходит к списку фильтров.

public EmployeeList(IEmployeesRepository repository, ConditionAggregator<employee> conditionAggregator)        {            this.repository = repository;            this.filters = new ConcreteFilter<employee>(conditionAggregator);            conditionAggregator.OnFilterChanged += this.FilterList;            _ = initAsync();        }

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

private void FilterList(){    this.Employees = this.filters.Apply(this.employeesFullList);}

EmployeesViewModel подключен к пользовательскому интерфейсу. В этом примере продемонстрирован только один фильтр свойства EmployeeTypeselected, но в ConditionAggregator можно передать множество фильтров. Следующий фрагмент кода - это метод-конструктор, в котором регистрируется условие фильтра.

public EmployeesViewModel(IEmployeesRepository repository)        {            this.repository = repository;            Condition&lt;employee&gt; filterEmployee = new Condition&lt;employee&gt;((e) =&gt; e.employeeCode == this.EmployeeTypeSelected);            this.conditionAggregator = new ConditionAggregator&lt;employee&gt;(new List&lt;condition&lt;employee&gt;&gt; { filterEmployee });            this.EmployeeList = new EmployeeList(repository, this.conditionAggregator);                    }  &lt;/condition&lt;employee&gt;&lt;/employee&gt;&lt;/employee&gt;&lt;/employee&gt;

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

Архитектура приложения

WPF.Demo.DataFilter - это представление пользовательского интерфейса WPF. Он имеет одну сетку и одно поле со списком для фильтрации. Проект WPF.Demo.DataFilter.ViewModels обрабатывает данные, фильтрует изменения и перезагружает данные для обновления пользовательского интерфейса. Проект WPF.Demo.DataFilter.Common представляет собой полную реализацию шаблона Pipeline & Filter. WPF.Demo.DataFilter.DAL загружает простенький json-файл в качестве хранилища данных.

Это основной интерфейс:


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


Подробнее..

Перевод Новый поток в C20 stdjthread

22.03.2021 18:15:22 | Автор: admin

Привет, Хабр! Перевод статьи подготовлен в рамках курса "C++ Developer. Professional"


Один из участников моего семинара в рамках CppCon 2018 спросил меня: Может ли std::thread быть прерван (interrupted)?. Мой ответ тогда был нет, но это уже не совсем так. С C++20 мы можем получить std::jthread (в итоге все таки получили прим. переводчика).

Позвольте мне развить тему, поднятую на CppCon 2018. Во время перерыва в моем семинаре, посвященному параллелизму, я побеседовал с Николаем (Йосуттисом). Он спросил меня, что я думаю о новом предложении P0660: Cooperatively Interruptible Joining Thread. На тот момент я ничего не знал об этом предложении. Следует отметить, что Николай является одним из авторов этого предложения (наряду с Хербом Саттером и Энтони Уильямсом). Сегодняшняя статья посвящена будущему параллелизма в C++. Ниже я привел общую картину параллелизма в текущем и грядущем C++.

Из названия документа Cooperatively Interruptible Joining Thread (совместно прерываемый присоединяемый поток) вы можете догадаться, что новый поток имеет две новые возможности: прерываемость (interruptible) и автоматическое присоединение (automatically joining, здесь и далее присоединение блокировка вызывающего потока до завершения выполнения, результат вызова метода join() прим. переводчика). Позвольте мне сначала рассказать вам об автоматическом присоединении.

Автоматическое присоединение

Это неинтуитивное поведение std::thread. Если std::thread все еще является joinable, то в его деструкторе вызывается std::terminate. Поток thr является joinable, если ни thr.join(), ни thr.detach() еще не были вызваны.

// threadJoinable.cpp#include <iostream>#include <thread>int main(){        std::cout << std::endl;    std::cout << std::boolalpha;        std::thread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};        std::cout << "thr.joinable(): " << thr.joinable() << std::endl;        std::cout << std::endl;    }

При выполнении программа терминируется.

Оба потока терминируются. На втором запуске поток th имеет достаточно времени, чтобы отобразить свое сообщение: Joinable std::thread.

В следующем примере я заменяю хедер <thread> на "jthread.hpp" и использую std::jthread из грядущего стандарта C++.

// jthreadJoinable.cpp#include <iostream>#include "jthread.hpp"int main(){        std::cout << std::endl;    std::cout << std::boolalpha;        std::jthread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};        std::cout << "thr.joinable(): " << thr.joinable() << std::endl;        std::cout << std::endl;    }

Теперь поток thr автоматически присоединяется в своем деструкторе, если он все еще является joinable, как, например, в этом примере.

Прерывание std::jthread

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

// interruptJthread.cpp#include "jthread.hpp"#include <chrono>#include <iostream>using namespace::std::literals;int main(){        std::cout << std::endl;        std::jthread nonInterruptable([]{                                   // (1)        int counter{0};        while (counter < 10){            std::this_thread::sleep_for(0.2s);            std::cerr << "nonInterruptable: " << counter << std::endl;             ++counter;        }    });        std::jthread interruptable([](std::interrupt_token itoken){         // (2)        int counter{0};        while (counter < 10){            std::this_thread::sleep_for(0.2s);            if (itoken.is_interrupted()) return;                        // (3)            std::cerr << "interruptable: " << counter << std::endl;             ++counter;        }    });        std::this_thread::sleep_for(1s);        std::cerr << std::endl;    std::cerr << "Main thread interrupts both jthreads" << std:: endl;    nonInterruptable.interrupt();    interruptable.interrupt();                                          // (4)        std::cout << std::endl;    }

Я запустил в main два потока, nonInterruptable, который нельзя прерывать, и interruptable, который можно (строки 1 и 2). В отличие от потока nonInterruptable, поток interruptable, получает std::interrupt_token и использует его в строке 3, чтобы проверить, был ли он прерван: itoken.is_interrupted(). В случае прерывания в лямбде срабатывает return и, следовательно, поток завершается. Вызов interruptable.interrupt() (строка 4) триггерит завершение потока. Аналогичный вызов nonInterruptable.interrupt() не сработает для потока nonInterruptable, который, как мы видим, продолжает свое выполнение.

Вот более подробная информация о токенах прерывания (interrupt tokens), присоединяющихся потоках и условных переменных.

Токены прерывания

Токен прерывания std::interrupt_token моделирует совместное владение (shared ownership) и может использоваться для сигнализирования о прерывании, если токен валиден. Он предоставляет три метода: valid, is_interrupted, и interrupt.

itoken.valid() true, если токен прерывания может быть использован для сигнализировании о прерывании

itoken.is_interrupted() true, если был инициализирован с true или был вызван метода interrupt()

itoken.interrupt() если !valid() или is_interrupted(), то вызов метода не возымеет эффекта. В противном случае, сигнализирует о прерывании посредством itoken.is_interrupted() == true. Возвращает значение is_interrupted()

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

std::jthread jthr([](std::interrupt_token itoken){    ...    std::interrupt_token interruptDisabled;     std::swap(itoken, interruptDisabled);     // (1)           ...    std::swap(itoken, interruptDisabled);     // (2)    ...}

std::interrupt_token interruptDisabled не валиден. Это означает, что поток не может принять прерывание между строками (1) и (2), но после строки (2) уже может.

Присоединение потоков

std::jhread представляет собой std::thread с дополнительным функционалом, реализующим сигнализирование о прерывании и автоматическое присоединение. Для поддержки этой функциональности у него есть std::interrupt_token.

Новые перегрузки Wait для условных переменных

Две вариации wait wait_for и wait_until из std::condition_variable получат новые перегрузки. Они принимают std::interrupt_token.

template <class Predicate>bool wait_until(unique_lock<mutex>& lock,                 Predicate pred,                 interrupt_token itoken);template <class Rep, class Period, class Predicate>bool wait_for(unique_lock<mutex>& lock,               const chrono::duration<Rep, Period>& rel_time,               Predicate pred,               interrupt_token itoken);template <class Clock, class Duration, class Predicate>bool wait_until(unique_lock<mutex>& lock,                 const chrono::time_point<Clock, Duration>& abs_time,                 Predicate pred,                 interrupt_token itoken);

Новые перегрузки требует предикат. Эти версии гарантированно получают уведомления, если поступает сигнал о прерывании для переданного им std::interrupt_token itoken. После вызовов wait вы можете проверить, не произошло ли прерывание.

cv.wait_until(lock, predicate, itoken);if (itoken.is_interrupted()){    // interrupt occurred}

Что дальше?

Как я и обещал в своей последней статье, следующая статья будет посвящена оставшимся правилам определения концептов (concepts).


Узнать подробнее о курсе "C++ Developer. Professional".

Смотреть запись демо-занятия по теме
Области видимости и невидимости: участники вместе с экспертом попробовали реализовать класс общего назначения и запустить несколько unit-тестов с использованием googletest.

Подробнее..

Перевод Если вы пишете код в Windows, вы заслуживаете лучшего терминала

17.04.2021 18:19:17 | Автор: admin

Я хочу сделать признание. Когда дело доходит до моего компьютера, я оставляю все в значительной степени сыром виде. Конечно, у меня есть любимые маленькие инструменты. Я использую плагины Chrome, такие как Wappalyzer, и множество расширений VS Code, таких как Chrome Debugger и Live Server. Но я сознательно не использую темы, шрифты, средства форматирования и другие приятные для глаз настройки. В далеком прошлом, когда я только начинал программировать, я тратил слишком много времени на перестройку своей индивидуальной настройки на разных компьютерах и на новом оборудовании. Постоянные настройки устарели, поэтому я решил по возможности сократить до стокового.

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

Но теперь я вынужден признать, что был неправ. Или, по крайней мере, есть еще один инструмент, для которого мне нужно освободить место. Поскольку Windows Terminal не просто заменяет скрипучую часть программного обеспечения ОС с кодовой базой 30-летней давности, он также добавляет некоторые действительно практичные функции.

Кодовой базе Windows Console 30 лет на самом деле она старше, чем разработчики, которые сейчас над ней работают. - Рич Тернер, менеджер по Microsoft

Терминал открыт

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

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

Вы думаете, что запускаете PowerShell, но на самом деле вы запускаете интерфейс ConHost, который взаимодействует с PowerShell.

Microsoft очень не хочет что-либо менять в работе ConHost, потому что это стержень вековой обратной совместимости. Фактически, основной принцип дизайна ConHost - не нарушать обратной совместимости любой ценой. Даже исправление ошибок рискует уничтожить век сценариев и инструментов, которые каким-то образом все еще работают в режиме совместимости в современной Windows.

Вместо этого Microsoft начала создавать новый терминал под названием Windows Terminal. Он существует уже почти год, но еще не дошел до включения в ОС Windows. Это означает, что если вам нужен Терминал Windows, вы должны установить его из Windows Store. (Или вы можете загрузить его с GitHub и собрать самостоятельно, потому что новый терминал, естественно, имеет открытый исходный код.)

Почему терминал Windows?

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

  • Несколько вкладок. Помните, когда в веб-браузерах была только одна вкладка? Как мы это ненавидели! Но мы терпели это в ConHost уже целое поколение. К счастью, Windows Terminal позволяет открывать столько вкладок, сколько нужно в одном окне.

    Иногда мелочи - это большие делаИногда мелочи - это большие дела
  • Несколько панелей. Это похоже на несколько вкладок, но вы можете видеть разные экземпляры терминала в аккуратном порядке бок о бок или сверху и снизу. И вы управляете всем этим с помощью удобных нажатий клавиш. Удерживая Alt + Shift, нажмите +, чтобы открыть новую панель справа, или -, чтобы открыть новую панель внизу. Затем вы можете переходить с панели на панель, удерживая Alt и нажимая клавиши со стрелками. Круто!

  • Одновременное использование нескольких оболочек. Терминал Windows поддерживает любую стандартную программу оболочки. Вы можете использовать старую добрую PowerShell, почти устаревшую командную строку, Azure Cloud Shell (для управления онлайн-ресурсами Azure) и даже bash, если вы включили Windows Linux Subsystem. И вы можете запускать их все рядом, на разных вкладках или панелях одного и того же окна Терминала Windows.

    Оболочки сошли с умаОболочки сошли с ума
  • Масштабирование, которое работает. Мое любимое сочетание клавиш масштабирования - удерживать Ctrl и вращать колесико мыши. Это работает и в ConHost, но при этом неудобно изменяет размер окна. Терминал Windows масштабирует более разумно, и он распознает удобное сочетание клавиш Ctrl + 0, чтобы вернуть все в нормальное состояние. И не повредит, что Windows Terminal поставляется с новым элегантным шрифтом Cascadia Code, который отлично смотрится при любом размере.

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

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

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

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

  • Настраиваемая прозрачность с размытием фона. Вы даже можете настроить его на лету, удерживая Ctrl + Shift и вращая кнопку мыши. Но зачем?

  • Цветовые схемы и пользовательские фоновые изображения.

  • Анимированные фоны в формате GIF. (Привет, Windows Plus примерно из 1998 года.)

Конечно, если вы решите использовать эти функции, я не буду судить.

Краткое примечание о терминале VS Code

Если вы используете Visual Studio Code, вы, вероятно, знакомы с его интегрированным терминалом. Вы можете выбрать, какую оболочку использовать (например, PowerShell или bash), но вы всегда используете терминал VS Code, а не ConHost.

Тем не менее, терминал VS Code довольно прост. Терминал Windows не может заменить встроенный терминал. Однако вы можете настроить Windows Terminal так, чтобы он работал как внешний терминал для VS Code. Таким образом, когда вы запускаете терминал из VS Code, вы откроете отдельное окно Windows Terminal, что даст вам больше места для передышки и современные функции, которые вам действительно нужны.

Последнее слово

Терминал Windows неуклонно продвигается к версии 2.0, которая ожидается этой весной, и в конечном итоге включение в Windows. Планируется длинный список новых функций, включая возможность отрывать вкладки и перемещать их из одного окна терминала. к другому, бесконечная прокрутка и приятный пользовательский интерфейс для управления настройками. Будет ли он вызывать безумную любовь, как VS Code или язык C #? Нет. Но иногда достаточно сделать жизнь менее болезненной.

Скачать Windows Terminal можно здесь.

Подробнее..

Стажировка и наставничество как инструмент развития команд

27.04.2021 18:13:12 | Автор: admin

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

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

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

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

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

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

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

Основные принципы и подходы:

I этап - подбор кандидатов:

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

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

II этап - отбор потенциальных стажеров:

  1. Тестовое задание до личной встречи, что, как можно лучше, расскажет о теоретических знаниях (hard skills).

  2. Личная встреча или общение, дает четкое понимание о личных качествах (soft skills) и представление о том насколько кандидат подходит и сможет вписаться в текущие процессы, разделить и перенять дух команды.

  3. Импровизация от кандидата. Интересный кейс был в Vodafone Ukraine - когда кандидаты создавали свою презентационную видео визитку и раскрывали свои личности через креатив.

III этап - развитие молодых специалистов:

  1. Обучение: формирование образовательного процесса и вовлечение в него стажеров.

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

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

  4. Чуткость: понимание рабочего настроения и внутреннего ощущения стажера.

IV этап - вовлечение новых коллег:

  1. Равноправие: стажеры - полноправные члены команды и нет никакого разделения.

  2. Вовлеченность: все задачи из реальных процессов и их решение ведет к полноценному продукту или успешному завершению определенной продуктовой стадии.

  3. Осознанность: никакой рутины или ненужных задач, особенно тех, которые не относятся или никак не влияют на реальные продукты.

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

  5. Мотивация: выделение небольших задач, реализация которых будет постоянно показывать результат и прогресс.

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

Одним из наиболее успешных за последнее время стал кейс в инновационном направления Vodafone Ukraine:

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

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

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

Отзыв product owner Марины:

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

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

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

Иногда не хватало помощи в операционных водафоновских вопросах (прописать договор, создать какие-то внутренние заявки), поэтому у меня на них уходило много времени и они слегка дизморалили

Отзыв product owner Максима:

"Для меня стажировка принципе отложилась в памяти как супер крутое время и в том числе благодаря крутым наставникам.

Соответственно, самое позитивное:

1) готовность инвестировать время в мои знания. А с учетом постоянной нехватки времени - это супер круто

2) правильные советы и в нужное время

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

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

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

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

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

Отзыв tech lead Федора:

1. Был опыт наставничества ранее, но с меньшим пониманием процессов и чуть в другом аплуа. Ранее скорее я был в роли тимлида + разработчика, а сейчас скорее техлид + PM и немного CTO

2. Думаю тут все сложилось. И я хотел попробовать в таком формате и ты предложил. win-win

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

4. Да, для меня это тоже развитие однозначно, пусть и не такое стремительное. Мне нравится эта роль в команде.

5. Перспективность брать и развивать джунов больше зависит от трудолюбия каждого конкретного человека. Также есть риски, что получив такие знания он (джун) захочет развиваться не в том коллективе, которые эти знания дал, а там, где за эти знания заплатят больше, но это нормальная ситуация, если компания к этому готова. Что касается построения процессов понятных для разработчика и роли тех. лида, как таковой, то для команды работающей на результат это must have

Отзыв fullstack backend разработчика Александра:

Кураторы адекватные, менторы толковые

Учат понятно, потенциал видят) поддержка есть)

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

Зачастую отсутствие обратной связи в режиме one-to-one создает проблему, которая может привести к негативу, разладу в работе и уходу человека из команды.

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

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

Отзыв product ownerЭда:

В компании Vodafone я менял направление работы 3 раза: стажировка стратегом, работа PM партнерских развлекательных сервисов и, наконец, junior PM продуктов с собственной разработкой (eHealth, eLearning). Собственно последний этап и был самым интересным. Каждый день драйвило то, что именно вы с командой определяете список задач в гибких условиях, а от твоих действий зависит результат всего продукта. Эта зависимость и мотивировала гуглить новую информацию или спрашивать совет старших, а информация касалась буквально всего: интернет-маркетинг, брендинг, копирайтинг, кросс-функциональная коммуникация и многого другого.

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

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

Спустя время я понимаю, что весь этот этап качественно повлиял на мои профессиональные навыки. Более того, сейчас советы Антона могут не ограничиваться конкретным рабочим вопросом, но и дополняться чем-то более обыденным по типу что посмотреть или почитать? что определенно круто и ценно.

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

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

Подробнее..

MQTTv5.0 Обзор новых функций. Часть 2

26.09.2020 22:13:23 | Автор: admin
Всем привет!

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

Прим. Статья направлена на тех, кто имеет интерес или необходимость глубоко погружаться в тонкости MQTT. Здесь не будет картинок и лирических отступлений, только хардкор!!!

Далее приведена таблица всех свойств (см. п. 2.2.2.2 в спецификации).

ID Название Тип данных Пакет / Will Properties
1 PayloadFormatIndicator Byte PUBLISH, Will Properties
2 MessageExpiryInterval FourByteInteger PUBLISH, Will Properties
3 ContentType UTF-8Encoded String PUBLISH, Will Properties
8 ResponseTopic UTF-8Encoded String PUBLISH, Will Properties
9 CorrelationData BinaryData PUBLISH, Will Properties
11 SubscriptionIdentifier VariableByteInteger PUBLISH, SUBSCRIBE
17 SessionExpiryInterval FourByteInteger CONNECT, CONNACK, DISCONNECT
18 AssignedClientIdentifier UTF-8Encoded String CONNACK
19 ServerKeepAlive TwoByteInteger CONNACK
21 AuthenticationMethod UTF-8Encoded String CONNECT, CONNACK, AUTH
22 AuthenticationData BinaryData CONNECT, CONNACK, AUTH
23 RequestProblem Information Byte CONNECT
24 WillDelayInterval FourByteInteger WillProperties
25 RequestResponse Information Byte CONNECT
26 ResponseInformation UTF-8Encoded String CONNACK
28 ServerReference UTF-8Encoded String CONNACK, DISCONNECT
31 ReasonString UTF-8Encoded String CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT, AUTH
33 ReceiveMaximum TwoByteInteger CONNECT, CONNACK
34 TopicAliasMaximum TwoByteInteger CONNECT, CONNACK
35 TopicAlias TwoByteInteger PUBLISH
36 MaximumQoS Byte CONNACK
37 RetainAvailable Byte CONNACK
38 UserProperty UTF-8String Pair CONNECT, CONNACK, PUBLISH, Will Properties, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, DISCONNECT, AUTH
39 MaximumPacketSize FourByteInteger CONNECT, CONNACK
40 WildcardSubscription Available Byte CONNACK
41 SubscriptionIdentifier Available Byte CONNACK
42 SharedSubscription Available Byte CONNACK


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


Payload Format Indicator индикатор формата полезной нагрузки


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


Message Expiry Interval интервал истечения сообщения


Число, представляющее интервал истечения срока действия сообщения (в секундах).

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


Content Type тип содержимого


Значение типа содержимого определяется отправляющим и получающим клиентом.


Response Topic топик для ответа


Строка UTF-8, которая используется в качестве топика для ответного сообщения.

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

Взаимодействие запрос/ответ в этом случае происходит следующим образом (см. п. 4.10.1 в спецификации):

  1. Клиент (отправитель) публикует сообщение запроса с некоторым топиком, в котором указан топик для ответа.
  2. Другой клиент (получатель) предварительно подписывается на фильтр топиков, который соответствует имени топика, использовавшегося при публикации сообщения запроса. В результате он получает сообщение запроса. Может быть несколько клиентов, подписанных на этот топик или может не быть вовсе.
  3. Отвечающий клиент выполняет соответствующее действие на основе полученного сообщения, а затем публикует сообщение ответа с тем топиком, который был указан в свойстве с топиком ответа.
  4. При типичном использовании запрашивающая сторона предварительно подписывается на топик ответа и тем самым получает ответное сообщение. Однако какой-то другой клиент может быть подписан на топик ответа и в этом случае ответное сообщение также будет получено и обработано этим клиентом. Если при отправке ответного сообщения нет подписчиков на топик ответа, ответное сообщение не будет доставлено ни одному клиенту.


Correlation Data корреляционные данные


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

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

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


Subscription Identifier идентификатор подписки


Число, представляющее идентификатор подписки.

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

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

Идентификатор подписки связан с любой подпиской, созданной или измененной в результате пакета SUBSCRIBE. Если есть идентификатор подписки, он сохраняется вместе с подпиской. Если это свойство не указано, то отсутствие подписки сохраняется вместе с подпиской.

Идентификаторы подписки являются частью состояния сеанса на сервере и возвращаются клиенту, получающему соответствующий пакет PUBLISH. Они удаляются из состояния сеанса сервера, когда сервер получает пакет UNSUBSCRIBE, когда сервер получает пакет SUBSCRIBE от клиента для того же фильтра топиков, но с другим идентификатором подписки или без идентификатора подписки, или когда сервер отправляет Session Present равным 0 в пакете CONNACK.


Session Expiry Interval интервал истечения сеанса


Число, представляющее интервал истечения сеанса (в секундах).

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

Если интервал истечения сеанса равен 0xFFFFFFFF (UINT_MAX), сеанс не истекает.

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

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

Установка Clean Start равной 1 и интервалом истечения сеанса 0 эквивалентна установке CleanSession равной 1 в спецификации спецификации MQTT версии 3.1.1. Установка Clean Start в 0 и отсутствие интервала истечения сеанса эквивалентна установке CleanSession в 0 в версии спецификации MQTT 3.1.1.


Assigned Client Identifier назначенный идентификатора клиента


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


Server Keep Alive Keep Alive сервера


Число, определяющее время Keep Alive, назначенное сервером. Если сервер возвращает Server Keep Alive в пакете CONNACK, клиент должен использовать это значение вместо значения, отправленного им в качестве Keep Alive. Если сервер не отправляет Server Keep Alive, он должен использовать значение Keep Alive, установленное клиентом в пакете CONNECT.

Основное использование Server Keep Alive для сервера проинформировать клиента о том, что он отключит клиента в случае неактивности раньше, чем наступит истечение времени Keep Alive, указанного клиентом.


Authentication Method метод аутентификации


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

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

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


Authentication Data данные аутентификации


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


Request Problem Information информация о проблеме запроса


Клиент использует это значение, чтобы указать, отправляется ли строка причины или пользовательские свойства в случае сбоев.

  • 0 сервер может вернуть строку причины или пользовательские свойства в пакете CONNACK или DISCONNECT, но не должен отправлять строку причины или пользовательские свойства в любом пакете, кроме PUBLISH, CONNACK или DISCONNECT,
  • 1 сервер может вернуть строку причины или пользовательские свойства для любого пакета, где это разрешено.


Will Delay Interval интервал задержки Will Message


Число, представляющее интервал задержки Will Message (в секундах). Если интервал задержки отсутствует, значение по умолчанию равно 0 и перед публикацией Will Message задержки нет.

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

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


Request Response Information запрос информации для ответа


Байт со значением 0 или 1. Клиент использует это значение, чтобы запросить у сервера информацию ответа в CONNACK.

  • 0 сервер не должен возвращать информацию ответа,
  • 1 сервер может вернуть информацию ответа в пакете CONNACK, но это не обязательно, даже если клиент запросил эту информацию.


Response Information информация для ответа


Строка UTF-8, которая используется в качестве базы для создания Response Topic. Способ, которым клиент создает Response Topic из информации ответа, не определен этой спецификацией.

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


Server Reference ссылка на сервер


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

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


Рекомендуется, чтобы каждая ссылка состояла из имени, за которым следуют двоеточие и номер порта. Если имя содержит двоеточие, строка имени может быть заключена в квадратные скобки. Имя, заключенное в квадратные скобки, не может содержать символ правой квадратной скобки (]). Это используется для представления адреса IPv6, который использует в качестве разделителя двоеточия.

Имя в ссылке на сервер обычно представляет собой имя хоста, DNS-имя, SRV-имя или IP-адрес. Значение после двоеточия обычно представляет собой номер порта в десятичном виде. Это не требуется, если информация о порте берется из имени (например, для SRV) или используется по умолчанию.

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

Примеры
myserver.xyz.org
myserver.xyz.org:8883
10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883


Reason String строка причины


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

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


Receive Maximum максимальное значение количества пакетов QOS>0


Число, устанавливающее квоту отправки, которая используется для ограничения количества пакетов PUBLISH QOS> 0, которые могут быть отправлены без получения PUBACK (для QoS 1) или PUBCOMP (для QoS 2). То есть это значение используется для ограничения количества публикаций QoS 1 и QoS 2, отправляемых одновременно. Клиент/сервер не должны отправлять сообщения с QoS 1 и QoS 2, если есть отправленные сообщения количества Receive Maximum, на которые еще не получены ответы. При достижении Receive Maximum отправку пакетов с QoS 0 также можно приостановить, но не обязательно. При этом задерживать отправку пакетов, отличных от PUBLISH, не требуется.

Если и клиент, и сервер установили Receive Maximum равным 1, они удостоверяются, что не будет более одного сообщения в полете одновременно.

Указанное значение применяется только к текущему сетевому соединению и инициализируется повторно при переподключении.

Если значение не указано, то по умолчанию оно равно 65 535.


Topic Alias Maximum максимальное значение псевдонима топика


Число, представляющее максимальное значение псевдонима топика.

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


Topic Alias псевдоним топика


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

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

Если пакет PUBLISH содержит псевдоним топика, получатель обрабатывает его следующим образом (см. п. 3.3.4 в спецификации):

  1. Если получатель уже установил сопоставление для псевдонима топика, то
    a) Если пакет имеет топик нулевой длины, получатель обрабатывает его, используя имя топика, которое соответствует псевдониму топика
    b) Если пакет содержит топик ненулевой длины, получатель обрабатывает пакет, используя это имя топика, и обновляет свое отображение для псевдонима топика на имя топика из входящего пакета.
  2. Если у получателя еще нет сопоставления для этого псевдонима, то
    a) Если пакет имеет топик нулевой длины, это ошибка протокола
    b) Если пакет содержит топик с ненулевой длиной, получатель обрабатывает пакет с использованием этого имени топика и устанавливает его сопоставления для псевдонима топика с именем топика из входящего пакета.


Maximum QoS максимальный QoS


Принимает значение 0 или 1. Если максимальное QoS отсутствует, клиент использует максимальное QoS 2.

Если сервер не поддерживает пакеты PUBLISH QoS 1 или QoS 2, он должен отправить максимальное QoS в пакете CONNACK, указав самое высокое QoS, которое он поддерживает.

Если Клиент получает максимальное QoS от Сервера, он не должен отправлять пакеты PUBLISH с уровнем QoS, превышающим указанный максимум.


Retain Available доступно сохранение


  • 0 сохраненные сообщения не поддерживаются,
  • 1 сохраненные сообщения поддерживаются.

Клиент, получающий значение Retain Available с сервера равное 0, не должен отправлять пакет PUBLISH с флагом RETAIN, установленным на 1.


User Property cвойство пользователя


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

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

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


Maximum Packet Size максимальный размер пакета


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

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

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


Wildcard Subscription Available доступна подписка с подстановочными знаками


  • 0 подписки с подстановочными знаками не поддерживаются,
  • 1 такие подписки поддерживаются.

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

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


Subscription Identifier Available доступен идентификатор подписки


  • 0 идентификаторы подписки не поддерживаются,
  • 1 идентификаторы подписки поддерживаются.

Если свойство отсутствует, то идентификаторы подписки поддерживаются.


Shared Subscription Available доступна общая подписка


  • 0 общие подписки не поддерживаются,
  • 1 общие подписки поддерживаются.

Если свойство отсутствует, то общие подписки поддерживаются.

Заключение


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




Вот, пожалуй, и всё. Для тестирования функциональности мне показался довольно удобным и наглядным вот этот проект redboltz/mqtt_cpp.

Буду очень рада, если в комментариях вы поделитесь другими open-source проектами MQTT-клиентов (как с GUI, так и без него), поддерживающих версию 5.0.
Подробнее..

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

25.09.2020 18:15:34 | Автор: admin
points of view by sanja

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

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

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

Удаленная система выходит из строя


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

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

  1. Определите путь обработки ошибок. В коде должны быть явно определены средства для обработки ошибок. Например, правильно разработанная страница ошибок, журнал исключений с метриками ошибок или автоматическое выключение с механизмом резервирования. Главное ошибки должны обрабатываться явно. Нельзя допускать, чтобы пользователи видели ошибки работы вашего кода.
  2. Составьте план восстановления. Рассмотрите каждое удаленное взаимодействие в вашем коде и выясните, что нужно сделать для восстановления прерванной работы. Запускается ли рабочий процесс с точки сбоя? Публикуются ли все полезные данные во время сбоя в таблице очередей или таблице повторов? И повторяются ли каждый раз, когда удаленная система восстанавливается? Есть ли скрипт для сравнения баз данных двух систем и их синхронизации? Системный план восстановления нужно разработать и реализовать до развертывания фактического кода.

Удаленная система медленно отвечает


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

Некоторые из проблем можно решить прозрачно для кода приложения, если использовать технологии Service Mesh, такие как Istio. Тем не менее нужно убедиться, что такие проблемы обрабатываются вне зависимости от метода:

  1. Установите тайм-ауты для удаленных системных вызовов. Это касается и тайм-аутов для удаленного вызова API и базы данных, публикации событий. Проверьте, установлены ли конечные и разумные тайм-ауты для всех удаленных систем в вызовах. Это позволит не тратить ресурсы на ожидание, если удаленная система перестала отвечать на запросы.
  2. Используйте повторные попытки после тайм-аута. Сеть и системы ненадежны, повторные попытки обязательное условие устойчивости системы. Как правило, повторные попытки устраняют множество пробелов в межсистемном взаимодействии.

    Если возможно, используйте какой-то алгоритм в ваших попытках (фиксированный, экспоненциальный). Добавление небольшого джиттера в механизм повтора даст передышку вызываемой системе, если она под нагрузкой, и приведет к увеличению скорости ответа. Обратная сторона повторных попыток идемпотентность, о которой расскажу позже в этой статье.
  3. Применяйте автоматический размыкатель (Circuit Breaker). Существует не так много реализаций, которые поставляются с этим функционалом, например Hystrix. В некоторых компаниях пишут собственных внутренних обработчиков. Если есть возможность, обязательно используйте Circuit Breaker или инвестируйте в его создание. Четкая структура для определения запасных вариантов в случае ошибки хороший вариант.
  4. Не обрабатывайте тайм-ауты как сбои. Тайм-ауты не сбои, а неопределенные сценарии. Их следует обрабатывать способом, который поддерживает разрешение неопределенности. Стоит создавать явные механизмы разрешения, которые позволят системам синхронизироваться в случае тайм-аута. Механизмы могут варьироваться от простых сценариев согласования до рабочих процессов с отслеживанием состояния, очередей недоставленных писем и многого другого.
  5. Не вызывайте удаленные системы внутри транзакций. Когда удаленная система замедляется, вы дольше удерживаете соединение с базой данных. Это может быстро привести к исчерпанию соединений с базой данных и, как следствие, к сбою в работе вашей собственной системы.
  6. Используйте интеллектуальное пакетирование. Если работаете с большим количеством данных, выполняйте пакетные удаленные вызовы (вызовы API, чтение БД), а не последовательные это устранит нагрузку на сеть. Но помните: чем больше размер пакета, тем выше общая задержка и больше единица работы, которая может дать сбой. Так что оптимизируйте размеры пакета для производительности и отказоустойчивости.

Построение системы, которую вызывают другие системы


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

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

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

Общие рекомендации


  1. Используйте агрессивное кэширование. Сеть нестабильна, поэтому кэшируйте столько данных, сколько возможно. Ваш механизм кэширования может быть удаленным, например сервер Redis, работающий на отдельной машине. По крайней мере, вы перенесете данные в свою область управления и уменьшите нагрузку на другие системы.
  2. Определите единицу сбоя. Если API или сообщение представляет несколько единиц работы (пакет), то какова единица сбоя? В случае сбоя вся полезная нагрузка выйдет из строя или, напротив, отдельные блоки будут успешны или потерпят неудачу независимо? Отвечает ли API кодом успеха или неудачей в случае частичного успеха?
  3. Изолируйте внешние доменные объекты на границе системы. Еще один пункт, который, по моему опыту, вызывает проблемы в долгосрочной перспективе. Не стоит разрешать использование доменных объектов других систем во всей вашей системе для повторного использования. Это связывает вашу систему с работоспособностью другой системы. И каждый раз, когда меняется другая система, нужно рефакторить код. Поэтому стоит создавать собственную внутреннюю схему данных и преобразовывать внешние полезные данные в эту схему. И затем использовать внутри собственной системы.

Безопасность


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

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

Удачи!

Что еще почитать:

  1. Как спокойно спать, когда у вас облачный сервис: основные архитектурные советы.
  2. Как технический долг и лже-Agile убивают ваши проекты.
  3. Наш канале в Телеграме о цифровой трансформации.
Подробнее..

История разработки The Light Remake. Часть 2

21.09.2020 12:22:00 | Автор: admin


Приветствую, читатель! В первой части статьи, посвященной разработке The Light Remake в общих чертах был рассмотрен процесс переноса игры на новую версию Unity. Я немного рассказал об используемых шейдерах и эффектах, о том, какие решения были реализованы в работе со светом, какой дополнительный контент был создан, какой контент из старой версии был переработан и т.д. Во второй части речь пойдет о других аспектах разработки, о постэффектах, структуре проекта, работе со звуком, оптимизации и прочих нюансах.

Часть 2


Постэффекты


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

Гамма

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

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





Volumetric light

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

В какой то момент мне на глаза попался очень занимательный эффект объемного освещения Volumetric lights Он позволяет создавать очень приятное, плотное свечение и лучи, которые могут исходить из разных источников. Атмосфера в сцене становится более объемной, объекты начинают утопать в легкой воздушной дымке, что меня крайне впечатлило. Единственным минусом стала высокая ресурсоемкость эффекта. Тем не менее, я решил его применить и благодаря Volumetric light удалось создать приятный свет от солнца во всей сцене, а также мистические направленные пучки света в отдельных местах: в коридоре на старте игры, в помещении с воробушком. Также эффект использовался для подчеркивания акцентов на светящихся ночью фонарных столбах, в эпизоде с проектором, в бойлерной для усиления света из окон, в финальной сцене вознесения персонажа к свету.



SSAO и SSR

Другими наиболее тяжелыми эффектами стал SSAO (Ambient Occlusion ), создающий мягкие затенения по углам и под поверхностями, без него сейчас никуда. Также был добавлен SSR (Screen Space Reflections ), с которым правда было много проблем еще на этапе разработки. SSR создает имитацию отражений на глянцевых поверхностях, в результате чего можно получить симпатичные рефлексы на кафельной плитке, металле и т.д. Проблема в том, что эффект оказался очень тяжелым и понижал FPS на моем железе почти в два раза. Путем некоторых манипуляций в коде постэффекта мне удалось немного снизить качество рассчета и несколько улучшить производительность. В целом частота кадров стала приемлемой, но в некоторых условиях (например при включенном Vsync) SSR вызывал периодические фризы и рывки при движении персонажа.



Другие

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



Кстати говоря, сейчас при работе с графикой часто вспоминается мой личный опыт обучения в художественной школе, где нас учили подчеркивать контрастные тени под объектами, отображать рефлексы на поверхностях предметов и накладывать в работе с пейзажами дымку на горизонте для подчеркивания воздушной перспективы. Теперь-то я знаю, что это все было SSAO, SSR и Global Fog!

Как это работает?


Первоначальный проект был собран буквально на коленке. Никаких навыков программирования на тот момент я не имел и весь функционал игры был построен на нескольких простых скриптах. Основным был Activate trigger скрипт, который я обнаружил в стандартных ассетах Unity. Все функции были построены на том, что персонаж попадает в триггер, включает или выключает определенные объекты и вызывает необходимые действия. О сохранениях или внутриигровых настройках не шло даже речи.

Система

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

Поскольку игра состоит по большому счету из одной локации, я решил не заморачиваться с подгрузками сцен, префабов и компонентов, разместил все необходимое в одной главной сцене. Были созданы контроллеры, которые управляют всем необходимым, переключают настройки, сохраняют игру и считывают сохраненные данные, считывают тексты для субтитров и записок из специального файла в корне игры и т.д. Для простоты реализации как и в прошлых играх было решено использовать систему сохранения в чекпоинтах. На локации находится несколько десятков интерактивных объектов вроде дверей, геймплейных предметов, керосиновых ламп и т.д. Для каждого сохранения необходимо записать идентификаторы состояния объекта, представляющие из себя обычно Int переменную. Например, дверь закрыта и заперта на ключ: DoorOpen o, DoorLocked 1. Не буду вдаваться в подробности относительно программерской темы, поскольку мои навыки в этой области несколько специфичны и поверхностны, однако, для реализации собственных проектов вполне достаточны.

Два финала

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



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

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

Субтитры и локализация

Для ремейка был проработан новый для меня подход к локализации и вообще отображению текстов и сообщений. Точнее, подход этот ранее уже был использован в проекте 7th Sector, однако там объемы контента были значительно меньше. За основу взят метод сохранения данных в хмл документ. Вся текстовая информация для локализации изначально хранится в xml файле в корне игры. Сообщения поделены на группы и имеют индивидуальную метку, принадлежат определенным категориям и определенным языкам. Для переноса строки я решил использовать символ ( * ), а для начала нового сообщения ( # ).



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

Звук


Некоторая часть базовых звуков была перенесена из оригинальных исходников. Амбиент, звуки пения птиц, звук кинопроектора и т.д. было решено оставить для сохранения узнаваемости проекта. Однако, требовалось большое количество и нового контента. Были добавлены звуки самого персонажа, шагов, активируемых предметов, дыхания героя в определенных моментах, эффекта сердцебиения и т.д. Большее разнообразие было внесено в набор звуков окружения, добавлен стрекот кузнечиков, рандомные звуки падения предметов и хлопков дверей. Кстати говоря, на добавление шума цикад или кузнечиков меня вдохновили пейзажи новой Half Life Alyx, в которой очень здорово передана атмосфера жаркого летнего дня. Некоторое время наслаждался, прослушивая и просматривая на Youtube записи с амбиентами из City17.

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

Целым пластом ответственной и кропотливой работы стало создание саундтрека. Композиции в оригинальном проекте не были лицензионными, это был набор из треков Ludovico Einaudi, композитора Thomas Newman и OST из игры Afraid of monsters. Тем не менее, обычно нам западает в душу то, что мы услышали впервые, например оригиналы треков чаще всего кажутся более приятными, чем их ремиксы, по крайней мере для меня. В данном случае при создании новых композиций очень хотелось сохранить стиль и атмосферу оригинальных треков. С данной задачей, как мне кажется, отлично справился композитор Дмитрий Николаев, с которым мы уже работали ранее над игрой 35MM. Особое впечатление на меня произвел новый взгляд на динамичную композицию, звучащую во время показа кинофильма в лекционном зале. Трек сохранил оригинальный энергичный и немного психоделичный стиль, стал звучать свежо, но узнаваемо. Кстати, сам кинофильм тоже был сильно переработан и включал в себя много нового материала. Контент для видео более тщательно выбирался во избежании нарушения авторства, а часть фрагментов была создана самостоятельно.

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



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

Оптимизация


Одной из наиболее болезненных тем и задач в работе над ремейком стала оптимизация. Так вышло, что почти все проекты, созданные мной ранее на старой версии Unity ( 4.6) были достаточно просты в плане нагрузки на железо. Игра 35ММ на моей карте GTX 970 местами выдавала 200- 300 fps в достаточно сложных и загруженных сценах. Оригинальный Свет, собранный на еще более ранней версии движка показывал FPS еще выше. Но при переходе на Unity 2017 частота кадров просела в 2-3 раза. Понятно, что сцена стала намного сложнее, добавились просчеты света и отражений, дополнительные постэффекты и т.д. но настолько разительного снижения производительности я не ожидал. Загвоздка в том, что даже убрав из сцены практически все наполнение, показатель FPS у меня не поднимался выше 200-300. Это полупустая сцена, Карл! Была проведена большая работа по упрощению некоторой геометрии, созданию Lod групп, настройке Occlusion culling и т.д.



Также камерам с помощью скрипта назначалась дистанция отсечения определенных слоев. Для реализации использовался пример из мануалов Unity. Объекты разных размеров были присвоены отдельным слоям, которые перестают рендериться, если камера находится слишком далеко. Мелкие объекты, вроде банок, мусора, деревянных обломков, книг отсекаются после 20-30 метров. Более крупные после 60-80. Все перечисленные меры позволили значительно снизить число Drawcalls. В среднем количество вызовов отрисовки в сцене колеблется в диапазоне 800 2000 DC. Количество полигонов в кадре не превышает 1 миллиона.



Отсечение светильников

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

Немного деталей


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



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



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

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



  • В катакомбах в одном из мест используется прием зацикленного коридора или петли. Похожая задумка была реализована в игре STALKER Зов Припяти на локации, где располагался Оазис. Когда мы попадаем в определенный триггер, скрипт переносит нас в почти такой же коридор, но уже зеркально и в обратном направлении. В результате, блуждая по туннелям мы несколько раз выходим в один и тот же зал. Телепортация не всегда срабатывает корректно, поэтому можно заметить резкий скачок и изменение дымки/ тумана перед собой.

  • Специально для проекта был снят видеоматериал с видами реальной территории, взятой в качестве референса 3 корпус Московского Гуманитарного университета. Из материалов был смонтирован ролик, который стал секретным бонусом, доступным после прохождения игры. Для проникновения на территорию пришлось перелезать через старый бетонный забор. При переброске бойцов в целевой сектор ни одни штаны порваны не были.



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



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



Парочка мыслей


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

Желаю всем удачи, творческого вдохновения и высокого FPS!
Подробнее..

Из песочницы История разработки мобильной игры Полет на Буране

10.10.2020 18:11:17 | Автор: admin
Любовь к космосу у меня возникла с самого детства. Не то, чтобы я академически владел фундаментальными знаниями в астрономии, просто меня притягивала неизвестность, которая таится в несоизмеримых просторах вселенной.



Я всегда мечтал посетить музей космонавтики в Москве и познакомиться с историей освоения космического пространства непосредственно от первого лица. Посмотреть первые спутники и автоматические станции, скафандры Юрия Гагарина и Алексея Леонова, макеты мировых космодромов, отведать еду космонавтов в кафе Под ракетой и конечно же купить сувениров.

Однажды осенью мне удалось спланировать полет в город герой Москва. Столица встретила меня хорошей погодой.

image

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



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

image

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



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

По сей день меня вдохновляет тема космоса и я решил разработать небольшую игру для мобильного телефона на эту тему

За основу я решил взять советский орбитальный корабль-ракетоплан Буран, созданный в рамках программы Энергия Буран:



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

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

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

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



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



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

Первым объектом стал непосредственно сам Буран. Весь объект я разделил на четыре части: три ускорителя и сам корабль:



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

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



Для начала, в качестве теста, я сделал первое разделение на 5 км, а следующее на двадцатом.



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

Иначе говоря позиция Бурана по оси Y, в данном случае, не меняется, а вот позиция стартового комплекса смещается:



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



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



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

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

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



Завершающим этапом я навел порядок в приборной панели, добавил звуковые эффекты и финишный экран:



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

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



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



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



Спустя 3 месяца после публикации приложения можно наблюдать следующую статистику:



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

Что касается монетизации, то можно наблюдать следующую картину:



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

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

Но мои навыки в разработке и запуске подобных проектов с полного нуля очень сильно выросли. И кто знает, какую статистику покажет это приложение в будущем? Возможно уже завтра Буран наберет нужную скорость чтобы по настоящему полететь. Жизнь покажет!

Всем спасибо!
Подробнее..

Начало работы с нейронными сетями

15.02.2021 02:12:27 | Автор: admin

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

  • Искусственные нейроны

  • Весы(weights) и смещения(biases)

  • Активационные функции(activation functions)

  • Слои нейронов(layers)

  • Реализация нейронной сети на Java

Раскрывая нейронные сети

Во-первых, термин нейронные сети может создать снимок мозга в вашем сознании, в частности для тех, кто ранее познакомился с ним. В действительности это правда, мы считаем мозг большая и естественная нейронная сеть. Однако что мы можем сказать об искусственных нейронных сетях (ANN artificial neural network)? Хорошо, он начинается с антонима естественный и первая мысль, которая приходит в нашу голову это картинка искусственного мозга или робота учитывает термин искусственный. В этом случае, мы так же имеем дело с созданием структуры, похожей и вдохновленной человеческим мозгом; поэтому это названо искусственным интеллектом. Поэтому читатель, который не имел прошлого опыта с ANN, сейчас может думать, что книга учит, как строить интеллектуальные системы, включая искусственный мозг, способный эмулировать человеческое сознание, используя Java программы, не так ли? Конечно мы не будем покрывать создание искусственного мышления машин как в трилогии Матрицы; однако эта книга растолкует несколько неимоверных способностей и что могут эти структуры. Мы предоставим читателю Java исходники с определением и созданием основных нейросетевых структур, воспользоваться всеми преимуществами языка программирования Java.

Почему искусственные нейронные сети?

Мы не можем начать говорить про нейросети без понимания их происхождения, включая также термин. Мы используем термины нейронные сети (NN) и ANN взаимозаменяемо в этой книге, хотя NN более общий, покрывая также
естественные нейронные сети. Таким образом, что же такое на самом деле ANN? Давайте изучим немного историю этого термина.

В 1940-ых нейрофизиолог Warren McCulloch и математик Walter Pits спроектировали первую математическую реализацию искусственного нейрона, комбинируя нейронаучный фундамент с математическими операциями. В то время многие исследования осуществлялись на понимании человеческого мозга и как и если бы мог смоделирован, но в пределах области неврологии. Идея McCulloch и Pits была реально уникальна, потому что добавлен математический компонент. Далее, считая, что мозг состоит из миллиардов нейронов, каждый из них взаимосвязан с другими миллионами, в результате чего в некоторых триллионах соединениях, мы говорим о гигантской структуре сети. Однако, каждый нейрон очень простой, действуя как простой процессор, способный суммировать и распространять сигналы.

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

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

Задачи, быстро решаемые человеком

Задачи, быстро решаемые компьютером

Классификация изображений Распознавание голоса идентификация лиц Прогнозирование событий на основе предыдущего опыта

Комплексные вычисления Исправление грамматических ошибок Обработка сигналов Управление операционной системой

Как устроены нейронные сети

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

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

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

Самый базовый элемент искусственный нейрон

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

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

Давая жизнь нейронам активационная функция

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

Четыре самых используемых активационных функций:

  • Сигмоида (Sygmoid)

  • Гиперболический тангенс(Hyberbolic tangent)

  • Жесткая пороговая функция(Hard limiting threshold)

  • Линейная(linear)

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

Фундаментальные величины весы(weights)

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

Важный параметр смещение

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

Части образующие целое слои

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

Нейронные сети могут быть составлены из нескольких соединенных слоев, которые называются многослойными сетями. Обычные нейронные сети могут быть разделены на 3 класса:
1. Input layer;
2. Hidden layer;
3. Output layer;
На практике, дополнительный нейронный слой добавляет другой уровень
абстракции внешней стимуляции, тем самым повышая способность
нейронных сетей представлять больше комплексных данных.

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

Изучение архитектуры нейронных сетей

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

1. Нейронные соединения:
1.1 Однослойные(monolayer) сети;
1.2 Многослойные(multilayer) сети;
2. Поток сигналов:
2.1 Сети прямой связи(Feedforward networks);
2.2 Сети обратной связи(Feedback networks);

Однослойные сети

Нейронная сеть получает на вход сигналы и кормит их в нейроны, которые в очереди продуцируют выходные сигналы. Нейроны могут быть соединены с другими с или без использования рекуррентности. Примеры таких архитектур: однослойный персептрон, Adaline(адаптивный линейный нейрон), самоорганизованная карта, нейронная сеть Элмана(Elman) и Хопфилда.

Многослойные сети

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

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

Сети прямой связи(feedforward networks)

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

Сети обратной связи(Feedback networks)

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

Специальная причина добавить рекуррентность в сеть это выработка динамического поведения, в частности когда сеть адресует проблемы, включая временные ряды или распознавание образов, которые требуют внутреннюю память для подкрепления обучающего процесса. Тем не менее, такие сети особенно трудны в тренировке, в конечном счете не в состоянии учиться. Многие feedback сети однослойные, такие как сети Элмана(Elman) и Хопфилда(Hopfield), но возможно и построить рекуррентную многослойную сеть, такие как эхо и рекуррентные многослойные персептронные сети.

От незнания к знаниям процесс обучения

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

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

Давайте начнем реализацию! Нейронные сети на практике

В этой книге мы покроем все процессы реализации нейронных сетей на Java. Java это объектно-ориентированный язык программирования, созданный в 1990-ые маленькой группой инженеров из Sun Microsystems, позже приобретенной компанией Oracle в 2010-ых. Сегодня, Java представлена во многих устройствах, которые участвуют в нашей повседневной жизни. В объектно-ориентированном языке, таком как Java, мы имеем дело склассами и объектами. Класс план чего-то в реальной жизни, а объект образец такого плана, например, car(класс, ссылающийся на все машины) и my car(объект, ссылающийся на конкретную машину мою). Java классы обычно состоят из атрибутов и методов(или функций), которые включают принципы объектно-ориентированного программирования(ООП). Мы собираемся кратко рассмотреть эти принципы без углубления в них, поскольку цель этой книги просто спроектировать и создать нейронные сети с практической точки зрения. В этом процессе четыре принципа уместны и нуждаются в рассмотрении:

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

  • Инкапсуляция: Аналогично инкапсуляции продукта, при которой некоторые соответствующие функции раскрыты открыто (публичные(public) методы), в то время как другие хранится скрытым в пределах своего домена (частного(private) или защищенного(protected)), избегая неправильное использование или избыток информации.

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

  • Полиморфизм: Во многом схожа с наследованием, но с изменениями в методах со схожими сигнатурами, представляющие разные поведения в разных классах.

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

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

Имя класса: Neuron

Атрибуты

private ArrayList listOfWeightIn

Переменная ArrayList дробных чисел представляет список входных весов

private ArrayList listOfWeightOut

Переменная ArrayList дробных чисел представляет список выходных весов

Методы

public double initNeuron()

Инициализирует функции listOfWeightIn, listOfWeightOut с псевдослучайными числами

Параметры: нет

Возвращает: Псевдослучайное число

public ArrayList getListOfWeightIn()

Геттер ListOfWeightIn

Параметры: нет

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

public void setListOfWeightIn(ArrayList listOfWeightIn)

Сеттер ListOfWeightIn

Параметры: список дробных чисел, сохранненных в объекте класса

Возвращает: ничего

public ArrayList getListOfWeightOut()

Геттер ListOfWeightOut

Параметры: нет

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

public void setListOfWeightOut(ArrayList listOfWeightOut)

Сеттер ListOfWeightOut

Параметры: список дробных чисел, сохранненных в объекте класса

Возвращает: ничего

Реализация класса: файл Neuron.java

Имя класса: Layer

Заметка: Этот класс абстрактный и не может быть проинициализирован.

Атрибуты

private ArrayList listOfNeurons

Переменная ArrayList объектов класса Neuron

private int numberOfNeuronsInLayer

Целочисленное значение для хранения количества нейронов, которая является частью слоя.

Методы

public ArrayList getListOfNeurons()

Геттер listOfNeurons

Параметры: нет

Возвращает: listOfNeurons

public void setListOfNeurons(ArrayList listOfNeurons)

Сеттер listOfNeurons

Параметры: listOfNeurons

Возвращает: ничего

public int getNumberOfNeuronsInLayer()

Геттер numberOfNeuronsInLayer

Параметры: нет

Возвращает: numberOfNeuronsInLayer

public void setNumberOfNeuronsInLayer(int numberOfNeuronsInLayer)

Сеттер numberOfNeuronsInLayer

Параметры: numberOfNeuronsInLayer

Возвращает: ничего

Реализация класса: файл Layer.java

Имя класса: InputLayer

Заметка: Этот класс наследует атрибуты и методы от класса Layer

Атрибуты

Нет

Методы

public void initLayer(InputLayer inputLayer)

Инициализирует входной слой с дробными псевдорандомными числами

Параметры: Объект класса InputLayer

Возвращает: ничего

public void printLayer(InputLayer inputLayer)

Выводит входные весы слоя

Параметры: Объект класса InputLayer

Возвращает: ничего

Реализация класса: файл InputLayer.java

Имя класса: HiddenLayer

Заметка: Этот класс наследует атрибуты и методы от класса Layer

Атрибуты

Нет

Методы

public ArrayList initLayer( HiddenLayer hiddenLayer, ArrayList listOfHiddenLayers, InputLayer inputLayer, OutputLayer outputLayer )

Инициализирует скрытый слой(и) с дробными псевдослучайными числами

Параметры: Объект класса HiddenLayer, список объектов класса HiddenLayer, объект класса InputLayer, объект класса OutputLayer

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

public void printLayer(ArrayList listOfHiddenLayers)

Выводит входные весы слоя(ев)

Параметры: Список объектов класса HiddenLayer

Возвращает: ничего

Реализация класса: файл HiddenLayer.java

Имя класса: OutputLayer

Заметка: Этот класс наследует атрибуты и методы от класса Layer

Атрибуты

Нет

Методы

public void initLayer(OutputLayer outputLayer)

Инициализирует выходной слой с дробными псевдорандомными числами

Параметры: Объект класса OutputLayer

Возвращает: ничего

public void printLayer(OutputLayer outputLayer)

Выводит входные весы слоя

Параметры: Объект класса OutputLayer

Возвращает: ничего

Реализация класса: файл OutputLayer.java

Имя класса: NeuralNet

Заметка: Значения в топологии нейросети фиксированы в этом классе(два нейрона во входном слое, два скрытых слоя с тремя нейронами в каждом, и один нейрон в выходном слое). Напоминание: Это первая версия.

Атрибуты

private InputLayer inputLayer

Объект класса InputLayer

private HiddenLayer hiddenLayer

Объект класса HiddenLayer

private ArrayList listOfHiddenLayer

Переменная ArrayList объектов класса HiddenLayer. Может иметь больше одного скрытого слоя

private OutputLayer outputLayer

Объект класса OutputLayer

private int numberOfHiddenLayers

Целочисленное значение для хранения количества слоев, что является частью скрытого слоя

Методы

public void initNet()

Инициализирует нейросеть. Слои созданы и каждый список весов нейронов созданы случайно

Параметры: нет

Возвращает: ничего

public void printNet()

Печатает нейросеть. Показываются каждое входное и выходное значения каждого слоя.

Параметры: нет

Возвращает: ничего

Реализация класса: файл NeuralNet.java

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

Показанный следующий код имеет тестовый класс, главный метод объектом класса NeuralNet, названный n. Когда этоn метод вызывается (путем выполнения класса), он вызывает initNet () и printNet () методы из объекта n, генерирующие следующий результат, показанный на рисунке справа после кода. Он представляет собой нейронную сеть с двумя нейронами во входном слое, три в скрытом слое и один в выходном слое:

public class NeuralNetTest {    public static void main(String[] args) {        NeuralNet n = new NeuralNet();        n.initNet();        n.printNet();    }}

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

В сумме

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

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

Оригинал книги: Neural Network Programming with Java

Подробнее..

Наши попытки процедурной анимации движения персонажа

20.01.2021 12:12:03 | Автор: admin

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

Возникла необходимость сделать анимацию персонажа для игры, которую мы делаем на коленке". Чем нам не угодили mocap - анимации и анимации с mixamo.com:

  • нужно искать наиболее подходящие анимации

  • нужно много анимаций

  • визуально анимации должны сочетаться

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

Побираясь, как бездомный в чьем-то ведре, в поисках нужных анимаций в течении недели, мне удалось собрать некого Франкенштейна. Именно Франкенштейна потому, что анимаций надыбал отовсюду. Ходил персонаж как офисный работник, крался как эльф 80-го уровня, приседал как человек-паук. Шучу, все было не так уж и ужасно, конечно, для обывателя может и пойдет, а вот меня все же неустраивало разношерстность анимаций. Хотя блендинг и прочие процедурные фишки сильно улучшали дело... Да и ноги не прилипали к земле как надо. Меня это жутко раздражает, когда анимация персонажа не на 100% соответствует тому, что он делает, ноги проходят сквозь пол, руки сквозь стены... ну вы поняли, 21-й век как никак.

Что важно для анимации персонажа: нужно передать ощущение того как персонаж передвигается с учетом физики. Короче, анимация нужна процедурная. Это крайне важно для нашего 3D пазла от первого лица.

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

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

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

Отлично, цепляем все к телу, еще немного IK и получаем что-то типа (одно из приближений):

Обратите внимание на лодыжку. C ней вечно были проблемы. Дело в том, что наш робот сделан инженерами-конструкторами и соединения должны гнуться строго по оси и никак иначе. Хотя это далеко не последний вариант робота, но в целом все примерно так. (Спойлер: соединение ложыжки мы все же переделали. Инженеры поставили его на 3 гидропривода, что дало нужное число степеней свободы).

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

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

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

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

Здесь экспериментирую с тем как робота придавливает плита. Робот должен корректно анимировать позицию своего тела:

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

Что хотелось бы добавить. Полностью избежать использования mocap анимации не удалось. Почему так? Дело в том, что роботу нужна индивидуальность, стиль перемещения. именно для этого ему даются наборы анимаций с которых он перенимает пластику движения и использует ее при расчете процедурной анимации перемещения. Как-то так.В тестах использовалась модель-аналог робота Федора. Извините, это неточная копия. Чертежей не было, собрали "на глаз" :D

Ссылки на наши некоторые наработки по игре (в виде скетчей):

Twitter

Instagram

VK

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru