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

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

Перевод Мир JavaScript в 2021 году

23.02.2021 12:21:01 | Автор: admin
Мир веб-разработки весьма изменчив. Изменения в нём происходят очень быстро. Что принесёт в него 2021 год? Здесь я хочу поделиться выводами о грядущих крупных JS-трендах, которые я сделал, проанализировав соответствующие исследования, проведённые в 2020 году.




Сначала пара слов о самих этих исследованиях. К сожалению, какое-то время нам придётся обходиться без свежих материалов отличного Front End Tooling Survey. Это усложняет поиск трендов. И хотя в этом году на одно хорошее исследование стало меньше, вместо него появилось одно новое The State of Front End. Но оно проводится первый год, поэтому в нашем распоряжении нет его данных за прошлые годы, что, опять же, не способствует облегчению задачи поиска трендов. Правда, в нём приняло участие внушительное количество разработчиков со всего мира (4500), что, определённо, делает его ценным источником информации.

Менеджеры пакетов


В прошлом году я предлагал понаблюдать за развитием PNPM. Этот менеджер рассчитан на недопущение конфликтов версий пакетов и хорошо показывает себя при работе с монорепозиториями. Существуют люди, которые очень им увлечены, в прошлом году его репозиторий набрал 9500 звёзд на GitHub. PNPM, совершенно очевидно, привлёк на свою сторону немало разработчиков. Но мне кажется, что в 2021 году он вряд ли сможет серьёзно конкурировать с другими менеджерами пакетов. Дело в том, что Yarn и NPM используются в огромном множестве реальных проектов, и в том, сколько энергии команды разработчиков обоих этих проектов тратят на их развитие, на оснащение их новыми возможностями. Появление некоторых из этих новшеств стало непосредственной реакцией на наличие аналогичных возможностей у PNPM. Например это Рабочие области (Workspaces) Yarn. Это показывает важность конкуренции в развитии опенсорса.

Тестирование


В 2019 году Cypress и Puppeteer стали заметными новыми инструментами. Их успех продолжился и в 2020. Но Microsoft выпустила новый инструмент для сквозного тестирования, Playwright. Такое ощущение, что он появился буквально ниоткуда, но в 2020 году он набрал почти 20 тысяч звёзд на GitHub. У Microsoft, у самой большой в мире компании, занимающейся разработкой ПО, есть возможности для широкого продвижения своих разработок. Но это лишь частично объясняет популярность Playwright. Главная причина его популярности богатый функционал и возможность лёгкого перехода на него с Puppeteer.


Playwright возглавляет список тестировочных фреймворков. И это несмотря на то, что в 2019 году о нём ещё никто не знал

С тех пор как Сатья Наделла стал CEO Microsoft, у компании появился обычай выпускать популярные и мощные опенсорсные инструменты. Например VS Code.

Разновидности JavaScript


В прошлом году я говорил о том, что TypeScript, медленно, но верно, захватывает JavaScript-мир. Этот тренд усилился. Создатели бесчисленного множества опенсорсных проектов спешат включить TypeScript в список поддерживаемых этими проектами возможностей. Например, Deno, проект, репозиторий которого на GitHub стал самым звёздным JS-проектом в 2020 году, имеет встроенный компилятор TypeScript.

В прошлом году я, учитывая всеобщий интерес к статической типизации и функциональному программированию, рекомендовал понаблюдать за PureScript, в котором всё это есть. Но в 2020 году этот проект, так сказать, не взлетел, на GitHub ему досталось всего 641 новая звезда, а интерес к нему упал на 3%. Если учесть огромный разрыв между TypeScript и его конкурентами, можно сказать, что война языков окончена, и что выиграла её разработка Microsoft. Любому новому проекту очень нелегко будет привлечь внимание сообщества после тех лет, которые оно провело в размышлениях и в атмосфере, перенасыщенной разновидностями JavaScript.

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

Фронтенд-разработка


В 2019 году больше всего GitHub-звёзд досталось фреймворку Vue. В то время это стало для всех большой новостью, это чётко указывало на то, что разработчики любят Vue. В 2020 году история повторилась. Но при этом рыночная доля React, если взглянуть на сведения по загрузкам NPM-пакетов, всё ещё огромна.


Загрузки React в прошлом году

При оценке популярности UI-фреймворков применимы ещё два полезных показателя. Это теги в GitHub и вакансии. Сейчас на GitHub имеется более 80000 репозиториев с тегом React, а с тегом Vue 25000. Если же взглянуть на рынок труда, то окажется, что в прошлом мае ресурс Career Karma сообщал о том, что на Indeed.com имеется 10005 вакансий React-разработчиков в США и лишь 1025 вакансий для Vue-разработчиков. Библиотека React буквально вездесуща, она хорошо выдерживает натиск сильных конкурентов.

Я не могу завершить этот раздел, не упомянув о Svelte и Angular. Фреймворк Angular всё ещё весьма популярен в прошлом году он набрал 13,3 тысячи новых звёзд на GitHub, каждую неделю его загружали из NPM почти 2,5 миллиона раз. Для кого-то, учитывая популярность React, это может показаться неожиданным, но закрывать глаза на эти сведения нельзя. Если говорить о Svelte, то инструмент это, в сравнении с другими похожими, очень молод. Но он, в исследовании State of JS, занял первую строчку по показателю удовлетворённости разработчиков. Правда, я ожидаю, что в 2021 году его популярность будет расти умеренными темпами. Дело в том, что React- и Vue-разработчикам для перехода на Svelte приходится взбираться на довольно-таки крутую кривую обучения.

Бэкенд-разработка


Сфера разработки серверных частей веб-проектов сейчас устроена довольно сложно. Здесь фреймворки для генерирования статических сайтов соседствуют с проектами, рассчитанными на создание API. Если немного во всём этом разобраться и сосредоточиться лишь на фреймворках, рассчитанных исключительно на серверы, то окажется, что позиции Express с 51500 GitHub-звёзд всё ещё весьма крепки. Но в интересующую нас область стремительно ворвался фреймворк Nest, который, только за 2020 год набрал 10300 новых звёзд на GitHub. В результате общее количество звёзд этого фреймворка достигло 33,6 тысяч. Разработчики прибегают к этому фреймворку, привлечённые тем, что он выдвигает строгие требования к проектам, создаваемым на его основе. Это способно ускорить разработку и упростить обслуживание готовых проектов. И, кстати, Nest использует TypeScript.

Если принять во внимание процветание фуллстек-фреймворков, то окажется, что сейчас на наших глазах разворачивается очень важная битва за сердца и умы разработчиков. Дело в том, что такие фреймворки оказывают огромное влияние на архитектуру и производительность приложений, на то, как именно эти приложения работают. В настоящий момент два подобных фреймворка, основанных на React NextJS и Gatsby, значительно популярнее своих Vue-конкурентов, но это лишь подтверждает всё то, что нам известно об экосистеме инструментов для фронтенд-разработки. Внимания же заслуживает то, насколько упал рейтинг удовлетворённости разработчиков фреймворком Gatsby. Отдельные наблюдения указывают на то, что при работе с этим фреймворком программисты сталкиваются с неприятным опытом, хотя есть и сведения о том, что это не так. Учитывая то, что фреймворк NextJS разработан Vercel, и то, что его оснащают возможностями по генерированию статических сайтов, я могу говорить лишь о том, что в этом году его привлекательность будет расти.

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


В сфере инструментов для сборки проектов прямо сейчас видна острая конкурентная борьба. Несмотря на то, что разработчики жалуются на DevX Webpack, этот сборщик проектов, долгое время удерживавший пальму первенства в своей сфере, всё ещё занимает первое место среди конкурентов по объёму использования. В прошлом году можно было видеть, как в сферу сборщиков проектов пришёл Rome, в этом году к верхним строчкам отчётов проекта Rising Stars будут стремиться esbuild, Snowpack и Vite. Цель esbuild проста: ускорение процесса сборки проектов. Это, очевидно, весьма ценно для множества программистских команд и объясняет рост популярности данного инструмента.


Сборщики esbuild и Snowpack попали в верхние строчки соответствующего раздела исследования State of JS 2020

Одна из метрик популярности проектов это количество GitHub-звёзд. Но есть и другие показатели. Например, Snowpack стал первым в отчёте по технологиям, которые интересны разработчикам, и, что даже важнее, оказался в верхней части отчёта об удовлетворённости технологиями. Популярность Snowpack и Vite это недвусмысленное послание, говорящее нам о том, что сообщество серьёзно относится к нативным ES-модулям. Это крайне важная тема, так как выбор инструмента оказывает влияние на процесс сборки проектов, на кеширование, на соотношение модулей, используемых в разработке и в продакшн-окружении.

Инструменты для управления состоянием приложений


Существует ли UI-фреймворк, который можно назвать полным решением для разработки интерфейсов, не подключив к нему соответствующую библиотеку для управления состоянием приложений? Если не принимать во внимание споры о сопоставлении сложности решений с выгодностью их применения в расчёте на будущее, оказывается, что сфера управления состоянием приложений весьма интересна. Дело в том, что Redux испытывает давление с двух сторон: со стороны самой библиотеки React, и со стороны новых независимых разработок.

Я по собственному опыту знаю о мощи хуков React и API Context, но и у них есть свои ограничения. В любом случае, они, определённо, пользуются среди React-разработчиков большой популярностью, так как почти половина участников исследования State of Front End сообщила о том, что пользуется ими.


Раздел исследования State of Front End 2020, посвящённый инструментам для управления состоянием приложений

Итоги


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

Из данных 2020 года можно сделать один важный вывод: облик мира JavaScript-инструментов формируют крупные разработчики ПО. Так, TypeScript, разработка Microsoft, становится индустриальным стандартом, а у проектов, в которых используется этот язык, больше шансов добиться успеха. Хорошие примеры этого тренда NestJS и NextJS (которые не стоит путать).

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

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

Чего вы ждёте от JavaScript-инструментов в 2021 году?

Подробнее..

PHP 8 и развитие языка в 30 вопросах и ответах

24.02.2021 12:05:30 | Автор: admin
В конце ноября мы провели стрим с Никитой Поповым и Дмитрием Стоговым, ключевыми контрибьюторами ядра PHP. За полчаса мы получили 100+ вопросов и ребята не успели ответить на все. Поэтому я сгруппировал оставшиеся сообщения по темам, отсеял совсем специфические и собрал ответы в текстовом виде. Все острые и холиварные вопросы оставил.



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



Планируется ли дальнейшее развитие type hinting? Например, для сигнатур функций.


Простор для улучшений, безусловно, есть. Со времени стрима появились Enum RFC и соответственно возможность их использовать в декларациях типов.

Из того что обсуждалось:
Intersection Types RFC обсуждался несколько раз, вот тут есть немного контекста.
Дженерики см. ниже.
Тайпхинты для Callable тут было несколько RFC: Typesafe callable, Callable prototypes, Functional interfaces. То есть запрос есть, но пока не было удачного RFC.
Алиасы типов обсуждалось в контексте юнион типов, но пока без отдельного RFC.

Конкретных планов по этим идеям пока нет.

Частый вопрос будет ли асинхронный PHP?


Мы получили 13 вопросов за время ноябрьского стрима, вот что именно спрашивали люди
Появится ли когда-нибудь неблокирующий ввод/вывод?

Будет ли добавлена какая-то асинхронность в php имеется в виду встроенная в ядро.

Как оцениваете github.com/amphp/ext-fiber, какая вероятность пройти rfc?

Будет ли РНР асинхронным?

Планируется ли добавление функционала Promise`ов?

Будет ли как то развиваться многопоточное/асинхронное программирование в дальнейших версиях? Есть конкретные планы?

LibUV. В каком состоянии интеграция LibUV в ZendEngine? И будет ли она?

Будет ли event loop-ы на РНР конкурентные с Нодой?

Планируется ли реализация такого же event loop как в javascrypt?

Theoretically, is it possible, to implement light threads, like coroutines in Kotlin<3, via JIT and FFI? Or is it possible to create an async mechanism using JIT?

Есть ли будущее у асинхронного пхп (reactphp, swoole, asyncphp)?

Будет ли движение php в сторону асинхронщины?

Планируется ли внедрение асинхронности и/или многопоточности в следующих версиях языка?

Смотря что понимать под асинхронным PHP.

Писать неблокирующий код на PHP можно уже сейчас с помощью Amp, ReactPHP, Workerman.

Активно обсуждается предложение по файберам RFC и готова реализация. Если оно будет принято, то это упростит работу с пакетами типа ReactPHP и Amp. Подробнее было на канале PHP Digest.

Вот примеры, как может выглядеть аналог async/await в PHP 8.1 + Amp v3 и на ReactPHP.

Кроме того, еще есть Swoole. Это расширение для PHP, в котором реализовано уже все для создания полностью асинхронных приложений, в том числе драйверы БД, а также корутины и каналы. И даже использование стандартных функций для работы с IO (например file_get_contents()). В 2017 я его не рекомендовал, но сейчас он оброс отличной экосистемой и готов для использования в продакшне.

Есть более минималистичное подмножество Swoole: swow. Теоретически, у него есть даже шансы быть включенным в ядро. Но пока об этом рано говорить. Дождемся результатов голосования по файберам.

Что по дженерикам в PHP?


Мы получили 5 вопросов за время ноябрьского стрима, вот что именно спрашивали люди
Будут ли в php generic`и?

Планируется ли реализация дженериков для PHP? Когда она может увидеть свет?

Дженерики. Будут ли, и когда. Полноценные, или фейковые, без рантайм проверки.

Ну когда же будут дженерики?

Есть ли в планах дженерики?

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




Дальше перевод ответа Никиты на Reddit.

Для тех, кто не слишком знаком, есть три широких способа реализации дженериков:
  • Type-erasure (стираемые): Дженерики просто удаляются и Foo<T> становится Foo. Во время выполнения дженерики ни на что не влияют, и предполагается, что проверки типов осуществляются на каком-то предварительном этапе компиляции/анализа (прим. Python, TypeScript).
  • Reification (реификация): Дженерики остаются в рантайме и могут быть на этом этапе использованы (и в случае PHP, могут быть проверены в рантайме).
  • Monomorphization (мономорфизация): С точки зрения пользователя, это очень похоже на реификацию, но подразумевает, что для каждой комбинации аргументов дженериков генерируется новый класс. То есть, Foo<T> не будет хранить информацию что, класс Foo инстанциирован с параметром T, а вместо этого будут созданы классы Foo_T1, Foo_T2, , Foo_Tn специализированный для данного типа параметра.

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

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

Мономорфизация как главная стратегия реализации не имеет смысла в PHP. Она важна для таких языков, как C++ или Rust, где возможность специализировать код для определенных типов имеет большое значение для производительности (и даже в этом случае размер кода остается большой проблемой). В PHP мы не получим от него достаточной выгоды в плане производительности, чтобы оправдать накладные расходы по памяти (опять же, когда речь идет об общей мономорфизации). Тем более что непонятно, как можно кэшировать мономорфизированные методы в opcache (из-за требований иммутабельности).

Единственная причина, по которой мономорфизация была предложена в качестве стратегии реализации, заключается в том, что она упростит реализацию наивной модели дженериков. Предполагается, что нам просто нужно сгенерировать новые классы для всех комбинаций, а остальным частям ядра вообще ничего не нужно знать о дженериках. Однако эта идея ломается, если учесть вариативность дженерик параметров (Traversable<int> это Traversable<int|string>), поскольку такие отношения не могут быть смоделированы без непосредственного знания дженерик параметров.

> Не было заметно особо отзывов по исследованию дженериков, которое вы опубликовали на GitHub. Были ли закулисные разговоры об этом, или это все?

Нет, не было особо разговоров об этом. В последний раз, когда я говорил об этом с Дмитрием, его позиция была (неудивительно) жестким "нет". Слишком много сложностей добавляется, потенциально слишком большое влияние на производительность.

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

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

function test(): List<int> {    // We don't want to write this:    return new List<int>(1, 2, 3);    // We want to write this:    return new List(1, 2, 3);}

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

Это оставляет меня в конфликте с поддержкой дженериков в PHP, и это также является причиной, по которой я не настаивал на обсуждениях этой темы. Я сам не уверен, что это хорошая идея.

> Вы рассматривали стираемые дженерики, как это делает Python?

And that leaves us with the cowards way out

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

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

Хуже того, в PHP даже не будет встроенного валидатора типов, а проблема будет делегирована стороннему инструменту статического анализа, такому как psalm, phpstan или phan (или, по крайней мере, как я понимаю). Это означает, что тип может быть нарушен по умолчанию, и вы должны пойти еще добавить что-то, чтобы предотвратить это.

Еще хуже (черт возьми, насколько хуже может быть?), у нас в PHP есть разделение типов на слабое и строгое. Типы в PHP это не просто декларации типов, они также могут действовать как приведение типов!

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

class StringList {    public function add(string $value) { $this->data[] = $value; }}$list = new StringList;$list->add(42);var_dump($list); // ["42"]


class List<T> {    public function add(T $value) { $this->data[] = $value; }}$list = new List<string>;$list->add(42);var_dump($list); // [42]

Даже strict_types=1 не полностью спасает нас от этого, потому что преобразования int->float по-прежнему разрешены.

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

Sorry, I just don't have a good answer for you :(


Есть попытка стандартизировать синтаксис и семантику дженериков в PHP: github.com/DaveLiddament/php-generics-standard. Пока она довольно сырая и на ранней стадии обсуждения.

Что можно сделать, чтобы разработка PHP стала доступной более широкому кругу разработчиков?


Под этим заголовком мы объединили 3 оригинальных вопроса с ноябрьского стрима
Расскажите, пожалуйста, о текущем состоянии в области публикаций о внутреннем устройстве PHP (яызка и виртуальной машины). Есть ли что-то, что может помочь, кроме непосредственно чтения исходников?

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

Когда можно будет писать php на php?

Ядро PHP в обозримом будущем останется на C. Поэтому если есть желание контрибьютить, то придется с ним разобраться. Никита обновляет PHP Internals book, в котором описаны внутренности ядра.

И есть статья Никиты PHP 7 Virtual Machine и даже перевод она актуальна и из нее можно почерпнуть все самое важное для старта.

Что касается расширений, то тут есть потенциал для развития. Никита и Дмитрий занимаются исследованием возможности писать расширения на PHP. Ждем результатов этого исследования.

Планируется ли открыть внутреннее API PHP наружу через FFI, чтобы писать экстеншены на самом языке? С учетом отказа от PECL в PHP 8 вопрос становится актуальным.


Такого плана нет.

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

Можно ли будет подключать к PHP модули на других языках, кроме C? Я видел примеры подключения Rust через FFI и это очень криво выглядит.


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

В качестве альтернативы FFI есть возможность подключения wasm модулей: wasmerio/wasmer-php.

Какое будущее у FFI?


Кажется, что в FFI уже есть все, чтобы его использовать. Пока использование не особо активно.

Убили короткий тэг. Чем руководствовались, какой в этом смысл?


Если речь про <?, то его не убили. Было горячее обсуждение RFC, но в итоге решили, что в ближайшие 5 лет трогать его не будут.

Зачем добавляют mixed type?


Есть два основных аргумента в его пользу:

mixed сигнализирует о том, что тип не забыли указать, просто он не может быть уточнён,
mixed часто фигурирует в документации PHP.

Подробнее можно прочитать в предложении RFC.

Подписывайтесь на канал PHP Digest, чтоб узнавать про такие вещи первыми. Про mixed была заметка еще в мае 2020.

Будет ли развитие рефлексии? Изменение проперти типов?


Изменения типов свойств, то есть ReflectionProperty::setType, и вообще любых изменений классов не будет никогда. Классы неизменяемы это часть философии языка.

Почему Reflection медленный?


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

Почему php разработчики получают меньше, чем Java разработчик (примерно одного уровня). Или это не так?


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

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

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

А во-вторых, от особенностей рынка.

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

Кроме того, что такое Java и PHP разработчики примерно одного уровня? Как вы сравниваете?

Почему стоит выбрать PHP, а не Go или Node.js?


Выбирайте, что больше нравится.

PHP это в основном веб-разработка. Сейчас более популярны "универсальные" языки типа Python и JS. Планируется ли расширять зону применения PHP?


PHP, как язык, такой же универсальный как JS или Python. Вопрос в рантаймах, то есть где его можно запускать. Станет ли возможным запускать PHP в браузере, как JS? Не думаю, потому что зачем?

Одним из мотивов для FFI и JIT как раз был потенциал применения PHP в других сферах. Что из этого выйдет поживем-увидим.

Планируете ли вы интегрировать final свойства в PHP, как, например, immutable data classes в Java?


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

Есть подробное исследование темы от Larry Garfield. И есть черновик RFC по аксессорам свойств, который позволит делать иммутабельные объекты.

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


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

Если нужна помощь с оформлением RFC пишите либо мне pronskiy, либо @Danack. Кстати, можно ознакомиться с его репозиторием c непринятыми RFC github.com/Danack/RfcCodex и документом по этикету RFC.

Почему фичи PHP 8 такие сырые?


Привет, Кирилл serafimarts :-) Жизнь слишком коротка, чтобы закончить хоть что

  1. Именованные аргументы не гарантируют совместимость на уровне интерфейсов и значительно увеличивают проблемы BC для OS библиотек.

    Есть предложение по атрибуту для задания алиасов #[NamedParameterAlias].
  2. Атрибуты не имеют возможности иерархичной/вложенной декларации и текущий API не позволяет их ввести нормально в будущем.

    См. ниже.
  3. Property Promotion не доделан: Зачем нужны фигурные скобки у конструктора с такими аргументами? Как декларативно их прокинуть в родительский конструктор?

    Не помню, чтоб кто-то поднимал этот вопрос. Почему ты не задал его во время обсуждения RFC?
  4. Почему добавили match, нарушающий дизайн языка? А если добавляются выражения, то где ""$some = foreach (...) => ..."" или ""$some = if(...) => ...""?"

    Видимо, потому, что другие выражения мало кому нужны. Всегда можно создать RFC.
  5. Зачем добавлять union types без возможности их внешней декларации: "type iterable = array | \Traversable""? Почему только дизъюнкция и нет конъюнкции типов?

    Алисы типов годная идея, обсуждается, пока без конкретных планов. По поводу конъюнкции см. ответ в первом вопросе про развитие тайпхинтов.
  6. Зачем костыль со Stringable, но не добавлять такие же неявные имплементации Countable, Serializable, etc...? Почему утиная типизация только для Stringable?

    Stringable нужен, чтоб можно было использовать объект с __toString() там, где стоит тайпхинт string. C Countable и Serializable такой проблемы нет.


Иногда это выбор между сделать так или вообще никак. В разных RFC голосования склоняется в ту или иную сторону.

Какие перспективы по введению nested attributes?


Ответ есть в самом RFC: Why are nested attributes not allowed?

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

То есть, теоретически поддержку сделать можно, но пока не планируется.

А почему с вводом аннотаций не ввели сразу какую то привязку к функциям или методам из коробки, как в том же питоне?


Это другой концепт. Атрибуты в PHP это метаданные, а в Python это декораторы.

Если хочется именно декораторы, возможно, наилучшим решением будет goaop/framework. Пока он, правда, не совместим с PHP 8.

Планируется ли улучшение SPL или поддержки разных структур данных в PHP?


Вот пара вопросов на этот счет от зрителей стрима
Есть какие-то планы по развитию/улучшению SPL в сторону https://github.com/php-ds
Планируется ли добавить в ядро PHP хорошо спроектированные популярные структуры данных: деревья, графы, списки, очереди, множества, кортежи и т.д.
Планируется ли добавление специализированных структур данных (кортежи, множества, деревья т.д.) в ядро?

Есть интерес улучшить эту часть PHP. Из недавнего: обсуждается возможность добавить неймспейс SPL.

Что касается php-ds, то его автор не хотел бы, чтоб расширение мерджили в ядро, потому что в этом случае он был бы привязан к релизному циклу PHP.

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


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

Вот пример из недавнего: Restrict $GLOBALS usage ограничено использование $GLOBALS, что позволило избавиться от кучи внутренних проверок и упростить кодовую базу. Именно об этом говорил Дмитрий Стогов на стриме отвечая на вопрос: что стоило бы убрать в следующих версиях PHP.

Планируется ли в php добавлять библиотеки для машинного обучения?


Нет. А в каком языке они есть в ядре?

Можно использовать RubixML/ML или просто специализированные инструменты.

Как вы относитесь к гегемонии Laravel? AR/статик-методы/трейты


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

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

Целесообразно ли поддерживать активно-растущий проект на php 5.6 или начинать понемногу переписывать на php 7 или 8?


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

Есть приведение типов, например int или array, можно сделать приведение типов к объекту?


Можно.

В php есть довольно старое расширение php SOAP. Будет ли оно как-то развиваться/дополняться?


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

Если вам нужен SOAP, то лучше использовать юзерленд реализации на PHP, а не расширение.

А с какой версии можно использовать или в typehint? CurlHandle|false


Объединенные типы появились в PHP 8.0.

А для чего добавили $object::class? Теперь будут споры что же использовать get_class() или ::class


Добавили для консистентности. Зачем спорить, если можно использовать только новый вариант? roll_safe.gif

Какое будущее у поддержки альтернатив сред выполнения? GraalVM как пример.


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

Будут ли когда-нибудь в PHP возможность запретить доступ к внутренним классам извне библиотеки? Чтобы библиотека предоставляла доступ только к интерфейсным классам, а внутренние использовать запрещено.


Такая идея возникала RFC Namespace visibility и даже чуть раньше была реализация подобного. Проблема в том, что хотелось бы private/internal на уровне пакетов. А у нас только неймспейсы и поэтому непонятно как это должно работать.

Пока остается использовать PHPDoc тэг @internal.

Будет ли возможность определить, какие трейты использует класс? По аналогии с class_implements


class_uses()

Применение каких новинок PHP 8 будет влиять на снижение производительности?


Почти всех :-) Хоть оно и незначительное. Кроме, разве что, проверки типов когда много используется наследования. Но в PHP 8.1 станет намного лучше благодаря inheritance cache.

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


Зависит от приложения. Вот inheritance cache для Symfony дает прирост 8% это приблизительно и есть оверхед проверки типов.

Ребята, по памяти будут улучшения? как пример двигать sbrk при застоях и больших дырках


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

Какие компании финансируют развитие php? Неужели новые версии php выпускают энтузиасты безвозмездно?


Прямо: Zend, JetBrains, Microsoft. Косвенно много других, например, MongoDB, Oracle,

Сколько человек работает над ядром пхп?


Фултайм работают трое: Никита Попов (JetBrains) и Дмитрий Стогов (Zend) над ядром, и Christoph M. Becker (Microsoft) над расширениями.

Активное участие принимают многие. Можно посмотреть по статистике контрибьюторов.

Вот облако тегов тех, кто приложил руку в PHP 8.0:

Картинка от php.watch.

Когда будет PHP 8.1.0?


Новые версии языка выходят каждый год приблизительно в ноябре-декабре. Соответственно, PHP 8.1 ожидается в конце 2021.

p.s. Подключайтесь к стриму в субботу, чтобы узнать больше о состоянии PHP в 2021 году



Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 455 (15 21 февраля 2021)

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


Медиа|Веб-разработка|CSS|JavaScript|Браузеры|Занимательное


Медиа


podcast Подкаст Веб-стандарты 269. Прощание с Edge, веб-платформа, z-index, однострочники, атака на зависимости, инди-веб, CTF
podcast Подкаст Сделайте мне красиво 55 Не ешьте фрукты, не используйте margin
podcast Фронтенд Юность (18+) #173: Make TypeScript JavaScript Again
podcast Новости 512 от CSSSR: TypeScript 4.2 RC, долой Express, дата и время на JS, Lerna 4.0.0, Mocha 8.3.0, HolyJS Moscow 2020
podcast Новости 512 от CSSSR: DevTools, лямбды на TS, интервью с Бренданом Айком, Angular 12 next, Node.js 15.9.0, Nuxt.js 2.15.0
video Отсобеседование #2: Собеседование смелого Frontend Middle разработчика

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


habr Грабли WebRTC: как мы допиливали чужой велосипед
Пишем сайт/PWA и выкатываем в прод с Github Actions
en Полное руководство по SEO для начинающих
en Фронт фронтенда и тыл фронтенда в веб-разработке
en Вам, вероятно, не нужен микро-фронтенд
en Создание Tab компонента
en Как преобразовать любой сайт/страницу в устанавливаемое прогрессивное веб-приложение (PWA)




CSS


en Как сделать прилипающий уменьшающийся хедер при прокрутке без JavaScript
en Переключатели с условиями в CSS
en Ответственное скрытие контента
en Что нужно знать о CSS-in-JS на 2021 год
en Отладка CSS Grid как профи
en Используйте CSS Clamp для создания более гибкой обертки
en Три способа создания blob-форм с помощью CSS и SVG
en Создание трехмерных миров с помощью CSS
video en Курс Tailwind CSS: From Zero to Production на YouTube

JavaScript


habr Поддержка JavaScript-приложений в долгосрочной перспективе
Управление памятью в JavaScript
en Редиректы JavaScript и SEO
en Введение в миксины в TypeScript
en Более быстрые вызовы JavaScript
en Как значительно улучшить fetch() с помощью Decorator Pattern
en Создание оглавления с активным индикатором с помощью JavaScript Intersection Observers
en 20 забавных JavaScript-мемов








Браузеры


habr Новая утечка истории браузера через favicon
В Brave обнаружен баг, из-за которого браузер оставляет данные onion-сайтов в трафике DNS
Скоро браузер Chrome для Windows и Android будет потреблять меньше оперативной памяти
Microsoft Edge ждёт обновление: появятся виджеты, поиск по вкладкам и меню расширений


Занимательное


Доля WordPress перевалила за 40% среди топ 10 млн сайтов в рейтинге Alexa
GitHub создал сервис для 3D-визуализации аккаунтов пользователей
В Китае все чаще меняют менеджеров на алгоритмы: те следят за работниками, дают им задачи и могут списать часть зарплаты
Microsoft начала принудительное удаление Flash Player из Windows 10
Туннельный синдром, близорукость, выгорание: чем болеют айтишники

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

Дайджест свежих материалов из мира фронтенда за последнюю неделю 456 (22 28 февраля 2021)

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


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


Медиа


podcast Новости 512 от CSSSR: Firefox 86, TypeScript 4.2, рендеры и мемоизация, Код Петцольда, Babel 7.13.0, 30-летие Python
podcast Подкаст Фронтенд Юность #174: Самый типизированный
video Pro Conf #88: OpenJS World 2020
video Как сделать презентацию на движке Shower: быстрый старт, шаблон, элементы и устройство темы

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


en Будущее веб-приложений это HTML-over-WebSockets
en Как веб-компоненты используются в GitHub и Salesforce
en 10 самых популярных методов взлома веба в 2020 году
en Сколько времени нужно SEO, чтобы показать результаты?
en Новости веб-платформы: Reduced Motion, CORS, WhiteHouse.gov, popups, and100vw
en Настигнет ли Deno в 2021 году NodeJS?




CSS


en Создавайте адаптивные эффекты изображения с помощью градиентов CSS и aspect-ratio
en Стилизация символов CSS с помощью Background-Clip
en Houdini: JavaScript API для расширения CSS
en Погружение в псевдоэлементы ::before и ::after
en Как имитировать прозрачность с помощью CSS Background
en Обеспечение правильного вертикального положения для крупного текста
en Погружение в тени
en Поддержка CSS-in-JS в DevTools
en Будущее CSS: анимация с прокруткой и @scroll-timeline (часть 1)
en Отладка repaint-проблем, вызванных CSS Transition
en Красота крошечных улучшений в CSS
en Как стилизовать битые изображения с помощью css
en DRY подход к цветовым темам вCSS


JavaScript


habr Мир JavaScript в 2021 году
habr Неудачный опыт миграции Electron приложения на ECMAScript модули
habr JavaScript нанобенчмарки и преждевременные тормоза
en Анонс TypeScript 4.2
en Как работать с датой в простом Javascript библиотеки не нужны
en 7 вопросов для интервью о ключевом слове this в JavaScript.
video Redux vs Mobx: плюсы, минусы, область применения






Браузеры


Релиз Firefox 86
В десктопной версии Google Chrome появилась поддержка автоматических субтитров
Яндекс.Браузер ограничит передачу данных пользователей сторонним трекерам
Компания Mozilla опровергла ложную информацию об удалении лисы с логотипа Firefox



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

Тотальный JavaScript изучаем JS с акцентом на практической составляющей

22.02.2021 16:14:10 | Автор: admin


Доброго времени суток, друзья!

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


Однако, когда дело касается практических аспектов JavaScript, информацию приходится собирать буквально по крупицам. Собственно, этим я и занимался на протяжении последних 4-5 месяцев.

Предлагаю вашему вниманию Тотальный JavaScript.

Вот что вы найдете в этом репозитории:

  • Огромное количество сниппетов (утилит, вспомогательных функций), разделенных по типам данных не могу назвать точного количества (порядка 4000 строк кода без комментариев и пробелов). Следует отметить, что не все функции являются настоящими сниппетами с точки зрения возможности их использования (как есть) в реальных приложениях, некоторые всего лишь эксперименты, демонстирующие те или иные (безграничные?) возможности языка. Коллекция все время пополняется
  • 230 практических вопросов приводится пример кода, необходимо выполнить его в уме и решить, что будет выведено в консоль. Конечно, на практике мы редко занимается чем-то подобным, ведь гораздо легче и, главное, быстрее законсолить кусок подозрительного кода. Однако, на мой взгляд, умение решать подобные задачи как нельзя лучше демонстрирует понимание основных принципов и характерных особенностей работы JavaScript. В качестве недостатка этого раздела отмечу почти полное отсутствие вопросов по классам и this. Постараюсь в ближайшем будущем его устранить
  • 68 задач разного уровня сложности подборка задач из учебника Ильи Кантора (большинство), немного адаптированных под нужды реальных приложений. Структура раздела, в основном, следует структуре учебника с небольшими лирическими отступлениями
  • Паттерны проектирования подробное описание и примеры всех паттернов, которые называет Банда Четырех в своей книге Паттерны объектно-ориентированного программирования, на JavaScript (также в разделе имеются примеры на TypeScript смотрите исходный код). При подготовке данного раздела многое позаимствовано у Refactoring Guru, за что ему (или им) огромное спасибо
  • Что за черт, JavaScript? список тонких моментов работы JavaScript. Этот раздел не слишком актуален, учитывая возможности современного JS, однако интересен тем, что позволяет узнать, каким был язык раньше, до того, как завоевал мир веб-разработки. Де факто, он остается прежним, но следование простым правилам (например, использование const или let вместо var или "===" вместо "==") позволяет решить большую часть проблем, с которыми сталкивались разработчики в прошлом

Уверен, что каждый найдет для себя что-нибудь интересное.

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

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

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

Core Web Vitals как Google решил оценивать сайты

02.03.2021 12:21:43 | Автор: admin


Всем привет!

Сегодня поговорим о важности пользовательского взаимодействия, ведь совсем скоро придется подготовить свои сайты к максимальному ускорению загрузки. Возможно, вы уже слышали про Core Web Vitals

В прошлом году Google начал масштабный пересмотр факторов ранжирования в поисковике, чтобы улучшить качество поисковой выдачи. И в ноябре команда Google анонсировала Core Web Vitals новые факторы оценки качества ресурсов, которые смогут влиять на индексацию и вступят в силу в мае 2021 года. Давайте разбираться.


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

Чем Core Web Vitals отличается от прочих факторов ранжирования?
Положительная сторона Core Web Vitals в прозрачности: это понятные, публично доступные критерии, которые можно отслеживать и улучшать с помощью специального набора инструментов. Кроме того, с момента анонсирования и до официального запуска есть много времени, чтобы уже начать работать над Core Web Vitals.

Андрей Липатцев, Web Partnerships Google

Исследования показали, что 47% пользователей ожидают загрузки страницы до 2 секунд. Согласно отчету Google, если это время увеличивается с 1 до 3 секунд, количество отказов возрастает на 32%. А при увеличении с 1 до 6 секунд на целых 106%.
Если ресурс будет отвечать пороговым значениям Core Web Vitals, покидать сайт будут на целых 24% пользователей меньше.

Core Web Vitals



Среди многих показателей ранжирования (оптимизации для мобильных устройств, безопасный просмотр, безопасность HTTPS и т.д.) Google выделил основные (core), жизненно важные для пользователя. Метрики, составляющие Core Web Vitals, со временем будут развиваться и дополняться.

Текущий набор показателей фокусируется на трех аспектах взаимодействия с пользователем:


  • Largest Contentful Paint (LCP) определяет скорость загрузки страницы и ее крупных визуальных элементов. Хороший показатель до 2,5 с.
  • First Input Delay (FID) измеряет интерактивность сайта, то есть насколько быстро он становится доступным к взаимодействию после загрузки. Желательным будет показатель до 100 мс.
  • Cumulative Layout Shift (CLS) показывает скорость визуальной стабилизации, то есть насколько быстро всё становится на свои места. Идеальным будет показатель меньше 0,1.

Давайте разберем каждый показатель подробнее для более глубокого понимания. Или можете перейти сразу к пункту Как улучшить показатели Core Web Vitals

LCP (загрузка)


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

Старые метрики, такие как load или DOMContentLoaded, не подходят, так как они всегда соответствуют тому, что пользователь видит на экране. А более новые показатели производительности, такие как First Contentful Paint (FCP), отражают только самое начало процесса загрузки.
В ходе исследований обнаружилось, что более точный способ измерить загрузку основного содержимого страницы, это посмотреть, когда был отрисован самый большой элемент.

Так появилась метрика Largest Contentful Paint (LCP), которая измеряет время рендеринга самого большого элемента на странице.


Что считается большим элементом?


  • тег img
  • элементы image внутри тега svg
  • постер в теге video
  • фоновое изображение, загруженное с помощью url() (не считая CSS градиента)
  • блочные элементы, содержащие текстовые узлы или другие дочерние элементы.

Пока рассматривается ограниченный список, чтобы упростить начальное внедрение Core Web Vitals. Дополнительные элементы (например, тег svg, video) планируют добавить в будущем по мере проведения дополнительных исследований.

Как это работает?


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

Чтобы справиться с таким изменением, браузер отрисовывает первый кадр и отправляет PerformanceEntry с параметром large-contentful-paint, который идентифицирует самый большой элемент. Но затем, после рендеринга последующих кадров, браузер будет отправлять PerformanceEntry при каждом изменении самого большого элемента.

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

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


Рис.1. Изменение самого большого элемента по мере загрузки содержимого

Как определяется размер самого большого элемента?


Размер элемента определяется в области видимости пользователя: если элемент выходит за её пределы (обрезан или имеет overflow: hidden), то эти части не учитываются.

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

Для текстовых элементов учитывается только размер их текстовых узлов.

Для всех элементов любые margin, padding или border не рассматриваются.

FID (интерактивность)


Метрика First Input Delay (FID) помогает измерить первое впечатление пользователя об интерактивности и быстродействии вашего сайта.

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

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



FID можно измерить только в реальных условиях.

Почему рассматривается именно первый ввод?


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




CLS (визуальная стабильность)


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

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


Рис.2. Пример Cumulative Layout Shift

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

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


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

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

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

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

layout shift score = impact fraction * distance fraction



Рис.3. Коэффициент воздействия

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


Рис.4. Доля расстояния

Доля расстояния это наибольшее расстояние, на которое любой нестабильный элемент переместился в кадре (по горизонтали или вертикали), деленное на размер экрана.
В приведенном примере наибольшим размером экрана является высота, а нестабильный элемент перемещается на 25%.

Коэффициент визуальной стабильности = 0.75 * 0.25 = 0.1875



Как улучшить показатели Core Web Vitals?
Если ваше приложение не дотягивает до идеальных показателей, то нужно заняться вопросом повышения скорости. Итак, что можно сделать:

  • Сжимать, оптимизировать (бандл, картинки и т.д.).
  • Включить кеширование это позволит ускорить доступ к страницам, которые пользователь посещал ранее.
  • Использовать потенциал технологии AMP.
  • Уменьшить редиректы или использовать SPA.
  • Использовать CDN это позволит распределить контент между несколькими серверами, снижая количество маршрутизаторов между конечными файлами и пользователем.
  • И т.д.


Библиотеки и инструменты


Самый простой способ измерить все Core Web Vitals использовать js-библиотеку web-vitals, которая измеряет каждую метрику в соответствии с Инструментами Google.

import {getCLS, getFID, getLCP} from 'web-vitals';function sendToAnalytics(metric) {  const body = JSON.stringify(metric);  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||      fetch('/analytics', {body, method: 'POST', keepalive: true});}getCLS(sendToAnalytics);getFID(sendToAnalytics);getLCP(sendToAnalytics);

Или можно использовать расширение Web Vitals для Chrome.



  • Lighthouse позволяет проверять интерактивность, доступность, скорость загрузки страниц сайта в лабораторных условиях. C ним можно работать через командную строку, веб-интерфейс Page Speed Insights, инструменты для разработчиков в Chrome. Используйте Lighthouse после улучшений или изменений на сайте.
  • То, что видят пользователи, доступно в базе данных CrUX общедоступном наборе реальных данных о производительности пользователя. В базе находятся порядка 8-9 миллионов страниц.
  • PageSpeed Insights агрегирует данные из Lighthouse и CrUX и отображает их в отчете.
  • В Google Search Console есть данные по Core Web Vitals, и они доступны для каждой отдельной страницы и ее динамике.
  • В Chrome Dev Tools трассируются все три показателя LCP, CLS, TBT.


Рис.5. Пример отображения показателей в PageSpeed Insights

Итог


Не забывайте периодически следить за скоростью загрузки своего приложения. Быстрая реакция на любые негативные изменения позволит минимизировать потери и вовремя внести необходимые коррективы. Core Web Vitals влияет не только на индексацию, но и главным образом на конверсию, посещаемость и в результате на прибыль. К счастью, Google предупредил заранее о запуске новых факторов ранжирования, поэтому у вас есть еще время исправить все погрешности к запуску Core Web Vitals (май 2021).

Полезные ссылки и используемые материалы:

Подробнее..

Учим HostBinding работать с Observable

26.02.2021 18:20:16 | Автор: admin

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

<button [disabled]=isLoading$ | async>

Но его нельзя применить к @HostBinding. Давным-давно это было возможно по ошибке, но это быстро исправили:

@Directive({  selector: 'button[my-button]'  host: {    '[disabled]': '(isLoading$ | async)'  }})export class MyButtonDirective {

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

Как работает асинхронный байндинг?

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

Зачем это может понадобиться?

Мы часто работаем с RxJS в Angular. Большинство наших сервисов построены на Observable-модели. Вот пара примеров, где возможность завязываться на реактивные данные в @HostBinding была бы полезна:

  • Перевод атрибутов на другой язык. Если мы хотим сделать динамическое переключение языка в приложении мы будем использовать Observable. При этом обновлять ARIA-атрибуты, title или alt для изображений довольно непросто.

  • Изменение класса или стилей. Observable-сервис может управлять размером или трансформацией через изменение стилей хоста. Или, например, мы можем использовать реактивный IntersectionObserver для применения класса к sticky-шапке в таблице:

  • Изменение полей и атрибутов. Иногда мы хотим завязаться на BreakpointObserver для обновления placeholder или на сервис загрузки данных для выставления disable на кнопке.

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

В Taiga UI библиотеке, над которой я работаю, есть несколько инструментов, чтобы сделать этот процесс максимально декларативным:

import {TuiDestroyService, watch} from '@taiga-ui/cdk';import {Language, TUI_LANGUAGE} from '@taiga-ui/i18n';import {Observable} from 'rxjs';import {map, takeUntil} from 'rxjs/operators';@Component({   selector: 'my-comp',   template: '',   providers: [TuiDestroyService],})export class MyComponent {   @HostBinding('attr.aria-label')   label = '';   constructor(       @Inject(TUI_LANGUAGE) language$: Observable<Language>,       @Inject(TuiDestroyService) destroy$: Observable<void>,       @Inject(ChangeDetectorRef) changeDetectorRef: ChangeDetectorRef,   ) {       language$.pipe(           map(getTranslation('label')),           watch(changeDetectorRef),           takeUntil(destroy$),       ).subscribe();   }}

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

@HostBinding('attr.aria-label')readonly label$ = this.translations.get$('label');

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

Event-плагины спешат на помощь!

Мы не можем добавить свою логику к байндингу на хост. Но мы можем сделать это для @HostListener! Я уже писал статью на эту тему. Прочитайте ее, если хотите узнать, как добавить декларативные preventDefault/stopPropagation и оптимизировать циклы проверки изменений. Если кратко Angular позволяет добавлять свои сервисы для обработки событий. Подходящий сервис выбирается с помощью имени события. Давайте перепишем код следующим образом:

@HostBinding('$.aria-label.attr')@HostListener('$.aria-label.attr')readonly label$ = this.translations.get$('label');

Выглядит странно пытаться решить задачу @HostBinding через @HostListener но читайте дальше и вы всё увидите.

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

У плагинов для обработки событий есть доступ к элементу, имени события и функции-обработчику. Последний аргумент для нас бесполезен, так как это обертка, созданная компилятором. Так что нам нужно как-то передать наш Observable через элемент. Вот тут-то нам и пригодится @HostBinding. Мы положим Observable в поле с тем же именем, и тогда у нас будет доступ к нему внутри плагина:

addEventListener(element: HTMLElement, event: string): Function {   element[event] = EMPTY;   const method = this.getMethod(element, event);   const sub = this.manager       .getZone()       .onStable.pipe(           take(1),           switchMap(() => element[event]),       )       .subscribe(method);   return () => sub.unsubscribe();}

Компилятор Angular

Посмотрим на этот код повнимательнее. Первая строка может вас смутить. Хоть мы и можем назначать произвольные поля на элементы, Angular попытается их провалидировать:

Возможно, вы видели такое раньшеВозможно, вы видели такое раньше

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

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

А отписку за нас сделает сам Angular достаточно вернуть функцию, прерывающую стрим.

Этого хватит, чтобы код заработал в JIT, AOT же более щепетилен. Мы добавили несуществующее поле во время выполнения, но AOT желает знать про него на этапе компиляции. До тех пор, пока эта задача не будет закрыта, мы не можем создавать свои списки разрешенных полей. Поэтому нам придется добавить NO_ERRORS_SCHEMA в модуль с подобным байндингом. Это может звучать страшно, но все, что эта схема делает, перестает проверять, есть ли поле у элемента при байндинге. Кроме того, если у вас WebStorm, вы продолжите видеть предупреждение:

Это сообщение не мешает сборкеЭто сообщение не мешает сборке

Также AOT требует реализации Callable-интерфейса для использования @HostListener. Мы можем имитировать его с помощью простой функции, сохранив оригинальный тип:

function asCallable<T>(a: T): T & Function {    return a as any;}

Итоговая запись:

@HostBinding('$.aria-label.attr')@HostListener('$.aria-label.attr')readonly label$ = asCallable(this.translations.get$('label'));

Другой вариант вовсе отказаться от @HostBinding ведь нам надо назначить его лишь один раз. Если ваш стрим приходит из DI, что происходит довольно часто, можно создать FactoryProvider. В него можно передать ElementRef и назначить поле в нем:

export const TOKEN = new InjectionToken<Observable<boolean>>("");export const PROVIDER = {  provide: TOKEN,  deps: [ElementRef, IntersectionObserverService],  useFactory: factory,}export function factory(  { nativeElement }: ElementRef,  entries$: Observable<IntersectionObserverEntry[]>): Observable<boolean> {  return nativeElement["$.class.stuck"] = entries$.pipe(map(isIntersecting));}

Теперь достаточно будет оставить только @HostListener. Его даже можно написать прямо в декораторе класса:

@Directive({  selector: "table[sticky]",  providers: [    IntersectionObserverService,    PROVIDER,  ],  host: {    "($.class.stuck)": "stuck$"  }})export class StickyDirective {  constructor(@Inject(TOKEN) readonly stuck$: Observable<boolean>) {}}

Приведенный выше пример можно увидеть вживую на StackBlitz. В нем IntersectionObserver используется для задания тени на sticky-шапке таблицы:

Обновление полей

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

private getMethod(element: HTMLElement, event: string): Function {   const [, key, value, unit = ''] = event.split('.');   if (event.endsWith('.attr')) {       return v => element.setAttribute(key, String(v));   }   if (key === 'class') {       return v => element.classList.toggle(value, !!v);   }   if (key === 'style') {       return v => element.style.setProperty(value, `${v}${unit}`);   }   return v => (element[key] = v);}

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

{    provide: EVENT_MANAGER_PLUGINS,    useClass: BindEventPlugin,    multi: true,}

Это небольшое дополнение способно существенно упростить ваш код. Нам больше не надо беспокоиться о подписке. Описанный плагин доступен в новой версии 2.1.1 нашей библиотеки @tinkoff/ng-event-plugins, а также в @taiga-ui/cdk. Поиграться с кодом можно на StackBlitz. Надеюсь, этот материал будет для вас полезным!

Подробнее..

Чем хорош сайт на Тильде? И почему не надо лезть в дорогостоящие решения

03.03.2021 20:06:54 | Автор: admin

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

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

Если говорить коротко - это конструктор сайтов, который приобрел большую популярность в последние годы на территории России и стран СНГ в частности. Конечно, основной офер компании заключается в том, что любой новичок никогда до этого не имеющий опыта в web- разработке и в целом digital, сможет сделать для себя или своего небольшого начинания посадочную страницу. Казалось бы, причем тут вообще могут быть агентства или студии? Давайте разбираться.

Все опять исходит из профессионализма конкретных участников разработки, будь то дизайнер или маркетолог. В любом деле необходим опыт и сноровка, чтобы хорошо делать свое дело. Человек, который ни разу не работал со смыслами, не знает, что такое прототип и в целом не имеет представления, что такое дизайн, и из чего он состоит - не сможет справиться с поставленной задачей. Да, конечно Tilda предоставляет в своем ассортименте решения в виде готовых блоков из которых можно собрать небольшой MVP (минимальный жизнеспособный продукт), но вряд ли такой продукт сможет потягаться со средними сайтами в той нише для которой этот сайт предназначается. Вот именно в таких случаях на помощь приходят команды со своей экспертизой в решениях данных задач. Казалось бы, за что некоторые команды берут по 200.000 за разработку на условной бесплатной платформе?

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

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

Если ваша задача начинается со слов:
Чтобы продавать или Нужна презентация - вам не нужен код.

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

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

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

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

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

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

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

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

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

Рассмотрим параллельно 2 ситуации, которые могут показаться разными, но по факту объединены одним и тем же.
1. Заказчик не имеет серьезного бюджет, но ему срочно необходим небольшой сайт для мероприятия, которое стартует уже в конце недели.
2. Заказчик имеет серьезный бюджет, но у него отсутствуют амбициозные задачи и в целом планы на будущий сайт. Сроки не превышают 14 дней, но для простоты понимания давайте приведем также к 7 дням.

Задача
По факту перед нами стоит задача, как сделать симпатичный MVP-проект, в срок не превышающий 7 дней.

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

День 1. Подбор референсов и обсуждение проекта
Для экономии времени и ресурсов приступаем к аналитике, но акцентируем внимание только на самых важных моментах, а именно:

  • какова будет общая концепция продукта;

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

  • Что нравится целевой аудитории;

  • что из референсов может лучше всего подойти.

Конец дня ознаменовывается обсуждением выбранных решений с заказчиками. На каждый из этапом тратим примерно по 2 часа.

День 2. Прототипирование
Так как у нас выделен только 1 день на прототип - прибегаем к быстрому решению при помощи Figma. В рамках этого сервиса есть уже готовые ui киты, другими словами блоки, которые можно компоновать в дальнейшем как угодно. Опираемся на те примеры сайтов, которые утвердили с заказчиками на предыдущем этапе и на основе их логики / структуры - переносим все это на наш прототип в Figma. В завершении идем презентовать и защищать структуру перед заказчиками.

День 3-4. Дизайн
На данном этапе делаем акцент, выделяя под него 2 суток. Так как он является самым основным в рамках работы на Тильде. Определяемся с 3 наиболее интересными и продуманными работами в данной нише, предварительно все это согласовав с заказчиками. Первый день занимает подбор и поиск будущих элементов сайта, а именно:

  • Иконки

  • Изображения

  • иные визуальные элементы, которые нельзя отнести к чему-то конкретному, но от этого они не становятся менее важными

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

День 5. Верстка
Верстка на Тильде представляет из себя работу в zero-blockах, что значительно упрощает нашу задачу. Другими словами, если вам когда-то приходилось сталкиваться с Powerpoint/Photoshop и тд то вы без труда сможете представить сложность при работе с данным инструментом. Как правило все завязано на интуитивно понятном интерфейсе и функционале. В целом вся верстка - это своеобразный конструктор где единственное, что остается делать это переносить элементы с прототипа и двигать их в соответствии с дизайном. Но не стоит забывать, что некоторый пулл-задач не получится решить при помощи zero-blockов, что отсылает нас обратиться за помощью к верстальщику для добавления сложного элемента на сайт. Как правило такие задачи составляют менее 5% от общего числа.

День 6. Подключение домена
Одним из заключительных этапов - подключение домена. Долго не раздумывая, идем на любой из популярных хостингов-провайдеров (reg.ru,Timeweb.comи др.) Указываем DNS сервера Тильды, обновляем всю информацию и жмем подключить домен. Весь этот процесс заканчивается проставлением галочек и индексированием на новый домен. По сути основная работа на этом заканчивается. 6 и 7 день можно было бы объединить в один, но зачастую приходится долго ждать обратной связи от провайдеров, срок ожидания которых может составлять до 1 дня.

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

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

  • как добавлять контент;

  • Как редактировать текст и менять изображения;

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

Где они уже самостоятельно сможет все настроить, повторяя шаги из видео.

Вывод
Тильда - это отличный инструмент для вашего бизнеса если вы:

  • только начинаете;

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

  • когда все, что требуется от сайта - это продавать или демонстрировать портфолио.

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

Подробнее..

Я никогда не научусь верстать и другие мифы о разработке

24.02.2021 14:21:40 | Автор: admin
За 15 лет я успел забыть, что и так можно былоЗа 15 лет я успел забыть, что и так можно было

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

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

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

Миф 1. Веб-разработка не для меня

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

Слушай, это легко. Пиши весь код в TypeScript. Все модули, использующие Fetch компилируй в ES6, транспайль их с Babel с stage-3, и загружай с SystemJS. Если у тебя нет Fetch, используй polyfill, или Bluebird, Request или Axios, и обрабатывай промисы с await.

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

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

Миф 2. Страшно сделать ошибку

Негативный опыт хороший учитель.

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

Миф #24149. Картинки из xkcd делают статью о программировании лучшеМиф #24149. Картинки из xkcd делают статью о программировании лучше

Что делать. Разрешите себе пробовать снова и снова. Что самое страшное может случиться? Ну, зависнет страничка, и придётся перезагрузить компьютер. В крайнем случае переустановите свою Windows 98. Это не сложно, зато можно продолжать попытки.

Миф 3. Ошибка конец света

В начале кажется, что если в коде ошибка, то сломано вообще всё. Обычно это не так. Я хотел бы посчитать, сколько времени потратило человечество на исправление ошибок в один символ в каком-нибудь PHP. Или хотя бы я сам, когда писал сортировки массивов на Паскале. Хотел бы, но не могу.

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

Миф 4. Сложно сделать первый проект

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

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

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

Миф 5. Код можно никому не показывать

Этот миф распространён среди тех, кто представляет себе программистов в виде злобных капюшонистых хакеров в тёмном подвале.

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

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

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

Чтобы было проще, объединяйтесь с другими новичками. Придумайте общий проект и пилите его вместе (а то мы джва года ждём). Вы научитесь командной работе, будете поддерживать друг друга, спорить, обмениваться мнениями. Вместе расти проще, чем в одиночку. А ещё, если не лень, можно скинуться на ревьюера кода, чтобы было выгоднее.

Все мы знаем, зачем нужен GitВсе мы знаем, зачем нужен Git

Миф 6. После курсов платят по 200 тысяч

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

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

Миф 7. Невозможно научиться самостоятельно

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

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

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


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

1 марта в HTML Academy начинается очередной марафон по вёрстке. За три недели вы разберётесь в основах HTML и CSS, сверстаете сайт по макету и выложите его в интернет. В программе 28 тренажёров, которые обычно платные, но для участников марафона будут доступны бесплатно. Ещё разыграем одно место на курсе HTML и CSS. Профессиональная вёрстка сайтов.

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

Подробнее..

Создаем веб-приложение на Haskell с использованием Reflex. Часть 1

24.02.2021 20:16:16 | Автор: admin

Введение


Всем привет! Меня зовут Никита, и мы в Typeable для разработки фронтенда для части проектов используем FRP-подход, а конкретно его реализацию на Haskell веб-фреймоворк reflex. На русскоязычных ресурсах отсутствуют какие-либо руководства по данному фреймворку (да и в англоязычном интернете их не так много), и мы решили это немного исправить.


В этой серии статей будет рассмотрено создание веб-приложения на Haskell с использованием платформы reflex-platform. reflex-platform предоставляет пакеты reflex и reflex-dom. Пакет reflex является реализацией Functional reactive programming (FRP) на языке Haskell. В библиотеке reflex-dom содержится большое число функций, классов и типов для работы с DOM. Эти пакеты разделены, т.к. FRP-подход можно использовать не только в веб-разработке. Разрабатывать мы будем приложение Todo List, которое позволяет выполнять различные манипуляции со списком задач.



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

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


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

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


  • Dynamic a является объединением Behavior a и Event a, т.е. это контейнер, который всегда содержит в себе некоторое значение, и, подобно событию, он умеет уведомлять о своем изменении, в отличие от Behavior a.

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


Подготовка


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


Чтобы ускорить процесс сборки, имеет смысл настроить кэш nix. В случае, если вы не используете NixOS, то вам нужно добавить следующие строки в файл /etc/nix/nix.conf:


binary-caches = https://cache.nixos.org https://nixcache.reflex-frp.orgbinary-cache-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=binary-caches-parallel-connections = 40

Если используете NixOS, то в файл /etc/nixos/configuration.nix:


nix.binaryCaches = [ "https://nixcache.reflex-frp.org" ];nix.binaryCachePublicKeys = [ "ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=" ];

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


  • todo-client клиентская часть;
  • todo-server серверная часть;
  • todo-common содержит общие модули, которые используются сервером и клиентом (например типы API).

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


  • Создать директорию приложения: todo-app;
  • Создать проекты todo-common (library), todo-server (executable), todo-client (executable) в todo-app;
  • Настроить сборку через nix (файл default.nix в директории todo-app);
    • Также надо не забыть включить опцию useWarp = true;;
  • Настроить сборку через cabal (файлы cabal.project и cabal-ghcjs.project).

На момент публикации статьи default.nix будет выглядеть примерно следующим образом:


{ reflex-platform ? ((import <nixpkgs> {}).fetchFromGitHub {    owner = "reflex-frp";    repo = "reflex-platform";    rev = "efc6d923c633207d18bd4d8cae3e20110a377864";    sha256 = "121rmnkx8nwiy96ipfyyv6vrgysv0zpr2br46y70zf4d0y1h1lz5";    })}:(import reflex-platform {}).project ({ pkgs, ... }:{  useWarp = true;  packages = {    todo-common = ./todo-common;    todo-server = ./todo-server;    todo-client = ./todo-client;  };  shells = {    ghc = ["todo-common" "todo-server" "todo-client"];    ghcjs = ["todo-common" "todo-client"];  };})

Примечание: в документации предлагается вручную склонировать репозиторий reflex-platform. В данном примере мы воспользовались средствами nix для получения платформы из репозитория.

Во время разработки клиента удобно пользоваться инструментом ghcid. Он автоматически обновляет и перезапускает приложение при изменении исходников.


Чтобы убедиться, что все работает, добавим в todo-client/src/Main.hs следующий код:


{-# LANGUAGE OverloadedStrings #-}module Main whereimport Reflex.Dommain :: IO ()main = mainWidget $ el "h1" $ text "Hello, reflex!"

Вся разработка ведется из nix-shell, поэтому в самом начале необходимо войти в этот shell:


$ nix-shell . -A shells.ghc

Для запуска через ghcid требуется ввести следующую команду:


$ ghcid --command 'cabal new-repl todo-client' --test 'Main.main'

Если все работает, то по адресу localhost:3003 вы увидите приветствие Hello, reflex!



Почему 3003?


Номер порта ищется в переменной окружения JSADDLE_WARP_PORT. Если эта переменная не установлена, то по умолчанию берется значение 3003.


Как это работает


Вы можете заметить, мы использовали при сборке не GHCJS, а обычный GHC. Это возможно благодаря пакетам jsaddle и jsaddle-warp. Пакет jsaddle предоставляет интерфейс для JS для работы из-под GHC и GHCJS. С помощью пакета jsaddle-warp мы можем запустить сервер, который посредством веб-сокетов будет обновлять DOM и играть роль JS-движка. Как раз для этого и был установлен флаг useWarp = true;, иначе по умолчанию использовался бы пакет jsaddle-webkit2gtk, и при запуске мы бы увидели десктопное приложение. Стоит отметить, что еще существуют прослойки jsaddle-wkwebview (для iOS приложений) и jsaddle-clib (для Android приложений).


Простейшее приложение TODO


Приступим к разработке!


Добавим следующий код в todo-client/src/Main.hs.


{-# LANGUAGE MonoLocalBinds #-}{-# LANGUAGE OverloadedStrings #-}module Main whereimport Reflex.Dommain :: IO ()main = mainWidgetWithHead headWidget rootWidgetheadWidget :: MonadWidget t m => m ()headWidget = blankrootWidget :: MonadWidget t m => m ()rootWidget = blank

Можно сказать, что функция mainWidgetWithHead представляет собой элемент <html> страницы. Она принимает два параметра head и body. Существуют еще функции mainWidget и mainWidgetWithCss. Первая функция принимает только виджет с элементом body. Вторая первым аргументом принимает стили, добавляемые в элемент style, и вторым аргументом элемент body.


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

Функция blank равносильна pure () и она ничего не делает, никак не изменяет DOM и никак не влияет на сеть событий.


Теперь опишем элемент <head> нашей страницы.


headWidget :: MonadWidget t m => m ()headWidget = do  elAttr "meta" ("charset" =: "utf-8") blank  elAttr "meta"    (  "name" =: "viewport"    <> "content" =: "width=device-width, initial-scale=1, shrink-to-fit=no" )    blank  elAttr "link"    (  "rel" =: "stylesheet"    <> "href" =: "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"    <> "integrity" =: "sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"    <> "crossorigin" =: "anonymous")    blank  el "title" $ text "TODO App"

Данная функция сгенерирует следующее содержимое элемента head:


<meta charset="utf-8"><meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"><link crossorigin="anonymous" href="http://personeltest.ru/aways/stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"  integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" rel="stylesheet"><title>TODO App</title>

Класс MonadWidget позволяет строить или перестраивать DOM, а также определять сеть событий, которые происходят на странице.


Функция elAttr имеет следующий тип:


elAttr :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m a

Она принимает название тэга, атрибуты и содержимое элемента. Возвращает эта функция, и вообще весь набор функций, строящих DOM, то, что возвращает их внутренний виджет. В данном случае наши элементы пустые, поэтому используется blank. Это одно из наиболее частых применений этой функции когда требуется сделать тело элемента пустым. Так же используется функция el. Ее входными параметрами являются только название тэга и содержимое, другими словами это упрощенная версия функции elAttr без атрибутов. Другая функция, используемая здесь text. Ее задача вывод текста на странице. Эта функция экранирует все возможные служебные символы, слова и тэги, и поэтому именно тот текст, который передан в нее, будет выведен. Для того чтобы встроить кусок html, существует функция elDynHtml.


Надо сказать, что в приведенном выше примере использование MonadWidget является избыточным, т.к. эта часть строит неизменяемый участок DOM. А, как было сказано выше, MonadWidget позволяет строить или перестраивать DOM, а также позволяет определять сеть событий. Функции, которые используются здесь, требуют только наличие класса DomBuilder, и тут, действительно, мы могли написать только это ограничение. Но в общем случае, ограничений на монаду гораздо больше, что затрудняет и замедляет разработку, если мы будем прописывать только те классы, которые нам нужны сейчас. Поэтому существует класс MonadWidget, которые представляет собой эдакий швейцарский нож. Для любопытных приведём список всех классов, которые являются надклассами MonadWidget:


type MonadWidgetConstraints t m =  ( DomBuilder t m  , DomBuilderSpace m ~ GhcjsDomSpace  , MonadFix m  , MonadHold t m  , MonadSample t (Performable m)  , MonadReflexCreateTrigger t m  , PostBuild t m  , PerformEvent t m  , MonadIO m  , MonadIO (Performable m)#ifndef ghcjs_HOST_OS  , DOM.MonadJSM m  , DOM.MonadJSM (Performable m)#endif  , TriggerEvent t m  , HasJSContext m  , HasJSContext (Performable m)  , HasDocument m  , MonadRef m  , Ref m ~ Ref IO  , MonadRef (Performable m)  , Ref (Performable m) ~ Ref IO  )class MonadWidgetConstraints t m => MonadWidget t m

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


newtype Todo = Todo  { todoText :: Text }newTodo :: Text -> TodonewTodo todoText = Todo {..}

Тело будет иметь следующую структуру:


rootWidget :: MonadWidget t m => m ()rootWidget =  divClass "container" $ do    elClass "h2" "text-center mt-3" $ text "Todos"    newTodoEv <- newTodoForm    todosDyn <- foldDyn (:) [] newTodoEv    delimiter    todoListWidget todosDyn

Функция elClass на вход принимает название тэга, класс (классы) и содержимое. divClass это сокращенная версия elClass "div".


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


foldDyn :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> b) -> b -> Event t a -> m (Dynamic t b)

Она похожа на foldr :: (a -> b -> b) -> b -> [a] -> b и, по сути, выполняет такую же роль, только в роли списка здесь событие. Результирующее значение обернуто в контейнер Dynamic, т.к. оно будет обновляться после каждого события. Процесс обновления задаётся функцией-параметром, которая принимает на вход значение из возникшего события и текущее значение из Dynamic. На их основе формируется новое значение, которое будет находиться в Dynamic. Это обновление будет происходить каждый раз при возникновении события.


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


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


newTodoForm :: MonadWidget t m => m (Event t Todo)newTodoForm = rowWrapper $  el "form" $    divClass "input-group" $ do      iEl <- inputElement $ def        & initialAttributes .~          (  "type" =: "text"          <> "class" =: "form-control"          <> "placeholder" =: "Todo" )      let        newTodoDyn = newTodo <$> value iEl        btnAttr = "class" =: "btn btn-outline-secondary"          <> "type" =: "button"      (btnEl, _) <- divClass "input-group-append" $        elAttr' "button" btnAttr $ text "Add new entry"      pure $ tagPromptlyDyn newTodoDyn $ domEvent Click btnEl

Первое нововведение, которое мы встречаем тут, это функция inputElement. Ее название говорит само за себя, она добавляет элемент input. В качестве параметра она принимает тип InputElementConfig. Он имеет много полей, наследует несколько различный классов, но в данном примере нам наиболее интересно добавить нужные атрибуты этому тегу, и это можно сделать при помощи линзы initialAttributes. Функция value является методом класса HasValue и возвращает значение, которое находится в данном input. В случае типа InputElement оно имеет тип Dynamic t Text. Это значение будет обновляться при каждом изменении, происходящем в поле input.


Следующее изменение, которое тут можно заметить, это использование функции elAttr'. Отличие функций со штрихом от функций без штриха для построения DOM заключается в том, что эти функции вдобавок возвращают сам элемент страницы, с которым мы можем производить различные манипуляции. В нашем случае он необходим, чтобы мы могли получить событие нажатия на этот элемент. Для этого служит функция domEvent. Эта функция принимает название события, в нашем случае Click и сам элемент, с которым связано это событие. Функция имеет следующую сигнатуру:


domEvent :: EventName eventName -> target -> Event t (DomEventType target eventName)

Ее возвращаемый тип зависит от типа события и типа элемента. В нашем случае это ().


Следующая функция, которую мы встречаем tagPromptlyDyn. Она имеет следующий тип:


tagPromptlyDyn :: Reflex t => Dynamic t a -> Event t b -> Event t a

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


Тут следует сказать про то, что функции, которые содержат в своём названии слово promptly, потенциально опасные они могут вызывать циклы в сети событий. Внешне это будет выглядеть так, как будто приложение зависло. Вызов tagPromplyDyn valDyn btnEv, по возможности, надо заменять на tag (current valDyn) btnEv. Функция current получает Behavior из Dynamic. Эти вызовы не всегда взаимозаменяемые. Если обновление Dynamic и событие Event в tagPromplyDyn возникают в один момент, т.е. в одном фрейме, то выходное событие будет содержать те данные, которые получил Dynamic в этом фрейме. В случае, если мы будем использовать tag (current valDyn) btnEv, то выходное событие будет содержать те данные, которыми исходный current valDyn, т.е. Behavior, обладал в прошлом фрейме.


Здесь мы подошли к еще одному различию между Behavior и Dynamic: если Behavior и Dynamic получают обновление в одном фрейме, то Dynamic будет обновлен уже в этом фрейме, а Behavior приобретет новое значение в следующем. Другими словами, если событие произошло в момент времени t1 и в момент времени t2, то Dynamic будет обладать значением, которое принесло событие t1 в промежутке времени [t1, t2), а Behavior (t1, t2].


Задача функции todoListWidget заключается в выводе всего списка Todo.


todoListWidget :: MonadWidget t m => Dynamic t [Todo] -> m ()todoListWidget todosDyn = rowWrapper $  void $ simpleList todosDyn todoWidget

Здесь встречается функция simpleList. Она имеет следующую сигнатуру:


simpleList  :: (Adjustable t m, MonadHold t m, PostBuild t m, MonadFix m)  => Dynamic t [v]  -> (Dynamic t v -> m a)  -> m (Dynamic t [a])

Эта функция является частью пакета reflex, и в нашем случае она используется для построения повторяющихся элементов в DOM, где это будут подряд идущие элементы div. Она принимает Dynamic список, который может изменяться во времени, и функцию для обработки каждого элемента в отдельности. В данном случае это просто виджет для вывода одного элемента списка:


todoWidget :: MonadWidget t m => Dynamic t Todo -> m ()todoWidget todoDyn =  divClass "d-flex border-bottom" $    divClass "p-2 flex-grow-1 my-auto" $      dynText $ todoText <$> todoDyn

Функция dynText отличается от функции text тем, что на вход принимает текст, обернутый в Dynamic. В случае, если элемент списка будет изменен, то это значение также обновится в DOM.


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


rowWrapper :: MonadWidget t m => m a -> m arowWrapper ma =  divClass "row justify-content-md-center" $    divClass "col-6" ma

Функция delimiter просто добавляет элемент-разделитель.


delimiter :: MonadWidget t m => m ()delimiter = rowWrapper $  divClass "border-top mt-3" blank


Полученный результат можно посмотреть в нашем репозитории.


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

Подробнее..

Run, config, run как мы ускорили деплой конфигов в Badoo

25.02.2021 18:11:56 | Автор: admin

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

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

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

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


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

Случай из жизни

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

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

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

Для отключения сервисов мы используем свою систему с говорящим названием Выключалка хостов (disable hosts). Принцип её работы довольно прост:

  • выбираем в веб-интерфейсе сервисы, которые нужно отключить (или, наоборот, включить);

  • нажимаем на кнопку Deploy;

  • изменения сохраняются в базе данных и затем доставляются на все машины, на которых выполняется PHP-код.

В коде при подключении к сервису стоит проверка вида:

if (\DownChecker\Host::isDisabled($host)) {   $this->errcode = self::ERROR_CONNECT_FAILED;   return false;}

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

Процесс деплоя разбит на несколько шагов:

  • упаковываем конфиги в tar-архив;

  • копируем архив на сервер через rsync или scp;

  • распаковываем архив в отдельную директорию;

  • переключаем симлинк на новую директорию.

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

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

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

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

В поисках альтернативного транспорта

Тут может возникнуть резонный вопрос: зачем изобретать велосипед, если можно использовать классическую схему с базой данных (БД) и кешированием?

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

  • использование БД и кеша это дополнительная завязка на внешний сервис, а значит, ещё одна потенциальная точка отказа;

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

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

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

Но в этой схеме нам не понравилась идея с запросами в цикле. У нас около 2000 серверов, на которых может выполняться PHP-код. Если мы будем делать запрос в базу/кеш хотя бы один раз в секунду, то это будет создавать довольно большой фон запросов (2k rps), а сами данные при этом обновляются не так часто. На этот случай есть решение событийная модель, например шаблон проектирования Publisher-Subscriber (PubSub). Из популярных решений можно было использовать Redis, но у нас он не прижился (для кеширования мы используем Memcache, а для очередей у нас есть своя отдельная система).

Зато прижился Consul, у которого есть механизм отслеживания изменений (watches) на базе блокирующих запросов. Это в целом похоже на PubSub и вписывается в нашу схему с обновлением конфига по событию. Мы решили сделать прототип нового транспорта на базе Consul, который со временем эволюционировал в отдельную систему под названием AutoConfig.

Как работает AutoConfig

Общая схема работы выглядит следующим образом:

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

  • в облаке работает скрипт, который исключает пачку ключей из очереди и записывает их в Consul; помимо самих ключей, в Consul хранится специальная индексная карта со списком всех ключей (о ней я расскажу чуть позже);

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

  • обработчик записывает изменения в файл.

Со стороны кода работа с системой выглядит довольно просто:

Обновление ключа

$record = \AutoConfig\AutoConfigRecord::initByKeyData('myKey', 'Hello, Habr!', 'Eugene Tupikov');$storage = new \AutoConfig\AutoConfigStorage();$storage->deployRecord($record);

Чтение ключа

$reader = new \AutoConfig\AutoConfigReader();$config = $reader->getFromCurrentSpace('myKey');

Удаление ключа

$storage = new \AutoConfig\AutoConfigStorage();$storage->removeKey('example');

Подробнее про Consul watch

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

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

curl -X PUT --data 'hello, harb!' http://127.0.0.1:8500/v1/kv/habr-key

Затем отправляем запрос на чтение нашего ключа

curl -v http://127.0.0.1:8500/v1/kv/habr-key

API обрабатывает запрос и возвращает ответ, содержащий HTTP-заголовок X-Consul-Index с уникальным идентификатором, который соответствует текущему состоянию нашего ключа.

......< X-Consul-Index: 266834870< X-Consul-Knownleader: true......<[  {    "LockIndex": 0,    "Key": "habr-key",    "Flags": 0,    "Value": "dXBkYXRlZCAy",    "CreateIndex": 266833109,    "ModifyIndex": 266834870  }]

Мы отправляем новый запрос на чтение и дополнительно передаем значение из заголовка X-Consul-Index в параметре запроса index

curl http://127.0.0.1:8500/v1/kv/habr-key?index=266834870Ждем изменений ключа...

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

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

curl -X PUT --data 'updated value' http://127.0.0.1:8500/v1/kv/habr-key

Возвращаемся на первую вкладку и видим, что запрос на чтение вернул обновленное значение (изменились ключи Value и ModifyIndex):

[  {    "LockIndex": 0,    "Key": "habr-key",    "Flags": 0,    "Value": "dXBkYXRlZA==",    "CreateIndex": 266833109,    "ModifyIndex": 266835734  }]

При вызове команды

consul watch -type=key -key=habr_key <handler>

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

Зачем нужна индексная карта

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

consul watch -type=keyprefix -prefix=auto_config/ <handler>

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

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

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

Она имеет следующий формат:

return [    'value' => [        'version' => 437036,        'keys' => [            'my/awesome/key' => '80003ff43027c2cc5862385fdf608a45',            ...            ...        ],        'created_at' => 1612687434    ]]

В случае если карта обновилась, обработчик:

  • считывает текущее состояние карты с диска;

  • находит изменившиеся ключи (для этого и нужен хеш значения);

  • вычитывает через HTTP API актуальные значения изменившихся ключей и обновляет нужные файлы на диске;

  • сохраняет новую индексную карту на диск.

И ещё немного про Consul

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

Ограничение размера значения ключа (и не только)

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

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

Чтобы обойти эти ограничения, мы сделали следующее:

  • разбили индексную карту на несколько частей (по 1000 ключей в каждой) и обновляем только части, содержащие изменённые ключи;

  • ограничили максимальный размер одного ключа AutoConfig 450 Кб, чтобы оставить место для шардов индексной карты (значение выбрано опытным путём);

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

    • сначала вычитывает N ключей из очереди и проверяет их суммарный размер;

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

Отсутствие встроенной репликации

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

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

В качестве заключения

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

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

За последние пару лет система стала популярна у наших разработчиков и сегодня фактически является стандартом при работе с конфигами, для которых не требуется атомарная раскладка. Например, через AutoConfig у нас деплоятся параметры A/B-тестов, настройки промокампаний, на его базе реализован функционал Service Discovery и многое другое.

Общее количество ключей на данный момент порядка 16 000, а их суммарный размер примерно 120 Мб.

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

Расскажите в комментариях, как вы деплоите конфиги в своих проектах.

Подробнее..

Перевод Подготовка экосистем Dart и Flutter к переходу на null safety

26.02.2021 18:20:16 | Автор: admin

Поезд null safety мчится вперёд, уже почти официально анонсирован Flutter 2.0 (подключайтесь к предстоящему Flutter Engage), экосистема Dart тоже не стоит на месте. Мы перевели на русский язык новость из официального блога Dartlang и настоятельно рекомендуем вам переводить свои пакеты на новые рельсы, если вы этого ещё не сделали!

Вышел стабильный API для null safety

На днях вышла новая бета версия Dart, которая отличается повышенной стабильностью и наличием надежной null safety системы, над которой мы работали больше года. Обновленная бета (2.12.0259.9.beta) доступна на dart.dev и на бета канале Flutter. До выхода стабильной версии null-safe Dart критических изменений больше не предвидится.

Мы призываем разработчиков публиковать null-safe версии своих пакетов, чтобы пользователи смогли получить полноценную функциональность экосистемы на момент публикации стабильной версии null-safe Dart. Сами мы этот процесс уже запустили опубликовали стабильные версии null-safe пакетов, таких как args, yaml и grpc. Если все ваши зависимости в null-safe состоянии и опубликованы под стабильной версией (например, 1.0.0 вместо 1.0.0-nullsafety.123), вам пора заняться тем же!

На pub.dev мы также добавили новую фичу, которая сама размечает версии пакетов, помечая preview-релизы, если стабильная версия зависимого Dart SDK еще не вышла. Preview-релизы будут автоматически помечены как стабильные версии, как только состоится релиз стабильного Dart SDK.

Cтабильная (1.6.0) и preview (2.0.0) версии пакета args на pub.devCтабильная (1.6.0) и preview (2.0.0) версии пакета args на pub.dev

В руководстве по переходу на null safety есть вся последняя информация о том, как организовать миграцию ваших пакетов. Обратите особое внимание на ограничения Dart SDK и версии ваших зависимостей в pubspec. В том числе обратите внимание и на версию SDK, который вы используете для тестирования непрерывной интеграции (CI). Стабильная null-safe версия Dart выйдет уже скоро! Спасибо вам за поддержку!

Подробнее..

80 докладов и статей, которые запомнились PHP-сообществу в 2020 году

27.02.2021 12:14:40 | Автор: admin

Недавно мы решили узнать, чем помимо пандемии 2020-й запомнится пхп-разработчикам кто что читал, смотрел, над чем работал. Получив свыше 1500 ответов, мы сели их разбирать. Вот что получилось.

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

Как мы получили данные и кто давал ответы

В конце декабря 2020 мы сверстали форму из 14 вопросов: часть подразумевала свободный ответ, часть выбор из вариантов (подробнее тут). Ссылку на форму мы раскидали по чатам и каналам для PHP-разработчиков в телеграме, а также сделали анонсы в твиттере, на Хабре и запустили небольшую таргетированную компанию в ВК. До конца января 2021 мы принимали анонимные ответы.

1506 респондентов заполнили опросник целиком или частично.

44% определили себя как мидлы

24% как сеньоры

13% как тимлиды

Структура поста

1.1. Популярные стримы

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

1.3. Доклады на английском, которые запомнились

2.1. Самые упоминаемые статьи и книги

2.2. Часто упоминаемые статьи

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

2.4. Статьи на английском

1. Что смотрели

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

1.1. Стримы

Назад к оглавлению

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

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

Трейты в PHP зло? Валентин Удальцов против всех

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

Выбрать между PHP и Go просто, достаточно

Cтрим, в котором те, кто писал на PHP, но перешел на Go, и те, кто пишет в основном на PHP, не холиварили а написали два сервиса и разбирали их код, параллельно рассказывая про слабые места и границы применимости своего любимого языка.

Рефакторим c Александром Макаровым, Валентином Удальцовым, Валентином Назаровым, Леонидом Корсаковым

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

Митап Фреймворки и инструменты PHP

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

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

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

1.2. Записи докладов

Назад к оглавлению

Cycle ORM и графы доклад Антона Титова с онлайн-конференции PHP fwdays'20. Отличия подходов ActiveRecord и DataMapper. Решение проблемы топологической сортировки зависимостей ORM, используя итеративную сортировку в глубину.

Самое интересное в PHP 8 JIT, Preloading и FFI и не только в докладе Дмитрий Стогова с PHP Russia 2019.

Поговорим про код доклад Александра Макарова с онлайн-конференции PHP fwdays'20. Разбор принципов, которые позволяют писать код, который ломается меньше.

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

PHP: Неправильный путь Кирилл Несмеянов о том, с чего начать изучение PHP, как качать свой скилл и что будет с языком дальше. Запись с митапа в Иваново в конце 2019-го.

30+ примеров угроз: формы, файлы, заголовки, браузер, консоль, БД Александр Макаров про безопасность в веб-разработке: от базовых вещей до особенностей PHP. Запись с казанского PHP-митапа в конце 2019-го.

Разработка гибридных PHP/Go-приложений с использованием RoadRunner доклад Антона Титова с конференции PHP Russia. Как демонизировать PHP-приложение для повышения производительности.

MySQL, который мы не знаем доклад Виктор Зинченко с онлайн-конференции PHP fwdays'20. Как организовать мониторинг MySQL с помощью Prometheus, Grafana и что делать с медленными запросами.

Рефакторинг PHP-кода с применением DDD Виталий Чирков на примерах показывает, какие приёмы сработали в его случае. Запись с митапа в офисе Badoo в феврале 2020-го.

Грамотное ООП: организация надёжной бизнес-логики доклад Дмитрия Елисеева с конференции PHP Russia 2019. Тренируемся в объектно-ориентированной декомпозиции для грамотного проектирования сущностей по обязанностям и учимся сочинять ко этому быстрые, удобные и надёжные юнит-тесты.

Быстрый способ разобраться с легаси и начать жить Сергей Жук про то, как превратить работу с легаси в увлекательное приключение. Или все переписать? Или и так сойдет? Запись с краснодарского PHP-митапа в ноябре 2020-го.

Спасибо этим и другим каналам, что делилсь видеоСпасибо этим и другим каналам, что делилсь видео

Зачем и как писать качественные Unit-тесты Алексей Солодкий рассказывает об основных концепциях unit-тестирования и том, как поддерживать качество тестов на проекте. Запись с митапа в офисе Badoo в марте 2019-го.

Как контрибьютить в Symfony и зачем это делать запись с митапа в рамках PHP Russia 2019.

От Doctrine ORM к CQRS за 20 минут Дмитрий Симушев о том, что делать, когда хаки с оптимизацией доктрины больше не работают. Запись с митапа в офисе Skyeng летом 2019-го.

Очень странные дела на PHP Кирилл Несмеянов о применении PHP за гранью веб-разработки. Запись с онлайн-митапа весной 2020-го.

Перенос проекта на PHP 7: от сбора фактов до результата Максим Шамаев о том, что делать, когда к вам придут и предложат разобраться с очень старым кодом. Запись с онлайн-митапа весной 2020-го.

Различные эволюции от старта до релиза в PHP продукте видео с онлайн-конференции PHP fwdays'20. Александр Савченко про None-Breaking change development , cross-stack контракты, Trunk Based development и много чего еще.

Строим Highload на PHP и Redis Михаил Мазеин про то, что делать с очередями из миллиона сообщений. Запись с нижегородского PHP-митапа в конце 2019-го.

Big Ball of Mud и другие проблемы монолита, с которыми мы справились Юлия Николаева о модульном монолите как альтернативе микросервисам. Запись с онлайн-митапа весной 2020-го.

1.3. Записи докладов на английском

Назад к оглавлению

Effortless Software Development

Package Design Principles in Practice

Queues, busses and the messenger component

Getting the most out of the PHP 7 engine the example of Symfony

More Than a Query Language: SQL in the 21st Century

2. О чем читали

Мы получили 360 уникальных релевантных ответов, по которым можно было восстановить ссылки на материалы, а затем отобрали из них самые упоминаемые.

А вот на закрытые вопросы люди отвечали охотнее)А вот на закрытые вопросы люди отвечали охотнее)

2.1. Самые упоминаемые материалы

Назад к оглавлению

Мне не нравится то, во что превращается PHP мощное заявление от @AlexLeonov, которое получило развитие на стриме к выходу PHP 8.

PHP 8 что нового? обзор нововведений от @rela589n, вышедший за день до релиза 8-ки.

Регулярные PHP-дайджесты от @pronskiy упоминались разные выпуски. Кстати, с этого года они доступны в виде стримов на ютубе.

Архитектура сложных веб-приложений. С примерами на Laravel перевод книги @Adelf перевел он сам и выложил для скачивания на GitHub.

Собеседование php-developer: вопросы и ответы подборка, которую составил @Nidhognit чтобы подготовиться к собеседованиям. Спасибо, что поделился с сообществом!

Что не так с трейтами? превью к стриму, где в итоге победили трейты и @SerafimArts :)

Сейчас я буду убеждать вас использовать статический анализ в PHP расшифровка подкаста Между скобок, в котором @seregazhuk и @vudaltsov обсуждают Psalm и не только.

Зачем ограничивать наследование с помощью final? материал из конца 2019 от @parshikov_pavel который нашел благодарного читателя в 2020-м.

Куда катится PHP, а также про Yii и другие фреймворки презентация от @SamDark, которую удобно читать.

2.2. Также часто упоминали

Назад к оглавлению

Aсинхронный PHP расшифровка доклада Антона Шабовты с PHP Russia 2019.

Как переиспользовать код с бандлами Symfony 5? цикл статей от Романа Науменко.

Как я пытался улучшить Laravel, а сделал только хуже не метод, а монстр или история одного коммита.

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

Занятное мини-интервью с основными контрибьюторами PHP 8 частичная расшифровка стрима с Никитой Поповым и Дмитрием Стоговым. Продолжение недавно вышло тут.

В карантин нагрузка выросла в 5 раз, но мы были готовы та самая история от Lingualeo. 685 комментариев под постом!

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

Среда разработки PHP на базе Docker как быстро создать на локальной машине универсальную среду разработки.

Мёртвый код: найти и обезвредить чтобы что-то добавить, нужно что-то удалить.

У Вас проблемы с legacy значит, Вам повезло! Распил монолита на PHP - название говорит само за себя

Spiral: высокопроизводительный PHP/Go фреймворк - обзор от автора инструмента

Почему стоит попробовать Drupal 9 - если вы ищете CMS.

Отпусти меня, PHP - ведущий телеграм-канала PHP Today делится болью.

НЕкостыль: gRPC-клиент на PHP в продакшене боевое решение, которое, к тому же, легко пишется.

Уязвимости PHP-фреймворков сколько из них знаешь ты?

FFI: пишем на Rust в PHP-программе материал из осени 2019-го, который вспоминают до сих пор.

DDD на практике туториал из 2018-го, который был актуален и в 2020.

2.3. Переводы

Назад к оглавлению

Улучшения покрытия PHP кода в 2020 году почему метрики покрытия кода врут (и что с этим делать).

Модернизация старого PHP-приложения какие антипаттерны ловить в вашем легаси. Больше такого из опыта российских компаний на PHP Russia этим летом.

Современный PHP без фреймворков статья о возможности стать лучше как разработчик.

Понимаем JIT в PHP 8 как это работает и почему прирост производительности (кажется) не будет колоссальным.

Эволюция PHP от 5.6 до 8.0 небольшая шпаргалка с продолжением.

2.4. На английском

Назад к оглавлению

Its not legacy code its PHP

Object Oriented Done Right

Laravel beyond CRUD: the next chapter

My journey into event sourcing

PHP 8: before and after

Commits are snapshots, not diffs

Modular Monolith: A Primer

p.s. Спасибо всем, кто поделился тем, что читал и смотрел в 2020-м!

p.p.s. Отдельное и огромное спасибо @jm_sub и @alyssashch за помощь в обработке данных.

Подробнее..

Как я сделал веб-фреймворк без MVC Pipe Framework

23.02.2021 14:15:47 | Автор: admin

Проработав фулстек разработчиком около 10 лет, я заметил одну странность.
Я ни разу не встретил не MVC веб-фреймворк. Да, периодически встречались вариации, однако общая структура всегда сохранялась:


  • Codeigniter мой первый фреймворк, MVC
  • Kohana MVC
  • Laravel MVC
  • Django создатели слегка подменили термины, назвав контроллер View, а View Template'ом, но суть не изменилась
  • Flask микрофреймворк, по итогу все равно приходящий к MVC паттерну

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


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

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


  1. REST (порой GraphQL или другие варианты) бэкенд, выполняющий роль провайдера данных.
  2. Frontend, написаный на каком-либо из фреймворков большой тройки.

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

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


О фреймворке


В Pipe Framework (далее PF) нет понятий модель-представление-контроллер, но я буду использовать их для демонстрации его принципов.


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


Step это самодостаточная и изолированная единица, призванная выполнять только одну функцию, подчиняясь принципу единственной ответственности (single responsibility principle).


Более детально объясню на примере. Представим, у вас есть простая задача создать API ендпоинт для todo приложения.


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


Я выделил извлечь и трансформировать чтобы вы могли ассоциировать MVC концепты с концептами, которые я использую в PF.

То есть, мы можем провести аналогию между MVC (Модель-Представление-Контроллер) и ETL (Извлечение-Преобразование-Загрузка):


Model Extractor / Loader


Controller Transformer


View Loader


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


Как видите, я обозначил View как Loader. Позже станет понятно, почему я так поступил.

Первый роут


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


Первое, на что необходимо обратить внимание, это три типа шагов:


  • Extractor
  • Transformer
  • Loader

Как определиться с тем, какой тип использовать?


  1. Если вам надо извлечь данные из внешнего ресурса: extractor.
  2. Если вам надо передать данные за пределы фреймворка: loader.
  3. Если вам надо внести изменения в данные: transformer.

Именно поэтому я ассоциирую View с Loader'ом в примере выше. Вы можете воспринимать это как загрузку данных в браузер пользователя.

Любой шаг должен наследоваться от класса Step, но в зависимости от назначения реализовывать разные методы:


class ESomething(Step):    def extract(self, store):        ...class TSomething(Step):    def transform(self, store):        ...class LSomething(Step):    def load(self, store):        ...

Как вы можете заметить, названия шагов начинаются с заглавных E, T, L.
В PF вы работаете с экстракторами, трансформерами, и лоадерами, названия которых слишком длинные, если использовать их как в примере:


class ExtractTodoFromDatabase(Extractor):    pass

Именно поэтому, я сокращаю названия типа операции до первой буквы:


class ETodoFromDatabase(Extractor):    pass

E значит экстрактор, T трансформер, и L лоадер.
Однако, это просто договоренность и никаких ограничений со стороны фреймворка нет, так что можете использовать те имена, которые захотите :)


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


  1. Извлекаем данные из базы
  2. Преобразовываем данные в JSON
  3. Отправляем данные в браузер посредством HTTP.

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


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


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


Для этих целей, в PF предусмотрен @configure декоратор. То есть, вы просто перечисляете настройки, которые хотите добавить в шаг, следующим образом:


DATABASES = {    'default': {        'driver': 'postgres',        'host': 'localhost',        'database': 'todolist',        'user': 'user',        'password': '',        'prefix': ''    }}DB_STEP_CONFIG = {    'connection_config': DATABASES}

и потом передаете как аргумент декоратору, примененному к классу:


@configure(DB_STEP_CONFIG)class EDatabase(EDBReadBase):    pass

Итак, давайте создадим корневую папку проекта:


pipe-sample/


Затем папку src внутри pipe-sample:


pipe-sample/    src/

Все шаги, связанные с базой данных, будут находится в db пакете, давайте создадим и его тоже:


pipe-sample/    src/        db/            __init__.py

Создайте config.py файл с настройками для базы данных:


pipe-sample/src/db/config.py


DATABASES = {    'default': {        'driver': 'postgres',        'host': 'localhost',        'database': 'todolist',        'user': 'user',        'password': '',        'prefix': ''    }}DB_STEP_CONFIG = {    'connection_config': DATABASES}

Затем, extract.py файл для сохранения нашего экстрактора и его концигурации:


pipe-sample/src/db/extract.py


from src.db.config import DB_STEP_CONFIG # наша конфигурация"""PF включает в себя несколько дженериков для базы данных,которые вы можете посмотреть в API документации"""from pipe.generics.db.orator_orm.extract import EDBReadBase@configure(DB_STEP_CONFIG) # применяем конфигурацию к шагу class EDatabase(EDBReadBase):    pass     # нам не надо ничего добавлять внутри класса    # вся логика уже имплементирована внутри EDBReadBase

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

Теперь мы готовы к созданию первого пайпа.


Добавьте app.py в корневую папку проекта. Затем скопируйте туда этот код:


pipe-sample/app.py


from pipe.server import HTTPPipe, appfrom src.db.extract import EDatabasefrom pipe.server.http.load import LJsonResponse from pipe.server.http.transform import TJsonResponseReady@app.route('/todo/') # декоратор сообщает WSGI приложению, что этот пайп обслуживает данный маршрутclass TodoResource(HTTPPipe):     """    мы расширяем HTTPPipe класс, который предоставляет возможность описывать схему пайпа с учетом типа HTTP запроса    """    """    pipe_schema это словарь с саб пайпами для каждого HTTP метода.     'in' и 'out' это направление внутри пайпа, когда пайп обрабатывает запрос,    он сначала проходит через 'in' и затем через 'out' пайпа.    В этом случае, нам ничего не надо обрабатывать перед получением ответа,     поэтому опишем только 'out'.    """    pipe_schema = {         'GET': {            'out': (                # в фреймворке нет каких либо ограничений на порядок шагов                # это может быть ETL, TEL, LLTEETL, как того требует задача                # в этом примере просто так совпало                EDatabase(table_name='todo-items'),                TJsonResponseReady(data_field='todo-items_list'), # при извлечении данных EDatabase всегда кладет результат запроса в поле {TABLE}_item для одного результата и {TABLE}_list для нескольких                LJsonResponse()            )        }    }"""Пайп фреймворк использует Werkzeug в качестве WSGI-сервера, так что аргументы должны быть знакомы тем кто работал, например, с Flask. Выделяется только 'use_inspection'. Inspection - это режим дебаггинга вашего пайпа.Если установить параметр в True до начала воспроизведения шага, фреймворк будет выводить название текущего шага и содержимое стор на этом этапе."""if __name__ == '__main__':    app.run(host='127.0.0.1', port=8080,            use_debugger=True,            use_reloader=True,            use_inspection=True            )

Теперь можно выполнить $ python app.py и перейти на http://localhost:8000/todo/.


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


class EQueryStringData(Step):    """    Generic extractor for data from query string which you can find after ? sign in URL    """    required_fields = {'+{request_field}': valideer.Type(PipeRequest)}    request_field = 'request'    def extract(self, store: frozendict):        request = store.get(self.request_field)        store = store.copy(**request.args)        return store

Стор


На данный момент, стор в PF это инстанс frozendict.
Изменить его нельзя, но можно создать новый инстанс используя frozendict().copy() метод.


Валидация


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


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


Пример


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


class PrettyImportantTransformer(Step):    required_fields = {'+some_field': valideer.Type(dict)} # `+` значит обязательное поле

Динамическая валидация


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


class EUser(Step):    pk_field = 'id' # EUser будет обращаться к полю 'id' в сторе    required_fields = {'+{pk_field}': valideer.Type(dict)} # все остальное так же

Пайп фреймворк заменит это поле на значение pk_field автоматически, и затем валидирует его.


Объединение шагов


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


В этом примере я использую оператор | (OR)


    pipe_schema = {        'GET': {            'out': (                # В случае если EDatabase бросает любое исключение                 # выполнится LNotFound, которому в сторе передастся информация об исключении                EDatabase(table_name='todo-items') | LNotFound(),                 TJsonResponseReady(data_field='todo-items_item'),                LJsonResponse()            )        },

Так же есть оператор & (AND)


    pipe_schema = {        'GET': {            'out': (                # В этом случае оба шага должны выполниться успешно, иначе стор без изменений перейдет к следующему шагу                 EDatabase(table_name='todo-items') & SomethingImportantAsWell(),                 TJsonResponseReady(data_field='todo-items_item'),                LJsonResponse()            )        },

Хуки


Чтобы выполнить какие-либо операции до начала выполнения пайпа, можно переопределить метод: before_pipe


class PipeIsAFunnyWord(HTTPPipe):    def before_pipe(self, store): # в аргументы передается initial store. В случае HTTPPipe там будет только объект PipeRequest        pass

Также есть хук after_pipe и я думаю нет смысла объяснять, для чего он нужен.


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


Пример использования из исходников фреймворка:


class HTTPPipe(BasePipe):    """Pipe structure for the `server` package."""    def interrupt(self, store) -> bool:        # If some step returned response, we should interrupt `pipe` execution        return issubclass(store.__class__, PipeResponse) or isinstance(store, PipeResponse)

Потенциальные преимущества


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


  1. Принудительная декомпозиция: разработчик вынужден разделять задачу на атомарные шаги. Это приводит к тому, что сначала надо подумать, а потом делать, что всегда лучше, чем наоборот.
  2. Абстрактность: фреймворк подразумевает написание шагов, которые можно применить в нескольких местах, что позволяет уменьшить количество кода.
  3. Прозрачность: любая, пусть даже и сложная логика, спрятанная в шагах, призвана выполнять понятные для любого человека задачи. Таким образом, гораздо проще объяснить даже нетехническому персоналу о том, что происходит внутри через преобразование данных.
  4. Самотестируемость: даже без написаных юнит тестов, фреймворк подскажет вам что именно и в каком месте сломалось за счет валидации шагов.
  5. Юнит-тестирование осуществляется гораздо проще, нужно только задать начальные данные для шага или пайпа и проверить, что получается на выходе.
  6. Разработка в команде тоже становится более гибкой. Декомпозировав задачу, можно легко распределить различные шаги между разработчиками, что практически невозможно сделать при традиционном подходе.
  7. Постановка задачи сводится к предоставлению начального набора данных и демонстрации необходимого набора данных на выходе.

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


pip install pipe-framework


Планы по развитию:


  1. Django Pipe: специальный тип Pipe, который можно использовать как Django View.
  2. Смена Orator ORM на SQL Alchemy для Database Generics (Orator ORM библиотека с приятным синтаксисом, но слабой поддержкой, парой багов, и недостаточным функционалом в стабильной версии).
  3. Асинхронность.
  4. Улучшеный Inspection Mode.
  5. Pipe Builder специальный веб-дашбоард, в котором можно составлять пайпы посредством визуальных инструментов.
  6. Функциональные шаги на данный момент шаги можно писать только в ООП стиле, в дальнейшем планируется добавить возможность использовать обычные функции

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


Хорошего дня!

Подробнее..

Перевод Новое тестирование фичей в Django 3.2

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

Пару недель назад Django 3.2 выпустил свой первый альфа-релиз, а финальный релиз выйдет в апреле. Он содержит микс новых возможностей, о которых вы можете прочитать в примечаниях к релизу. Эта статья посвящена изменениям в тестировании, некоторые из которых можно получить на более ранних версиях Django с пакетами backport.

1. Изоляция setUpTestData()

В примечании к релизу говорится:

Объекты, назначенные для классификации атрибутов в TestCase.setUpTestData() теперь выделяются для каждого тестового метода.

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

Прием TestCase.setUp() нередко используется из юнит-теста для создания экземпляров моделей, которые используются в каждом тесте:

from django.test import TestCasefrom example.core.models import Bookclass ExampleTests(TestCase):    def setUp(self):        self.book = Book.objects.create(title="Meditations")

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

setUpTestData() позволяет создавать данные на уровне класса один раз на TestCase. Его использование очень похоже на setUp(), только это метод класса:

from django.test import TestCasefrom example.core.models import Bookclass ExampleTests(TestCase):    @classmethod    def setUpTestData(cls):        cls.book = Book.objects.create(title="Meditations")

В промежутках между тестами Django продолжает откатывать (rollback) любые изменения в базе данных, поэтому они остаются там изолированными. К сожалению, до этого изменения в Django 3.2 rollback не происходили в памяти, поэтому любые изменения в экземплярах моделей сохранялись. Это означает, что тесты не были полностью изолированы.

Возьмем, к примеру, эти тесты:

from django.test import TestCasefrom example.core.models import Bookclass SetUpTestDataTests(TestCase):    @classmethod    def setUpTestData(cls):        cls.book = Book.objects.create(title="Meditations")    def test_that_changes_title(self):        self.book.title = "Antifragile"    def test_that_reads_title_from_db(self):        db_title = Book.objects.get().title        assert db_title == "Meditations"    def test_that_reads_in_memory_title(self):        assert self.book.title == "Meditations"

Если мы запустим их на Django 3.1, то финальный тест провалится:

$ ./manage.py test example.core.tests.test_setuptestdataCreating test database for alias 'default'...System check identified no issues (0 silenced)..F.======================================================================FAIL: test_that_reads_in_memory_title (example.core.tests.test_setuptestdata.SetUpTestDataTests)----------------------------------------------------------------------Traceback (most recent call last):  File "/.../example/core/tests/test_setuptestdata.py", line 19, in test_that_reads_in_memory_title    assert self.book.title == "Meditations"AssertionError----------------------------------------------------------------------Ran 3 tests in 0.002sFAILED (failures=1)Destroying test database for alias 'default'...

Это связано с тем, что in-memory изменение из test_that_changes_title() сохраняется между тестами. Это происходит в Django 3.2 за счет копирования объектов доступа в каждом тесте, поэтому в каждом тесте используется отдельная изолированная копия экземпляра модели in-memory. Теперь тесты проходят:

$ ./manage.py test example.core.tests.test_setuptestdataCreating test database for alias 'default'...System check identified no issues (0 silenced)....----------------------------------------------------------------------Ran 3 tests in 0.002sOKDestroying test database for alias 'default'...

Спасибо Simon Charette за изначальное создание этой функциональности в проекте django-testdata, и до его объединения в систему Django. На старых версиях Django вы можете использовать django тест-данные для той же изоляции, добавив декоратор@wrap_testdata в ваши методы setUpTestData(). Он очень удобен, и я добавлял его в каждый проект, над которым работал.

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

2. Использование faulthandler по умолчанию

В примечании к релизу говорится:

DiscoverRunner сейчас использует faulthandler по умолчанию.

Это небольшое улучшение, которое может помочь вам дебаггить сбои низкого уровня. Модуль Python faulthandler предоставляет способ сброса экстренной трассировки в ответ на проблемы, которые приводят к сбою в работе интерпретатора Python.Тогда тестовый runner Django использует faulthandler. Подобное решение было скопировано из pytest, который делает то же самое.

Проблемы, которые ловит faulthandler, обычно возникают в библиотеках на языке C, например, в драйверах баз данных. Например, OS сигнал SIGSEGV указывает на ошибку сегментации, что означает попытку чтения памяти, не принадлежащей текущему процессу. Мы можем эмулировать это на Python, напрямую посылая сигнал самим себе:

import osimport signalfrom django.test import SimpleTestCaseclass FaulthandlerTests(SimpleTestCase):    def test_segv(self):        # Directly trigger the segmentation fault        # signal, which normally occurs due to        # unsafe memory access in C        os.kill(os.getpid(), signal.SIGSEGV)

Если мы делаем тест в Django 3.1, мы видим это:

$ ./manage.py test example.core.tests.test_faulthandlerSystem check identified no issues (0 silenced).[1]    31127 segmentation fault  ./manage.py test

Это нам не очень помогает, так как нет никакой зацепки на то, что вызвало ошибку сегментации.

Вместо этого мы видим трассировку на Django 3.2:

$ ./manage.py test example.core.tests.test_faulthandlerSystem check identified no issues (0 silenced).Fatal Python error: Segmentation faultCurrent thread 0x000000010ed1bdc0 (most recent call first):  File "/.../example/core/tests/test_faulthandler.py", line 12 in test_segv  File "/.../python3.9/unittest/case.py", line 550 in _callTestMethod  ...  File "/.../django/test/runner.py", line 668 in run_suite  ...  File "/..././manage.py", line 17 in main  File "/..././manage.py", line 21 in <module>[1]    31509 segmentation fault  ./manage.py test

( Сокращенно )

Faulthandler не может генерировать точно такую же трассировку, как стандартное исключение в Python, но он дает нам много информации для дибаггинга сбоя.

3. Timing (тайминг)

В примечании к релизу говорится:

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

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

$ ./manage.py test --timingCreating test database for alias 'default'...System check identified no issues (0 silenced)....----------------------------------------------------------------------Ran 3 tests in 0.002sOKDestroying test database for alias 'default'...Total database setup took 0.019s  Creating 'default' took 0.019sTotal database teardown took 0.000sTotal run took 0.028s

Благодарим Ахмада А. Хуссейна за участие в этом мероприятии в рамках Google Summer of Code 2020.

Если вы используете pytest, опция --durations N работает схоже.

Из-за системы фикстуры pytest время настройки базы данных будет отображаться как время настройки (setup time) только для одного теста, что сделает этот тест более медленным, чем он есть на самом деле.

4. Обратный вызов (callbacks) тестаtransaction.on_commit()

В примечании к релизу говорится:

Новый метод TestCase.captureOnCommitCallbacks() собирает функции обратного вызова (callbacks functions), переданные в transaction.on_commit(). Это позволяет вам тестировать эти callbacks, не используя при этом более медленный TransactionTestCase.

Это вклад, который я ранее сделал и о котором ранее рассказывал.

Итак, представьте, что вы используете опцию ATOMIC_REQUESTSот Django, чтобы перевести каждый вид в транзакцию (а я думаю, что так и должно быть!). Затем вам нужно использовать функцию transaction.on_commit() для выполнения любых действий, которые зависят от того, насколько длительно данные хранятся в базе данных. Например, в этом простом представлении для формы контактов:

from django.db import transactionfrom django.views.decorators.http import require_http_methodsfrom example.core.models import ContactAttempt@require_http_methods(("POST",))def contact(request):    message = request.POST.get('message', '')    attempt = ContactAttempt.objects.create(message=message)    @transaction.on_commit    def send_email():        send_contact_form_email(attempt)    return redirect('/contact/success/')

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

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

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

(Я ранее говорил об увеличении скорости в три раза благодаря конвертации тестов из TransactionTestCase в TestCase.)

Решением в Django 3.2 является новая функция captureOnCommitCallbacks(), которую мы используем в качестве контекстного менеджера. Она захватывает любые callbacks и позволяет вам добавлять утверждения или проверять их эффект. Мы можем использовать это, чтобы проверить наше мнение таким образом:

from django.core import mailfrom django.test import TestCasefrom example.core.models import ContactAttemptclass ContactTests(TestCase):    def test_post(self):        with self.captureOnCommitCallbacks(execute=True) as callbacks:            response = self.client.post(                "/contact/",                {"message": "I like your site"},            )        assert response.status_code == 302        assert response["location"] == "/contact/success/"        assert ContactAttempt.objects.get().message == "I like your site"        assert len(callbacks) == 1        assert len(mail.outbox) == 1        assert mail.outbox[0].subject == "Contact Form"        assert mail.outbox[0].body == "I like your site"

Итак, мы используем captureOnCommitCallbacks() по запросу тестового клиента на просмотр, передавая execute флаг, чтобы указать, что фальшивое сохранение (коммит) должно запускать все callbacks. Затем мы проверяем HTTP-ответ и состояние базы данных, прежде чем проверить электронную почту, отправленную обратным вызовом (callback). Наш тест затем покрывает все на просмотре, оставаясь быстрым и классным!

Чтобы использовать captureOnCommitCallbacks() в ранних версиях Django, установите django-capture-on-commit-callbacks.

5. Улучшенный assertQuerysetEqual()

В примечании к релизу говорится:

TransactionTestCase.assertQuerysetEqual() в данный момент поддерживает прямое сравнение с другой выборкой элементов запроса в Django

Если вы используете assertQuerysetEqual() в ваших тестах, это изменение точно улучшит вашу жизнь!

В дополнение к Django 3.2, assertQuerysetEqual() требует от вас сравнения с QuerySet после трансформации. Далее происходит переход по умолчанию к repr(). Таким образом, тесты, использующие его, обычно проходят список предварительно вычисленных repr() strings для вышеупомянутого сравнения:

from django.test import TestCasefrom example.core.models import Bookclass AssertQuerySetEqualTests(TestCase):    def test_comparison(self):        Book.objects.create(title="Meditations")        Book.objects.create(title="Antifragile")        self.assertQuerysetEqual(            Book.objects.order_by("title"),            ["<Book: Antifragile>", "<Book: Meditations>"],        )

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

Из Django 3.2 можно передать QuerySet или список объектов для сравнения, что позволяет упростить тест:

from django.test import TestCasefrom example.core.models import Bookclass AssertQuerySetEqualTests(TestCase):    def test_comparison(self):        book1 = Book.objects.create(title="Meditations")        book2 = Book.objects.create(title="Antifragile")        self.assertQuerysetEqual(            Book.objects.order_by("title"),            [book2, book1],        )

Спасибо Питеру Инглсби и Хасану Рамезани за то, что они внесли эти изменения. Они помогли улучшить тестовый набор Django.

Финал

Наслаждайтесь этими изменениями, когда Django 3.2 выйдет или сделайте это раньше через пакет backport.


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

Также приглашаем всех желающих посмотреть открытый вебинар на тему Использование сторонних библиотек в django. На занятии мы рассмотрим общие принципы установки и использования сторонних библиотек вместе с django; а также научимся пользоваться несколькими популярными библиотеками: django-debug-toolbar, django-cms, django-cleanup и т.д.

Подробнее..

PHP Дайджест 199 (8 22 февраля 2021)

22.02.2021 14:21:33 | Автор: admin

В PHP 8.1 будет enum, и еще два принятых, два отклоненных и три новых RFC предложения для PHP 8.1. WordPress используется на 40% сайтов. Почему нужно убрать strict_types, почему не стоит использовать empty(), а также инструменты, видео, статьи, подкасты, и PHP Дайджест Live в 20:00 МСК.

Приятного чтения!



Новости и релизы



PHP Internals


  • check[RFC] Enumerations
    С результатом 44 против 7 голосование завершено. В PHP 8.1 будет enum.
    enum RfcStatus {    case Draft;    case UnderDiscussion;    case Accepted;}function setRfcStatus(RfcStatus $status) :void {    // ...}setRFCStatus(RfcStatus::Accepted); // ОкsetRFCStatus('Draft');             // TypeError
    

    Подробнее про инамы можно прочитать в пересказах RFC в статье Брента и еще подробнее на php.watch.

    В Symfony уже открыли тикеты для добавления поддержки инамов.
  • check[RFC] Deprecate passing null to non-nullable arguments of internal functions
    В текущих версиях PHP стандартные функции без ошибок принимают null в качестве аргумента, когда параметр не nullable.

    А начиная с PHP 8.1 встроенные функции тоже будут бросать TypeError. Например, str_contains("", null). 3v4l.org/OVoa0A.

    Интересный факт: предложение принято единогласно, притом что это довольно крупная поломка обратной совместимости в PHP.
  • check[RFC] Array unpacking with string keys
    Предложение принято и в PHP 8.1 будет работать распаковка любых массивов, в том числе со строковыми ключами:
    $array1 = ['a' => 'apple', 'p' => 'pear'];$array2 = ['b' => 'banana', 'o' => 'orange'];$array = [...$array1, ...$array2];// Приблизительно то же самое что:$array = array_merge($array1, $array2);
    
  • [RFC] Fibers
    Из предложения по файберам был убран планировщик, потому что он сильно усложнял реализацию и вероятность принятия предложения.

    Теперь Fiber API предоставляет самый минимум и похож на аналогичные возможности в Ruby.

    Пример использования с ReactPHP trowski/react-fiber:
    Скрытый текст
    $loop = new FiberLoop(Factory::create());$browser = new Browser($loop);$request = function (string $method, string $url) use ($browser, $loop): void {    /** @var Response $response */    $response = $loop->await($browser->requestStreaming($method, $url));    /** @var ReadableStreamInterface $stream */    $stream = $response->getBody();    $body = $loop->await(Stream\buffer($stream));    var_dump(\sprintf(        '%s %s; Status: %d; Body length: %d',        $method,        $url,        $response->getStatusCode(),        \strlen($body)    ));};$requests = [];$requests[] = $loop->async($request, 'GET', 'https://reactphp.org');$requests[] = $loop->async($request, 'GET', 'https://google.com');$requests[] = $loop->async($request, 'GET', 'https://www.php.net');$loop->await(Promise\all($requests));
    
  • [RFC] CachedIterable (rewindable, allows any key&repeating keys)
    Tyson Andre предлагает добавить кеширующий итератор. Он сохраняет состояние любого итератора и внутри себя содержит иммутабельные копии его ключей и значений.
  • Proposal: namespace the SPL
    Обсуждается предложение создать неймспейс Spl и создать в нем алиасы для существующих классов: Spl\FixedArray -> SplFixedArray. А все новые классы, такие как CachedIterable и ReverseIterator уже вносит сразу в новый неймспейс.

    А пока в качестве альтернативы есть отличный инструмент azjezz/psl.
  • [RFC] mysqli bind in execute
    Kamil Tekiela продолжает инициативу по улучшению mysqli. В этом RFC предлагает добавить новый необязательный параметр в mysqli_stmt::execute(). Он будет принимать массив значений, которые автоматически биндятся, вместо отдельного вызова mysqli_stmt::bind_param(). В последний сейчас принимает только переменные по ссылке.
  • cross[RFC] PHP\iterable\any() and all() on iterables Предложение добавить функции any() и all() для итераторов не прошло голосование.
  • cross[RFC] var_representation(): readable alternative to var_export() Идея добавить альтернативу для var_export не нашла поддержки, поэтому пока используем юзерленд альтернативу brick/varexporter.
  • [Draft] Unify PHP's typing modes В PHP по сути есть два режима типизации. Один слишком слабый, а другой, strict_types=1 слишком строгий. Этот документ описывает причины существования этих двух режимов, их недостатки и что нужно сделать, чтобы объединять оба режима.

    Документ написан George Peter Banyard, и пока он не планирует его выдвигать в качестве официального RFC.

    Разберем его положения на стриме.
  • Об Observer API в PHP 8 Статья о внутреннем API для отслеживания входа и выхода из функции. Он существенно упростил разработку расширений типа Xdebug, профайлеров и APM-решений New Relic, Tideways, и т.п.

Инструменты


  • renoki-co/php-k8s Позволяет управлять ресурсами кубернетиса из PHP.
  • marcocesarato/php-conventional-changelog Генерирует с changelog из сообщений коммитов.
  • andrey-helldar/package-wizard CLI-инструмент для создания начальной структуры пакетов.
  • rryqszq4/ngx_php7 Встраиваемый в nginx интерпретатор PHP. Позволяет создавать обработчики запросов на PHP, модифицировать запрос/ответ, фильтровать тело ответа и заголовки, и прочее.

Symfony



Laravel



Yii



Async PHP


  • swow/swow Расширение для PHP, которое предоставляет асинхронные возможности на базе libuv, включая асинхронный стрим, то есть из коробки работающие PDO, file_get_сontents() и т.п. (когда они обернуты в корутину). По сути, является минималистичным подмножеством Swoole.

phpstorm PhpStorm



Статьи



Аудио/Видео



Занимательное


  • mario-deluna/php-render 3D рендерер на чистом PHP, даже безШейде Шейдеры, парсер .obj файлов и прочее.
    Код примера:





Уже традиционный стрим по мотивам PHP Дайджеста. Будет разбор новостей и ссылок из выпуска с подробностями и дополнительными деталями.
Начало в 20:00 Москва, Минск / 19:00 Киев.



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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 198
Подробнее..

Обновления ASP.NET Core в .NET 6 Preview 1

25.02.2021 10:21:06 | Автор: admin

Новая версия .NET, 6 Preview 1, уже доступна и готова к вашей оценке. Это первая предварительная версия .NET 6, следующего крупного обновления платформы .NET. Ожидается, что .NET 6 поступит в полноценный доступ в ноябре этого года и будет выпуском с долгосрочной поддержкой (LTS).

Если вы работаете с Windows и используете Visual Studio, мы рекомендуем установить последнюю предварительную версию Visual Studio 2019 16.9. Если вы используете macOS, мы рекомендуем установить последнюю предварительную версию Visual Studio 2019 для Mac 8.9.

Основная работа, запланированная с ASP.NET Core в .NET 6

.NET 6 использует открытый процесс планирования, поэтому вы можете изучить все основные темы, запланированные для этого релиза, на Blazor-веб-сайте themesof.net. В дополнение к этим верхнеуровневым темам мы собираемся также предоставить множество улучшений, ориентированных на пользователей. Вы можете найти список основных задач, запланированных для ASP.NET Core в .NET 6, в нашем выпуске дорожной карты. Вот некоторые из основных функций ASP.NET Core, запланированных для выпуска .NET 6:

Мы приветствуем отзывы и участие в процессе планирования и создания на GitHub.

Что нового в ASP.NET Core в .NET 6 Preview 1?

  • Поддержка IAsyncDisposableв MVC

  • DynamicComponent

  • InputElementReferenceразделен на релевантные компоненты

  • dotnet watchтеперь являетсяdotnet watch runпо дефолту

  • Nullable reference type annotations

Начало работы

Чтобы начать работу с ASP.NET Core в .NET 6 Preview 1, установите .NET 6 SDK.

Обновление существующего проекта

Чтобы обновить существующее приложение ASP.NET Core с .NET 5 до .NET 6 Preview 1:

  • Обновите целевую платформу для вашего приложения, доnet6.0.

  • Обновите все ссылки на пакеты Microsoft.AspNetCore.* до6.0.0-preview.1.*.

  • Обновите все ссылки на пакеты Microsoft.Extensions.* до6.0.0-preview.1.*.

См. полный список критических изменений в ASP.NET Core для .NET 6 здесь.

DynamicComponent

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

<DynamicComponent Type="@someType" />

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

<DynamicComponent Type="@someType" Parameters="@myDictionaryOfParameters" />@code {    Type someType = ...    IDictionary<string, object> myDictionaryOfParameters = ...}

InputElementReferenceразделен на релевантные компоненты

Соответствующие встроенные компоненты Blazor ввода теперь предоставляют удобную ссылку ElementReference для базового ввода, что упрощает распространенные сценарии, такие как установка фокуса пользовательского интерфейса на вводе. Затронутые компоненты: InputCheckbox, InputDate, InputFile, InputNumber, InputSelect, InputText и InputTextArea.

dotnet watchтеперь являетсяdotnet watch runпо дефолту

Запуск dotnet watch теперь будет запускать dotnet watch run по умолчанию, экономя драгоценное время ввода.

Nullable Reference Type Annotations

Мы применяем аннотации обнуляемости к частям ASP.NET Core. Значительное количество новых API было аннотировано в .NET 6 Preview 1.

Используя новую функцию C# 8, ASP.NET Core может обеспечить дополнительную безопасность во время компиляции при обработке ссылочных типов, например защиту от исключений нулевых ссылок. Проекты, которые выбрали использование аннотаций, допускающих значение NULL, могут видеть новые предупреждения во время сборки от API-интерфейсов ASP.NET Core.

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

<PropertyGroup>    <Nullable>enable</Nullable></PropertyGroup>

Подробности читайте здесь.

Подробнее..

Тильда для ресторанов на Bubble без кода

26.02.2021 14:22:55 | Автор: admin

Год назад Евгений Спорыхин руководил SMM-агентством и привлекал разработчиков на проекты. Зерокодингом увлекся после первого потока нашего интенсивна Airtable Express. Сейчас Женя один из лучших экспертов по Bubble, руководит студией NoCode Hero и преподает в университете Зерокодер. Он рассказал о своем новом кейсе конструкторе сайтов для рестораторов на Bubble.

Как придумал идею

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

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

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

  1. Рестораторам должно быть просто его использовать

  2. Удобная админка с базовыми функциями: статистика по заказам, среднему чеку, клиентам.

  3. Интеграция с платежными сервисами и возможность запомнить клиентов.

  4. Адаптация под мобильные и рестораторы, и клиенты чаще всего выходили в инстаграм с телефонов.

Автоматически сгенерированный мини-сайт ресторанаАвтоматически сгенерированный мини-сайт ресторана

Что под капотом

Собрал всё на Bubble мобильные Adalo и Glide не потянули бы сложную бизнес-логику.

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

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

Интерфейс для ресторатора

Ресторатор регистрируется и добавляет свои заведения (можно добавить целую сеть) и у него появляется набор возможностей:

  • По каждому ресторану статистика ведется отдельно.

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

  • На вкладке Заказы вся информация по текущим и уже выполненным заказам.

  • На вкладке Клиенты базовая CRM.

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

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

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

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

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

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

Добавление категорий блюд в интерфейсе для ресторатораДобавление категорий блюд в интерфейсе для ресторатора

Пользовательский интерфейс

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

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

Внешний вид конструктора сайтов для рестораторовВнешний вид конструктора сайтов для рестораторов

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

В корзине при оформлении заказа автоматически подтягиваются данные пользователя: имя, телефон, имейл. В стандартной комплектации Bubble уже есть Google Maps он дает возможность настроить автодополнение адреса.

Хаки для разработчиков

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

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

Настройки Optional SetsНастройки Optional Sets

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

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

Сколько потратил на разработку

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

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

Настройки блюд: можно вывести в рекомендованное, поменять стоимость, выставить фильтры по категориямНастройки блюд: можно вывести в рекомендованное, поменять стоимость, выставить фильтры по категориям

Планы на будущее

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

Bubble это платформа для создания веб-приложений без навыков программирования, инструмент all-in-one. В нем есть визуальный редактор, базы данных, инструменты для бизнес-логики и работы с разными API. Он позволяет создавать полнофункциональные чаты, форумы, системы сбора и обработки заявок, таск-трекеры, маркетплейсы, CRM и дашборды. Присоединяйтесь к сообществу Bubble Chat & Community и каналу Зерокодер.

Подробнее..

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

28.02.2021 00:20:44 | Автор: admin
Здравствуйте! Меня зовут Алексей, я средний фронтенд-разработчик, наверное. А может джуниор, смотря от лица какой компании смотреть.

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

И у меня прям щелкнуло в голове, ну правда, что за полумеры?



Я не нашёл эту пикчу, простите. Если что, кидайте под пост.

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

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

Или то, что я пришёл на собеседование, значит я в ловушке и слаб?

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

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

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

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

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

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

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

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

Понимаю, что слишком эмоционально, но как есть, обсуждение острой темы.

Всем хороших офферов и достойных кандидатов!
Подробнее..

RamblerFrontamp Meetup 9

04.03.2021 14:05:18 | Автор: admin

Прошлый год не считается, поэтому начнем все заново:)

Мы анонсируем проведение девятого RamblerFront& Meetup 18-го марта в 19:00. В этом году он пройдет онлайн, но обещает быть столь же интересным, как и предыдущие.

Обязательно регистрируйтесь заранее по ссылке.

Ведущий и модератор:


В программе:

  • AdBlock: Блокировка рекламы с помощью JS и не только

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

  • Content Indexing API. Страницы, доступные в offline

Content Indexing API новый инструмент от Google, показывающий, какие страницы доступны в офлайн-режиме. Как работает Content Indexing API, когда его следует использовать, как мы внедряли его на lenta.ru и какие видим перспективы вы узнаете из доклада.

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

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

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru