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

Ruvds_переводы

Перевод JavaScript исполнилось 25 лет

21.12.2020 12:20:35 | Автор: admin
4 декабря 2020 года JavaScript исполнилось 25 лет. Автор материала, перевод которого мы сегодня публикуем, Даниэль Адамс, говорит, что этот язык повлиял на её карьеру, а так же на профессиональную деятельность многих программистов. JavaScript повлиял и на то, как обычные люди со всего мира пользуются веб-сайтами. Даниэль предлагает отпраздновать 25-летие JavaScript, вспомнив о 25 заметных событиях, которые сделали экосистему, сложившуюся вокруг этого языка, такой, какой она стала в наши дни.



1995


1. Создание JavaScript


В 1995 году Брендану Эйху, программисту из компании Netscape, известной выпуском браузера Netscape Navigator, поручили создать скриптовый язык, который должен был выполняться на стороне клиента и хорошо стыковался бы с Java. Первая версия языка отличается от того JavaScript, который мы знаем и любим, но этот язык был создан за 10 дней и в нём уже присутствовали те возможности, вроде функций, являющихся объектами первого класса, которыми мы пользуемся до сих пор.

1997


2. Выпуск стандарта ECMAScript


Несмотря на то, что к 1997 году JavaScript существовал уже два года, для того чтобы реализации JavaScript могли бы использоваться в различных браузерах, нужны были открытые стандарты этого языка. В 1997 компании Netscape и Microsoft объединились в рамках Ecma International для того чтобы стандартизировать язык. Это привело к появлению первой редакции стандарта ECMAScript.

1999


3. В Internet Explorer появляется API XMLHTTP


Вероятно, кто-то ещё помнит о том, как теги iframe использовались для того чтобы, при выполнении запросов, не перезагружать страницы, просматриваемые пользователями. В марте 1999 года в Internet Explorer 5.0 появился браузерный API XMLHTTP, дающий разработчикам возможность выполнять загрузку данных в фоновом режиме.

2001


4. У JavaScript появляется собственный формат представления данных


В 2001 году появился формат представления данных JSON (JavaScript Object Notation), описание которого можно найти на json.org. В 2006 году на всеобщее рассмотрение был вынесен проект RFC, в котором был описан синтаксис JSON, позволяющий организовывать обмен данными между различными приложениями. В частности, с использованием HTTP-запросов, необходимых для обеспечения работы веб-страниц. Формат JSON, благодаря своей простоте, стал весьма популярным и был стандартизирован (ECMA-404). Он широко используется и в наши дни.

2005


5. Сдвиг в сторону AJAX


После того, как другие браузеры последовали за Inernet Explorer в деле поддержки фоновых запросов, позволяющих обновлять содержимое веб-страниц без их перезагрузки, появилось такое понятие, как AJAX (Asynchronous Javascript and XML, асинхронный JavaScript и XML). В сфере веб-разработки начался сдвиг в сторону использования асинхронных механизмов.

2006


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


Браузеры становились всё сложнее. Поэтому появилась потребность в инструментах, которые могли бы помочь программистам при разработке клиентских приложений. В 2005 году был создан набор инструментов Firebug, предназначенный для отладки JavaScript-приложений в браузере Mozilla Firefox и представленный в виде расширения для браузера. Это был первый инструмент, дававший разработчикам возможность исследовать и отлаживать код прямо в браузере. Последняя версия Firebug вышла в 2017 году.

7. Выпуск библиотеки jQuery


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

2008


8. Создание JavaScript-движка V8


По мере того, как веб-сайты превращались из обычных HTML-страниц в JavaScript-приложения, браузерам, в которых выполнялись эти приложения, крайне важно было развиваться в соответствующем направлении. В период между 2007 и 2010 годами вышли релизы многих браузеров, направленные на улучшение возможностей по выполнению JavaScript-кода. Когда был выпущен браузер Google Chrome, используемый в нём JavaScript-движок, V8, был выпущен в виде отдельного проекта. V8 это, с его just-in-time-компилятором, знаковый проект в мире JavaScript. Этот движок нашёл применение не только в браузере Chrome, но и в других разработках, выступая в роли надёжной и быстрой среды выполнения JavaScript.

9. Выпуск первых инструментов разработчика, встроенных в браузер


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

2009


10. Проект CommonJS и попытка стандартизации JavaScript-модулей


Проект CommonJS представлял собой попытку найти способ разбиения JavaScript-кода, используемого за пределами браузера, на модули. Это позволило бы превратить скрипты, код которых содержался в одном большом файле, в наборы модулей. Модули CommonJS оказали влияние на систему модулей Node.js.

11. Проект Node.js и бэкенд-разработка на JavaScript


JavaScript получил широкое применение в качестве браузерного языка для разработки приложений задолго до того, как на этом языке стали создавать серверные программы. В 2009 году Райан Даль, сотрудник Joyent, представил на JSConf EU проект Node.js асинхронную среду выполнения JavaScript, управляемую событиями.

12. Появление CoffeeScript синтаксического сахара для JavaScript


CoffeeScript появился задолго до популяризации идеи расширения возможностей работы с типами в JavaScript. CoffeeScript это язык программирования, созданный под влиянием Ruby, Python и Haskell, код, написанный на котором, компилировался в JavaScript. Компилятор был изначально написан на Ruby, потом переписан на самом CoffeeScript, а значит, в итоге, был представлен JavaScript-кодом. CoffeeScript обрёл популярность благодаря тому, что облегчал работу программистов, давая им возможность пользоваться надёжными JavaScript-конструкциями.

2010


13. У Node.js появляется первый менеджер пакетов


Вскоре после выпуска Node.js был создан и npm (Node Package Manager, менеджер пакетов Node.js). Npm, фактически, стал стандартом в деле управления зависимостями и в клиентских, и в серверных приложениях. Его использование упрощает публикацию и установку кода, управление кодом, предназначенным для многократного использования. В основе этих механизмов лежит файл package.json. В рамках проекта npm создан реестр пакетов, фактически база данных, в которой можно публиковать пакеты. На основе материалов этого реестра построены сотни тысяч приложений.

14. Состоялся первый выпуск Express


В 2010 году вышла первая версия фреймворка Express.js, создатели которого вдохновлялись идеями проекта Sinatra из мира Ruby. Express.js создавался как веб-фреймворк, обладающий минимальными необходимыми возможностями (маршрутизация, ПО промежуточного слоя, вспомогательные HTTP-механизмы) и не навязывающий разработчику некоего правильного подхода к разработке веб-сервера. Если верить данным GitHub, то можно сказать, что Express.js в наши дни является самым популярным JavaScript-фреймворком для серверной разработки.

15. Появились первые современные MVC-фреймворки, основанные на JavaScript


В то время, как набирала популярность серверная разработка на JavaScript, начали появляться MVC-фреймворки для фронтенд-разработки. Среди них стоит отметить Backbone.js и AngularJS (позже этот фреймворк переписали и переименовали в Angular). Началось практическое использование этих фреймворков, они полюбились JavaScript-разработчикам. Подход к фронтенд-разработке, используемый в Backbone.js, хорошо соответствовал нуждам описания бизнес-логики приложений. А AngularJS использовал декларативный подход, который позволял создавать надёжные браузерные проекты. Оба фреймворка повлияли на фреймворки и библиотеки для фронтенд-разработки, появившиеся позже. В частности, речь идёт о React, Ember.js и Vue.js.

2011


16. Ember.js и концепция соглашения превыше конфигурации


В 2011 году появился форк проекта SproutCore, названный Ember.js. Ember.js предлагает JavaScript-разработчикам концепцию соглашения превыше конфигурации (convention over configuration). Благодаря этому им не нужно тратить время на размышления об архитектурных особенностях проектов, которые могут быть стандартизированы на уровне фреймворка.

2012


17. Появление механизмов статической типизации, нацеленных на JavaScript-разработчиков


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

2013


18. Появление React


В 2013 году разработчик из Facebook Джордан Уолк представил миру новую JavaScript-библиотеку, в основе которой не лежала популярная в те времена концепция MVC, используемая во многих других веб-фреймворках. React это библиотека, предназначенная для разработки приложений, основанных на компонентах, направленная на разработку интерфейсов (то есть реализующая возможности того, чему в аббревиатуре MVC соответствует буква V). Эта библиотека в наши дни является одним из самых популярных инструментов веб-разработки.

19. Проект Electron и разработка настольных приложений с использованием Node.js


Популярность Node.js и других технологий, использующих JavaScript, росла. Всё складывалось так, что вполне естественным оказалось бы использование этих технологий не только для разработки веб-проектов. В результате компания GitHub объединила Node.js и движок Chromium в проекте Electron, нацеленном на создание настольных приложений. Среди приложений, созданных на базе Electron, стоит упомянуть GitHub Desktop, Slack и Visual Studio Code.

2015


20. Выпуск стандарта ES2015/ES6


В июне 2015 года вышла шестая версия стандарта ECMAScript. Выхода этого стандарта ждали многие JavaScript-разработчики. Он должен был включить в себя множество ценных возможностей. Среди них экспорт и импорт модулей (ES-модули), объявление констант и многое другое. Предыдущая версия ECMAScript (ES5) была выпущена за 6 лет до этого, а работа над большинством новых возможностей велась с момента выхода стандарта ES3, выпущенного 16 годами ранее.

21. GraphQL становится альтернативой REST


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

22. Выход Node.js 4


В 2015 году в мире JavaScript-разработки произошло значительное событие, которое заключалось в том, что проект io.js снова стал единым целым с проектом Node.js. Всего год назад был создан форк Node.js, названный io.js. Сделано это было в попытке сократить длительность циклов релиза проекта. Когда io.js снова стал частью Node.js, уже вышла 3-я версия io.js. Поэтому, после объединения проектов, совершенно естественным был выход Node.js 4-й версии, в которой совместное существование двух проектов начиналось, так сказать, с чистого листа. После этого циклы релиза Node.js оказались согласованными с выпуском свежих версий движка V8.

2016


23. Появление менеджера пакетов Yarn


Через несколько месяцев после печально известного инцидента с npm-пакетом left-pad был выпущен новый менеджер пакетов Yarn. Этот менеджер пакетов был создан для того чтобы обеспечить более высокую однородность сред выполнения одного и того же кода на разных компьютерах. С выпуском Yarn в экосистеме JavaScript появилось такое понятие, как автоматически генерируемые lock-файлы, что повлияло на развитие других менеджеров пакетов, на то, как в них реализованы механизмы поддержки разработки JavaScript-проектов.

2019


24. Node + JS = OpenJS


После того, как многие годы JS Foundation и Node.js Foundation работали сами по себе, было решено объединить эти две организации, создав новую OpenJS Foundation. Её целью является расширение уровня взаимодействия технологий и создание единой площадки, поддерживающей развитие различных проектов в среде JavaScript-разработки.

2020


25. Первый выпуск Deno наделал много шума


В этом году Райан Даль, создатель Node.js, представил миру Deno среду для выполнения JavaScript- и TypeScript-кода, основанную, как и Node.js, на движке V8. Проект вызвал большой интерес сообщества веб-разработчиков. Этому способствовала популярность Node.js, и то, что в Deno имеется встроенная поддержка TypeScript.

Итоги


Хотя те 25 событий, о которых мы говорили, представляют собой заметные вехи истории JavaScript, помимо них вокруг этого языка происходило ещё очень много интересного и важного. Экосистема JavaScript попросту не существовала бы в её современном виде, если бы не все те силы, которые были приложены к развитию этой экосистемы разработчиками со всего мира. Каждый PR, каждое выступление на конференции, каждая новая публикация всё это внесло вклад в развитие JavaScript. Поэтому мы благодарим всех, чьими силами этот язык стал тем, что он есть, и предлагаем вместе отправиться в светлое будущее JavaScript.

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



Подробнее..

Перевод Три малоизвестных факта об AVIF

25.12.2020 12:18:15 | Автор: admin
AVIF графический формат, основанный на видеокодеке AV1, представляет собой один из самых современных форматов хранения изображений. Судя по ранним публикациям и исследованиям, AVIF показывает достойные результаты в сравнении с JPEG и WebP. Но, даже учитывая то, что этот формат хорошо поддерживается браузерами, AVIF, в плане кодирования и декодирования изображений, всё ещё представляет собой ультрасовременную технологию, которой свойственны определённые проблемы.



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

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


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

Вероятно, это вполне поддаётся объяснению, а систему кодирования изображений можно настроить так, чтобы решить эту проблему. Но большинство тех, кто пользуется AVIF, не станет заниматься чем-то подобным. Эти люди, вероятно, просто воспользуются возможностями оптимизаторов изображений, вроде squoosh.app, или функциями CDN, ориентированных на работу с изображениями, вроде ImageEngine. На следующем графике приведено сравнение размеров AVIF-изображений, получаемых средствами двух вышеупомянутых сервисов, с размерами оптимизированных WebP-изображений.


Зависимость эффективности сжатий изображений от их размера в пикселях

Видно, что обычно байтовые размеры WebP-изображений оказываются больше размеров AVIF-изображений. На изображениях, имеющих большие пиксельные размеры, AVIF показывает себя лучше, при этом с большими изображениями ImageEngine работает значительно эффективнее, чем squoosh.app.

А теперь обратим внимание на одну примечательную деталь, видную в начале графиков. На изображениях размером примерно 100x100 пикселей squoosh.app работает лучше ImageEngine, а затем, на изображениях размером примерно 80x80 пикселей, как и на изображениях меньшего размера, первенство оказывается за WebP.

Этот тест показывает сравнительно однородные результаты на изображениях разных типов. В данном случае использовалось это изображение с Picsum.
Размер (пиксели) Исходный JPEG-файл (байты) Оптимизированный WebP-файл (байты) ImageEngine, AVIF-файл (байты) squoosh.app, AVIF-файл (байты)
50 1,475 598 888 687
80 2,090 1,076 1,234 1,070
110 3,022 1,716 1,592 1,580
150 4,457 2,808 2,153 2,275
170 5,300 3,224 2,450 2,670
230 7,792 4,886 3,189 3,900
290 10,895 6,774 4,056 5,130

2. Формат AVIF, возможно, не очень хорошо подходит для хранения высокоэнтропийных изображений товаров


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

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

Ниже показан увеличенный фрагмент фотографии товара (вот её полные JPEG и AVIF-варианты), на котором чётко видна разница между обычным оптимизированным JPEG-изображением и AVIF-изображением, оптимизированным с помощью squoosh.app.


Сравнение JPEG и AVIF

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

Более того, AVIF, в отличие от JPEG, не поддерживает прогрессивный рендеринг изображений. При создании типичной страницы товара прогрессивный рендеринг может стать важнейшим механизмом по улучшению ключевых показателей производительности страниц. Например, это нечто вроде показателя Largest Contentful Paint (LCP, время отображения наибольшего элемента страницы) и других метрик Core Web Vitals. Даже если на загрузку JPEG-версии изображения требуется немного больше времени, что объясняется тем, что JPEG-файл больше AVIF-файла, JPEG-файл, скорее всего, начнёт рендериться раньше, чем AVIF-файл. Понаблюдать за подобным поведением изображений разных форматов можно в этом видео.

3. Формат JPEG 2000 это сильный конкурент AVIF


Главной сильной стороной AVIF называют чрезвычайно маленький размер файлов при приемлемом визуальном качестве изображений. В ранних публикациях об AVIF и в отчётах об исследованиях этого формата основное внимание уделялось именно этому моменту. Но в некоторых случаях те же задачи, для решения которых применяют AVIF, можно более эффективно решить, прибегнув к формату JPEG 2000 (JP2). JPEG 2000 это сравнительно старый формат, он не привлекает к себе столько же внимания, сколько AVIF, даже учитывая то, что его уже поддерживает компания Apple.

Для того чтобы на практике разобраться в том, о чём идёт речь, давайте посмотрим на этого очаровательного щенка. Размер файла AVIF-версии изображения, созданный средствами squoosh.app с применением стандартных настроек, имеет размер 27,9 Кб. Преобразование того же изображения в формат JPEG 2000 средствами ImageEngine даёт файл размером 26,7 Кб. Разница небольшая, но достаточная для демонстрации возможностей JPEG 2000.

А как насчёт визуального качества изображений? Для сравнения как-либо обработанных версий изображений с их оригиналами часто используется оценка их структурных различий (DSSIM, Structural Dissimilarity). При расчёте показателя DSSIM исходное изображение сравнивается с его вариантом, преобразованным в другой формат. Чем ниже этот показатель тем выше качество нового изображения. Инструмент для вычисления показателя DSSIM, ссылка на который дана выше, работает с PNG-файлами. Для проведения сравнения файлы форматов JPEG 2000 и AVIF без потерь преобразовали в формат PNG. Результаты эксперимента обобщены в следующей таблице.
Формат DSSIM (0 неотличим от оригинала) Размер файла (Кб)
JPEG 2000 0,019 26,7
AVIF 0,012 27,9

AVIF отличается немного лучшим показателем DSSIM, но разница в качестве изображений практически незаметна.


Слева направо: исходное JPEG-изображение, AVIF-изображение, JPEG 2000-изображение

Итоги


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

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

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

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

Пользуетесь ли вы графическим форматом AVIF?



Подробнее..

Перевод CSS о выводе коротких и длинных текстов

26.12.2020 12:22:33 | Автор: admin
Когда, пользуясь возможностями CSS, создают макет страницы, важно учитывать то, что в различных элементах этой страницы могут выводиться короткие и длинные текстовые материалы. Страницы, кроме того, нужно тестировать на предмет того, как они отображают тексты разной длины. Если разработчик чётко понимает то, как обрабатывать различные тексты, выводимые на странице, если он соответствующим образом спроектировал макет, это способно избавить его от множества неприятных неожиданностей.



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

Обзор проблем


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


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

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

  • Нужно ли в такой ситуации обрезать текст?
  • Нужно ли размещать текст в нескольких строках? Если да то каково максимальное количество таких строк?

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


Слово вышло за пределы контейнера

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

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


Кнопка, в которой выводится слишком короткий текст, выглядит необычно

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

Что делать? Возможно, стоит настроить свойство кнопки min-width. Благодаря этому она сможет нормально выводить подписи разной длины.

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

Длинные тексты


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

Свойство overflow-wrap


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

.card {overflow-wrap: break-word;}


Без использования свойства overflow-wrap слово выходит за пределы контейнера

Свойство hyphens


Значение auto CSS-свойства hyphens позволяет сообщить браузеру о том, что он должен самостоятельно принять решение о разделении длинных слов с использованием дефиса и о переносе их на новые строки. Это свойство может принимать и значение manual, что позволяет, используя особые символы, предусмотреть возможность и порядок переноса слова на новую строку в том случае, если в этом возникнет необходимость.

.element {hyphens: auto;}


Без использования свойства hyphens браузер не переносит слово на новую строку

Применяя значение auto свойства hyphens важно помнить о том, что браузер будет переносить любое слово, которое не помещается в строку. Что это значит? Взгляните на следующий рисунок.


Браузер может использовать знак переноса в любом слове

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

Обрезка однострочного текста


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


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

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

.element {white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}

Обрезка многострочного текста


Если нужно обрезать текст, для вывода которого предусмотрено поле, вмещающее несколько строк, нужно прибегнуть к CSS-свойству line-clamp:

.element {display: -webkit-box;-webkit-line-clamp: 3;-webkit-box-orient: vertical;overflow: hidden;}

Для того чтобы это сработало, необходимо использовать и свойство display: -webkit-box. Свойство -webkit-line-clamp позволяет указать максимальное количество строк, по достижении которого текст надо обрезать.


Сравнение обрезки однострочного и многострочного текста

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


Настройка свойства padding приводит к нарушению вывода текста

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


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

.code {overflow-x: auto;}


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

Свойство padding


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


Проблема при выводе подписи к флажку

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

Короткие тексты


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

Установка минимальной ширины элемента


Вернёмся к примеру, который я приводил в начале статьи.

Кнопка, в которой выводится слишком короткий текст

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


Результаты настройки минимальной ширины кнопки

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

Практические примеры


Карточка профиля пользователя


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


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

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

/* Решение 1 */.card__title {text-overflow: ellipsis;white-space: nowrap;overflow: hidden;}/* Решение 2 */.card__title {display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;overflow: hidden;}

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

Навигационные элементы


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


Названия навигационных элементов, выведенные на разных языках

Длина слова About из LTR-языка больше, чем длина аналогичного по смыслу слова из RTL-языка. При выводе на таком языке соответствующий пункт навигационного меню выглядит слишком коротким. Известно, что если в дизайне страниц используются маленькие области, с которыми нужно взаимодействовать пользователям, это плохо сказывается на UX. Как исправить проблему? В данном случае можно просто настроить минимальную ширину навигационного элемента:

.nav__item {min-width: 50px;}


Решение проблемы короткого текста

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

Поле для вывода содержимого статей


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


Длинное слово выходит за пределы контейнера

Здесь имеется длинное слово, которое выходит за пределы контейнера и является причиной появления горизонтальной полосы прокрутки. Выше мы уже говорили о решениях подобных проблем, которые заключаются в использовании CSS-свойств overflow-wrap или hyphens.

Например, эту проблему можно решить так:

.article-content p {overflow-wrap: break-word;}

Оформление виртуальной корзины для покупок


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


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

Решить эту проблему можно, настроив внутренние или внешние отступы элементов. Конкретные действия зависят от ситуации. Здесь я приведу простой пример, предусматривающий использование свойства margin-right при настройке элемента, выводящего название товара.

.product__name {margin-right: 1rem;}

Flexbox-макеты и вывод длинных текстов


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


Элементы выглядят нормально

Вот разметка к этому примеру:

<div class="user"><div class="user__meta"><h3 class="user__name">Ahmad Shadeed</h3></div><button class="btn">Follow</button></div>

Вот стили:

.user {display: flex;align-items: flex-start;}.user__name {text-overflow: ellipsis;white-space: nowrap;overflow: hidden;}

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


Длинное имя пользователя портит внешний вид страницы

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

.user__meta {/* другие стили */min-width: 0;}

После этого даже вывод в элементе длинного имени пользователя не испортит макет. Некоторые подробности об использовании свойства min-width при разработке Flexbox-макетов вы можете найти в этом материале.

Итоги


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

Сталкивались ли вы с проблемами, связанными с выводом текстов разной длины на веб-страницах?



Подробнее..

Перевод Итог десятилетнего исследования связи жестоких видеоигр и подростковой жестокости в дальнейшей жизни

30.12.2020 14:14:51 | Автор: admin

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


В журнале Cyberpsychology, Behavior, and Social Networking были опубликованы результаты продольного исследования, которое продолжалось десять лет. Оно было посвящено изучению того, как игра в жестокие видеоигры в раннем подростковом возрасте (10 лет) отражается на поведении взрослых людей (23 года). Это исследование не нашло корреляции между игрой в жестокие видеоигры в детстве и повышенным уровнем агрессии по прошествии десяти лет.



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

Собственно, это исследование учитывает неоднородность, группируя схожих людей, имеющих общие наборы характеристик, которые похожим образом меняются со временем. Семьи, участвовавшие в исследовании, набирали в большом северо-западном городе начиная с 2007 года (первая волна), пользуясь телефонными справочниками. Им нужно было заполнить опросники. 65% семей были белыми, 12% чернокожими, 19% многоэтническими, 4% другими. В исходной выборке были недостаточно представлены семьи с низким социоэкономическим статусом. Поэтому понадобился их поиск с помощью других участников программы и объявлений. Нужно это было для того чтобы диверсифицировать и дополнить исходную выборку.

Рейтинги видеоигр с точки зрения присутствия в них сцен насилия определялись с привлечением данных Common Sense Media. Эта организация известна надёжными рейтингами фильмов, книг и компьютерных программ, в том числе игр. Участники исследования оценивались по различным поведенческим характеристикам, таким, как агрессия, симптомы депрессии и беспокойства, учёт интересов общества в своих поступках.

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

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

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



Подробнее..

Перевод Практика использования спецификации CSS Scroll Snap

11.01.2021 14:11:20 | Автор: admin
Часто ли у вас возникало желание воспользоваться какой-нибудь возможностью CSS, позволяющей, без лишних усилий, создать элемент-контейнер, поддерживающий прокрутку? CSS, что очень хорошо, даёт нам такую возможность. Я, когда только начинал заниматься фронтенд-разработкой, пользовался для создания прокручиваемых элементов JavaScript-плагинами. Но иногда нужно что-то такое, что позволяет создавать подобные элементы просто и быстро, без привлечения JavaScript. Сделать это можно, воспользовавшись спецификацией CSS Scroll Snap.



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

Зачем использовать CSS Scroll Snap?


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


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

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

Основы работы с контейнерами, поддерживающими прокрутку


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

  • Использование свойства из группы свойств overflow со значением, отличающимся от visible.
  • Применение некоего способа вывода элементов в контейнере, позволяющего расположить их рядом друг с другом (inline-элементы).

Рассмотрим пример.

Вот HTML-код:

<div class="section"><div class="section__item">Item 1</div><div class="section__item">Item 2</div><div class="section__item">Item 3</div><div class="section__item">Item 4</div><div class="section__item">Item 5</div></div>

Вот стили:

.section {white-space: nowrap;overflow-x: auto;}

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

.section {display: flex;overflow-x: auto;}


Контейнер, поддерживающий прокрутку элементов

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

Проблема контейнеров, поддерживающих прокрутку содержимого


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

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


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

Как видите, каждый элемент приходится буквально вести на его место, не отрывая палец от экрана. Это не свайп и это очень неудобно с точки зрения пользователя. Но, используя возможности CSS Scroll Snap, мы можем решить эту проблему, просто описав точки привязки (snap point), которые упростят горизонтальную или вертикальную прокрутку содержимого страницы.

Знакомство с CSS Scroll Snap


Для того чтобы воспользоваться возможностями CSS Scroll Snap дочерние элементы должны выводиться внутри контейнера в inline-режиме. Сделать это можно с использованием одного из вышеописанных методов. Я использую для этих целей Flexbox-макет.

Вот HTML-код:

<div class="section"><div class="section__item">Item 1</div><div class="section__item">Item 2</div><div class="section__item">Item 3</div><div class="section__item">Item 4</div><div class="section__item">Item 5</div></div>

Вот CSS-код:

.section {display: flex;overflow-x: auto;}

Теперь нам, чтобы спецификация CSS Scroll Snap заработала бы, нужно воспользоваться ещё парой свойств. Главный вопрос тут заключается в том, куда именно их нужно добавить.

Сначала надо добавить свойство scroll-snap-type к контейнеру, поддерживающему прокрутку. В нашем примере это элемент с классом section. Затем нужно добавить свойство scroll-snap-align к дочерним элементам (класс section__item).

.section {display: flex;overflow-x: auto;scroll-snap-type: x mandatory;}.section__item {scroll-snap-align: start;}

Вы, вероятно, видя кучу новых ключевых слов, вроде x mandatory и start, чувствуете сейчас некоторую растерянность. Но ничего страшного тут нет скоро вы с ними разберётесь.

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

Вот как это выглядит.


Элементы привязаны к началу контейнера

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

Свойство scroll-snap-type


Свойство scroll-snap-type, в соответствии со спецификацией, позволяет указать на то, что элемент является snap-контейнером. Его значения позволяют задать то, насколько жёстко осуществляется привязка содержимого этого контейнера при прокрутке, а так же то, прокрутка по каким осям принимается во внимание.

Оси контейнера


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

/* Горизонтальная прокрутка */.section {display: flex;overflow-x: auto;scroll-snap-type: x;}/* Вертикальная прокрутка */.section {height: 250px;overflow-y: auto;scroll-snap-type: y;}


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

Настройка жёсткости привязки


Мы, настраивая контейнер, можем задать не только направление прокрутки, но и то, насколько жёстко содержимое должно быть связано с точками привязки. Сделать это можно с помощью значений mandatory и proximity свойства scroll-snap-type.

Ключевое слово mandatory означает, что браузер обязательно должен привязать элемент к точке привязки. Представим, что свойство scroll-snap-align установлено в значение start. Это означает, что элементы при прокрутке обязательно должны ориентироваться на начало контейнера.

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


Элементы жёстко привязаны к началу контейнера

Вот CSS-код:

.section {display: flex;overflow-x: auto;scroll-snap-type: x mandatory;}.section__item {scroll-snap-align: start;}

Здесь можно найти видео к этому примеру.


Прокрутка с привязкой

А вот интерактивный вариант этого примера.


Работа с интерактивным вариантом примера

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

А вот если тут используется значение proximity, то элементы будут вести себя уже немного свободнее, привязка работает не так жёстко. Обратите внимание на то, что значение proximity используется в свойстве scroll-snap-type по умолчанию. Я показываю тут последствия его явного применения лишь ради того, чтобы сделать изложение понятнее.


Элементы привязаны к началу контейнера менее жёстко

.section {display: flex;overflow-x: auto;/* proximity - это значение, используемое по умолчанию, я описываю его лишь для того чтобы сделать изложение понятнее */scroll-snap-type: x proximity;}

Вот видеодемонстрация.


Последствия использования значения proximity

Свойство scroll-snap-align


При настройке дочерних элементов контейнера, поддерживающего прокрутку, нужно указать то, к какому месту контейнера привязываются эти элементы. Делается это путём настройки свойства scroll-snap-align, которое поддерживает значения start (начало), center (центр) и end (конец).

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


Значения свойства scroll-snap-align и их влияние на содержимое, расположенное в контейнере по горизонтали

Если содержимое в контейнере ориентировано вертикально, это значит, что точки привязки, задаваемые значениями start, center и end, тоже расположены по вертикали.


Значения свойства scroll-snap-align и их влияние на содержимое, расположенное в контейнере по вертикали

Ниже приведено несколько видеопримеров.

Привязка содержимого к началу контейнера


При установке свойства scroll-snap-align в значение start содержимое контейнера привязывается к его началу.


Привязка содержимого к началу контейнера

Привязка содержимого к центру контейнера


При установке свойства scroll-snap-align в значение center содержимое контейнера привязывается к его центру.


Привязка содержимого к центру контейнера

Привязка содержимого к концу контейнера


При установке свойства scroll-snap-align в значение end содержимое контейнера привязывается к его концу.


Привязка содержимого к концу контейнера

Использование свойства scroll-snap-stop


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

Вот стили:

.section__item {scroll-snap-align: start;scroll-snap-stop: normal;}

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


При слишком быстрой прокрутке некоторые элементы можно пропустить

По умолчанию свойство scroll-snap-stop установлено в значение normal. Для принудительной привязки элементов это свойство можно установить в значение always. Благодаря этому браузер будет останавливать прокрутку каждый раз, когда очередной элемент достигает точки привязки.

Вот соответствующие стили:

.section__item {scroll-snap-align: start;scroll-snap-stop: always;}

Вот видеодемонстрация.


Браузер не позволяет пропускать элементы

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


Точки привязки это, при использовании значения always, то же самое, что и знаки STOP

Здесь можно найти интерактивный пример, позволяющий поэкспериментировать со свойством scroll-snap-stop.


Эксперименты со свойством scroll-snap-stop

Внутренние отступы и свойство scroll-padding


Сокращённое свойство scroll-padding позволяет устанавливать внутренние отступы, воздействующие на прокрутку содержимого в контейнере. Оно похоже на обычное свойство padding. На следующем рисунке показаны последствия создания внутреннего левого отступа шириной в 50px. Благодаря этому дочерние элементы будут привязаны к точке, отстоящей от края контейнера на 50 пикселей.

Вот стили, соответствующие сценарию горизонтальной прокрутки:

.section {overflow-x: auto;scroll-snap-type: x mandatory;scroll-padding: 0 0 0 50px;}


Использование свойства scroll-padding при реализации горизонтальной прокрутки

То же самое применимо и к контейнерам с вертикальной прокруткой:

.section {overflow-y: auto;scroll-snap-type: y mandatory;scroll-padding: 50px 0 0 0;}


Использование свойства scroll-padding при реализации вертикальной прокрутки

Внешние отступы и свойство scroll-margin


Сокращённое свойство scroll-margin позволяет настраивать внешние отступы дочерних элементов контейнера, учитываемые при их прокрутке. Например, как показано на следующем рисунке, добавив к элементу .item-2 свойство scroll-margin-left: 20px, мы можем сместить позицию привязки элемента на 20 пикселей.


Использование свойства scroll-margin

В результате прокрутка остановится в тот момент, когда до края контейнера будет 20 пикселей. А если пользователь продолжит прокручивать список и дойдёт до элемента .item-3, то край этого элемента будет привязан к началу контейнера, так как для этого элемента свойство scroll-margin не задано.

Способы использования CSS Scroll Snap


Список изображений


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


Прокручиваемый список изображений

Вот стили:

.images-list {display: flex;overflow-x: auto;scroll-snap-type: x;gap: 1rem;-webkit-overflow-scrolling: touch; /* Важно для iOS-устройств */}.images-list img {scroll-snap-align: start;}

Обратите внимание на то, что в качестве значения свойства scroll-snap-type используется x. Жёсткость привязки будет, по умолчанию, установлена в значение proximity.

Вот интерактивный вариант этого примера.


Эксперименты со списком изображений

Список друзей


Список друзей это ещё один отличный способ использования CSS Scroll Snap. Нижеприведённый пример взят с Facebook (то есть перед нами реальный пример).


Список друзей

Вот CSS-код к этому примеру:

.list {display: flex;overflow-x: auto;scroll-snap-type: x mandatory;gap: 1rem;scroll-padding: 48px;padding-bottom: 32px;-webkit-overflow-scrolling: touch;}.list-item {scroll-snap-align: start;}

Обратите внимание на то, что при настройке контейнера используется свойство padding-bottom: 32px. Сделано это для того чтобы дать дополнительное пространство для вывода тени, задаваемой свойством box-shadow.


Без использования свойства padding-bottom тень выводится не полностью

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


В данном примере нас интересует значение center, задаваемое свойству scroll-snap-align дочерних элементов контейнера.


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

Вот стили:

.list {display: flex;overflow-x: auto;scroll-snap-type: x mandatory;-webkit-overflow-scrolling: touch;}.list-item {scroll-snap-align: center;}

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

Здесь с этим примером можно поэкспериментировать.


Эксперименты со списком аватаров

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


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


Элементы, занимающие всю высоту области просмотра страницы

Вот разметка к этому примеру:

<main><section class="section section-1"></section><section class="section section-2"></section><section class="section section-3"></section><section class="section section-4"></section><section class="section section-5"></section></main>

Вот стили:

main {height: 100vh;overflow-y: auto;scroll-snap-type: y mandatory;-webkit-overflow-scrolling: touch;}.section {height: 100vh;scroll-snap-align: start;}

Здесь можно найти рабочий вариант этого примера.


Элементы, занимающие всю доступную высоту области просмотра страницы

Значения inline и block свойства scroll-snap-type


Полагаю, стоит сказать о том, что при настройке свойства scroll-snap-type можно использовать логические CSS-значения inline и block. Вот пример:

main {scroll-snap-type: inline mandatory;}

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

Подробнее о логических CSS-свойствах можно почитать здесь.

Доступность


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

Вот CSS-код:

.wrapper {scroll-snap-type: y mandatory;}h2 {scroll-snap-align: start;}


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

Можете испытать это сами.


Неудачное использование CSS Scroll Snap

Пожалуйста, постарайтесь так не делать!

Пользуетесь ли вы возможностями CSS Scroll Snap в своих проектах?
Подробнее..

Перевод Заметки о Unix два сценария работы с конвейерами

13.01.2021 12:17:24 | Автор: admin
Мне встречалось множество рекомендаций о повышении безопасности использования shell-скриптов в Bash путём включения опции pipefail (например это рекомендуется в данном материале 2015 года). Это, с одной стороны, хорошая рекомендация. Но включение pipefail может привести к конфликту. В одном из двух сценариев использования конвейеров эта опция показывает себя замечательно, а вот в другом то, к чему приводит её включение, выглядит просто ужасно.



Для того чтобы понять суть этой проблемы давайте сначала разберёмся с тем, за что именно отвечает опция Bash pipefail. Обратимся к документации:

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

Причина использования pipefail заключается в том, что иначе команда, неожиданно завершившаяся с ошибкой и находящаяся где-нибудь в середине конвейера, обычно остаётся незамеченной. Она, если использовалась опция set -e, не приведёт к аварийному завершению скрипта. Можно пойти другим путём и тщательно проверять всё с использованием $PIPESTATUS, но это означает необходимость выполнения больших объёмов дополнительной работы.

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

generate --thing | sed 1000q | gronkulate

Команда sed, после получения 1000 строк, завершит работу и закроет конвейер, в который пишет данные generate. А generate получит сигнал SIGPIPE и, по умолчанию, остановится. Статус выхода команды будет отличаться от нуля, а это значит, что с использованием pipefail работа всего конвейера завершится с ошибкой (а с использованием set -e скрипт нормально завершит работу).

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

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

Конечно, пользоваться подобными конвейерами можно и в окружении, где применяется pipefail, и даже с set -e. Например, можно сделать так, чтобы один из шагов конвейера всегда сообщал бы об успешном завершении:

(generate --thing || true) | sed 1000q | gronkulate

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

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

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

Подробнее..

Перевод Умная новогодняя ёлка

14.01.2021 00:13:39 | Автор: admin
Мечтали ли вы когда-нибудь о том, чтобы включать и выключать гирлянду на новогодней ёлке, просто произнеся некие волшебные слова? Если так оно и есть значит эта статья написана специально для вас. Её автор хочет рассказать о том, как оснастить новогоднюю ёлку системой голосового управления.



Материалы


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

  • Реле.
  • Espressif ESP8266 ESP-01.
  • Arduino UNO.
  • Макетная плата.
  • Блок питания для макетной платы.
  • Гирлянда для ёлки.
  • Соединительные провода.
  • Отвёртка.

Программы:


Обзор проекта


Это мой первый проект такого рода. Он посвящён созданию системы, которая позволяет, используя Google Assistant, включать и выключать ёлочную гирлянду.

Вот схема проекта.


Схема проекта

Вот как выглядят компоненты проекта в сборе.


Система голосового управления ёлочной гирляндой

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

Настройка Blynk


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

Загрузим приложение Blynk на мобильное устройство (из Apple Store или из Google Play).

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


Вход в приложение

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


Создание нового проекта

Выберем имя для проекта, укажем, в качестве устройства, Arduino Uno, а в качестве метода подключения WiFi.


Настройка основных свойств проекта

Теперь, касанием экрана, вызовем меню. Нас интересует элемент Text Input.


Выбор элемента Text Input

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


Настройка элемента Text Input

В поле Title можно ввести всё что угодно. В поле OUTPUT надо записать V0. Поле HINT оставим пустым, в поле CHARACTER LIMIT установим ограничение на количество символов, равное 20.

На этом настройка Blynk завершена.

Теперь займёмся IFTTT.

Настройка IFTTT


Загрузите мобильное приложение IFTTT и войдите в него. Коснитесь кнопки Create. Теперь коснитесь кнопки Add в поле If This и найдите Google Assistant. Коснитесь соответствующего значка и выберите в появившемся списке первый пункт Say a phrase with text ingredient он позволяет настроить реакцию систему на фразу, содержащую ключевое слово.

В поле What do you want to say? я ввёл Google turn $ the lights. Символ $ это ключевое слово. Если хотите можете задать тут дополнительную фразу, но ключевое слово всегда должно быть одним и тем же. Затем надо указать ответ, который даст Google Assistant.

Далее, нужно выбрать язык (этот материал написан на английском поэтому я выбрал тут English). Теперь коснёмся Continue и на появившемся после этого экране, в поле Then That, коснёмся Add и выполним поиск по ключевому слову Webhooks. Далее, коснёмся соответствующего значка и займёмся настройками раздела Make a web request.

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

ping blynk-cloud.com

Далее, надо записать IP-адрес, который появится в выходных данных этой команды. Он, например, может выглядеть как 188.166.206.43. Это адрес Blynk-сервера для вашей страны.

Теперь возвращаемся к настройкам приложения IFTTT. Здесь, в разделе Make a web request, в поле URL, нужно ввести такую конструкцию:

http://только что полученный IP-адрес/Токен аутентификации проекта Blynk/pin/V0

В поле Method надо выбрать PUT.

В поле Content Type надо выбрать application/json.

В поле Body надо поместить следующую конструкцию:

["{{TextField}}"]

Теперь надо коснуться кнопки Continue, задать заголовок действия и завершить настройку. После этого IFTTT будет готов к работе.

Код


Вот код нашего проекта, предназначенный для Arduino:

#define BLYNK_PRINT Serial#define EspSerial Serial1#include <SoftwareSerial.h>#include <SPI.h>#include <ESP8266_Lib.h>#include <BlynkSimpleShieldEsp8266.h>// Токен аутентификации надо получить в приложении Blynk.char auth[] = "yourAuthToken";// Данные для подключения к WiFi.// Установите пароль в "" для открытых сетей.char ssid[] = "YourSSID";char pass[] = "YourPassword";SoftwareSerial EspSerial(2, 3); // RX, TX ESP8266String s;  // для хранения получаемых ключевых словBLYNK_WRITE(V0)   // будет запускаться каждый раз, когда приложение Blynk отправляет строку{s=param.asStr();Serial.print(s); //Строка, отправленная приложением Blynk, будет выведена в Serial Monitorif(s=="on"){digitalWrite(7, HIGH);    //Пин 7 установлен в setup()}else if(s=="off"){digitalWrite(7, LOW);}else{Serial.print("Say on or off");}}// Скорость ESP8266 в бодах:#define ESP8266_BAUD 9600ESP8266 wifi(&EspSerial);void setup(){// Отладочная консольSerial.begin(9600);pinMode(7, OUTPUT);    //Pin 7 установлен в режим вывода данных// Установим скорость в бодах для ESP8266EspSerial.begin(ESP8266_BAUD);delay(10);Blynk.begin(auth, wifi, ssid, pass);}void loop(){Blynk.run();}

Обратите внимание на этот фрагмент кода:

if(s=="on")else if(s=="off")

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

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

I am $

Для включения гирлянды решено использовать слово happy, для выключения слово sad. Это значит, что вышеприведённый код нужно будет переписать так:

if(s=="happy")else if(s=="sad")

Код нужно загрузить на Arduino UNO, запустить проект Blynk, открыть Google Assistant и попросить его включить гирлянду.

Планируете ли вы воспользоваться Blynk, IFTTT и Google Assistant в своих проектах?

Подробнее..

Перевод Кунг-фу стиля Linux запуск команд

17.01.2021 12:19:01 | Автор: admin
Одна из особенностей Linux- и Unix-подобных операционных систем, возможность мощная, но, в то же время, вызывающая немало путаницы, заключается в том, что в этих системах до одной и той же цели можно добраться разными путями. Возьмём, например, что-то простое, вроде запуска последовательностей команд. Как это сделать? Пожалуй, самый очевидный ответ на этот вопрос заключается в написании shell-скрипта. Это потрясающе гибкий подход к решению подобной задачи. Но что если нужно всего лишь запустить несколько команд, по возможности ничем не усложняя себе жизнь? Выглядит такая задача весьма простой, но существует множество способов решить её от простого ввода этих команд в командной строке, до планирования их запуска. За выполняющимися командами, кроме того, можно наблюдать, организовав мониторинг очереди задач так, как он может быть организован на мейнфрейме.



Поговорим о запуске команд в Linux, рассмотрим несколько способов запуска последовательностей команд из bash (и из многих других оболочек Linux). Здесь мы коснёмся таких вопросов, как использование утилит cron и at, поговорим о системе пакетного выполнения команд с использованием очереди (task spooler). Я, как и в большинстве случаев обсуждения возможностей Linux, не могу сказать, что то, о чём я хочу рассказать, хотя бы близко подходит к полному освещению способов запуска команд в Linux. Но я надеюсь, что мой рассказ даст вам некоторые идеи относительно управления выполнением последовательностей команд.

Запуск команд из командной оболочки


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

date ; df ; free

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


Не запускайте эту последовательность команд в реальной системе!

Нам нужно стереть в папке foo все файлы (но не подпапки для этого нужен ключ -r). А что если не удастся выполнить команду cd? Тогда будут стёрты все файлы в текущей папке. А это очень плохая идея, которая, к тому же, нарушает правило наименьшего удивления.

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

cd /foo && ls  # команда rm тут не используется, поэтому эта конструкция ничего не испортит

Если директория foo существует команда cd вернёт 0, который будет воспринят как истинное значение. Это означает, что результат операции И может быть истинным. Поэтому работа продолжается и выполняется команда ls. А вот если команду cd выполнить не удастся результат будет ложным. Если любое из входных значений функции, реализующей логику оператора И, является ложным, то остальные входные значения роли уже не играют. В результате если хотя бы одна из частей этой конструкции вернёт ложное значение выполнение всей последовательности команд будет остановлено. Получается, что если директории /foo не существует команда ls попросту не выполнится.

С использованием && можно строить и более длинные конструкции.


Более длинная конструкция, в которой используется && (тут тоже есть rm, поэтому будьте очень осторожны, пытаясь запустить нечто подобное в реальной системе)

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

grep "alw" /etc/passwd || echo No such user



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

Попробуйте, вместо alw, ввести своё имя пользователя, а потом испытайте эту конструкцию с именем пользователя, которого в вашей системе нет (уверен, у вас нет пользователя alw). Если grep отработает успешно, то команда echo выполнена не будет.

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

Планирование запуска команд в определённое время


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

Файл crontab редактируют, пользуясь одноимённой командой (crontab -e). Каждая строка этого файла, не являющаяся комментарием, описывает некую команду, которую нужно выполнить. Первая часть такого описания сообщает о том, когда именно нужно выполнить команду. Вторая часть содержит указание на саму команду. Например, вот запись, позволяющая запустить команду обновления duckdns:

*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1

В начале строки находится описание времени запуска команды минуты, час, день месяца, день недели. Конструкция */5 указывает на то, что команду нужно запускать каждые 5 минут. Символы * являются универсальными местозаполнителями, представляющими любой час, день месяца и так далее. Есть множество особых конструкций, которыми можно пользоваться в подобных описаниях. Для того чтобы упростить их составление можете попробовать этот crontab-редактор. Пример работы с ним показан ниже.


Работа с crontab-редактором

Правда, при использовании cron можно столкнуться с одной проблемой. Она заключается в том, что логика утилиты основана на предположении о том, что компьютер работает в режиме 24/7. Так, если запланировать запуск некоей задачи на ночь, а ночью компьютер будет выключен задача выполнена не будет. Есть ещё одна утилита, anacron, которая создана в попытке исправить этот недостаток. Она, учитывая некоторые ограничения, похожа на cron, но она навёрстывает упущенное в том случае, если на момент запланированного запуска некоей задачи компьютер был выключен.

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

at now + 10 minutes

В ответ на эту команду будет показано простое приглашение командной строки, с помощью которого можно вводить команды. В данном случае эти команды будут выполнены через 10 минут. Эта команда, конечно, поддерживает и указание абсолютных временных значений. Кроме того, программа вас поймёт, если вы вместо 4PM сообщите ей о teatime (серьёзно). Команда atq позволяет просмотреть список запланированных задач. А команда atrm позволяет отменять запуск запланированных команд. Это пригодится в том случае, если по какой-то причине в выполнении запланированной команды больше нет необходимости. Если воспользоваться пакетной формой команды (batch), система выполнит команды тогда, когда нагрузка на неё будет не слишком высокой.

Если почитать справку по at, то можно узнать о том, что утилита, по умолчанию, использует очередь a для обычных задач, а очередь b для пакетных задач. Для указания очередей можно использовать буквы из диапазонов a-z и A-Z. От имени очереди зависит приоритет помещённых в неё задач.

Тут мне хотелось бы отметить то, что в большинстве систем все задачи, поставленные в очередь, будут выполняться в оболочке, заданной как оболочка, используемая по умолчанию (вроде /bin/sh), и это необязательно будет bash. Может понадобиться использовать именно bash, или протестировать команды в оболочке, используемой по умолчанию. Если просто запустить скрипт, в котором, в качестве интерпретатора указан bash (например #!/usr/bin/bash), то это будет незаметно.

Пакетное выполнение задач


Хотя утилита at имеет вариант, выглядящий как batch, её нельзя назвать полноценной системой, предназначенной для пакетного выполнения задач. Существует несколько подобных систем для Linux, обладающих различными особенностями. Одна из таких систем называется Task Spooler (в репозиториях Ubuntu task-spooler). В некоторых системах соответствующая команда выглядит как ts, но в Debian использование подобного имени команды приводит к конфликту, поэтому там используется команда tsp.

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

alw@enterprise:~$ tsp wget http://www.hackaday.com0alw@enterprise:~$ tspID State Output E-Level Times(r/u/s) Command [run=0/1]0 finished /tmp/ts-out.TpAPIV 0 0.22/0.00/0.00 wget http://www.hackaday.comalw@enterprise:~$ tsp -i 0Exit status: died with exit code 0Command: wget http://www.hackaday.comSlots required: 1Enqueue time: Fri Jun 9 21:07:53 2017Start time: Fri Jun 9 21:07:53 2017End time: Fri Jun 9 21:07:53 2017Time run: 0.223674salw@enterprise:~$ tsp -c 0--2017-06-09 21:07:53-- http://www.hackaday.com/Resolving www.hackaday.com (www.hackaday.com)... 192.0.79.32, 192.0.79.33Connecting to www.hackaday.com (www.hackaday.com)|192.0.79.32|:80... connected.HTTP request sent, awaiting response... 301 Moved PermanentlyLocation: http://hackaday.com/ [following]--2017-06-09 21:07:53-- http://hackaday.com/Resolving hackaday.com (hackaday.com)... 192.0.79.33, 192.0.79.32Reusing existing connection to www.hackaday.com:80.HTTP request sent, awaiting response... 200 OKLength: unspecifiedSaving to: index.html0K .......... .......... .......... .......... .......... 1.12M50K .......... .......... .......... ... 6.17M=0.05s2017-06-09 21:07:53 (1.68 MB/s) - index.html saved [85720]

Первая команда запускает, в виде задачи, утилиту wget (это, на самом деле, задача 0). Выполнение команды tsp позволяет просмотреть список задач, находящихся в очереди (в данном случае это всего одна задача, которая уже завершена). Опция -i позволяет просмотреть сведения об указанных задачах. Опция -c выводит выходные данные задачи. Опцию -c можно воспринимать как нечто вроде команды cat. Ещё одна опция, -t, похожа на опцию -f команды tail. Выходные данные задачи можно, кроме того, отправить по электронной почте, воспользовавшись опцией -m.

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

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

Итоги


Как и во многих других ситуациях, возникающих при работе в Linux, вышеописанные способы запуска наборов команд можно комбинировать. Например, можно сделать так, чтобы cron поставил бы задачу в очередь. А сама эта задача может представлять собой скрипт, в котором применяются операторы && и ||, управляющие внутренней логикой выполнения набора команд. Думаете, что это неоправданно сложно? Может быть. Как я уже говорил, можно просто взять и написать обычный скрипт. Но в Linux есть и много других полезных механизмов для решения самых разных задач.

Как вы обычно запускаете наборы команд в Linux?

Подробнее..

Перевод Первый настоящий палмтоп

18.01.2021 12:13:17 | Автор: admin
Однажды, во времена, когда ещё не было COVID-19, я шёл по аэропорту к своему гейту. Вдруг я вспомнил об одном документе, который мне хотелось прочесть во время полёта. Но я забыл взять с собой этот документ. Правда, это меня совершенно не расстроило. Я просто ненадолго остановился, достал кое-что из кармана и загрузил из интернета то, что мне было нужно. Потом, удобно устроившись в кресле самолёта, я спокойно приступил к чтению. Начитавшись, я немного поработал над C-кодом одной шароварной программы, разработкой которой я занимался.

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

Это был HP-200LX первый настоящий палмтоп. Я регулярно им пользовался до середины 2000-х годов. Такой компьютер, полностью рабочий, всё ещё лежит в ящике моего стола. Сегодня я хочу рассказать о том, как родилось это интересное устройство.


HP-200LX

Немного истории


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

Компания Hewlett Packard начинала как производитель измерительного и аналитического оборудования. Это дело живо до сих пор, правда, в 1999 году всё, что не имеет отношения к компьютерам, было выделено в отдельную компанию (Agilent), а позже, в 2013, одно из подразделений Agilent было выделено в самостоятельную компанию (Keysight). Компьютерное подразделение Hewlett Packard появилось в 1960-е годы, оно произвело несколько семейств компьютеров, в одно из которых входил HP 2100, который естественным образом дополнял лабораторное аналитическое оборудование, производимое компанией.


Миникомпьютер HP 1000 E-Series

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

Томас Осборн, инженер, создавший прототип калькулятора, пребывал в расстроенных чувствах из-за того, что его проект не заинтересовал более 30 компаний, в число которых входила и HP. Тогда его бывший коллега познакомил его с кем-то из HP. Том встретился с командой специалистов HP и его тут же привлекли к работе над настольным компьютером, который должен был помещаться в ящик для пишущей машинки Билла Хьюлетта. HP-9100 назвали калькулятором устройство было таким компактным, что никто не поверил бы в то, что это компьютер. И оно, в итоге, посредством некоторых ухищрений, влезло в ящик для пишущей машинки. В 1968 году HP-9100 вышел в продажу. Он добился невероятного успеха, превышающего все ожидания.

После выхода HP 9100 Билл Хьюлетт сделал такое предложение: следующая машина должна стоить в десять раз дешевле, чем HP-9100, она должна быть в десять раз меньше и в десять раз быстрее. В целом, всё свелось к тому, что новый калькулятор должен помещаться в карман его рубашки. Для того чтобы создать такое устройство, одной только счастливой случайности было недостаточно. Это стало возможным лишь после того, когда появились надёжные интегральные схемы MOS LSI. Это устройство, HP-35, вышло через четыре года, в 1972 году. Это был ещё один успешный продукт, который стал одним из родоначальников большой линейки калькуляторов RPN, которыми пользовалось целое поколение учёных и инженеров. А некоторые из этих калькуляторов работают до сих пор.

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

Но те, кто занимались в HP компьютерами, тоже не сидели сложа руки. Компьютеры HP, выходившие 1980-е, становились всё компактнее и компактнее. Это были системы 80-й серии, такие, как HP-85. Среди заметных компактных моделей тех времён можно отметить HP-71 и HP-75.

Портативный компьютер HP-110 вышел в 1984 году. В его ROM были записаны MS-DOS и Lotus 123. Учитывая то, что компания HP постоянно занималась разработкой всё более компактных и мощных компьютеров, неудивительно то, что она, в итоге, создала то устройство, которое я достал из кармана в зале ожидания аэропорта.


Портативный компьютер HP-110

Другие компактные компьютеры


Другие компании, производившие калькуляторы и компьютеры, тоже в это время не бездельничали. Если вспомнить старые компьютеры, помещавшиеся в карман, то можно сказать, что таких устройств было много, и что они обладали разными возможностями. Так, компания Radio Shack выпускала несколько моделей карманных компьютеров, которые могли выполнять программы, написанные на языке Basic. В частности, это был PC1, выпущенный в 1980, и PC2, выпущенный в 1981. Оба представляли собой результат ребрендинга устройств японской компании Sharp. Это были, учитывая их размеры, удивительно мощные устройства. Я, на самом деле, после окончания института, несколько лет пользовался PC2. Мне тогда, на работе, очень пригодился четырёхцветный перьевой плоттер для вывода схем антенн.

В 1983 году вышли компьютеры Radio Shack семейства TRS-80 Model 100, основанные на разработках японской компании Kyocera. Они обрели немалую популярность в среде журналистов и тех, кому нужно было работать с текстами на ходу.


Портативный компьютер Radio Shack TRS-80 Model 100

В то время как это были устройства, которые относились к компьютерам общего назначения, другие компании занимались работой над карманными компьютерами нового типа, которые в итоге стали называться PDA (Personal Digital Assistant, личный цифровой секретарь). По-русски эти устройства называют КПК карманный персональный компьютер, карманный компьютер. В качестве примеров подобных устройств можно привести Atari Portfolio, вышедший в 1989, и Psion Organizer (1986).

Рождение палмтопа


Учитывая то, что происходило на компьютерном рынке в конце 1980-х, неудивительно то, что HP занялась разработкой PDA. В 1988 году идея переросла в научно-исследовательский проект Cheetah, а потом, в начале 1989, в формальное исследование. Изначально планировалось положить в основу этого проекта калькулятор HP-19, который открывался вертикально, как книга. Через некоторое время проект переименовали в Jaguar, были приняты решения о предварительных спецификациях устройства, в качестве корпуса было решено использовать раскладушку. Но проект не получил одобрения от менеджеров компании и, в августе 1989 года, был приостановлен. Правда, ненадолго.

В дело снова вмешался счастливый случай. С компанией HP связалась Lotus Development Corporation и предложила снова поработать вместе и создать карманное устройство для Lotus 1-2-3. Потом была череда совещаний и две компании пришли к соглашению, касающемуся работы над совместным проектом. Этому проекту суждено было стать HP-95LX первым палмтопом. Команда, которая им занималась, состояла из высококлассных специалистов, началась работа над итоговым проектом устройства.

В результате получился MS-DOS-компьютер, из ROM которого можно было запустить систему Lotus 1-2-3 и полный набор PDA-инструментов. В 1991 вышел 95LX, вскоре за ним, в 1993 и 1994, вышли 100LX и 200LX. К тому времени 200LX мог похвастаться CGA-дисплеем (80x25 символов в текстовом режиме), слотом для PCMCIA-карт, последовательным и инфракрасным портами. В нём была и полноценная клавиатура с цифровым блоком. Это был настоящий наладонный компьютер, который мог выполнять функции PDA, полнофункционального компьютера, или и того, и другого. И он мог неделями работать от пары стандартных AA-батареек. Hewlett Packard снова попала в яблочко.

Чем особенно интересен HP-200LX?


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

Клавиатура


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

Расширение возможностей устройства


Наличие в устройстве слота PCMCIA означает, что к нему можно подключить множество карт расширения. Так, к палмтопам часто подключали Flash-карты, а позже Compact Flash-карты с адаптером. Это давало сотни мегабайт дополнительной памяти (что весьма немало для устройства, работающего под управлением DOS). К PCMCIA-слоту подключали телефонные модемы, FAX-карты, Ethernet-адаптеры и даже GSM-модемы. А если для кого-то и палмтоп оказывался слишком большим устройством, можно было синхронизировать PDA-данные с совсем маленьким PDA REX3 PCMCIA. Это был именно тот шароварный проект о котором я рассказывал в начале, C-код которого я писал в самолёте.



Органайзер REX3

Приложения


Помимо того, что владелец 200LX мог пользоваться практически любыми DOS-программами, вокруг 200LX сложилось обширное сообщество разработчиков, предлагавших множество самых разных программ, рассчитанных на палмтоп. Это были, например, программы для доступа в интернет, почтовые клиенты, FTP-клиенты, программы для чтения Usenet-материалов. Я часто использовал мой палмтоп при разработке программ для встраиваемых систем. У меня было несколько компиляторов C, ассемблер и даже компилятор Fortran. В моём распоряжении имелась система для переключения задач и целая куча игр. Вспомните какую-нибудь старую игру для DOS и, наверняка, окажется так, что играть в неё можно было на 200LX.

Модификации, выполняемые пользователями


Хотя 200LX был прекрасно спроектирован, сообщество пользователей устройства быстро додумалось до того, что его можно улучшать. Чаще всего речь шла о разгоне микропроцессора (в 2 раза!) и об увеличении объёма встроенной оперативной памяти. Ещё людям хотелось оснастить 200LX подсветкой экрана, несколько экземпляров даже были модифицированы в этом направлении.

Сообщество


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

Связь с внешним миром


200LX, оснащённый модемом или Ethernet-картой, мог выходить в интернет из любой точки мира. Я работал в таком режиме несколько лет, используя местные номера SprintNet или Tymnet, которые можно найти в обычной телефонной книге, подключался по telnet к моему интернет-провайдеру и получал электронные письма. Я подключал 200LX к интернету через платные телефоны, используя акустический модем, я выходил в Сеть из отелей в Азии и Европе и, конечно, из залов ожидания аэропортов.

Последователи и уход с рынка


В рассматриваемом семействе палмтопов HP существовало довольно много вариантов устройств. Это были, например, HP Omnigo, HP-1000CX, HP-700LX обычный 200LX с крэдлом для сотового телефона Nokia.


HP-700LX

Но HP, в итоге, переориентировалась на WinCE. Этот выбор мог иметь определённую популярность среди пользователей PDA, но, с точки зрения сообщества, сложившегося вокруг прежних устройств, выглядело как шаг назад. Это были модели семейства HP-300LX, и, позже семейства HP-620LX.

Говорят, что всё хорошее имеет свойство заканчиваться. И жизненный путь семейства палмтопов HP не исключение. В 1999 году HP сообщила об остановке производства всех PDA. Многие пользователи держались за свои палмтопы ещё несколько лет, но большинство, в итоге, не без сожаления, сменило их на что-то другое. Устройства от HP, хотя и добротные, имели некоторые слабые места, и были просто не рассчитаны на работу в течение 15-20 лет.

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

HP-200LX был настоящим прорывом, продуктом, который появился в мире технологий в нужное время. В 1990-х почти невозможно было даже представить себе полноценный компьютер, который можно было носить в кармане, но команда специалистов из HP и Lotus создала именно такой компьютер. И, помимо того, что это была полнофункциональная DOS-машина, в неё, вдобавок, был встроен впечатляющий набор PIM-инструментов, не говоря уже о Lotus 1-2-3, о Quicken, о слоте для PCMCIA-карт и о других полезных возможностях.

Пользовались ли вы HP-200LX

Подробнее..

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

23.01.2021 12:21:22 | Автор: admin
Какое оно программирование следующего поколения? Помочь ответить на этот вопрос могут воспоминания о том, как эволюционировали персональные компьютеры. Для того чтобы разобраться с тем, что я имею в виду, давайте ненадолго заглянем в 1970-е.



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


1975 год был революционным для персональных компьютеров. Тогда вышел Altair 8800 первый персональный компьютер, снискавший коммерческий успех. Вскоре появился Altair Basic первый язык программирования для этой машины, разработанный Биллом Гейтсом и Полом Алленом.

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

image
Работа с Altair Basic на Altair 8800 с использованием телетайпа (я называю это компьютером с призрачной машинисткой) (источник)

Компьютеры обзаводятся дисплеями


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

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

image
VisiCalc на Apple II (источник)

Появление графических интерфейсов


Первый графический пользовательский интерфейс (GUI, Graphical User Interface) был создан Xerox в 1973 году. Первым компьютером, использующим GUI, идею рабочего стола и мышь, был Xerox Alto. Этот компьютер оказал значительное влияние и на компьютер Apple Macintosh, и на ОС Windows, которые вышли, соответственно, в 1984 и в 1985 годах.

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

Текстовое программирование


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

Достаточно странно то, что программирование компьютеров до сих пор осуществляется именно так. В этом мы недалеко ушли от первого Altair 8800 и от телетайпа. Мы вводим команды в консоли и передаём структурированные текстовые инструкции компиляторам или интерпретаторам.

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

image
Что это? Программирование в 2021 или в 1981? Точно и не скажешь

Сильные стороны визуализации


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

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

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


Визуальные средства для создания запросов в Amplitude

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


Визуальная разработка пользовательского интерфейса с помощью Builder.io

Проблемы визуализации


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

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

Если снова вернуться в прошлое, то большинство современных no-code-инструментов можно сравнить с аркадными автоматами. Такие автоматы создавались для решения одной узкоспециализированной задачи. В них было всё то, что в революционном Macintosh казалось прямо-таки каким-то волшебством. У них, например, был дисплей, работать с ними было очень легко, даже ребёнок мог без проблем пользоваться ими. А это очень похоже на нынешнее поколение no-code-инструментов.

image
Аркадный автомат

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

Как решить эти проблемы?


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

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

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

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

Где сейчас используются визуальные инструменты программирования?


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

Инструменты разработчика, возможности которых расширены за счёт визуализации


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

image
Игра Призрак Цусимы

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

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


Визуальные анимации и визуальное программирование в Unreal Engine 4

Ещё один хороший пример из этой сферы это свежий SwiftUI в Xcode.

image
SwiftUI в Xcode

No-code-инструменты


В сферах традиционной разработки и веб-разработки наблюдается стремительное развитие no-code-инструментов. Платформы вроде Airtable, Zapier, Builder и Retool демонстрируют нам удобства средств визуальной разработки, подключённые к существующему коду, к существующим данным и API.

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


Разработка интерфейса в Builder.io, основанная на React-компонентах. Редактирование базы данных в Airtable

Куда мы идём?


Полагаю, связь между традиционными системами программирования и no-code-средами будет, со временем, становиться всё сильнее и сильнее. Мы находимся в самом начале эры визуального программирования, сегодняшний день можно сравнить с тем временем, когда появился Apple II. У нас имеется наш вариант Macintosh (простые и мощные средства визуальной разработки) и iPhone (то, чем с лёгкостью может воспользоваться кто угодно).

Вот некоторые из многих проектов, которые сейчас выглядят особенно интересными: Storybook, JSX Lite, Blockly и Build.


Визуальные средства для работы с UI-компонентами в Storybook. Создание компонентов с помощью визуальных инструментов в JSX Lite


Разработка программ путём компоновки блоков в Blockly. Визуальное JavaScript-программирование в Build

Как вы относитесь к no-code-инструментам?

Подробнее..

Перевод Умные часы на Arduino, поддерживающие Bluetooth

25.01.2021 12:19:59 | Автор: admin
Автор статьи, перевод которой мы сегодня публикуем, рассказывает о том, как собрать умные часы, основанные на Arduino и поддерживающие связь с мобильным телефоном по Bluetooth. Часы оснащены аккумулятором. От одной зарядки они работают около шести часов.


Умные часы

Компоненты


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

Аппаратные компоненты:

  • Плата Arduino Nano R3.
  • Модуль OLED-дисплея: ElectroPeak 0.96 OLED 64x128 Display Module.
  • Bluetooth-модуль: HC-05 Bluetooth Module.
  • Вибромотор на 5В.
  • Кнопка SparkFun Pushbutton switch 12mm.
  • Переключатель 636NH/2.
  • Зарядный модуль.
  • Перезаряжаемая батарея на 3,7 В.

Программы:

  • Arduino IDE.
  • MIT App Inventor 2.

Инструменты и расходные материалы

  • Паяльник.
  • Бессвинцовый припой.
  • Клеевой пистолет.

Обзор проекта


Я сделал Arduino-часы с поддержкой Bluetooth, которым посвящён этот материал, после того, как проработал много материалов, посвящённых подобным проектам. Я оснастил мои часы некоторыми дополнительными возможностями. Среди них работа с заметками, калькулятор, и возможности, основанные на взаимодействии часов с телефоном: получение SMS, сведений о звонках, поиск телефона. Часы оснащены аккумуляторной литий-полимерной батареей (3,7 В, 500 мАч), которую можно перезаряжать. Зарядка длится около 30 минут, её хватает примерно на 6 часов. Это я считаю максимальным временем работы от одной зарядки. Часы запрограммированы так, чтобы их дисплей выключался бы через 15 секунд бездействия.


Демонстрация работы часов

Сборка проекта


Шаг 1: подготовка кода для Arduino


Сначала нужно открыть код проекта в Arduino IDE и проверить его работоспособность на Arduino Nano. Код приведён ниже. Вы можете модифицировать его так, как вам нужно.

Исходный код
#include <SoftReset.h>#include "U8glib.h"#include "SoftwareSerial.h"#include <MemoryFree.h>#include <avr/sleep.h>#define nextButton 7   //Номера пинов для кнопок#define previousButton 5#define menuButton 4#define mot 9#define buzz A3SoftwareSerial bluetooth(2,3);  //Для работы с Bluetooth используется библиотека SoftwareSerialconst unsigned char PROGMEM ICON_BITMAP_1[] ={   //Логотип Android0x00,0x00,0x00,0x00,0x04,0x40,0x07,0xc0,0x0f,0xe0,0x0b,0xa0,0x1f,0xf0,0x00,0x00,0x5f,0xf4,0x5f,0xf4,0x5f,0xf4,0x5f,0xf4,0x1f,0xf0,0x0c,0x60,0x0c,0x60,0x00,0x00};const unsigned char PROGMEM ICON_BITMAP_20[] ={  //Чат0x00,0x00,0x00,0x00,0x3f,0xf8,0x40,0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x49,0x24,0x40,0x04,0x40,0x04,0x40,0x04,0x3f,0xc8,0x00,0x28,0x00,0x18,0x00,0x08,0x00,0x00};const unsigned char PROGMEM ICON_BITMAP_65[] ={  //Полоса сигнала0x00,0x00,0x00,0x00,0x3f,0xe0,0x10,0x40,0x08,0x80,0x05,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x00,0x0c,0x00,0x6c,0x03,0x6c,0x1b,0x6c,0x1b,0x6c,0x00,0x00,0x00,0x00};int i,c=0,t=0;boolean clockUp = true;boolean menuShow = false;boolean selectButtonbool = false;boolean newMessage = false;boolean newCall = false;boolean newCallR = false;boolean newNote = false;boolean newBuzz = false;byte hours = 0;byte minutes = 0;byte seconds = 0;byte day = 0;byte date = 0;byte month = 0;int year = 0;int k,j,n1,n2,n3,n4,n5,n6,n7,n8,N1,N2,N3,N4,N5,N6,N7,N8 = 0;float x1,x2,ans = 0.0;char DateBuffer[30];char* TimeStorage [6];char* vout;String monthw;String number;String numberR;String NoteS;String message;   //Переменная для SMS-сообщенийString blReceived; //Хранилище для строк, полученных по bluetoothchar msg[150];   //Символьный массив для SMSchar numb[15];char numbR[15];char note[300];String op[4]={"+","-","*","/"};byte menuSelection = 0;  //Менюbyte menuSelections = 0; //Настройкиbyte menuSelectiona = 0; //Приложенияbyte len;     //Длина сообщенияbyte len1;byte len2;byte l;static unsigned long lastTick = 0;//U8GLIB_SSD1306_ADAFRUIT_128X64 u8g(U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST);//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST); // I2C / TWIU8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST);//U8GLIB_SH1106_128X64_2X u8g(13, 11, 10, 9,8); // SW SPI Com: SCK = 13, MOSI = 11, CS = 10, A0 = 9, RST = 8void setup(void) {//Если нужно - перевернуть экран//u8g.setRot180();pinMode(nextButton,INPUT); //Установка цифровых пинов в виде входовpinMode(previousButton,INPUT);pinMode(menuButton,INPUT);pinMode(mot,OUTPUT);pinMode(buzz,OUTPUT);Serial.begin(9600);bluetooth.begin(9600);digitalWrite(nextButton,HIGH); //Включение внутренней подтяжки для всех кнопокdigitalWrite(previousButton,HIGH);digitalWrite(menuButton,HIGH);digitalWrite(mot,HIGH);delay(500);digitalWrite(mot,LOW);drawStartUp();delay(4000);}//Конец блока настроекvoid loop() {int k,j,n1,n2,n3,n4,n5,n6,n7,n8,N1,N2,N3,N4,N5,N6,N7,N8 = 0;float x1,x2,ans = 0.0;t++;if(t>160){sleep();}if(digitalRead(menuButton)==LOW)t=0;if(bluetooth.available() > 0 /*&& bluetooth.find("(")*/){blReceived = bluetooth.readString();if(blReceived.startsWith("1"))  //1 - это начальная часть сообщения{digitalWrite(mot,HIGH);delay(1000);digitalWrite(mot,LOW);seconds++;blReceived.toCharArray(DateBuffer,blReceived.length()+1);vout = strtok(DateBuffer,",");for(int i=0;i<=6;i++){//if(TimeStorage[i] = NULL)//{Serial.println("Exited Loop");// break;}TimeStorage[i] = strtok(NULL,",");delay(10);}//int example = bluetooth.parseInt();day = atoi(TimeStorage[0] - 1);date = atoi(TimeStorage[1]);month = atoi(TimeStorage[2]);year = atoi(TimeStorage[3]);hours = atoi(TimeStorage[4]);minutes = atoi(TimeStorage[5]);seconds = atoi(TimeStorage[6]);blReceived="";t=0;}else if (blReceived.startsWith("2")){digitalWrite(mot,HIGH);delay(1000);digitalWrite(mot,LOW);seconds++;newMessage = true;message = blReceived;//sms();//delay(300000);blReceived="";t=0;}else if (blReceived.startsWith("3")){digitalWrite(mot,HIGH);delay(1000);digitalWrite(mot,LOW);seconds++;newCall = true;number = blReceived;//num();//delay(300000);blReceived="";t=0;}else if (blReceived.startsWith("4")){digitalWrite(mot,HIGH);delay(1000);digitalWrite(mot,LOW);newCallR = true;numberR = blReceived;t=0;u8g.firstPage();do{u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0,20);u8g.print("Calling...");u8g.setPrintPos(0,40);u8g.print(numberR);len2 = numberR.length();number.toCharArray(numbR, len2+1);//Serial.println(len2);if(len2<15){//Serial.println("If");for (int i = 0;(i-1)<len2;i++){//Serial.println("Enter for loop");u8g.setPrintPos(i*8,30);u8g.print(numbR[i-15]);delay(10);if(numbR[i-16] == '\0'){//Serial.println("Break!!");break;//delay(3000);}}}}while (u8g.nextPage());blReceived="";delay(15000);seconds=seconds+15;newCallR = false;}else if (blReceived.startsWith("5")){digitalWrite(mot,HIGH);delay(1000);digitalWrite(mot,LOW);seconds++;newNote = true;NoteS = blReceived;//notes();//delay(300000);blReceived="";t=0;}else if (blReceived.startsWith("6")){newBuzz = true;Buzz();blReceived="";t=0;}/*Serial.println(day-1);Serial.println(date);Serial.println(month);Serial.println(year);Serial.println(hours);Serial.println(minutes);Serial.println(seconds);*/}   //Конец if для datetime/*Serial.println(day-1);Serial.println(date);Serial.println(month);Serial.println(year);Serial.println(hours);Serial.println(minutes);Serial.println(seconds);*//*else if(bluetooth.available() > 0 && bluetooth.find("CMD")==true){Serial.println("ENTERED MESSAGE");message = bluetooth.readString();Serial.println(message);//delay(300000);}*/if(digitalRead(previousButton)==LOW && digitalRead(nextButton)==LOW) //Кнопка активирована{seconds=seconds+4;if(menuShow == false)  //Переключение состояний меню{menuShow = true;delay(100);menu(); //Возврат номера менюif(menuSelection == 4){seconds=seconds+4;delay(100);menuSelection = 0;while(digitalRead(menuButton)== HIGH){HA();delay(10);}}if(menuSelection == 3){seconds=seconds+4;settings();if(menuSelections == 2){seconds=seconds+4;delay(100);menuSelections=0;delay(100);while(digitalRead(menuButton)== HIGH){notice();}delay(100);}if(menuSelections == 3){seconds=seconds+4;delay(100);menuSelections=0;while(digitalRead(menuButton)==HIGH){torch();delay(10);}}if(menuSelections == 4){seconds=seconds+4;delay(100);menuSelections=0;while(digitalRead(menuButton)==HIGH){bluetooth.write("1");delay(1000);bluetooth.write("");}}bluetooth.write("");bluetooth.write("2");bluetooth.write("");}if(menuSelection == 2){seconds=seconds+4;apps();if(menuSelectiona == 2){seconds=seconds+4;delay(100);menuSelectiona = 0;while(digitalRead(menuButton)==HIGH){Calc();}bluetooth.write("");}if(menuSelectiona == 3){seconds=seconds+4;delay(100);menuSelectiona = 0;while(digitalRead(menuButton)== HIGH){sms();}delay(100);}if(menuSelectiona == 4){seconds=seconds+4;delay(100);menuSelectiona = 0;while(digitalRead(menuButton)== HIGH){call();}delay(100);}}}if(menuShow == true){menuShow = false;delay(100);}}//Конец ifadvanceTime();delay(30);}//Конец циклаvoid sleep(){u8g.firstPage();do{}while(u8g.nextPage());}void advanceTime(){if(millis()-lastTick>950) //где-то{lastTick = millis();seconds++;}if (seconds > 59){minutes++;seconds = 0;}if (minutes >59){hours++;minutes = 0;}if (hours > 23){hours = 0;minutes = 0;date+=1;}draw();}//*****------------------U8Glib Functions------------------*****void HA(){u8g.firstPage();do {bluetooth.write("3");u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0,10);u8g.print("Home Automation");u8g.setFont(u8g_font_unifont);u8g.setPrintPos(30,10);u8g.print("In Development");bluetooth.write("");} while( u8g.nextPage() );}void Buzz(){while(c<5){for(i=1;i<256;i++){analogWrite(buzz,i);delay(3.92);}for(i=256;i>0;i--){analogWrite(buzz,i);delay(3.92);}c++;}c=0;newBuzz=false;}void drawStartUp(){u8g.firstPage();do{//u8g_prepare();u8g.setColorIndex(0);u8g.drawBox(0,0,127,63);u8g.setColorIndex(1);u8g.setFont(u8g_font_7x14r);u8g.setPrintPos(5,10);u8g.print("Developed By:-");u8g.setFont(u8g_font_fur17r);u8g.setPrintPos(10,38);u8g.print("USER");u8g.setFont(u8g_font_7x14r);u8g.setPrintPos(5,62);u8g.print("V402");}while(u8g.nextPage());} //Конец drawStartUpvoid menu(){u8g.setColorIndex(1);//u8g.drawCircle(20,20,14);menuSelection = 1;while(digitalRead(menuButton) == HIGH){if(digitalRead(nextButton) == LOW){delay(20);menuSelection++;delay(50);}if(digitalRead(previousButton) == LOW){delay(20);menuSelection--;delay(50);}if(menuSelection > 4){menuSelection = 1;}if(menuSelection < 1){menuSelection = 4;}//Вывод изображенияdrawMenu();delay(50); //Для устранения "дрожания"//Вывод меню}//Конец цикла while}//Конец блока работы с менюvoid drawMenu(){u8g.firstPage();do{u8g.setFont(u8g_font_6x10r);u8g.setPrintPos(30,8);u8g.print("--- Menu ---");//u8g.setPrintPos(0,10);//u8g.print(menuSelection);u8g.drawFrame(0,(menuSelection*13)-3,128,13); //Высота блока - 13 пикселейu8g.drawLine(0,10,128,10);//Вывод менюu8g.drawStr(3,20,"Return");u8g.drawStr(3,20+13,"Apps");u8g.drawStr(3,20+13+13,"Settings");u8g.drawStr(3,20+13+13+13,"Home Auto(Beta)");//u8g.drawStr(3,20+13+13+13,"More");}while(u8g.nextPage());}void apps(){u8g.setColorIndex(1);//u8g.drawCircle(20,20,14);menuSelectiona = 1;delay(200);while(digitalRead(menuButton) == HIGH){if(digitalRead(nextButton) == LOW){delay(20);menuSelectiona++;Serial.println("Up");delay(50);}if(digitalRead(previousButton) == LOW){delay(20);menuSelectiona--;delay(50);}if(menuSelectiona > 4){menuSelectiona = 1;}if(menuSelectiona < 1){menuSelectiona = 4;}//Вывод изображенияdrawApps();delay(50); //Устранение "дрожания"}//Конец цикла while}void notice(){u8g.firstPage();do{u8g.setFont(u8g_font_6x10r);//u8g.print(NoteS);l = NoteS.length();NoteS.toCharArray(note, l+1);//Serial.println(len);if(l>0){for(i=0;i<20;i++){u8g.setPrintPos(i*6,8);u8g.print(note[i]);delay(10);}}if(l>20){for(i=20;i<40;i++){u8g.setPrintPos((i-20)*6,17);u8g.print(note[i]);delay(10);}}if(l>40){for(i=40;i<60;i++){u8g.setPrintPos((i-40)*6,26);u8g.print(note[i]);delay(10);}}if(l>60){for(i=60;i<80;i++){u8g.setPrintPos((i-60)*6,35);u8g.print(note[i]);delay(10);}}if(l>80){for(i=80;i<100;i++){u8g.setPrintPos((i-80)*6,43);u8g.print(note[i]);delay(10);}}if(l>100){for(i=100;i<120;i++){u8g.setPrintPos((i-100)*6,51);u8g.print(note[i]);delay(10);}}if(l>120){for(i=120;i<140;i++){u8g.setPrintPos((i-120)*6,59);u8g.print(note[i]);delay(10);}}}while (u8g.nextPage());delay(30);newNote = false;}void torch(){u8g.firstPage();do{u8g.drawBox(0,0,127,63);}while(u8g.nextPage());}void drawApps(){u8g.firstPage();do{u8g.setFont(u8g_font_6x10r);u8g.setPrintPos(5,8);u8g.print("--- Applications ---");//u8g.setPrintPos(0,10);//u8g.print(menuSelection);u8g.drawFrame(0,(menuSelectiona*13)-3,128,13); //Высота блока - 13 пикселейu8g.drawLine(0,10,128,10);//Вывод менюu8g.drawStr(3,20,"Return");u8g.drawStr(3,20+13,"CALC");u8g.drawStr(3,20+13+13,"SMS");u8g.drawStr(3,20+13+13+13,"CALLS");//u8g.drawStr(3,20+13+13+13,"More");}while(u8g.nextPage());}void settings(){u8g.setColorIndex(1);//u8g.drawCircle(20,20,14);menuSelections = 1;delay(200);while(digitalRead(menuButton) == HIGH){if(digitalRead(nextButton) == LOW){delay(20);menuSelections++;Serial.println("Up");delay(50);}if(digitalRead(previousButton) == LOW){delay(20);menuSelections--;delay(50);}if(menuSelections > 4){menuSelections = 1;}if(menuSelections < 1){menuSelections = 4;}//Вывод изображенияdrawSettings();delay(50); //Устранение "дрожания"}//Конец цикла while}void sms(){u8g.firstPage();do{u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0,10);u8g.print(message);len = message.length();message.toCharArray(msg, len+1);//Serial.println(len);if(len>16){//Serial.println("If");for (int i = 0;(i-1)<len;i++){//Serial.println("Enter for loop");u8g.setPrintPos(i*8,30);u8g.print(msg[i+16]);delay(10);if(msg[i+17] == '\0'){//Serial.println("Break!!");break;//delay(3000);}}}if(len>32){//Serial.println("If");for (int i = 0;(i-1)<len;i++){//Serial.println("Enter for loop");u8g.setPrintPos(i*8,50);u8g.print(msg[i+32]);delay(10);if(msg[i+33] == '\0'){//Serial.println("Break!!");break;//delay(3000);}}}}while (u8g.nextPage());delay(30);newMessage = false;}void call(){u8g.firstPage();do{u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0,10);u8g.print(number);len1 = number.length();number.toCharArray(numb, len1+1);//Serial.println(len1);if(len1<15){//Serial.println("If");for (int i = 0;(i-1)<len1;i++){//Serial.println("Enter for loop");u8g.setPrintPos(i*8,30);u8g.print(numb[i-15]);delay(10);if(numb[i-16] == '\0'){//Serial.println("Break!!");break;//delay(3000);}}}}while (u8g.nextPage());delay(30);newCall = false;}void drawSettings(){u8g.firstPage();do{u8g.setFont(u8g_font_6x10r);u8g.setPrintPos(15,8);u8g.print("--- Settings ---");//u8g.setPrintPos(0,10);//u8g.print(menuSelection);u8g.drawFrame(0,(menuSelections*13)-3,128,13); //Высота блока - 13 пикселейu8g.drawLine(0,10,128,10);//Вывод менюu8g.drawStr(3,20,"Return");u8g.drawStr(3,20+13,"Notes");u8g.drawStr(3,20+13+13,"Torch");u8g.drawStr(3,20+13+13+13,"Find My Phone");}while(u8g.nextPage());}void CalcPrintdata(){u8g.setPrintPos(50,25);u8g.print(n3);u8g.setPrintPos(40,25);u8g.print(n4);u8g.setPrintPos(30,25);u8g.print(n5);u8g.setPrintPos(20,25);u8g.print(n6);u8g.setPrintPos(60,25);u8g.print(".");u8g.setPrintPos(70,25);u8g.print(n7);u8g.setPrintPos(80,25);u8g.print(n8);u8g.setPrintPos(3,37);u8g.print(op[k]);u8g.setPrintPos(50,37);u8g.print(N3);u8g.setPrintPos(40,37);u8g.print(N4);u8g.setPrintPos(30,37);u8g.print(N5);u8g.setPrintPos(20,37);u8g.print(N6);u8g.setPrintPos(60,37);u8g.print(".");u8g.setPrintPos(70,37);u8g.print(N7);u8g.setPrintPos(80,37);u8g.print(N8);u8g.setPrintPos(5,46);u8g.print("---------------");u8g.setPrintPos(5,54);u8g.print(ans);}void Calc(){u8g.firstPage();do{u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0,10);u8g.print("Calculator:-");if(digitalRead(previousButton) == LOW){j++;delay(300);if(j>12)j = 0;}if(digitalRead(nextButton)==LOW && digitalRead(previousButton)==LOW){j=13;delay(300);}if(j==0){CalcPrintdata();if(digitalRead(nextButton) == LOW){n6++;delay(300);}if(n6>9)n6=0;}if(j==1){CalcPrintdata();if(digitalRead(nextButton) == LOW){n5++;delay(300);}if(n5>9)n5=0;}if(j==2){CalcPrintdata();if(digitalRead(nextButton) == LOW){n4++;delay(300);}if(n4>9)n4=0;}if(j==3){CalcPrintdata();if(digitalRead(nextButton) == LOW){n3++;delay(300);}if(n3>9)n3=0;}if(j==4){CalcPrintdata();if(digitalRead(nextButton) == LOW){n7++;delay(300);}if(n7>9)n7=0;}if(j==5){CalcPrintdata();if(digitalRead(nextButton) == LOW){n8++;delay(300);}if(n8>9)n8=0;}if(j==6){CalcPrintdata();if(digitalRead(nextButton) == LOW){k++;delay(300);}if(k>3)k=0;}if(j==7){CalcPrintdata();if(digitalRead(nextButton) == LOW){N6++;delay(300);}if(N6>9)N6=0;}if(j==8){CalcPrintdata();if(digitalRead(nextButton) == LOW){N5++;delay(300);}if(N5>9)N5=0;}if(j==9){CalcPrintdata();if(digitalRead(nextButton) == LOW){N4++;delay(300);}if(N4>9)N4=0;}if(j==10){CalcPrintdata();if(digitalRead(nextButton) == LOW){N3++;delay(300);}if(N3>9)N3=0;}if(j==11){CalcPrintdata();if(digitalRead(nextButton) == LOW){N7++;delay(300);}if(N7>9)N7=0;}if(j==12){CalcPrintdata();if(digitalRead(nextButton) == LOW){N8++;delay(300);}if(N8>9)N8=0;}if(j==13){CalcPrintdata();x1 = n6*1000+n5*100+n4*10+n3+n7*0.1+n8*0.01;x2 = N6*1000+N5*100+N4*10+N3+N7*0.1+N8*0.01;if(k==0){ans = x1 + x2;}if(k==1){ans = x1 - x2;}if(k==2){ans = x1 * x2;}if(k==3){ans = x1 / x2;}}}while(u8g.nextPage());}void draw(){u8g.firstPage();do{if(t<160){//u8g.drawLine(0,15,128,15);u8g.setFont(u8g_font_7x14r);u8g.drawStr(3,14,"V402");u8g.setFont(u8g_font_fur17r);String strDate = String("");char time[10];   //9if (hours < 10)strDate += "0";strDate += hours;strDate += ":";if (minutes < 10)strDate += "0";strDate += minutes;strDate += ":";if (seconds < 10)strDate += "0";strDate += seconds;strDate.toCharArray(time,10);   //9time[12] = 0x00;      //9u8g.drawStr(20,40,time);switch (month){case 01:monthw = "January";break;case 02:monthw = "February";break;case 03:monthw = "March";break;case 04:monthw = "April";break;case 05:monthw = "May";break;case 06:monthw = "June";break;case 07:monthw = "July";break;case 8:monthw = "August";break;case 9:monthw = "September";break;case 10:monthw = "October";break;case 11:monthw = "November";break;case 12:monthw = "December";break;}u8g.setFont(u8g_font_6x10r); //Более мелкий шрифт для даты//u8g.setFont(u8g_font_7x14r);u8g.setPrintPos(0,55);u8g.print(date);u8g.setPrintPos(15,55);u8g.print("/");/*switch (date){case 1:u8g.print("st");break;case 2:u8g.print("nd");break;case 3:u8g.print("rd");break;default:u8g.print("th");break;}*/u8g.setPrintPos(27,55);u8g.print(month); //Ранее - monthwu8g.setPrintPos(35,55);u8g.print("/");u8g.setPrintPos(43,55);u8g.print(year);//u8g.drawBitmapP(110,1,1,8,IMG_indicator_msg);u8g.drawBitmapP( 110, 1, 2, 16, ICON_BITMAP_65);if(newNote == true){u8g.drawBitmapP(88,45,2,16,ICON_BITMAP_20);}if(newMessage == true){u8g.drawBitmapP(110,45,2,16,ICON_BITMAP_1);}if(newCall == true){u8g.drawBitmapP(110,45,2,16,ICON_BITMAP_1);}}}while(u8g.nextPage());}//Конец draw

Шаг 2. Создание Android-приложения с использованием MIT App Inventor 2


Откройте сайт MIT App Inventor, создайте бесплатную учётную запись и нажмите на кнопку Create Apps!. Далее нажмите на кнопку Start new project.

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


Приложение, созданное в MIT App Inventor

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


Общий вид раздела Blocks для Screen1


Левая часть раздела Blocks для Screen1 (оригинал)


Правая часть раздела Blocks для Screen1 (оригинал)

Теперь создайте ещё пару экранов приложения и дайте им имена OnScreen и OffScreen. Ниже показаны разделы Blocks для этих двух экранов.


Раздел Blocks для OnScreen


Раздел Blocks для OffScreen

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

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

Шаг 3. Сборка аппаратных компонентов проекта



Схема проекта (оригинал)

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

  • Пины Arduino D2 и D3 подключаются к TXD и RXD Bluetooth-модуля HC-05.
  • Пины D4, D5, D7 подключаются к кнопкам Menu (Меню), Next (Следующий) и Prev (Предыдущий).
  • Пин D9 подключается к вибромотору.
  • Пины A4 и A5 подключаются к SDA и SCL OLED-диспля с разрешением 128x64.
  • + батареи подключается к B+ зарядного модуля.
  • батареи подключается к B- зарядного модуля.
  • Выход зарядного модуля + подключается к Vin Arduino (между ними надо поставить переключатель чтобы иметь возможность включать и отключать питание).
  • Линии 5V и GND подключаются к компонентам в соответствии со схемой.

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

Работа с часами


Одновременное нажатие кнопок Next и Prev позволяет открыть меню. Для выбора пункта меню используется кнопка Menu. Для переключения пунктов меню используются кнопки Next и Prev.

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

Подробнее..

Перевод Заметки о Unix системный вызов write(), на самом деле, не такой уж и атомарный

28.01.2021 12:17:06 | Автор: admin


Недавно я читал материал Эвана Джонса Устойчивое хранение данных и файловые API Linux. Я полагаю, что автор этой довольно хорошей статьи ошибается, говоря о том, чего можно ожидать от команды write() (и в том виде, в каком она описана в стандарте POSIX, и на практике). Начну с цитаты из статьи:

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

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

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

После того как произошёл успешный возврат из операции записи (write()) в обычный файл:

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

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

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

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

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

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

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

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

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

(Большинство программ не выполняют операции read() и write() над одним и тем же файлом в одно и то же время из двух потоков.)

P.S. Обратите внимание на то, что даже операции записи в конвейеры и в FIFO являются атомарными только если объём записываемых данных достаточно мал. Запись больших объёмов данных, очевидно, не должна быть атомарной (и в реальных Unix-системах она таковой обычно и не является). Если бы стандарт POSIX требовал бы атомарности при записи ограниченных объёмов данных в конвейеры, и при этом указывал бы на то, что запись любых объёмов данных в файлы так же должна быть атомарной, это выглядело бы довольно-таки необычно.

P.P.S. Я с осторожностью относился бы к принятию как данности того, что в некоем варианте Unix, в многопоточной среде, полностью реализованы атомарные операции read() и write(). Возможно, я настроен скептически, но я бы сначала это как следует проверил. Всё это похоже на излишне прихотливые требования POSIX, на которые разработчики систем закрывают глаза во имя простоты и высокой производительности своих решений.

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

Подробнее..

Перевод 9 самых популярных PHP-фреймворков

01.02.2021 12:14:39 | Автор: admin
Десятки лет PHP был самым любимым языком программирования в мире. И это не случайно. PHP-разработка ведётся быстро, получающиеся в итоге проекты отличаются высоким уровнем безопасности, их легко поддерживать. Кроме того, в ходе разработки PHP-проектов приходится писать не слишком много собственного кода благодаря тому, что существует огромное количество PHP-библиотек. В наши дни PHP используется приблизительно на 79% веб-сайтов.



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

1. Laravel



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

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

А после появления Homestead заранее подготовленного контейнера Vagrant, установка Laravel стала до крайности простой. Самая свежая на данный момент версия Laravel 8 вышла в сентябре 2020 года.

2. Yii



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

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

3. CodeIgniter



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

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

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

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

4. Symfony



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

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

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

5. CakePHP



CakePHP фреймворк, существующий с 2005 года, известен теми удобствами, которые он даёт веб-разработчикам. Он нуждается в весьма скромных настройках, не требует пользоваться XML- или YAML-файлами. У него есть собственная ORM, что выгодно отличает его от других подобных инструментов. В плане безопасности у него тоже всё хорошо, в частности, в нём имеется система защиты от CSRF-атак.

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

CakePHP был одним из самых первых PHP-фреймворков, поддерживающих MVC, ворвавшихся в мир веб-разработки. Он используется в крупных известных проектах вроде 10 Fast Fingers, Printivo, Visit NC и Coconala.

6. Zend Framework



Zend Framework это ещё один фреймворк, в основе которого лежат компоненты. Его ещё называют фреймворком промежуточного уровня. Он появился в 2006 году, сейчас самой современной является его версия 3.0.0. В нём применяется объектно-ориентированный MVC-подход, который известен тем, что помогает разработчикам заниматься своим делом, ни на что не отвлекаясь.

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

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

7. Phalcon



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

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

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

8. FuelPHP



FuelPHP это MVC-фреймворк, вышедший в 2011 году и поддерживаемый силами сообщества разработчиков. Он известен своей гибкостью. В нём реализована особая версия MVC HMVC (Hierarchical Model-View-Controller иерархическая версия архитектуры модель-вид-контроллер). HMVC, в отличие от MVC, поощряет многократное использование кода. FuelPHP, кроме того, предлагает разработчикам отличную расширяемость, модульность, хорошую систему организации кода. В результате этот фреймворк позволяет программистам экономить время и бережно расходует системные ресурсы.

FuelPHP позволяет создавать веб-проекты разных масштабов. Он отличается надёжной и хорошо продуманной системой безопасности с поддержкой Output Encoding, с защитой от CSRF- и XSS-атак.

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

9. Slim



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

Это один из лучших фреймворков для начинающих. Его очень легко освоить, он отличается дружелюбной и простой документацией. На него стоит взглянуть тем, кого интересует разработка RESTful-API. Он облегчает решение таких задач, как маршрутизация, шифрование куки-файлов, HTTP-кеширование на стороне клиента.

Фреймворк Slim предназначен для маленьких но мощных веб-приложений и API.

Что выбрать?


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

Какими PHP-фреймворками вы пользуетесь?

Подробнее..

Перевод Разбираемся с развёртыванием CodeReady Containers на Linux

21.04.2021 12:06:56 | Автор: admin
Подумываете ли вы о том, чтобы использовать Red Hat CodeReady Containers (CRC) для решения задач локальной OpenShift-разработки? Собираетесь ли устанавливать CRC на Linux? В этом материале я хочу рассказать именно об этом. Мы обсудим некоторые особенности работы CRC и поговорим о настройке контейнеров.



Тут используется система CRC версии 1.21.0, в основе которой лежит OpenShift Container Platform (OCP) версии 4.6.9. Я устанавливаю CRC на Debian 10 GNU/Linux, но нам подойдёт любой современный дистрибутив Linux вроде Fedora или Ubuntu. CRC 1.21.0 можно установить на Linux-хосте, который удовлетворяет следующим требованиям:

  • На нём установлены KVM и libvirt.
  • Его сетевые настройки выполняются с использованием NetworkManager.
  • Пользователь, устанавливающий CRC, имеет sudo-доступ к этому хосту.

Перед установкой CRC нужно будет загрузить tarball-дистрибутив CRC и так называемый pull secret. Pull secret это JSON-файл, который содержит аутентификационную информацию, необходимую для доступа к защищённым реестрам образов, поддерживаемым Red Hat. Если вы не являетесь клиентом Red Hat вы можете присоединиться к Red Hat Developer Program, к программе Red Hat для разработчиков, и бесплатно загрузить этот файл. Участие в этой программе позволяет, кроме того, загрузить tarball-дистрибутив CRC. А отсюда дистрибутив можно скачать без лишних формальностей.

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

Обзор порядка развёртывания CodeReady Containers


Если описать работу CRC в общих чертах, то окажется, что эта система создаёт виртуальную машину, на которой работает OpenShift-кластер, состоящий из одного узла. Этот узел одновременно играет и роль главного узла (master node), и роль рабочего узла (worker node). Платформа OpenShift не устанавливается при развёртывании CRC. CRC запускает OpenShift из предустановленного образа виртуальной машины. Процесс развёртывания CRC показан на следующей схеме.


Развёртывание CRC

CRC, работая на Linux-хосте, использует libvirt для создания сети, пула хранения данных и виртуальной машины CRC. Данные этой виртуальной машины хранятся на постоянной основе в томе libvirt. Это обеспечивает то, что объекты, создаваемые пользователем в OpenShift, не будут исчезать после перезагрузок CRC.

У экземпляра OpenShift имеется фиксированный набор хранилищ PersistentVolume. Пользователи могут подключать эти хранилища к подам приложений, выполняя запросы PersistentVolumeClaim (PVC). Тут поддерживаются все режимы доступа к хранилищам: ReadWriteOnce, ReadWriteMany и ReadOnlyMany. Содержимое постоянных хранилищ информации размещается на хосте, в директории /var/mnt/pv-data.

А как насчёт интегрированного реестра образов? В основе реестра образов в OpenShift лежит хранилище PersistentVolume, работа с которым организована через общедоступный маршрут default-route-openshift-image-registry.apps-crc.testing. Пользователи могут применять этот маршрут для отправки образов своих контейнеров в реестр до запуска их в OpenShift.

Последнее, на что стоит обратить внимание, анализируя вышеприведённую схему, представлено конфигурацией DNS. Зачем это нужно? CRC настраивает разрешение DNS-имён на Linux-хосте таким образом, чтобы запросы к конечным точкам api.crc.testing и *.apps-crc.testing перенаправлялись бы к экземпляру OpenShift. Программа NetworkManager, нужная для работы CRC, используется для выполнения таких настроек DNS. CRC просит NetworkManager развернуть экземпляр dnsmasq, который перенаправляет DNS-запросы для конечных точек OpenShift другому экземпляру dnsmasq, развёрнутому внутри виртуальной машины. А разрешением имён занимается именно этот второй экземпляр dnsmasq.

Настройка CodeReady Containers


В этом разделе мы поговорим о некоторых конфигурационных параметрах CRC, которые вам может понадобиться подстроить под свои нужды. По умолчанию виртуальной машине CRC даётся 4 vCPU, 8790 МиБ RAM и 31 Гиб дискового пространства. Эти значения, что зависит от конкретной ситуации, может понадобиться увеличить. Я, на своём настольном компьютере, предпочитаю увеличивать количество vCPU до 10. Сделать это можно с помощью следующей команды:

./crc config set cpus 10

Стандартный размер оперативной памяти в 8790 МиБ кажется мне достаточно скромным. Большая часть этой памяти используется компонентами OpenShift. В результате из 8790 МиБ памяти приложениям пользователя доступно лишь что-то наподобие 2 Гиб. На моём настольном компьютере достаточно много RAM. Обычно я повышаю размер памяти, доступной CRC, примерно до 46 ГиБ:

./crc config set memory 47104

Если говорить о дисковом пространстве, то, как уже было сказано, по умолчанию виртуальной машине CRC выделяется 31 ГиБ. Примерно половина этой ёмкости нам не доступна, так как она используется операционной системой Red Hat CoreOS и компонентами OpenShift. Около 16 ГиБ остаётся на нужды интегрированного реестра образов и других хранилищ информации (PVC), создаваемых для приложений. И, хотя стандартный размер дискового пространства в 31 ГиБ это вполне прилично, я предпочитаю увеличивать этот показатель до 120 ГиБ, делая это лишь для того чтобы привести его в соответствие со стандартным дисковым пространством кластеров OCP. Обратите внимание на то, что CRC позволяет увеличивать размер дискового пространства не только при первоначальной настройке системы, но и в любое время после её установки. Для настройки размеров дискового пространства, доступного CRC, можно воспользоваться такой командой:

./crc config set disk-size 120

CRC необходим файл pull secret OpenShift. Я обычно указываю CRC путь к соответствующему файлу, который храню в защищённом месте домашней директории. Этот файл необходим OpenShift вне зависимости от того, как именно была произведена установка OpenShift. Тот же pull secret, который использован для CRC, может быть применён для установки OpenShift с использованием установщика OpenShift. После того, как я загрузил нужный файл с сайта Red Hat и сохранил его по адресу ~/.mysecrets/ocp/pull-secret.json, мне нужно сообщить о его местоположении CRC:

./crc config set pull-secret-file ~/.mysecrets/ocp/pull-secret.json

И последний параметр, о котором мне хочется рассказать, это consent-telemetry. Если заблаговременно его настроить, можно избежать вопроса Would you like to contribute anonymous usage statistics [y/N], который будет задавать CRC в процессе работы. Вот команда для настройки этого параметра:

./crc config set consent-telemetry yes

CRC сохраняет вышеописанные настройки в файле ~/.crc/crc.json. Если хотите можете просто отредактировать этот файл. Полный список доступных конфигурационных параметров CRC можно узнать, выполнив следующую команду:

./crc config --help

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

Установка CodeReady Containers


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

./crc setup

А пока всё устанавливается поговорим о бинарном файле CRC. Обратили ли вы внимание на то, что этот файл довольно-таки велик? В частности, его размер для CRC 1.21.0 составляет примерно 2,6 ГиБ. Почему он такой большой? Для того чтобы ответить на этот вопрос давайте исследуем его структуру, схематическое представление которой представлено ниже.


Структура бинарного файла CRC

Бинарник CRC состоит из четырёх частей. Первая часть это исполняемый код CRC. Вторая это утилита admin-helper-linux, которая используется для обновления файла /etc/hosts. Третья это исполняемый код демона crc-driver-libvirt, который реализует специфические функции виртуализации libvirt и абстрагирует детали виртуализации от ядра CRC. Четвёртая часть это так называемый CRC-бандл (на схеме это crc_libvirt_4.6.9.crcbundle). Этот бандл содержит образ виртуальной машины и именно на его долю приходится большая часть размера бинарника CRC.

Теперь вернёмся к установке CRC. На начальной стадии работы команда ./crc setup извлекает все компоненты, прикреплённые к исполняемому коду CRC, и размещает их в директории ~/.crc. Встроенный в бинарник CRC-бандл представлен архивом tar.xz, который тут же распаковывается в папку ~/.crc/cache. В бандле содержатся следующие материалы:

  • Файл crc-bundle-info.json несёт в себе метаданные бандла. CRC обращается к этому файлу в процессе развёртывания системы.
  • Образ виртуальной машины crc.qcow2 содержит предустановленный узел OpenShift. Этот образ будет использован в качестве базы для образа диска виртуальной машины CRC.
  • Временный ключ id_ecdsa_crc используется CRC для подключения к виртуальной машине по SSH на начальном этапе работы, сразу после её первого запуска. После подключения к виртуальной машине CRC генерирует новую уникальную пару SSH-ключей и добавляет их в файл машины ~core/.ssh/authorized_keys. Временный SSH-ключ удаляется из этого файла, а значит его больше нельзя будет использовать для подключения к соответствующей виртуальной машине.
  • В файле kubeadmin-password хранится пароль пользователя kubeadmin в OpenShift.
  • Файл kubeconfig позволяет входить в OpenShift в виде пользователя kube:admin. Этот файл включает в себя приватный ключ, необходимый для успешного прохождения процедуры аутентификации.
  • Исполняемый файл oc это клиент oc, версия которого соответствует версии OpenShift-кластера, входящего в состав бандла.

После того, как процедура извлечения компонентов CRC завершится, директория ~/.crc будет выглядеть так:

tree --noreport .crc.crc bin   admin-helper-linux   crc-driver-libvirt   oc     oc -> /home/anosek/.crc/cache/crc_libvirt_4.6.9/oc cache   crc_libvirt_4.6.9     crc-bundle-info.json     crc.qcow2     id_ecdsa_crc     kubeadmin-password     kubeconfig     oc   crc_libvirt_4.6.9.crcbundle crc.json

Следующий достойный внимания шаг, выполняемый в процессе установки CRC, представляет собой настройку DNS на Linux-хосте. CRC настраивает DNS так, чтобы запросы на подключение к конечным точкам api.crc.testing и *.apps-crc.testing перенаправлялись бы к экземпляру OpenShift. Заранее известно то, что данный экземпляр OpenShift собирается открыть свои конечные точки на жёстко заданном IP-адресе 192.168.130.11. Как же CRC добивается правильного разрешения DNS-имён для конечных точек OpenShift? CRC создаёт для этого файл /etc/NetworkManager/conf.d/crc-nm-dnsmasq.conf, содержащий следующую настройку:

[main]dns=dnsmasq

Эта настройка сообщает NetworkManager о том, что, во-первых, надо запустить экземпляр dnsmasq, а во-вторых что надо модифицировать файл /etc/resolv.conf на компьютере так, чтобы этот экземпляр dnsmasq рассматривался бы как DNS-сервер, используемый по умолчанию. На следующем шаге CRC настраивает сервер dnsmasq, создавая конфигурационный файл /etc/NetworkManager/dnsmasq.d/crc.conf с таким содержимым:

server=/apps-crc.testing/192.168.130.11server=/crc.testing/192.168.130.11

Благодаря этому DNS-запросы для доменов crc.testing и apps-crc.testing, а так же для всех их поддоменов, перенаправляются к DNS-серверу 192.168.130.11. Этот DNS-сервер будет развёрнут внутри виртуальной машины CRC и будет отвечать за разрешение имён конечных точек OpenShift.

Обратите внимание на то, что вышеописанный перенаправляющий DNS-сервер создаётся только в том случае, если хост не использует для разрешения DNS-имён systemd-resolved. Если ваш хост использует systemd-resolved, это значит, что CRC настроит перенаправление с его помощью, не прибегая к запуску дополнительного перенаправляющего сервера dnsmasq.

На последнем этапе работы команды ./crc setup выполняется создание сети libvirt. Что для этого нужно? CRC создаёт libvirt-сеть с именем crc типа NAT. Единственный хост в этой сети получит IP-адрес 192.168.130.11:

virsh net-dumpxml crc<network connections='1'><name>crc</name><uuid>49eee855-d342-46c3-9ed3-b8d1758814cd</uuid><forward mode='nat'><nat><port start='1024' end='65535'/></nat></forward><bridge name='crc' stp='on' delay='0'/><mac address='52:54:00:fd:be:d0'/><ip family='ipv4' address='192.168.130.1' prefix='24'><dhcp><host mac='52:fd:fc:07:21:82' ip='192.168.130.11'/></dhcp></ip></network>

В этой сети будет находиться виртуальная машина CRC. Этой виртуальной машине будет назначен MAC-адрес 52:fd:fc:07:21:82. Благодаря вышеприведённым настройкам этой машине назначается фиксированный IP-адрес 192.168.130.11. Оба эти адреса жёстко заданы в CRC.

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

Запуск CodeReady Containers


После того, как завершится работа команды ./crc setup, выполним следующую команду, которая позволяет создать и запустить виртуальную машину CRC:

./crc start

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

Сначала CRC запускает серию предварительных проверок, которые уже выполнялись в ходе работы команды ./crc setup. Они нужны для того чтобы удостовериться в том, что то, что имеет отношение к CRC, всё ещё находится в хорошем состоянии.

Далее CRC создаёт libvirt-пул хранения данных, называемый crc и располагающийся в директории ~/.crc/machines/crc. В этом пуле создаётся образ диска для виртуальной машины crc.qcow2 размером 120 ГиБ. Мы выбрали именно этот размер, настроив параметр disk-size в одном из предыдущих разделов. Это так называемый thin-provisioned-образ. Он не сразу занимает всё выделенное ему пространство, его физический размер увеличивается по мере необходимости, при записи в него данных. CRC, кроме того, поддерживает изменение размера этого образа. Если пользователь решит изменить параметр disk-size CRC изменит образ перед запуском виртуальной машины. Описание пула хранения данных libvirt может выглядеть так, как показано ниже:

virsh pool-dumpxml crc<pool type='dir'>  <name>crc</name>  <uuid>ecfe5181-6476-43a8-9ff9-a133841df011</uuid>  <capacity unit='bytes'>1769532428288</capacity>  <allocation unit='bytes'>1119201026048</allocation>  <available unit='bytes'>650331402240</available>  <sоurce>  </sоurce>  <target>    <path>/home/anosek/.crc/machines/crc</path>    <permissions>      <mode>0755</mode>      <owner>1000</owner>      <group>1000</group>    </permissions>  </target></pool>

После того, как образ диска для виртуальной машины готов, CRC создаёт саму виртуальную машину и запускает её. Эта виртуальная машина носит имя crc, посмотреть её описание можно так:

virsh dumpxml crc<domain type='kvm' id='2'><name>crc</name><uuid>4269a53d-0fa7-465e-9190-ab33047244ee</uuid><memory unit='KiB'>46000128</memory><currentMemory unit='KiB'>46000128</currentMemory><vcpu placement='static'>10</vcpu><resource><partition>/machine</partition></resource>...<devices><disk type='file' device='disk'><driver name='qemu' type='qcow2' io='threads'/><source file='/home/anosek/.crc/machines/crc/crc.qcow2' index='1'/><backingStore type='file' index='2'><format type='qcow2'/><source file='/home/anosek/.crc/cache/crc_libvirt_4.6.9/crc.qcow2'/><backingStore/></backingStore>...</domain>

Количество vCPU и размер оперативной памяти виртуальной машины соответствуют ранее настроенным параметрам cpus и memory. Из кода описания виртуальной машины можно узнать о том, что машина базируется на образе ~/.crc/machines/crc/crc.qcow2. А этот образ, на самом деле, является лишь образом-наложением (overlay image), в основе которого лежит базовый образ ~/.crc/cache/crc_libvirt_4.6.9/crc.qcow2. В начале работы образ-наложение пуст. В него попадает то, что пишет на диск виртуальная машина CRC. Все изменения, внесённые в данные виртуальной машиной, на постоянной основе хранятся в образе-наложении, они не теряются после перезапуска машины. Это значит, что конфигурация OpenShift никуда не исчезнет в промежутке между выполнением команд ./crc stop и ./crc start.

Далее, CRC создаёт новую пару SSH-ключей, которая будет использоваться для подключения к виртуальной машине по SSH с учётной записью пользователя core. Публичный ключ из этой пары добавляется в файл ~core/.ssh/authorized_keys виртуальной машины, что и позволяет пользователю core к ней подключаться. CRC использует SSH для выполнения дальнейших настроек виртуальной машины в процессе её запуска.

Если пользователь поменял параметр disk-size, это значит, что файловая система нуждается в расширении, выполняемом для того, чтобы она охватывала бы всё доступное дисковое пространство. Это достигается путём выполнения команды xfs_growfs / внутри виртуальной машины. Как уже было сказано, по умолчанию в CRC в качестве размера диска установлен 31 ГиБ. В этот момент файловая система расширяется до 120 ГиБ, до того размера, который мы задали в ходе настройки CRC.

Теперь CRC запускает внутри виртуальной машины DNS-сервер podman-контейнер, в котором работает dnsmasq. Этот DNS-сервер разрешает доменные имена *.apps-crc.testing, api.crc.testing и api-int.crc.testing в IP-адрес 192.168.130.11, который является адресом самой виртуальной машины. Эти доменные имена назначены широко известным конечным точкам OpenShift, то есть, соответственно входному маршрутизатору, серверу API и точке доступа к внутреннему API. Этот DNS-сервер используется и виртуальной машиной, и Linux-хостом. Запросы хоста доходят до этого сервера благодаря перенаправляющему DNS-серверу, о котором мы говорили выше.

В процессе запуска виртуальной машины CRC добавляет в /etc/hosts Linux-хоста следующую строчку:

192.168.130.11 api.crc.testing console-openshift-console.apps-crc.testing default-route-openshift-image-registry.apps-crc.testing oauth-openshift.apps-crc.testing

Я не знаю точно для чего это делается. Разрешение вышеупомянутых имён уже выполняется dnsmasq-сервером, который работает внутри виртуальной машины CRC. Но полагаю, что, в любом случае, полезно знать о том, что CRC пишет эти данные в /etc/hosts.

На следующем шаге запуска виртуальной машины CRC запускает службу kubelet, которая, в свою очередь, запускает службы OpenShift. Если сертификат клиента kubelet истёк в то время, пока виртуальная машина CRC была выключена, kubelet выполнит запрос на подпись сертификата (Certificate Signing Request, CSR) для получения нового сертификата. CRC выполняет проверку на наличие новых CSR и автоматически их одобряет.

На последнем шаге запуска CRC добавляет файл pull secret пользователя в кластер OpenShift.

Конфигурационные данные, имеющие отношение к конкретному экземпляру виртуальной машины CRC, хранятся в директории .crc/machines/crc. Взглянуть на них можно так:

tree --noreport .crc/machines.crc/machines crc config.json crc.qcow2 id_ecdsa id_ecdsa.pub kubeconfig

Если сейчас вам удалось запустить собственную виртуальную машину CRC примите поздравления!

Полезные команды CodeReady Containers


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

Для того чтобы открыть в браузере, используемом по умолчанию, OpenShift Web Console воспользуйтесь такой командой:

./crc console

Так можно вывести сведения об учётных данных пользователя OpenShift:

./crc console --credentialsTo login as a regular user, run 'oc login -u developer -p developer https://api.crc.testing:6443'.To login as an admin, run 'oc login -u kubeadmin -p HqC3I-wgtiB-q7qCf-KEsuK https://api.crc.testing:6443

К виртуальной машине CRC можно подключиться по SSH в виде пользователя core:

ssh -i ~/.crc/machines/crc/id_ecdsa core@api.crc.testing

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

Итоги


В этом материале раскрыты вопросы развёртывания CodeReady Containers на Linux-хосте. Мы начали с рассмотрения требований к системе, на которой планируется запускать CRC. В разделе, посвящённом обзору установки CRC, мы поговорили о том, как CRC взаимодействует с libvirt для обеспечения работы виртуальной машины OpenShift. Мы, кроме того, обсудили особенности настроек DNS, выполняемых CRC. Перед тем, как переходить к запуску системы, мы рассмотрели особенности настройки CRC и предоставили в распоряжение виртуальной машины дополнительные ресурсы в размерах, превышающих те, которые используются по умолчанию. Мы подробно разобрали установку и запуск CRC. И, наконец, я рассказал о нескольких командах, которые кажутся мне полезными.

Вот моё видео, посвящённое развёртыванию CRC на Linux.

Надеюсь, вам было интересно читать мой рассказ о CodeReady Containers.

Пользуетесь ли вы Red Hat CodeReady Containers?

Подробнее..

Перевод Запуск тестов Selenium в Jenkins

01.05.2021 16:18:08 | Автор: admin
В наши дни понятие DevOps у всех на слуху. Это организационный подход, широко используемый для ускорения разработки и развёртывания приложений. Организации внедряют у себя практики DevOps, так как они обещают дать тем, кто их использует, всё лучшее, что существует в мире разработки ПО, причём на всех этапах работы от планирования и тестирования, до развёртывания и мониторинга проектов. В реализации практик DevOps важную роль играют CI/CD-инструменты вроде Jenkins. А интеграция Jenkins с Selenium значительно облегчает процесс автоматизации Selenium-тестов.



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

Что такое Jenkins?


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

Подробнее о Jenkins можно почитать здесь.

Что такое Selenium?


Selenium это опенсорсный инструмент, который широко используется для автоматизации тестирования веб-приложений. Пользоваться им просто, существуют форумы по Selenium, куда может обратиться тот, кто столкнулся с какими-то проблемами. Благодаря этому Selenium обрёл определённую популярность в кругах тестировщиков ПО. В состав Selenium входят четыре основных компонента: Selenium IDE, Selenium RC, Selenium WebDriver и Selenium Grid. Они созданы в расчёте на решение различных задач. Selenium даёт тому, кто им пользуется, возможности по кросс-браузерному и параллельному тестированию веб-приложений. Это позволяет тестировщикам выполнять испытания приложений в разных операционных системах и браузерах, что, в частности, даёт им возможность проверить совместимость веб-проекта с различными браузерами.

Если вас интересуют подробности о Selenium посмотрите этот материал.

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

Установка, настройка и запуск Jenkins


Шаг 1 Для того чтобы загрузить Jenkins можно воспользоваться соответствующей ссылкой на официальном сайте проекта. Нас, в частности, интересует .war-файл Jenkins.

Шаг 2 Загрузим файл jenkins.war и сохраним его в выбранную директорию.

Шаг 3 Откроем окно командной строки и перейдём в папку, в которой хранится загруженный .war-файл.

Шаг 4 Выполним команду java -jar Jenkins.war. Будет запущен сервер Jenkins.

Шаг 5 Обычно Jenkins по умолчанию запускается на порте 8080. Если этим портом уже пользуется какой-то другой сервис, можно указать при запуске Jenkins другой порт, воспользовавшись командой такого вида:

java -jar jenkins.war --httpPort=8081

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

Настройка порта для Jenkins

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

Пароль

В консоли, после успешного завершения установки, можно будет увидеть сообщение Jenkins is fully up and running.

Шаг 6 Запустим браузер и перейдём к localhost. Как уже говорилось, по умолчанию Jenkins запускается на порте 8080. В моём случае порт был изменён на 8081, именно к этому порту localhost мне и нужно подключиться.

После этого откроется страница настроек Jenkins. Она используется для создания административной учётной записи.

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

Окно ввода пароля

Шаг 8 После ввода пароля система предложит нам установить плагины.

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

Если вы не знаете точно о том, какие именно плагины вам понадобятся можете выбрать вариант Install suggested plugins. Это приведёт к установке набора наиболее часто используемых плагинов. Но если вы знаете о том, что именно вам нужно, основываясь, например, на требованиях к проекту, вы можете выбрать вариант Select plugins to install и самостоятельно выбрать плагины для установки.

После этого плагины будут загружены и установлены.

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

Создание учётной записи администратора

Существует множество способов интеграции Jenkins с Selenium WebDriver. Некоторые из них мы рассмотрим ниже. Разные способы интеграции этих систем применимы в различных условиях.

Первый метод интеграции Jenkins с Selenium


Шаг 1 В панели управления Jenkins щёлкнем по значку New Item для создания нового проекта. Укажем имя проекта и выберем вариант Freestyle Project. После этого нажмём OK.

Начало создания нового проекта

Настройка свойств проекта

Шаг 2 На вкладке свойств проекта General введём, в поле Description, описание проекта.

Ввод описания проекта

Шаг 3 На вкладке Source Code Management выберем None в группе параметров Source Code Management.

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

Шаг 4 Jenkins позволяет планировать задания по сборке проектов. Время запуска заданий указывают в таком формате:

MINUTE HOUR DOM MONTH DOW

Смысл этих сокращений раскрыт в следующей таблице

Сокращённое наименование Описание
MINUTE Минуты в пределах часа (0 59)
HOUR Часы в пределах дня (0 23)
DOM День месяца (1 31)
MONTH Месяц (1 12)
DOW День недели (0 7), где воскресенье может быть представлено значениями 0 и 7

Шаг 5 Создадим проект, который будем применять для запуска тестов с использованием Selenium WebDriver и TestNG.

Вот код на Java:

package Pages;import static org.testng.Assert.assertEquals;import java.util.concurrent.TimeUnit;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.testng.annotations.AfterTest;import org.testng.annotations.BeforeTest;import org.testng.annotations.Test;public class LoginPage {WebDriver driver;@BeforeTestpublic void setUp() {System.setProperty("webdriver.chrome.driver", "C:\\Users\\Shalini\\Downloads\\chrom86_driver\\chromedriver.exe");driver = new ChromeDriver();}public void login() {String login_url = "https://opensource-demo.orangehrmlive.com/";driver.get(login_url);driver.manage().window().maximize();driver.manage().timeouts().pageLoadTimeout(10, TimeUnit.SECONDS);driver.findElement(By.id("txtUsername")).sendKeys("Admin");driver.findElement(By.id("txtPassword")).sendKeys("admin123");System.out.println(driver.getTitle());}@Testpublic void dashboard() {driver.findElement(By.id("menu_dashboard_index")).click();String textPresent = driver.findElement(By.xpath("//*[@id=\"content\"]/div/div[1]/h1")).getText();String textToBePresent = "DashBoard";assertEquals(textPresent, textToBePresent);}@AfterTestpublic void tearDown() {driver.quit();}}

Шаг 6 Создадим файл TestNG.xml:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"><suite name="TestSuite"><test name="LoginTest"><groups><run><include name="DemoTest"/></run></groups><classes><class name="Pages.LoginPage"></class></classes></test></suite>

Шаг 7 В папке проекта добавим зависимости. Создадим .bat-файл со следующим содержимым:

ava cp bin;lib/* org.testng.TestNG TestNG.xml

Тут надо указать точное имя файла TestNG.xml, созданного ранее.

Шаг 8 Выберем в панели управления Jenkins проект, который мы создали в самом начале работы. Щёлкнем по Configure. На вкладке General щёлкнем по Advanced и установим флажок Use custom workplace. Введём в поле Directory путь к папке проекта.

Ввод пути к папке проекта

Шаг 9 На вкладке Build откроем выпадающий список Add Build Step и выберем Execute Windows batch Command. Введём в него имя .bat-файла, созданного на шаге 7.

Ввод сведений о .bat-файле

Шаг 10 Щёлкнем по кнопке Apply, потом по кнопке Save для того чтобы сохранить изменения, внесённые в настройки Jenkins-проекта. Теперь щёлкнем по кнопке Build Now, после чего можно будет понаблюдать за процессом выполнения тестов.

Запуск сборки проекта

Второй метод интеграции Jenkins с Selenium


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

Что такое Maven?


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

Совместное использование Maven, Jenkins и Selenium WebDriver даёт возможность построения DevOps-модели, реализующей механизм непрерывной интеграции программных проектов.

Установка Maven


Шаг 1 Загрузим дистрибутив Maven с официального сайта проекта.

Шаг 2 Добавим в состав системных переменных MAVEN_HOME переменную, в которой содержится путь к домашней директории Maven.

Системная переменная MAVEN_HOME

Шаг 3 Внесём в переменную Path путь к директории bin, содержащейся в домашней директории Maven.


Настройка переменной Path

Шаг 4 Для того чтобы проверить правильность установки Maven откроем окно командной строки и попробуем выполнить команду mvn version.

Проверка правильности установки Maven

Шаг 5 Создадим Maven-проект и добавим в файл pom.xml соответствующие зависимости:

<project xmlns="http://personeltest.ru/away/maven.apache.org/POM/4.0.0" xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://personeltest.ru/away/maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>demoProject</groupId><artifactId>demoProject</artifactId><version>0.0.1-SNAPSHOT</version><build><sourceDirectory>src</sourceDirectory><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version><configuration><sоurce>1.8</sоurce><target>1.8</target></configuration></plugin></plugins></build><dependencies><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>3.141.59</version></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>7.3.0</version><scope>test</scope></dependency></dependencies></project>

Вот Java-код:

package WebDriverProject;import org.junit.Assert;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.firefox.FirefoxDriver;public class LoginClass {WebDriver driver;@BeforeTestpublic void setup() {System.setProperty("WebDriver.gecko.driver", "C:\\Users\\shalini\\Downloads\\geckodriver-v0.26.0-win64\\geckodriver.exe");driver = new FirefoxDriver(); //Инициализация WebDriver}@Testpublic void loginTest() {driver.get("https://opensource-demo.orangehrmlive.com/"); //Определение URLString pageTitle = driver.getTitle(); //get the title of the webpageSystem.out.println("The title of this page is ===> " + pageTitle);Assert.assertEquals("OrangeHRM", pageTitle); //Проверка заголовка страницыdriver.findElement(By.id("txtUsername")).clear(); //Очистить поле перед вводом значенийdriver.findElement(By.id("txtUsername")).sendKeys("Admin"); //Ввести имя пользователяdriver.findElement(By.id("txtPassword")).clear();driver.findElement(By.id("txtPassword")).sendKeys("admin123"); //Ввести парольdriver.findElement(By.id("btnLogin")).click(); //Щёлкнуть по кнопке LoginSystem.out.println(Successfully logged in );}@AfterTestpublic void teardown() {driver.quit();}}

Вот XML-файл с настройками тестов:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"><suite name="TestSuite"><test name="LoginTest"><groups><run><include name="DemoTest"/></run></groups><classes><class name=" WebDriverProject.LoginClass"></class></classes></test></suite>

Интеграция Selenium и Jenkins с помощью Maven


Только что мы говорили об интеграции Jenkins с Selenium WebDriver. То, что получилось, лучше всего использовать для автоматизации Selenium-тестов. Интеграция Jenkins с Selenium WebDriver даёт разработчику надёжную систему кросс-браузерного тестирования проектов. Здесь мы поговорим об интеграции Jenkins и Selenium с помощью Maven.

Шаг 1 Запустим сервер Jenkins.

Шаг 2 Откроем браузер и перейдём на localhost, использовав порт, на котором работает Jenkins.

Открытие панели управления Jenkins в браузере

Шаг 3 Щёлкнем по кнопке New Item в панели управления

Кнопка New Item

Шаг 4 Введём имя проекта и, в качестве типа проекта, выберем Maven project.

Ввод имени проекта и выбор его типа

Шаг 5 Нажмём на кнопку OK. После этого можно будет увидеть, как в панели инструментов появилось новое задание.

Новый проект в панели управления

Шаг 6 Выберем проект и нажмём на кнопку Configure.

Переход к настройке проекта

Шаг 7 На вкладке Build, в поле Root POM, введём полный путь к файлу pom.xml. В поле Goals and options введём clean test.

Настройка параметров сборки проекта

Шаг 8 Щёлкнем Apply, а затем Save.

Шаг 9 Нажмём на кнопку Build Now.

Запуск сборки проекта

Шаг 10 Будет начата сборка проекта, а после её завершения появится соответствующее сообщение. Для того чтобы посмотреть полный журнал нужно щёлкнуть по кнопке Console Output.

Просмотр журнала сборки

Итоги


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

Надеюсь, теперь вы без труда сможете интегрировать Jenkins с Selenium WebDriver.

Пользуетесь ли вы Jenkins и Selenium WebDriver?

Подробнее..

Перевод Собственный микроскоп из кубиков LEGO

06.05.2021 20:17:21 | Автор: admin
Сегодня мы хотим познакомить вас с проектом Lego Microscope. Цель этого проекта заключается в том, чтобы предоставить всем желающим инструкции и ресурсы по сборке собственного микроскопа из кубиков LEGO.
Микроскоп

Обзор проекта


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

  • Инструкции по сборке микроскопа в формате PDF.
  • Перечень кубиков LEGO в виде .LDR-файла. Если у вас дома нет нужных кубиков LEGO этот файл можно выгрузить на Bricklink.com для того чтобы быстро собрать необходимые кубики.
  • Ссылки на страницы, на которых можно купить линзы для окуляра (Европа, Великобритания).
  • Сведения о модуле камеры iPhone 5s, линза из которого используется в этом проекте.
  • Набор исследователя (Explorer Kit), который призван помочь всем желающим расширить свои знания в области микроскопии и оптики.
  • Научная статья, посвящённая Lego Microscope.


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

Инструкции


Имеются два набора инструкций.

  • Полный план работ, который можно использовать при сборке микроскопа за один заход.
  • Пошаговый план, разделённый на 5 частей (A, B, C, D, E), который используется в том случае, если принято решение изучать материалы Набора исследователя и попутно собирать микроскоп. Если вы решите пойти именно таким путём вам понадобятся материалы из этой папки репозитория.

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


Фрагмент плана сборки микроскопа

Оптика


Хотя мы стремились к тому, чтобы всё, что можно, было бы собрано из кубиков LEGO, в том числе система освещения, мы вынуждены использовать линзы сторонних производителей, так как LEGO не продаёт оптику. Сейчас для сборки микроскопа нужно 3 вида линз:

  • Окуляр: для него мы использовали две простые акриловые линзы (диаметр 34,5 мм, фокусное расстояние 106 мм). В частности, речь идёт о линзах 308.OM7. Линзы мы скрепили обычной прозрачной клейкой лентой. Вот ссылки на страницы, где их можно купить (Европа, Великобритания).
  • Объектив с маленьким увеличением: тут мы воспользовались простой стеклянной линзой (диаметр 18 мм, фокусное расстояние 26,5 мм). Речь идёт о линзе 551.OA51 (Европа, Великобритания). Вы вполне можете использовать тут и какие-то другие линзы. Для того чтобы выбранная вами линза могла бы быть установлена так, как запланировано в инструкции, обращайте внимание на её размеры.
  • Объектив с большим увеличением: его роль сыграла пластиковая линза из модуля камеры iPhone 5. В момент написания этого текста такой модуль можно было купить за 2-4. При этом вся электроника модуля нам не нужна, что печально, но нам требуется лишь линза, а не весь модуль.


Модуль камеры

Набор исследователя


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


Пример наблюдений, выполненных с помощью микроскопа

Хотите собрать собственный LEGO-микроскоп?

Подробнее..

Перевод Рассказ о том, почему в 2021 году лучше выбирать TypeScript, а не JavaScript

15.05.2021 14:20:40 | Автор: admin
Недавно я, используя React Native, занимался разработкой мобильного приложения для медитации Atomic Meditation. Эта программа помогает тем, кто ей пользуется, выработать привычку медитировать, ежедневно уделяя этому занятию какое-то время. В ходе работы у меня появились серьёзные причины приступить к изучению TypeScript и начать пользоваться им вместо JavaScript в проектах среднего и крупного размера.

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



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

День 1: всё идёт как надо


В React Native есть объект AsyncStorage, который представляет собой хранилище данных типа ключ/значение с асинхронным доступом к значениям по ключам. Он даёт разработчику очень простой механизм для организации постоянного хранения данных на мобильном устройстве пользователя.

Например, воспользоваться им можно так:

AsyncStorage.setItem("@key", value)

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

Ниже показано применение React-хука useState для объявления переменной sessionCount и для установки её начального значения в 0. Тут же имеется и функция setSessionCount, которая позволяет менять состояние sessionCount:

const [sessionCount, setSessionCount] = useState(0)

Предположим, пользователь завершил сеанс медитации (я, напомню, занимался разработкой приложения для медитации). В sessionCount хранится общее количество сеансов медитации, завершённых пользователем (я буду теперь называть этого пользователя Anxious Andy беспокойный Энди). Это значит, что нам надо прибавить 1 к значению, хранящемуся в sessionCount. Для этого вызывается функция setSessionCount, в которой и выполняется прибавление 1 к предыдущему значению sessionCount. А потом количество завершённых медитаций нужно сохранить в AsyncStorage в виде строки.

Всё это надо сделать в некоей функции, которую я предлагаю назвать saveData:

// Пользователь завершил сеанс медитацииconst saveData = () => {setSessionCount(prev => {const newSessionCount = prev + 1AsyncStorage.setItem("@my_number", newSessionCount.toString())return newSessionCount})}

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

День 2: затишье перед бурей


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

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

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

useEffect(() => {AsyncStorage.getItem("@my_number").then(data => setSessionCount(data))}, [])

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

Новое значение, как и прежде, мы записываем в хранилище:

// Пользователь завершил сеанс медитацииconst saveData = () => {setSessionCount(prev => {const newSessionCount = prev + 1AsyncStorage.setItem("@my_number", newSessionCount.toString())return newSessionCount})}

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

День 3: буря


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

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

Но его любовь к этой программе быстро сходит на нет

Программа сообщает ему о том, что он провёл 11 сеансов медитации. А он-то медитировал всего два раза!


Неправильная статистика по сеансам медитации

Что пошло не так?


В первый день мы записали в sessionCount начальное значение число 0.

Пользователь завершил сеанс медитации поэтому мы добавили к sessionCount 1. Затем мы преобразовали то, что получилось, в строку в 1, после чего записали это в асинхронное хранилище (вспомните оно может хранить только строковые данные).

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

Пользователь завершает сеанс медитации и мы прибавляем к sessionCount 1. А в JavaScript 1 + 1 равняется 11, а не 2.

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

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

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

Решить эту и другие подобные проблемы можно с помощью TypeScript.

Что такое TypeScript?


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

Браузеры не могут выполнять TypeScript-код. Поэтому TypeScript-файлы проекта надо транспилировать в JavaScript. На выходе получится несколько JavaScript-файлов (или один большой бандл с JS-кодом проекта).

Использование TypeScript в React Native-проектах


Добавить поддержку TypeScript в существующий React Native-проект очень просто. А именно, надо будет кое-что установить из npm и сделать пару настроек.

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

После того, как изменено расширение файла, TypeScript разразится гневной тирадой о том, что аргумент типа 'string | null' нельзя назначить параметру типа 'SetStateAction<number>'.


TypeScript предупреждает разработчика о том, что с типами данных что-то не так

Это значит, что мне тут, чтобы избавиться от сообщения об ошибке, надо, во-первых, проверить data на null, а во-вторых преобразовать из строки в число (воспользовавшись parseInt()):

useEffect(() => {AsyncStorage.getItem("@my_number").then(data => {if (data) {setSessionCount(parseInt(data))}})}, [])

Использование TypeScript подталкивает разработчика к написанию более качественного и надёжного кода. Это просто замечательно!

По каким материалам изучать TypeScript?


Я изучал TypeScript по этому видеокурсу канала Net Ninja. И если бы мне надо было бы что-нибудь изучить, то я в первую очередь поинтересовался бы тем, нет ли на этом канале курса по тому, что мне нужно.

Кроме того, официальная документация по TypeScript очень даже хороша.

Итоги


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

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

Используете ли вы TypeScript в своих React-проектах?


Подробнее..

Перевод Регрессия и линейные комбинации векторов

19.05.2021 16:06:46 | Автор: admin
Недавно я помогал вести курс по линейной алгебре, который организовали Тай-Даная Брэдли и Джек Хидари. Одним из вопросов, который периодически возникал у слушателей курса, был вопрос о том, почему программистов должна заботить тема линейной комбинации векторов.



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

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

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

Конкретный пример этого регрессия. Большинство людей, говоря о регрессии, имеют в виду линейную регрессию. Речь идёт о поиске линейной функции, вроде , которая хорошо аппроксимирует некие данные. В случае с функцией от нескольких переменных имеется, например, в роли вектора входных переменных, и в роли вектора коэффициентов или весов. А сама функция тогда выглядит как .

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


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

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


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

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


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


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

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

Простой стохастический градиентный спуск


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

Начнём с объявления нескольких полезных псевдонимов типов:

from typing import Callable, Tuple, ListInput = Tuple[float, float, float]Coefficients = List[float]Gradient = List[float]Hypothesis = Callable[[Input], float]Dataset = List[Tuple[Input, float]]

Затем объявим простой класс-обёртку для базисных функций:

class QuadraticBasisPolynomials:def __init__(self):self.basis_functions = [lambda x: 1,lambda x: x[0],lambda x: x[1],lambda x: x[2],lambda x: x[0] * x[1],lambda x: x[0] * x[2],lambda x: x[1] * x[2],lambda x: x[0] * x[0],lambda x: x[1] * x[1],lambda x: x[2] * x[2],]def __getitem__(self, index):return self.basis_functions[index]def __len__(self):return len(self.basis_functions)def linear_combination(self, weights: Coefficients) -> Hypothesis:def combined_function(x: Input) -> float:return sum(w * f(x)for (w, f) in zip(weights, self.basis_functions))return combined_functionbasis = QuadraticBasisPolynomials()

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

def total_error(weights: Coefficients, data: Dataset) -> float:hypothesis = basis.linear_combination(weights)return sum((actual_output - hypothesis(example)) ** 2for (example, actual_output) in data)def single_point_error(weights: Coefficients, point: Tuple[Input, float]) -> float:return point[1] - basis.linear_combination(weights)(point[0])

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


Здесь это линейная комбинация базисных функций:


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


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


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

def gradient(weights: Coefficients, data_point: Tuple[Input, float]) -> Gradient:error = single_point_error(weights, data_point)dE_dw = [0] * len(weights)for i, w in enumerate(weights):dE_dw[i] = -2 * error * basis[i](data_point[0])return dE_dw

И, наконец, реализуем ядро алгоритма градиентного спуска, в которое входят механизмы, упрощающие отладку кода:

import randomdef print_debug_info(step, grad_norm, error, progress):print(f"{step}, {progress:.4f}, {error:.4f}, {grad_norm:.4f}")def gradient_descent(data: Dataset,learning_rate: float,tolerance: float,training_callback = None,) -> Hypothesis:weights = [random.random() * 2 - 1 for i in range(len(basis))]last_error = total_error(weights, data)step = 0progress = tolerance * 2grad_norm = 1.0if training_callback:training_callback(step, 0.0, last_error, 0.0)while abs(progress) > tolerance or grad_norm > tolerance:grad = gradient(weights, random.choice(data))grad_norm = sum(x**2 for x in grad)for i in range(len(weights)):weights[i] -= learning_rate * grad[i]error = total_error(weights, data)progress = error - last_errorlast_error = errorstep += 1if training_callback:training_callback(step, grad_norm, error, progress)return basis.linear_combination(weights)

Сгенерируем какой-нибудь простой набор данных и запустим оптимизацию:

def example_quadratic_data(num_points: int):def fn(x, y, z):return 2 - 4*x*y + z + z**2data = []for i in range(num_points):x, y, z = random.random(), random.random(), random.random()data.append(((x, y, z), fn(x, y, z)))return dataif __name__ == "__main__":data = example_quadratic_data(30)gradient_descent(data,learning_rate=0.01,tolerance=1e-06,training_callback=print_debug_info)

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


Ошибка и шаги алгоритма

Ядра и регуляризация


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

Настоящее полиномиальное ядро. Мы решили использовать простой набор полиномиальных функций. Это тесно связано с концепцией ядра, но настоящее полиномиальное ядро использует немного другие базисные функции. Оно масштабирует некоторые из базисных функций, используя . Но зачем это делать? Ответ сводится к технике повышения эффективности вычислений, называемой ядерным трюком. Этот трюк, если кратко его описать, позволяет вычислять скалярное произведение двух линейных комбинаций векторов в векторном пространстве без вычисления координат данных в пространстве. Если в реализации используемого алгоритма регрессии используются только скалярные произведения векторов (что справедливо для аналитических решений регрессионных задач), то в нашем распоряжении оказываются сильные стороны нелинейного моделирования признаков и нам, при этом, не нужно тратить ресурсы на непосредственное вычисление признаков. В этой связи уместно будет обсудить гораздо больше теоретических математических вопросов (см. также: Reproducing Kernel Hilbert Space), но тут я не буду углубляться в эту тему.

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

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


Подробнее..

Перевод Использование веб-компонентов при работе над GitHub

29.05.2021 14:11:58 | Автор: admin
Мы, сотрудники GitHub, гордимся тем, что наша платформа обеспечивает тем, кто ей пользуется, первоклассный опыт разработчика (Developer Experience, DX). Значительная часть наших усилий сосредоточена на фронтенде системы, который мы стремимся сделать настолько простым, быстрым и доступным, насколько это возможно. Для проекта таких масштабов, как GitHub, это та ещё задача. В кодовой базе нашего фронтенда, как и во многих других подобных кодовых базах, применяются компоненты независимые, изолированные фрагменты кода, пригодные для многократного использования. Они позволяют командам, которые занимаются проектом, создавать тщательно проработанные интерфейсы, делая своё дело быстро и эффективно, но при этом не отступая от высоких стандартов качества, принятых в GitHub.



Мы, в работе над GitHub, широко используем веб-компоненты. У нас имеется почти два десятка опенсорсных веб-компонентов, и ещё несколько десятков компонентов, код которых закрыт.

Как мы к этому пришли


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

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

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

Два наших первых пользовательских элемента были выпущены в 2014 году. Это были <relative-time> и <local-time>, которые умели показывать время и дату в удобной форме, а так же <include-fragment>, который позволял нам выполнять отложенную загрузку HTML-фрагментов. Постепенно к нам пришло понимание того, какими мощными возможностями могут обладать подобные элементы. Тогда мы приступили к широкомасштабной замене существующих механизмов кодовой базы на новые. Например поменяли наше модальное диалоговое окно facebox на веб-компонент <details-dialog>. Теперь у нас были самые разные компоненты от весьма универсальных, многоцелевых сущностей, реализующих распространённые шаблоны поведения, вроде <remote-input>, до узкоспециализированных компонентов, таких, как элемент <markdown-toolbar> и родственные ему элементы.

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

Улучшение процесса создания компонентов


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

Фреймворк ViewComponent


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

Библиотека Catalyst


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

Разработчики библиотеки Catalyst вдохновлялись замечательной библиотекой Stimulus и базовым классом LitElement от Google. Она спроектирована в расчёте на решение особого набора задач, стоящих перед нашими разработчиками. Наши внутренние DX-исследования показали, что применение Catalyst даёт, в сравнении со старыми подходами, существенные улучшения в деле написания кода компонентов.

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

Вспомогательные инструменты


Мы предлагаем разработчикам набор опенсорсных конфигураций линтера. Общие правила, применяемые при написании кода, выражены в плагине eslint-plugin-github. А для более глубокой проверки кода веб-компонентов мы используем плагин eslint-plugin-custom-elements. Оформление этих плагинов в виде опенсорсных проектов позволило нам убрать код из монолита, но при этом оставаться последовательными при работе над проектом.

У нас, кроме того, имеются внутренние тесты, которые направлены на проверку того, следуют ли программисты рекомендованным подходам к разработке, и того, что они больше не используют паттерны и модели, реализующие некие схемы поведения элементов системы, признанные устаревшими. Один из наших тестов направлен на контроль того, чтобы разработчики не применяли бы в новом коде паттерн facebox, признанный устаревшим. Этот тест предлагает использовать в качестве альтернативы facebox элемент <details-dialog>.

class FaceboxDeprecationTest < Test::Fast::TestCase  EXPECTED_NUMBER_OF_FACEBOXES = 44  # Find facebox triggers set with rel=facebox, in either HTML attributes or  # as part of hash assignment (for rails helpers)  REGEX_FOR_FACEBOX_BINDING = %r|rel\s*[=:]>?\s*["']?facebox|  REGEX_FOR_DATA_FACEBOX = %r|data-facebox\s*=>?\s*|  def test_limit_facebox    actual_rel_facebox = grep(REGEX_FOR_FACEBOX_BINDING, options: %w[-In], paths: %w[app/**/*.erb])    actual_data_facebox = grep(REGEX_FOR_DATA_FACEBOX, options: %w[-In], paths: %w[app/**/*.erb])    count = actual_rel_facebox.count("\n") + actual_data_facebox.count("\n")    assert_operator count, :<=, EXPECTED_NUMBER_OF_FACEBOXES, <<-EOLIt looks like you added a facebox. Please use <details-dialog> instead.If you must increment EXPECTED_NUMBER_OF_FACEBOXES in this test, please/cc @github/ui-frameworks-reviewers in your pull request, as we may be able to help! Thanks.EOL    assert_equal EXPECTED_NUMBER_OF_FACEBOXES, count, <<-EOLIt looks like you removed a facebox. YOU ARE AWESOME!Please decrement EXPECTED_NUMBER_OF_FACEBOXES in this test and treat yourself tosomething special.  You deserve it.EOL  endend

Новый жизненный цикл наших веб-компонентов


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

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

Catalyst и начало жизненного цикла компонента


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

Для регистрации веб-компонента может понадобиться некоторый объём шаблонного кода, но мы упрощаем эту задачу за счёт применения особого соглашения об именовании сущностей и благодаря наличию некоторого количества TypeScript-декораторов. То, что в Catalyst называется Actions, позволяет решить задачу организации прослушивания событий проще, чем она решается через поддержку глобальных прослушивателей событий. Внесение изменений в то, что в Catalyst называется Targets, в применении к существующему HTML-коду, лучше, чем рендеринг HTML-шаблонов в браузере.

Извлечение компонента из монолита


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

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

Реализация особых требований, предъявляемых к опенсорсным компонентам


Компоненты, входящие в состав монолита, могут быть тесно связаны с логикой конкретного приложения. У них могут быть зависимости, проверять их можно существующими тестами. А к опенсорсным компонентам мы предъявляем совсем другие требования. Количество зависимостей наших опенсорсных компонентов должно быть близко к 0, они не должны быть ориентированы на применение лишь в рамках некоей библиотеки или какого-то фреймворка, они должны быть нетребовательными к ресурсам, нестилизованными, отделёнными от любых других компонентов. Они должны решать лишь одну задачу и решать её они должны хорошо.

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

Отличным примером компонента, прошедшего все стадии опенсорсного жизненного цикла, является элемент <typing-effect>. Одна из команд GitHub недавно создала прототип элемента интерфейса, напоминающего терминал, текст в котором появлялся так, будто кто-то набирает этот текст на клавиатуре. Ранее мы пользовались весьма качественной и надёжной библиотекой typed.js, с помощью которой добивались анимированного эффекта ввода текста с клавиатуры. Изначально команда решила снова обратиться к этой библиотеке.

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

Тогда была создана первая версия нашей собственной анимации ввода текста, предназначенная для нового интерфейса. Благодаря использованию Catalyst на это ушло меньше дня, а объём кода не превышал 40 строк. Поняв, что использование этого эффекта может заинтересовать и другие команды, мы решили вывести соответствующий элемент в опенсорс. Мы отрефакторили код компонента, избавив его от всех зависимостей, извлекли его из библиотеки Catalyst и сделали опенсорсным, назвав <typing-effect>.

Результаты


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

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

Что дальше?


Мы продолжаем выводить в опенсорс всё больше универсальных веб-компонентов, реализующих различные модели поведения. Всё это называется GitHub Elements. Коллекцию этих элементов можно найти в этом репозитории, она согласована с нашей страницей на webcomponents.org.

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

Пользуетесь ли вы веб-компонентами в своих проектах?


Подробнее..

Перевод О неоправданно хорошей работе -z var

30.05.2021 18:13:46 | Автор: admin
Есть такой сабреддит /r/nononoyes, где публикуют видео, в которых происходит что-то такое, что, на первый взгляд, кажется ужасно неправильным, идущим к катастрофе. Но в конце всё, чудесным образом, заканчивается хорошо.

В том сабреддите хорошо смотрелась бы команда [ -z $var ].



Это конструкция, которая используется в bash для проверки того, является ли переменная пустой. Но тут не хватает кавычек (её более правильный вариант выглядел бы как [ -z $var ]). Это, при работе с переменными, которые могут быть пустыми, часто приводит к неприятностям.

Рассмотрим обратное выражение [ -n $var ], которое проверяет переменную на то, что она является непустой. Та же проблема с кавычками делает её полностью бесполезной:

Входные данные Ожидаемый результат [ -n $var ]
""
False
True!
"foo"
True
True
"foo bar"
True
False!

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

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

Количество аргументов Действие Типичный пример
0 Возврат False.
[ ]
1 Возврат True, если аргумент $1 не является пустым.
[ "$var" ]
2 Применение унарного оператора $1 к $2.
[ -x "/bin/ls" ]
3 Применение бинарного оператора $2 к $1 и к $3.
[ 1 -lt 2 ]

Если это учесть становится понятным то, почему команда [ -n $var ] в двух случаях ведёт себя неправильно:

  • Когда переменная является пустой и при этом не заключена в кавычки, она удаляется, и мы передаём команде 1 аргумент литеральную строку -n. Так как -n не является пустой строкой команда выдаёт True, а должна была бы выдать False.
  • Когда переменная содержит foo bar и не заключена в кавычки, она разделяется на два аргумента, в результате мы передаём команде 3 аргумента: -n, foo и bar. Так как foo это не бинарный оператор, результатом работы команды является False (с выдачей сообщения об ошибке), а, на самом деле, результатом должно быть True.

А теперь посмотрим на то, как работает [ -z $var ]:

Входные данные Ожидаемый результат [ -z $var ] Команда test
""
True: пустая переменная
True
1 аргумент: проверка того, является ли -z непустой переменной
"foo"
False: непустая переменная
False
2 аргумента: применение -z к foo
"foo bar"
False: непустая переменная False (ошибка) 3 аргумента: применение foo к -z и bar

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

Другими словами, [ -z $var ] работает гораздо лучше, чем этого можно ожидать.

Конечно, это не значит, что я призываю всех отказаться от кавычек. Для foo bar [ -z $var ] вернёт правильный код выхода, но при этом выведет некрасивое сообщение об ошибке. Для (это строка, в которой имеются только пробелы) вернёт True, хотя должна вернуть False, так как соответствующий аргумент удаляется так, как если бы он был пустым. Bash, кроме того, что неправильно, при попытке воспользоваться механизмом внедрения кода, пропустит конструкцию var="foo -o x", так как она будет нормально воспринята командой test.

Какова мораль сей басни? Она такая же, как и всегда: не забывайте о кавычках. Даже тогда, когда кажется, что и без них всё работает как надо.

Утилита ShellCheck знает об этих особенностях. Тут можно проверить код, о котором мы говорили. А именно, анализируя конструкцию [ -n $var ], программа разразится гневным сообщением красного цвета, а рассматривая конструкцию [ -z $var ] всего лишь покажет обычное зелёное предупреждение об отсутствии кавычек.

Сталкивались ли, при работе в bash, с проблемами, связанными с кавычками?


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru