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

Программирование

Перевод Выработка уникальных идей для Data Science-проектов за 5 шагов

22.09.2020 16:11:47 | Автор: admin
Вероятно, самое сложное в любом Data Science-проекте это придумать оригинальную, но реализуемую идею. Специалист, который ищет такую идею, легко может попасться в ловушку наборов данных. Он тратит многие часы, просматривая существующие наборы данных и пытаясь выйти на новые интересные идеи. Но у такого подхода есть одна проблема. Дело в том, что тот, кто смотрит лишь на существующие наборы данных (c Kaggle, Google Datasets, FiveThirtyEight), ограничивает свою креативность, видя лишь небольшой набор задач, на которые ориентированы изучаемые им наборы данных.

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



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

1. Почему я хочу начать работу над новым проектом?


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

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

2. К каким сферам относятся мои интересы и мой опыт?


Подумать над этим вопросом стоит по трём основным причинам.

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

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

3. Как найти вдохновение?


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

  • Новости, авторские статьи, публикации в блогах. Чтение о неких событиях или явлениях, которые наблюдали авторы публикаций, это отличный способ выработки идей. Например, портал WIRED опубликовал эту статью, посвящённую тому, что функция автодополнения ввода при поиске в Google демонстрирует политическую предвзятость. Вдохновившись этой идеей, можно исследовать систематические ошибки в языковых моделях. Или можно задаться вопросом о возможности предсказания географического положения человека на основе поисковых запросов, вводимых им в Google.
  • Научная литература. Научные публикации часто включают в себя рассказы о нерешённых вопросах, имеющих отношение к исследуемой теме. Например, в этой публикации рассказывается о языковой модели GPT-2 и упоминается о том, что эта модель, без её тонкой настройки, показывает себя на определённых задачах, вроде ответов на вопросы, не лучше, чем попытка решить эти задачи методом случайного угадывания. Почему бы не написать что-нибудь о нюансах тонкой настройки этой модели?
  • Материалы из сферы науки о данных. Чтение материалов, представляющих темы, связанные с Data Science, и содержащие обзоры соответствующих проектов, способно привести к новым идеям. Например, когда я прочитала об NLP-исследовании сериала Офис, я тут же пожалела о том, что мне эта идея не пришла раньше, чем автору материала. Но почему бы не исследовать какой-нибудь другой сериал? А может, изучить несколько фильмов и попытаться определить языковые паттерны? А для написания текстов к любимому сериалу можно попробовать воспользоваться моделью GPT-2.

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

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

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

4. Где найти подходящие данные?


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

  • Существующие хранилища данных: Kaggle, Google Datasets, FiveThirtyEight, BuzzFeed, AWS, UCI Machine Learning Repository, data.world, Data.gov и многие другие, которые можно найти с помощью Google.
  • Источники данных, используемые другими дата-сайентистами. Поищите сведения по интересующей вас теме в Google и в Google Scholar. Выясните, пытался ли уже кто-нибудь найти ответ на вопрос, похожий на ваш. Какие данные использовались в похожих исследованиях? Например, ресурс Our World in Data представляет академические и неакадемические источники данных, о которых вы можете не знать.
  • Данные, которые нужно собирать самостоятельно. Для сбора таких данных можно прибегнуть к веб-скрапингу, к анализу текстов, к различным API, к отслеживанию событий, к работе с лог-файлами.

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

5. Реализуема ли найденная идея?


Итак, у вас есть фантастическая идея! Но можно ли её реализовать? Снова пройдитесь по этапам процесса генерирования идей. Подумайте о том, чего вы хотите достичь (вопрос 1), интересна ли вам выбранная область, если ли у вас опыт работы в ней (вопрос 2), есть ли у вас данные, необходимые для реализации идеи (вопрос 4). Теперь вам нужно определить следующее: имеются ли у вас навыки, необходимые для реализации идеи и для достижения цели.

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

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

Итоги


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

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

Как вы ищите новые идеи для своих Data Science-проектов?



Подробнее..

Перевод Горячая четвёрка умирающих языков программирования

25.09.2020 16:06:58 | Автор: admin
Я занимался поиском лучших языков программирования 2020 года и наткнулся на страницы, на которых шла речь о языках, теряющих популярность. Я программист, и я понимаю, что любому программисту крайне важно знать о том, какие технологии являются актуальными, а какие нет.

Каждый программист это писатель.

Серкан Лейлек


Я, после того, как насмотрелся на отчёты о языках программирования, теряющих актуальность, выбрал 4 языка, которые, как я полагаю, уже не стоят того, чтобы их изучали. Я, ради подкрепления своих выводов, прибегну к некоторым показателям популярности языков. В частности, речь идёт об индексе PYPL (PopularitY of Programming Language Index, индекс популярности языков программирования), о данных Google Trends и о некоторых сведениях, которые можно найти на платформе YouTube.


Фрагмент рейтинга PYPL (источник)

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

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

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

1. Perl


Интерес к языку программирования Perl стремительно падает. Хорошие показатели он демонстрировал в период с 2004 по 2009 годы, а после этого начался спад. Хотя этот язык пока и не мёртв, но он уже и не очень-то жив.

Информацию по нему не особенно активно ищут на YouTube и в Google. Например, есть видео по Perl, загруженное 4 года назад и набравшее всего 240 тысяч просмотров.


Видео по Perl

Кроме того, показатели языка идут вниз и в рейтинге PYPL.

Я решил сравнить Perl с каким-нибудь другим языком, с Python в данном случае, и обратился к Google Trends.


Сравнение Perl (красная линия) и Python (синяя линия), последние 12 месяцев

Как видно, красная линия, представляющая Perl, находится где-то на уровне нуля.

2. Haskell


Язык Haskell выглядит лучше, чем Perl. Он, к тому же, используется во многих крупных компаниях вроде Facebook и IBM. На YouTube есть видео по Haskell, загруженное 5 лет назад. Оно набрало 585 тысяч просмотров.


Видео по Haskell

Посмотрим теперь на показатели Google Trends, сравним Haskell и Python.


Сравнение Haskell (синяя линия) и Python (красная линия), последние 5 лет

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

3. Objective-C


Язык Objective-C, если ориентироваться на рейтинг PYPL, вырос в популярности на 0,2%. А что будет, если взглянуть на данные с YouTube?


Видео по Objective-C

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

Обратимся теперь к показателям Google Trends.


Сравнение Objective-C (синяя линия) и Python (красная линия), последние 5 лет

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

4. Visual Basic for Applications


Visual Basic for Applications, VBA, был у всех на слуху в 2004 году, а вот после 2009 интерес к нему начал падать. Я, например, изучал этот язык в школе.

Рейтинг PYPL указывает на то, что популярность VBA упала на 0,2%.

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


Видео по VBA

Если посмотреть на данные по VBA, которые имеются на Google Trends, то окажется, что интерес к VBA с 2004 года стабильно падает.


Сравнение VBA (красная линия) и Python (синяя линия), c 2004 года по настоящее время

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

Python


Я занимаюсь серверной разработкой, используя Python. Я, кроме того, сделал несколько проектов, используя фреймворк Django. Что тут сказать мне нравится Python.

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


Языки, знание которых помогает в поиске работы

Я, например, создал проект на Django. А именно, речь идёт о сайте с вопросами и ответами для разработчиков. Этот проект всё ещё в работе. Я расширяю его и занимаюсь его оптимизацией.

Python в рейтинге PYPL демонстрирует рост на 2,9%. Если поинтересоваться данными YouTube по просмотрам видео о Python, то окажется, что они, за короткие промежутки времени, набирают миллионы просмотров.


Видео по Python

Анализ исследования Stack Overflow


Выше я опирался на рейтинг PYPL, на данные с Google Trends и на анализ видео по интересующим меня языкам программирования на YouTube. Теперь же я обращусь к результатам опроса разработчиков, проведённого Stack Overflow в 2020 году. А именно, к данным по языкам программирования, на которых программисты пишут, но не хотят продолжать этим заниматься.


Данные опроса Stack Overflow (источник)

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


Зарплаты разработчиков и их связь с языками программирования (источник)

Итоги


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

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

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

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



Подробнее..

Почему обзоры кода это хорошо, но недостаточно

23.09.2020 18:06:49 | Автор: admin
image1.png

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

Попробуйте найти ошибку в коде функции, взятой из библиотеки structopt:

static inline bool is_valid_number(const std::string &input) {  if (is_binary_notation(input) ||      is_hex_notation(input) ||      is_octal_notation(input)) {    return true;  }  if (input.empty()) {    return false;  }  std::size_t i = 0, j = input.length() - 1;  // Handling whitespaces  while (i < input.length() && input[i] == ' ')    i++;  while (input[j] == ' ')    j--;  if (i > j)    return false;  // if string is of length 1 and the only  // character is not a digit  if (i == j && !(input[i] >= '0' && input[i] <= '9'))    return false;  // If the 1st char is not '+', '-', '.' or digit  if (input[i] != '.' && input[i] != '+' && input[i] != '-' &&      !(input[i] >= '0' && input[i] <= '9'))    return false;  // To check if a '.' or 'e' is found in given  // string. We use this flag to make sure that  // either of them appear only once.  bool dot_or_exp = false;  for (; i <= j; i++) {    // If any of the char does not belong to    // {digit, +, -, ., e}    if (input[i] != 'e' && input[i] != '.' &&        input[i] != '+' && input[i] != '-' &&        !(input[i] >= '0' && input[i] <= '9'))      return false;    if (input[i] == '.') {      // checks if the char 'e' has already      // occurred before '.' If yes, return false;.      if (dot_or_exp == true)        return false;      // If '.' is the last character.      if (i + 1 > input.length())        return false;      // if '.' is not followed by a digit.      if (!(input[i + 1] >= '0' && input[i + 1] <= '9'))        return false;    }    else if (input[i] == 'e') {      // set dot_or_exp = 1 when e is encountered.      dot_or_exp = true;      // if there is no digit before 'e'.      if (!(input[i - 1] >= '0' && input[i - 1] <= '9'))        return false;      // If 'e' is the last Character      if (i + 1 > input.length())        return false;      // if e is not followed either by      // '+', '-' or a digit      if (input[i + 1] != '+' && input[i + 1] != '-' &&          (input[i + 1] >= '0' && input[i] <= '9'))        return false;    }  }  /* If the string skips all above cases, then  it is numeric*/  return true;}

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

image2.png

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

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

V560 A part of conditional expression is always false: input[i] <= '9'. structopt.hpp 1870

Для тех, кто не заметил ошибку, дам пояснения. Самое главное:

else if (input[i] == 'e') {  ....  if (input[i + 1] != '+' && input[i + 1] != '-' &&      (input[i + 1] >= '0' && input[i] <= '9'))      return false;}

Вышестоящее условие проверяет, что i-тый элемент является буквой 'e'. Соответственно следующая проверка input[i] <= '9' не имеет смысла. Результат второй проверки всегда false, о чём и предупреждает инструмент статического анализа. Причина ошибки проста: человек поспешил и опечатался, забыв написать +1.

Фактически получается, что функция не до конца выполняет свою работу по проверке корректности введённых чисел. Правильный вариант:

else if (input[i] == 'e') {  ....  if (input[i + 1] != '+' && input[i + 1] != '-' &&      (input[i + 1] >= '0' && input[i + 1] <= '9'))      return false;}

Интересное наблюдение. Эту ошибку можно рассматривать как разновидность "эффекта последней строки". Ошибка допущена в самом последнем условии функции. В конце внимание программиста ослабло, и он допустил эту малозаметную ошибку.


Если вам понравится статья про эффект последней строки, то рекомендую познакомиться с другими аналогичными наблюдениями: 0-1-2, memset, сравнения.

Всем пока. Ставлю лайк тем, кто самостоятельно нашёл ошибку.
Подробнее..

Перевод Почему это антипаттерн?

21.09.2020 20:06:45 | Автор: admin
Всем привет. В сентябре в OTUS стартует сразу несколько курсов по JS-разработке, а именно: JavaScript Developer. Professional, JavaScript Developer. Basic и React.js Developer. В преддверии старта этих курсов мы подготовили для вас еще один интересный перевод, а также предлагаем записаться на бесплатные демо-уроки по следующим темам:



А теперь перейдём к статье.





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

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

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

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

Стандартный подход: используем пропсы для передачи значений


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

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

App обращается к ContentArea
ContentArea обращается к MainContentArea
MainContentArea обращается к MyDashboard
MyDashboard обращается к MyOpenTickets
MyOpenTickets обращается к TicketTable
TicketTable обращается к последовательности TicketRow
Каждый TicketRow обращается к TicketDetail

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

Может ли компонент TicketDetail в примере выше считывать значения состояния, которые хранятся в ContentArea? Или. Может ли компонент TicketDetail вызывать функции, которые находятся в ContentArea?
Ответ на оба вопроса да. Теоретически все потомки могут знать обо всех переменных, которые хранятся в родительских компонентах. Они также могут вызывать функции предков но с большой оговоркой. Это возможно, только если такие значения (значения состояния или функции) в явном виде переданы потомкам через пропсы. В противном случае значения состояния или функции компонента не будут доступны его дочернему компоненту.

В небольших приложениях и утилитах это особой роли не играет. Например, если компоненту TicketDetail нужно обратиться к переменным состояния, которые хранятся в TicketRow, достаточно сделать так, чтобы компонент TicketRow передавал эти значения своему потомку TicketDetail через один или несколько пропсов. Точно так же дело обстоит в случае, когда компоненту TicketDetail нужно вызвать функцию, которая находится в TicketRow. Компонент TicketRow передаст эту функцию своему потомку TicketDetail через проп. Головная боль начинается, когда какому-нибудь компоненту, расположенному далекоооо вниз по дереву, нужно получить доступ к состоянию или функции компонента, расположенного вверху иерархии.
Для решения этой проблемы в React переменные и функции традиционно передаются на все уровни вниз. Но это загромождает код, отнимает ресурсы и требует серьезного планирования. Нам пришлось бы передавать значения на много уровней примерно так:
ContentArea MainContentArea MyDashboard MyOpenTickets TicketTable TicketRow TicketDetail
То есть для того чтобы передать переменную состояния из ContentArea в TicketDetail, нам нужно проделать огромную работу. Опытные разработчики понимают, что возникает безобразно длинная цепочка передачи значений и функций в виде пропсов через промежуточные уровни компонентов. Решение настолько громоздкое, что из-за него я даже пару раз бросал изучение React.

Чудовище по имени Redux


Я не единственный, кто считает, что передавать через пропсы все значения состояния и все функции, общие для компонентов, очень непрактично. Вряд ли вы найдете хоть сколько-нибудь сложное React-приложение, к которому не прилагался бы инструмент управления состоянием. Таких инструментов не так уж мало. Лично я обожаю MobX. Но, к сожалению, отраслевым стандартом считается Redux.

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

Поэтому они придумали Redux.
Если React это Мона Лиза, то Redux это пририсованные ей усы. Если вы используете Redux, вам придется написать тонну шаблонного кода почти в каждом файле проекта. Устранение проблем и чтение кода становятся адом. Бизнес-логика выносится куда-то на задворки. В коде разброд и шатание.

Но если перед разработчиками стоит выбор: React + Redux или React без каких-либо сторонних инструментов управления состоянием, они почти всегда выбирают React + Redux. Поскольку библиотеку Redux разработали авторы ядра React, она по умолчанию считается одобренным решением. А большинство разработчиков предпочитают использовать решения, которые были вот так молчаливо одобрены.

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

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

Новый взгляд на общие значения в React


Я слишком упрям, чтобы так просто сдаться. Взглянув на Redux, я понял, что нужно искать другие решения. Я могу использовать Redux. И я работал в командах, которые пользовались этой библиотекой. В общем, я понимаю, что она делает. Но это не значит, что Redux мне нравится.
Как я уже говорил, если без отдельного инструмента для управления состоянием не обойтись, то MobX примерно в миллион раз лучше, чем Redux! Но меня мучает более серьезный вопрос. Он касается коллективного разума React-разработчиков:

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

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

Представим, что в гипотетическом приложении, о котором я писал выше, мы создаем такой файл:

// components.jslet components = {};export default components;


И все.Только две короткие строчки кода. Мы создаем пустой объект старый добрый JS-объект. Экспортируем его по умолчанию с помощью export default.

Теперь давайте посмотрим, как может выглядеть код внутри компонента <ContentArea>:

// content.area.jsimport components from './components';import MainContentArea from './main.content.area';import React from 'react';export default class ContentArea extends React.Component {   constructor(props) {      super(props);      components.ContentArea = this;   }   consoleLog(value) {      console.log(value);   }   render() {      return <MainContentArea/>;   }}


По большей части он выглядит, как вполне нормальный классовый React-компонент. У нас есть простая функция render(), которая обращается к следующему компоненту вниз по дереву. У нас есть небольшая функция console.log(), которая выводит в консоль результат выполнения кода, и конструктор. Но в конструкторе есть некоторые нюансы.

В самом начале мы импортировали простой объект components. Затем в конструкторе мы добавили новое свойство к объекту components с именем текущего React-компонента (this).В этом свойстве мы ссылаемся на компонент this. Теперь при каждом обращении к объекту components у нас будет прямой доступ к компоненту <ContentArea>.

Давайте посмотрим, что происходит на нижнем уровне иерархии. Компонент <TicketDetail> может быть таким:

// ticket.detail.jsimport components from './components';import React from 'react';export default class TicketDetail extends React.Component {   render() {      components.ContentArea.consoleLog('it works');      return <div>Here are the ticket details.</div>;   }}


А происходит вот что. При каждом рендере компонента TicketDetail будет вызываться функция consoleLog(), которая хранится в компоненте ContentArea.

Обратите внимание, что функция consoleLog() не передается по всей иерархии через пропсы. Фактически функция consoleLog() не передается никуда вообще никуда, ни в один компонент.
И тем не менее TicketDetail может вызвать функцию consoleLog(), которая хранится в ContentArea, потому что мы выполнили два действия:

  1. Компонент ContentArea при загрузке добавил в общий объект components ссылку на себя.
  2. Компонент TicketDetail при загрузке импортировал общий объект components, то есть у него был прямой доступ к компоненту ContentArea, несмотря на то что свойства ContentArea не передавались компоненту TicketDetail через пропсы.


Этот подход работает не только с функциями/колбэками. Его можно использовать для прямого запроса значений переменных состояния. Представим, что <ContentArea> выглядит так:

// content.area.jsimport components from './components';import MainContentArea from './main.content.area';import React from 'react';export default class ContentArea extends React.Component {   constructor(props) {      super(props);      this.state = { reduxSucks:true };      components.ContentArea = this;   }   render() {      return <MainContentArea/>;   }}


Тогда мы можем написать <TicketDetail> вот так:

// ticket.detail.jsimport components from './components';import React from 'react';export default class TicketDetail extends React.Component {   render() {      if (components.ContentArea.state.reduxSucks === true) {         console.log('Yep, Redux is da sux');      }      return <div>Here are the ticket details.</div>;   }}


Теперь при каждом рендере компонента <TicketDetail> он будет искать значение переменной state.reduxSucks в <ContentArea>. Если переменная вернет значение true, функция console.log() выведет в консоль сообщение. Это произойдет, даже если значение переменной ContentArea.state.reduxSucks никогда не передавалось вниз по дереву ни одному из компонентов через пропсы. Таким образом, благодаря одному простому базовому JS-объекту, который обитает за пределами стандартного жизненного цикла React, мы можем сделать так, чтобы любой потомок мог считывать переменные состояния непосредственно из любого родительского компонента, загруженного в объект components. Мы даже можем вызывать функции родительского компонента в его потомке.

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

Для начала в компоненте <ContentArea> создадим простую функцию, которая меняет значение переменной reduxSucks.

// content.area.jsimport components from './components';import MainContentArea from './main.content.area';import React from 'react';export default class ContentArea extends React.Component {   constructor(props) {      super(props);      this.state = { reduxSucks:true };      components.ContentArea = this;   }   toggleReduxSucks() {      this.setState((previousState, props) => {         return { reduxSucks: !previousState.reduxSucks };      });   }   render() {      return <MainContentArea/>;   }}


Затем в компоненте <TicketDetail> мы вызовем этот метод через объект components:

// ticket.detail.jsimport components from './components';import React from 'react';export default class TicketDetail extends React.Component {   render() {      if (components.ContentArea.state.reduxSucks === true) {         console.log('Yep, Redux is da sux');      }      return (         <>            <div>Here are the ticket details.</div>            <button onClick={() => components.ContentArea.toggleReduxSucks()}>Toggle reduxSucks</button>         </>      );   }}


Теперь после каждого рендера компонента <TicketDetail> пользователь сможет нажимать кнопку, которая будет изменять (переключать) значение переменной ContentArea.state.reduxSucks в режиме реального времени, даже если функция ContentArea.toggleReduxSucks() никогда не передавалась вниз по дереву через пропсы.

С таким походом родительский компонент может вызвать функцию непосредственно из своего потомка. Вот как это можно сделать.
Обновленный компонент <ContentArea> будет выглядеть так:

// content.area.jsimport components from './components';import MainContentArea from './main.content.area';import React from 'react';export default class ContentArea extends React.Component {   constructor(props) {      super(props);      this.state = { reduxSucks:true };      components.ContentArea = this;   }   toggleReduxSucks() {      this.setState((previousState, props) => {         return { reduxSucks: !previousState.reduxSucks };      });      components.TicketTable.incrementReduxSucksHasBeenToggledXTimes();   }   render() {      return <MainContentArea/>;   }}


А теперь добавим логику в компонент <TicketTable>. Вот так:

// ticket.table.jsimport components from './components';import React from 'react';import TicketRow from './ticket.row';export default class TicketTable extends React.Component {   constructor(props) {      super(props);      this.state = { reduxSucksHasBeenToggledXTimes: 0 };      components.TicketTable = this;   }   incrementReduxSucksHasBeenToggledXTimes() {      this.setState((previousState, props) => {         return { reduxSucksHasBeenToggledXTimes: previousState.reduxSucksHasBeenToggledXTimes + 1};      });         }   render() {      const {reduxSucksHasBeenToggledXTimes} = this.state;      return (         <>            <div>The `reduxSucks` value has been toggled {reduxSucksHasBeenToggledXTimes} times</div>            <TicketRow data={dataForTicket1}/>            <TicketRow data={dataForTicket2}/>            <TicketRow data={dataForTicket3}/>         </>      );   }}


В результате компонент <TicketDetail> не изменился. Он все еще выглядит так:

// ticket.detail.jsimport components from './components';import React from 'react';export default class TicketDetail extends React.Component {   render() {      if (components.ContentArea.state.reduxSucks === true) {         console.log('Yep, Redux is da sux');      }      return (         <>            <div>Here are the ticket details.</div>            <button onClick={() => components.ContentArea.toggleReduxSucks()}>Toggle reduxSucks</button>         </>      );   }}


Вы заметили странность, связанную с этими тремя классами? В иерархии нашего приложения ContentArea это родительский компонент для TicketTable, который является родительским компонентом для TicketDetail. Это означает, что когда мы монтируем компонент ContentArea, он еще не знает о существовании TicketTable.А функция toggleReduxSucks(), записанная в ContentArea, неявно вызывает функцию потомка: incrementReduxSucksHasBeenToggledXTimes().Получается, код работать не будет, так?

А вот и нет.

Смотрите. Мы создали в приложении несколько уровней, и есть только один путь вызова функции toggleReduxSucks(). Вот так.
  1. Монтируем и рендерим ContentArea.
  2. В ходе этого процесса в объект components загружается ссылка на ContentArea.
  3. В результате монтируется и рендерится TicketTable.
  4. В ходе этого процесса в объект components загружается ссылка на TicketTable.
  5. В результате монтируется и рендерится TicketDetail.
  6. У пользователя появляется кнопка Изменить значение reduxSucks (Toggle reduxSucks).
  7. Пользователь нажимает кнопку Изменить значение reduxSucks.
  8. Нажатие кнопки вызывает функцию toggleReduxSucks(), которая записана в компоненте ContentArea.
  9. Это в свою очередь вызывает функцию incrementReduxSucksHasBeenToggledXTimes() из компонента TicketTable .
  10. Все работает, потому что к тому моменту, когда пользователь сможет нажать кнопку Изменить значение reduxSucks, ссылка на компонент TicketTable будет загружена в объект components. А функция toggleReduxSucks() при вызове из ContentArea сможет найти ссылку на функцию incrementReduxSucksHasBeenToggledXTimes(), записанную в TicketTable, в объекте components.

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

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


Как я уже объяснил, я глубоко уверен в том, что Redux не идет ни в какое сравнение с MobX. И когда мне выпадает честь работать над проектом с нуля (к сожалению, нечасто), я всегда агитирую за MobX. Не за Redux. Но когда я разрабатываю собственные приложения, я вообще редко использую сторонние инструменты управления состоянием практически никогда. Вместо этого я просто-напросто кеширую объекты/компоненты, когда это возможно. А если это подход не работает, я частенько возвращаюсь к решению, которое используется в React по умолчанию, то есть просто передаю функции/переменные состояния через пропсы.

Известные проблемы этого подхода


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

  • Лучше всего он работает с одиночками.
    Например, в нашей иерархии в компоненте находятся компоненты со связью ноль-ко-многим. Если вы захотите кешировать ссылку на каждый потенциальный компонент внутри компонентов (и их дочерних компонентов ) в кеш components, вам придется сохранить их в массив, и тут могут возникнуть сложности. Я всегда избегал этого.
    При кешировании объекта components предполагается, что мы не можем использовать переменные/функции из других компонентов, если они не были загружены в объект components. Это очевидно.
    Если архитектура вашего приложения делает такой подход нецелесообразным, то не надо его использовать. Он идеально подходит для одностраничных приложений, когда мы уверены в том, что родительский компонент всегда монтируется раньше потомка. Если вы решили сослаться на переменные/функции потомка непосредственно из родительского компонента, создавайте такую структуру, которая будет выполнять эту последовательность только после загрузки потомка в кеш components.
    Можно считывать переменные состояния из других компонентов, ссылки на которые хранятся в кеше components, но если вы захотите обновить такие переменные (через setState()), вам придется вызвать функцию setState(), которая записана в соответствующем компоненте.


    Ограничение ответственности


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

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

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

    Я обнаружил, что JS-разработчики и, в частности, React-разработчики бывают слишком категоричны. Иногда они действительно объясняют, почему подход А неправильный, а подход Б правильный. Но в большинстве случаев они просто смотрят на фрагмент кода и объявляют, что он плохой, даже если сами не могут объяснить, почему.

    Так почему же этот подход так раздражает React-разработчиков?


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

    • С таким подходом о чистых функциях можно забыть, он захламляет приложение жесткими зависимостями.
      Окей...Понял. Но те самые люди, которые с ходу отвергли этот подход, с удовольствием будут использовать Redux (или MobX, или любое другое средство управления состоянием) почти со всеми классами/функциями в своих React-приложениях. Я не отрицаю, что иногда без инструментов управления состоянием действительно сложно обойтись. Но любой такой инструмент по своему характеру это гигантский генератор зависимостей. Каждый раз, когда вы используете инструменты управления состоянием с функциями/классами, вы захламляете приложение зависимостями. Обратите внимание: я не говорил, что нужно отправлять каждую функцию или класс в кеш объекта components. Вы самостоятельно решаете, какие именно функции/классы будут кешироваться в components, а какие функции/классы будут обращаться к тому, что вы поместили в кеш components. Если вы пишете чистую вспомогательную функцию/класс, то наверняка моя идея с кешем components вам не подходит, потому что для кеширования в components компоненты должны знать о других компонентах приложения.Если вы пишете компонент, который будет использоваться в разных фрагментах кода вашего приложения или даже в разных приложениях, не применяйте этот подход. Но опять же, если вы создаете такой глобальный компонент, в нем не нужно использовать ни Redux, ни MobX, ни какое-либо еще средство управления состоянием.
    • Просто в React так не делается. Или Это не соответствует отраслевым стандартам.
      Ага Это мне говорили не раз. И знаете что? Когда я это слышу, я даже немножко перестаю уважать своего собеседника. Если единственная причина это какое-то размытое так не делается или отраслевой стандарт, который сегодня один, а завтра другой, то разработчик просто чертов лентяй. Когда появилась React, у нас не было вообще никаких инструментов управления состоянием. Но люди начали изучать эту библиотеку и решили, что они нужны. И их создали.Если вы действительно хотите соответствовать отраслевым стандартам, просто передавайте все переменные состояния и все обратные вызовы функций через пропсы.Но если вам кажется, что базовая реализация React не удовлетворяет ваши потребности на 100 %, откройте глаза (и разум) и взгляните повнимательней на нестандартные решения, которые не были одобрены лично господином Дэном Абрамовым.


    Итак, что скажете В?


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

    Этот подход действительно плох? Ну правда. Я хочу знать. Если это действительно антипаттерн, я буду безмерно благодарен тем, кто обоснует его неправильность. Ответ я к такому не привык меня не устроит. Нет, я не зациклился на этом методе. Я не утверждаю, что это панацея для React-разработчиков. И я признаю, что он подходит не для всех ситуаций. Но может хоть кто-нибудь объяснить мне, что в нем не так?

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

    Бесплатные уроки:


Подробнее..

Разбираем тестовое задание на должность фронтенд-разработчика на Vue.js

22.09.2020 20:11:13 | Автор: admin

Первое правило тестовых заданий - никогда не делайте тестовые задания!

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

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

Для ознакомления с заданием, которое я получил, прошу под спойлер:

Техническое задание:

Средствами Vue.js реализуйте небольшое SPA приложение для заметок.

Каждая заметка имеет название и список задач (todo list), далее - Todo. Каждый пункт Todo состоит из чекбокса и относящейся к нему текстовой подписи.

Приложение состоит всего из 2х страниц.

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

  • перейти к созданию новой заметки

  • перейти к изменению

  • удалить (необходимо подтверждение)

Страница изменения заметки позволяет определенную заметку отредактировать, отметить пункты Todo, а после сохранить изменения. Действия с заметкой:

  • сохранить изменения

  • отменить редактирование (необходимо подтверждение)

  • удалить (необходимо подтверждение)

  • отменить внесенное изменение

  • повторить отмененное изменение Действия с пунктами Todo:

  • добавить

  • удалить

  • отредактировать текст

  • отметить как выполненный

Требования к функционалу:

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

  • Подтверждение действий (удалить заметку) выполняется с помощью диалогового окна.

  • Интерфейс должен отвечать требованиям usability.

  • После перезагрузки страницы состояние списка заметок должно сохраняться.

  • Можно пренебречь несоответствием редактирования текста с помощью кнопок отменить/повторить и аналогичным действиям с помощью комбинацияй клавиш (Ctrl+Z, Command+Z, etc.).

Технические требования:

  • Диалоговые окна должны быть реализованы без использования "alert", "prompt" и "confirm".

  • В качестве языка разработки допускается использовать JavaScript или TypeScript.

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

  • Верстка должна быть выполнена без использования UI библиотек (например Vuetify).

  • Адаптивность не обязательна, но приветствуется.

  • Логика приложения должна быть разбита на разумное количество самодостаточных Vue-компонентов.

Особое внимание стоит обратить на следующие моменты:

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

  • Читабельность и наличие элементарной архитектуры.

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

Тестовое задание должно быть предоставлено в следующем виде:

  • Ссылка на публичный репозиторий (GitHub, BitBucket, GitLab) с исходным кодом.

  • Ссылка на сайт для тестирования функционала. Или Dockerfile и docker-compose.yaml, позволяющие развернуть локально командой docker-compose up работоспособную копию сайта.ехническое заданиеехническое заданиеехническое задание

Вот что у меня в итоге получилось.

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

Разберем по пунктам задание и возможные способы его решения:

Средствами Vue.js реализуйте небольшое SPA приложение для заметок. Тут все просто: используем Vue CLI для создания проекта.

Каждая заметка имеет название и список задач todo list, (далее - Todo). Каждый пункт Todo состоит из чекбокса и относящейся к нему текстовой подписи. - А вот и первая сложность, у нас будет два уровня абстракции: множество заметок и множество дел, которые составляют заметку.

Приложение состоит всего из 2х страниц. Тут возник вопрос - использовать ли Vue Router? Это же всего две страницы. Дальше из задания мы узнаем, что первая страница используется для отображения списка заметок, а вторая - для редактирования отдельной заметки. Конечно, можно переключать компоненты условным оператором, но раутер даст возможность перехода на сгенерированные страницы для каждой заметки. Но это уже больше двух страниц, или сгенерированные страницы считаются за одну, так как они по одному шаблону сделаны? После недолгих колебаний я решил все-таки его использовать.

Дальше следуют подробности по каждой странице, обратим внимание на два пункта:

  • отменить внесенное изменение

  • повторить отмененное изменение

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

Do и RedoDo и Redo
  • Все действия на сайте должны происходить без перезагрузки страницы. Это означает стандартное SPA, мы и так используем Vue CLI.

  • Подтверждение действий (удалить заметку) выполняется с помощью диалогового окна.

  • Диалоговые окна должны быть реализованы без использования "alert", "prompt" и "confirm".

    Диалоговое окно должно создавать Promise, который зависит от решения юзера. Желательно, чтобы решение было переиспользуемым для всех наших случаев. Помучавшись над решением, я пришел к выводу, что лучше использовать готовое решение, к тому же запретов на пакеты не было. Я использовал vue-modal-dialogs - очень удобная библиотека, рекомендую. Надеюсь её перепишут для Vue 3.

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

  • После перезагрузки страницы состояние списка заметок должно сохраняться. - Так как серверной части у нашего приложения не планируется, заметки следует сохранять на стороне клиента, для этого есть два решения Cookie и localStorage. Выбираем кому что ближе. Я выбрал localStorage. К тому же я решил не использовать Vuex, а вместо него использовать локальное хранилище, как единыйисточник истины. Для небольшого приложения без бэка это выглядит разумным решением, экономящем время, в других случаях я не рекомендовал бы так делать.

  • В качестве языка разработки допускается использовать JavaScript или TypeScript. - а разве есть еще варианты? Честно говоря, ТЗ оставляет ощущение, что тот кто его составлял плохо знаком с Vue. TypeScript на Vue 2 спорно применять, слабая поддержка. Посмотрим, что будет на Vue 3.

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

  • В качестве сборщика, если это необходимо, используйте Webpack. Vue CLI это и есть Webpack, настроенный для удобства создания SPA на Vue.js

  • Верстка должна быть выполнена без использования UI библиотек (например Vuetify) - это минус. Для того чтобы "оживить" приложение, я использовал Material Icons от Гугла, вместо кнопок. Не знаю, оценил ли заказчик, он так и не ответил.

  • Адаптивность не обязательна, но приветствуется. - flexbox в помощь.

  • Логика приложения должна быть разбита на разумное количество самодостаточных Vue-компонентов. - у меня вышло семь: 2 на страницы, еще заметка, тудушка, заголовок заметки, кнопка-иконка и диалоговое окно.

    Остальные пункты менее важны.

Еще из вкусностей:

Я использовал пакет v-click-outside. Название говорит само за себя. Он добавляет директиву, которая срабатывает при клике вне элемента. Можно было написать самому, но я решил не изобретать велосипед. Использовал для отмены редактирования тудушки, если пользователь кликнул где-то еще. Это в задании не было, включим это в юзабилити.

Еще мне пришла в голову такая мысль: а что делать, если юзер захочет покинуть страницу редактирования заметки? Куда повесить вызов диалогового окна: на историю браузера, на кнопки меню? Есть элегантное решение. Vue-Router добавляет хуки жизненного цикла в компонент. Хук beforeRouteLeave поможет нам во всех ситуациях когда пользователь пытается покинуть страницу. Пусть наш хук вызывает модальное окно. Только не забыть сделать его асинхронным, ведь окно возвращает промис. Например, вот так:

  async beforeRouteLeave (to, from, next) {    if (await confirm('Do you realy want to leave this page?',       'All unsaved changes will be lost.')) {        this.clearNote()        next()      } else{        next(from)      }  }

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

Если возникли вопросы и замечания, прошу в комментарии или личку.

Подробнее..

Перевод Как Chrome DevTools с велосипеда на стандарт пересели

25.09.2020 14:16:06 | Автор: admin


Краткая заметка о том, как в команде Chrome DevTools проходила миграция с внутреннего загрузчика модулей на стандартные модули JavaScript. Рассказываем, насколько и почему затянулась миграция, о скрытых издержках миграции и расскажем о выводах команды DevTools после завершения миграции. Но начнём с истории инструментов веб-разработчика.

Введение


Как вы, возможно, знаете, Chrome DevTools это веб-приложение HTML, CSS и JavaScript. За эти годы DevTools стала многофункциональной, умной и хорошо осведомленной о современной веб-платформе. Хотя DevTools расширился, его архитектура во многом напоминает первоначальную, когда инструмент был частью WebKit.

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

Вначале не было ничего


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

Первое упоминание о модульной системе в DevTools относится к 2012 году: это было введение списка модулей с соответствующим списком исходников. Часть инфраструктуры Python, используемой в то время для компиляции и сборки DevTools. В 2013 года модули были извлечены в файл frontend_modules.json этим коммитом, а затем, в 2014 году, в отдельные module.json (здесь). Пример module.json:

{  "dependencies": [    "common"  ],  "scripts": [    "StylePane.js",    "ElementsPanel.js"  ]}


С 2014 года module.json используется в инструментах разработчика для указания на модули и исходные файлы. Тем временем экосистема веба быстро развивалась, и было создано множество форматов модулей: UMD, CommonJS и в конечном итоге стандартизированные модули JavaScript. Однако DevTools застрял на module.json. Не стандартизированная, уникальной модульная система имела несколько недостатков:

  1. module.json требовал собственные инструменты сборки.
  2. Не было интеграции с IDE. Конечно же, она требовала специальных инструментов для создания файлов, понятных ей: (оригинальный скрипт генерации jsconfig.json для VS Code).
  3. Функции, классы и объекты были помещены в глобальную область видимости, чтобы сделать возможным совместное использование между модулями.
  4. Был важен порядок перечисления файлов. Не было никакой гарантии, что код, на который вы полагаетесь, загружен, кроме проверки человеком.

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

Преимущества стандарта


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

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

Поскольку модули JavaScript были стандартными, это означало, что IDE, такие как VS Code, инструменты проверки типов, похожие на компилятор Closure/TypeScript, и инструменты сборки вроде Rollup и минификаторов смогут понять написанный исходный код. Более того, когда новый человек присоединяется к команде DevTools, ему не приходится тратить время на изучение проприетарного module.json.

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

Сколько стоит блеск новизны?


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

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

Последний пункт оказался очень важным. Несмотря на то, что теоретически мы могли добраться до модулей JavaScript, во время миграции мы получили бы код, который должен был бы учитывать оба типа модулей. Такое не только технически сложно, но и означает, что все инженеры, работающие над DevTools, должны знать, как работать в такой среде. Они должны были бы постоянно спрашивать себя: Что происходит в этом коде, это module.json или JS, и как я могу внести изменения?.

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


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

  1. Убедиться, что стандартные модули максимально полезны.
  2. Убедиться, что интеграция с существующими модулями на базе module.json безопасна и не приводит к негативному воздействию на пользователя (ошибки регрессии, разочарование пользователя).
  3. Давать руководства по миграции DevTools. В первую очередь с помощью встроенных в процесс сдержек и противовесов, чтобы предотвратить случайные ошибки.

Электронная таблица, преобразования и технический долг


Цель была ясна. Но ограничения module.json оказалось трудно обойти. Потребовалось несколько итераций, прототипов и архитектурных изменений, прежде чем мы разработали удобное решение. Мы закончили тем, что написали проектный документ со стратегией миграции. В этом документе указывалась первоначальная оценка времени: 2-4 недели.

Самая интенсивная часть миграции заняла 4 месяца, а от начала до конца прошло 7 месяцев!


Первоначальный план, однако, выдержал испытание временем: мы хотели научить среду выполнения DevTools загружать все файлы, старым способом использовать перечисленные в массиве scripts module.json, в то время как все файлы, перечисленные в массиве modules должны были загружаться динамическим импортом языка. Любой файл, который будет находиться в массиве modules, может работать с import и export из ES6.

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


Фрагмент таблицы миграции здесь.

Фаза экспорта


Первым этапом было добавление операторов экспорта для всех сущностей, которые должны быть разделены между модулями/файлами. Трансформация автоматизировалась с помощью запуска скрипта для каждой папки. Допустим, в module.json есть такая сущность:

Module.File1.exported = function() {  console.log('exported');  Module.File1.localFunctionInFile();};Module.File1.localFunctionInFile = function() {  console.log('Local');};


Здесь Module это имя модуля. File1 имя файла. В дереве кода это выглядит так: front_end/module/file1.JS.

Код выше преобразуется в такой:

export function exported() {  console.log('exported');  Module.File1.localFunctionInFile();}export function localFunctionInFile() {  console.log('Local');}/** Legacy export object */Module.File1 = {  exported,  localFunctionInFile,};


Первоначально мы планировали переписать импорт в один файл на этом этапе. Например, в приведенном выше примере мы бы переписали Module.File1.localFunctionInFile на localFunctionInFile. Однако мы осознали, что было бы проще автоматизировать и безопаснее разделить эти два преобразования. Таким образом, перенос всех сущностей в один файл станет второй подфазой импорта.

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

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

В конце концов, обновление самой первой папки (добавление export) заняло около недели и несколько попыток с перекладываниями.

Фаза импорта


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

Например, следующие сущности module.json:

Module.File1.exported();AnotherModule.AnotherFile.alsoExported();SameModule.AnotherFile.moduleScoped();


Преобразуются в:

import * as Module from '../module/Module.js';import * as AnotherModule from '../another_module/AnotherModule.js';import {moduleScoped} from './AnotherFile.js';Module.File1.exported();AnotherModule.AnotherFile.alsoExported();moduleScoped();


Однако при таком подходе есть оговорки:

  1. Не каждая сущность была названа по принципу Module.File.symbolName. Некоторые сущности были названы Modele.File или даже Module.CompletelyDifferentName. Несоответствие означало, что мы должны были создать внутреннее сопоставление от старого глобального объекта к новому импортированному объекту.
  2. Иногда возникали конфликты имен moduleScoped. Наиболее заметно, что мы использовали шаблон объявления определенных типов Events, там, где каждая сущность названа просто Events. Это означало, что при прослушивании нескольких типов событий, объявленных в разных файлах, в операторе import у этих Events возникнет коллизия именования.
  3. Как оказалось, между файлами существовали циклические зависимости. Это было прекрасно в контексте глобальной области видимости, так как сущность использовалась после загрузки всего кода. Однако, если вам требуется импорт, циклическая зависимость проявит себя. Такое не приводит к проблеме сразу, если только у вас нет побочных вызовов функций в коде глобальной области видимости, (они были у DevTools). В общем, потребовалось некоторое хирургическое вмешательство и рефакторинг, чтобы обезопасить трансформацию.


Дивный новый мир модулей JavaScript


В феврале 2020 года, через 6 месяцев после старта в сентябре 2019 года, были выполнены последние очистки в папке ui/. Так миграция неофициально закончилась. Когда осела пыль, мы официально отметили миграцию как законченную 5 марта 2020 года.

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

Статистика


Консервативные оценки количества CL (аббревиатура changelist термин, используемый в Gerrit, аналогичное пул-реквесту GitHub), участвующих в этой миграции, составляют около 250 CL, в основном выполняемых 2 инженерами. У нас нет окончательной статистики о размере внесенных изменений, но консервативная оценка измененных строк (сумма абсолютной разницы между вставками и удалениями для каждого CL) составляет примерно 30 000 строк, то есть около 20% всего интерфейсного кода DevTools.

Первый файл с применением экспорта поддерживается в Chrome 79, выпущенном в стабильном релизе в декабре 2019 года. Последнее изменение для перехода на импорт поставляется в Chrome 83, выпущенном в стабильном релизе в мае 2020 года.

Мы знаем об одной регрессии из-за миграции в стабильном Chrome. Автозавершение фрагментов кода в меню команд сломалось из-за постороннего экспорта по умолчанию. Было и несколько других регрессий, но наши автоматизированные тестовые наборы и пользователи Chrome Canary сообщили о них. Мы исправили ошибки до того, как они могли попасть в стабильные релизы Chrome.

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

Чему мы научились?


  1. Принятые в прошлом решения могут оказать долгосрочное влияние на ваш проект. Несмотря на то, что модули JavaScript (и другие форматы модулей) были доступны в течение довольно долгого времени, DevTools не был в состоянии оправдать миграцию. Решение о том, когда мигрировать, а когда нет, трудно и держится на обоснованных догадках.
  2. Первоначальные оценки времени недели, а не месяцы. В значительной степени это связано с тем, что мы обнаружили больше неожиданных проблем, чем ожидали при первоначальном анализе затрат. Даже при том, что план миграции был основательным, технический долг блокировал работу чаще, чем нам хотелось бы.
  3. Миграция включала большое количество (казалось, не связанных между собой) очисток технического долга. Переход к современному стандартизированному формату модулей позволил нам перестроить лучшие практики кодирования на современную веб-разработку. Например, мы смогли заменить пользовательский упаковщик Python на минимальную конфигурацию Rollup.
  4. Несмотря на большое влияние на нашу кодовую базу (~20% измененного кода), было зарегистрировано очень мало регрессий. Хотя было много проблем с миграцией первых двух файлов, через некоторое время у нас был надежный, частично автоматизированный рабочий процесс. Это означало, что негативное влияние на наших стабильных пользователей было минимальным.
  5. Научить коллег тонкостям конкретной миграции трудно, а иногда и невозможно. Миграции такого масштаба трудно отслеживать и они требуют большого объема знаний в предметной области. Передача этих знаний другим людям, работающим в той же кодовой базе, сама по себе нежелательна для работы, которую они выполняют. Знание того, чем делиться, а чем не делиться необходимое искусство. Поэтому крайне важно сократить количество крупных миграций или, по крайней мере, не выполнять их одновременно.

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя онлайн-курсы SkillFactory:



Подробнее..

Перевод Compose повсюду композиция функций в JavaScript

25.09.2020 18:15:34 | Автор: admin
Перевод статьи подготовлен специально для студентов курса JavaScript Developer.Basic.





Введение


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

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

Основы


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

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

var compose = function(f, g) {    return function(x) {        return f(g(x));    };};


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

А теперь рассмотрим этот код:

function reverseAndUpper(str) {  var reversed = reverse(str);  return upperCase(reversed);}


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

var reverseAndUpper = compose(upperCase, reverse);


Теперь можно использовать функцию reverseAndUpper:

reverseAndUpper('тест'); // ТСЕТ


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

function reverseAndUpper(str) {  return upperCase(reverse(str));}


Этот вариант выглядит более элегантным, его проще сопровождать и использовать повторно.

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

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

var compose = function() {  var funcs = Array.prototype.slice.call(аргументы);   return funcs.reduce(function(f,g) {    return function() {      return f(g.apply(this, аргументы));    };  });};


С такой функцией мы можем написать примерно такой код:

Var doSometing = compose(upperCase, reverse, doSomethingInitial); doSomething('foo', 'bar');


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

Примеры


Начнем с простого:

function notEmpty(str) {    return ! _.isEmpty(str);}


Функция notEmpty это отрицание значения, возвращаемого функцией _.isEmpty.

Мы можем добиться такого же результата с использованием функции _.compose из библиотеки Lodash. Напишем функцию not:

function not(x) { return !x; } var notEmpty = _.compose(not, _.isEmpty);


Теперь можно использовать функцию notEmpty с любым аргументом:

notEmpty('foo'); // truenotEmpty(''); // falsenotEmpty(); // falsenotEmpty(null); // false


Это очень простой пример. Давайте рассмотрим что-нибудь посложнее:
функция findMaxForCollection возвращает максимальное значение из коллекции объектов со свойствами id и val (значение).

function findMaxForCollection(data) {    var items = _.pluck(data, 'val');    return Math.max.apply(null, items);} var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data);


Для решения этой задачи можно использовать функцию compose:

var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck); var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data, 'val'); // 6


Здесь есть над чем поработать.

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

function pluck(key) {    return function(collection) {        return _.pluck(collection, key);    }}


Функцию findMaxForCollection нужно еще немного подкрутить. Давайте создадим собственную функцию max.

function max(xs) {    return Math.max.apply(null, xs);}


Теперь можно сделать функцию compose более элегантной:

var findMaxForCollection = _.compose(max, pluck('val')); findMaxForCollection(data);


Мы написали собственную функцию pluck и можем использовать ее только со свойством 'val'. Возможно, вам непонятно, зачем писать собственный метод выборки, если в Lodash уже есть готовая и удобная функция _.pluck. Проблема в том, что _.pluck ожидает коллекцию в качестве первого аргумента, а мы хотим сделать по-другому. Изменив порядок следования аргументов, мы можем применить функцию частично, передав ключ в качестве первого аргумента; возвращаемая функция будет принимать данные (data).
Можно еще немного подшлифовать наш метод выборки. В Lodash есть удобный метод _.curry, который позволяет записать нашу функцию так:

function plucked(key, collection) {    return _.pluck(collection, key);} var pluck = _.curry(plucked);


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

function max(xs) {    return Math.max.apply(null, xs);} function plucked(key, collection) {    return _.pluck(collection, key);} var pluck = _.curry(plucked); var findMaxForCollection = _.compose(max, pluck('val')); var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data); // 6


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

var findMaxForCollection = _.compose(max, pluck('val'));


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

var data = [{id: 1, val: 5, active: true},             {id: 2, val: 6, active: false },             {id: 3, val: 2, active: true }];


Назовем эту функцию getMaxIdForActiveItems(data). Она принимает коллекцию объектов, отфильтровывает все активные объекты и возвращает максимальное значение из отфильтрованных.

function getMaxIdForActiveItems(data) {    var filtered = _.filter(data, function(item) {        return item.active === true;    });     var items = _.pluck(filtered, 'val');    return Math.max.apply(null, items);}


А можно сделать этот код поэлегантнее? В нем уже есть функции max и pluck, поэтому нам остается лишь добавить фильтр:

var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter); getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5


С функцией _.filter возникает та же проблема, которая была с _.pluck: мы не можем частично применить эту функцию, потому что она ожидает коллекцию в качестве первого аргумента. Мы можем изменить порядок следования аргументов в фильтре, обернув начальную функцию:

function filter(fn) {    return function(arr) {        return arr.filter(fn);    };}


Добавим функцию isActive, которая принимает объект и проверяет, присвоено ли флагу active значение true.

function isActive(item) {    return item.active === true;}


Функцию filter с функцией isActive можно применить частично, поэтому в функцию getMaxIdForActiveItems мы будем передавать только данные.

var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));


Теперь нам нужно лишь передать данные:

getMaxIdForActiveItems(data); // 5


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

var isNotActive = _.compose(not, isActive); var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));


Заключение


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

Ссылки


lodash
Hey Underscore, You're Doing It Wrong! (Эй, Underscore, ты все делаешь не так!)
@sharifsbeat



Читать ещё:


Подробнее..

Scala мертва?

22.09.2020 10:23:41 | Автор: admin

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

Предыстория:
мой основной бэкграунд - Java бэкенд. В какой-то момент стала интересна Scala. Я поработал около года в маленьком стартапе, где мы переписывали бэкенд с Python на Scala. Затем через некоторое время я начал искать варианты с переездом в цивилизованные страны и получил 4-5 офферов на Scala в нескольких Европейских странах и 1 оффер на Java Так я оказался в Австралии. И без Scala.

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

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

Здесь нужно сделать ремарку о том, что же из себя представляет рынок разработки в Австралии: здесь есть офисы нескольких крупных компаний типа Google, Amazon, Microsoft, есть Atlassian, который пытается быть на них похож, есть несколько заскорузлых монструозных банков (как везде) и огромная куча мелких (и не очень) стартапов. Как вы можете себе представить, почти никто из них не использует Scala вне контекста Data Science.

Компании можно разделить на 2 большие категории: устоявшиеся компании (в основном Java и, что меня очень сильно удивляет, dotNet, видимо это следствие как раз наличия офиса Microsoft и работы его маркетологов) и хипстерские стартапы с GoLang или NodeJS на бэкенде. Надо сказать, что здесь довольно сильно развито движение FullStack разработки, поэтому NodeJS очень популярен, так как позволяет фронтендерам писать бэкенд :)

На удивление, есть даже несколько (больше 1) компаний, пишущих на Clojure и Elixir. Kotlin тоже немного присутствует, но только совместно с Java.

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

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

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

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

Навороченный рантайм вытеснен Docker и Kubernetes, которые благодаря легкому автоскейлингу позволяют теоретически писать приложения даже без сборки мусора, просто перезапуская приложение каждые N минут. Вместо этого важными становятся: малый размер бинарника (здравствуй, GoLang), простота и предсказуемость (даже примитивность рантайма), интероперабельность с другими частями системы (здравствуй TypeScript и Swift на бэкенде).

В общем, к моему большому сожалению, похоже из всех JVM-based языков причины использовать на бэкенде остались только у Java, благодаря необходимости поддерживать огромное легаси и наличию большого количества разработчиков на рынке, ну и возможно Kotlin, благодаря Android (хотя как это связано с бэкендом я не придумал).

А теперь давайте сыграем в небольшую игру.

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

Раунд 1: Go vs Java

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

Раунд 2: Go vs Scala

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

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

Раунд 3: Java vs Scala

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

Раунд 4: Java vs Kotlin

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

Раунд 5: Kotlin vs Scala

См. Раунд 3.

Раунд 6: Scala/Java/Kotlin vs TypeScript

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

Раунд 7: Go vs TypeScript

Ну тут просто супер очевидно, даже вообще без вариантов.

А что выбрали бы вы?

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

В общем, резюмируя сказанное выше, повторю, что у меня складывается стойкое мнение, что микросервисы и Kubernetes постепенно убивают Java и другие языки на базе JVM. Понятно, что Java будет жить вечно, как Cobol, Kotlin будет жить в Android, а вот Scala, похоже, в Data Science. Благодаря современным технологиям становится совсем не важно, на чем написан твой бэкенд, если его можно быстро запускать/перезапускать/автоскейлить.

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

Подробнее..

Проекты Центра разработки Intel в России. Intel Integrated Performance Primitives

22.09.2020 10:23:41 | Автор: admin
Наш рассказ об очередном проекте Intel, сделанном в России. Это библиотека Intel Integrated Performance Primitives набор готовых к употреблению, высоко оптимизированных под различные архитектуры Intel, к тому же совершенно бесплатных базовых функций для работы с изображениями, сигналами и произвольными данными. Каким образом зародился этот проект, как развивался, что происходит в Intel IPP сейчас в статье под катом. А начнем мы, как это принято в резюме, с современности.

На КДПВ вход на этаж IPP в офисе Intel в Нижнем Новгороде

Что такое Intel IPP сейчас


Итак, Intel IPP это кирпичики функциональных примитивов, позволяющие существенно ускорить работу ПО, обрабатывающего медиа и данные, за счет специфичных для конкретных микроархитектур и платформ Intel оптимизаций, а также максимально широкого использования наборов векторных инструкций Intel SSE и Intel AVX всех версий.
Функции IPP покрывают четыре больших домена:

  • Обработка изображений;
  • Сжатие данных;
  • Обработка сигналов;
  • Криптография.

Текущая версия Intel IPP 2020 Update 2 содержит более 2 500 примитивов обработки изображений, 1 300 примитивов обработки сигналов, 500 компьютерного зрения и 300 криптографии.

Библиотека постоянно совершенствуется оптимизируется под новые платформы, добавляется новая функциональность и неизбежно удаляется старая, малоиспользуемая.
Intel IPP работает на любом устройстве с х86 архитектурой под управлением Linux, macOS, Windows и Android. То есть, поддерживаются процессоры не только Intel, но и других производителей, причём, работает IPP на них быстро, хотя, конечно, и не так супербыстро, как на устройствах Intel.

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

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

Для интересующихся скоростью работы на Intel Xeon и Core некоторые бенчмарки.

В настоящее время IPP доступна в составе Intel Parallel Studio XE, Intel System Studio, и просто сама по себе. И абсолютно бесплатно для персонального и коммерческого использования.

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

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

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

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

История IPP


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

В 1996 (или 1997 году по свидетельствам разных очевидцев) в головном офисе Intel Санта Клара прошло совещание по поводу дальнейших планов развития SPL и IPL с участием американских кураторов проекта и приглашенных специалистов из Сарова, среди которых был будущий архитектор, вдохновитель и руководитель команды IPP Борис Сабанин, а также Сергей Кириллов, в настоящее время возглавляющий работу над IPP-криптографией.

Саровская команда привезла свой список предложений, и одним из них было открытие для пользователей интерфейсов низкоуровневых функций IPL и SPL, так как они все равно уже были реализованы и оптимизированы, меж тем некоторым пользователям форматы данных IPL были неудобны, у них в готовых продуктах уже были свои сложившиеся форматы изображений. Предлагаемый прототип интерфейса IPP, использующий более простые в сравнении с IPL/ISL структуры, был создан Борисом Сабаниным в ходе обсуждения буквально на салфетке. Но в то время предложение российской стороны хотя и не было отвергнуто, но и не получило особой поддержки оно оказалось в середине списка с малым приоритетом. Но через пару лет кто-то в Intel вспомнил об этом (скорее всего, Шин Ли, впоследствии ставший евангелистом Intel IPP) и планы поменялись.


Книга про Intel IPP, написанная в 2004 Stewart Taylor, участником исторического совещания по созданию IPP (в то время только что нанятым в Intel бакалавром Стэнфорда)

Так началась работа над библиотеками Intel Performance Primitives, которые позднее были переименованы в Integrated Performance Primitives.

Внутренний вариант IPP, назовем его 1.0, был создан в 1999 году. Это был скорее Proof of Concept, прототип для подтверждения жизнеспособности концепции. Он не выпускался как продукт, но позволил определить и уточнить концепцию, архитектуру и спецификации IPP. Первая публичная версия сразу носила номер 2.0 и вышла в апреле 2002 года.

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


Картина Мост к саровской башне авторства Бориса Сабанина, известного не только IPP (здесь можно посмотреть другие картины Сабанина, в том числе его автопортрет)

Но наследием IPL/ISL дело не ограничилось. Практически сразу появились примитивы для криптографии и компрессии данных. Начались эксперименты в области Computer Vision, которые позже переросли в проект с OpenCV, использующий ускорение алгоритмов с помощью примитивов в ippCV домене.

Конечно же, это был не единственный эксперимент и ответвление в истории создания библиотек. IPP шли в ногу со всем Intel. Соответственно, например, пятая версия IPP помимо x86 поддерживала и процессор Intel XScale (ARM архитектура) и Intel Itanium (IA-64)! В разные годы IPP включали такие компоненты как трассировка лучей (realistic rendering), операции с небольшими матрицами (small matrix operations), проверка целостности данных (data integrity), видео и аудио кодеки.

Эту функциональность при желании можно использовать и сейчас с помощью пакета IPP Legacy Libraries, доступного для скачивания.

Более того, видеокодеки IPP в дальнейшем послужили основой другого известного продукта Intel Intel Media SDK, а трассировка лучей была реализована в проекте с открытом кодом Intel Embree.

Из интересных технологических опытов в области IPP-строения можно отметить пример Windows-драйвера для демонстрации возможности работы IPP в режиме ядра, а также версию IPP для работы на интегрированных Intel GPU, написанную на C for Metal.

Любопытно, что номера версий IPP сначала шли по порядку от 1 до 9, а потом начали обозначаться годом выхода 2017-2020.


Команда разработчиков Intel IPP в 2003 году

За время существования семейства библиотек IPP в работе над ними принимало участие более 100 человек в Сарове, Нижнем Новгороде и Москве. Сейчас штаб-квартира IPP находится в Нижнем Новгороде и выглядит очень привлекательно!


Оформление этажа IPP в Intel

IPP совсем не примитивная библиотека!


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

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

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

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

Поскольку приложения бывают разные, и IPP сделаны тоже разные. А именно, в комплект поставки IPP входят по два набора библиотек в 32 и 64-битной версиях: одна чисто однопоточная внутри, а вторая с внутренним распараллеливанием значительного количества функций при помощи OpenMP (точный список функций прилагается в сопроводительных документах). Кроме того, для библиотек обработки изображения есть и еще одна версия мнопоточный слой (Threading Layer), представляющая собой надстройку над однопоточной IPP и использующая либо OpenMP либо Intel TBB для внешнего распараллеливания работы над изображениями, разделяемыми для этого на фрагменты (тайлы). Исходные коды IPP Threading Layer доступны в пакете IPP и нужны тем, кто хочет получить максимально возможный контроль над параллельной работой своего кода.

Почти с самого возникновения IPP разработчикам пришлось озаботиться проблемой того, что конвейеры обработки изображений и сигналов, состоящие из отдельных функций IPP, работают медленнее, чем хотелось бы. Объясняется это просто: при вызове IPP-функций происходит как правило загрузка-выгрузка из кеша или даже из памяти, а эта операция может оказаться заметно дороже собственно вычислений. Этот эффект особенно заметен при обработке больших данных не тех, что называют big data, а например, изображений формата FullHD (не говоря уже о 4К).

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

В результате была реализована C++ надстройка над IPP, которая строила графы конвейеров, нарезала картинки на кусочки, и потом запускала параллельный цикл, который в каждом потоке выполнял не одну операцию, а весь IPP конвейер на отдельном тайле. В конце, конечно результаты склеивались. Сначала был сделан прототип, он показал приличное ускорение. Потом была создана настройка под названием DMIP (deferred-mode image processing). Далее, в 2011 году на одном из первых заседаний комитета стандартизации OpenVX в Khronos, DMIP был упомянут и горячо поддержан комитетом с учетом популярности графов у разработчиков железа. Так стандарт OpenVX получился основанным на графовой технологии. Стандарт OpenVX по разным причинам не получил достаточной популярности, но зато сейчас графовую парадигму поддерживает и развивает технологию команда Intel Graph API. А так как Graph API входит в OpenCV, OpenVINO, Movidius SDK, то налицо прямое влияние технологий IPP на стандарты компьютерного зрения и современные API.

IPP полезные ссылки


Еще раз приведем самые главные ссылки из этой статьи.


Intel IPP от первого лица


Предоставим слово людям, в разные годы сыгравшим важную роль в судьбе Intel Performance Primitives.

Владимир Дудник, руководитель команды Intel IPP в 2009-2011 годах
По-моему, у IPP по прежнему очень сильная концепция предоставить индустрии легкий, достаточно низкоуровневый слой абстракции от различий разных аппаратных платформ. Несмотря на существенный прогресс в развитии компиляторов, по-прежнему нет возможности из описания на языке высокого уровня получить оптимальный по размеру и производительности машинный код для функций преобразования Фурье, фильтров разного рода и т.п. Это означает что у IPP по-прежнему есть ниша, где она нужна и действительно полезна для ускорения разработки кросс-платформенных высокопроизводительных программ.
Думается, есть потенциальная возможность присмотреться к потребностям разных фреймворков глубокого обучения, там много достаточно стандартных, относительно простых операций, которые могут быть ускорены SIMD инструкциями. Часть из них, как правило многомерные свертки, покрываются библиотекой MKL, но и для IPP возможность применения можно найти.

Вадим Писаревский, лидер проекта OpenCV, участник команды IPPCV в 2006-2008 годах
Если говорить про IPP в целом, то, конечно, скорость у библиотеки выдающаяся. Особенно флагманские функции типа FFT. Ядра чистый изумруд! К счастью, у меня была возможность немного поизучать исходники IPP в свое время, и местами это был просто учебник, как надо писать эффективный код.
В свое время команду IPP очень сильно заинтересовала проблема автогенерации кода, в частности, на OCaml, и впечатлили успехи проекта Spiral, который сумел автоматически сгенерировать ядра FFT и родственных преобразований на уровне или даже обходивших по скорости реализованные в IPP. Часть этих ядер потом была включена в IPP. А меня, в свою очередь, эта тема зацепила настолько, что мой текущий проект, 13-14 лет спустя, с этой темой связан.

Павел Бердников, руководитель команды IPP QA в 2011-2015 годах, руководитель команды IPP в 2017-2020 годах
Работу в IPP я начал в QA команде в Сарове в 2000г. с написания тестов, позже создав единую билдовую систему для такого сложного проекта, автоматизированный процесс построения и тестирования.
А 17 лет спустя в Нижнем Новгороде стал руководителем всей команды IPP. Пришлось знакомиться с партнёрами внутри Intel и пользователями за его пределами. Изменить способы планирования и разработки кода от старых waterfall & development к современным agile & DevOps. Пересмотреть стратегии проекта и придумать новые. Так проект больше сосредоточился на криптографии и компрессии данных, появились интересные направления работы и новые области развития.
Реалистичные планы по развитию проекта развивать что есть и смотреть в смежные области. Фантастические планы, которые были ранее в мечтах, разбились о понимание мировых трендов и нужд наших пользователей. Пришло осознание, что бесконечные ресурсы, которые всегда очень хочется получить чтобы сделать больше и глубже, не дадут ожидаемого результата. Наверное, если бы я получил полную свободу действий и ресурсы, я бы пошёл дальше в область исследований новых алгоритмов, в область университетов и работы с ними.

Валентин Кубарев, руководитель команды Image & Signal Processing проекта Intel IPP, 2020 год настоящее время
Работу в IPP я начал в качестве SW инженера в 2011 году, когда проект только переехал из Сарова в Нижний Новгород. Меня всегда интересовала работа, связанная с математикой и оптимизацией. Знакомство с IPP я начал с функциональности изменения размера изображения, после чего долгое время отвечал за поддержку, разработку и оптимизацию геометрических преобразований, таких как Resize, WarpAffine, WarpPerspective и т. д. C 2017 года активно участвовал в задачах связанных с криптографией, и, в итоге, в 2018 мы смогли открыть код IPP Crypto и выложить проект на GitHub. С конца 2018 года работал в другом проекте, в 2020 году снова вернулся в IPP, но уже как руководитель Image & Signal Processing домена. Считаю, что в данный момент данная часть проекта недооценена, поэтому активно работаю с пользователями продукта IPP и нашими партнёрами внутри компании Intel, чтобы вывести проект на новый уровень.
Подробнее..

Перевод Julia готова для прода

22.09.2020 10:23:41 | Автор: admin


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


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


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

В течение многих лет я бы с этим согласился, но после JuliaCon 2020, я думаю, мы можем с уверенностью заявить, что


Джулия готова идти в производство!


Позвольте мне теперь привести список ключевых (на мой взгляд) презентаций, сделанных во время JuliaCon 2020, которые заставили меня сделать этот вывод.


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


Создание микросервисов и приложений


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


Еще будет интересно:


  • Доклад по характеристикам приложений, где Кристоффер Карлссон покажет как создать исполняемые файлы, которые могут быть запущены на машинах, на которых не установлена Julia.
  • Презентация Julia for scripting, в ходе которой [Фредрик Экре]() обсуждает лучшие практики использования Julia в контекстах, где вам нужно много раз выполнять короткие фрагменты кода.
  • Доклад Genie.jl, в котором Адриан Сальчану показывает зрелый, стабильный, производительный и многофункциональный фреймворк веб-разработки на Julia.

Управление зависимостями


Пара докладов Pkg.update() и What's new in Pkg показывает, что в настоящее время Julia имеет лучшие в своем классе функциональные возможности для управления зависимостями корпоративного уровня для ваших проектов. Список предоставляемых функций настолько велик, что здесь трудно перечислить их все.


Позвольте мне только упомянуть один конкретный инструмент в этой экосистеме BinaryBuilder.jl, который позволит взять программное обеспечение, написанное на компилируемых языках, таких как C, C++, Fortran, Go или Rust, и построить предварительно скомпилированные артефакты, которые могут быть легко использованы из пакетов Julia (что означает, что не нужна никакая компиляция на стороне клиента, когда вы устанавливаете пакеты, имеющие такие зависимости).


Интеграция с внешними библиотеками


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



Здесь стоит добавить, что Джулия уже много лет отлично интегрируется с Python, см. JuliaPy.


Хорошим всеобъемляющим примером выполнения некоторой реальной работы в Julia, требующей интеграции, является создание многоканальной беспроводной настройки динамиков, где показано, как легко сшивать всякие штуки вместе (и, в частности, с помощью ZMQ.jl, Opus.jl, PortAudio.jl, и DSP.jl).


Еще один интересный доклад, демонстрирующий возможности интеграции, это JSServe: Websites & Dashboards в Julia, который показывает высокопроизводительный фреймворк для легкого объединения интерактивных графиков, markdown, виджетов и простого HTML/Javascript в Jupyter / Atom / Nextjournal и на веб-сайтах.


Инструментарий разработчика


В паре замечательных докладов Juno 1.0 и Using VS Code рассказывается, что текущая поддержка IDE для Джулии в VS Code первоклассна. У вас есть все для полного счастья: анализ кода (статический и динамический), отладчик, рабочие области, интеграция с ноутбуками Jupyter и удаленные возможности.


Управление рабочими процессами в машинном обучении


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


Однако в дополнение к конкретным моделям вам нужны фреймворки, позволяющие управлять рабочими процессами ML. В этой области есть две интересные презентации: MLJ: A machine learning toolbox for Julia, и AutoMLPipeline: A ToolBox for Building ML Pipelines. По моему опыту, такие инструменты имеют решающее значение, когда вы хотите перейти с вашими ML-моделями из песочницы дата-саянтиста в реальное производственное использование.


Выводы


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


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

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


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


Готова Ли Джулия для прода? Вопросы и ответы с Богумилом Каминским


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


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


InfoQ: Не могли бы вы описать ваш быкграунд и вашу связь с Julia?


Bogumi Kamiski: Я профессор Варшавской школы экономики SGH, Польша, работаю в области операционных исследований и имитационного моделирования.
Я нахожусь в топ-5% участников языка Julia по количеству коммитов, внес значительный вклад в экосистему данных Julia и, в частности, один из основных мейнтейнеров DataFrames.jl.
Я занимаю второе место по ответу на тег [julia] в StackOverflow. Прежде чем перейти на полный рабочий день в академию, я более 10 лет руководил командой из нескольких сотен разработчиков и аналитиков, развертывающих проекты BI/DWH/data science для крупнейших польских корпораций и учреждений.


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


Kamiski: Здесь очень важно определить, что я понимаю под готовностью Джулии к проду.


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


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


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


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


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


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


Почему это так важно? Ну, вы можете легко "отправить" проект Julia и ожидать, что любой человек в любой среде должен быть в состоянии относительно легко запустить его. Конечно, будут угловатые моменты; но мой опыт ежедневного использования Linux и Windows 10 заключается в том, что все должно просто работать на обеих платформах.


Если вы делаете проект в Julia, вы можете ожидать, что либо есть пакет, который делает то, что вы хотите, либо вы можете легко использовать код, написанный на C или Python, и заставить его работать. В своем посте я хотел подчеркнуть, что мы находимся именно в этом состоянии. В качестве примера, основанного на чем-то, над чем я работал на этой неделе, у Джулии есть отличный пакет LightGraphs.jl для работы с графиками, но мои сотрудники используют Python и предпочитают использовать igraph. Вот пример кода с использованием графика (взято из учебника для графика в Python):


import igraph as igg = ig.Graph()g.add_vertices(3)g.add_edges([(0,1), (1,2)])g.add_edges([(2, 0)])g.add_vertices(3)g.add_edges([(2, 3), (3, 4), (4, 5), (5, 3)])g.pagerank()

Теперь вы спросите, как бы выглядел эквивалент в Julia. А вот так:


using PyCallig = pyimport("igraph")g = ig.Graph()g.add_vertices(3)g.add_edges([(0,1), (1,2)])g.add_edges([(2, 0)])g.add_vertices(3)g.add_edges([(2, 3), (3, 4), (4, 5), (5, 3)])g.pagerank()

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


Что это означает на практике? Если вы делаете проект, вы не застреваете с мыслью: "могу ли я использовать Джулию, так как через три месяца, возможно, мне понадобится что-то в проекте, чего еще нет в Джулии"? Но скорее вы знаете: "я могу относительно безопасно использовать Julia, так как в настоящее время многие пакеты общего назначения уже существуют, и даже если чего-то не хватает, я могу просто использовать это с другого языка, и это будет относительно безболезненно, независимо от того, является ли это C/Python/R/...".


Также частью производственной готовности является то, что с PackageCompiler.jl вы можете создавать "приложения, которые представляют собой набор файлов, включая исполняемый файл, который может быть отправлен и запущен на других машинах без установки Julia на этой машине." Я не рассматриваю это как существенную часть готовности для продакшена (многие скриптовые языки, считающиеся готовыми к производству, не предоставляют этой опции), но во многих сценариях это хорошая функция.


Теперь позвольте мне уточнить, что я не вижу в определении термина "готовый к производству". Я не рассматриваю Джулию как язык, который лучше всего подходит для любого вида проекта. Каждый язык программирования имеет свою нишу, и ниша Julia это высокопроизводительные вычисления / наука о данных (или как вы это там называете). Если вам нужны компактные бинарные файлы наверняка Julia не является лучшим вариантом. Если вы хотите разрабатывать приложения для Android тут тоже посоветую смотреть в другом направлении.


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


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


Kamiski: Процитирую то, что я написал в начале своего поста:


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


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


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


Теперь, что касается таких вещей, как документация, пакеты, инструменты и поддержка конечно, это должно и может быть улучшено. И я согласен, что более зрелые экосистемы, такие как R/Python/Java, в среднем имеют здесь лучший охват. Например, как соразработчик DataFrames.jl, я могу сказать вам, что большинство последних PR связаны с документацией. Но я бы не стал недооценивать сообщество Джулии здесь. В общем, если у вас есть какой-либо вопрос и вы разместите его на SO, Julia Discourse или Julia Slack, вы можете получить ответ обычно в течение нескольких минут, самое большее часов. Вот типичная история такого рода: люди обычно очень отзывчивы, и ошибки исправляются довольно быстро.


Если бы меня попросили назвать главный сомнительный момент касательно Джулии, то это было бы наличие достаточного количества людей, квалифицированных в этом языке в общем сообществе разработчиков. Я могу понять владельцев продуктов / менеджеров проектов и их чувство риска того, что они не смогут найти достаточно людей для работы над своими проектами, как только они возьмут на себя смелость начать работать на Julia. Однако здесь я убежден, что ситуация быстро улучшается. В прошедшем JuliaCon 2020 приняли участие более 20 000 участников. Кроме того, есть много ресурсов, уже доступных бесплатно в интернете.


InfoQ: Помимо вопроса о том, готова ли Джулия к проду или нет, или для каких областей она предназначена, каковы, на ваш взгляд, основные сильные стороны языка? Рассматриваете ли вы его как замену Python, R или любому другому языку, по крайней мере в области научных вычислений и науки о данных?


Kamiski: Я думаю, что здесь снова лучше всего процитировать последний опрос разработчиков Julia, слайды с 8 по 11. Я бы сосредоточился на трех главных вещах из слайда 8:


  • Скорость здесь ситуация относительно проста. Возьмите любой зрелый пакет, такой как TensorFlow или PyTorch, который требует производительности; они написаны в основном на C++. А Python это всего лишь тонкая оболочка вокруг ядра C++. Теперь возьмем Flux.jl или Knet.jl. Они по существу реализованы в чистом виде. Итак, суть такова: если вам нужно, чтобы ваш код работал быстро, в то же время используя преимущества языка высокого уровня, то Julia это естественный выбор. Кроме того, как объяснялось выше, если есть внешняя библиотека, которая очень быстра, и вы хотите использовать ее, обычно это относительно легко сделать.


  • Простота использования есть множество проявлений этого аспекта, но мой опыт показывает, что когда кто-то получает представление о принципах Джулии, он очень хорошо сравнивается с точки зрения синтаксиса и дизайна, например, с R/Python при выполнении вычислений. Язык был разработан вокруг поддержки этих видов задач в лучшем виде. И это не только синтаксис это также выбор того, когда вещи будут происходить явно, а когда нет (например, с трансляцией). Исходя из моего опыта, это делает код Джулии простым в обслуживании, а хорошо написанный код в значительной степени самодокументируется.


  • Код является открытым и может быть изменен этот аспект имеет два измерения. Прежде всего, большинство пакетов Julia лицензированы MIT, что часто очень приветствуется в корпоративных средах. Во-вторых поскольку большинство пакетов написано на Julia, то если вам не нравится, как что-то работает вы просто модифицируете это сами (что гораздо проще, чем в R/Python, где, скорее всего, то, что вам нужно изменить, написано, например, на C, C++, Fortran).



Люди часто спрашивают меня, вижу ли я Джулию в качестве замены R / Python. И я думаю об этом так::


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


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


  • Если у вас много устаревшего кода R/Python и вы им довольны просто придерживайтесь его и помните, что если у вас есть некоторые критически важные для производительности части, они могут быть относительно легко переписаны на Julia и интегрированы обратно с вашей исходной кодовой базой (я сделал много таких проектов).



InfoQ: Как вы видите эволюцию Джулии?


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


Если бы я назвал здесь некоторые основные изменения, то это были бы:


  • Улучшение поддержки многопоточности. Я думаю, что это действительно актуально для основных случаев использования Julia. Так-то поддержка есть, но все же здесь многое можно улучшить. Точно так же следует ожидать улучшения в обработке GPU/TPU.


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


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


  • Рост сообщества я считаю, что число людей, заинтересованных в Джулии, значительно увеличивается, о чем свидетельствует, например, количество участников JuliaCon2020. Это означает, что: а) в будущем будет легче нанимать качественных разработчиков Julia, если они понадобятся, и б) это создаст положительную обратную связь для качества экосистемы Julia, поскольку все больше людей сообщают о проблемах и участвуют в них. Опять же, как сопровождающий DataFrames.jl я наблюдаю этот сдвиг: люди, которые никогда не были в "ядре" разработки пакета, открывают вопросы / делают PR и обсуждают функциональные возможности в социальных сетях.



Если вы заинтересовались языком Julia, все доклады с JuliaCon 2020 доступны на YouTube.

Подробнее..

EBPF современные возможности интроспекции в Linux, или Ядро больше не черный ящик

22.09.2020 14:11:12 | Автор: admin


У всех есть любимые книжки про магию. У кого-то это Толкин, у кого-то Пратчетт, у кого-то, как у меня, Макс Фрай. Сегодня я расскажу вам о моей любимой IT-магии о BPF и современной инфраструктуре вокруг него.

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

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

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

Что такое eBPF?


Итак, о какой такой магии вам тут собирается рассказывать 34-летний бородатый мужик с горящими глазами?

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



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

BIOS, EFI, операционная система, драйвера, модули, библиотеки, сетевое взаимодействие, базы данных, кеши, оркестраторы типа K8s, контейнеры типа Docker, наконец, наш с вами софт с рантаймами и сборщиками мусора. Настоящий профессионал может отвечать на вопрос о том, что происходит после того, как вы вбиваете ya.ru в браузере, несколько дней.

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

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

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

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

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

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



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

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

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

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


Простенький фильтр для tcpdump представлен в виде BPF-программы

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

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



В 2014 году Алексей Старовойтов расширил функциональность BPF. Он увеличил количество регистров и допустимый размер программы, добавил JIT-компиляцию и сделал верификатор, который проверял программы на безопасность. Но самым впечатляющим было то, что новые BPF-программы могли запускаться не только при обработке пакетов, но и в ответ на многочисленные события ядра, и передавали информацию туда-сюда между kernel и user space.

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

У обычного пользователя Linux появилась суперспособность заглядывать под капот, ранее доступная только хардкорным разработчикам ядра или недоступная никому. Эта опция сравнима с возможностью без особых усилий написать программу для iOS или Android: на старых телефонах это было или невозможно, или значительно сложнее.

Новая версия BPF от Алексея получила название eBPF (от слова extended расширенная). Но сейчас она заменила все старые версии BPF и стала настолько популярной, что для простоты все называют её просто BPF.

Где используют BPF?


Итак, что же это за события, или триггеры, к которым можно прицепить BPF-программы, и как люди начали использовать новоприобретённую мощь?

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

Первая группа используется для обработки сетевых пакетов и для управления сетевым трафиком. Это XDP, traffic control-ивенты и ещё несколько.

Эти ивенты нужны, чтобы:

  • Создавать простые, но очень эффективные файрволы. Компании вроде Cloudflare и Facebook с помощью BPF-программ отсеивают огромное количество паразитного трафика и борются с самыми масштабными DDoS-атаками. Так как обработка происходит на самой ранней стадии жизни пакета и прямо в ядре (иногда BPF-программа даже пушится сразу в сетевую карту для обработки), то таким образом можно обрабатывать колоссальные потоки трафика. Раньше такие вещи делали на специализированных сетевых железках.
  • Создавать более умные, точечные, но всё ещё очень производительные файрволы такие, которые могут проверить проходящий трафик на соответствие правилам компании, на паттерны уязвимостей и т. п. Facebook, например, занимается таким аудитом внутри компании, несколько проектов продают такого рода продукты наружу.
  • Создавать умные балансировщики. Самым ярким примером является проект Cilium, который чаще всего используется в кластере K8s в качестве mesh-сети. Cilium управляет трафиком: балансирует, перенаправляет и анализирует его. И всё это с помощью небольших BPF-программ, запускаемых ядром в ответ на то или иное событие, связанное с сетевыми пакетами или сокетами.

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

В данной группе есть такие триггеры, как:

  • perf events ивенты, связанные с производительностью и с Linux-профилировщиком perf: железные процессорные счётчики, обработка прерываний, перехват minor/major-исключений памяти и т. п. Например, вы можете поставить обработчик, который будет запускаться каждый раз, когда ядру надо вытащить из свопа какую-то страницу памяти. Представьте себе, например, утилиту, которая отображает программы, которые в данный момент используют своп.
  • tracepoints статические (определённые разработчиком) места в исходниках ядра, при прикреплении к которым можно достать статическую же информацию (ту, которую заранее подготовил разработчик). Может показаться, что в данном случае статичность это плохо, ведь я говорил, что один из недостатков логов заключается в том, что они содержат только то, что изначально добавил программист. В каком-то смысле это так, но tracepoints обладают тремя важными преимуществами:
    • их довольно много раскидано по ядру в самых интересных местах;
    • когда они не включены, они не тратят ресурсы;
    • они являются частью API, стабильны и не меняются, что очень важно, так как другие триггеры, о которых пойдёт речь, не имеют стабильного API.

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

  • USDT то же самое, что tracepoints, только для user space-программ. То есть вы как программист можете добавлять такие места в свою программу. И многие крупные и известные программы и языки программирования уже обзавелись такими трейсами: MySQL, например, или языки PHP, Python. Часто они выключены по умолчанию и для их включения нужно пересобрать интерпретатор с параметром enable-dtrace или подобным. Да, в Go у нас тоже есть возможность регистрировать такие трейсы. Кто-то, может быть, узнал слово DTrace в названии параметра. Дело в том, что такого рода статические трейсы были популяризированы в одноимённой системе, которая зародилась в ОС Solaris: например, момент создания нового треда, запуска GC или чего-то, связанного с конкретным языком или системой.

Ну а дальше начинается ещё один уровень магии:

  • ftrace-триггеры дают нам возможность запускать BPF-программу в начале практически любой функции ядра. Полностью динамически. Это значит, что ядро вызовет вашу BPF-функцию до начала выполнения любой выбранной вами функции ядра. Или всех функций ядра как угодно. Вы можете прикрепиться ко всем функциям ядра и получить на выходе красивую визуализацию всех вызовов.
  • kprobes/uprobes дают почти то же самое, что ftrace, только у нас есть возможность прицепиться к любому месту при исполнении функции как ядра, так и в user space. В середине функции есть какой-то if по переменной и вам надо построить гистограмму значений этой переменной? Не проблема.
  • kretprobes/uretprobes здесь всё аналогично предыдущим триггерам, но мы можем стриггернуться при завершении выполнения функции ядра и программы в user space. Такого рода триггеры удобны для просмотра того, что функция возвращает, и для замера времени её выполнения. Например, можно узнать, какой PID вернул системный вызов fork.

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

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

API, или Как это использовать


Окей, Марко, ты нас уговорил посмотреть в сторону BPF. Но как к нему подступиться?

Давайте посмотрим, из чего состоит BPF-программа и как с ней взаимодействовать.



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

У BPF-программы есть возможность взаимодействовать со второй частью с user space-программой. Для этого есть два способа. Мы можем писать в циклический буфер, а user space-часть может из него читать. Также мы можем писать и читать в key-value-хранилище, которое называется BPF map, а user space-часть, соответственно, может делать то же самое, и, соответственно, они могут перекидывать друг другу какую-то информацию.

Прямолинейный путь


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



Первое доступное упрощение использование библиотеки libbpf, которая поставляется с исходниками ядра и позволяет не работать напрямую с системным вызовом BPF. По сути, она предоставляет удобные обёртки для загрузки кода, работы с так называемыми мапами для передачи данных из ядра в user space и обратно.

bcc


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



По сути, он готовит всё сборочное окружение и даёт нам возможность писать единые BPF-программы, где С-часть будет собрана и загружена в ядро автоматически, а user space-часть может быть сделана на простом и понятном Python.

bpftrace


Но и BCC выглядит сложным для многих вещей. Особенно люди почему-то не любят писать части на С.

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



Знаменитый специалист в области производительности и observability Брендан Грегг подготовил следующую визуализацию доступных способов работы с BPF:



По вертикали у нас простота инструмента, а по горизонтали его мощь. Видно, что BCC очень мощный инструмент, но не суперпростой. bpftrace гораздо проще, но при этом уступает в мощности.

Примеры использования BPF


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

И BCC, и bpftrace содержат папку Tools, где собрано огромное количество готовых интересных и полезных скриптов. Они одновременно являются и местным Stack Overflow, с которого вы можете копировать куски кода для своих скриптов.

Вот, например, скрипт, который показывает latency для DNS-запросов:

 marko@marko-home ~$ sudo gethostlatency-bpfccTIME   PID  COMM         LATms HOST16:27:32 21417 DNS Res~ver #93    3.97 live.github.com16:27:33 22055 cupsd         7.28 NPI86DDEE.local16:27:33 15580 DNS Res~ver #87    0.40 github.githubassets.com16:27:33 15777 DNS Res~ver #89    0.54 github.githubassets.com16:27:33 21417 DNS Res~ver #93    0.35 live.github.com16:27:42 15580 DNS Res~ver #87    5.61 ac.duckduckgo.com16:27:42 15777 DNS Res~ver #89    3.81 www.facebook.com16:27:42 15777 DNS Res~ver #89    3.76 tech.badoo.com :-)16:27:43 21417 DNS Res~ver #93    3.89 static.xx.fbcdn.net16:27:43 15580 DNS Res~ver #87    3.76 scontent-frt3-2.xx.fbcdn.net16:27:43 15777 DNS Res~ver #89    3.50 scontent-frx5-1.xx.fbcdn.net16:27:43 21417 DNS Res~ver #93    4.98 scontent-frt3-1.xx.fbcdn.net16:27:44 15580 DNS Res~ver #87    5.53 edge-chat.facebook.com16:27:44 15777 DNS Res~ver #89    0.24 edge-chat.facebook.com16:27:44 22099 cupsd         7.28 NPI86DDEE.local16:27:45 15580 DNS Res~ver #87    3.85 safebrowsing.googleapis.com^C%

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

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

 marko@marko-home ~$ sudo bashreadline-bpfccTIME   PID  COMMAND16:51:42 24309 uname -a16:52:03 24309 rm -rf src/badoo

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

Скрипт для просмотра флоу вызовов высокоуровневых языков:

 marko@marko-home ~/tmp$ sudo /usr/sbin/lib/uflow -l python 20590Tracing method calls in python process 20590... Ctrl-C to quit.CPU PID  TID  TIME(us) METHOD5  20590 20590 0.173  -> helloworld.py.hello5  20590 20590 0.173   -> helloworld.py.world5  20590 20590 0.173   <- helloworld.py.world5  20590 20590 0.173  <- helloworld.py.hello5  20590 20590 1.174  -> helloworld.py.hello5  20590 20590 1.174   -> helloworld.py.world5  20590 20590 1.174   <- helloworld.py.world5  20590 20590 1.174  <- helloworld.py.hello5  20590 20590 2.175  -> helloworld.py.hello5  20590 20590 2.176   -> helloworld.py.world5  20590 20590 2.176   <- helloworld.py.world5  20590 20590 2.176  <- helloworld.py.hello6  20590 20590 3.176  -> helloworld.py.hello6  20590 20590 3.176   -> helloworld.py.world6  20590 20590 3.176   <- helloworld.py.world6  20590 20590 3.176  <- helloworld.py.hello6  20590 20590 4.177  -> helloworld.py.hello6  20590 20590 4.177   -> helloworld.py.world6  20590 20590 4.177   <- helloworld.py.world6  20590 20590 4.177  <- helloworld.py.hello^C%

Здесь на примере показан стек вызовов программы на Python.

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


Не пытайтесь что-то здесь углядеть. Картинка используется как справочник

А что у нас с Go?


Теперь давайте поговорим о Go. У нас два основных вопроса:

  • Можно ли писать BPF-программы на Go?
  • Можно ли анализировать программы, написанные на Go?

Пойдём по порядку.

На сегодняшний день единственный компилятор, который умеет компилировать в формат, понимаемый BPF-машиной, Clang. Другой популярный компилятор, GСС, пока не имеет BPF-бэкенда. И единственный язык программирования, который может компилироваться в BPF, очень ограниченный вариант C.

Однако у BPF-программы есть и вторая часть, которая находится в user space. И её можно писать на Go.

Как я уже упоминал выше, BCC позволяет писать эту часть на Python, который является первичным языком инструмента. При этом в главном репозитории BCC также поддерживает Lua и C++, а в стороннем ещё и Go.



Выглядит такая программа точно так же, как программа на Python. В начале строка, в которой BPF-программа на C, а затем мы сообщаем, куда прицепить данную программу, и как-то с ней взаимодействуем, например достаём данные из EPF map.

Собственно, все. Рассмотреть пример подробнее можно на Github.
Наверное, основной недостаток заключается в том, что для работы используется C-библиотека libbcc или libbpf, а сборка Go-программы с такой библиотекой совсем не похожа на милую прогулку в парке.

Помимо iovisor/gobpf, я нашёл ещё три актуальных проекта, которые позволяют писать userland-часть на Go.


Версия от Dropbox не требует никаких C-библиотек, но вот kernel-часть BPF-программы вам придётся собрать самостоятельно с помощью Clang и затем загрузить в ядро Go-программой.

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

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

На самом деле, есть ещё один вопрос: зачем вообще писать BPF-программы на Go? Ведь если посмотреть на BCC или bpftrace, то BPF-программы в основном занимают меньше 500 строк кода. Не проще ли написать скриптик на bpftrace-языке или расчехлить немного Python? Я тут вижу два довода.

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

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



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

Анализируем программы на Go


Если помните, у нас был ещё один вопрос: можем ли мы анализировать программы, написанные на Go, с помощью BPF? Первая мысль конечно! Какая разница, на каком языке написана программа? Ведь это просто скомпилированный код, который так же, как и все остальные программы, что-то считает на процессоре, кушает память как не в себя, взаимодействует с железом через ядро, а с ядром через системные вызовы. В принципе, это правильно, но есть особенности разного уровня сложности.

Передача аргументов


Одна из особенностей состоит в том, что Go не использует ABI, который использует большинство остальных языков. Так уж получилось, что отцы-основатели решили взять ABI системы Plan 9, хорошо им знакомой.

ABI это как API, соглашение о взаимодействии, только на уровне битов, байтов и машинного кода.

Основной элемент ABI, который нас интересует, то, как в функцию передаются её аргументы и как из функции передаётся обратно ответ. Если в стандартном ABI x86-64 для передачи аргументов и ответа используются регистры процессора, то в Plan 9 ABI для этого использует стек.

Роб Пайк и его команда не планировали делать ещё один стандарт: у них уже был почти готовый компилятор для C для системы Plan 9, простой как дважды два, который они в кратчайшие сроки переделали в компилятор для Go. Инженерный подход в действии.

Но это, на самом деле, не слишком критичная проблема. Во-первых, мы, возможно, скоро увидим в Go передачу аргументов через регистры, а во-вторых, получать аргументы со стека из BPF несложно: в bpftrace уже добавили алиас sargX, а в BCC появится такой же, скорее всего, в ближайшее время.

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

Уникальный идентификатор треда


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

Но в Go горутины гуляют между системными тредами: сейчас горутина выполняется на одном треде, а чуть позже на другом. И в случае с Go нам бы в ключ положить не TID, а GID, то есть ID горутины, но получить мы его не можем. Чисто технически этот ID существует. Грязными хаками его даже можно вытащить, так как он где-то в стеке, но делать это строго запрещено рекомендациями ключевой группы разработчиков Go. Они посчитали, что такая информация нам не нужна будет никогда. Как и Goroutine local storage, но это я отвлёкся.

Расширение стека


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

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

Если говорить о C, то стек там имеет фиксированный размер. Если мы вылезем за пределы этого фиксированного размера, то произойдёт знаменитый stack overflow.

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

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

Тут и кроется основная проблема: uretprobes, которые используют для прикрепления BPF-функции, к моменту завершения выполнения функции динамически изменяют стек, чтобы встроить вызов своего обработчика, так называемого trampoline. И такое неожиданное для Go изменение его стека в большинстве случаев заканчивается падением программы. Упс!

Впрочем, эта история не уникальна. Разворачиватель стека C++ в момент обработки исключений тоже падает через раз.

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

Но если вам очень нужно поставить uretprobe, то проблему можно обойти. Как? Не ставить uretprobe. Можно поставить uprobe на все места, где мы выходим из функции. Таких мест может быть одно, а может быть 50.

И здесь уникальность Go играет нам на руку.

В обычном случае такой трюк не сработал бы. Достаточно умный компилятор умеет делать так называемый tail call optimization, когда вместо возврата из функции и возврата по стеку вызовов мы просто прыгаем в начало следующей функции. Такого рода оптимизация критически важна для функциональных языков вроде Haskell. Без неё они и шагу бы не могли ступить без stack overflow. Но с такой оптимизацией мы просто не сможем найти все места, где мы возвращаемся из функции.

Особенность в том, что компилятор Go версии 1.14 пока не умеет делать tail call optimization. А значит, трюк с прикреплением ко всем явным выходам из функции работает, хоть и очень утомителен.

Примеры


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

Для препарирования возьмём простенькую программку. По сути, это веб-сервер, который слушает на 8080 порту и имеет обработчик HTTP-запросов. Обработчик достанет из URL параметр name, параметр Go и сделает какую-то проверку сайта, а затем все три переменные (имя, год и статус проверки) отправит в функцию prepareAnswer(), которая подготовит ответ в виде строки.



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

Триггерить нашу программу будем простым запросом через curl:



В качестве первого примера с помощью bpftrace напечатаем все вызовы функций нашей программы. Мы здесь прикрепляемся ко всем функциям, которые попадают под main. В Go все ваши функции имеют символ, который выглядит как название пакета-точка-имя функции. Пакет у нас main, а рантайм функции был бы runtime.



Когда я делаю curl, то запускаются хендлер, функция проверки сайта и подфункция-горутина, а затем и функция подготовки ответа. Класс!

Дальше я хочу не только вывести, какие функции выполняются, но и их аргументы. Возьмём функцию prepareAnswer(). У неё три аргумента. Попробуем распечатать два инта.
Берём bpftrace, только теперь не однострочник, а скриптик. Прикрепляемся к нашей функции и используем алиасы для стековых аргументов, о которых я говорил.

В выводе мы видим то, что передали мы 2020, получили статус 200, и один раз передали 2021.



Но у функции три аргумента. Первый из них строка. Что с ним?

Давайте просто выведем все стековые аргументы от 0 до 4. И что мы видим? Какая-то большая цифра, какая-то цифра поменьше и наши старые 2021 и 200. Что же это за странные цифры в начале?



Вот здесь уже полезно знать устройство Go. Если в C строка это просто массив символов, который заканчивается нулём, то в Go строка это на самом деле структура, состоящая из указателя на массив символов (кстати, не заканчивающийся нулём) и длины.



Но компилятор Go при передаче строчки в виде аргумента разворачивает эту структуру и передаёт её как два аргумента. И получается, что первая странная цифра это как раз указатель на наш массив, а вторая длина.

И правда: ожидаемая длина строки 22.

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



Ну и заглянем в рантайм. Например, мне захотелось узнать, какие горутины запускает наша программа. Я знаю, что горутины запускаются функциями newproc() и newproc1(). Подконнектимся к ним. Первым аргументом функции newproc1() является указатель на структуру funcval, которая имеет только одно поле указатель на функцию:



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



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

Заключение


Это всё, о чём я хотел вам рассказать. Надеюсь, что у меня получилось вдохновить вас.

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

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

Что же до Go, то мы оказались, как обычно, довольно уникальными. Вечно у нас какие-то нюансы: то компилятор другой, то ABI, нужен какой-то GOPATH, имя, которое невозможно загуглить. Но мы стали силой, с которой принято считаться, и я верю, что жизнь станет только лучше.
Подробнее..

Переписывание истории репозитория кода, или почему иногда можно git push -f

22.09.2020 16:11:47 | Автор: admin


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



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

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

На самом низком уровне git-репо представляет собой набор объектов и указателей на них. Каждый объект имеет свой уникальный 40-значный хэш (20 байт в 16-ричной системе), который вычисляется на основе содержимого объекта.



Иллюстрация взята из The Git Community Book

Основные типы объектов это blob (просто содержимое файла), tree (набор указателей на blobs и другие trees) и commit. Объект типа commit представляет собой только указатель на tree, на предыдущий коммит и служебную информацию: дата/время, автор и комментарий.

Где здесь ветки и тэги, которыми мы привыкли оперировать? А они не являются объектами, они являются просто указателями: ветка указывает на последний коммит в ней, тэг на произвольный коммит в репо. То есть когда мы в IDE или GUI-клиенте видим красиво нарисованные веточки с кружочками-коммитами на них они строятся на лету, пробегая по цепочкам коммитов от концов веток вниз к корню. Самый первый коммит в репо не имеет предыдущего, вместо указателя там null.

Важный для понимания момент: один и тот же коммит может фигурировать в нескольких ветках одновременно. Коммиты не копируются при создании новой ветки, она просто начинает расти с того места, где был HEAD в момент отдачи команды git checkout -b <branch-name>.

Итак, почему же переписывание истории репозитория вредно?



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

Почему-то мало кто знает, что довольно давно у команды git push существует безопасный ключ --force-with-lease, который заставляет команду завершиться с ошибкой, если в удалённом репозитории есть коммиты, добавленные другими пользователями. Я всегда рекомендую использовать его вместо -f/--force.

Вторая причина, по которой команда git push -f считается вредной, заключается в том, что при попытке слияния (merge) ветки с переписанной историей с ветками, где она сохранилась (точнее, сохранились коммиты, удалённые из переписанной истории), мы получим адское число конфликтов (по числу коммитов, собственно). На это есть простой ответ: если аккуратно соблюдать Gitflow или Gitlab Flow, то такие ситуации, скорее всего, даже не возникнут.

И наконец есть неприятная побочка переписывания истории: те коммиты, которые как бы удаляются при этом из ветки, на самом деле, никуда не исчезают и просто остаются навечно висеть в репо. Мелочь, но неприятно. К счастью, эту проблему разработчики git тоже предусмотрели, введя команду сборки мусора git gc --prune. Большинство git-хостингов, как минимум GitHub и GitLab, время от времени,
производят эту операцию в фоне.

Итак, развеяв опасения перед изменением истории репозитория, можно, наконец, перейти к главному вопросу: зачем оно нужно и когда оправдано?

На самом деле, я уверен, что практически каждый из более-менее активных пользователей git хоть раз, да изменял историю, когда вдруг оказывалось, что в последнем коммите что-то пошло не так: вкралась досадная опечатка в код, сделал коммит не от того пользователя (с личного e-mail вместо рабочего или наоборот), забыл добавить новый файл (если вы, как я, любите пользоваться git commit -a). Даже изменение описания коммита приводит к необходимости его перезаписи, ведь хэш считается и от описания тоже!

Но это тривиальный случай. Давайте рассмотрим более интересные.

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

Если теперь нажать, не глядя, кнопку Merge, то в главную ветку (у многих она по старинке называется master) вольются полтора десятка коммитов типа My feature, day 1, Day 2, Fix tests, Fix review и т.д. От этого, конечно, помогает режим squash, который сейчас есть и в GitHub, и в GitLab, но с ним надо быть осторожными: во-первых, он может заменить описание коммита на что-то непредсказуемое, а во-вторых заменить автора фичи на того, кто нажал кнопку Merge (у нас это вообще робот, помогающий релиз-инженеру собрать сегодняшний деплой). Поэтому самым простым будет перед окончательной интеграцией в релиз схлопнуть все коммиты ветки в один при помощи git rebase.

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



У меня рука машинально потянулась к кнопке Report abuse, потому что как ещё можно охарактеризовать реквест из 50 коммитов с почти 2000 изменённых строк? И как его, спрашивается, ревьюить?

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

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

Сделать это всё нам поможет всё тот же git rebase с ключом --interactive. В качестве параметра надо передать ему хэш коммита, начиная с которого нужно будет переписать историю. Если речь о последних 50 коммитах, как в примере на картинке, можно написать git rebase --interactive HEAD~50 (подставьте вместо 50 вашу цифру).

Кстати, если вы в процессе работы над задачей подливали к себе ветку master, то сначала надо будет сделать rebase на эту ветку, чтобы merge-коммиты и коммиты из мастера не путались у вас под ногами.

Вооружившись знаниями о внутреннем устройстве git-репозитория, понять принцип действия rebase на master будет несложно. Эта команда берёт все коммиты в нашей ветке и меняет родителя первого из них на последний коммит в ветке master. См. схему:




Иллюстрации взяты из книги Pro Git

Если изменения в C4 и C3 конфликтуют, то после разрешения конфликтов коммит C4 изменит своё содержание, поэтому он переименован на второй схеме в C4.

Таким образом, вы получите ветку, состоящую только из ваших изменений, и растущую из вершины master. Само собой, master должен быть актуальным. Можно просто использовать версию с сервера: git pull --rebase origin/master (как известно, git pull равносилен git fetch && git merge, а ключ --rebase заставит git сделать rebase вместо merge).

Вернёмся наконец к git rebase --interactive. Его делали программисты для программистов, и понимая, какой стресс люди будут испытывать в процессе, постарались максимально сохранить нервы пользователя и избавить его от необходимости чрезмерно напрягаться. Вот что вы увидите на экране:


Это репозиторий популярного пакета Guzzle. Похоже, что rebase ему не помешал бы

В текстовом редакторе открывается сформированный файл. Внизу вас ожидает подробная справка о том, что тут вообще делать. Далее в режиме простого редактирования вы решаете, что делать с коммитами в вашей ветке. Всё просто, как палка: pick оставить как есть, reword поменять описание коммита, squash слить воедино с предыдущим (процесс работает снизу вверх, то есть предыдущий это который строчкой ниже), drop вообще удалить, edit и это самое интересное остановиться и замереть. После того, как git встретит команду edit, он встанет в позицию, когда изменения в коммите уже добавлены в режим staged. Вы можете поменять всё, что угодно в этом коммите, добавить поверх него ещё несколько, и после этого скомандовать git rebase --continue, чтобы продолжить процесс rebase.

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

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

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

Ещё одна фигура высшего пилотажа, полезная в случае, если надо несколько изменений в одном и том же файле разложить по разным коммитам git add --patch. Она бывает полезна и сама по себе, но в сочетании с директивой edit она позволит вам разделить один коммит на несколько, причём сделать это на уровне отдельных строк, чего не позволяет, если я не ошибаюсь, ни один GUI-клиент и ни одна IDE.

Убедившись ещё раз, что всё в порядке, вы наконец можете со спокойной душой сделать то, с чего начался этот туториал: git push --force. Ой, то есть, разумеется, --force-with-lease!



Поначалу вы, скорее всего, будете тратить на этот процесс (включая первоначальный rebase на master) час, а то и два, если фича реально развесистая. Но даже это намного лучше, чем ждать два дня, когда ревьювер заставит себя наконец взяться за ваш реквест, и ещё пару дней, пока он сквозь него продерётся. В будущем же вы, скорее всего, будете укладываться в 30-40 минут. Особенно помогают в этом продукты линейки IntelliJ со встроенным инструментом разрешения конфликтов (full disclosure: компания FunCorp оплачивает эти продукяы своим сотрудникам).

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

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

Блокчейн-платформа R-chain общая архитектура и эволюция

23.09.2020 18:06:49 | Автор: admin

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



Данная статья довольно объемная и вместе с этим информативная. Поэтому надеемся на вашу вовлеченность и предупреждаем о формате tutorial.

В 2016-2017 годах мы выполнили ряд исследовательских проектов по построению децентрализованной платформы для торгового финансирования. Использовали тестовый Ethereum (Rinkeby) как базовую платформу распределенного реестра и Ethereum Swarm как средство децентрализованного обмена файлами. Кроме общих вопросов построения децентрализованной платформы испытывали возможности смарт-контрактов, применения оракулов и арбитражных смарт-контрактов. Некоторые из этих результатов есть
На базе этих исследовательских проектов претворились в жизнь
Итогом этой достаточно длительной работы стал, как говорят военные, прием на снабжение IT Райффайзенбанка штатной децентрализованной платформы R-Chain. Теперь она предлагается как элемент клиентского обслуживания для корпоративных групп разного размера.

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

Содержание



Особенности корпоративных и межкорпоративных систем


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

  • У платформы должен быть оператор юридическое лицо, несущее перед участниками ответственность за её функционирование. Вариант каждый за себя, всё решит консенсус участников и прочее, характерное для публичных блокчейн-платформ, здесь не пройдет.
  • Использование платформы участником должно иметь достаточно простую юридическую обвязку, максимально близкую к существующему электронному документообороту. Поверьте, описание математических основ алгоритма консенсуса много-ранговой блокчейн-сети на 15 страницах это не то, что ожидают увидеть ваши юристы.
  • Система должна удовлетворять требованиям регуляторов и обычаям документооборота по криптографии, используемой для защиты данных и подтверждения юридической значимости действий. Например, предлагая свой продукт российским клиентам, вы не раз убедитесь, что после слов мы используем ГОСТ количество вопросов заказчика волшебным образом сокращается, а его желание использовать вашу платформу пропорционально возрастает.
  • Компоненты узла платформы, устанавливаемые у участника, должны хорошо ложиться в принятую в корпоративных системах сетевую сегментацию. Нахождение в ДМЗ коммерческой тайны или ключей ЭЦП, обеспечивающих юридическую значимость и всё.
  • Возможность форсмажорного изменения состояния сделок в нарушение заложенной ролевой модели. Не секрет, что в жизни что-то может пойти не так пути бизнеса подчас неисповедимы, а регуляторы и решения суда делают их еще более кучерявыми, причем нередко задним числом. И в один далеко не прекрасный момент сделка может оказаться в состоянии, из которого ее нельзя по ролевой модели перевести в состояние, предписанное судьбой для этого платформа должна предусматривать соответствующие механизмы, которые позволят установить сделку в нужное состояние при условии надлежащего консенсуса участников этой сделки.

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

Общая архитектура платформы


Архитектура программных компонентов


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



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

Универсальный бизнес-процесс


Естественно встал вопрос, какими именно свойствами следует наделить универсальный бизнес-процесс, чтобы он обеспечивал, с одной стороны, максимальное использование преимуществ блокчейн-платформ, а с другой, максимальную гибкость и функциональность для применения в Бизнес-приложении. Дополнительным условием была возможность реализации выбранных свойств на большинстве из распространённых DLT-платформ, функциональные возможности которых в некоторых аспектах существенно отличаются (Ethereum/Quorum/Masterchain, Hyperledger Fabric, Corda, EOS, Waves). Базируясь на опыте своих и чужих проектов, мы пришли к следующим выводам.

Универсальный бизнес-процесс должен иметь следующий атрибутивный состав:

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

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

  • Полнота и целостность информации
  • Конфиденциальность электронных документов за пределами круга участников процесса
  • Контроль следования карте переходов процесса
  • Хранение истории изменения состояний процесса

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

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

Параметры процесса носят условно открытый характер, так как передаются непосредственно через смарт-контракт. Для некоторых блокчейн-платформ они являются принципиально публичными (Ethereum/Masterchain), для других могут быть закрыты штатными средствами обеспечения приватности данных (Quorum приватные смарт-контракты, Hyperledger Fabric каналы и приватные данные). Наверное, важнейшим из параметров ядра процесса в нашей реализации является вид процесса, так как он несёт не только смысловую, но и функциональную нагрузку в зависимости от вида процесса адаптер DLT выбирает шаблон смарт-контракта, которым данный процесс будет представляться. Зачем это нужно? Очевидно, что существует бесчисленное количество видов сделок и столь же многообразны бизнес-процессы, обеспечивающие их реализацию. В достаточно большом количестве случаев бизнес-процессы по сути отличаются только картой переходов (с точки зрения платформы) и могут быть реализованы одним единственным смарт-контрактом, поддерживающим произвольную карту переходов (об этом ниже). Но с конкретным бизнес-процессом могут быть связаны и достаточно уникальные моменты:

  • Обращения к оракулам (например, для сделок связанных с курсом валют)
  • Обращение к другим сделкам или к виртуальным счетам (например, для автоматического резервирования средств или проверки наличных остатков)
  • Контроль времени события (например, проверка срока подачи документов по аккредитиву или требования по гарантии)
  • и так далее

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

В число атрибутов ядра нашего процесса входят также статус и примечание: первое это кратное описание состояния процесса (New, Canceled, Closed, OnАpproval), второе длинная строка с более подробным описание к статусу. Мы ограничиваем длину примечания где-то до 1000 символов (например, Недостаточно средств на счете), так как для передачи значительных объемов информации (тем более конфиденциальной) предназначены электронные документы, прикрепляемые к процессу.

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

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

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

  • статус
  • примечание
  • идентификатор инициатора транзакции изменения состояния

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

Обеспечение юридической значимости очень важный вопрос, отмеченный нами в разделе Особенности корпоративных и межкорпоративных систем. Мы изначально исходили из концепции, что юридическая значимость должна обеспечиваться не средствами блокчейн-платформы, а за счет использования внешнего PKI, имеющего регуляторную поддержку или соответствующий уровень доверия у участников платформы. Грубо говоря, электронный документ, обеспечивающий юридическое обоснование ваших действий (платежный документ, контракт, требование и так далее) и прилагаемый к процессу, должен быть подписан на базе кошерного PKI (в России ГОСТ, где-то за рубежом, например, SSL или PGP/GPG ). Бизнес-приложение проверит внешнюю подпись и выполнит соответствующее действие. Или не выполнит в зависимости от результата. Кто-то скажет, что это не по-евангелистски и надо убеждать юристов в юридической значимости блокчейн-транзакций. Мы прошли много шагов этого путешествия и результат всегда был один. Впрочем, в случае с Россией сертификация Мастерчейн открывает определенные возможности в этом плане как говорится Удачной охоты!

Преимущества использования универсального бизнес-процесса


Что же в итоге нам дал такой подход?

  • Расширение круга потенциальных разработчиков децентрализованных бизнес-приложений. Так как при предложенной зеркальной архитектуре от разработчика собственно бэкенд-приложения не требуется какой-либо работы непосредственно с децентрализованными компонентами, то появляется возможность привлечь к разработке без каких-либо ограничений самый широкий круг обычных разработчиков, знающих толк в необходимых продуктовых областях. Это значительно снижает сроки и стоимость разработки и повышает ее качество. В наших проектах из четырех разработчиков бэкендов трое вообще никогда ранее не работали с блокчейн-системами, а еще один имел опыт разработки на Corda, в то время как приложение работало через Зеркало с Ethereum, а затем с Quorum. С другой стороны, квалифицированные блокчейн-разработчики вместо рутинной работы по раз за разом выполняемой переделке упаковки бизнес-данных в блокчейн могут заняться действительно сложными вопросами связанными с развитием децентрализованных платформ.
  • Зонирование ошибок. Не секрет, что в сложных программных системах, а децентрализованные приложения мы можем, без сомнения, отнести к таковым, время от времени что-то идет не так. Если учесть, что многие из используемых децентрализованных компонентов крайне молоды и сыры (просто в силу этой молодости и условий своего создания), то риск этого что-то не так очень сильно возрастает. И ситуация, когда на глаза изумленного пользователя выплывает багровое сообщение Ваша транзакция ёк, потому что..., никого не радует. В случае разделения программных слоев на зеркало и бизнес-приложение, зеркало перенаправляет ошибки технологической части от конечного пользователя на техническую поддержку. Это снимает ненужную психологическую нагрузку с пользователя, которому достаются только бизнес-ошибки, соответствующие его пониманию и возможности исправления. Если пользователь надлежащим образом выполнил свою часть работы, то дальнейшая забота о правильной реализации бизнес-процессов переходит с его плеч на службу технической поддержки, располагающей соответствующими навыками и инструментами. При возникновении технологических проблем у поддержки имеется несколько десятков минут, а то и часов, на то, чтобы обнаружить и исправить ошибку, и если поддержка не забывает о своих обязанностях, то пользователь может так никогда и не узнать о том, что к нему стучались страшные insufficient funds for gas * price + value или gas required exceeds allowance or always failing transaction.
  • Возможность подкапотных модернизаций блокчейн-базиса. Отсутствие прямой зависимости бизнес-приложения от конкретной реализации блокчейн-базиса позволяет произвести необходимые изменения в этом базисе, не затрагивая само бизнес-приложение. А трудозатраты на бизнес-приложение, особенно с развитым диалоговым интерфейсом, может составить 75-95% от общих трудозатрат на программный комплекс. Разумеется, грамотный лид уже внутри приложения выделит интерфейсные классы и предпримет прочие меры обеспечения модульности, а подход лучше не пересобирать то, что еще работает жизнь не раз подтверждала. Таким образом, вы можете:

    >>> Заменить блокчейн-базис или DFS, если, например, вы изначально ошиблись в оценке производительности и сориентировались на недостаточно мощную блокчейн-платформу. Или для простоты начинали на Ethereum, но решили перейти на более взрослое решение. Или вдруг появилась новая убойная блокчейн-платформа (TON!), а вы начали чуток раньше. Или выбранный изначально компонент оказался неработоспособен в условиях конкретной сложной топологии, а вы до этого только в гомогенном облаке. Или в общем, мало ли что может произойти в период быстрого развития всех компонентов децентрализованных систем, который сейчас имеет место.

    >>> Создавать в качестве блокчейн-базиса сложные комбинированные решения. Например, в одном из исследовательских проектов в качестве базиса использовалось два параллельных блокчейна Ethereum это давало возможность, с одной стороны, выделить канал для высокоприоритетных неблокируемых операций, оставив пакетные и неспешные в своем канале, а с другой, просто создавало возможность кратного увеличения пропускной способности.
  • Возможность смены юридически значимой криптографии. В определенной степени это подпункт предыдущего пункта. Но опыт нашего проекта с международной гарантией заставляет вынести его отдельно. И, трактуя этот опыт более широко, при трансграничном применении, особенно во многих юрисдикциях, желательно, чтобы ваша платформа не только могла менять криптографию, но и могла работать одновременно в разных криптографиях с разными участниками. Здесь, безусловно, возникает много интересных и сложных технических и юридических вопросов, например, возможность введения в состав сети доверенных узлов перешифровывания и заверения подписей будем их решать.
  • Потенциальная возможность стандартизации и межсетевого обмена на уровне состояний процессов. На нынешнем этапе развития корпоративных децентрализованных приложений данный пункт, наверное, из разряда космических кораблей, бороздящих просторы Вселенной. Но плох тот минометчик, который не умеет кидать мину за холм. Выделение абстрактных объектов и их формализация неотъемлемый этап процессов стандартизации и обязательное условие широкой интеграции блокчейн-сетей. Очевидно, что одним универсальным бизнес-процессом (или его аналогом) здесь дело не обойдется сложные связи и процессы физического мира и потребности бизнес-приложений наверняка приведут к введению и других абстрактных объектов со своими наборами свойств и возможностей.

Недостатки использования универсального бизнес-процесса


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

  • Где разводить бизнес-логику в хранимых процедурах БД или в коде бизнес-приложений?
  • Что лучше универсальная ЭВМ или спецвычислитель?
  • Вы уверены, что выбранный вами базис сохранит совместимость при последующих жизненных обновлениях? И вообще переживет следующие 2 года?

Ответ достаточно прост, если:

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

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

Мониторинг сети узлов


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

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

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

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

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

  • При критическом отставании контрольной метки блокчейн-канала, что интерпретируется как выпадение узла из блокчейн-сети или полное нарушение ее функционирования
  • При критическом отставании контрольной метки DFS-канала, что интерпретируется как выпадение узла из DFS-сети или полное нарушение ее функционирования
  • При ошибках в очереди операций блокируются все последующие операции, связанные с этим бизнес-объектом (универсальным бизнес-процессом)

Отдельное внимание было уделено обработке ошибок базы данных, используемой Зеркалом. Данная БД используется не только как интерфейсная с бизнес-приложениями, но и как статусный стек для конечного автомата очередей операций Зеркала. Как показал опыт, при наличии специфичных ошибок при изменении данных в таблицах БД может произойти зацикливание операций с массовой повторной отправкой транзакций и прочими удовольствиями. Однажды мы из-за подобной ошибки за два дня сделали полугодовой объем цепочки на Quorum. Поэтому при обнаружении ошибки такого рода Зеркало полностью выключается и ждет ручного реагирования службы поддержки.

Узлы Центрального мониторинга извлекают из СКМ информацию от всех узлов сети (включая себя, кстати) и анализируют ее, позволяя своевременно обнаружить такие опасные или потенциально опасные состояния как, например:
  • Полное нарушение функционирования блокчейн- или DFS-сети
  • Выпадение отдельных узлов из блокчейн- или DFS-сети
  • Отставание отдельных узлов в обработке данных блокчейн-канала
  • Наличие ошибок в очередях обработки Зеркала
  • Наличие зависания операций и чрезмерного роста очередей в Зеркале

На картинке ниже пример простейшего монитора одной из наших тестовых сетей:



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

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

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

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

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


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

Первым был проект выпуска международной гарантии, в котором нашими партнерами были коллеги из Белоруссии март-декабрь 2018 года.

Начинали мы с конфигурации Ethereum Ethereum Swarm Крипто-Про (DLT-DFS-криптография), хорошо зарекомендовавшей себя в исследовательских проектах. Вместо использовавшейся публичной тестовой PoA-сети Ethereum Rinkeby была поднята приватная сеть Ethereum PoA и приватная сеть Ethereum Swarm. Каких-либо технических проблем изначально не выплыло, но мы столкнулись с криптографической неприятностью один из белорусских участников наотрез отказывался использовать предлагаемые нами средства криптографии, ссылаясь на локальный закон об электронном документообороте. Найти качественное решение в моменте тогда не удалось, но появилось устойчивое понимание о непростой и важной роли криптографии в успехе международных проектов.

Уже в процесс прогона контрольных сделок на реальной инфраструктуре сети (каждый участник развернул узел на своих ресурсах) были выявлены сбои в работе Ethereum Swarm потери файлов составляли на уровне 20%. Было сделано предположение, что потери связанны с проблемами, возникающими в клиенте Swarm при параллельной отправке нескольких файлов. В целом данное предположение подтвердилось: опытным путем удалось подобрать паузу между отправками в Swarm отдельных файлов в 5 секунд. При переходе к совсем боевой конфигурации сети, которая в силу особенностей примененного сетевого сегментирования в инфраструктуре Райффайзенбанка потребовала создания транзитного Swarm-узла, выявилась критическая проблема Ethereum Swarm допускал при работе через транзитный узел потерю до 30% файлов. Расслоенная архитектура и хорошая система мониторинга позволила успешно провести реальный выпуск гарантии в режиме ручной подкачки бензина, но судьба Ethereum Swarm была предрешена. Надо сказать, что заявленная способность Ethereum Swarm работать в топологиях с отсутствием прямой связи отправитель-получатель была одной из главных причин выбора его в качестве технологической основы DFS, и неспособность его надежно работать в таком режиме создала массу проблем.

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

Следующим проектом стало создание внутригрупповой сети для Группы Компаний Аскона сентябрь 2018 текущее время.

С учетом опыта проекта по международной гарантии в качестве технологической основы для DFS была выбрана IPFS (InterPlanetary File System). Она нормально отрабатывала отправку файлов в параллель, и ей не потребовались специальные подстройки режимов. Единственным, пожалуй, слабым местом IPFS является невозможность (оговоренная!) работать в транзитных топологиях. При построении сетей с большим числом участников реализация каждым из них полной звезды доступов каждого к каждому является, мягко говоря, организационной проблемой. С другой стороны, все участники реализуют доступ между собой и опорными узлами оператора. Поэтому для организации беспрепятственной раздачи файлов был реализован следующий механизм:

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

Таким образом, проект Аскона стартовал в конфигурации Ethereum IPFS Крипто-Про.

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

и тут сразу в двух проектах условно чужих, но в которых мы участвовали как эксперты, на аналогичных конфигурациях словили мега-форки размером в тысячи блоков, с потерей транзакций части сети. Анализ логов и толковищ блокчейн-сообщества привел к неутешительному выводу использование Ethereum PoA (а в некоторых случаях даже и PoW) в компактных сетях с небольшим числом узлов (а корпоративные сети относятся именно сюда) имеет высокий риск появления таких монстров. К тому же в нашей тестовой сети стал периодически показываться загадочный баг, когда узел выпадал из сети и больше не хотел с ней синхронизироваться. Даже после переустановки Ethereum и полной зачистки. Короче, стало ясно, что для прод-сети нужна альтернатива для блокчейн-базиса. И быстро. Очень быстро.

Решением оказался Quorum практически, родной брат Ethereum. Число доработок в Зеркале было минимальным, бизнес-приложение, понятно, доработок вообще не требовало.

На текущий момент переход на Quorum принес только плюсы:

  • Использованный Raft-консенсус исключает форки
  • Отсутствие пустых блоков уменьшает размер цепочки

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

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

В итоге всей этой драматической эволюции мы пришли к конфигурации Quorum IPFS Крипто-ПРО, которую сейчас и используем на внутреннем рынке РФ.

Возможно, кто-то задаст закономерный вопрос: А что же, раньше вы про Quorum не слышали, что ли?. Слышали и про Quorum, и при Hyperledger Fabric, и про EOS. Автор данной статьи даже посещал первый воркшоп на русском языке по Corda осенью 2017 года. Наверное, специально для умного ответа на такие вопросы Гегель и придумал свою Диалектику. Начинавшая исследования в 2016 году небольшая команда имела хороший опыт разработки диалоговых приложений под Windows, а публичный Ethereum (тестовый понятно) имел наименьший порог входа из блокчейн-платформ. А так как мы были заинтересованы в проведении исследований именно по блокчейн-тематике, а не в ковырянии разных докеров, без которых запустить взрослые Quorum или Hyperledger Fabric просто нереально (да и не на всех виртуальных Windows-платформах возможно), то и выбор был очевиден. По мере того как результаты исследований стали привлекать внимание бизнес-подразделений банка и его партнеров, появилась возможность расширить команду, поручить сапоги сапожникам, а пироги пирожникам, разжиться Linux-серверами и так далее. И, естественно, никто не выбрасывал наработанные решения, пока они сохраняли потенциал развития. Диалектика и Эволюция.

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


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

Какие же основные выводы можно сделать из всего этого опыта?

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

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

Поэтому говорить о том, что какая-то из платформ окажется абсолютно доминирующей, я думаю, нельзя. У каждой есть свой круг потенциальных пользователей и задач, где ее использование наиболее рационально и рентабельно. Это касается и Ethereum, и Quorum, и Hyperledger Fabric, и Corda. Здесь как и с языками программирования только Вася и Петя, знающие по одному языку будут до одури спорить о том, что лучше плюсы или жаба. А Семен Петрович и Альберт Иванович, знающие их по десятку, будут мирно рассуждать когда лучше плюсы, а когда жаба.

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

  • Специализированные децентрализованные файловые системы существуют дольше, лучше отлажены и на текущий момент эффективнее справляются с этой задачей по сравнению с аналогичным функционалом DLT-платформ. Мы проводили оценочные эксперименты. По их результатам при использовании Hyperledger Fabric и Corda файлы размером более 2M имеют высокую вероятность застрять в канале, в то время, как IPFS без проблем пробрасывает и 100M. А если ваш бизнес-кейс предполагает движение каких-либо неформализованных документов типа pdf (контрактов, договоров и прочее), то 50M минимум, к которому вам надо готовится.
  • Такой подход позволяет достаточно эффективно физически развести блокчейн-канал и канал передачи файлов, что весьма актуально для систем с высоким смешанным трафиком (транзакции + документы), тем более, что операционный приоритет транзакций обычно более высок.
  • В качестве альтернативы децентрализованным файловым системам неплохо смотрятся облачные файловые хранилища, например, стандарта S3. Они, конечно, несколько подрывают истинную децентрализованность, но с точки зрения безотказности вполне вписываются в распределенные сети, а по скорости обмена и надежности даже превосходят DFS. Тут возможно появление даже неких гибридных решений.
  • Если смотреть немного вдаль, на возможную интеграцию между собой отдельных корпоративных сетей, особенно построенных на разных блокчейн-базисах, то выделение файлового канала потенциально упрощает решение.

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

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

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

Деконструкция TDD

23.09.2020 20:15:33 | Автор: admin

Здравствуйте, меня зовут Дмитрий Карловский. А вы на канале "Core Dump", где мы берём разные темы из компьютерной науки и деконструируем их по полочкам. Начнём мы с разработки через тестирование.


Test Driven Development

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


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


Видео запись этого разбора.


Суть TDD


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


Pure TDD


И тут сразу возникает вопрос вопрос на миллион...


Что делать, когда тест изначально зелёный?


Варианты ответов...


  • Сломать код
  • Удалить тест
  • Это невозможно

Если сломать код, то тесты естественным образом покраснеют. А после того как мы откатим изменение, тесты снова станут зелёными.


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


Наконец, моё любимое: по TDD такого быть не должно. Где-то ты накосячил, что у тебя так получилось. Покайся, грешник.


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


Парадокс воронов


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



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


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


Изначально зелёные тесты неизбежны


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


  1. R G
  2. R G
  3. R G
  4. G ?
  5. G ?
  6. G ?
  7. G ?
  8. G ?

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


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


Правильный TDD


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


Fixed TDD


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


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


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


В такой форме TDD уже можно применять с пользой. Однако...


TDD приводит к куче лишней работы


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


Давайте рассмотрим типичный сценарий написания простой функции...


Итерация В начале В процесссе В результате
1 R R G
2 GR RR GG
3 GGR RRR GGG
4 GGGR GGRR GGGG
5 GGGGR GGGGR GGGGG
6 GGGGGR RRRRRR GGGGGG

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


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


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


Когда TDD полезен


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


  • Исправление дефектов
  • Заранее известный контракт
  • Не заставить себя писать тесты

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


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


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


Программировать ли по TDD?


Если кто-то вам скажет, что он "программирует по TDD", то можете быть уверены, что он попросту не ведает, что творит. У TDD есть ряд фундаментальных проблем, поэтому его применение оправдано лишь в весьма ограниченном числе случаев. И то, не в той форме в которой ему как правило учат многочисленные коучи.


- Ритуализация :-(- Явно некорректный код :-(- Бесполезная работа :-(- Там, где это уместно :-)- Не зацикливаться :-)

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


Что ещё посмотреть по TDD?


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



Продолжение следует..


  • Лайк
  • Подписка
  • Комментарий
  • Поделись-ка

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


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


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


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


На этом пока что всё. С вами был боевой программер Дмитрий Карловский.

Подробнее..

Враг не пройдёт, или как помочь командам соблюдать стандарты разработки

24.09.2020 00:22:57 | Автор: admin
Подход governance as a code обеспечивает контроль соблюдения архитектурных принципов как в части конфигураций инфраструктуры, так и в части программного кода. Правила проверки каждого артефакта, будь то конфигурация k8s, список библиотек или даже описание сценария CI/CD, описаны специальным кодом проверки правил, имеют свой жизненный цикл, могут тестироваться и ничем не отличаются от обычного программного продукта.

Александр Токарев (Сбербанк) расскажет, как и что можно проверять в процессе разработки программного обеспечения, чтобы разрабатывать более безопасные и качественные приложения, и почему Сбербанк решил не использовать такие очевидные решения как SonarCube, а разработать собственное решение на базе Open Policy Agent без дополнительных пакетов над ним. Также Александр покажет, когда выбирать admission controller, когда использовать чистый Open Policy Agent, а когда можно обойтись без какого-либо контроля.

Александр поговорит о том, нужны ли стандарты, что такое язык Rego и что за крутой продукт Open Policy Agent, а также рассмотрит нетиповые кейсы его применения, как с ним работать, и как его использовать для контроля. Email Александра.



Итак, представьте Сбербанк:
  • Более 50 приложений в облачной платформе OpenShift в production;
  • Релизы не реже двух раз в месяц, а порой и чаще;
  • Ожидается не менее 20 новых приложений в год для перевода в Openshift;
  • И все это на фоне микросервисной архитектуры.

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

План перехода к автоматизации


Мы решили переложить все эти задачи на автоматизированный контроль и определили для себя следующие уровни зрелости автоматизированного контроля:
  1. Базовые проверки;
  2. Интеграция с контролем версий;
  3. Повторное использование проверок;
  4. Автоматическое применение проверок;
  5. Коррекции на основе проверок;
  6. Продвинутые проверки когда вы всему научились и можете писать очень крутые вещи.

Уровни построены так, что, если вы не достигли предыдущего, на следующий не перейдете.

Базовые проверки


Google предлагает начать их с Kubernetes и приводит нас к абсолютно старым и неживым проектам:



Можно обнаружить живые и развивающиеся проекты, но при их изучении понимаешь, что для результата надо написать очень много букв с точки зрения CRD, они жёстко прибиты к Kubernetes, у них нет DSL (потому что сложные проверки описываются через REGEXP), и при этом нет возможности дебага политик. Вот пример DSL такого продукта, который проверяет CPU и memory limit в кластере. Это абсолютно своя CRD, которая выводит обычное сообщение, и далее нужно много букв и REGEXP для того, чтобы это контролировать:



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



После долгих поисков мы обнаружили замечательный продукт Open Policy Agent (OPA), который на самом деле движок политик, написанный на Go. Он очень быстрый, потому что inmemory. Им можно проверять всё что угодно с использованием собственного декларативного языка Rego, который не сложнее SQL. Можно использовать внешние данные и встраивать продукт куда угодно. Но самое главное мы можем управлять форматом ответа, когда это не просто boolean true/false, а всё, что мы захотим увидеть.



Простейшая проверка, проверяющая заполнение ревестов и лимитов, включает в себя получение всех контейнеров Kubernetes, которые объявлены в нашем YAML, их проверку на заполнение нужных полей (проходя по структуре YAML) и вывод сообщения (JSON). Если с проверкой всё хорошо, то в result не будет ничего. Три строчки кода и у вас она работает:



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

Примеры проверок K8S




Может создаться ощущение, что Open Policy Agent используется только для Kubernetes, но это не так. Проверять можно всё что угодно, лишь бы это был JSON:
  • Maven;
  • NPM;
  • Terraform;
  • Даже ER diagrams, потому что в том же Power Designer это XML.

Таким образом мы приходим к Open Policy Agent в Сбербанке:



Есть Open Policy Agent и есть система плагинов на Python (потому что на нем элементарно написать преобразования) и конечно же пользовательский интерфейс. Таким образом проверка абсолютно любой конфигурации (K8S YAML, Pom.xml, .properties, .ini) работает просто.

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



Вернемся к примеру maven:



Предположим, мы хотим контролировать использование spring-boot-starter-actuator для написания тех самых readinessProbe. Живая проверка очень проста: мы получаем все dependency, а дальше смотрим, что библиотека это spring-boot-starter-actuator. Как я уже говорил, большая часть проверок это вывод диагностических сообщений об их прохождении/непрохождении:



Однако бывают более сложные кейсы. Предположим, нам нужно проверить конфигурацию Kubernetes на наличие разрешённых image (fluentbit2, envoy3, nginx:1.7.9) и вывести имя запрещённого. Видим, что у нас есть валидная конфигурация с абстрактным nginx и есть конфигурация с запрещённым nginx:



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



Воспользуемся ссылкой от Amazon, они же профессионалы! Большой код, 19 строк букв где та самая лаконичность, которую я вам рекламировал раньше? Какие-то непонятные команды, палки что это? Долго-долго читаем документацию, понимаем, что это так называемый Object comprehension, и что на самом деле коллеги из Amazon транслируют массив как есть в список объектов. Почему? Потому, что из документации непонятно, что можно объявить объект таким элементарным и очевидным образом:



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



А как же написать, чтобы было красиво, и чтобы пользоваться всей мощностью декларативности? На самом деле все очень просто: получаем контейнер Kubernetes, а дальше представляем, что у нас SQL и фактически данными строчками пишем not in. Мыслите в терминах SQL, будет гораздо проще. За 7 строчек кода мы получаем действительно нужную нам информацию все неразрешённые контейнеры name:



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



Плагин Visual Studio Code
Действительно прекраснейший плагин. В нем есть всё, что надо для средств разработки:
  • Syntax check проверка синтаксиса;
  • Highlighting подсветка;
  • Evaluation вычисления проверок;
  • Trace трассировка;
  • Profile профилирование;
  • Unit tests запуск юнит-тестов



Интеграция


Теперь надо это всё интегрировать. Есть два пути интеграции в OPA:
  • Push-интеграция, когда через REST API помещаем наши проверки и внешние данные в Open Policy Agent:

curl -X PUT localhost:8181/v1/data/checks/ --data-binary @check_packages.rego
curl -X PUT localhost:8181/v1/data/checks/packages --data-binary @permitted_packages.json

  • Pull-интеграция OPA bundle server, которая мне очень нравится.

Представьте, у вас есть Open Policy Agent сервер, есть bundle-сервер (который надо написать самостоятельно) и есть данные по вашим проверкам:



В соответствии с конфигурацией Open Policy Agent сервер умеет ходить в bundle-сервер по фиксированному урлу и забирать данные, если они ещё не подготовлены. Далее он ожидает их приема в .gzip формате, полностью поддерживая спецификации кэширования если вы в bundle-сервере реализовали обработку тэга ETag, то Open Policy Agent сервер не будет протаскивать мегабайты ваших проверок через сеть в случае неизменения:



Выполняем проверку. Open Policy Agent имеет прекрасный resfull API после загрузки наших данных мы получаем имя пакета, который мы создали при создании файла, и имя правила. Дальше мы подаём для проверки наш артефакт в виде JSON. Если вы долго читали документацию, то поняли, что наш артефакт надо обрамить в input:



Обрабатываем результат (возвращённый JSON) где угодно в Jenkins, admission controller, UI, whatever Чаще всего результат приходит в таком виде:



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


Политики OPA у нас разработаны для тех проверок, которые не связаны с Kubernetes, и тех, что связаны с Kubernetes, но рекомендуются. Они хранятся в гите и через наш Bundle server, как и метаданные, попадают в Open Policy Agent. Мы пользуемся встроенными механизмами Open Policy Agent для Unit-тестирования и тестируем все наши проверки.

То есть Open Policy Agent проверяет сторонние артефакты (Java configs, K8S config, артефакты CI/CD), а те, которые надо заблокировать для попадания на кластеры, проходят через Gatekeeper, и в итоге наш кластер в безопасности. В принципе мы можем проверять всё что угодно, и смотреть результаты проверок через наш пользовательский интерфейс:



Чего мы добились:
  • У нас есть 24 обязательных правила и 12 необязательных;
  • 80 правил планируется к использованию в данном продукте;
  • Автоматическая проверка одного проекта за 10-20 секунд.

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

Коррекции на основе проверок


Приведу пример банка Goldman Sachs. У них большой облачный кластер из 1800 namespace (12 общих кластера Kubernetes на виртуалках, в каждом кластере по 150 namespace). Всё это управляется из централизованной системы под названием Inventory, где находится информация:
  • По безопасности (Security inventory): Roles, Rolebindings, Clusterroles, Clusterrolbindings;
  • По управлению ресурсами (Capacity Inventory): cpu, memory через ResourceQuotas и LimitRange;
  • По управлению дисками (NFS inventory): Persistent volumes и Persistent volume claims.

Коллеги используют такую архитектуру решения:
  • Pull измененных политик и данных из гита и inventory через bundle-сервер;
  • Создание объектов в K8S на основе данных, создаваемых OPA;
  • Hand-made mutating admission controller на основе JSON, возвращенного OPA, формируется YAML и прогоняется на кластере:

Всё состояние кластера синхронизируются в Bundle server с помощью плагина Kube-mgmt от разработчиков OPA. Bundle server через определенные периоды времени ходит в гит и забирает измененные политики.
Для самой системы Inventory процесс чуть-чуть другой. Она уведомляет Bundle server об изменениях, и он сразу же после этого идёт и забирает данные. Любое изменение вызывает запуск проверок, который вызывает создание JSON, а JSON переводится в YAML, прогоняется через Controller Manager и запускается на кластере.



Когда такая система стоит в продакшене, очень важно её мониторить. Коллеги мониторят 11 показателей, относящихся по большей части к garbage коллектору, Go и времени ответа от OPA-сервера:
  1. Go routine and thread counts;
  2. Memory in use (stack vs heap);
  3. Memory allocated (stack vs heap);
  4. GC stats;
  5. Pointer lookup count;
  6. Roundtrip time by http method;
  7. Percentage of requests under 500ms, 200ms, 50ms;
  8. Mean API request latency;
  9. Recommendations for alerting;
  10. Number of OPA instances up at any given time;
  11. OPA responding under 200ms for 95% of requests.

Что же удалось создать коллегам:
  • 24 проверки;
  • 1 Mb справочных данных по безопасности 3500 правил;
  • 2 Mb справочных данных по дискам и ресурсам 8000 правил;
  • Применение правила на кластере от 2 до 5 минут реально очень быстро.


Продвинутые проверки


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

Fugue


Мы можем использовать Fugue, который представляет движок политик как сервис (governance as a code as a service). Он проверяет конфигурации облаков Amazon, Azure, GCP. Представляете, насколько там сложные политики, что ему надо проверять не только Kubernetes, но и relational database services, VPC и т.д.? Фактически каждый из продуктов каталога Amazon и прочих облаков может быть проверен готовыми политиками.
Fugue работает как admission controller. Ещё одна из очень его больших ценностей это наборы пресетов под регуляторы PSI DSS, HIPAA, SOC, etc. Фактически вы делаете нужную инфраструктуру в Amazon, запускаете продукт и говорите: Проверь на PSI DSS, и вуаля, вы получаете фактически аудит.

Очевидно, что Fugue реализован на OPA, но так как политики очень сложные, коллеги реализовали свой интерпретатор Rego для отладки продвинутых проверок. Выполнение происходит на Open Policy Agent, а отладка уже на своем интерпретаторе. Так как это облачный продукт, у него прекраснейший пользовательский интерфейс, который позволяет понять, какие проверки пройдены, какие нет и процент их соответствия:



Набор правил действительно самый разнообразный есть элементарнейшие вещи в стиле на машине с RDS, если она гарантирует высокую доступность, должны быть использованы multi availability zone deployment:



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



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

Fregot


Fregot это очень живой продукт, который позволяет отлаживать проверки на Rego. За счет чего? У него упрощённый дебаг (breakpoints и watch variables) и расширенная диагностика по ошибкам, которая важна, когда вы будете пользоваться ванильной OPA в этом случае чаще всего вы будете получать сообщение: var _ is unsafe, и вам будет абсолютно непонятно, что с этим делать:



Conftest


Это утилита проверки, использующая Open Policy Agent. Она содержит в себе потрясающе огромный встроенный набор конвертеров, и сама выбирает конвертер, исходя из расширения файла:



Ещё раз обратите внимание, насколько выразительный язык Rego всего одной строкой мы проверяем, что нельзя запускать контейнер из-под root:



Дальше мы говорим: conftest test, и указываем имя того объекта, который нам надо протестировать. И все проверки, которые есть в директории утилиты, проверяются по файлам, а вы получаете результат.

Плюсы:
  • Большое количество конверторов файлов: YAML, INI, TOML, HOCON, HCL, HCL1, CUE, Dockerfile, EDN, VCL, XML.
  • Много примеров проверок при работе с Open Policy Agent.

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


Gatekeeper


Сейчас это Admission controller, а в будущем он будет работать как Mutating admission controller, который не только проверяет на условие, но и в случае прохождения условий меняет команду так, как ему надо:



Admission controller встает между вашими командами и кластером Kubernetes. Вы отправляете команду, admission controller вызывает внешнюю проверку условий и, если условие выполнено, то команда запускается в кластере. Если не выполнено, то команда просто откатывается.

За счет чего обеспечивается повторное использование в продукте Gatekeeper? Есть два объекта:
  • Policy Template это механизм описания функций, где вы задаете текст проверки на Rego и набор входных параметров;
  • Сама политика это вызов функции с указанием значений параметров.

Gatekeeper синхронизирует в течение жизненного цикла своей работы весь кластер кубера в себя, а дальше любое действие кластера вызывает Webhook, по нему команда (YAML объект, каким он будет после применения изменения) приходит в Open Policy Agent. Дальше результат проверки возвращается как пропущенная, либо как нет.

Пример проверки на Gatekeeper:


Это своя CRD, само собой. Мы даем ей имя и говорим, что входной параметр это метки, которые представляют собой массив строк. Пишем на Rego нашу проверку, и в данном случае она проверяет, что для объектов заданы необходимые метки. Мы используем внутри Rego наши входные параметры это тот самый template. А живая политика выглядит так: используй такой-то template и такие-то метки. Если у проекта не будет таких меток, то он просто в кластер не попадёт:



Плюсы:
  • Великолепно работает повторное использование через template;
  • Есть огромные готовые библиотеки проверок;
  • Тесная интеграция с K8S.

В то же время эти плюсы становятся автоматически минусами:
  • Нельзя запустить Gatekeeper без K8S;
  • Нет юнит-тестов констрейнтов и темплейтов;
  • Многословные CRD;
  • Нет UI (пользовательского интерфейса);
  • информацию для аналитики приходится получать через парсинг логов;
  • Закрыта возможность вызова внешних сервисов;
  • Нельзя использовать bundle server для помещения в него внешних данных;
  • Функционал Mutation в процессе разработки, о чем регулярно напоминают разработчикам в их гите.

Что мы получили на выходе


Итак, фактически мы преобразовали уровни зрелости технологического контроля к готовому технологическому стеку:



OPA на практике use cases


Как мы увидели, Open Policy Agent решает задачи проверки любых структурированных данных, а также коррекции проверенных данных. Хотя на самом деле Open Policy Agent позволяет делать гораздо больше, например, помогает решать задачи авторизации, задачи database row level security (sql databases, ElasticSearch), а некоторые даже пишут на нём даже игры (Corrupting the Open Policy Agent to Run My Game).

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

OPA as a sidecar


У коллег в Pinterest всё развернуто в Amazon, поэтому у них принят подход Zero-trust security и, само собой, все реализовано на OPA. Под его контролем находится всё, что связано с Kubernetes, с виртуальными машинами, авторизацией в Kafka, а также с авторизацией на Envoy.

Таким образом их нагрузка колеблется от 4.1M до 8.5M QPS в секунду. При этом имеется кэш с длительностью жизни 5 минут. В Open Policy Agent приходит от 204 до 437 тысяч в секунду запросов действительно большие цифры. Если вы хотите работать с такими порядками, надо конечно думать о производительности.

Что я рекомендую в этой части:
  • Network footprint подумать о тех нюансах, которые приносит сеть.
  • OPA library single-thread если вы используете Open Policy Agent не как сервер, а как библиотеку, то библиотека будет однопоточной.
  • Use OPA server instead multi-thread вам придется делать многопоточность вручную.
  • Memory for data 20x from raw data если у вас есть 10 МБ внешних данных каких-нибудь JSON-справочников, то они превратятся в 200 МБ на сервере.
  • Partial evaluation ms to ns вам надо разобраться, как работает его фишка механизма предрасчета, фактически компилирование статической части проверок.
  • Memory for partial evaluation cache вам надо понимать, что для всего этого требуются кэши.
  • Beware arrays с массивами Open Policy Agent работает не так хорошо.
  • Use objects instead и именно поэтому коллеги с Amazon транслировали массивы в объекты.

Как же коллеги из Pinterest решили эти задачи? На каждом сервисе или каждом хосте, или ноде у них расположен Open Policy Agent как sidecar, и работают они с ним через библиотеку, а не напрямую через REST, как показывал я. В той самой библиотеке находится кэш с временем жизни 5 минут:



Очень интересно, как происходит у них доставка политик. Есть кластер, есть Bitbucket, а дальше данные из него хуками на коммит пробрасываются на S3. Так как запись в него асинхронна в принципе, то результат идет в Zookeeper, а он уведомляет Sidecar об изменениях. Sidecar забирает данные из S3, и всё прекрасно:



OPA authorization


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



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



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



Penetration testing


На самом деле авторизация это всегда о безопасности. Поэтому в 2018 году было проведено Penetration-тестирование: 6 человек в течение 18 дней пытались взломать Open Policy Agent. Результаты тестирования говорят, что OPA это действительно безопасное решение:



Конечно, были ошибки:
Identified Vulnerabilities
OPA-01-001 Server: Insecure Default Config allows to bypass Policies (Medium)
OPA-01-005 Server: OPA Query Interface is vulnerable to XSS (High)
Miscellaneous Issues
OPA-01-002 Server: Query Interface can be abused for SSRF (Medium)
OPA-01-003 Server: Unintended Behavior due to unclear Documentation (Medium)
OPA-01-004 Server: Denial of Service via GZip Bomb in Bundle (Info)
OPA-01-006 Server: Path Mismatching via HTTP Redirects (Info) Conclusions Introduct

Но это были те ошибки, которые случаются по большей части от того, что есть проблемы с документацией:
What is more, the shared documentation was unclear and misleading at times (see OPA-01-001), so that arriving at a secure configuration and integration would require a user to have an extensive and nearly-internal-level of knowledge. As people normally cannot be expected to know what to look for, this poses a risk of insecure configurations.


OPA не экзотика


Из того, что я сказал, может создаться ощущение, что Open Policy Agent это странный, непонятный, никому не нужный продукт. На самом деле в Open Policy Agent полно фишек, которые еще никто не использует:
  • Юнит-тестирование;
  • Трейсинг, профайлинг и бенчмаркинг;
  • Механизмы ускорения производительности Conditional evaluation;
  • Возможность обращения к внешним http-сервисам;
  • Работа с JWT-токенами.

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



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



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



Open Policy Agent находится под крылом CNCF, и он более чем живой. В итоге хотелось бы сказать, что да, по данному продукту сложно найти информацию даже в объемной и неоднозначной документации. Но продукт активно развивается, живет не только в контейнерах, его можно использовать в абсолютно разных кейсах. И он действительно безопасен, как показали Penetration-тесты.

Если вы хотите его использовать, вам придется создавать UI и научиться писать правила на языке, близком к естественному, а не на языке кода. Учите Rego и используйте Fregot для дебага.

Помните, что Гугл, скорее всего, вам не поможет.

Тем временем мы готовимся к конференции HighLoad++, которая состоится офлайн 9 и 10 ноября в Москве (Сколково). Это будет наша первая очная встреча после 9 месяцев онлайнового общения.

HighLoad++ это 3000 участников, 160 докладов, 16 параллельных треков докладов, мастер-классов и митапов. Единственная конференция, где за два дня можно узнать, как устроены Facebook, ВКонтакте, Одноклассники, Яндекс, Mail.ru, Amazon, Badoo, Авито, Alibaba и другие крупнейшие компании.



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

Новости и подборки докладов мы публикуем в рассылке и в telegram-канале @HighLoadChannel подпишитесь, чтобы быть в курсе обновлений.
Подробнее..

Полиморфные аллокаторы C17

24.09.2020 14:19:46 | Автор: admin
Уже совсем скоро в OTUS стартует новый поток курса C++ Developer. Professional. В преддверии старта курса наш эксперт Александр Ключев подготовил интересный материал про полиморфные аллокаторы. Передаем слово Александру:



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

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

Если вы хотите std::vector с определенным аллокатором памяти, можно задействовать Allocator параметр шаблона:

auto my_vector = std::vector<int, my_allocator>();


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

auto my_vector = std::vector<int, my_allocator>();auto my_vector2 = std::vector<int, other_allocator>();auto vec = my_vector; // okvec = my_vector2; // error

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

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

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

Одной из основных проблем на текущий момент остается несовместимость новых версий контейнеров из std::pmr с аналогами из std.

Основные компоненты std::pmr:


  • std::pmr::memory_resource абстрактный класс, реализация которого в конечном счете отвечают за работу с памятью.
  • Содержит следующий интерфейс:
    • virtual void* do_allocate(std::size_t bytes, std::size_t alignment),
    • virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
    • virtual bool do_is_equal(const std::pmr::memory_resource& other) const noexcept.
  • std::pmr::polymorphic_allocator имплементация стандартного аллокатора, использует указатель на memory_resource для работы с памятью.
  • new_delete_resource() и null_memory_resource() используются для работы с глобальной памятью
  • Набор готовых пулов памяти:
    • synchronized_pool_resource
    • unsynchronized_pool_resource
    • monotonic_buffer_resource
  • Специализации стандартных контейнеров с полиморфным аллокатором, std::pmr::vector, std::pmr::string, std::pmr::map и тд. Каждая специализация определена в том же заголовочном файле, что и соответствующий контейнер.
  • Набор готовых memory_resource:
    • memory_resource* new_delete_resource() Свободная функция, возвращает указатель на memory_resource, который использует глобальные операторы new и delete выделения памяти.
    • memory_resource* null_memory_resource()
      Свободная функция возвращает указатель на memory_resource, который бросает исключение std::bad_alloc на каждую попытку аллокации.

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

  • class synchronized_pool_resource : public std::pmr::memory_resource
    Потокобезопасная имплементация memory_resource общего назначения состоит из набора пулов с разными размерами блоков памяти.
    Каждый пул представляет из себя набор из кусков памяти одного размера.
  • class unsynchronized_pool_resource : public std::pmr::memory_resource
    Однопоточная версия synchronized_pool_resource.
  • class monotonic_buffer_resource : public std::pmr::memory_resource
    Однопоточный, быстрый, memory_resource специального назначения берет память из заранее выделенного буфера, но не освобождает его, т.е может только расти.

Пример использования monotonic_buffer_resource и pmr::vector:

#include <iostream>#include <memory_resource>   // pmr core types#include <vector>        // pmr::vector#include <string>        // pmr::string int main() {char buffer[64] = {}; // a small buffer on the stackstd::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');std::cout << buffer << '\n'; std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; std::pmr::vector<char> vec{ &pool };for (char ch = 'a'; ch <= 'z'; ++ch)    vec.push_back(ch); std::cout << buffer << '\n';}

Вывод программы:

_______________________________________________________________aababcdabcdefghabcdefghijklmnopabcdefghijklmnopqrstuvwxyz______

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

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

Можно, конечно, вызвать reserve() для вектора, чтобы минимизировать реаллокации, но цель примера именно в том чтобы продемонстрировать, как меняется monotonic_buffer_resource при расширении контейнера.

Хранение pmr::string


Что если мы хотим хранить строки в pmr::vector?

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

Если вы хотите воспользоваться этой возможностью, нужно использовать std::pmr::string вместо std::string.

Рассмотрим пример с заранее выделенным на стеке буфером, который мы передадим в качестве memory_resource для std::pmr::vector std::pmr::string:

#include <iostream>#include <memory_resource>   // pmr core types#include <vector>        // pmr::vector#include <string>        // pmr::string int main() {std::cout << "sizeof(std::string): " << sizeof(std::string) << '\n';std::cout << "sizeof(std::pmr::string): " << sizeof(std::pmr::string) << '\n'; char buffer[256] = {}; // a small buffer on the stackstd::fill_n(std::begin(buffer), std::size(buffer) - 1, '_'); const auto BufferPrinter = [](std::string_view buf, std::string_view title) {    std::cout << title << ":\n";    for (auto& ch : buf) {        std::cout << (ch >= ' ' ? ch : '#');    }    std::cout << '\n';}; BufferPrinter(buffer, "zeroed buffer"); std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};std::pmr::vector<std::pmr::string> vec{ &pool };vec.reserve(5); vec.push_back("Hello World");vec.push_back("One Two Three");BufferPrinter(std::string_view(buffer, std::size(buffer)), "after two short strings"); vec.emplace_back("This is a longer string");BufferPrinter(std::string_view(buffer, std::size(buffer)), "after longer string strings"); vec.push_back("Four Five Six");BufferPrinter(std::string_view(buffer, std::size(buffer)), "after the last string");   }

Вывод программы:

sizeof(std::string): 32sizeof(std::pmr::string): 40zeroed buffer:_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________after two short strings:#m######n#############Hello World######m#####@n#############One Two Three###_______________________________________________________________________________________________________________________________________________________________________________#after longer string strings:#m######n#############Hello World######m#####@n#############One Two Three####m######n#####################________________________________________________________________________________________This is a longer string#_______________________________#after the last string:#m######n#############Hello World######m#####@n#############One Two Three####m######n#####################________#m######n#############Four Five Six###________________________________________This is a longer string#_______________________________#

Основные моменты, на которые нужно обратить внимание в данном примере:

  • Размер pmr::string больше чем std::string. Связано этот с тем, что добавляется указатель на memory_resource;
  • Мы резервируем вектор под 5 элементов, поэтому при добавлении 4х реаллокаций не происходит.
  • Первые 2 строки достаточно короткие для блока памяти вектора, поэтому дополнительного выделения памяти не происходит.
  • Третья строка более длинная и для потребовался отдельный кусок памяти внутри нашего буфера, в векторе при этом сохраняется только указатель на этот блок.
  • Как можно видеть из вывода, строка This is a longer string расположена почти в самом конце буфера.
  • Когда мы вставляем еще одну короткую строку, она попадает снова в блока памяти вектора

Для сравнения проделаем такой же эксперимент с std::string вместо std::pmr::string

sizeof(std::string): 32sizeof(std::pmr::string): 40zeroed buffer:_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________after two short strings:###w###########Hello World########w###########One Two Three###_______________________________________________________________________________________________________________________________________________________________________________________________#new 24after longer string strings:###w###########Hello World########w###########One Two Three###0#######################_______________________________________________________________________________________________________________________________________________________________________#after the last string:###w###########Hello World########w###########One Two Three###0#######################________@##w###########Four Five Six###_______________________________________________________________________________________________________________________________#


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

Еще раз про расширение вектора:


Упоминалось, что когда память в пуле заканчивается, аллокатор запрашивает ее с помощью оператора new().

На самом деле это не совсем так память запрашивается у memory_resource, возвращаемого с помощью свободной функции
std::pmr::memory_resource* get_default_resource()
По умолчанию эта функция возвращает
std::pmr::new_delete_resource(), который в свою очередь выделяет память с помощью оператора new(), но может быть заменен с помощью функции
std::pmr::memory_resource* set_default_resource(std::pmr::memory_resource* r)

Итак, давайте рассмотрим пример, когда get_default_resource возвращает значение по умолчанию.

Нужно иметь в виду, что методы do_allocate() и do_deallocate() используют аргумент выравнивания, поэтому нам понадобится С++17 версия new() c поддержкой выравнивания:

void* lastAllocatedPtr = nullptr;size_t lastSize = 0; void* operator new(std::size_t size, std::align_val_t align) {#if defined(_WIN32) || defined(__CYGWIN__)auto ptr = _aligned_malloc(size, static_cast<std::size_t>(align));#elseauto ptr = aligned_alloc(static_cast<std::size_t>(align), size);#endif if (!ptr)    throw std::bad_alloc{}; std::cout << "new: " << size << ", align: "          << static_cast<std::size_t>(align)          << ", ptr: " << ptr << '\n'; lastAllocatedPtr = ptr;lastSize = size; return ptr;}

Теперь давайте вернемся к рассмотрению основного примера:

constexpr auto buf_size = 32;uint16_t buffer[buf_size] = {}; // a small buffer on the stackstd::fill_n(std::begin(buffer), std::size(buffer) - 1, 0); std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)*sizeof(uint16_t)}; std::pmr::vector<uint16_t> vec{ &pool }; for (int i = 1; i <= 20; ++i)vec.push_back(i); for (int i = 0; i < buf_size; ++i)std::cout <<  buffer[i] << " "; std::cout << std::endl; auto* bufTemp = (uint16_t *)lastAllocatedPtr; for (unsigned i = 0; i < lastSize; ++i)std::cout << bufTemp[i] << " ";

Программа пытается положить 20 чисел в вектор, но учитывая, что вектор только растет, нам нужно места больше чем в зарезервированном буфере с 32 записями.

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

Вывод программы:

new: 128, align: 16, ptr: 0xc73b201 1 2 1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 01 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 132 0 0 0 0 0 0 0 144 0 0 0 65 0 0 0 16080 199 0 0 16176 199 0 0 16176 199 0 0 15344 199 0 0 15472 199 0 0 15472 199 0 0 0 0 0 0 145 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Судя по выводу в консоль выделенного буфера хватает только для 16 элементов, и когда мы вставляем число 17, происходит новая аллокация 128 байт с помощью оператора new().

На 3й строчке мы видим блок памяти аллоцированный с помощью оператора new().

Приведенный выше пример с переопределением оператора new() вряд ли подойдет для продуктового решения.

К счастью, нам никто не мешает сделать свою реализацию интерфейса memory_resource.

Все что нам нужно при этом

  • унаследоваться от std::pmr::memory_resource
  • Реализовать методы:
    • do_allocate()
    • do_deallocate()
    • do_is_equal()
  • Передать нашу реализацию memory_resource контейнерам.

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


Читать ещё


Подробнее..

Перевод Знакомимся с Event Sourcing. Часть 2

24.09.2020 16:22:36 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Java Developer. Professional.
Читать первую часть.



Особенности реализации Event Sourcing


С технической точки зрения для Event Sourcing требуется только реализация записи событий в журнал и чтения из журнала.

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

Журнал событий (event log) очень распространенный паттерн, используемый совместно с системами обмена сообщениями (Message broker, Message-oriented middleware) и системами обработки потоков событий. Брокер сообщений, используемый как журнал событий, при необходимости может хранить всю историю сообщений.

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

В более сложных Event Sourcing-системах должны присутствовать производные хранилища состояния для эффективных запросов на чтение, так как получение текущего состояния через обработку всего журнала событий со временем может перестать масштабироваться. И реляционные, и документные БД могут использоваться и как журнал событий и как хранилище производных сущностей, через которые можно быстро получить текущее состояние. Фактически такое разделение задач представляет собой CQRS (Command Query Responsibility Segregation, разделение ответственности на команды и запросы). Все запросы направляются в производное хранилище, что позволяет оптимизировать его независимо от операций записи.

Помимо технической части есть и другие моменты, на которые стоит обратить внимание.

Потенциальные проблемы Event Sourcing


Несмотря на преимущества Event Sourcing, у него есть и недостатки.

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

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

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

Event Sourcing может хорошо работать в больших системах, так как паттерн журнал событий естественным образом масштабируется горизонтально. Например, журнал событий одной сущности необязательно должен физически находиться вместе с журналом событий другой сущности. Однако, такая легкость масштабирования приводит к дополнительным проблемам в виде асинхронной обработки и согласования данных в конечном счете (eventually consistent). Команды на изменение состояния могут приходить на любой узел, после чего системе необходимо определить, какие узлы отвечают за соответствующие сущности и направить команду на эти узлы, после чего обработать команду, а затем реплицировать сгенерированные события на другие узлы, где хранятся журналы событий. И только после завершения этого процесса новое событие становится доступным как часть состояния системы. Таким образом, Event Sourcing фактически требует, чтобы обработка команд была отделена от запроса состояния, то есть CQRS.

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

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

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

Выводы


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

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

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

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

Подробнее..

Новинки Laravel 8

24.09.2020 18:11:15 | Автор: admin


Привет, хабр! В преддверии старта курса "Framework Laravel", наш эксперт и по совместительству активный участник российского сообщества Laravel Виталий Юшкевич, подготовил обзор новинок в Laravel 8. Передаю слово Виталию:


Всем привет!


Последний релиз Laravel 8 получился неоднозначным. С одной стороны, появилось много новых инструментов для быстрого старта; улучшена работа фабрик, приведена структура папок в формат "как обычно используется". С другой стороны, в этом релизе существенно изменили подходы к базовой разработке веб-приложений на Laravel, а привычный ui с поддержкой preset бутстрапа и реакта добавляли фиксом отдельной библиотекой после большого количества возмущений со стороны сообщества.


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




Попробуем установить новую версию Laravel и посмотреть на новинки внимательнее.


Приложение laravel устанавливается с помощью глобально установленного установщика https://github.com/laravel/installer. Если у вас он уже был установлен, то можете изменить зависимость в вашем глобальном composer.json. Он находится по следующему пути:


~/.composer/composer.json. 

Актуальная версия для laravel 8:


"laravel/installer": "^4.0".

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


composer global update 

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


При создании нового приложения добавился новый флаг:


--jet             Installs the Laravel Jetstream scaffolding

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


Для установки и работы laravel 8 подняли требования к минимальной версии php до 7.3. Все требования можно посмотреть по ссылке https://laravel.com/docs/8.x/installation#server-requirements


Изменения


Изменение структуры директорий модели


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


При этом сохранилась обратная совместимость в работе команды make:model. Если у вас есть директория Models, то при выполнении команды php artisan make:model ModelName новая модель будет автоматически создана в директории Models с соответствующим namespace (App\Models). Если же у вас нет этой директории, то модель создастся в корне app и namespace будет App. Мелочь, а приятно.


Migration Squashing


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


В Laravel 8 появилась команда schema:dump, которая создает дамп текущей базы в отдельную директорию schema. Если вызвать эту команду с флагом prune, то все текущие миграции удалятся. При следующих обновлениях базы сначала будет выполняться этот дамп, а затем оставшиеся миграции.


Model Factory Classes


В этом релизе была переработана работа с фабриками. Model factory это полноценный класс, а вызов фабрики осуществляется через вызов метода модели factory() (* Модель должна использовать трейт HasFactory).


Это существенно упрощает работу с фабриками. Например, теперь гораздо удобнее задавать разные состояния для генерации модели через отдельные методы и при вызове метода make() или create() последовательно применять цепочки состояний. Например,


User::factory()->active()->subscribed()->client()->make()

Улучшена работа со связями. Появились 2 волшебных метода: has и for. Например, если мы хотим создать пользователя с 5 постами, мы можем написать:


User::factory()->has(Post::factory()->count(5))->create()более "магичный" вариант тоже будет работатьUser::factory()->hasPosts(5)->create()

Метод for() работает аналогично для отношений BelongsTo


Улучшения в работе Maintenance Mode


У команды php artisan down появились новые флаги:


--redirect[=REDIRECT]  The path that users should be redirected to--render[=RENDER]      The view that should be prerendered for display during maintenance mode--retry[=RETRY]        The number of seconds after which the request may be retried--secret[=SECRET]      The secret phrase that may be used to bypass maintenance mode--status[=STATUS]      The status code that should be used when returning the maintenance mode response [default: "503"]

Если мы зададим параметр secret, например так:


php artisan down --secret=my_secret_down_key

то мы сможем обратиться к сайту по адресу mysite.com/my_secret_down_key, после чего работать с сайтом, как будто он в обычном режиме (выставляется кука). При этом для остальных пользователей приложение будет находится в maintenance режиме.


Параметр render позволяет отобразить шаблон как финальный html код. Этот параметр можно использовать как для отображения кастомного шаблона (задаем наш шаблон), так и использовать отображение рендера страниц ошибок (render="errors::503"). Последний вариант использования полезен в следующем если во время обновления приложения вы выполняете composer update, то ваши зависимости могут поломаться и вывод стандартной заглушки поломаться. Если вы будете использовать render 503 страницы, то вывод не поломается и будет выводится подготовленный заранее шаблон html страницы.


Улучшена работа с Closure-Based Event Listeners


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


Суть улучшения сведена к оптимизациии:


Раньше нужно было писать так:Event::listen(MyEvent::class, function(MyEvent $event) {    // do smth})Теперь можно писать так:Event::listen(function(MyEvent $event) {    // do smth})Если нужно сделать обработкик queueable, то замыкание нужно обернуть в другое:Event::listen(queueable(function(MyEvent $event) {    // do smth}))

Time Testing helper


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


Пример из документации:


public function testTimeCanBeManipulated(){    // Travel into the future...    $this->travel(5)->milliseconds();    $this->travel(5)->seconds();    $this->travel(5)->minutes();    $this->travel(5)->hours();    $this->travel(5)->days();    $this->travel(5)->weeks();    $this->travel(5)->years();    // Travel into the past...    $this->travel(-5)->hours();    // Travel to an explicit time...    $this->travelTo(now()->subHours(6));    // Return back to the present time...    $this->travelBack();}

Улучшена работа rate-limit


В предыдущей версии rate limit задавался на уровне middleware. Начиная с 8 версии управление rate limit перенесено в RouteServiceProvider через замыкания. Существенно улучшена гибкость работы. Теперь в рамках одного midleware можно настроить разные rate limit, а также устанавливать лимиты для пользователей или по ip (например, привелигированным пользователям дать менее жесткие ограничения).


JetStream и изменения frontend


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


Job batching


Фасаду Bus добавили новый метод batch() для обработки блоков задач. Можно контролировать запуск следующих методов после выполнения всех задач. Пример кода из документации:


use App\Jobs\ProcessPodcast;use App\Podcast;use Illuminate\Bus\Batch;use Illuminate\Support\Facades\Bus;use Throwable;$batch = Bus::batch([    new ProcessPodcast(Podcast::find(1)),    new ProcessPodcast(Podcast::find(2)),    new ProcessPodcast(Podcast::find(3)),    new ProcessPodcast(Podcast::find(4)),    new ProcessPodcast(Podcast::find(5)),])->then(function (Batch $batch) {    // All jobs completed successfully...})->catch(function (Batch $batch, Throwable $e) {    // First batch job failure detected...})->finally(function (Batch $batch) {    // The batch has finished executing...})->dispatch();return $batch->id;

Другие изменения


Также в этом релизе были добавлены closure для job dispatch, улучшения artisan serve, обновления Routing Namespace и исправления багов. Полный changelog доступ в документации. https://laravel.com/docs/8.x/releases и в репозитории :)


Вместо итогов


Этот релиз добавляет удобства в работу, привносит много улучшений. Вместе с тем, нет ощущения, что это был самый сильный релиз Laravel. Изменения по frontend части вызвали достаточно много обсуждений, "вынудив" добавить фиксом старый ui через отдельную библиотеку. Taylor обозначил новый вектор развития фреймворка, с которым многие могут не согласится. Сохранит ли Taylor этот вектор и в следующем релизе? Сохранит ли laravel такие же темпы привлечения людей в свое комьюнити? Время будет единственным судьей.


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

Подробнее..

Вредные советы Как обучить джуниора

25.09.2020 14:16:06 | Автор: admin

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

Первая встреча с джуниором

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

Создание учетных записей

Разумеется, учетные записи не должны быть готовы. В идеале админ Аркадий должен выпучить на вас глаза и спросить: Какой еще новый сотрудник? Мне никто ничего не говорил. Администраторам в принципе все нужно говорить в последний момент, чтобы не расслаблялись. Не лишним будет потянуть с созданием учеток пару недель и проверить, начнет ли джуниор сам спрашивать о них. Тем самым вы проверите его настойчивость и способность goals achievement.

Знакомство с командой

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

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

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

Начало работы и эстимейты

Итак, ваш молодой боец приступил к выполнению своей первой задачи. Хорошо бы ее поставить устно, но подойдет и тикет в Jira. Первое, что вы должны спросить, так это estimate. Джуниоров особенно полезно приучать к временным оценкам задач. Далее я даю вам подробную инструкцию как вести себя при оценивании:

  1. Джуниор называет вам какое-то число. Допустим, восемь.

  2. Вам нужно скривить рот, глубоко вздохнуть и спросить: Что восемь?

  3. Слегка смутившись, джуниор вам ответит: Ну восемь часов

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

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

  6. Не обращая внимания на бледное лицо джуниора, уходите без каких-либо объяснений.

Такой подход настроит джуниора на рабочий лад и придаст ему настроения.

Код-ревью и разбор ошибок

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

Менторинг

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

Поддержка

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

Учите хорошим практикам

Наверняка кто-то из вас заметил фразу зачем тебе юнит-тесты? в предыдущем пункте. Сразу дайте джуниору понять, что юнит-тесты это зло. Когда разработчик пишет юнит-тесты, он заявляет всему миру, что он в себе не уверен. Он буквально кричит: Ребята, мой код не работает!. Вы хотите работать с такими профессионалами? Вот и я тоже. Помимо юнит-тестов, у вас не должно быть всяких новомодных CI и CD, интеграционных и, прости Господи, e2e тестов. Но все это тема для отдельной статьи.

Про мотивацию

Мотивация быть должна! Но, разумеется, не материальная. Если вы поднимете джуниору зарплату, он просто расслабится и начнет писать код еще хуже, чем в самом начале. Мотивировать нужно поступками, фразами и испытаниями. Очень полезно при всех его коллегах воскликнуть: Б***ь, Сережа, ну что это за *****? Маша из бухгалтерии решит эту задачу лучше. Это разозлит джуниора, но злость эта будет положительной и правильной. Я бы даже сказал, праведной! Впредь ему захочется писать как можно лучше, чтобы даже Света из той же бухгалтерии не смогла сделать круче.

Если джуниор слишком любопытен

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

Заключение

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

Подробнее..

Простой интерпретатор Lisp на Umka

26.09.2020 22:13:23 | Автор: admin

Разработка моего статически типизированного скриптового языка Umka вошла в ту стадию, когда потребовалась проверка языковых возможностей на более сложных примерах, чем скрипты в пару десятков строк. Для этого я решил реализовать на своём языке интерпретатор Lisp. На это меня вдохновил педагогический эксперимент Роба Пайка, одного из создателей языка Go. Недавно Пайк опубликовал маленький интерпретатор Lisp на Go. Особенно впечатлило замечание Пайка, что описание интерпретатора заключено на одной странице 13 древнего руководства по Lisp 1.5. Учитывая синтаксическое родство Umka и Go, было трудно не поддаться соблазну построить такой интерпретатор на Umka, но не буквальным переносом кода Пайка, а полностью заново, от основ. Надеюсь, знатоки Lisp и функциональных языков простят мне наивное изумление от соприкосновения с прекрасным.

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

Определение минимального интерпретатора Lisp действительно занимает меньше страницы. Конечно, с некоторой натяжкой: в нём используются функции, определённые на нескольких предыдущих страницах. Кажется, создатель Lisp Джон Маккарти из азарта старался превзойти сам себя в лаконизме и в итоге опубликовал микроруководство по Lisp, содержащее определение языка вместе с исходником интерпретатора в общей сложности две журнальные страницы. Правда, добавил в заголовок: "Not the whole truth".

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

Базовые конструкции языка для тех, кто с ними не знаком
  • (car x) выделение головы списка x

  • (cdr x) выделение хвоста списка x

  • (cons x y) соединение списков x и y

  • (atom x) проверка x на атомарность

  • (eq x y) проверка атомарных элементов x и y на равенство

  • (cond (a x) (b y)) выбор значения x или y по условию a или b

  • (quote x) указание использовать x как есть, без вычисления

  • ((lambda (x) a) y) вызов безымянной функции с телом a, формальным параметром x и фактическим параметром y

  • ((label ff (lambda (x) a)) y) присвоение безымянной функции имени ff

  • t истина

  • nil ложь или пустое выражение

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

((label fac (lambda (n) (cond ((eq n 0) 1) ((quote t) (mul n (fac (sub n 1))))))) 6)

В микроруководстве Маккарти этими средствами выражен весь интерпретатор Lisp, за исключением лексического и синтаксического разбора. В руководстве Lisp 1.5 на той самой странице 13 приведён почти такой же интерпретатор, но в более человекочитаемом псевдокоде. Его я и взял за основу своего маленького проекта. Потребовалось лишь добавить разбор текста программы, некое подобие REPL и импровизированную арифметику. Роб Пайк, видимо, поступил так же, но отказался от конструкции label в пользу defn, которая позволила ему не определять функцию заново всякий раз, когда требуется её вызвать. В ядре Lisp такой возможности не предусмотрено.

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

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

Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru