<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Для продолжающих</title>
    <description>Для тех, кто уже знает основы и хочет продолжать профессионально развиваться. </description>
    <link>https://tproger.ru/tag/for-amateurs</link>
    <atom:link href="https://tproger.ru/tag/for-amateurs/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sat, 03 Oct 2026 14:17:22 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Для продолжающих</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>itertools в Python: ленивые итераторы без лишних циклов</title>
      <link>https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov</link>
      <comments>https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov</guid>
      <description><![CDATA[<p>Разбираем модуль itertools из стандартной библиотеки Python: как ленивые итераторы экономят память, какие функции использовать чаще всего и где поджидают подводные камни.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov">itertools в Python: ленивые итераторы без лишних циклов</a>»</p>]]></description>
      <category><![CDATA[Алгоритмы и структуры данных]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 12 Jul 2026 17:20:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если ваш код регулярно превращается в пять вложенных for и огромный список, который держится в памяти только ради того, чтобы тут же быть выброшенным в мусор, — скорее всего, вам не хватает itertools. Этот модуль стандартной библиотеки Python собрал ленивые итераторы, которые превращают обработку данных в конвейер: элементы поступают, преобразуются и уходят дальше без лишних промежуточных коллекций.</p><p>itertools — это не просто набор функций, а способ мыслить о данных как о потоке. Вместо того чтобы сначала строить список, а потом его перебирать, вы описываете, что должно произойти с каждым элементом, и получаете результат по мере необходимости. В статье разберём три семейства инструментов, покажем рабочие примеры и укажем на типичные ловушки.</p><p>itertools — модуль стандартной библиотеки Python, который предоставляет ленивые итераторы для композиции цепочек обработки данных.</p><p>Ленивость означает, что элементы создаются по запросу, поэтому можно работать с большими файлами и бесконечными потоками, не загружая всё в память.</p><p>Функции разделены на три группы: бесконечные итераторы (count, cycle, repeat), конечные итераторы (chain, islice, groupby и др.) и комбинаторные (product, permutations, combinations).</p><p>Многие рутинные задачи — flatten, батчинг, скользящее окно, группировка — решаются в одну строку, если знать правильную комбинацию функций.</p><p>Подводные камни: groupby требует предварительной сортировки по тому же ключу; итераторы одноразовые; tee() может неэкономно расходовать память.</p><h2>Зачем вообще ленивые итераторы?</h2><p>Обычный подход в Python — сгенерировать список и пройтись по нему циклом. Это просто и читаемо, пока данных немного. Когда файл весит несколько гигабайт, а строки приходят из сети, список становится дорогим: он требует памяти и времени, хотя в каждый момент вам нужен лишь один текущий элемент.</p><p>Ленивый итератор не строит коллекцию целиком. Он помнит текущее состояние и умеет «добыть» следующий элемент. Поэтому itertools работает с любыми итерируемыми источниками — файлами, генераторами, сетевыми потоками — и не требует, чтобы весь объём данных поместился в оперативную память.</p><p><b>Память vs скорость:</b><br />Ленивость экономит память, но не всегда ускоряет код. Если данные всё равно нужны целиком — например, для сортировки — список может оказаться быстрее. Используйте итераторы там, где важна потоковая обработка.</p><h2>Три семейства инструментов</h2><p>Документация Python делит функции модуля на три группы. Такое разделение помогает быстро выбрать инструмент: нужна бесконечная последовательность, преобразование конечной или перебор комбинаций.</p><h3>Бесконечные итераторы: count, cycle, repeat</h3><p>count — это range без конца. Ему можно задать начальное значение и шаг, и он будет выдавать числа до тех пор, пока его не остановят снаружи. cycle бесконечно повторяет переданную последовательность, а repeat — бесконечно или заданное число раз возвращает один объект.</p><p>Классический трюк — сочетание map и count: map(f, count()) работает как математическое табулирование tabulate(f), знакомое из SML и Haskell.</p><h3>Конечные итераторы: chain, islice, groupby и другие</h3><p>Это самая многолюдная группа. Здесь есть функции для склейки последовательностей, фильтрации, нарезки, группировки и пакетной обработки. Их объединяет одно: на вход подаётся конечный или контролируемый итератор, на выходе — тоже итератор.</p><p>batched появился в Python 3.12 и сразу стал незаменимым инструментом: партии запросов к API, пакеты строк для вставки в базу, страницы данных. Последняя партия может быть короче — это поведение по умолчанию.</p><p>groupby часто путают с SQL-аналогом, но это не тот же инструмент. Python-версия группирует только подряд идущие одинаковые ключи, поэтому перед ней обычно нужна сортировка по тому же ключу. Если забыть про это, результат покажется случайным.</p><h3>Комбинаторные итераторы: product, permutations, combinations</h3><p>Эти функции генерируют декартово произведение, перестановки и сочетания. Они незаменимы в тестировании, алгоритмах на графах, задачах оптимизации и даже в простых играх. Все они ленивые, поэтому можно перебирать комбинации по одной, не строя гигантский список.</p><p>product с аргументом repeat удобен для перебора многомерных конфигураций: product([0, 1], repeat=3) даст все двоичные triples, как в таблице истинности.</p><h2>Подводные камни, за которые хватаются новички</h2><p>Несмотря на простоту отдельных функций, у модуля есть несколько особенностей, которые легко превратить в баг.</p><ul><li>groupby работает только с подряд идущими одинаковыми ключами. Перед вызовом сортируйте данные по тому же ключу, иначе группы разобьются.</li><li>Итераторы одноразовые. После list(iterator) исходный итератор опустошён, и второй проход по нему даст пустой результат.</li><li>tee копирует данные во внутренний буфер, пока все производные итераторы не прочитают их. Если один итератор сильно отстаёт, память может расти не хуже списка.</li><li>zip_longest с бесконечным итератором никогда не остановится. Ограничивайте такие комбинации islice или takewhile.</li><li>product полностью потребляет входные итераторы, чтобы построить пулы значений. С бесконечными последовательностями его использовать нельзя.</li></ul><h2>Рецепты: от простого к составному</h2><p>Документация Python включает раздел рецептов — готовые комбинации функций, которые решают частые задачи. Некоторые из них настолько удобны, что со временем превращаются в полноценные функции модуля: так появились accumulate, compress и pairwise.</p><p>Для sliding_window сейчас часто используют рецепт из документации или аналог из more-itertools, где функция уже реализована и хорошо протестирована.</p><h2>Выводы</h2><p>itertools — это не библиотека для красивых однострочников, а инструмент для правильного мышления о данных. Он помогает отказаться от лишних промежуточных списков, писать компактные конвейеры и работать с потоками, которые не помещаются в память. Главное — не гнаться за краткостью любой ценой: иногда явный цикл понятнее, чем цепочка из пяти функций.</p><blockquote>Together, they form an iterator algebra making it possible to construct specialized tools succinctly and efficiently in pure Python.</blockquote><p>Источник: <a href="https://docs.python.org/3/library/itertools.html">itertools — Functions creating iterators for efficient looping</a>. Если в вашем коде до сих пор царят вложенные циклы и огромные списки — попробуйте заменить их на поток. Скорее всего, получится короче, быстрее и понятнее.</p>]]></content:encoded>
    </item>
    <item>
      <title>10+ методов от сеньоров и тимлидов для слаженной работы с командой и карьерного роста</title>
      <link>https://tproger.ru/articles/10-metodov-ot-senorov-i-timlidov-dlya-slazhennoj-raboty-s-komandoj-i-karernogo-rosta-erid-ljn8jtbn9</link>
      <comments>https://tproger.ru/articles/10-metodov-ot-senorov-i-timlidov-dlya-slazhennoj-raboty-s-komandoj-i-karernogo-rosta-erid-ljn8jtbn9?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Вика Овсянникова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/10-metodov-ot-senorov-i-timlidov-dlya-slazhennoj-raboty-s-komandoj-i-karernogo-rosta-erid-ljn8jtbn9</guid>
      <description><![CDATA[<p>Составили лонгрид про общение в команде: как проводить созвоны, организовывать брейнштормы, корректно давать фидбек или решать конфликты.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/10-metodov-ot-senorov-i-timlidov-dlya-slazhennoj-raboty-s-komandoj-i-karernogo-rosta-erid-ljn8jtbn9">10+ методов от сеньоров и тимлидов для слаженной работы с командой и карьерного роста</a>»</p>]]></description>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Для мотивации]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Карьера]]></category>
      <category><![CDATA[Управление проектами]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Для продвинутых]]></category>
      <category><![CDATA[Soft Skills]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 18 Mar 2024 16:32:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вы джун+ или мидл разработчик. Вроде харды на высоком уровне, но что-то все равно не позволяет вам сменить грейд. Обычно это софт-скилы — на более высоких позициях вы не просто выполняете задачи, но еще и ставите их другим, а также направляете и учите младших коллег. Чтобы перейти на следующую карьерную ступень, придется подтягивать и мягкие навыки.</p><p>Мы пообщались с сотрудниками и экспертами сопровождения <a href="https://tprg.ru/NKOD">Яндекс Практикума</a> и составили обширный лонгрид, в котором вы найдете фишки для выстраивания эффективного и живого общения с командой.</p><h2>Оглавление</h2><ol><li><a href="https://tproger.ru/#one">Почему важно живое общение</a></li><li><a href="https://tproger.ru/#2">Как организовывать созвоны, от групповых до 1-2-1</a></li><li><a href="https://tproger.ru/#3">Как готовиться и проводить групповые созвоны</a></li><li><a href="https://tproger.ru/#4">Что делать с 1-2-1</a></li><li><a href="https://tproger.ru/#5">Как давать обратную связь</a></li><li><a href="https://tproger.ru/#6">Как разрешать конфликты</a></li><li><a href="https://tproger.ru/#7">Как проводить брейнштормы</a></li><li><a href="https://tproger.ru/#8">Как организовать трекер</a></li><li><a href="https://tproger.ru/#9">Как организовать общение в течение дня</a></li><li><a href="https://tproger.ru/#10">Как в развитии софтов может помочь школа Яндекс Практикума</a></li></ol><h2>Почему важно живое общение</h2><p>Под живым общением мы подразумеваем живую коммуникацию, при которой команда активно вовлекается в работу, а не сухо отвечает «Ок» или вообще молчит. Выстроить работу без этого сложно. Команда — это люди с общими целями, но у каждого участника есть своя идентичность. Живое общение помогает:</p><ul><li>сохранить связь с реальностью,</li><li>эффективнее креативить и решать сложные проблемы,</li><li>создавать чувство безопасности.</li></ul><blockquote>У разработчиков, людей технического склада ума, есть страх, непонимание: я вроде бы прочитал, что мне написали, но не понял, что мне хотели сказать просто из-за того, что это текст. Я часто сталкивалась с бэкенд-разработчиками или девопсами, которым сложно даже эмоджи считывать. Они говорят: а я не знаю, он улыбнулся, потому что подшучивает надо мной или потому что помочь хочет.</blockquote><p>Мидлам чаще приходится применять софт-скилы: помогать, давать обратную связь джунам, проводить встречи и т. д. Развитое живое общение поможет наладить с коллегами понимание и потенциально вырасти в должности — сеньоры пользуются софт-скилами чуть ли не чаще, чем хардами. И помочь в этом может Школа наставников Яндекс Практикума.</p><h2>Как организовывать созвоны, от групповых до 1-2-1</h2><p><b>Самые ходовые инструменты:</b> Discord, Zoom, Microsoft.Teams, Google.Meet</p><p>Можно использовать Discord как инструмент живого взаимодействия. Он создает эффект опенспейса на удаленке: там можно разбиться по комнатам, постоянно кто-то онлайн, занимается парным программированием. Такая атмосфера работает сама на себя: ты видишь, что в комнате есть люди, происходит движ, к ним можно быстро зайти и уточнить какой-то вопрос. Discord хорош для формата ежедневных коммуникаций в маленьких командах разработки на 6-8 человек.</p><p>В Zoom для этого банально нужно больше кликов и времени: создать встречу, отправить приглашения, зайти в приложение. Но он хорошо подходит для формата вебинаров, докладов, где разговаривает в основном один человек.</p><p>У нас распределенная команда, все работают удаленно из разных точек России, поэтому я старался делать дополнительные слоты, в которые мы могли бы собраться и во что-то позалипать. Мы играли в онлайн-версию испорченного телефона: дорисовывали картинки друг за другом и дописывали истории, чтобы сплотить ребят. Для этого тоже использовали Discord.</p><p>В Zoom или Meet есть свои плюсы — можно записать митинги. Но есть и минусы: если кто-то что-то обсуждает, ты не можешь к ним как в офисе подойти и послушать, что они говорят. А Discord как раз это решает.</p><h3>Как готовиться и проводить групповые созвоны</h3><p>Для начала нужно объяснить, зачем человеку приходить на встречу. Сформируйте и зафиксируйте в описании цель встречи, адженду, тщательно продумайте состав участников: точно ли вам нужна вся команда?</p><p>Дальше смотрим на контекст встречи. К каким-то можно не готовиться, например, там, где нужно просто сделать объявление. Кстати, новости лучше рассказывать лично, а не в переписке, чтобы обсудить возникающие вопросы и сразу получить обратную связь.</p><h4>Собирайте ожидания от встречи</h4><p>Иногда даже при прописанной адженде люди могут ожидать не того, о чем собирался говорить фасилитатор. Уточните перед началом, что все понимают, что вы будете обсуждать. Это полезная обратная связь: если рассинхронились, то можно либо на ходу менять тему, либо сразу акцентировать, что это не то место. В дальнейшем вы, как организатор, поймете, как формулировать приглашение на встречу так, чтобы не дезинформировать людей.</p><h4>Проводите разминки</h4><p>Во время созвона для начала нужно расшевелить аудиторию. Представьте, встречается толпа незнакомцев. Чтобы создать комфортную атмосферу, нужно их расслабить, дать познакомиться. Можно начать с неформального вопроса в духе «Как я провел выходные». Так происходит очеловечивание квадратиков на экране. Мы же обычные люди, а не просто скучные специалисты, у нас есть прикольные интересы, которыми можно поделиться. Если в компании есть культура включать камеры, то вообще прекрасно, потому что так гораздо проще устанавливать контакт.</p><h4>Контролируйте общий план созвона, чтобы не отклоняться от курса</h4><p>Участники часто начинают углубляться в детали, до которых проект, возможно, даже не дошел. Надо честно говорить: коллеги, кажется, мы общаемся не по теме. Это самая действенная штука, я не знаю лучше варианта.</p><h4>В такие моменты важно говорить уверенно: да, было!</h4><p>На ретроспективе иногда обсуждают вопросы, которые одной команде могут быть неинтересны: больше связаны с тестированием или с разработкой. Тогда участники начинают скучать. Чтобы вернуть их, можно еще спросить про опыт других команд: а как было у вас? Даже если эта тема им не сильно близка, это поможет включиться в обсуждение.</p><h4>Поддерживайте динамику созвона с помощью теории вдоха-выдоха</h4><p>Вне зависимости от формата, будь то рабочая встреча, вебинар, лайв-кодинг, или что-то еще, очень важна динамика встречи и теория вдоха-выдоха. Это про когнитивную нагрузку. Человек не может сконцентрировано воспринимать информацию слишком долго, мозгу нужно отдыхать. Поэтому лучше чередовать «вдохи» и «выдохи».</p><p>Вдох — это концентрация. Мы напрягаемся, идет когнитивная нагрузка, воспринимаем информацию. Выдох — мы переключаемся и даем мозгу передохнуть. Пусть это будут хотя бы несколько коротких выдохов даже в рамках очень серьезной встречи. Выдохами могут быть разные вещи, в зависимости от формата: тот же мемчик в презентации, если мы понимаем, что был долгий и сложный блок. Или обсуждение вопросов, если монолог был длинным.</p><h4>Проводите шеринг и дебрифинг</h4><p>Шеринг — это когда мы дали блок информации или провели какую-то активность, а потом спрашиваем, какие у вас чувства, эмоции относительно этого, что думаете?</p><p>А дебрифинг — это рефлексия и обсуждение прожитого опыта. Например, когда на тренинге мы спрашиваем, что вы с собой забираете из этой практики? Приземляем на свой опыт, чтобы потом использовать в работе.</p><p><a href="https://tproger.ru/#0">Вернуться в начало</a></p><h3>Что делать с 1-2-1</h3><p>1-2-1 можно проводить регулярно, когда к команде присоединяется новый участник. Нужно отслеживать в каком он состоянии, как адаптируется.</p><p>Либо когда команда создается с нуля, они друг к другу притираются. Когда команда уже сбитая между собой, то 1-2-1 начинают тратить время. А если и менеджер новый, они вообще теряют смысл, потому что команде хорошо, и нужно вливаться уже менеджеру.</p><h4>Фиксируйте, что обсуждали ранее — лучше обезличенными формулировками</h4><p>У меня всегда были записки о предыдущей встрече, что я должен сделать на следующей. Я выбирал однозначные обезличенные формулировки, чтобы человек понял и не ушел в защиту. Например, если у меня есть вопросы к перформансу сотрудника, я спрашивал: «Если 100% перформанса, это тебя никто не отвлекает, то какой у тебя сейчас процент от 100%?». Мы выявляли, что не так, и тогда можно было давать обратную связь о том, что мешает человеку достичь результата. То есть я пытался заставить его рефлексировать, дать ответ и возможность помочь ему.</p><h2>Как давать обратную связь</h2><p>В обратной связи есть три компонента: источник сообщения, само сообщение и получатель. Есть еще помехи, которые осложняют восприятие сообщения получателем. Чтобы все было нормально, должно быть как можно меньше звеньев между источником и получателем обратной связи.</p><h3>Добавляйте конкретики</h3><p>К примеру, даже в похвалу. Недостаточно просто сказать — «Ты классный сотрудник». Человек может испытать какую-то эмоцию, но что делать с этой информацией, непонятно. Важно сказать, что именно вы сюда вкладываете, что именно мне нужно продолжать делать в моей хорошей работе, чтобы она продолжала быть хорошей.</p><h3>Сначала учите, а потом лечите и мочите</h3><p>Это метод, при котором вы объясняете человеку, почему его действия плохие. Возможно, он просто не знает. Когда вы убедились, что он вас понял, то объясняете, как делать правильно, а уже потом даете фидбэк. До стадии «мочить» я не доходил, обычно во второй мы пытались выправить поведение.</p><p>Также есть метод «Пинок»:<br />P — это позитив,<br />IN — что-то, что можно доработать,<br />N — это негатив,<br />OK — самая важная часть — понял ли человек обратную связь.</p><p>Многие менеджеры команды сопровождения рассказывали, что команда просит не использовать метод сэндвича — позитивная обратная связь, потом критика, потом снова что-то приятное.</p><p>Когда какие-то изменения подаются с этими смягчениями, ты начинаешь задумываться, а что имелось в виду? Сейчас у людей идет запрос на честность — просто скажите нам открыто, что происходит. Но, конечно, в рамках здоровой коммуникации. Мы <a href="https://tprg.ru/NKOD">в Практикуме</a> за бережный подход и уважение.</p><p>Если студент направил работу с большим количеством ошибок, ревьюеру все равно нужно написать, что получилось хорошо, даже если кажется, что позитивных моментов нет. Человек учится, и курсы — это песочница для проб и ошибок. Наша задача — поддержать студента и помочь побыстрее оттуда выйти с помощью объяснений и подсказок. Проверяя очередную работу студента и повторяя один и тот же комментарий много раз, важно помнить, что их будет смотреть живой человек со своими эмоциями и ощущениями. Поэтому каждую работу важно проверять одинаково качественно.</p><p><a href="https://tproger.ru/#0">Вернуться в начало</a></p><h2>Как разрешать конфликты</h2><p>Открытые конфликты — это прекрасно. Можно сразу разобраться, что произошло, и поговорить, либо с привлечением тимлида, либо самим.</p><p>Но когда все находятся в happy bubble, делают вид, что все хорошо, и при этом продолжают работать скрепя зубами, уже сложнее.</p><h3>Выводите людей на чистоту</h3><p>Можно сначала попробовать на 1-2-1 собрать фактуру, то есть вообще разобраться, что происходит. Это создаст безопасную, доверительную атмосферу. А дальше уже обязательно честно со всеми поговорить. Это тоже про культуру — мы не замалчиваем проблемы, а умеем открыто их принимать.</p><p>Может помочь также метод тухлых рыб. Это фасилитационная сессия, где ты помогаешь доставать конфликты, их коллективно обсуждать. Суть в том, что предлагаются определенные предметы, метафоры, чтобы разбавить атмосферу. И идея в том, что каждый вываливает, что ему нравится, не нравится, без привязки к личностям, но к действиям, конкретным событиям и фактам. И важно именно снять напряжение и научиться говорить открыто. А результатом может быть что угодно. В конце может оказаться, что команду надо расформировывать, так как они не могут работать друг с другом, это тоже нормально.</p><h2>Как проводить брейнштормы</h2><p><b>Самые ходовые инструменты:</b> Miro, Jam, Draw.io, Microsoft Whiteboard, Plant UML</p><p>Главное правило брейншторма — делиться абсолютно всеми идеями, которые приходят в голову, даже если они кажутся вам странными. Поэтому при генерации также нужно соблюдать и другое правило — не критиковать, а дополнять и докручивать мысли коллег. В брейншторме обычно участвуют либо отдел, либо команда, то есть много человек, поэтому нужно продумать, как организовывать большое количество людей, которые еще и будут высказываться.</p><p>Важно ответить на такие вопросы:</p><ul><li>Кто модератор?</li><li>Где проводить брейншторм?</li><li>Как потом транслировать результаты?</li><li>Сколько времени нужно заложить на выступление?</li><li>Как эффективно выстроить коммуникацию?</li></ul><h3>На брейншторме должна быть «строгая» модерация</h3><p>Модерирует обычно организатор. Он отвечает на вопросы в чате, отслеживает поднятые руки и реакции в камерах, следит за таймингом и корректным выполнением заданий. Например, чтобы на первом этапе участники не критиковали никого, а скидывали все, что придумывается. А помимо этого организатору еще бы здорово подготовить пространство, в котором все будут работать, попросить кого-то все записывать.</p><p>Важно считать не чистое время, а грязное, то есть учитывать технические и коммуникационные особенности. Например, есть механика, когда в группе из пяти человек высказывается каждый. Модератор планирует по минуте на высказывание, но забывает про переходы между людьми, а ведь они тоже занимают время. В итоге из запланированных 5 минут, группа выйдет за тайминг и получится 6-7. И так в каждой активности формируется снежный ком выхода за тайминг.</p><h3>Разбивайте большую команду на маленькие группы</h3><p>Если людей на встрече много, несколько десятков, то они с неохотой включаются в беседу. Тогда лучше разбивать их на маленькие группы, а потом объединять результаты из каждой комнаты, либо вовлекать через механики, которые не требуют подавать голос публично, в Miro стикеры поперекидывать, например.</p><p>Мы использовали Miro и аналоги: в Meet есть Jam, Draw.io, но они не такие удобные. Для брейнштормов архитектуры мы готовим UML-диаграммы, их можно редактировать в режиме реального времени. То есть кто-то пишет код, и он появляется на картинке. Потом его можно перекинуть в Notion.</p><p>В работе не всем удобно, что Whiteboard сильно отдельно от Jira и Confluence. То есть когда база знаний в одном месте, а идеи генерируются в другом месте, приходится постоянно это между собой актуализировать.</p><p><a href="https://tproger.ru/#0">Вернуться в начало</a></p><h2>Как организовать трекер</h2><p><b>Самые ходовые инструменты:</b> Jira, Notion, Redmine, Яндекс.Трекер, ClickUp</p><p>Трекер — это всего лишь инструмент. Кто-то может им не пользоваться, им достаточно написать на доске цель и идти к ней. А кому-то нужно отслеживать количество часов работы, чтобы вести учет для статистики, отчетов и т. д.</p><h3>Выделите у цели четкие стадии работы</h3><p>У нас есть трекер задач, мы выстраиваем там ближайший спринт с расписанной целью. У нее есть четыре состояния: заведена, в процессе, достигнута, не достигнута. Как именно эта цель достигается, мне, как менеджеру, уже не так важно. Какие-то команды в трекере задач сидят полностью, какие-то меньше и пользуются внутренним блокнотом, а потом раз в неделю прикладывают задачи в трекер, как лог. Кто-то на внутреннем Wiki ведет дейлики.</p><h3>Кастомизируйте любой инструментарий под свой бизнес-процесс</h3><p>Первый трекер, которым я пользовался, был Redmine. Но лучше всего оказался Jira. У меня такой синдром утенка, и я знаю, как все устроено в Jira, поэтому я стараюсь везде сделать как в Jira. Сейчас мы используем Яндекс.Трекер, он похож на Jira. В любом случае инструментарий нужно дорабатывать под свой бизнес-процесс.</p><p>Например, я использовал ClickUp, но там нельзя давать тикет по номеру. В Jira это очень удобно: название проекта, несколько букв и номер. Он легко запоминается, в ClickUp такой штуки нет. Там ID какой-то просто набор букв, и ты никогда в жизни его не запомнишь.</p><h3>Используйте Kanban-доски</h3><p>У нас командная работа организована в Notion по Kanban. Есть 4 вертикальные колонки: сделаны, в работе, в плане на спринт и жду ответ. А в горизонтальных колонках указаны ответственные. И, соответственно, они скрываются, открываются как выпадающие списки, то есть мы можем всегда зайти и посмотреть, кто что из нас делает.</p><h3>Добавляйте фишки в оформление</h3><p>Наша милая фишка — в название задачи мы добавляем эмодзи. Это помогает расслабиться, потому что ты не только видишь текст, но и понимаешь, что вы находитесь в приятной интересной атмосфере.</p><p>Мне нравится ценность <a href="https://tprg.ru/NKOD">Практикума</a> про честность и открытость. Важно, чтобы люди не боялись сказать, что у них не получилось. Если не успели выполнить задачку в спринт, так и говорим, что я не успела, переношу на следующий. Это нормально, я и чувствую себя комфортно и безопасно, всегда знаю, что если вдруг что, то меня поймут, поддержат.</p><p>Notion начинает хорошо продвигаться в продуктовой разработке. Но какие-то технические моменты там не совсем удобно заливать. У разработчиков не очень хорошо получается работать в Notion, фронтендер еще с ним дружит, потому что понимает, как он работает. А вот бэкендеров тяжело туда переманить.</p><p><a href="https://tproger.ru/#0">Вернуться в начало</a></p><h2>Как организовать общение в течение дня</h2><p><b>Самые ходовые инструменты:</b> Telegram, Mattermost, Slack, Discord</p><h3>Мемы — это тимбилдинг</h3><p>Команда эффективно работает, когда она работает слаженно. Для этого необходимы тимбилдинги, неформальное общение. Если нет возможности устроить командный выезд, то мемы — неплохая замена. Можно создать чат, где коллеги будут обмениваться приколами. Это помогает разгрузить и сплотить команду.</p><h3>Заведите отдельный канал для разгрузки</h3><p>У нас был канал в Discord, где можно было поделиться честным мнением о чем-то. Если ты не хочешь быть токсичным, но при этом хочешь выразить то, что тебе совсем не нравится, ты можешь написать туда: бесит Flask — и слить в канал свои переживания.</p><h3>Сообщайте коллегам свои рабочие часы</h3><p>Мы советуем нашим наставникам и ревьюерам создавать отдельную папку в мессенджере под работу, чтобы разделять контекст личной жизни и работы. У нас все работают в разных часовых поясах, мы относимся к этому с пониманием и заранее обговариваем удобное время для встреч. Те, кто живут в другом часовом поясе, выстраивают свой рабочий день совсем по-другому: если мы созваниваемся вечером, у них только утро. Удобно, если в мессенджере есть возможность поставить статус или в описании зафиксировать, по какому часовому поясу ты живешь. В наши времена это супер актуально.</p><h2>Как в развитии софтов может помочь школа Яндекс Практикума</h2><p>У каждого из нас свой опыт обучения и, честно говоря, не всегда хороший. Мы всё время наблюдали, как люди транслируют свой опыт: родители, учителя в школе или преподаватели в университете. Кто-то делал это мягко и доходчиво, а кто-то директивно и сложно. Важно понимать, что этих людей редко кто учил, как можно мягко и эффективно передавать свой опыт другим.</p><p>Мы же стараемся подготовить наших экспертов как раз к такой передаче опыта и обучаем следующему:</p><ul><li>как учатся студенты;</li><li>созданию безопасной атмосферы, в которой комфортно проходить обучение;</li><li>здоровой коммуникации со студентами: мягкому tone of voice, письменной коммуникации, алгоритмам решения сложных ситуаций;</li><li>работе с эмоциональным интеллектом;</li><li>как давать обратную связь;</li><li>инструментам передачи опыта;</li><li>проведению онлайн-вебинаров.</li></ul><p>Наставник передает свой опыт с помощью двух основных сред: общаясь со студентами в чате и проводя вебинары. У каждой среды есть свои особенности. Например, в рамках проведения вебинаров есть ряд инструментов публичного выступления в онлайне, которые мы можем применять в рамках обучения.</p><p>При этом у нас нет цели превращать вебинар в шоу, а основная задача наставника — добиться образовательного результата для студентов. Поэтому человеку нужно научиться проводить вебинары и передавать опыт в них. Вот чему мы учим для эффективного проведения вебинара:</p><ul><li>составлению структуры вебинара;</li><li>групповой динамике и взаимодействию с участниками;</li><li>технической подготовке (важно помнить про стабильный интернет, камеру, ровный фон и отсутствие посторонних шумов);</li><li>публичным выступлениям в онлайне: расположению в камере, работе с голосом, объяснению сложных идей.</li></ul><p>Важно проговаривать любые детали, которые кажутся банальными, так как не для всех очевидно, что это влияет на внимание аудитории. Все это применимо и к рабочим созвонам, разница между обучением и работой невелика — и там, и там вы взаимодействуете с людьми и что-то им транслируете.</p><p>Благодаря подготовке к работе со студентами и постоянной практике передачи опыта, наши эксперты быстро развивают свои софты, а значит, растут профессионально: из джунов в мидлы, из мидлов в синьоры. Помимо того, что эксперт знает технические аспекты, он может их объяснить, эффективно делегировать задачи, дать развивающую обратную связь в корректной форме.</p><p>Школа наставников доступна всем, кто пройдет этапы отбора на роль наставника в Практикуме. Узнать, какие роли открыты сейчас, можно <a href="https://tprg.ru/NKOD">здесь</a>.</p><p><i>Реклама АНО ДПО «Образовательные технологии Яндекса», LjN8JtbN9</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Сказ о том, как я за год решил более 600 leetcode задач</title>
      <link>https://tproger.ru/articles/skaz-o-tom--kak-ya-za-god-rewil-bolee-600-leetcode-zadach</link>
      <comments>https://tproger.ru/articles/skaz-o-tom--kak-ya-za-god-rewil-bolee-600-leetcode-zadach?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Suleiman Dibirov]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/skaz-o-tom--kak-ya-za-god-rewil-bolee-600-leetcode-zadach</guid>
      <description><![CDATA[<p>Личный опыт: как за год решить более 600 задач на LeetCode, пройти coding-интервью в Booking, Careem и Avito и перестать бояться алгоритмических задач. Советы по выбору языка, порядку тем и ресурсам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/skaz-o-tom--kak-ya-za-god-rewil-bolee-600-leetcode-zadach">Сказ о том, как я за год решил более 600 leetcode задач</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Задачи умеренной сложности]]></category>
      <category><![CDATA[Задачи повышенной сложности]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 25 Jan 2024 12:23:08 GMT</pubDate>
      <content:encoded><![CDATA[<p>Хочу рассказать вам историю о том, как я начинал с уровня — «не могу решить даже 1 easy задачу из 10» до уровня — «могу решить каждую вторую medium задачу» и прошел несколько coding сессий в таких компаниях как Booking, Careem, Avito…Всем салют!</p><p>Началось все с того, что в конце 2022 года я твердо решил, что хочу устроиться в FAANG компанию, но для этого, как вы наверно все знаете, нужны знания алгоритмов и структур данных, коих у меня не было, потому что в обычной работе тебе не нужны heap, tree, disjoint set и другие структуры данных, поэтому начать пришлось практически с нуля.</p><p>Было очень тяжело, т.к. не знал с чего начинать, по какому принципу решать задачи, что важно, а что не очень, сколько нужно решить задач, чтоб чувствовать уверенность, получится ли вообще перейти на medium задачи, т.к. решение даже easy задач заводило в тупик, ниже постараюсь ответить на все эти вопросы.</p><p>Самое первое, что приходит в голову, когда хочешь начать решать задачи на литкоде — это открыть <a href="https://leetcode.com/problemset/all/">страницу с задачами</a> и начать решать их подряд, но это не очень хорошая идея, даже если вы применили фильтр easy, потому что очень много задач с пометкой easy очень сложно решить и это может сильно демотивировать, а то и понизить самооценку.</p><p>Начинать решать LeetCode нужно не подряд, а по темам: сначала array, hashmap, set — и только потом stack, tree, graphs.</p><p>Если задача не решается за 30–40 минут — смотрите разбор, изучайте решение и возвращайтесь к ней снова. Повторение — ключ к закреплению.</p><p>Python — лучший язык для LeetCode: лаконичный синтаксис и богатая стандартная библиотека (Counter, defaultdict, heapq) экономят время на интервью.</p><p>Уверенность при прохождении coding-интервью появляется примерно после 400–500 решённых задач; 600+ задач снимают страх полностью.</p><h2>Советы</h2><ol><li>Первый совет – обращайте внимание на процент Acceptance, чем этот процент больше, тем лучше, потому что это означает, что у многих людей получается решить эту задачу и есть вероятность, что и у вас получится.Также можете открыть study plan и попробовать решать задачи оттуда:<br /> – <a href="https://leetcode.com/studyplan/top-100-liked/">Топ 100 залайканных задач,</a> если много лайков, значит есть вероятность, что это не какая-то хитросплетеная задача, которая может понизить вашу оценку(есть много задач для которых нужны специфичные знания и это может быть неочевидно с первого взгляда)<br /> – <a href="https://leetcode.com/studyplan/top-interview-150/">Топ 150 задач для подготовки к интервью</a>, вероятность встретить эти задачи на интервью высока, но тут могут быть сложные задачи, которые трудно будет решить в начале пути<br /> – Как пишет leetcode, <a href="https://leetcode.com/studyplan/leetcode-75/">это “Must-do problem list for interview prep”</a>. Задачи в этих списках распределены на разные темы, решать нужно их следуя следующему совету.</li><li>Второй совет – начинайте с самых фундаментальных структур данных, таких как array(одномерный/двумерный), set, hashmap, они будут встречаться вам постоянно, к примеру, во многих задачах время выполнение можно улучшить добавив set или hashmap(но нужно учесть, что увеличится потребление памяти) и только после того как освоите их, можно начинать углубляться в другие структуры как stack, queue, linked list, tree, graphs.<br />На самом leetcode <a href="https://leetcode.com/explore/learn/">есть карточки, в которых дается объяснение структурам данных</a> и алгоритмам + задачи для закрепления этих тем, как и написано выше, начинайте с простых карточек – array, string и постепенно повышайте уровень, я несколько раз возвращался к карточкам, т.к. некоторые моменты порой были непонятны с первого раза, поэтому не думайте, что с вами что-то не так, если что-то непонятно, вернетесь потом и обнаружите, что уже понимаете это.<br /><a href="https://neetcode.io/roadmap">Тут есть roadmap с темами и задачами</a>, очень советую<br /><a href="https://www.techinterviewhandbook.org/grind75">Тут можно создать понедельный план</a> с применением различных фильтров<br /><a href="https://seanprashad.com/leetcode-patterns/">Список из 150 популярных задач</a>, разделенные на темы, сложность и компании</li><li>Третий совет – если не можете придумать оптимальное решение, начинайте с решения “в лоб”, как только получится, подумайте над тем, как это решение можно улучшить, может можно использовать какую-то структуру? может массив можно отсортировать? а что если использовать set или hashmap? можно ли использовать какой-то шаблон? Если ничего не помогает и вы уже потратили 20-40 минут, то смело открывайте решение задачи и изучайте это решение, как только вы это сделали, закройте решение и попробуйте заново решить(таких итераций может быть несколько, это нормально!), если после нескольких итераций не получилось, просто перепечатайте решение(я неоднократно так делал), НО не забудьте через время вернуться к этой задаче и попробовать решить заново(вполне вероятно, что не получится и это тоже нормально, просто повторяете все шаги заново, у меня бывали случаи, когда я одну и ту же задачу решал и по 10 раз, пока точно не буду уверен, что точно понял).</li><li>Четвертый совет – пройдитесь <a href="https://hackernoon.com/14-patterns-to-ace-any-coding-interview-question-c5bb3357f6ed">по этим 14 шаблонам</a>, особенно уделите внимание two pointers, sliding window и fast &amp; slow pointers, т.к. они чаще всего встречаются. Берете шаблон, находите задачи под этот шаблон(на странице с leetcode проблемами есть фильтр по тегам, нужно выбрать нужный тег, к примеру “Two pointers” или “Sliding window” и т.д.) и решаете столько задача, сколько нужно, чтоб закрепить эту тему.</li><li>Пятый и самый важный совет – практика, практика и еще раз практика, в начале мне хотелось каждый раз плакать от своей тупости, потому что не мог решать даже простые задачи и это не преувеличение, фраза – “какой же я тупой” не уходила из головы первые несколько месяцев и даже после решения 600 задач все еще бывают моменты, когда самооценка падает, если не получилось решить какую-то задачу, особенно если оказалось, что решается очень легко, но что важно – с каждой решенной задачей вы будете становиться лучше относительно себя.</li></ol><p>Минус литкода <a href="https://firecode.io/">решили на этой платформе</a>, где задачи даются один за другим, начиная с легких и повторяя уже решенные задачи, чтоб закрепить. Есть объяснение разных тем, после решения задачи можно посмотреть как другие решили, что очень полезно, т.к. ты видишь разные подходы решения одной и той же задачи.</p><h2>FAQ</h2><ol><li>Что делать, если не получается решить задачу?Если после 30-40 минут у вас не получилось решить задачу, то посмотрите решение, попробуйте найти видео решение этой задачи, как только посмотрели и разобрали, попробуйте решить заново, если не получилось, еще раз посмотрите и так по кругу. Время от времени возвращайтесь к старым задачам и решайте их заново, особенно если решение какой-то задачи далось вам с трудом.Я порой одну и ту же задачу решал по 10-15 раз, а то и больше</li><li>Сколько задач нужно решить, чтобы начать откликаться на вакансии?Тут все индивидуально, я полгода каждый день решал(сам или с помощью видео) несколько задач, решил где-то 300 задач и только в этот момент почувствовал почву под ногами, а уверенность почувствовал только после решение где-то 400-500 задач. Некоторым достаточно и 100-200 задач, к сожалению я не из их числа.</li><li>Какой язык программирования выбрать для решения задач?Если коротко – Python.Я начинал решать на php, потом перешел на go, после начал решать на java и наконец <a href="https://www.youtube.com/watch?v=0K_eZGS5NsU">после просмотра видео</a> решил попробовать python и с тех пор решаю на этом ЯП. Java – тратишь время на большие конструкции, к примеру – List&lt;Integer&gt; items = new ArrayList&lt;&gt;();<br />PHP – путался в аргументах функций, неудобно было в целом писать, хотя практически все время пишу на этом языкеGo – мало встроенных функций, даже min/max, теперь уже есть, но тогда не было, также нужно конвертировать int в float и обратно, тратишь лишнее время, хотя код и предсказуемыйPython – лаконичный код, много встроенных функций, которые помогают решать задачи (Counter, defaultdict, divmod, lambda и т.д.), время тратишь именно на решение задачи, а не на печатания и прочее, скорость на собеседовании очень важна.</li><li>Как найти время на задачи?<br />В моем случае я старался решать по 2 задачи в день, одну задачу до начала работы и вторую задачу после работы. На выходных уделял больше времени, смотрел видео, читал статьи, старался решать больше задач, также старался повторно решать старые задачи, чтобы закрепить тему и задачу.</li><li>Нужно ли покупать leetcode premium?<br />Самое полезное у premium – вы можете посмотреть в каких компаниях давали ту или иную задачу, если вы готовитесь к конкретной компании, то рекомендую, иначе достаточно бесплатной версии, т.к. видео объяснение задачи можно найти на канале Neetcode(ссылка ниже) и т.д.</li><li>Стоит ли покупать доступ к educative.io?<br />Очень много видел рекомендаций курса Grokking Coding Interview Patterns от educative, но не нашел этот курс очень полезным, единственное понравилось визуальное объяснение, но на мой взгляд лучше посмотреть видео на ютуб</li><li>Стоит ли покупать доступ к algoexpert.io?<br />На мой взгляд очень полезный ресурс, для каждой задачи есть подробное объяснение, чуть ли не каждое видео по 30-50 минут. На сайте постоянно проходят разные акции и можно ухватить все курсы на год за 99$, адекватная цена, приятный сайт, много тест кейсов для задач и сам редактор приятный и удобный</li></ol><figure><img src="https://media.tproger.ru/user-uploads/96915/2024-01-23/2b2f3e16-2951-4eec-a8ea-0962078e83fe.jpg" alt="" /></figure><h2>Результат</h2><ol><li>Достиг свою изначальную цель, ради которой и начинал решать алгоритмы – прохождение кодинг интервью, один из этапов, которые обязательны в FAANG компаниях, уже прошел в нескольких компаниях и что самое главное – теперь нет страха перед этим этапом.</li><li>Более глубоко начал понимать структуры данных, раньше понятия не имел, что такое heap, а сейчас имею полное представление об этой структуре, а также о других структурах.</li><li>В процессе работы начал чуть иначе смотреть на код, где-то видишь, что можешь использовать больше памяти и сократить time complexity, а где-то наоборот.</li><li>Последнее, но не по важности – фитнес для мозга, перешел из уровня – “не хочу – не буду”, до уровня – “хочу решать задачи каждый день”</li></ol><p>Читайте также на Tproger: <a href="https://tproger.ru/articles/kak-podgotovitsya-k-sobesedovaniyu-v-it-kompaniyu">как подготовиться к техническому интервью в ИТ-компанию</a> и <a href="https://tproger.ru/articles/algoritmy-i-struktury-dannyh-dlya-nachinajushhih">алгоритмы и структуры данных для начинающих</a>.</p><h2>Полезные ссылки</h2><ol><li>Пожалуй <a href="https://www.youtube.com/@NeetCode">самый полезный канал</a>, где человек в доступной форме объясняет задачу и решение.</li><li>Тоже <a href="https://www.youtube.com/@WilliamFiset-videos">очень много полезных видео</a> с объяснениями разных структур данных.</li><li><a href="https://www.youtube.com/c/BackToBackSWE">Объясняет на доске структуры данных, задачи</a>.</li><li><a href="https://www.youtube.com/@insidecode">insidecode</a></li><li><a href="https://www.youtube.com/@geekific/videos">geekific</a></li><li><a href="https://www.youtube.com/@MichaelMuinos/videos">MichaelMuinos</a></li><li><a href="https://www.youtube.com/c/NickWhite/videos">NickWhite</a></li><li><a href="https://www.youtube.com/channel/UCMrgnopgrjjIJn-xyYhZ7nA">Cracking FAANG</a></li></ol>]]></content:encoded>
    </item>
    <item>
      <title>Как джуниор-разработчику вырасти до мидла за год</title>
      <link>https://tproger.ru/articles/kak-dzhunior-razrabotchiku-vyrasti-do-midla-za-god</link>
      <comments>https://tproger.ru/articles/kak-dzhunior-razrabotchiku-vyrasti-do-midla-za-god?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[МТС]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-dzhunior-razrabotchiku-vyrasti-do-midla-za-god</guid>
      <description><![CDATA[<p>Проанализировали требования работодателей к разработчикам и выяснили, как стать мидлом за год и что для этого нужно.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-dzhunior-razrabotchiku-vyrasti-do-midla-za-god">Как джуниор-разработчику вырасти до мидла за год</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Карьера]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Образование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 19 Oct 2023 11:09:01 GMT</pubDate>
      <content:encoded><![CDATA[<p>Плох тот джун, который не мечтает стать мидлом. Статус мидла даёт возможность делать крутые проекты и <a href="https://habr.com/ru/companies/habr_career/articles/719730/">увеличить зарплату в среднем в 2 раза</a>.</p><p>Чтобы дорасти до мидла, придётся прокачивать разные хард- и софт-скилы: от знания фреймворков до коммуникативных навыков. В статье разберёмся, можно ли сделать это за год, и если да, то как.</p><h2>Как устроены грейды в IT</h2><p>Грейды — разделение одной должности на несколько категорий в зависимости от знаний и умений сотрудника.</p><p>В IT-сфере у специалистов три грейда: Junior, Middle, Senior. Грейдирование позволяет привязать зарплату к компетенциям работника, мотивировать и стимулировать его. Чтобы расти в деньгах и работать над более интересными проектами, разработчики прокачивают компетенции и повышают грейд.</p><p><b>Нередко можно услышать, что грейды в IT условные:</b></p><ul><li>разные компании к одному и тому же уровню специалиста выдвигают разные требования</li><li>в некоторых компаниях деления на джунов и мидлов вообще нет</li></ul><p>Но практика доказывает обратное. Мы изучили 100 открытых вакансий на hh.ru и увидели: компании часто ищут разработчиков определённых уровней, а требования к хард- и софт-скилам у джуна и мидла отличаются.</p><h2>Чем отличается джун от мидла</h2><p>Вот основные инструменты и навыки, которыми должны владеть разработчики разного уровня. В основе списка — требования работодателей из реальных вакансий.</p><p><b>Фреймворки</b></p><ul><li>Джун знает популярные фреймворки, например React, Laravel, Sinatra, Django, Spring. Задачи решает по шаблону, с помощью опытных коллег.</li><li>Мидл уверенно владеет одним или несколькими популярными фреймворками. Разбирается в асинхронном программировании. Решает нестандартные задачи.</li></ul><p><b>Git и GitHub</b></p><ul><li>Джун знает о проектах на GitHub. Может к ним присоединиться, чтобы помочь исправить документацию или несложную ошибку. Пишет код и отправляет его опытным коллегам на ревью и публикацию.</li><li>Мидл работает с Git: возвращается к исходным файлам, просматривает историю изменений, восстанавливает любые данные, работает с файлами вместе с коллегами. Размещает проекты на GitHub, ведёт ветки, делает коммиты.</li></ul><p><b>Алгоритмы</b></p><ul><li>Джун понимает основы алгоритмизации, но практического опыта нет.</li><li>Мидл знает и использует алгоритмы для эффективного решения задач.</li></ul><p><b>Инструменты разработки и инженерные практики</b></p><ul><li>Джун навыком не владеет.</li><li>Мидл умеет покрывать код тестами и правильно его организовывать.</li></ul><p><b>Код</b></p><ul><li>Код джуна работает, но его трудно читать и поддерживать.</li><li>Мидл пишет чистый простой код, который легко поймут другие члены команды. Понимает принципы контейнеризации приложений. Умеет работать с Docker.</li></ul><p><b>Понимание бизнес-потребностей</b></p><ul><li>Джун выполняет задачу без привязки к конкретным целям бизнеса.</li><li>Мидл умеет реализовать в коде то, чего хочет бизнес. Понимает, как воплотить идею в жизнь с учётом технических возможностей.</li></ul><p><b>Умение составлять план и работать по нему</b></p><ul><li>Джун не умеет строить план по решению крупной задачи. Получает задание и сразу начинает его выполнять тем способом, который знает.</li><li>Мидл, прежде чем кодить, продумывает план. Может аргументировать, почему выбрал тот или иной вариант.</li></ul><p><b>Насмотренность</b></p><ul><li>Джун знает 1-2 способа, как решить задачу.</li><li>Мидл знает современные тренды. Видел и отмечал хорошие решения других разработчиков, использует их в работе.</li></ul><p><b>Самостоятельность</b></p><ul><li>Джун работает под руководством опытных коллег.</li><li>Мидл самостоятельно справляется с задачами.</li></ul><p><b>Форс-мажоры</b></p><ul><li>Джун иногда не знает, за что взяться, чтобы решить проблему.</li><li>Мидл пытается разобраться в задачах, с которыми не сталкивался.</li></ul><p><b>Коммуникативные навыки</b></p><ul><li>Джун, если что-то не знает, задаёт абстрактные вопросы: «Как исправить?», «Что делать, чтобы работало?»</li><li>Мидл, если возникла проблема, понимает, что нужно исправить. Задаёт коллегам конкретные вопросы. Анализирует работу менее опытных сотрудников. Подсказывает, как улучшить решение. Делится опытом и знаниями с новичками и стажёрами.</li></ul><h2>Как можно повысить свой грейд: 3 способа</h2><p>Опытные разработчики утверждают: чтобы подрасти, джуну понадобится от года до трёх лет.</p><p>Сделать это можно несколькими способами.</p><h3>Окончить курсы</h3><p><b></b>Чаще всего специализированные курсы проходят онлайн, учиться в лучших школах можно из любой точки мира. Главное достоинство курсов — студенты изучают и отрабатывают на практике только актуальные инструменты, которые востребованы у работодателей.</p><p>Иногда курсы проводят и сами работодатели. Например, <a href="https://www.teta.mts.ru/">в школе «МТС.Тета»</a> обучают Golang-разработчиков, аналитиков, фронтендеров и т. д. Так компании выращивают специалистов под свои задачи и приглашают их на работу.</p><figure><img src="https://media.tproger.ru/user-uploads/73713/2023-10-17/940f9587-8910-4d22-b1a4-a675694fbb61.png" alt="" /><figcaption>Школа «МТС.Тета» проводит шесть курсов по разным направлениям. Чтобы попасть на них, нужно подать заявку и пройти отбор</figcaption></figure><h3>Поработать стажёром</h3><p>Крупным компаниям выгодно брать новичков на стажировки: воспитать нужного специалиста внутри проще, чем искать его в другом месте. А для соискателей это отличная возможность подзаработать, перенять опыт коллег и поработать над продуктами, которыми пользуются миллионы людей.</p><p>Если стажировка пройдёт удачно, можно остаться в компании уже на правах полноценного сотрудника.</p><figure><img src="https://media.tproger.ru/user-uploads/73713/2023-10-17/e75fb941-d15b-40d0-b547-e22c5653e1ae.png" alt="" /><figcaption>Информацию о стажировках можно найти на сайтах корпораций.</figcaption></figure><p>Например, <a href="https://job.mts.ru/internship">в МТС</a> сейчас набирают тестировщиков, разработчиков, аналитиков.</p><h3>Самостоятельно</h3><p>Прокачать скилы исключительно своими силами сложно по двум причинам:</p><ul><li>нет обратной связи по отработке знаний на практике;</li><li>нужен высокий уровень самодисциплины.</li></ul><p>Из-за этого на повышение грейда может уйти больше времени. Но обучение на курсах тоже не гарантирует ни быстрый и лёгкий вход в IT, ни мгновенное развитие карьеры.</p><p>Множество счастливых историй на сайтах онлайн-школ — всего лишь уловка маркетологов. Гораздо больше случаев, когда люди потратили много лет, чтобы вырасти в профессии или вообще не смогли стать разработчиками. Но об этом редко рассказывают.</p><p>А вообще работодателя не интересует, где и как разработчик прокачивал свои скилы. Ему важнее, умеет ли сотрудник выполнять конкретные практические задачи и с их помощью достигать бизнес-целей.</p><p>Есть много способов повысить грейд бесплатно:</p><ul><li>смотреть видео на YouTube;</li><li>читать книги и статьи;</li><li>консультироваться с опытными разработчиками, которые помогут составить дорожную карту обучения;</li><li>проходить бесплатные курсы, одобренные IT-сообществом (про них мы рассказывали в этой <a href="https://tproger.ru/articles/stoit-li-idti-na-kursy-programmirovanija-v-2022-godu/">статье</a>).</li></ul><p>Конечно, на самостоятельное обучение потребуется больше времени: на учёбу придётся тратить 3–5 часов ежедневно. Можете ли вы себе это позволить, или работа, семья и домашняя рутина съедают каждую свободную минуту — это уже другой вопрос.</p><h2>Пошаговый план: как джуну стать мидлом за год</h2><p>Предположим, вы решили прокачать нужные мидлу скилы на курсах. Неважно, в какой сфере и с каким языком вы работаете — сейчас есть программы для разных специализаций.</p><p>Например, курс для ‎Python-разработчика выглядит примерно так.</p><p><b>В первый месяц</b> студенты изучают и отрабатывают на практике:</p><ul><li>Понятие «Качество кода». Стандарты оформления кода.</li><li>Работу с системой контроля версий Git.</li><li>Создание схемы базы данных и определение необходимых ограничений целостности.</li><li>Процесс вставки данных.</li><li>Нереляционные базы данных на примере NoSQL.</li><li>Выбор фреймворка. Базу данных Django.</li></ul><p><b>Во второй месяц:</b></p><ul><li>Технологии ORM.</li><li>Разработку серверной части Python с помощью Django.</li><li>Платформу Docker.</li><li>Фазы обработки запросов Nginx.</li></ul><p><b>В третий месяц:</b></p><ul><li>Поисковую систему Elasticsearch.</li><li>Полнотекстовый поиск и архитектуру ETL.</li><li>Асинхронный подход, итераторы, генераторы и корутины.</li><li>Асинхронные фреймворки: Twisted, AIOHTTP и FastAPI.</li></ul><p><b>В четвёртый месяц:</b></p><ul><li>Код-ревью: понятие и общие требования.</li><li>SOLID-принципы программирования.</li><li>Этапы и виды функционального тестирования.</li><li>Понятие API и плюсы работы с программным интерфейсом.</li></ul><p><b>В пятый месяц:</b></p><ul><li>Сервис аутентификации и авторизации пользователя в проекте.</li><li>Работу с чувствительной личной информацией: безопасность, шифрование, хеширование и управление доступом к ней.</li><li>Flask и Redis.</li></ul><p><b>В шестой месяц:</b></p><ul><li>OAuth 1.0, OAuth 2.0 и OpenID.</li><li>Работу с капчей и двухфакторной авторизацией.</li><li>Общение между микросервисами.</li><li>Настройку устойчивости сервиса к нагрузкам и изменениям API.</li><li>Внедрение в AsyncAPI авторизации, управление уровнем нагрузки и быстрое нахождение проблем.</li></ul><p><b>В седьмой месяц:</b></p><ul><li>Работу и функции UGC. Планирование сервиса.</li><li>Описание архитектуры и правила её проектирования.</li><li>Выбор хранилища, событийную работу с данными и работу с Kafka.</li><li>Подходы к хранению и обработке данных, которые не помещаются на один сервер.</li></ul><p><b>В восьмой месяц:</b></p><ul><li>Ещё одно распределённое хранилище, хранение данных и оптимизацию хранения и обработки.</li><li>CI/CD: построение непрерывной интеграции для приложения.</li><li>Логирование в микросервисной архитектуре.</li></ul><p><b>В девятый месяц:</b></p><ul><li>Сервис уведомлений.</li><li>Механизм отправки сообщений пользователям и единую политику контактов.</li><li>Проектирование системы коммуникаций.</li><li>Устройство e-mail и шаблонизаторов электронной почты.</li><li>Брокер сообщений RabbitMQ. Сервисы транзакционной рассылки писем и сокращённые ссылки.</li><li>CI/CD-конвейеры.</li></ul><p>Ещё два месяца уйдёт на выполнение дипломной работы. Т. е. примерно через 11 месяцев джун овладеет всеми навыками мидла, сможет просить повышение или откликаться на более интересные вакансии в других компаниях.</p><p>Обучение мидл-разработчиков разных специализаций может быть более интенсивным и занимать примерно полгода. Но, как правило, на таких курсах отводится мало часов на практику. Поэтому ещё столько же времени придётся самостоятельно оттачивать профмастерство на боевых задачах.</p><h2>Запомнить</h2><p>В IT-сфере есть три основных грейда: Junior, Middle, Senior. Специалисты более высоких уровней отличаются от джунов тем, что умеют самостоятельно решать сложные задачи.</p><p>Разработчики могут качать хард и софт-скилы разными способами: самостоятельно, на стажировке и специализированных курсах. Первый способ, скорее всего, займёт больше времени, два последних — более эффективны.</p><p>Окончание курсов и стаж работы не повышают грейд автоматически. Просидеть пять лет на позиции джуна — так тоже бывает. Но год — это минимальный срок, за который реально вырасти до уровня мидла.</p>]]></content:encoded>
    </item>
    <item>
      <title>Декларативный JavaScript</title>
      <link>https://tproger.ru/articles/deklarativnyj-javascript</link>
      <comments>https://tproger.ru/articles/deklarativnyj-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Sultan]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/deklarativnyj-javascript</guid>
      <description><![CDATA[<p>На примере JavaScript объяснили, как пересекается декларативное программирование с функциональным, и как кодить на JS декларативно.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/deklarativnyj-javascript">Декларативный JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 10 Oct 2023 12:01:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>Эта статья ориентирована на читателей, которые уже знакомы с функциональным программированием, но, возможно, не до конца разобрались с применением некоторых ключевых идей на практике. Мы постараемся разобрать их на конкретных примерах и понять, как пересекается декларативное программирование с функциональным. Следует отметить, что в контексте JavaScript применение функционального и, тем более, декларативного программирования в их чистой форме невозможно. Таким образом, далее речь пойдет о принципах и подходах, характерных для функционального стиля программирования.</p><p>В статьях, посвященных декларативному программированию, зачастую упоминаются SQL, HTML в качестве примеров декларативных языков. В рамках этих “языков” мы действительно формулируем желаемый результат, вместо инструкции по его достижению, что вполне соответствует рассматриваемому концепту. Однако я хочу выделить общую черту этих <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA">доменных языков</a>:</p><p>В декларативных языках отсутствуют переменные, циклы и логические конструкции.</p><p>Писать код на JavaScript с таким тезисом звучит как вызов и в какой-то степени абсурдно, не правда ли?</p><p>Вспомните, как часто вам приходилось писать подобный код в JSX, чтобы показать или скрыть компонент:</p><p>Давайте перепишем этот кусочек кода в <i>более</i> декларативной манере:</p><p>Как было сказано, это <i>более </i>декларативный вариант кода. В конечном итоге мы все равно упремся на логическую конструкцию `if`, но мы намерены прятать его все дальше от основного кода.</p><p>Наличие свойства `visible` будет подталкивать нас к написанию декларативного кода несмотря на то, что сам компонент частично реализован в императивном стиле.</p><p>Попробуем разобрать логический оператор `switch`, который является не только ближайшим родственником `if`, но и любимчиком многих разработчиков:</p><p>Наверное вы уже догадываетесь, к чему я веду, особенно учитывая, что подобный подход уже реализован в React Router.</p><p>Но я хочу рассмотреть данный код с позиции функционального программирования, а именно, представить теги HTML/JSX в виде функций, которые возвращают HTML, где атрибуты тега являются входными параметрами функции.</p><p>Основываясь на вышеупомянутую идею, перепишем JSX-код в виде наборов функций. Стоит учесть, что `switch/case` — это зарезервированные слова JavaScript, поэтому мы добавим к ним нижние подчеркивания:</p><p>Как вы можете заметить, декларативный код не обязательно означает HTML, JSX или SQL. В JavaScript мы можем представить синтаксис этих языков с помощью функций. Если нас учили, что функции должны представлять собой действия и их названия должны начинаться с глагола, например: `find`, `setTitle`, то в данном случае наши функции не всегда будут выражать действие и могут обозначать сущность. К примеру, SQL-запрос можно записать в следующем виде:</p><p>Таким образом я хочу протянуть нить между декларативным и функциональным программированием.</p><p>Я не считаю, что функциональное программирование преследует декларативный подход, скорее это побочный эффект от интенсивного использования функций.</p><p>Важно отметить, что функциональное программирование это не просто <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">DRY </a>принцип, где повторяющийся код выносится в отдельную функцию. Это техника написания универсальных функций, которые должны легко комбинироваться и предоставлять гибкость в использовании.</p><p>Предлагаю еще раз взглянуть на конструкции `switch/case` и представить их в виде обычной функции:</p><p>Данная реализация не обладает гибкостью и не может быть использована в композиции с другими функциями. Тем не менее, преобразовав функцию `selectComponent` в функцию высшего порядка и дополнив её вспомогательными функциями, можно достичь гибкости и универсальности.</p><p>Если вы не до конца понимали для чего нужны каррированные функции, то это хорошая иллюстрация практического применения. Каррированная функция представляет собой функцию, аналогичную обычной, но с дополнительной особенностью, которая позволяет приостанавливать её выполнение. То есть, мы вызываем её, передавая один параметр, и выполнение откладывается до момента, когда будут переданы все необходимые параметры. Такой подход позволяет описывать действия до фактического выполнения самой функции, что позволяет писать код в декларативном стиле.</p><p>Для ясности, предлагаю развернуть функцию `selectComponent`, после вызова функций `when` мы получим следующий код:</p><p>Гибкость проявляется в том, что мы сможем создать множество других версий `when`, но главная функция `select` не потребует изменений. Ниже пример функции с поддержкой ленивой загрузки компонентов:</p><p>Если мы вернемся к первой версии функции `selectComponent`, станет ясно, что расширить её функционал без внесения изменений не получится. Используя данный подход, мы способствуем разбиению кода на мелкие функции, каждая из которых решает конкретную задачу, и в дальнейшем они менее подвержены изменениям.</p><p>Когда требуется расширить функционал, мы просто добавляем новую функцию и комбинируем её с уже существующими, вместо того, чтобы каждый раз переписывать основную функцию.</p><p>В следующей статье мы подробно рассмотрим данную технику на примере создания редьюсеров Redux. Узнаем, как заменить `try/catch`  и продолжим обсуждение функционального программирования.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутая логика именования в Python</title>
      <link>https://tproger.ru/articles/prodvinutaya-logika-imenovaniya-v-python</link>
      <comments>https://tproger.ru/articles/prodvinutaya-logika-imenovaniya-v-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/prodvinutaya-logika-imenovaniya-v-python</guid>
      <description><![CDATA[<p>Разобрали на примерах, как и почему лучше называть различные объекты в Python.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/prodvinutaya-logika-imenovaniya-v-python">Продвинутая логика именования в Python</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 29 Aug 2023 09:34:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы уже практикуете программирование на Python и испытываете проблемы с отложенным чтением (как и большинство кодеров), то ваш навык именования может стать зоной роста.</p><p>Комментировать код зачастую не хочется. Во-первых, на это уходит много времени и сил. Во-вторых, неизвестно, пригодятся ли это вообще. Ведь программа может оказаться временной, и ей перестанут пользоваться через пару дней. Или сеньор удалит половину строк — вместе с теми самыми комментариями.</p><p>В такой ситуации немалую пользу принесет навык именования объектов кода.</p><p>Как назвать переменную — вопрос сложный и болезненный. Его обсуждают авторы на Хабре, за плохие попытки осуждают на Stack Overflow. Ситуацию высмеивают в <a href="https://devrant.com/rants/2004238/variable-name">мемах</a>. Если назвать переменную неправильно — или просто странно — люди, которым впоследствии придётся читать и рефакторить ваш код, запутаются. А после придут узнавать, что вы имели в виду под uber_magic_cat и где расширенные комментарии.</p><p>Чтобы таких ситуаций не было, и ни вы, ни команда не тратили своё время и ресурсы, предлагаю разобраться в теме и научиться правильно называть объекты.</p><h2>Сперва вспомним базу: PEP 8</h2><p>PEP (Python Enhancement Proposal) – руководство по улучшению кода, описывает рекомендации и стандарты, которые делают код более читаемым и понятным. Вот его основные принципы.</p><h2>Отступы и пробелы</h2><ul><li>Используйте четыре пробела для каждого уровня отступа. Не используйте табуляцию;</li><li>Избегайте пробелов в конце строки;</li><li>Добавляйте пустые строки между функциями и классами — и по обе стороны от операторов.</li></ul><h2>Максимальная длина строки</h2><ul><li>Ограничивайте строки кода до 79 символов (это оптимальная длина для среднестатистического экрана);</li><li>Если выражение длинное, делайте перенос как в примере ниже:</li></ul><h2>Импорты</h2><ul><li>Группируйте импорты в следующем порядке: стандартные, сторонние библиотеки, самописные модули. Разделяйте группы пустыми строками;</li><li>Импортируйте только необходимые компоненты.</li></ul><h2>Пробелы до и после операторов</h2><ul><li>Используйте пробелы до, после операторов (= + — * / и проч.) и после запятых;</li><li>Не добавляйте лишних пробелов: они могут ухудшить читаемость кода.</li></ul><h2>Имена переменных и функций</h2><ul><li>Имена должны легко читаться, для разделения слов используйте нижние подчеркивания (например, record_count, calculate_total);</li><li>Для констант используйте ЗАГЛАВНЫЕ_БУКВЫ_С_ПОДЧЕРКИВАНИЯМИ;</li><li>Не начинайте название с цифры;</li><li>Избегайте зарезервированных создателями ЯП слов (list, dict и так далее).</li></ul><h2>Комментарии</h2><ul><li>Добавляйте пояснительные комментарии для сложных частей кода;</li><li>Пишите комментарии с заглавной буквы, а текст отделяйте двумя пробелами от символа #.</li></ul><h2>Документация</h2><ul><li>Используйте docstrings для документирования модулей, функций, классов и методов.</li><li>Документируйте параметры функций, возвращаемые значения и возможные исключения.</li></ul><h2>Логические блоки</h2><ul><li>Используйте один уровень отступа для каждого нового блока кода (циклы, условия, функции и так далее).</li><li>Избегайте хардкод-чисел. Заменяйте числа в коде на именованные константы. Вместо:</li></ul><p>Лучше так:</p><h2>Зайдем немного глубже</h2><p>Теперь хочу обратить ваше внимание на пятый пункт — «Имена переменных и функций». Вы когда-нибудь задумывались, какими частями речи именовать тот или иной объект? Классу лучше быть существительным, функции – сказуемым, это, кажется логичным. Но как быть с остальными объектами?</p><p>Давайте пройдемся по часто используемым типам объектов и посмотрим, как лучше поступить с их именованием.</p><p>В качестве примеров буду приводить части своего кода: я обеспечиваю полный жизненный цикл данных из разных ботов: выгрузка, обработка, подгрузка в хранилище и вёрстка обновляемых отчётов. Так что специфику других профессий глубоко не затрагиваю.</p><h2>Переменные</h2><p>Но переменных много, правил именования — тоже. И базовая программа не охватывает всё. Поэтому делюсь несколькими принципами, которые я использую в повседневной работе:</p><ul><li>Используйте аббревиатуры. Так в моём боте выглядит переменная, содержащая идентификатор пользователя Telegram:</li></ul><ul><li>Оставляйте неизменными названия переменных, которые устоялись в документации. Порой проще дописать комментарий об объекте и найти подсказку в Stack Overflow, чем получить понятное название, содержимое которого спустя месяц не отладить:</li></ul><ul><li>Опускайте гласные в существительных. В рунете этой практике только предстоит устояться. А зря. Даже частичное опущение подойдет:</li></ul><h2>Функции</h2><p>В название функции часто добавляются сказуемое (глагол или причастие). Однако в моей практике для разграничения похожих функций удобнее приписывать еще и дополнение:</p><h2>Столбцы таблиц и столбцов</h2><p>С этим сталкиваюсь на практике практически каждый день. Вот основные принципы, которые я определила (думаю, во многом рекомендации у специалистов с опытом совпадут):</p><ul><li>По умолчанию именуйте столбцы по-английски. Большая часть библиотек сегодня поддерживает обращение через квадратные скобки (для случаев, когда в названии не латиница). Но если название станет аргументом, передаваемым через командную строку, то придётся запоминать, как именно передавать кириллицу:</li></ul><ul><li>Если решили именовать столбец по-русски, старайтесь использовать небольшое название (до 20 символов): такое при многократном обращении к столбцу не потребуется долго набирать:</li></ul><h2>Файлы</h2><p>Если речь идет о временном файле, который вы изучите сразу после исполнения и удалите, достаточно назвать его как объект:</p><p>Однако если речь идет о регулярном использовании, лучше «вложиться»:</p><ul><li>Гуглите и используйте аббревиатуры. К примеру, датафрейм о пациентах, прошедший Предварительную обработку данных (Exploratory Data Analysis) и выгруженный в файл, можно назвать ‘patients_eda.csv’.</li><li>Пишите в README пояснения к скриптам. Документация репозитория выручит, если придумать хорошее название не удастся.</li></ul><h2>Заключение</h2><p>Работа с именованием переменных постепенно становится проще. Уже сегодня можно спросить у ИИ-расширения для VSCode <a href="https://codeium.com/">Codeium</a>, что делает тот или иной объект — и отталкиваться от ответа. Но порой ответы бывают малополезными:</p><figure><img src="https://media.tproger.ru/user-uploads/79101/2023-08-27/05d6d446-2117-41bb-b299-0348053c82de.png" alt="" /></figure><p>Так что пока приходится самим искать идеальные названия. Хотелось бы верить, ситуация в будущем изменится.</p><p>Встречали странные имена в коде? Поделитесь в комментариях.</p>]]></content:encoded>
    </item>
    <item>
      <title>Ещё 10 фишек Python, которые поднимут ваш скилл на новый уровень</title>
      <link>https://tproger.ru/articles/eshhyo-10-fiwek-python-kotorye-podnimut-vaw-skill-na-novyj-uroven</link>
      <comments>https://tproger.ru/articles/eshhyo-10-fiwek-python-kotorye-podnimut-vaw-skill-na-novyj-uroven?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/eshhyo-10-fiwek-python-kotorye-podnimut-vaw-skill-na-novyj-uroven</guid>
      <description><![CDATA[<p>Рассказываем о 10 функциях в Python, использование которых прокачает ваш скилл программирования и уровень вашего кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/eshhyo-10-fiwek-python-kotorye-podnimut-vaw-skill-na-novyj-uroven">Ещё 10 фишек Python, которые поднимут ваш скилл на новый уровень</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 30 Jul 2023 09:01:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Фишек в Python никогда не бывает много. Чем больше таких вы освоите, тем выше шанс на практике оперативно справиться с каким-нибудь очередным затыком. Да и на техническом собеседовании покажете себя с лучшей стороны. В статье Елена Капаца  – Data Analyst компании Инфостарт – рассказывает, какие еще трюки круто помогают новичку (и не только) прокачаться.</p><p>С первой статьей цикла вы можете познакомиться по <a href="https://tproger.ru/?p=240611">ссылке</a>.</p><h2>1. Форматирование многострочного вывода</h2><p>Если вы хотите, чтобы вывод напоминал табличку с одинаковым отступом, то при использовании F-строк добавьте интерполируемой переменной двоеточие и число, одинаковое для всех print(), например, 10. Это «резервирует» пространство из 10 символов:</p><p>Вывод будет приятным и опрятным:</p><h2>2. Частичное совпадение пользовательского ввода</h2><p>Эта замечательная фича позволяет справляться с ситуациями, когда пользователь вводит не все, что от него ожидается. Фишка работает в версиях Python не ниже 3.10:</p><h2>3. Выборка простых чисел в списке любого размера</h2><p>Выбирать простые числа приходилось мне на практике разве что во время учёбы, так что найти этому коду реальное применение непросто. Однако его легко переиначить для поиска любых других типов чисел – чётных / нечётных / делимых на определенное число:</p><h2>4. Словарное включение</h2><p>Многим из нас знакомо списковое включение (List Comprehension), которое позволяет за одну строку кода сгенерировать список:</p><p>Оказывается, Python поддерживает аналогичный функционал для словарей! Код ниже генерирует словарь, где у каждого имени определяется длина в символах:</p><h2>5. Резервирование символов</h2><p>С помощью операторов &gt; / &lt; / ^ вы можете добавлять выходному значению символы, к примеру, облегчающие поиск среди других выходных значений:</p><p>Как видно в сниппете выше, &lt; добавит символы после, &gt; – до, а ^ – окружит с двух сторон.</p><h2>6. Splat-оператор</h2><p>Splat (одна звездочка) расширяет коллекцию до позиционных аргументов, а splatty-splat (две звездочки) — словарь до именованных аргументов:</p><p>Этот код:</p><p>Равнозначен этому:</p><h2>7. Фильтрация списка без цикла</h2><p>Здесь неожиданно выделился модуль itertools. Если у нас есть два списка с именами и идентификаторами:</p><p>То вы можете выбрать из списка leaders только те, где значения selector равны единице:</p><h2>8. Подсчёт числа элементов в списке</h2><p>Помимо способности collections.Counter() понимать, что именно в строке нужно подсчитать:</p><p>В случае со списком слов модуль автоматически рассчитывает, как часто встречается то или иное слово:</p><h2>9. Поиск отличий в списках</h2><p>Допустим, у нас есть две длинные строки, и мы хотим найти отличия. seq1 и seq2 в сниппете ниже – это последовательности генов:</p><p>Можно использовать комбинацию zip() и enumerate(). Первая сопоставит символы с одинаковыми индексами, а вторая – присвоит каждой паре символов порядковый номер (всего 24). Чтобы найти отличающиеся символы, достаточно сравнить символы попарно в цикле for:</p><h2>10. Перегонка кортежа в словарь</h2><p>Допустим, у нас есть кортеж, где каждое значение – строка из двух символов:</p><p>Если «навесить» функцию dict(), то в результате мы получим словарь с разделенными на ключ – значение парами:</p><h2>Заключение</h2><p>Лаконичный неочевидный код, в одну строку делающий то, что у новичка бы заняло пять, сродни задачам по комбинаторике в школе, – понятно не всем и не сразу. Так что не злитесь на себя, если такое забывается. Просто сохраняйте в закладки. И джуны, и сеньоры – все мы гуглим такое.</p><p>Дайте знать в комментариях, интересно ли вам получить регулярное продолжение этой рубрики.</p>]]></content:encoded>
    </item>
    <item>
      <title>Dunder-методы: зачем они нужны и что могут</title>
      <link>https://tproger.ru/articles/dunder-metody-zachem-oni-nuzhny-i-chto-mogut</link>
      <comments>https://tproger.ru/articles/dunder-metody-zachem-oni-nuzhny-i-chto-mogut?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/dunder-metody-zachem-oni-nuzhny-i-chto-mogut</guid>
      <description><![CDATA[<p>Рассказываем, что такое Dunder-методы, которые в Python определяются с двумя подчёркиваниями, зачем они нужны и как их использовать.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/dunder-metody-zachem-oni-nuzhny-i-chto-mogut">Dunder-методы: зачем они нужны и что могут</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 23 Jul 2023 08:27:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Абсолютно все питонисты рано или поздно спотыкаются о методы с двойными подчеркиваниями. Зачем они вообще нужны? Почему без них не обойтись? Кто это придумал? В чем отличие от классических методов без подчеркиваний? Почему они не везде? Если вы тоже задаетесь такими вопросами, то вашему вниманию обзор на некоторые из самых популярных методов с двойным подчеркиванием. В Python они определяют кастомные свойства и поведение объектов.</p><p>ВРЕЗКА. Стоит отличать их от магических методов IPython, которые делают работу в Google Colaboratory / Jupyter Notebook удобнее. Если вы хотите познакомиться с последними поближе, прочитайте мою статью “<a href="https://tproger.ru/?p=243630">Топ самых полезных магических команд для завсегдатаев Python</a>”.</p><p>Например, когда вы выполняете операции, такие как сложение (+), вычитание (-), умножение (*), и деление (/), Python автоматически вызывает соответствующие “магические” методы (__add__, __sub__, __mul__, и __div__ соответственно).</p><p>Слово «dunder» здесь – не ругательство, оно отсылает к двойному подчеркиванию. Два underscore нужны только для того, чтобы предотвратить конфликт имен с другими методами, реализованными ничего не подозревающими программистами. И внедрил их, конечно, создатель языка Гвидо ван Россум.</p><h2>Да кому нужны эти Dunder-методы?</h2><figure><img src="https://media.tproger.ru/uploads/2023/07/14e835c0-ad9d-46ce-8327-0ce14ed4187d.jpeg" alt="" /></figure><p>Чтобы понять, зачем этот класс методов выделен в отдельную группу, рассмотрим пример из <a href="https://stackoverflow.com/questions/2657627/why-does-python-use-magic-methods">треда на Stack Overflow</a>. Пользователь Greg Beech спрашивает, зачем вообще нужны такие усложнения, если вместо __len__ можно использовать просто len()? Замечательный сниппет от Mangu Singh Rajpurohit раз и навсегда отвечает на этот вопрос, на мой взгляд:</p><p>«Представьте, что у вас есть два словаря, и вы хотите их сложить:</p><p>При исполнении этого кода будет вызываться ошибка:</p><p>Ошибка TypeError гласит, что словари складывать нельзя. А ведь нам так надо! Так что давайте добавим классу “словарь” этот функционал. Точнее, инициируем дочерний класс AddableDict:</p><p>И вуаля! Словари складываются:</p><h2>Некоторые интересные Dunder-методы</h2><h3>1. __init__:</h3><p>Если у вас есть определенные классы в Python, вы обязательно встретитесь с методом __init__. Он отвечает за инициализацию экземпляра класса, поэтому именно в нем вы обычно устанавливаете его неотъемлемые атрибуты – например, длина ребра квадрата:</p><h3>2. __call__:</h3><p>Этот метод позволяет создать так называемые «вызываемые» объекты, то есть их можно вызывать как функции.</p><h3>3. __getitem__ и __setitem__:</h3><p>Методы позволяют обращения к объекту по индексу или ключу, как если бы это был список или словарь.</p><h3>4. __iter__ и __next__:</h3><p>Эти методы позволяют создавать итерируемые объекты, которые можно использовать в цикле for.</p><h3>5. __enter__ и __exit__:</h3><p>Эти методы используются для создания контекстных менеджеров (то есть объектов, которые можно использовать в блоке with), и позволяют управлять ресурсами, которые нужно освободить после использования.</p><p>При входе и выходе из блока with будут выведены соответствующие сообщения.</p><h2>И небольшая шпаргалка</h2><p>Вашему вниманию перечень несложных методов, которые могут пригодиться, если вы в целом готовы впустить Dunder’ы в свой код:</p><figure><img src="https://media.tproger.ru/uploads/2023/07/bde69018-6ea6-4fca-96c3-55ad4afd588d-scaled.jpg" alt="" /></figure><h2>Заключение</h2><p>Если честно, у меня ушел не один год, чтобы понять и принять концепцию Dunder-методов. Из-за информационной перегрузки мозг часто стремился избегать дополнительных сложностей — и магические методы как раз попали в эту категорию. Лишь исчерпав на практике классические возможности тех или иных объектов, я вернулась к этой теме с помощью StackOverflow.</p><p>Так что не корите себя, если эту тему хочется поначалу увидеть и тут же забыть: не все в Python подходит новичкам, на некоторые вещи нужны опыт и спрос. Но освоившись с такой крутой фишкой (и еще десятком таких), вы получите преимущество в конкурентной борьбе за интереснейшие проекты, лучшие условия труда и больший оффер.</p>]]></content:encoded>
    </item>
    <item>
      <title>Почему специалисты мидл-уровня исчезли с рынка</title>
      <link>https://tproger.ru/articles/gde-zolotaya-seredina-specialisty-midl-urovnya-ischezli-s-rynka</link>
      <comments>https://tproger.ru/articles/gde-zolotaya-seredina-specialisty-midl-urovnya-ischezli-s-rynka?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Савельева]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/gde-zolotaya-seredina-specialisty-midl-urovnya-ischezli-s-rynka</guid>
      <description><![CDATA[<p>Рассказываем, почему запрос компаний на миддл-специалистов остаётся высоким, и куда пропали мидлы с IT-рынка.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/gde-zolotaya-seredina-specialisty-midl-urovnya-ischezli-s-rynka">Почему специалисты мидл-уровня исчезли с рынка</a>»</p>]]></description>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Карьера]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 21 Jul 2023 09:25:38 GMT</pubDate>
      <content:encoded><![CDATA[<p>В 2022 году много иностранных ИТ-компаний ушло из РФ, а некоторые российские — сократили штат. Казалось бы, мы должны были увидеть больше кандидатов в активном поиске. Однако запрос рынка на хороших разработчиков, DevOps-инженеров, тестировщиков ПО, менеджеров продукта, особенно уровня middle, всё ещё гораздо больше, чем объём высвободившихся специалистов. Плюс уехали те, кто имеет возможность работать удаленно. Где же адекватные специалисты-мидлы?  — рассуждает <b>Анна Савельева</b>, HR-партнер ИТ-компании «Биорг» (Beorg Smart Vision).</p><h2>Малый бизнес в борьбе за кадры</h2><p>Больше других на рынке кадровый голод испытывают ИT-компании с хорошим продуктом, но небольшим штатом. Такие организации уже чуть больше стартапа, но и не зрелый бизнес, который может легко оплачивать, например, релокацию будущим сотрудникам из других регионов.</p><p>Для кандидатов высокого уровня небольшие компании — серые лошадки, на которых не все готовы ставить. А навыков джунов – определенно недостаточно, чтобы закрыть потребности в разработке. Остается открытым вопрос, который сегодня звучит по всему рынку и сильно мучает меня лично — куда делись нормальные мидлы?</p><p>Странно, когда на собеседовании разработчик, который имеет год опыта, претендует на оклад в 350 тыс. рублей. Но таких примеров все больше: навыки не так сильны, а ожидания, в том числе финансовые – очень высокие.</p><p>Возможно, причина в том, что массово сейчас развиваются различные быстро-курсы, где обещают успешный старт карьеры в ИТ. В целом, это  хорошо, но иногда люди выходят с несколько искаженной картиной мира.</p><p>Другая причина — очень много банков развивают свою внутреннюю разработку и балуют высокой зп, часто ощутимо выше рынка. Когда такие специалисты  выходят на рынок труда, хотят работать над более интересным им продуктом, то попадают в сложную ситуацию: задачи интереснее, чем в банке, а денег за них предлагают меньше. В этом случае хороший разработчик мидл-уровня предъявляет к работодателю очень серьёзные требования, что не всегда подходит для молодых компаний.</p><h2>В поиске золотой середины: ау, мидлы!</h2><p>Например, мы сильно споткнулись на вакансии full stack-разработчика, то есть человека с хорошим пониманием каждого уровня разработки. Суперкандидаты предпочитают уходить в крупные корпорации, другие же не дотягивают по компетенциям даже до среднего уровня. Не все при это думают о том, что в крупной компании – меньше свобода действий, больше бюрократии. Это традиционная «плата» для сотрудников корпораций. В итоге мы на какое-то время отказались от этой вакансии и вместо full-stack взяли еще одного backend-ера, усилив команду.</p><p>Примерно такая же история приключилась с разработчиком C++. Через 3 недели активных поисков по Москве мы переключились на другие регионы. Нужного кандидата нашли в Иркутске. Он приехал на две недели в Москву (билеты и проживание мы оплатили), познакомился с командой, задачами и сейчас работает удаленно. Конечно, мы опасались брать кандидата из другого города и часового пояса — с разницей в 5 часов.  Но в конечном итоге мы получили опытного сотрудника с широким профессиональным кругозором. Сейчас он возглавляет отдел разработки.</p><p>Product-менеджера мы искали около полугода. Находили хороших специалистов уровней middle и senior, но они выбирали более крупных работодателей, у которых уже есть выстроенные процессы. Многих почему-то смущало быть первопроходцем. Поскольку ранее продакта в компании не было, очевидно, часть процессов нужно было выстраивать с нуля. Конечно, это объемная работа, но, на наш взгляд, идеальный путь для профессионального развития. И показательно, что часть кандидатов не стала брать на себя эти обязательства.</p><h2>Как молодая ИТ-компания работает с кадрами</h2><p>Мы в «Биорг» ещё в 2022 году сумели набрать подходящих под нашу кадровую стратегию ИТ-специалистов. Что это значит? Исходим из того, что на рынке сложно найти хороших готовых спецов под наши задачи, карьерные предпочтения и финансовые ожидания которых полностью бы совпали с нашей компанией.</p><p>В этой связи самая распространенная у нас практика – брать людей с нужными компетенциями, которые хотят обучаться, — уже не джунов, но и до среднего уровня ещё не дотягивающих. Мы им помогаем развиваться, в том числе в контексте нашего бизнеса, вводим систему грейдов – чтобы люди росли профессионально и больше зарабатывали.</p><p>Получается, что обучить самим – выгоднее, чем искать на рынке готового специалиста. Оборотная сторона медали — люди, пройдя обучение, сразу начинают по-другому смотреть на рынок труда. Бывает, что разработчики уходят от нас в крупные компании. Мы по этому поводу шутим, что стали поставщиком ИТ-кадров для всего крупного корпоративного бизнеса. Как говорится, не благодарите. И тем не менее мы не отказываемся от позиции, что растить сотрудников компании — выгодно.</p><h2>Как может сложиться карьера в стартапе</h2><p>Яркий пример, как сотрудник развил свой потенциал в “Биорг” — наш техлид. Он присоединился к команде стартапа в 2017 на самых первых этапах разработки нашей платформы для оцифровки документов. Пришёл фактически джуном Его задача была развивать облачную краудсорсинговую часть платформы, которую мы используем для обучения нейросетей. Он написал её с нуля, дорабатывал отказоустойчивость по мере того, как нарастала сложность проектов, вместе с этим серьёзно наращивал и свои профессиональные компетенции. Со временем его модуль стал важной частью нашего сервиса по переводу данных из аналогового формата в машиночитаемый вид.</p><p>В конце 2022 года этот сотрудник реализовал прописанный в договоре опцион — приоритетное право на выкуп доли компании и стал совладельцем бизнеса наряду с ещё несколькими коллегами. Чем не история успеха? Вполне подтверждает тезис о том, что главное — нанимать талантливых сотрудников, которые быстро умеют обучаться и расти. Это создает самые неожиданные мультипликаторы для развития как команды и для роста бизнес-показателей.</p><p>В 2022 году компания запустила несколько продуктовых направлений. Стало очевидно, что продуктовая модель развития больше привлекает квалифицированных специалистов. Рассчитываем, что это повышает наши шансы привлечь достойных кандидатов. Но на вопрос, где же нормальные мидлы мы так и не нашли ответа, что лишний раз доказывает нашу точку зрения — проще и лучше вырастить своих.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как написать свой Python-модуль</title>
      <link>https://tproger.ru/articles/kak-napisat-svoj-python-modul</link>
      <comments>https://tproger.ru/articles/kak-napisat-svoj-python-modul?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-napisat-svoj-python-modul</guid>
      <description><![CDATA[<p>Если вы выделили повторяющийся функционал в отдельный набор функций и хотите стабилизировать код для быстрого подключения, статья для вас.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-napisat-svoj-python-modul">Как написать свой Python-модуль</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 21 Jul 2023 08:30:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Написание Python-модуля может быть полезным, когда нужно повторно использовать код в разных проектах или хочется создать библиотеку собственных функций и классов для облегчения разработки. Если вы кодите на Python на повседневной основе и — о чудо! — выделили повторяющийся функционал в отдельный набор функций, классов, методов и переменных, и хотите “стабилизировать” такой код для последующего быстрого подключения, то статья для вас.</p><p>Такое будет непросто повторить новичку, если нет живого примера адекватного объема под рукой, однако для вас это отличная возможность сделать сто шагов вперед в плане роста навыков.</p><p>Грубо говоря, Python-модуль представляет собой файл с расширением .py, который содержит определения функций, классов и переменных, доступных для использования в других программах. Давайте начнем с создания простого модуля под названием mymodule.</p><h2>Шаг 1: Создание модуля</h2><p>Создадим новый файл с именем mymodule.py и откроем его в любимом текстовом редакторе.</p><p>Кстати, если вы только начинаете писать код на Python и еще не знаете, в какой программе это делать, то вот моя подборка питонических IDE с хорошим UI / UX.</p><p>Здесь вы будете определять функции и классы для вашего модуля mymodule.py.</p><p>Так мы определяем:</p><ul><li>функцию greet(), которая принимает аргумент name и выводит приветствие;</li><li>класс Person, который имеет конструктор __init__() для установки имени;</li><li>метод say_hello(), который выводит приветствие с использованием имени объекта класса.</li></ul><h2>Шаг 2: Использование модуля</h2><p>Теперь модуль можно использовать его в других программах. Создадим новый файл с именем main.py и импортируем mymodule следующим образом:</p><p>Здесь мы используем ключевое слово import и вызываем функцию greet().</p><h2>API как следующая стадия развития модуля</h2><p>Если ваш модуль разрастается, становится полноценным проектом и обретает, скажем, динамические данные, то самое время ему стать полноценным API (Application Programming Interface – программный интерфейс приложения). Выражаясь простыми словами, то это следующая стадия развития вашего модуля.</p><p>С разработкой полноценных API помогают сервисы вроде <a href="https://swagger.io/">swagger.io</a>, который ускоряет подгонку вашего модуля к стандартам RESTful, и создание документации. Но об этом – уже в следующей статье.</p><h2>Когда в собственном модуле нет необходимости</h2><p>Написание и поддержка собственного модуля отнимают немалое время, как оказалось. И да, здесь лучше не воевать в одиночку: вместе с затыками бороться всегда проще. Так и хочется посоветовать: избегайте офферов, где у вас не будет команды разработчиков, это существенно замедлит кодинг. Более того, если проект, где пригождается самописный модуль, не столь велик, возможно, в таком апгрейде нет необходимости. Иначе на отладку, вызванную несовместимостью используемых внешних библиотек, вы потратите трудовые ресурсы, которых на такое тратить пока не стоит.</p><h2>Заключение</h2><p>Надеюсь, статьей мне удалось показать вам, как пишется элементарный модуль. В моей практике такое пригождалось, когда необходимо обсчитать итоговую эффективность бота с помощью классов “(не)успешный диалог”. Так что веселья ради найти, куда бы прикрутить такую фишку, довольно просто. Потому написание элементарных импортируемых модулей часто входит в обучение основам Python.</p><p>Напишите в комментариях, какие самописные модули вы используете в работе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Обзор книги для мидлов «Python. К вершинам мастерства» Лучано Рамальо</title>
      <link>https://tproger.ru/articles/obzor-knigi-quot-python-k-verwinam-masterstva-quot</link>
      <comments>https://tproger.ru/articles/obzor-knigi-quot-python-k-verwinam-masterstva-quot?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Лена Капаца]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/obzor-knigi-quot-python-k-verwinam-masterstva-quot</guid>
      <description><![CDATA[<p>Рассказали о плюсах и минусах книги «Python. К вершинам мастерства» и разобрали, кому её стоит прочесть.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/obzor-knigi-quot-python-k-verwinam-masterstva-quot">Обзор книги для мидлов «Python. К вершинам мастерства» Лучано Рамальо</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 26 May 2023 13:44:53 GMT</pubDate>
      <content:encoded><![CDATA[<p>Книга обязательна к прочтению Middle-разработчикам и выделяется своими чёткими, вдумчивыми объяснениями сложных концепций. Предполагается, что читатели имеют базовые познания в Python 3, потому автор не тратит время на вводную и сразу освещает тонкости.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/696289bd-65ab-4c5c-b5b2-47e56ac7a147.png" alt="" /><figcaption>Лучано Рамальо (Luciano Ramalho) – технический директор компании ThoughtWorks, лектор и автор нескольких книг о программировании. Родился и проживает в Бразилии.</figcaption></figure><h2>Структура книги</h2><p>Обложка книги с подколом от издательства o’Reilly: на иллюстрации песчаная игуана Намакуа.</p><p>Издание разделено на несколько тем:</p><ul><li>модель данных;</li><li>структуры данных;</li></ul><ul><li>функции как объекты;</li></ul><ul><li>объектно-ориентированные идиомы (устоявшиеся паттерны питонического ООП);</li></ul><ul><li>поток управления (порядок усвоения компилятором того или иного компонента программы);</li></ul><ul><li>метапрограммирование (способность программы использовать соседние скрипты).</li></ul><p>Код книги доступен в <a href="https://github.com/fluentpython/example-code-2e">официальном репозитории</a>. Но без чтения понять контекст того или иного скрипта, как правило, затруднительно.</p><h3>Преимущества</h3><p>Fluent Python написан в разговорном стиле, он прост и приятен для чтения. Рамальо следит за тем, чтобы терминов в тексте было не слишком много. И нередко сопровождает сложные мысли вспомогательными диаграммами и хинтами с изображением лемура:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/d4c19167-0ebf-45a6-b8bd-f2b4a4656c17.png" alt="" /></figure><p>Многие тезисы автор подкрепляет кодом. Посмотрите на простой пример — списковое включение (List Comprehension):</p><figure><img src="https://media.tproger.ru/uploads/2023/05/c0e2332f-3328-4738-a36c-b2b3bc27d07e.jpg" alt="" /></figure><p>Максимально полезной для меня стала глава №5 — о функциях. Помимо необходимого минимума по теме и десятка-другого must-have терминов, вроде функций высшего порядка и анонимных функций, автор рассказывает и об аннотации ф., интроспекции и прочем.</p><p>С базисом о функциях связана моя каждодневная работа, и глава отвечает на вопросы, которые волей-неволей задаёшь себе во время разработки, но обычно не записываешь, оставляя на подкорке. А во время чтения получаешь долгожданные ответы. В таком вижу маленькое счастье разработчика.</p><p>В конце каждой главы — экспертное резюме, которое экономит время на создание кода в будущем. Именно к таким сводкам возвращаешься спустя месяцы по прочтении “.. К вершинам мастерства», поскольку они направляют внимание сразу к нужному сниппету в главе и в целом часто снимают вопросы сами.</p><p>Рамальо также прекрасно показывает протокольные свойства технологии. То есть обращает Python не в объектно-, а в протокольно-ориентированный язык (ПОП). Покажу на примере.</p><p>Разницу между ООП и ПОП можно вкратце описать так. Представьте, что вы создаёте игру, в которой есть транспортные средства. Есть классы «автомобиль», «мотоциклы», и их логично сделать подвидами базового класса «моторный транспорт». Всё выглядит замечательно, пока игра не переносится в XXII век, где есть летающие автомобили. Создавать ещё один базовый класс, в котором соединяются обе функциональности, не стоит. Здесь на помощь придёт протокол, он как раз позволит автомобилям летать во второй половине игры о будущем, но ездить в первой части про настоящее.</p><p>Если в Swift, где ПОП был впервые представлен как концепция, протоколы – это основа основ, то в Python за идею «отыгрывают» методы. Подробнее об этом прекрасно рассказывает пользователь StackOverflow claudio в <a href="https://stackoverflow.com/questions/51895320/using-protocol-oriented-programming-pop-in-python">этом треде</a>.</p><p>Наконец, страсть Рамальо к Python проявляется в тексте. Это делает книгу не только образовательным ресурсом, но и источником удовольствия.</p><h2>Недостатки</h2><p>Глубина и темп книги могут не подойти абсолютным новичкам, поскольку:</p><ul><li>Сделано допущение о том, что 200–300 базовых терминов усвоены и не замедляют особо чтение.</li><li>Несмотря на хороший перевод издательства ДМК, английского в коде всё равно очень много, так что знание языка считаю необходимым.</li><li>Стоимость массивного издания на Ozon держится в районе 3,5 тысячи рублей, что автоматом наводит на мысль купить электронную версию. Пришлось останавливать себя, чтобы не спиратить.</li></ul><p>Работа Рамальо рекомендуется тем, кто уже имеет базовое представление о Python и хочет глубже понять философию и принципы разработки. Новичкам же прекрасно подойдёт другой бестселлер – «Изучаем Python» Марка Лутца.</p><h2>Заключение</h2><p>«… К вершинам мастерства» – это исключительный ресурс для стремящегося углубить своё владение Python. Это издание полно идей, применимых к программированию в целом. С ним вы научитесь на примерах создавать идиоматичный код, то есть использовать его, как предполагали создатели, эффективнее в итоге.</p><p>К книге естественно обращаться как к справочнику и по прочтении. Пускай вас не отпугивает её объём: это мастхэв для любого серьёзного питониста.</p>]]></content:encoded>
    </item>
    <item>
      <title>Регулярные выражения в JavaScript: это не так страшно, как вы думаете</title>
      <link>https://tproger.ru/articles/regulyarnye-vyrazheniya-v-javascript-eto-ne-tak-strawno-kak-vy-dumaete</link>
      <comments>https://tproger.ru/articles/regulyarnye-vyrazheniya-v-javascript-eto-ne-tak-strawno-kak-vy-dumaete?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[МТС]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/regulyarnye-vyrazheniya-v-javascript-eto-ne-tak-strawno-kak-vy-dumaete</guid>
      <description><![CDATA[<p>Подробный гайд с примерами по регулярным выражениям в JS, позволяющим выполнять любые манипуляции со строками.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/regulyarnye-vyrazheniya-v-javascript-eto-ne-tak-strawno-kak-vy-dumaete">Регулярные выражения в JavaScript: это не так страшно, как вы думаете</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Регулярные выражения]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 20 Apr 2023 10:14:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Регулярные выражения — тема, которой боятся многие разработчики. Между тем они требуются в большинстве вакансий уровня middle. В статье разберём основные символы и способы создания регулярных выражений в JavaScript, которые с небольшими отличиями работают и в других языках программирования.</p><p>Рассмотрим тему регулярных выражений от простого к сложному.</p><h2>Что такое регулярные выражения</h2><p>Регулярные выражения (ещё их называют Regular Expressions, сокращённо regex или regexp, регулярки) — специальные шаблоны, которые используют для поиска и обработки текста. Для поиска в них можно задавать дополнительные команды, например игнорирование регистра.</p><p>В отличие от обычных функций поиска и замены, которые встречаются во всех текстовых редакторах, регулярные выражения могут сочетаться с другим кодом. С их помощью можно выполнять сложные операции с текстом, менять порядок строк, извлекать отдельные слова, валидировать и передавать информацию на сервер.</p><p>А ещё регулярки экономят очень много кода. Но есть один недостаток — они часто выглядят пугающе, запутанно и странно, особенно если шаблон сложный.</p><p><b>Например, этот шаблон ищет email-адреса:</b></p><p>/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ </p><p>Почти во всех языках программирования есть регулярные выражения — реализация может отличаться, но основные моменты похожи. Их можно сравнить с отдельным мини-языком, который функционирует сам по себе.</p><p>Интересный момент: регулярные выражения действительно сложно писать, сложно читать и сложно поддерживать или изменять, но часто именно с их помощью разумнее выполнять работу над строками.</p><p><b>Например, нам нужно проверить номер телефона в форме регистрации:</b></p><p>В этом примере мы создали функцию validatePhoneNumber, которая принимает номер телефона в качестве аргумента и возвращает true, если номер телефона является действительным номером для России, и false в противном случае.</p><p>В регулярном выражении phoneRegex мы использовали символ ^, чтобы указать, что номер телефона должен начинаться с «+7» или «8» (возможно, без плюса). Затем мы использовали символы \d для указания цифр и {10} для указания количества цифр в номере телефона (10 цифр).</p><p>В примере использования мы передали строку «+79123456789» функции validatePhoneNumber и проверили результат.</p><h2>Как создать регулярное выражение в JavaScript</h2><p>Есть два способа создания регулярок — разберём каждый.</p><h3>1. Конструктор RegExp()</h3><p>Конструктор RegExp() позволяет создавать регулярные выражения на основе строки, передаваемой ему в качестве аргумента. Формат конструктора RegExp() выглядит следующим образом:</p><p>const regex = new RegExp("pattern", "flags");</p><p>Где «pattern» — это строка, содержащая регулярное выражение, а «flags» — дополнительные флаги, определяющие, как будет работать регулярное выражение. Например, флаг i указывает, что регулярное выражение должно игнорировать регистр символов.</p><p>Вот пример использования конструктора RegExp() для создания регулярного выражения, которое ищет все цифры в строке:</p><p>const regex = new RegExp("\\d", "g");</p><p>Здесь мы передаём строку \d в качестве первого аргумента, что означает «любая цифра». Флаг g указывает, что регулярное выражение должно искать все совпадения в строке.</p><p>Этот способ предпочтительнее, когда вы знаете, что выражение будет изменяться или вы не знаете шаблон. Например, это информация, которую вводит пользователь в формах.</p><h3>2. Литералы</h3><p>Литерал регулярного выражения, так же как и конструктор, состоит из двух частей. Первая часть — шаблон, который необходимо описать, он заключается в слеши (//). Вторая часть, после закрывающего слеша, — флаги, но их использование также необязательно.</p><p>Вот как он выглядит:</p><p>const regex = /pattern/flags;</p><p>Разберём на примере использования литерала для создания того же регулярного выражения, которое ищет все цифры в строке:</p><p>const regex = /\d/g;</p><p>Здесь мы используем литерал /\d/g, который означает «любая цифра» с флагом g, указывающим, что регулярное выражение должно искать все совпадения в строке.</p><p>Обратите внимание: если вы планируете создавать регулярные выражения при помощи литералов, такой подход не позволяет изменять задаваемые значения динамически — литералы регулярных выражений компилируются заранее при анализе скрипта. Зато если ваше регулярное выражение постоянно, то литералы позволяют увеличить производительность.</p><p>Оба эти способа создания регулярных выражений в JavaScript эквивалентны друг другу — выбирайте тот, который вам удобнее.</p><h2>Базовый синтаксис регулярных выражений в JavaScript</h2><p>Разберём основу синтаксиса регулярных выражений в JavaScript.</p><h3>Шаблоны и метасимволы</h3><p>Шаблоны — набор символов, которые используют для поиска определённых сочетаний символов в тексте. Метасимволы — специальные символы, которые используют для составления шаблонов.</p><p><b>Вот несколько примеров шаблонов и метасимволов:</b></p><p>. — соответствует любому одиночному символу, кроме символа новой строки.</p><p>* — соответствует нулю или более повторениям предыдущего символа.</p><p>+ — соответствует одному или более повторениям предыдущего символа.</p><p>? — соответствует нулю или одному повторению предыдущего символа.</p><p>^ — соответствует началу строки.</p><p>$ — соответствует концу строки.</p><p>() — определяет группу символов, которые могут быть повторены.</p><h3>Квантификаторы и модификаторы</h3><p><b>Квантификаторы</b> — метасимволы, которые определяют количество повторений предыдущего символа.</p><p><b>Некоторые квантификаторы:</b></p><p>{n} — соответствует ровно n повторениям предыдущего символа.</p><p>{n,m} — соответствует от n до m повторений предыдущего символа.</p><p>{n,} — соответствует n или более повторениям предыдущего символа.</p><p>? — делает предыдущий квантификатор ленивым, т. е. соответствует наименьшему возможному количеству повторений.</p><p>Пример использования квантификатора:</p><p>const pattern = /a{3}/; // соответствует трём символам "a" подряд</p><p>В тексте, который будет обрабатываться с помощью этого регулярного выражения, будут найдены все вхождения трёх символов a подряд. Например, строка «baaaad» будет соответствовать шаблону, а строка «abaa» — нет.</p><p>Модификаторы — специальные флаги, которые можно добавить к регулярному выражению для изменения его поведения.</p><p>Некоторые модификаторы:</p><p>g — глобальный поиск, ищет все совпадения в строке.</p><p>i — игнорирование регистра, игнорирует различия между верхним и нижним регистром.</p><p>m — многострочный поиск, позволяет искать совпадения в нескольких строках.</p><p>Пример использования модификатора:</p><p>const pattern = /test/gi; // соответствует строкам "test" или "Test" или "TEST" глобально и без учёта регистра</p><h3>Группы и обратные ссылки</h3><p>Группы — последовательности символов, заключённые в скобки (), которые могут повторяться с помощью квантификаторов. Группы также позволяют использовать обратные ссылки, которые ссылаются на результат совпадения группы в регулярном выражении. Обратная ссылка обозначается символом \ и номером группы, например, \1 ссылается на первую группу.</p><p>Пример использования групп и обратных ссылок:</p><p>​​const pattern = /(\w+)\s\1/; // соответствует повторяющейся последовательности символов, разделённой пробелом</p><p>В этом примере группа (\w+) соответствует любой последовательности символов, состоящей из буквенно-цифровых символов и подчёркивания. Затем \s соответствует пробелу, а \1 обратно ссылается на первую группу (\w+), так что регулярное выражение соответствует только тем строкам, в которых последовательность символов повторяется через пробел.</p><h2>Экранирование в регулярных выражениях</h2><p>Как мы разобрались выше, в регулярках есть символы, которые имеют специальное значение и используются для обозначения шаблонов, таких как поиск символов, чисел и т. д. Но иногда возникают ситуации, когда эти символы должны быть экранированы — использованы как обычные символы, вместо своего специального значения.</p><p>Для экранирования символа используется обратный слеш \.</p><p>Например, символ . в регулярном выражении означает любой символ, но если мы хотим использовать точку как обычный символ, мы можем экранировать его, написав \.</p><p><b>Примеры:</b></p><ul><li>Поиск всех цифр в тексте.</li></ul><ul><li>Замена всех пробелов на символ подчёркивания.</li></ul><ul><li>Замена всех точек в тексте на запятые, кроме точек, которые находятся внутри чисел.</li></ul><h2>Как использовать регулярки с методами объекта RegExp и String</h2><p>После того как базовый синтаксис и способы написания шаблонов усвоены, перейдём к использованию регулярок в работе. Для работы с регулярными выражениями в JavaScript есть несколько методов — рассмотрим каждый.</p><h3>Метод test()</h3><p>Метод test() проверяет, соответствует ли регулярное выражение заданной строке. Метод возвращает true, если строка соответствует регулярному выражению, и false, если не соответствует.</p><p>Пример использования метода test():</p><p>В этом примере мы используем метод test() для проверки, содержит ли строка str подстроку «Hello». Регулярное выражение /Hello/ соответствует строке «Hello», и метод test() вернёт true.</p><h3>Метод exec()</h3><p>Метод exec() используется для поиска совпадений регулярного выражения в заданной строке. Метод возвращает массив, содержащий найденное совпадение и дополнительную информацию о нём.</p><p>Пример использования метода exec():</p><p>В этом примере мы используем метод exec() для поиска совпадений регулярного выражения /Hello/ в строке str. Метод exec() возвращает массив, содержащий найденное совпадение «Hello», индекс первого символа совпадения в исходной строке и саму исходную строку.</p><h3>Метод match()</h3><p>Метод match() используется для поиска всех совпадений регулярного выражения в заданной строке. Метод возвращает массив, содержащий все найденные совпадения.</p><p>Пример использования метода match():</p><p>В этом примере мы используем метод match() для поиска всех совпадений регулярного выражения /the/gi в строке str. Метод match() возвращает массив, содержащий все найденные совпадения, включая повторения.</p><h3>Метод replace()</h3><p>Метод replace() принимает два аргумента: регулярное выражение и строку, на которую нужно заменить найденное совпадение. Этот метод ищет все совпадения с заданным регулярным выражением в исходной строке и заменяет их на указанную строку.</p><p>Пример использования метода replace():</p><p>В этом примере мы используем метод replace() для замены слова «мир» на «земля» в строке «Привет, мир!» Регулярное выражение /мир/ ищет все совпадения со словом «мир» в строке, и метод replace() заменяет их на слово «земля». Результатом работы метода является новая строка «Привет, земля!»</p><h3>Метод search()</h3><p>Метод search() принимает один аргумент — регулярное выражение. Он ищет первое совпадение с заданным регулярным выражением в исходной строке и возвращает индекс первого символа совпадения. Если совпадение не найдено, метод возвращает -1.</p><p>Например:</p><p>В этом примере мы используем метод search() для поиска первого совпадения со словом «текст» в строке «Это текст для примера». Регулярное выражение /текст/ ищет первое совпадение со словом «текст» в строке, и метод search() возвращает индекс первого символа этого совпадения, который равен 4.</p><h3>Метод split()</h3><p>Метод split() принимает один аргумент — регулярное выражение. Он разбивает исходную строку на массив подстрок, используя заданное регулярное выражение как разделитель.</p><p>Пример использования метода split():</p><p>В этом примере мы используем метод split() для разделения строки «Это текст для примера» на массив слов, используя регулярное выражение / / в качестве разделителя. Регулярное выражение / / ищет все пробелы в строке, и метод split() использует их в качестве разделителя для разделения строки на массив слов. Результатом работы метода является массив [«Это», «текст», «для», «примера»].</p><h2>Когда и как использовать регулярки на практике</h2><p>Регулярные выражения пригодятся для самых разных задач: поиска текста, валидации данных, замены текста, разбора текста на составляющие, генерации паролей и т. д.</p><p>Разберём подробнее несколько вариантов использования.</p><h3>Валидация email-адресов</h3><p>Один из популярных примеров использования регулярных выражений — валидация email-адресов.</p><p><b>Шаблон будет выглядеть так:</b></p><p>/^[^\s@]+@[^\s@]+\.[^\s@]+$/, где</p><p>^ — начало строки</p><p>@ — символ «@»</p><p>\. — символ «.» (экранированный с помощью слеша)</p><p>$ — конец строки</p><p>Таким образом, этот паттерн соответствует любому email-адресу, который состоит из локальной части (часть до символа «@») и доменной части (часть после символа «@» и до символа «.») с верным форматом.</p><p>Для проверки email-адреса на соответствие шаблону можно использовать метод test():</p><p>В этом примере функция validateEmail() принимает email-адрес в качестве аргумента и использует регулярное выражение для проверки его соответствия формату. Метод test() возвращает true, если email-адрес соответствует паттерну, и false в противном случае.</p><h3>Форматирование текста</h3><p>Регулярные выражения могут быть полезны для форматирования текста, например для замены повторяющихся символов, форматирования дат и т. д.</p><p>Рассмотрим пример форматирования даты с использованием регулярных выражений.</p><p>Допустим, у нас есть дата в формате «гггг-мм-дд» (например, «2023-03-29»), а мы хотим преобразовать её в формат «дд.мм.гггг» (например, «29.03.2023»). Для этого мы можем использовать регулярное выражение, чтобы разделить дату на отдельные части, а затем использовать эти части для создания новой строки в нужном формате.</p><p><b>Давайте разберём это регулярное выражение:</b></p><p>^ — начало строки</p><p>(\d{4}) — группа, соответствующая 4 цифрам (год)</p><p>— — символ «-«</p><p>(\d{2}) — группа, соответствующая 2 цифрам (месяц)</p><p>— — символ «-«</p><p>(\d{2}) — группа, соответствующая 2 цифрам (день)</p><p>$ — конец строки</p><p>В этом примере мы используем метод match() для поиска соответствия регулярному выражению в строке date. Если соответствие найдено, метод match() возвращает массив, содержащий соответствующие группы в порядке их появления в регулярном выражении. Затем мы используем эти группы для создания новой строки в нужном формате.</p><p>Регулярные выражения также могут быть полезны для форматирования других типов данных, например, для замены повторяющихся символов или форматирования номеров телефонов.</p><h3>Извлечение номеров телефонов из строки</h3><p>Регулярные выражения широко используются для извлечения информации из текстовых строк. Например, если есть строка, содержащая номера телефонов, можно использовать регулярные выражения, чтобы извлечь эти номера телефонов и сохранить их в базе данных.</p><p>Рассмотрим несколько примеров использования регулярных выражений для извлечения информации из строк.</p><p>Допустим, у вас есть строка, содержащая несколько номеров телефонов. Вы хотите извлечь эти номера телефонов из строки и сохранить их в базе данных.</p><p>Для этого вы можете использовать следующий код:</p><p>В этом примере мы используем регулярное выражение<b> </b>/\+\d\s?\(\d{3}\)\s?\d{3}-\d{2}-\d{2}/g, чтобы извлечь номера телефонов из строки string.</p><p>Регулярное выражение ищет символ «+», и за ним следует одна или несколько цифр, за которыми следует необязательный пробел. Затем идут открывающая и закрывающая скобки, за которыми следуют три цифры, после чего снова идёт необязательный пробел. Далее идут три цифры, тире, две цифры, тире и ещё две цифры. В результате, если в строке есть номер телефона в формате, соответствующем регулярному выражению, он будет извлечён и сохранён в массиве phoneNumbers.</p><h2>Ещё несколько советов по использованию регулярок</h2><ul><li>Пишите максимально простые регулярные выражения и не усложняйте код без необходимости. Чем проще регулярное выражение, тем быстрее и эффективнее оно будет работать.</li></ul><ul><li>Тестируйте регулярные выражения перед использованием. Это позволит выявить возможные ошибки или проблемы в выражении и сделать необходимые исправления. Используйте онлайн-сервисы, такие как regex101.com, regexr.com, regexplanet.com и подобные.</li><li>Не злоупотребляйте использованием регулярок. Иногда другие простые методы могут оказаться более эффективными и лёгкими в понимании.</li></ul><ul><li>Используйте комментарии и форматирование кода, чтобы облегчить его чтение и обслуживание. Регулярные выражения могут быть очень сложными для понимания.</li><li>Используйте библиотеки. Есть множество библиотек уже готовых шаблонов — часто этого достаточно для решения задачи.</li></ul><h2>Заключение</h2><p>Мы разобрали основные моменты и области применения регулярок. Надеемся, что гайд поможет упростить работу, перестать бояться сложных на вид шаблонов и решать с их помощью даже самые нетривиальные задачи.</p>]]></content:encoded>
    </item>
    <item>
      <title>Декораторы в Python: понять и полюбить</title>
      <link>https://tproger.ru/translations/demystifying-decorators-in-python</link>
      <comments>https://tproger.ru/translations/demystifying-decorators-in-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Картофельный Повелитель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/demystifying-decorators-in-python</guid>
      <description><![CDATA[<p>Разбираем декораторы в Python от основ до продвинутых приёмов: функции высших порядков, @-синтаксис, аргументы, мемоизация. Читайте с примерами кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/demystifying-decorators-in-python">Декораторы в Python: понять и полюбить</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 22 Mar 2023 10:12:27 GMT</pubDate>
      <content:encoded><![CDATA[<p>• Декоратор — это функция, которая оборачивает другую функцию, расширяя её поведение без изменения исходного кода
• В Python функции являются объектами первого класса: их можно передавать как аргументы, возвращать и хранить в переменных
• Синтаксис @decorator — это сахар для конструкции func = decorator(func)
• Декораторы с аргументами используют дополнительный уровень вложенности: внешняя функция принимает параметры и возвращает сам декоратор
• Для декорирования подходит любой вызываемый объект с методом __call__, не только функции</p><p>Декораторы — один из самых полезных инструментов в <a href="https://tproger.ru/articles/python-roadmap">Python</a>, однако новичкам они могут показаться непонятными. Возможно, вы уже встречались с ними, например, при работе с Flask, но не хотели особо вникать в суть их работы. Эта статья поможет вам понять, чем являются декораторы и как они работают. Эта статья — часть нашего <a href="https://tproger.ru/articles/python--polnyj-putevoditel-dlya-razrabotchika">полного путеводителя по Python</a>.</p><h2>Что такое декоратор?</h2><p>Новичкам декораторы могут показаться неудобными и непонятными, потому что они выходят за рамки «обычного» процедурного программирования как в Си, где вы объявляете функции, содержащие блоки кода, и вызываете их. То же касается и объектно-ориентированного программирования, где вы определяете классы и создаёте на их основе объекты. Декораторы не принадлежат ни одной из этих парадигм и исходят из области функционального программирования. Однако не будем забегать вперёд, разберёмся со всем по порядку.</p><p>Декоратор — это функция, которая позволяет обернуть другую функцию для расширения её функциональности без непосредственного изменения её кода. Вот почему декораторы можно рассматривать как практику метапрограммирования, когда программы могут работать с другими программами как со своими данными. Чтобы понять, как это работает, сначала разберёмся в работе функций в Python.</p><h2>Как работают функции</h2><p>Все мы знаем, что такое функции, не так ли? Не будьте столь уверены в этом. У функций Python есть определённые аспекты, с которыми мы нечасто имеем дело, и, как следствие, они забываются. Давайте проясним, что такое функции и как они представлены в Python.</p><h3>Функции как процедуры</h3><p>С этим аспектом функций мы знакомы лучше всего. Процедура — это именованная последовательность вычислительных шагов. Любую процедуру можно вызвать в любом месте программы, в том числе внутри другой процедуры или даже самой себя. По этой части больше нечего сказать, поэтому переходим к следующему аспекту функций в Python.</p><h3>Функции как объекты первого класса</h3><p>В Python всё является объектом, а не только объекты, которые вы создаёте из классов. Подробнее об объектах и <a href="https://tproger.ru/translations/python-data-types">типах данных в Python</a> читайте в отдельном гайде. В этом смысле он (Python) полностью соответствует идеям объектно-ориентированного программирования. Это значит, что в Python всё это — объекты:</p><ul><li>числа;</li><li><a href="https://pythonist.ru/stroki-v-python-polnoe-rukovodstvo/">строки</a>;</li><li>классы (да, даже классы!);</li><li>функции (то, что нас интересует).</li></ul><p>Тот факт, что всё является объектами, открывает перед нами множество возможностей. Мы можем сохранять функции в переменные, передавать их в качестве аргументов и возвращать из других функций. Можно даже определить одну функцию внутри другой. Иными словами, функции — это объекты первого класса. Чтобы лучше понять, как Python находит и подключает код, рекомендуем изучить <a href="https://tproger.ru/translations/guide-into-python-imports">систему импортов</a>. Из определения в <a href="https://ru.wikipedia.org/wiki/Объект_первого_класса">Википедии</a>:</p><p>Объектами первого класса в контексте конкретного языка программирования называются элементы, с которыми можно делать всё то же, что и с любым другим объектом: передавать как параметр, возвращать из функции и присваивать переменной.</p><p>И тут в дело вступает функциональное программирование, а вместе с ним — декораторы.</p><h3>Функциональное программирование — функции высших порядков</h3><p>В Python используются некоторые концепции из функциональных языков вроде Haskell и OCaml. Пропустим формальное определение функционального языка и перейдём к двум его характеристикам, свойственным Python:</p><ul><li>функции являются объектами первого класса;</li><li>следовательно, язык поддерживает функции высших порядков.</li></ul><p>Функциональному программированию присущи и другие свойства вроде отсутствия побочных эффектов, но мы здесь не за этим. Лучше сконцентрируемся на другом — функциях высших порядков. Что есть функция высшего порядка? Снова обратимся к <a href="https://ru.wikipedia.org/wiki/Функциональное_программирование#Функции_высших_порядков">Википедии</a>:</p><p>Функции высших порядков — это такие функции, которые могут принимать в качестве аргументов и возвращать другие функции.</p><p>Если вы знакомы с основами высшей математики, то вы уже знаете некоторые математические функции высших порядков порядка вроде дифференциального оператора d/dx. Он принимает на входе функцию и возвращает другую функцию, производную от исходной. Функции высших порядков в программировании работают точно так же — они либо принимают функцию(и) на входе и/или возвращают функцию(и).</p><h3>Пара примеров</h3><p>Раз уж мы ознакомились со всеми аспектами функций в Python, давайте продемонстрируем их в коде:</p><p>Здесь мы определили простую функцию. Из фрагмента кода далее вы увидите, что эта функция, как и классы с числами, является объектом в Python:</p><p>Как вы заметили, функция hello_world принадлежит типу &lt;class 'function'&gt;. Это означает, что она является объектом класса function. Кроме того, класс, который мы определили, принадлежит классу type. От этого всего голова может пойти кругом, но чуть поигравшись с функцией type вы со всем разберётесь.</p><p>Теперь давайте посмотрим на функции в качестве объектов первого класса.</p><p>Мы можем хранить функции в переменных:</p><p>Определять функции внутри других функций:</p><p>Передавать функции в качестве аргументов и возвращать их из других функций:</p><p>Из этих примеров должно стать понятно, насколько функции в Python гибкие. С учётом этого можно переходить к обсуждению декораторов.</p><h2>Как работают декораторы</h2><p>Повторим определение декоратора:</p><p>Декоратор — это функция, которая позволяет обернуть другую функцию для расширения её функциональности без непосредственного изменения её кода.</p><p>Раз мы знаем, как работают функции высших порядков, теперь мы можем понять как работают декораторы. Сначала посмотрим на пример декоратора:</p><p>Здесь decorator_function() является функцией-декоратором. Как вы могли заметить, она является функцией высшего порядка, так как принимает функцию в качестве аргумента, а также возвращает функцию. Внутри decorator_function() мы определили другую функцию, обёртку, так сказать, которая обёртывает функцию-аргумент и затем изменяет её поведение. Декоратор возвращает эту обёртку. Теперь посмотрим на декоратор в действии:</p><p>Магия, не иначе! Просто добавив @decorator_function перед определением функции hello_world(), мы модифицировали её поведение. Однако как вы уже могли догадаться, выражение с @ является всего лишь синтаксическим сахаром для hello_world = decorator_function(hello_world).</p><p>Иными словами, выражение @decorator_function вызывает decorator_function() с hello_world в качестве аргумента и присваивает имени hello_world возвращаемую функцию.</p><p>И хотя этот декоратор мог вызвать вау-эффект, он не очень полезный. Давайте взглянем на другие, более полезные (наверное):</p><p>Здесь мы создаём декоратор, замеряющий время выполнения функции. Далее мы используем его на функции, которая делает GET-запрос к главной странице Google. Чтобы измерить скорость, мы сначала сохраняем время перед выполнением обёрнутой функции, выполняем её, снова сохраняем текущее время и вычитаем из него начальное.</p><p>После выполнения кода получаем примерно такой результат:</p><p>К этому моменту вы, наверное, начали осознавать, насколько полезными могут быть декораторы. Они расширяют возможности функции без редактирования её кода и являются гибким инструментом для изменения чего угодно.</p><h3>Используем аргументы и возвращаем значения</h3><p>В приведённых выше примерах декораторы ничего не принимали и не возвращали. Модифицируем наш декоратор для измерения времени выполнения:</p><p>Вывод после выполнения:</p><p>Как вы видите, аргументы декорируемой функции передаются функции-обёртке, после чего с ними можно делать что угодно. Можно изменять аргументы и затем передавать их декорируемой функции, а можно оставить их как есть или вовсе забыть про них и передать что-нибудь совсем другое. То же касается возвращаемого из декорируемой функции значения, с ним тоже можно делать что угодно.</p><h3>Декораторы с аргументами</h3><p>Мы также можем создавать декораторы, которые принимают аргументы. Посмотрим на пример:</p><p>Здесь мы модифицировали наш старый декоратор таким образом, чтобы он выполнял декорируемую функцию iters раз, а затем выводил среднее время выполнения. Однако чтобы добиться этого, пришлось воспользоваться природой функций в Python.</p><p>Функция benchmark() на первый взгляд может показаться декоратором, но на самом деле таковым не является. Это обычная функция, которая принимает аргумент iters, а затем возвращает декоратор. В свою очередь, он декорирует функцию fetch_webpage(). Поэтому мы использовали не выражение @benchmark, а @benchmark(iters=10) — это означает, что тут вызывается функция benchmark() (функция со скобками после неё обозначает вызов функции), после чего она возвращает сам декоратор.</p><p>Да, это может быть действительно сложно уместить в голове, поэтому держите правило:</p><p>Декоратор принимает функцию в качестве аргумента и возвращает функцию.</p><p>В нашем примере benchmark() не удовлетворяет этому условию, так как она не принимает функцию в качестве аргумента. В то время как функция actual_decorator(), которая возвращается benchmark(), является декоратором.</p><h3>Объекты-декораторы</h3><p>Напоследок стоит упомянуть, что не только функции, а любые вызываемые объекты могут быть декоратором. Экземпляры классов/объекты с методом __call__() тоже можно вызывать, поэтому их можно использовать в качестве декораторов. Эту функциональность можно использовать для создания декораторов, хранящих какое-то состояние. Например, вот декоратор для мемоизации:</p><p>Само собой, этот декоратор нужен в основном в демонстрационных целях, в реальном приложении для подобного кеширования стоит использовать <a href="https://docs.python.org/3.7/library/functools.html#functools.lru_cache">functools.lru_cache</a>.</p><h2>P.S.</h2><p>Тут будут перечислены некоторые важные вещи, которые не были затронуты в статье или были затронуты вскользь. Вам может показаться, что они расходятся с тем, что было написано в статье до этого, но на самом деле это не так.</p><ul><li>Декораторы не обязательно должны быть функциями, это может быть любой вызываемый объект.</li><li>Декораторы не обязаны возвращать функции, они могут возвращать что угодно. Но обычно мы хотим, чтобы декоратор вернул объект того же типа, что и декорируемый объект. Пример:&gt;&gt;&gt; def decorator(func):...     return 'sumit'...&gt;&gt;&gt; @decorator... def hello_world():...     print('hello world')...&gt;&gt;&gt; hello_world'sumit'</li><li>Также декораторы могут принимать в качестве аргументов не только функции. <a href="https://github.com/hchasestevens/hchasestevens.github.io/blob/master/notebooks/the-decorators-they-wont-tell-you-about.ipynb">Здесь</a> можно почитать об этом подробнее.</li><li>Необходимость в декораторах может быть неочевидной до написания библиотеки. Поэтому, если декораторы кажутся вам бесполезными, посмотрите на них с точки зрения разработчика библиотеки. Хорошим примером является декоратор представления в Flask.</li><li>Также стоит обратить внимание на <a href="https://docs.python.org/3.7/library/functools.html#functools.wraps">functools.wraps()</a> — функцию, которая помогает сделать декорируемую функцию похожей на исходную, делая такие вещи, как сохранение doctstring исходной функции.</li></ul><h2>Часто задаваемые вопросы</h2><h3>Чем декоратор отличается от обычной функции?</h3><p>Декоратор — это функция высшего порядка, которая принимает другую функцию как аргумент и возвращает новую функцию с расширенным поведением. Обычная функция просто выполняет свой код. Декоратор же оборачивает чужой код, добавляя логику до и после его вызова, не изменяя исходную функцию.</p><h3>Можно ли применить несколько декораторов к одной функции?</h3><p>Да, декораторы можно комбинировать, записав их друг над другом. Они применяются снизу вверх: ближайший к функции декоратор оборачивает её первым, а верхний — последним. Например, @logger над @benchmark означает, что сначала функция обёрнется в benchmark, а затем результат — в logger.</p><h3>Когда стоит использовать декораторы, а когда — нет?</h3><p>Декораторы идеально подходят для сквозной функциональности: логирование, кеширование, проверка прав доступа, замер времени. Не стоит использовать их, если логика обёртки тесно связана с конкретной функцией — в таком случае проще изменить саму функцию. Также избегайте глубокой вложенности декораторов: если их больше трёх, код становится трудночитаемым.</p><h3>Зачем нужен functools.wraps?</h3><p>Без functools.wraps декорированная функция теряет своё имя, docstring и другие атрибуты — вместо них отображаются данные обёртки. Декоратор @functools.wraps(func) копирует метаданные исходной функции в обёртку, что упрощает отладку и делает help() полезным для декорированных функций.</p><h2>Заключение</h2><p>Надеемся, эта статья помогла вам понять, какая «магия» лежит в основе работы декораторов. Если нужно вспомнить основы — наш <a href="https://tproger.ru/translations/python-data-types">гайд по типам данных в Python</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Что делает интеграционный системный аналитик в банке</title>
      <link>https://tproger.ru/articles/chto-delaet-integracionnyj-sistemnyj-analitik-v-banke</link>
      <comments>https://tproger.ru/articles/chto-delaet-integracionnyj-sistemnyj-analitik-v-banke?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Виктория Овсянникова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/chto-delaet-integracionnyj-sistemnyj-analitik-v-banke</guid>
      <description><![CDATA[<p>Интеграционный системный аналитик в банке работает с интеграциями, использует специальные инструменты и учитывает требования компании к опыту соискателей.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/chto-delaet-integracionnyj-sistemnyj-analitik-v-banke">Что делает интеграционный системный аналитик в банке</a>»</p>]]></description>
      <category><![CDATA[Карьера]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 21 Mar 2023 08:10:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>В каждом из направлений разработки в Ренессанс Банке работает своя группа системных аналитиков. Соответственно, их задачи, обязанности и стек технологий будут отличаться. Даниил Пронин — руководитель группы системного анализа направления интеграции — рассказал, чем занимается интеграционный системный аналитик в банке, какими инструментами он пользуется, а также поделился, что ждут в компании от соискателей. Бонусом — несколько советов о том, как готовиться к собеседованию на эту позицию.</p><p><a href="https://tproger.ru/#1">Обязанности интеграционного системного аналитика</a><br /><a href="https://tproger.ru/#2">Хард скилы интеграционного системного аналитика</a><br /><a href="https://tproger.ru/#3">Инструменты системного аналитика</a><br /><a href="https://tproger.ru/#4">Софт скилы системного аналитика</a><br /><a href="https://tproger.ru/#5">Советы при подготовке к собеседованиям</a></p><h2>Обязанности интеграционного системного аналитика</h2><p>Системный аналитик — это переводчик с бизнес-языка на технический.</p><p>Бизнес-анализ прорабатывает всю процессную часть исходя из предпочтений заказчика. Например, нужно, чтобы клиент мог оформить карту. Бизнес-аналитики детализируют процесс, декомпозируют требования с описанием тех процессов, которые будут затронуты и изменены. А дальше уже системные аналитики описывают, какие изменения на стороне системы требуются, чтобы этот бизнес-процесс прошёл.</p><p>Чаще системный аналитик принимает требования от бизнес-аналитика. Наши системные аналитики изучают предоставленные документы (BRF, заключения бизнес-анализа), уточняют требования и согласуют их. Бывают случаи, когда требования даёт сразу заказчик, то есть бизнес. Тогда нужно в том числе провести небольшой бизнес-анализ.</p><p>Также аналитику на согласование приходят архитектурные заключения (для небольших доработок) и архитектурные решения от архитекторов. Обычно они описывают скоуп систем, которые изменяются, и появление новых и изменение имеющихся взаимодействий между старыми системами. Когда в интеграционном направлении появляется новый процесс, новая связь между интеграционным слоем и набором систем, мы предметно её прорабатываем.</p><p>Системный аналитик формирует спецификации на информационные системы. В случае интеграции — как должен осуществляться информационный обмен, какой атрибутивный состав. То есть какие поля принимаются на вход, какие отдаются на выход, какие преобразования присутствуют. Сервис может не только отправлять информацию дальше, но и выступать в роли вычислительного — сам производить расчёты на основе формул. Для этого нужно определить алгоритмы и необходимые функции. Когда вся документация и требования к компоненту готовы, системный аналитик передаёт их разработчикам на реализацию.</p><p>Для части интеграций мы используем корпоративную шину (IBM ESB). В планах полностью отказаться от неё, однако этот процесс трудоёмкий и надо позаботиться о том, чтобы наши потребители не сильно страдали от перехода на новую архитектуру. Сейчас наше целевое решение в части интеграционных взаимодействий — это микросервисная архитектура с использованием REST API и брокеров сообщений (Kafka, IBM MQ, который потенциально будет заменён на ActiveMQ).</p><p>Первым значимым результатом работы с микросервисами является контракт сервиса. Контракт — это описанный формат взаимодействия, который включает в себя доступные методы для вызова. По сути, инструкция для пользователя. Ему неважно, что происходит внутри сервиса, главное — показать, что он может передать на вход и что получить на выход. Например, ввести фамилию, имя, отчество, дату рождения и паспорт и получить идентификатор клиента.</p><p>Во многих организациях подход code first. Аналитики объясняют, что должно приходить на вход и выход, и передают эту информацию разработке в виде оформленной спецификации, либо в виде ТЗ. Дальше разработчики реализуют модель данных на уровне кода, алгоритмов, и на её основе автоматически генерируется контракт сервиса. Это Swagger-контракт или API-спецификация.</p><p>У нас же системный аналитик сначала прорабатывает контракт — договаривается с потребителями, как к нам обращаться. Мы используем этот подход потому, что максимально быстро у потребителя появляется вся необходимая информация для разработки своей части взаимодействия. К тому же контракт, описанный по OpenAPI-спецификации, позволяет разработчикам сгенерировать часть кода автоматически.</p><p>Есть два способа описать Swagger-контракт: JSON-формат, либо YAML-разметка. Мы используем последнюю. В YAML-разметке мы описываем, что получится в ответ на обращение, все ограничения, и отдаём потребителям. Благодаря этому они понимают, как им работать с сервисом, и могут начать разработку. То есть им не надо ждать, пока мы напишем ТЗ и код, так как в code first контракта без кода не получится.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/image6-2.png" alt="" /></figure><p>После того как контракт готов, мы приступаем к написанию спецификации — паспорта сервиса. Здесь описываются доступные функции сервиса и алгоритмы, валидации, альтернативные сценарии, последовательность обработки данных и произведение вычислений. Либо, если сервис композитный, включающий набор вызовов других систем, выстраивается последовательность этих вызовов, условия переходов, ошибок, повторных вызовов внутри. Уже на основе этого документа разработчик может закодить внутреннюю логику.</p><p>Описание сервиса дополняется UML-диаграммой последовательности. Мы визуализируем работу алгоритмов сервиса, включающих как внутренние вычисления, так и обращение к другим системам и сервисам. Это как раз удобно с композитными сервисами, потому что наглядно видно всех участников процесса.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/image2-2.png" alt="" /></figure><h2>Хард скилы интеграционного системного аналитика</h2><p>Чтобы всё это делать, системный аналитик должен разбираться в соответствующем стеке. У соискателей, которые приходят на эту должность, мы спрашиваем такие знания.</p><h3>Микросервисы</h3><p>Большая часть наших технологий — это интеграции на микросервисах. Соответственно, необходимо разбираться в видах архитектур и знать, что такое монолит и микросервисы.</p><p>Наши микросервисы реализуются на Java Spring Boot, развёртываются в OpenShift и публикуются на платформе управления API под названием IBM API Connect.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/image5-2.png" alt="" /></figure><p>Также важно уметь использовать средства просмотра логов. Мы используем Kibana, а также Zipkin для трассировки цепочки вызовов.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/image3-2.png" alt="" /></figure><h3>ESB</h3><p>Это промежуточное ПО, которое представляет собой большой транспортный пересадочный узел для входящих и исходящих потоков. То есть такой хаб, на котором публикуются различные сервисы. Обращение к ним происходит через SOAP, MQ, JMS и т. п. В SOAP-протоколе, например, описывается строго типизированный формат интеграции, то, как должны выглядеть входные и выходные сообщения. Они валидируются по XSD-схеме, при этом используется XML-разметка.</p><p>Через шину потребители могут обращаться к конечным системам. Она преобразует входное сообщение в другую форму: вызов хранимой процедуры или другие интерфейсы.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/image1-2.png" alt="" /></figure><figure><img src="https://media.tproger.ru/uploads/2023/03/image7-2.png" alt="" /></figure><h3>Виды интеграций</h3><p>Мы задаём стандартные вопросы про виды интеграций и ждём развёрнутый ответ с как можно большим количеством вариантов. Дальше предметно спрашиваем про те виды, которые у нас используются:</p><p>— REST API. В чём отличие REST API от SOAP-сервисов. Как правило, все дают базовый ответ, что REST — это архитектурный стиль, а SOAP — это протокол.</p><p>Нужно знать, какие методы используются в REST-сервисе. Многие впадают в ступор, что подразумевается под ними. Это HTTP-глаголы, поскольку REST базируется на HTTP-протоколе. Многие ограничиваются двумя методами — GET и POST, но на деле их больше. Важно понимать, чем они отличаются, если не приходилось с ними работать. Также ожидаем, что кандидат представляет, что такое REST-запрос: где можно передать входные параметры, где техническую информацию, что такое статус-коды, какие категории у них бывают.</p><p>— SOAP-сервисы. У нас они остались на шине, но иногда приходится обращаться в мастер-системы по SOAP-протоколу. Важно понимать, из каких артефактов состоит сервис, например, XSD, WSDL.</p><h3>Очереди и брокеры сообщений</h3><p>У нас это IBM MQ и Kafka, но важен опыт работы с любым брокером сообщений, так как их концепции похожи. Если соискатель работал с этими инструментами, то спрашиваем, какая разница в построении очередей. Нужно описать, как выглядит взаимодействие — кто подписчик, кто поставщик сообщений. Как брокер себя ведёт: толкает сообщения, либо просто хранит, пока их сам не прочитает подписчик, сколько хранит, удаляет ли. Нам важно, чтобы человек понимал принцип работы системы. Предметно знать необязательно, потому что всё-таки аналитики не проверяют средство просмотра и администрирования очередей или топиков.</p><p>На собеседовании мы также даём практическую задачу на проектирование сервиса, который будет возвращать информацию. Мы обсуждаем, например, авторизацию, защиту информации, разграничения доступа и параметризацию сервиса, нагрузку на сервис. Это близко к архитектуре, но у нас аналитики зачастую берут на себя роль solution-архитекторов, которые на уровне конкретной системы и компонентов принимают решение о реализации.</p><h2>Инструменты системного аналитика</h2><ul><li>Спецификации. YAML-разметка, Swagger и OpenAPI.</li><li>Для документации мы используем не стандартные страницы в Confluence или документы Word, а разметку AsciiDoc. Она лежит рядом с кодом, и в Confluence мы её подтягиваем через плагин.</li></ul><figure><img src="https://media.tproger.ru/uploads/2023/03/image4-2.png" alt="" /></figure><ul><li>Мы работаем в инструменте PlantUML, он позволяет текстом описывать UML-диаграммы, которые затем верстаются в картинку. Всю документацию мы кодим в различных разметках и храним рядом с кодом. В принципе, работа с документацией для интеграционного аналитика в Ренессансе — это скорее кодинг, чем работа в графических и текстовых редакторах.</li><li>Gitlab и Jira.</li><li>Средства для отладки тестирования: Postman, SoapUI для отладки сервисов или автоматизации вызовов. Иногда аналитику необходимо сымитировать вызов. Так он самостоятельно и быстро поймёт логику работы сервиса, входные и выходные параметры и сможет решить, нужны ли доработки. Если бы он лазил в документацию (которая не всегда может быть корректно составлена), то на это бы ушло больше времени, чем предметно что-то вызвать и смотреть на результат.</li><li>Базы данных. Как правило, системный аналитик редко ходит в БД. Но, тем не менее нужно уметь составлять базовый SQL-запрос с выборкой.</li><li>СМЭВ — это контурное взаимодействие с различными государственными ведомствами. С его помощью можно получить актуальную информацию, соответствующую законодательству, не спрашивая клиента.</li></ul><h2>Софт скилы системного аналитика</h2><p>Навыки общения будут универсальными для любого системного аналитика. Без софт скилов здесь не обойтись, потому что аналитик — связующее звено между бизнесом и разработкой. Поэтому ему важны такие навыки:</p><ul><li>Быть коммуникабельным. Регулярно участвовать во внутренних митингах с разработчиками. Также общаться с бизнес-аналитиками, архитекторами и отстаивать свою позицию. Поскольку не всегда те решения, которые предлагают общебанковские архитекторы, могут быть реализованы.</li><li>Не стесняться задавать вопросы. Самый плохой вопрос — незаданный. Нужно включаться в диалог, чтобы собрать максимум требований и закрыть максимум вопросов.</li><li>Участвовать в оценках. Мы оцениваем работу в человеко-днях, также у нас введён процент риска по ней. Чем больше неопределённости, например, непроработанных требований со стороны бизнес-анализа, отсутствие адекватной архитектуры, тем больше риска мы закладываем.</li></ul><p>В силах аналитика закрыть неопределённость до оценки, как раз общаясь с бизнес-аналитиками и архитектором. Чем точнее оценка, тем точнее можно прогнозировать скорость выполнения задачи и составить адекватный бэклог на предстоящий релиз. К тому же разработчики не будут простаивать, выходить в выходные или задерживаться, чтобы только успеть закрыть задачу.</p><ul><li>Уметь собирать требования различными способами. Можно использовать различные приёмы для того, чтобы снижать степень неопределённости: брейншторминг или опросы. Участвовать также не только в опросе бизнес-аналитики, но и напрямую с бизнесом.</li><li>Быть инициативным. В команде всегда ценны те люди, которые готовы помогать коллегам, включаться, чтобы причесать общий бэклог, забрать оттуда для себя задачу.</li><li>Не стесняться границ, зон ответственности. Бывают случаи, когда участники команды разработки коммуницируют с коллегами только через лидов. Это порочная практика, поскольку тратится время на эту лишнюю цепочку.</li><li>Оставаться ответственным. Не только за свои задачи, но и перед командой.</li></ul><p>Следствие всех этих софт скилов — синергия между участниками команды разработки, которая приводит к комфортному и продуктивному рабочему процессу.</p><h2>Советы при подготовке к собеседованиям</h2><p>Больше рассказывайте о предыдущем опыте и раскрывайте суть решаемых задач, проектов и стек технологий, с которыми работали. Часто встречаются резюме, в которых написано только «работа с интеграцией», или «участие в проектируемых интеграциях». Какие это были интеграции, насколько они были сложными, примеры решаемых задач — этого нет. Это усложняет процедуру подбора и приходится вслепую звать ребят на собеседование, что превращается в подобие лотереи.</p><p>Освежите в памяти базовые понятия. Часто соискатели не готовятся, когда идут на собеседование. Это выглядит логично, потому что человек идёт показать свои знания и опыт. Но чем шире у человека кругозор, тем креативнее решения он придумывает.</p>]]></content:encoded>
    </item>
    <item>
      <title>Менеджмент зависимостей в Javascript — управляем хаосом</title>
      <link>https://tproger.ru/articles/menedzhment-zavisimostej-v-javascript-upravljaem-haosom</link>
      <comments>https://tproger.ru/articles/menedzhment-zavisimostej-v-javascript-upravljaem-haosom?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Мария Кривоченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/menedzhment-zavisimostej-v-javascript-upravljaem-haosom</guid>
      <description><![CDATA[<p>В статье подробно поговорим про принципы управления зависимостями в Javascript и обсудим существующие пакетные менеджеры.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/menedzhment-zavisimostej-v-javascript-upravljaem-haosom">Менеджмент зависимостей в Javascript — управляем хаосом</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 01 Mar 2023 13:48:57 GMT</pubDate>
      <content:encoded><![CDATA[<p>Для многих разработчиков процесс установки зависимостей представляет собой магию, которая происходит при выполнении npm install. Имеющим у себя в проекте большое количество зависимостей, и тем, кто разрабатывает эти самые зависимости, публикуя в виде NPM-пакетов, будет полезно разобраться в принципах работы этой магии, чтобы сделать установку более эффективной и не создавать проблемы коллегам.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/install-easy.drawio.png" alt="" /></figure><p>Внешние зависимости в том или ином виде существовали в JavaScript практически с самого появления, и инструменты, управляющие ими, постепенно развивались. Чтобы разобраться, зачем они существуют и как именно решают задачи разработчиков, предлагаю проследить историю их развития с самого начала</p><ol><li><a href="https://tproger.ru/#part1">Как мы делали раньше.</a></li><li><a href="https://tproger.ru/#part2">Bower.</a></li><li><a href="https://tproger.ru/#part3">Версионирование.</a></li><li><a href="https://tproger.ru/#part4">Транзитивные зависимости.</a></li><li><a href="https://tproger.ru/#part5">Разрешение зависимостей.</a></li><li><a href="https://tproger.ru/#part6">Зависимости для локальной разработки.</a></li><li><a href="https://tproger.ru/#part7">Плоская модель установки.</a></li><li><a href="https://tproger.ru/#part8">Ручное разрешение конфликтов.</a></li><li><a href="https://tproger.ru/#part9">NPM.</a></li><li><a href="https://tproger.ru/#part10">Конфигурация NPM.</a></li><li><a href="https://tproger.ru/#part11">Авторизация в NPM.</a></li><li><a href="https://tproger.ru/#part12">Публикация пакетов.</a></li><li><a href="https://tproger.ru/#part13">Необязательные зависимости.</a></li><li><a>«Плагины» для пакетов.</a></li><li><a href="https://tproger.ru/#part15">Переопределение версий.</a></li><li><a href="https://tproger.ru/#part16">Воспроизводимость.</a></li><li><a href="https://tproger.ru/#part17">Yarn.</a></li><li><a href="https://tproger.ru/#part18">Собственный реестр пакетов.</a></li><li><a href="https://tproger.ru/#part19">Связывание пакетов локально.</a></li><li><a href="https://tproger.ru/#part20">Фантомные зависимости.</a></li><li><a href="https://tproger.ru/#part21">Структура зависимостей.</a></li><li><a href="https://tproger.ru/#part22">PNPM.</a></li><li><a href="https://tproger.ru/#part23">Будущее менеджмента зависимостей.</a></li></ol><h2>Как мы делали раньше</h2><p>До появления Node.js и NPM подключение библиотек к сайту осуществлялось с помощью тега script прямо в HTML:</p><p>&lt;script src=&lt;URL-библиотеки&gt;"&gt;&lt;/script&gt;</p><p>Чтобы это работало, нужно, чтобы по адресу &lt;URL-библиотеки&gt; был размещён .js файл. Сделать это можно было двумя способами:</p><ol><li>Воспользоваться CDN, на котором уже размещён код библиотеки: &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"&gt;&lt;/script&gt;.<br />В этом случае у нас не было контроля над тем, что на самом деле получал пользователь, мы делегируем всю работу провайдеру CDN и доверяем ему.<br />В качестве бонуса пользователи получали кросс-доменный кеш и если, например, они уже загрузили jQuery на другом сайте, при открытии нашего сайта они получали её из кеша вместо того, чтобы загружать его с CDN заново, так как URL совпадал. К сожалению, этот механизм <a href="https://www.stefanjudis.com/notes/say-goodbye-to-resource-caching-across-sites-and-domains/">более не актуален</a>.</li><li>Скачать код библиотеки и самостоятельно положить его, например, в директорию vendors: &lt;script src="vendors/jquery-3.6.1.min.js"&gt;&lt;/script&gt;.<br />Так мы получали полный контроль над кодом библиотек и способом его получения пользователями. И, при необходимости, могли производить над ними дополнительные преобразования (например, минифицировать).</li></ol><p>Второй способ становился всё более актуальным, но с ростом экосистемы JavaScript росло и количество библиотек, подключаемых к сайту. Скачивать все библиотеки вручную и хранить их в репозитории с кодом становилось накладно, поэтому появился инструмент, именуемый <a href="https://bower.io/">Bower</a>.</p><h2>Bower</h2><p><b>Bower </b>— пакетный менеджер. Его основная задача в автоматизации загрузки различных компонентов приложения со сторонних ресурсов. В репозитории с кодом мы в таком случае храним только информацию о том, что ему нужно скачать, в файле bower.json:</p><p>(Ничего не напоминает?)</p><p>При выполнении команды bower install Bower устанавливает зависимости, указанные в поле dependencies. У Bower есть свой реестр пакетов, из которого он их и скачивает.</p><h2>Версионирование</h2><p>В bower.json мы указываем не конкретный URL, по которому он должен загрузить библиотеку, а диапазон версий согласно <a href="https://semver.org/lang/ru/">SemVer</a>. Фактически это — реализация <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%B8%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B8_%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9">принципа инверсии зависимостей</a>: проект зависит не от конкретного кода, хранящегося на удалённом сервере, а от абстракции в виде диапазона версий. За выбор соответствующей версии и загрузку кода отвечает пакетный менеджер.</p><p>SemVer гарантирует, что при выборе любой версии из указанного диапазона проект будет работать.</p><h3>Как это работает?</h3><p>Например, мы хотим использовать в своём проекте библиотеку React. Открываем документацию и изучаем API библиотеки, обращая внимание на то, для какой версии библиотеки написана документация (например, 16.1.0).</p><p>Первый разряд версии означает изменения API, ломающие обратную совместимость (мажорные), второй — обратно совместимые изменения (минорные). Соответственно, минимальная версия, которая нам подойдёт для использования всего API из документации, — 16.1.0, максимальная, которую мы можем использовать, не опасаясь что проект перестанет работать, — 17.0.0.</p><p>Записать такой диапазон можно в виде &gt;=16.1.0 &lt;17.0.0. Для более краткой записи существуют модификаторы диапазона версий, с помощью которых мы можем обозначить тот же самый диапазон как ^16.1.0.</p><p>На практике всё не всегда так радужно, и разработчик библиотеки может случайно выпустить ломающие изменения в минорном обновлении. Поэтому нам приходится внимательно следить за историей обновлений и подбирать диапазон так, чтобы не напороться на проблемы. Либо указывать конкретную версию вместо диапазона.</p><h2>Транзитивные зависимости</h2><p>Bower позволил формализовать и автоматизировать управление зависимостями во фронтенд-разработке. Это подтолкнуло экосистему JavaScript к закономерному росту и, соответственно, усложнению.</p><p>Помимо появления пакетного менеджера, возникали <a href="https://tproger.ru/translations/js-modules-formats-loaders-builders/">различные модульные системы</a>. Это в совокупности позволило разработчикам библиотек использовать другие библиотеки, тем самым снизив уровень копипасты и, теоретически, объём кода, загружаемого пользователем.</p><p>Зависимости зависимостей проекта называются транзитивными.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/transitive-dependencies.drawio.png" alt="" /></figure><h2>Разрешение зависимостей</h2><p>Пакетный менеджер начинает установку с разрешения (resolution) зависимостей. На этом этапе он анализирует зависимости в поле dependencies и подбирает версии библиотек, соответствующие указанным в нём диапазонам.</p><p>Поскольку у загружаемых библиотек могут быть свои зависимости, разрешение зависимостей производится и для них. В результате процесс становится рекурсивным и представляет собой обход дерева, которое постепенно достраивается.</p><h2>Зависимости для локальной разработки</h2><p>Помимо использования библиотек в коде приложения, разработчики пишут автотесты, производят манипуляции с исходным кодом и делают множество других полезных вещей. Чтобы никто не изобретал свой велосипед, для них также используют различные библиотеки.</p><p>Но когда мы добавляем библиотеку в свой проект, мы не хотим вместе с исходным кодом загрузить ещё и тонну инструментов (которые, несомненно, полезны самой библиотеке, но нам могут быть не нужн). Поэтому для экономии дискового пространства пользователей библиотек в bower.json появилось поле devDependencies.</p><p>devDependencies — зависимости, которые пакетный менеджер установит, только если они являются прямыми зависимостями проекта. Транзитивные devDependencies пакетный менеджер игнорирует.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/dev-dependencies-install.drawio.png" alt="" /></figure><h2>Плоская модель установки</h2><p>Bower поддерживает только плоскую модель установки зависимостей, что подразумевает загрузку всех прямых и транзитивных зависимостей в одну директорию. Например, результат установки с Bower будет выглядеть так:</p><figure><img src="https://media.tproger.ru/uploads/2023/02/bower-install-result.drawio.png" alt="" /></figure><p>Такая структура допустима. Но с развитием экосистемы JavaScript количество транзитивных зависимостей быстро растёт, а это рано или поздно неизбежно приводит к конфликтам их версий (они могут возникнуть, если зависимости проекта зависят от разных версий одной и той же библиотеки):</p><figure><img src="https://media.tproger.ru/uploads/2023/02/dependency-version-conflict.drawio.png" alt="" /></figure><p>Bower устанавливает все зависимости в одну директорию и не может установить несколько версий одного и того же пакета. Поэтому разработчику приходится решать такие конфликты вручную, выбирая версию, которая будет использоваться. Это довольно рискованно в случае с несколькими разными мажорными версиями.</p><h2>Ручное разрешение конфликтов</h2><p>Для разрешения подобных конфликтов в bower.json появилось поле <a href="https://github.com/bower/spec/blob/master/json.md#resolutions">resolutions</a>, позволяющее вручную произвести разрешение транзитивной зависимости.</p><p>Тем не менее, выбор одной из нескольких мажорных версий зависимости — не самый лучший вариант. Одна из транзитивных зависимостей с высокой долей вероятности может сломаться. Более безопасно установить обе версии, чего Bower не позволяет.</p><p>Решение этой проблемы нашлось в смежной области — бэкенд-разработке на Node.js. Для платформы был разработан свой пакетный менеджер — <b>NPM</b>.</p><h2>NPM</h2><p>NPM имел nested-модель установки. Она подразумевает, что для каждой зависимости проекта создаётся своя директория node_modules, в которой изолированно хранятся её зависимости. Это позволяет избежать конфликтов версий.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/nested-installation-model.drawio.png" alt="" /></figure><p>Поскольку NPM изначально предназначался для Node.js, все пакеты в нём имели модульный формат CommonJS, который не поддерживается в браузере. Соответственно, использовать их для фронтенда было невозможно. Но с появлением <a href="https://browserify.org/">Browserify</a> (инструмента, собирающего все CommonJS модули в один файл), пост которого впоследствии занял <a href="https://webpack.js.org/">Webpack</a>, проблема была решена, и разработчики постепенно начали переходить с Bower на NPM.</p><p>Для безболезненной миграции с Bower в NPM появился флаг --flat, который меняет модель установки на плоскую.</p><p>Переход на nested-модель установки был не бесплатным. Директория node_modules представляла собой довольно глубокую иерархию пакетов, которая занимала колоссальное количество места на диске. А также могла приводить к проблемам из-за <a href="https://stackoverflow.com/questions/26155135/node-npm-windows-file-paths-are-too-long-to-install-packages">ограничения максимальной длины путей на Windows</a>.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/node-modules-meme.png" alt="" /></figure><p>Для бэкенда это было приемлемо. Но тянуть на сайт так много библиотек, среди которых множество дубликатов, никому не хотелось. Поэтому в NPM 3 появилась новая hoisted-модель установки и механизм дедупликации пакетов.</p><p>Hoisted-модель установки представляет собой нечто среднее между плоской и nested-моделями. В ней пакеты по возможности хранятся в верхней директории node_modules, а вложенности возникают только в случае конфликтов версий.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/hoisted-installation-model.drawio.png" alt="" /></figure><p>Работа модели обеспечивается <a href="https://nodejs.org/api/modules.html#all-together">механизмом разрешения модулей в Node.js</a>. При поиске пакета, указанного в require, Node.js проходит по всем директориям node_modules снизу вверх, то есть «всплывает» (аналогично всплытию переменных в JavaScript). Поэтому модель и называется hoisted.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/node-module-resolution.drawio.png" alt="" /></figure><h2>Конфигурация NPM</h2><p>Управлять тем, как NPM производит различные операции — такие как установка и публикация, — можно с помощью флагов командной строки и файла .npmrc.</p><p>В отличие от многих других конфигурационных файлов (например, .gitignore или .prettierrc), .npmrc не ищется рекурсивно. В общем случае NPM ожидает его только в двух местах: непосредственно в директории проекта и в домашней директории текущего пользователя (~/ для Linux и Mac OS или %homepath% для Windows). Оба файла будут объединены, при этом значения параметров проекта будут иметь приоритет над пользовательскими.</p><p>Чаще всего в .npmrc указывается параметр registry, который отвечает за выбор реестра пакетов. По умолчанию его значение равно https://registry.npmjs.com.</p><p>Можно указать отдельный registry для пакетов определённой организации. Предположим, компания, в которой вы работаете, публикует внутренние пакеты в приватном репозитории с префиксом @my-company/ (например, @my-company/awesome-library). В таком случае содержимое .npmrc будет выглядеть примерно так:</p><h2>Авторизация в NPM</h2><p>Чтобы публиковать пакеты или устанавливать их из приватного репозитория, необходимо авторизоваться в NPM. Это можно сделать с помощью команды npm login. Но я предпочитаю вручную указывать пакеты в .npmrc, так как это более явный способ — и это не сильно сложнее.</p><p>Авторизация с токеном &lt;MY_TOKEN&gt; для npmjs выглядит в .npmrc следующим образом:</p><p>//registry.npmjs.org/:_authToken=&lt;MY_TOKEN&gt;</p><p>Обратите внимание, что // в начале строки не обозначает комментарий. Это обычная часть URL, которая следует после протокола. Но в данном случае протокол не имеет значения, так как авторизация для http и https будет одинаковой.</p><p>Авторизационные данные для репозиториев лучше хранить в .npmrc, находящемся в домашней директории. Так они будут использоваться для всех проектов на вашей машине, и вы точно случайно не закоммитите их в GIT.</p><h2>Публикация пакетов</h2><p>Чтобы сделать свой NPM-пакет доступным для загрузки другими разработчиками, его надо опубликовать в реестре пакетов (registry). Глобальным реестром NPM-пакетов является https://registry.npmjs.com.</p><p>Существуют и другие зеркала: например, <a href="https://registry.yarnpkg.com/">https://registry.yarnpkg.com</a>. Но зачастую они просто проксируют npmjs, который на текущий момент является главным источником истины для JavaScript-пакетов.</p><p>Для публикации пакета существует команда npm publish. Она упаковывает всё содержимое в .tgz-архив — это можно сделать отдельно командой npm pack — и отправляет его в реестр пакетов. По умолчанию в архив попадает всё содержимое проекта. Поэтому размер пакета может оказаться неоправданно большим.</p><p>Если в package.json определено поле files, NPM упакует в архив только указанные в нём файлы и директории. Также можно указать исключения в файле .npmignore. Это работает аналогично тому, как работает .gitignore.</p><p>Предположим, вы собираете свою библиотеку с помощью компилятора Typescript в директорию lib. В этом случае в поле files следует указать ["/lib"]. Далее можно, например, исключить из публикации файлы тестов, добавив в .npmignore строчку *.test.*.</p><p>Некоторые критичные для пакета файлы — например, package.json и README.md — будут опубликованы в любом случае. А некоторые файлы и директории — например, .git или node_modules — никогда не попадут в публикуемый архив. Но с последним есть нюанс.</p><h3>Публикация зависимостей вместе с пакетом</h3><p>Если какие-то из зависимостей публикуемого пакета указаны в виде пути в файловой системе (например, file:../my-awesome-library, что не является хорошей практикой, но тем не менее случается), их можно опубликовать вместе с пакетом, указав их в поле bundledDependencies файла package.json. В таком случае директория node_modules всё же попадёт в публикуемый архив, но в ней останутся только пакеты, указанные в этом поле.</p><p>Когда пользователь установит пакет, у которого есть bundledDependencies, пакетный менеджер возьмёт такие зависимости из архива самого пакета вместо того, чтобы загружать их отдельно.</p><p>Основной сценарий использования bundledDependencies в настоящий момент — дать пользователям возможность загружать утилиты одним файлом. И снизить тем самым время загрузки, так как пакетный менеджер вместо нескольких последовательных запросов на сервер делает всего один. Так делает, например, <a href="https://github.com/npm/cli/blob/latest/package.json">сам NPM</a>.</p><h2>Необязательные зависимости</h2><p>В package.json существует поле optionalDependencies. Оно работает аналогично dependencies, но подразумевает, что пакет в целом может работать и без них. Его можно использовать, например, для каких-либо пакетов, которые нужны не всегда.</p><p>Так, установка Cypress предполагает загрузку около 500 мегабайт, что может негативно сказаться на времени выполнения CI. Если cypress не используется в некоторых окружениях, можно перенести его в секцию optionalDependencies и выполнять установку с флагом --omit=optional (--no-optional в более ранних версиях NPM).</p><p>Ключевое отличие optionalDependencies от dependencies в том, что если установить указанные в этом поле пакеты невозможно, NPM не завершит процесс с ошибкой. А продолжит установку остальных зависимостей в штатном режиме.</p><p>Эта особенность используется авторами NPM-пакетов, содержащих бинарные файлы для разных операционных систем. Например, сборщик esbuild написан на языке Go. При установке его зависимостей пакетный менеджер обратит внимание на поля os (операционная система) и cpu (архитектура процессора) в их package.json и установит только те, что соответствуют текущей ОС.</p><h2>«Плагины» для пакетов</h2><p>Когда мы устанавливаем, например, расширение для Chrome, то ожидаем, что оно будет использовать нашу версию Chrome, а не установит какую-то свою. С NPM-пакетами принцип тот же: плагин должен использовать уже установленную в проекте версию хост-пакета.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/duplicated-plugin-host.drawio.png" alt="" /></figure><p>Обратите внимание, что понятие «плагин» в данном случае довольно широкое и, например, библиотека React-компонентов будет фактически являться плагином для React.</p><p>React в приложении должен быть в единственном экземпляре. И если пакетный менеджер установит для библиотеки компонентов собственный React, то помимо засорения node_modules приложение может перестать работать.</p><p>При этом плагин может быть совместим только с определёнными версиями хост-пакета, поскольку использует его API. А значит — может перестать работать, если этот API будет удалён.</p><p>Реализацией вышеописанного механизма являются peerDependencies.</p><p>При разработке плагина стоит указать его хост-пакет в поле peerDependencies в package.json, чтобы подсказать пакетному менеджеру, как поступать в такой ситуации.</p><p>В таком случае мы не объявляем прямую зависимость библиотеки от хост-пакета, а предъявляем требования к пользователю, обязывая установить пакет самостоятельно.</p><p>NPM 7 и выше автоматически установит недостающие peerDependencies.</p><p>В peerDependencies стоит указывать как можно более широкий диапазон версий, чтобы дать пользователю библиотеки возможность выбора. Если, например, библиотека будет ожидать React ^17.0.0, а пользователь использует React 18.0.0, то возникнет конфликт версий зависимостей. Это приведёт к ошибке установки при использовании NPM 7 и выше.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/peer-dependency-conflict.png" alt="" /></figure><p>Пользователю эта ошибка может быть непонятна и он весьма вероятно попытается установить зависимости с флагом --force или --legacy-peer-deps, как подсказывает сам текст ошибки. Это заставит NPM работать по старинке (как до NPM 7), но может привести к проблемам с дубликатами.</p><h2>Переопределение версий</h2><p>Решить такие проблемы можно по старинке — вручную. Для этого в package.json появилось поле overrides, которое работает подобно полю resolutions из Bower, но поддерживает каскад, как в CSS.</p><p>Это не единственная для NPM аналогия с СSS, команда npm query поддерживает <a href="https://docs.npmjs.com/cli/v8/using-npm/dependency-selectors">СSS-селекторы для анализа дерева зависимостей</a>.</p><p>Похожее поле есть и в других пакетных менеджерах, но, поскольку для package.json нет никакой общей спецификации, работает и называется оно по-разному. Например, в Yarn есть поле resolutions.</p><h3>Опциональный хост</h3><p>Может случиться так, что библиотека достаточно универсальна и способна работать без хост-пакета, но при его наличии станет производить какие-то дополнительные действия. В таком случае мы не хотим заставлять пользователя устанавливать хост-пакет. Но если он его всё-таки установит, нам всё ещё придётся проследить, что его версия будет совместима с нашей библиотекой.</p><p>Для решения этой задачи в package.json существует поле peerDependenciesMeta — оно позволяет предоставить пакетному менеджеру дополнительный контекст для установки зависимостей.</p><p>На текущий момент в peerDependenciesMeta доступен только параметр optional, который говорит о том, что наличие пакета необязательно.</p><p>То есть peerDependenciesMeta.optional является аналогом optionalDependencies, но для peerDependencies.</p><h2>Воспроизводимость</h2><p>Как мы выяснили ранее, пакетный менеджер начинает установку с разрешения зависимостей. В большинстве случаев зависимости пакетов задаются не фиксированными версиями, а диапазонами версий. Это даёт пакетному менеджеру некоторый простор для манёвра, но лишает нас гарантии, что две выполненные друг за другом установки дадут одинаковый результат. Чем это грозит?</p><p>Допустим, мы установили зависимости проекта, реализовали новую фичу, протестировали все возможные сценарии и со спокойной душой отправили код в продакшен. Но на момент установки зависимостей в CI пакетный менеджер обнаружил, что может установить более свежую версию одной из транзитивных зависимостей.</p><p>В результате наш идеально выверенный код неожиданно начинает работать иначе. Возможно, риск напороться на неприятности из-за этого и невелик, но ему будет подвержена каждая установка зависимостей проекта.</p><p>Проблему решил альтернативный пакетный менеджер — <a href="https://classic.yarnpkg.com/en/">Yarn</a>. По завершении установки он генерирует файл yarn.lock, в котором сохраняется результат процесса разрешения зависимостей. А именно — конкретные версии пакетов, которые подобрал пакетный менеджер. Если такой файл есть в проекте, при запуске установки пакетный менеджер проверит, что package.json и yarn.lock соответствуют друг другу и, полностью пропустив этап разрешения зависимостей, загрузит пакеты по списку.</p><p>Такой подход ускоряет установку, ведь сетевых запросов в результате совершается меньше. И, что главное, делает её предсказуемой: две последующие установки точно дадут одинаковый результат, даже на другой машине.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/install-with-lockfile.drawio.png" alt="" /></figure><p>Yarn подтолкнул NPM к развитию, и впоследствии тоже научился генерировать свои <a href="https://docs.npmjs.com/cli/v9/configuring-npm/npm-shrinkwrap-json/">npm-shrinkwrap.json</a> и <a href="https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json">package-lock.json</a> файлы для реализации подобного механизма.</p><h3>npm ci</h3><p>Чтобы добиться действительно предсказуемой установки в автоматизированных средах, важно использовать команду npm ci вместо npm install.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/Snimok-jekrana-2023-03-01-v-15.33.27.png" alt="" /></figure><p>Команда npm ci расшифровывается как clean install, поскольку при её выполнении NPM полностью удаляет директорию node_modules и загружает все зависимости с чистого листа, что также улучшает воспроизводимость.</p><h2>Yarn</h2><p>Помимо вышеописанного механизма фиксации версий зависимостей, Yarn также имеет ряд других преимуществ перед NPM: простота использования, безопасность и скорость.</p><p>Давайте рассмотрим подробнее, в чём именно заключаются эти преимущества.</p><h3>Простота использования</h3><p>Функциональность NPM расширялась постепенно, новые фичи появлялись и его API разрастался, а кардинально менять его и заставлять разработчиков привыкать к новым командам при переходе на новую версию не хотелось. Создавать удобный DX в таких условиях довольно проблематично. Yarn же создавался с нуля, учитывая опыт использования NPM, поэтому его CLI получился несколько более интуитивным и простым в использовании.</p><p>Часто используемые команды стали короче, а команды для CI — читабельнее:</p><ul><li>npm install —&gt; yarn install/yarn;</li><li>npm install --save react —&gt; yarn add react;</li><li>npm ci —&gt; yarn install --frozen lockfile;</li></ul><h3>Безопасность</h3><p>Помимо фиксированных версий зависимостей, в yarn.lock сохраняется также их контрольная сумма (Subresource Integrity) в поле integrity каждого пакета. Она позволяет при установке из локфайла убедиться, что его никто не подменил и инсталлируется то же, что и при генерации локфайла.</p><p>Позже эту информацию стал сохранять и NPM.</p><h3>Скорость</h3><p>Основная причина быстроты Yarn — кэш. Он позволяет создать на своей машине собственный реестр пакетов и в процессе установки заменять сетевой запрос на копирование папок в файловой системе. Меньше сетевых запросов — меньше времени занимает установка.</p><p>При этом кэш можно переиспользовать между всеми проектами, которые вы разрабатываете на своей машине.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/package-manager-cache.drawio.png" alt="" /></figure><h2>Собственный реестр пакетов</h2><p>Чтобы получить контроль над пакетами, которые используются в проектах, большие компании организуют собственные репозитории пакетов, которые могут проксировать глобальный реестр NPM. Обычно для этого используется <a href="https://www.sonatype.com/products/nexus-repository">Nexus</a>.<br />Также собственный репозиторий может использоваться в качестве удалённого кэша, чтобы ускорять установку зависимостей — такой кэш будет находиться ближе к разработчикам.</p><p>Для этого можно воспользоваться более легковесным и опенсорсным аналогом Nexus — <a href="https://verdaccio.org/">Verdaccio</a>. Его, например, можно запустить в <a href="https://hub.docker.com/r/verdaccio/verdaccio">Docker</a> на своей машине, что позволит организовать кэш, переиспользуемый между всеми проектами и доступный для любого пакетного менеджера. Либо установить на сервер, который находится недалеко от вас, чтобы не расходовать ресурсы своей машины.</p><p>Для этого необходимо будет указать в .npmrc адрес сервера с Verdaccio.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/verdaccio.drawio_waifu2x_art_noise3_scale.png" alt="" /></figure><p>С Verdaccio можно и локально попрактиковаться в публикации пакетов, если у вас не было опыта.</p><h2>Связывание пакетов локально</h2><p>При разработке нескольких пакетов в едином монорепозитории возникает задача связать их между собой — чтобы они могли переиспользовать код друг друга. Публиковать их в NPM при каждом изменении и переустанавливать заново весьма накладно. К тому же их код уже находится рядом, и нужно просто локально подключить один пакет к другому. Это можно сделать несколькими способами:</p><ul><li>Импортировать код из библиотеки или вложить библиотеки друг в друга. Пожалуй, это худшее, что можно придумать, поскольку <a href="https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D1%86%D0%B5%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">связанность</a> кода будет неконтролируема, и все преимущества разбиения на пакеты сойдут на нет. И проект превратится в один большой монолит.</li><li>Указать в package.json одного пакета путь в файловой системе до другого (например, file:../my-library) вместо версии зависимости. В целом рабочий вариант, но нарушается инверсия зависимостей. Пакет перестаёт зависеть от абстракции и начинает зависеть от конкретного кода. Если его понадобится опубликовать, придётся включать в архив все его подобные зависимости с помощью поля bundledDependencies.</li><li>Использовать npm link. Можно указать в package.json пакета последнюю опубликованную в NPM версию зависимости и заменить её <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B0">симлинком</a> на локальную версию командой npm link. Делать это придётся после каждой установки зависимостей, что довольно неудобно.</li><li>Использовать <a href="https://lerna.js.org/">Lerna</a>. Lerna фактически была создана для автоматизации выполнения npm-link с целью организации монорепозитория.</li><li>Использовать Workspaces. С появлением во всех актуальных пакетных менеджерах механизма Workspaces использование Lerna стало бесполезным, поскольку практически всё то же самое можно получить из коробки, создав в корне монорепозитория package.json с полем workspaces:</li></ul><h2>Фантомные зависимости</h2><p>Как мы выяснили ранее, механизм всплытия пакетов в node_modules помогает избежать дублирования пакетов. Такие зависимости также становятся доступными в нашем пакете, из-за чего мы можем столкнуться с довольно непредсказуемыми проблемами.</p><p>Например, мы используем библиотеку library-a версии 1.0.0, которая, в свою очередь, зависит от библиотеки library-b. Поскольку library-b всплывает на верхний уровень node_modules, мы сможем импортировать её в проект.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/using-transitive-dependency.drawio.png" alt="" /></figure><p>Может случиться так, что в следующей патч-версии library-a больше не будет зависеть от library-b. Это вполне валидная ситуация, поскольку внешний API библиотеки не изменился.</p><p>В таком случае library-b не установится, и мы больше не сможем использовать её в своём проекте. Но весьма вероятно мы узнаем это только перед продакшн сборкой в CI, поскольку производим чистую установку с npm ci там.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/phantom-dependency.drawio.png" alt="" /></figure><p>Использование транзитивной зависимости без явного указания её в package.json называется <b>фантомной зависимостью</b>.</p><p>Простое решение этой проблемы заключается в валидации импортов в проекте с помощью <a href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md">ESLint-плагина</a>. Но давайте всё-таки копнём чуть глубже и попытаемся разобраться с первопричиной этой проблемы.</p><h2>Структура зависимостей</h2><p>Многие называют структуру зависимостей деревом, что не совсем верно с концептуальной точки зрения. Зависимости представляют собой <a href="https://ru.wikipedia.org/wiki/%D0%9E%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B0%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%B3%D1%80%D0%B0%D1%84">ориентированный ациклический граф</a>, а дерево является частным случаем этой структуры данных.</p><p>Самое важное отличие графа от дерева заключается в возможности возникновения ромбовидных зависимостей.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/diamond-shape-dependencies.drawio_waifu2x_art_noise3_scale.png" alt="" /></figure><p>Файловая система же представляет собой именно дерево и не может иметь ромбовидных зависимостей. Поэтому пакетному менеджеру приходится делать некоторые преобразования, чтобы записать пакеты на диск в node_modules.</p><p>Nested-модель установки наиболее близка к исходной структуре данных, но фактически она предлагает дублировать узлы графа, в которых возникли ромбовидные зависимости, что приводит к огромному количеству дубликатов.</p><p>На самом деле, в файловых системах есть более эффективный инструмент для решения этой задачи — симлинки, которые позволяют создать ссылку на файл или директорию, вместо дублирования содержимого.</p><p>На основе этой идеи был разработан новый пакетный менеджер — <a href="https://pnpm.io/">PNPM</a>.</p><h2>PNPM</h2><p>PNPM, в отличие от NPM и Yarn, не пытается сделать структуру node_modules как можно более плоской. Вместо этого он скорее нормализует граф зависимостей.</p><p>После установки PNPM создаёт в node_modules директорию .pnpm, которая концептуально представляет собой хранилище ключ-значений, в котором ключом является название пакета и его версия, а значением — содержимое этой версии пакета. Такая структура данных исключает возможность возникновения дубликатов.</p><p>Структура самой директории node_modules будет подобна nested-модели из NPM, но вместо физических файлов ней будут находиться симлинки, которые ведут в то самое хранилище пакетов.</p><figure><img src="https://media.tproger.ru/uploads/2023/02/pnpm-node-modules.drawio.png" alt="" /></figure><p>В node_modules каждого пакета будут находиться только симлинки на те пакеты, которые указаны у него в package.json. Это полностью избавляет нас от проблемы фантомных зависимостей, и потребность в наличии ESLint-плагина отпадает.</p><p>В версии NPM 9 появился флаг <a href="https://docs.npmjs.com/cli/v9/commands/npm-install/?v=true#install-strategy">install strategy</a>. Значение linked в нём включает подобную PNPM модель установки с симликами, но на текущий момент, это экспериментальная фича.</p><h3>Глобальное хранилище пакетов</h3><p>PNPM может создать директорию .pnpm не только в node_modules проекта, но и глобально. В таком случае node_modules у проектов будут содержать только симлинки, за счёт чего ускоряется установка зависимостей (создание симлинка занимает меньше времени, чем копирование файлов). Это экономт колоссальное количество дискового пространства.</p><h3>Переопределение зависимостей</h3><p>Для переопределения зависимостей PNPM тоже имеет <a href="https://pnpm.io/package_json#pnpmoverrides">свою версию поля overrides</a>. Помимо этого, он предлагает механизм хуков, которые позволяют вмешаться в процесс разрешения зависимостей.</p><p>В .pnpmfile.cjs можно написать JavaScript-код, который будет изменять package.json всех пакетов в дереве зависимостей на этапе разрешения. Это позволяет максимально точно исправлять ошибки, возникающие с транзитивными зависимостями.</p><h3>Простота использования</h3><p>PNPM имеет API, очень похожий на Yarn, так что можно не привыкать к новым командам в третий раз.</p><p>По всем вышеописанным причинам, я предпочитаю использовать PNPM во всех своих проектах.</p><p>Разработчики Yarn решили пойти по более революционному пути для решения​​ проблемы фантомных зависимостей, добавив режим <a href="https://yarnpkg.com/features/pnp">Plug’n’Play</a>. В этом режиме Yarn заменяет собой механизм разрешения модулей из Node.js и вместо директории node_modules создаёт файл .pnp.js, в котором сохраняет всю необходимую ему информацию для разрешения зависимостей. Не все пакеты в NPM совместимы с этим режимом, поэтому его внедрение может вызвать некоторые трудности. Но весьма вероятно, что для менеджмента зависимостей в JavaScript это большой шаг в будущее.</p><h2>Будущее менеджмента зависимостей в JavaScript</h2><p>По моим наблюдениям, управляющие зависимостями в JavaScript инструменты постепенно идут к полному избавлению от директории node_modules в проекте. И, возможно, к разрешению зависимостей прямо в рантайме благодаря <a href="https://telegra.ph/CommonJS-vs-ESM-12-22">ES-модулям</a>, которые уже поддерживаются всеми современными браузерами, а также в <a href="https://deno.land/">Deno</a> — альтернативе Node.js, в которой в принципе нет пакетного менеджера как такового.</p><p>Также довольно большую популярность обрела концепция Module Federation, представленная в Webpack 5, фактически позволяющая выполнять часть работы пакетного менеджера прямо в браузере пользователя в рантайме за счёт старого доброго script. Но это тоже выглядит как промежуточный шаг к полному переходу на ES-модули.</p><p>Больше про JavaScript и всё, что с ним связано, можете почитать <a href="https://t.me/hacknote_js">в канале</a>. И пишите в комментариях, что бы ещё хотели узнать по этой теме!</p>]]></content:encoded>
    </item>
    <item>
      <title>Навыки кодинга, необходимые крутому Linux-инженеру</title>
      <link>https://tproger.ru/articles/navyki-kodinga-neobhodimye-krutomu-linux-inzheneru</link>
      <comments>https://tproger.ru/articles/navyki-kodinga-neobhodimye-krutomu-linux-inzheneru?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Виктория Овсянникова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/navyki-kodinga-neobhodimye-krutomu-linux-inzheneru</guid>
      <description><![CDATA[<p>В вакансиях Linux-инженера среди требований встречается упоминание базовых навыков разработки ПО. Рассказываем, что входит в это понятие и зачем они нужны специалисту.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/navyki-kodinga-neobhodimye-krutomu-linux-inzheneru">Навыки кодинга, необходимые крутому Linux-инженеру</a>»</p>]]></description>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 12 Jan 2023 13:01:50 GMT</pubDate>
      <content:encoded><![CDATA[<p>Linux-инженер администрирует и поддерживает сервисы в компании. Он может не быть участником команды разработки, а работать только с инфраструктурой: устанавливать, настраивать и администрировать сервисы.</p><p>Может показаться, что кодить здесь не нужно, но навыки разработки ПО особенно важны, когда Linux-инженер помогает с автоматизацией процесса разработки. Linux-инженер из компании КРОК Антон Селиверстов рассказывает, что именно нужно уметь делать.</p><p><a href="https://tproger.ru/#1">Понимать процесс разработки ПО</a><br /><a href="https://tproger.ru/#2">Компилировать и читать код разработчика</a><br /><a href="https://tproger.ru/#3">Понимать основы тестирования</a><br /><a href="https://tproger.ru/#4">Автоматизировать установку сервисов</a><br /><a href="https://tproger.ru/#5">Настраивать мониторинг сервисов</a><br /><a href="https://tproger.ru/#6">Автоматизировать сбор логов</a><br /><a href="https://tproger.ru/#7">Владеть инструментами контейнеризации</a><br /><a href="https://tproger.ru/#8">Доставлять код в окружение</a></p><h2>Понимать процесс разработки ПО</h2><p>Чтобы понимать, когда и где нужна автоматизация, важно разобраться в базовых подходах к правильной разработке ПО. Для этого можно изучить манифест «<a href="https://12factor.net/ru/">Twelve-Factor App</a>». Это методология, описывающая 12 факторов, которые важно учитывать при создании SaaS-сервисов: декларативность, масштабируемость, непрерывное развёртывание и т. д.</p><h2>Компилировать и читать код разработчика</h2><p>В компании может быть много команд разработки. Кто-то пишет на Python, кто-то разрабатывает на Java. Учить все языки, чтобы работать с любой командой, не самый оптимальный вариант. Да и выбор языка программирования зависит даже не от компании, а от проекта. Но инженеру нужно уметь компилировать код. Это пригодится и вне работы в команде, так как многие инфраструктурные сервисы, вроде <a href="https://nginx.org/ru/">Nginx</a> или <a href="http://www.haproxy.org/">HAProxy</a> можно скомпилировать из исходного кода и установить.</p><p>Вдаваться во все тонкости языка на уровне разработчика Linux-инженеру не нужно, поэтому можно выбрать курсы, которые заточены под конкретные инженерные задачи. Для этого, например, подойдут программы «<a href="https://slurm.io/go-for-ops">Golang для инженеров</a>» или «<a href="https://slurm.io/course-python-for-ops">Python для инженеров</a>» от «Слёрм».</p><h2>Понимать основы тестирования</h2><p>Код необходимо тестировать, причём не только на этапе разработки, но и при доставке в окружения. Поэтому Linux-инженер должен разбираться в видах тестирования, знать, как они проводятся, уметь встраивать в CI/CD-пайплайн хотя бы тесты на безопасность и простейшие проверки. Например, линтеры — это программы, которые проверяют синтаксис кода и исправляют простые ошибки вроде незакрытых скобок, пропущенных точек с запятой.</p><p>Базовый вид тестирования на безопасность, который полезно использовать, — SAST-тестирование. Он позволяет проверить сервис на ошибки и уязвимости в исходном коде с помощью статического анализа.</p><h2>Автоматизировать установку сервисов</h2><p>Устанавливать сервисы можно вручную, выполняя все команды, а можно автоматизировать эти задачи. Для этого Linux-инженер пишет сценарии, которые при запуске выполняют необходимые действия на виртуальных машинах. Автоматизировать этот процесс можно с помощью инструментов конфигурации вроде Ansible. Он подключается к удалённой виртуальной машине и настраивает сервис в соответствии с необходимыми параметрами.</p><p><i>Познакомьтесь с этим инструментом, прочитав статью: </i><a href="https://tproger.ru/translations/ansible-how-to-use/">Что такое Ansible и как его использовать</a></p><h2>Настраивать мониторинг сервисов</h2><p>После установки необходимо осуществлять поддержку всей платформы, потому что проблемы в системе могут возникнуть на любом этапе.</p><p>Системы мониторинга собирают показатели с виртуальных машин и сервисов и на их основе строят информативные графики. По ним можно сделать вывод, насколько оптимально и корректно работает система, хватает ли ресурсов и т. д. Настраивать необходимые для анализа показатели можно с помощью <a href="https://prometheus.io/">Prometheus</a> и Grafana.</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Modal_dashboards.png" alt="" /></figure><h2>Автоматизировать сбор логов</h2><p>Умение находить и читать нужные логи поможет быстро определить и проанализировать ошибку. Так как виртуальных машин и сервисов в компании может быть много, на ручную проверку логов может уйти много времени. Поэтому есть системы централизованного логирования, которые автоматически собирают необходимые данные в одном месте. Одна из самых популярных — <a href="https://www.elastic.co/what-is/elk-stack">Elasticsearch</a>. С помощью дополнительного компонента Logstash эти логи передаются в систему и автоматически анализируются. А благодаря инструменту Kibana их можно удобно визуализировать, читать и фильтровать.</p><h2>Владеть инструментами контейнеризации</h2><p>Также нужно познакомиться с понятиями <a href="https://tproger.ru/articles/containers-explained/">контейнеризации</a> и инструментарием Docker. С его помощью можно запускать приложения изолированно от основной ОС. А чтобы управлять этими контейнерами, нужно знать инструменты оркестрации, например, <a href="https://kubernetes.io/ru/">Kubernetes</a>. Он их запускает, автоматически отслеживает состояние и перезапускает в случае сбоев. Поэтому Kubernetes наиболее полезен, когда микросервисов становится много. Также управлять контейнерами можно и с помощью Docker Compose — это инструмент, который позволяет описать конфигурацию из нескольких сервисов и запустить сразу несколько взаимосвязанных контейнеров.</p><h2>Доставлять код в окружение</h2><p>Также Linux-инженер занимается развёртыванием и управлением приложениями. Когда разработчик пушит свой код в Git-репозиторий, Linux-инженер помогает доставить код в нужное окружение: сначала в среду разработки, потом в тестовое окружение, потом уже на продакшн. Он создаёт пайплайн, который первым этапом тестирует код. Если тесты успешны, то пайплайн собирает Docker-образ и пушит в удалённый репозиторий, например, Nexus или GitLab Container Registry. После этого образ скачивается или запускается на нужном окружении: это может быть виртуальная машина или Kubernetes-кластер.</p><p>Навыки разработки ПО для Linux-инженера скорее вспомогательные, чем основные. Однако их знание даёт конкурентное преимущество на рынке — используя этот опыт, он может продолжить рост в качестве DevOps-инженера в команде разработки или стать SRE-инженером.</p><p><b>А чтобы вы могли ещё глубже прокачать навыки Linux-инженера, мы собрали для вас </b><a href="https://tproger.ru/articles/5-knig-dlja-linux-inzhenera-ot-azov-do-upravlenija-sistemami/">подборку книг: от азов до управления системами</a><b>.</b></p>]]></content:encoded>
    </item>
    <item>
      <title>Как стать архитектором ПО в 2023 году</title>
      <link>https://tproger.ru/articles/kak-stat-arhitektorom-po-v-2023-godu</link>
      <comments>https://tproger.ru/articles/kak-stat-arhitektorom-po-v-2023-godu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Виктория Овсянникова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-stat-arhitektorom-po-v-2023-godu</guid>
      <description><![CDATA[<p>Что делает ИТ-архитектор и как им стать? Инструкция по старту в профессии: специализации, ключевые задачи и необходимые знания.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-stat-arhitektorom-po-v-2023-godu">Как стать архитектором ПО в 2023 году</a>»</p>]]></description>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 24 Nov 2022 14:09:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>IT-архитектор — это проектировщик, который принимает ключевые решения по проекту. В банковской сфере IT-архитектору приходится строить сложные системы, учитывая такие особенности, как работа с большим количеством пользователей и объёмами данных.</p><p>Если вы разработчик или системный аналитик и любите решать стратегические задачи, то эта специальность может стать для вас работой мечты. Вместе с Владимиром Григорьевым, экспертом по архитектуре Газпромбанка, мы выделили области знаний, которые помогут сориентироваться в профессии.</p><ol><li><a href="https://tproger.ru/#1">Специализации и задачи IT-архитектора</a></li><li><a href="https://tproger.ru/#2">С какими областями знаний работает IT-архитектор</a></li><li><a href="https://tproger.ru/#3">Что должен знать архитектор ПО</a></li><li><a href="https://tproger.ru/#4">Софт-скилы ИТ-архитектора</a></li><li><a href="https://tproger.ru/#5">Опыт — это проработка гипотез</a></li></ol><h2>Специализации и задачи IT-архитектора</h2><p>В IT-архитектуре много направлений: enterprise, solution, system, data-архитектура. У каждой специализации свои особенности, которые в том числе зависят и от стека конкретной компании. Поэтому не так просто собрать универсальный пул навыков и технологий, подходящий любому IT-архитектору.</p><p>В больших компаниях задачи диверсифицированы по специалистам.</p><p>Как правило, <b>enterprise-архитектор </b>взаимодействует с бизнесом и проектирует концепцию решения. Он передаёт эти наработки <b>solution-архитектору</b>, который прорабатывает детали, учитывая внешние и внутренние взаимодействия. Также на этом этапе подбирается технический стек, на котором будет произведена реализация. Если ландшафт широкий и систем много, то для выбора техстека лучше выделить отдельную роль: технического или системного архитектора.</p><p>После подготовки детального решения, его необходимо проверить на безопасность. В нашем банке этим занимается отдельное подразделение. Если у <b>архитекторов безопасности</b> нет замечаний, то решение согласовано и может идти дальше. В противном случае мы исправляем замечания и проходим очередной круг согласования, пока вопрос не будет решён.</p><p><b>Технический архитектор</b> заведует артефактами развёртывания решений. Он знает, какое ПО использовать для быстрой записи огромных объёмов ненормализованных данных и как их качественно нормализовать. Как правило, для этого он пользуется целыми наборами типовых шаблонных развёртываний разнообразных систем.</p><p>К техническому архитектору могут обращаться на разных стадиях разработки решения: как на этапе создания концепции, чтобы получить подтверждение о возможностях технического продукта, так и на стадиях детального проектирования или согласования с архитекторами безопасности.</p><p>Что почитать: блог Мартина Фаулера про архитектуру ПО</p><p>В компаниях поменьше у IT-архитектора спектр задач может быть шире. Например, не только построить проект, но ещё и провести код-ревью. Тогда у IT-архитектора обязательно должен быть бэкграунд кодера.</p><h2>С какими областями знаний работает IT-архитектор</h2><p>Чтобы грамотно выстраивать проект, ИТ-архитектор в банке должен понимать, как устроены эти области знаний:</p><ol><li><b>Реализация бэкенда.</b> Где хранить данные, как к ним обеспечить доступ.</li><li><b>Реализация фронтенда.</b> Работа с клиентом, интернетом, каналами.</li><li><b>Интеграционная архитектура.</b> Возможности интеграции, как обеспечивать взаимодействие между системами, не положив ни одну из них.</li><li><b>Безопасная архитектура.</b> Как правильно обеспечить взаимодействие клиентов, быстро проводить идентификацию, авторизацию клиента.</li><li><b>Использование MDM.</b> Это системы, которые будут хранить мастер-данные по необходимым для проекта сущностям (данные клиентов, продуктов и т. д.).</li><li><b>Реализация справочников.</b> Важно правильно распространять по своим сервисам справочную информацию.</li><li><b>Монолитная, сервисная и микросервисная архитектуры.</b> Как они устроены, в чём разница, плюсы и минусы подходов.</li></ol><h2>Что должен знать архитектор ПО</h2><h3>Архитектурный фреймворк TOGAF</h3><p>Важно понимать, как строится архитектура предприятий. Мы пользуемся фреймворком <a href="https://pubs.opengroup.org/architecture/togaf9-doc/arch/chap03.html">TOGAF</a>: проект прорабатывается с точки зрения бизнес-требований, технологического стека, архитектуры данных. Эта методология позволяет реализовать устойчивую и высокопроизводительную систему.</p><p>Примечание 1Если при выходе из строя n-компонента система продолжает полноценно функционировать, то её можно считать <b>устойчивой</b>. N — величина динамическая и может изменяться в зависимости от критичности систем.</p><p>Примечание 2Если при увеличении нагрузки система так же качественно отрабатывает запросы, то её можно считать <b>высокопроизводительной</b>. Для банков этот параметр очень важен, ведь предусмотреть действия клиентов возможно далеко не всегда.</p><h4>Бизнес-требования</h4><p>IT-архитектор должен разработать технический сценарий реализации бизнес-задачи.</p><p>Для этого нужно проанализировать требования, которые выдвигает бизнес. Обычно они уже согласованы с другими отделами: безопасниками, проектными менеджерами, другими командами корпоративной архитектуры.</p><p>В требованиях формат задачи описывают без конкретных деталей. Например, бизнесу нужно реализовать возможность максимально быстро сообщать всем системам об изменении GUID клиента. Тогда можно наследовать большое количество фич, которые клиент сделал со старым GUID. Архитектор понимает, что для этого необходимо построить обратный поток из мастер-системы по данным клиента. Очень важно, чтобы на эти изменения подписались все сервисы, задействованные в этой задаче.</p><figure><img src="https://media.tproger.ru/uploads/2022/11/image1-2.png" alt="" /><figcaption>Так выглядит BPMN-модель события на изменение клиентских данных. Это также пример event-driven архитектуры, о которой пойдёт речь ниже.</figcaption></figure><p>Важный момент, IT-архитектор также должен проанализировать и определить <i>потенциальные изменения</i>, чтобы при интеграции новой системы с другими системами не произошло сбоя. Нужно уточнять у бизнеса: «Если изменятся, например, фамилия клиента или поле в паспорте, это критично?». Если да, то всё заносится в бизнес-требования.</p><h4>Техническая проработка</h4><p>После того как все бизнес-требования утверждены, IT-архитектор начинает проектировать схемы, чтобы «посадить» задачу на стек проекта.</p><p>ИТ-архитектор указывает, какие технологии, фреймворки используются в задаче, на чём написано приложение. Мы в Газпромбанке пишем на Java. Но бывают коробочные решения, которые требуют дополнительного внимания. Например, сервис на REST придётся серьёзно переписывать, чтобы вызывать SOAP.</p><p>Подробнее о Java и необходимых навыках можно почитать в кратком руководстве для начинающих или изучить в базовой дорожной карте языка.</p><p>Архитектор учитывает требования безопасности. По сути, это контроль вызовов: кто вызывает сервис, а кто является вызываемым. Если компрометирующий сервис «А» знает, как вызвать сервис «Б» из защищённой системы, то вся система перестаёт быть защищённой. Тогда нужно прописать, что сервис «Б» должен вызывать сервис «А» из доверенной зоны и возвращать данные по своим каналам.</p><p>Также важно понимать, какие данные передаются в этом потоке: только общедоступная информация или ещё и персональная. Если уровень критичности этих данных высок, то нужно принять дополнительные меры по их сохранности, например, выстраивание DMZ (более защищённых зон) для работы с ними.</p><h4>Архитектура данных</h4><p>Архитектор расписывает, как двигаются сущности, например: откуда берутся данные клиента и где прихраниваются, используются ли они только для передачи совместно с другими данными, изменяются ли при передаче, в каком месте сохраняются копии, где мастер-данные.</p><h3>Базы данных</h3><p>В начале 2000–х были две–три основных базы данных: Oracle, MySQL. Сейчас появляется множество новых реализаций: колоночные, документоориентированные, key-value БД. Их назначение примерно одинаковое — они хранят данные, но подходы к записи, чтению, масштабированию у них абсолютно разные.</p><p>Нужно понимать, какие существуют базы данных и какая между ними разница, что они позволяют делать, почему при использовании реляционных БД не всегда можно достичь большего профита. Например, если нет потребности в ACID (Atomicity, Consistency, Isolation, Durability), то можно рассмотреть NOSQL БД. Это даст возможность записывать большие объёмы данных в БД, к тому же масштабировать хранилище будет проще, чем при использовании реляционной БД.</p><p>Что почитать по теме:Как разобраться в видах СУБД<br />NoSQL базы данных<br />Популярные реляционные базы данных</p><h3>Интеграция</h3><p>Раньше использовались двухзвенные архитектуры систем: приложения напрямую интегрировались с хранилищем, и проблем с получением данных не было. Потом стало необходимо шарить данные в другие системы. В этот момент стало ясно, что постоянные запросы в монолиты с лёгкостью могут положить хранилище.</p><p>Сейчас, когда количество данных у банков многократно увеличивается, приходится выстраивать более корректное взаимодействие. Для этого используют интеграционные паттерны: как на бэкенд-систему положить сервисный мидл слой, чтобы он оркестрировал все запросы к этой бэкенд-системе, выступал своего рода буфером и не давал делать 100 вызовов в секунду.</p><p>Кого почитать: Максим Смирнов о сценариях интеграции приложений</p><h3>Паттерны проектирования</h3><h4>Event-driven архитектура</h4><p>Это удобный паттерн с большими функциональными возможностями: одно и то же событие могут принимать сразу несколько сервисов, по-разному на них реагируя. Здесь нет сильной связности между поставщиком и потребителем, они существуют автономно. Как маршрутизатор событий тут актуален Kafka.</p><p>Материалы по теме:<br />Что такое EDA и как для её построения использовать KafkaЧто такое Kafka и как начать с ним работу</p><h4>Domain-driven design (DDD)</h4><p>В этом случае все данные делятся на домены. Здесь нужно на ранней стадии спроектировать систему так, чтобы не допустить протекания доменов.</p><p>Подходы domain-driven design очень актуальны в больших компаниях. Если заранее не «нарезать» предметную область и не определить основные сущности этих доменов, то со временем данные могут задублироваться в разных частях системы, и синхронная актуализация этих данных вызовет трудности.</p><p>Например, если продукты клиента будут храниться не только в бэкенде, но и на уровне сервисов в нескольких местах, то при изменении списка этих продуктов не все зависимые сервисы смогут сразу получить эти данные (если заранее не реализовать обратный поток с этими данными, как описано в примере выше). Тогда в разных каналах у клиента может быть разная информация по продуктам.</p><p>GitHub про DDD: книги, видео, комьюнити, обучающие курсы.</p><h4>Onion-архитектура</h4><p>При проектировании системы за основу берётся независимый Core-уровень, на который накладывается второй, уже зависимый от первого. На второй уровень накладывается третий, который зависит от второго и так далее. При таком подходе верхний UI-слой не сможет управлять Core-уровнем или ходить напрямую в бэкенд, обходя сервисный слой.</p><h4>Подход CQRS</h4><p>CQRS (Command and Query Responsibility Segregation) — это парадигма, при которой операции записи отделены от операций чтения. Она необходима, когда нагрузка на чтение и запись данных распределена несимметрично. Например, когда большая часть бизнес-логики и сложных проверок приходятся на write-систему, а чтение данных происходит чаще, чем вставка изменений.</p><p>Также почитайте о плюсах и минусах паттерна CQRS.</p><h3>Инструменты визуализации решений</h3><ul><li>Бизнес-вижн прописывается с помощью <a href="https://tproger.ru/articles/modelirovanie-biznes-processov-praktika-ispolzovanija-camunda-bpm-v-java-razrabotke/">BPMN-подходов</a> моделирования.</li><li>Для построения архитектурных схем можно использовать <a href="https://support.microsoft.com/ru-ru/office/%D1%81%D1%85%D0%B5%D0%BC%D1%8B-uml-%D0%B2-visio-ca4e3ae9-d413-4c94-8a7a-38dac30cbed6">UML</a> или <a href="https://archimate.visual-paradigm.com/">Archimate нотации</a>. Мы используем Visio, либо аналог Visio (<a href="https://app.diagrams.net/">draw.io</a>).</li></ul><figure><img src="https://media.tproger.ru/uploads/2022/11/image3-1.png" alt="" /><figcaption>Описание движения сущности в информационной архитектуре.</figcaption></figure><figure><img src="https://media.tproger.ru/uploads/2022/11/image7.png" alt="" /></figure><p>Архитектура приложений описывает направление вызова: кто и кого «дёргает». Также на этом уровне отмечаются технические особенности решения и протоколы, по которым необходимо организовать взаимодействие.</p><ul><li>Существует также «салфеточный дизайн»: когда архитектура рисуется от руки, ИТ-архитектор ставит свою подпись и отдаёт в реализацию. В небольших компаниях такого вполне достаточно. Но в крупном бизнесе, когда потребителей слишком много, нужно согласовывать все решения с энтерпрайз-архитектурой и отделом безопасности.</li></ul><h2>Софт-скилы ИТ-архитектора</h2><p>IT-архитектор выступает посредником между бизнесом и техническими специалистами. Поэтому важный софт-скил архитектора — <b>быть переговорщиком</b>. Он должен договариваться, находить компромиссы, аргументировано объяснять бизнесу технические сложности реализации проекта, чтобы ставить адекватные сроки выполнения.</p><p>При этом также важны умение работать в команде и лидерские качества. Нужно доносить важность проекта до разработчиков: уметь мотивировать, находить к ним подходы, объяснять важность задач и правильно ставить их приоритетность.</p><p>Нужно развивать критическое мышление. В этом помогает <b>концепция</b> <b>helicopter view</b>: быть больше своих задач, смотреть выше. Это даёт более объёмную картину устройства проекта. Нужно анализировать, какой у задач бизнес-эффект, какой эффект будет при интеграции с другими сервисами продукта. Чем выше ты смотришь, тем больше вопросов себе задаёшь: действительно ли здесь нужно сделать именно так, или можно по-другому.</p><h2>Опыт — это проработка гипотез</h2><p>В профессии ИТ-архитектора всё очень индивидуально и зависит от решений, которых придерживаются в конкретной компании.</p><p>Так как же стать IT-архитектором? Выучить пару фреймворков и инструментов визуализации недостаточно. Сейчас в профессию приходят специалисты с уверенными хард-скиллами — системные аналитики, разработчики или другие крепкие айтишники.</p><p>Начинающему архитектору ПО нужно обязательно проверять свои гипотезы на встречах, группах, стратсессиях. Ведь профессионализм и компетенции в этой сфере нарабатываются не столько теоретическими знаниями, сколько опытом и решением практических задач.</p>]]></content:encoded>
    </item>
    <item>
      <title>Исключения в C++: безопасность, спецификации, бенчмарки</title>
      <link>https://tproger.ru/articles/isklyucheniya-v-c-garantii-bezopasnosti-i-specifikacii</link>
      <comments>https://tproger.ru/articles/isklyucheniya-v-c-garantii-bezopasnosti-i-specifikacii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Мария Кривоченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/isklyucheniya-v-c-garantii-bezopasnosti-i-specifikacii</guid>
      <description><![CDATA[<p>Разберём гарантии безопасности и выясним, как устроены исключения в C++, как они работают влияют на скорость выполнения программы.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/isklyucheniya-v-c-garantii-bezopasnosti-i-specifikacii">Исключения в C++: безопасность, спецификации, бенчмарки</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 23 Nov 2022 09:00:39 GMT</pubDate>
      <content:encoded><![CDATA[<p>Продолжаем разговор об исключениях в C++.</p><ol><li><a href="https://tproger.ru/#part1">Гарантии безопасности</a></li><li><a href="https://tproger.ru/#part2">Спецификации исключений в C++</a></li><li><a href="https://tproger.ru/#part3">Стоит ли избегать исключений</a></li><li><a href="https://tproger.ru/#part4">Как работают исключения</a></li><li><a href="https://tproger.ru/#part5">Заключение</a></li><li><a href="https://tproger.ru/#part6">Полезные ссылки</a></li></ol><p><a href="https://tproger.ru/articles/iskljuchenija-v-cpp-tipy-sintaksis-i-obrabotka/">В первой части</a> мы разобрали, как создавать исключения и работать с ними, а также какими они бывают. Разобрали ключевые слова try, catch и throw, синтаксис выбрасывания и обработки исключений, а ещё особые случаи, связанные с исключениями.</p><p>Вторая часть статьи больше подойдёт продвинутым программистам, которые хотят глубже разобраться в теме исключений. Однако никаких специальных знаний не требуется. Во второй части мы разберём:</p><ul><li>гарантии безопасности;</li><li>спецификации исключений;</li><li>как исключения влияют на скорость выполнения;</li><li>как устроены исключения в C++ и как они работают.</li></ul><p>Также рассмотрим философский вопрос о нужности исключений и альтернативных подходах, запустим три бенчмарка и в результате увидим, что иногда исключения не только не замедляют программу, а даже ускоряют.</p><p>Углубить и систематизировать знания C++ поможет курс «<a href="https://practicum.yandex.ru/cpp/?utm_source=pr&amp;utm_medium=content&amp;utm_content=23_11_22&amp;utm_campaign=pr_content_cpp_tproger">Разработчик C++</a>» в Яндекс Практикуме. Для тех, кто знает C++, но желает изучить работу по сети, Docker, Linux и множество вспомогательных инструментов, есть курс «<a href="https://practicum.yandex.ru/cpp-backend/?utm_source=pr&amp;utm_medium=content&amp;utm_content=23_11_22&amp;utm_campaign=pr_content_cpp-backend_tproger">C++ для бэкенда</a>».</p><h2>Гарантии безопасности</h2><p>Исключения — ситуация нештатная. Но они существуют для того, чтобы как-то обработать эту ситуацию и продолжить выполнение программы.</p><p>Чтобы было ясно, как исключительная ситуация может повлиять на работу, существует специальное понятие — гарантия безопасности исключений. Это описание вреда, который может нанести исключение, приводимое в документации к функции или методу.</p><p>Выделяют четыре уровня безопасности исключений:</p><ol><li><b>Гарантия отсутствия исключения.</b> Самая сильная гарантия. Означает, что исключение возникнуть не может, а значит, ничего не сломает.</li><li><b>Строгая гарантия безопасности.</b> Исключение может возникнуть, но в этом случае всё будет возвращено к тому, как было до вызова соответствующей функции. Иными словами, операция не удалась, но можно сделать вид, как будто её и не было.</li><li><b>Базовая гарантия безопасности.</b> При возникновении ошибки мы не можем вернуть всё как было, но всё равно останемся в корректном состоянии. Все инварианты будут сохранены, все ненужные ресурсы освобождены, ничего не утечёт. С объектом, из-за которого возникло исключение, можно продолжать работать.</li><li><b>Отсутствие безопасности.</b> Не можем гарантировать ничего. Если исключение произошло, лучше поскорее завершить работу программы.</li></ol><p>Рассмотрим пример. Напишем собственную операцию push_back для вставки в вектор.</p><h3>Гарантия отсутствия безопасности</h3><p>Вызов new не опасен в отличие от перемещения элементов: мы ничего не знаем про конструктор перемещения неизвестного объекта T. Он может выбрасывать исключение.</p><p>Эта реализация метода не даёт гарантий. Если во время перемещения объектов возникло исключение, то мы как минимум получим утечку памяти. Кроме того, у вектора будет неправильный размер, например, если перемещение прервалось на середине.</p><p>В примере мы использовали new для простоты и наглядности. Реальный вектор должен выделять сырую память без инициализации.</p><h3>Базовая гарантия безопасности</h3><p>Улучшим нашу функцию, чтобы подняться с четвёртого уровня безопасности на третий. Это минимальный уровень, который допустимо использовать в программах.</p><p>Уже лучше: размер вектора будет корректен, и мы не допустим утечки. Однако такая вставка может привести, например, к обнулению вектора, если исключение возникло в самом первом перемещении.</p><h3>Строгая гарантия безопасности</h3><p>Над строгой гарантией нужно потрудиться.</p><p>У строгой гарантии есть неприятный эффект: нам пришлось отказаться от перемещений в пользу копирований. Таким образом, она отрицательно влияет на эффективность.</p><p>Можно сделать всё эффективно, если есть уверенность, что перемещение объектов типа T не выбрасывает исключений. Стандартный std::vector так и делает.</p><h3>Гарантия отсутствия исключения</h3><p>И наконец, достигнем вершины — напишем метод без исключений:</p><p>Возврат флага успеха — альтернатива исключениям. Функция очень похожа на предыдущий вариант. Однако при такой реализации мы ничего не сможем узнать о том, какая именно ошибка произошла.</p><h2>Спецификации исключений в C++</h2><h3>Такой разный noexcept</h3><p>Как говорилось выше, вставка в вектор может работать эффективнее, если есть гарантия, что перемещение объекта не выбрасывает исключений. Такую гарантию можно дать для произвольной функции, если пометить её словом noexcept:</p><p>Если же мы хотим явно сказать, что функция выбрасывает исключение, можно написать noexcept(false):</p><p>Но это лишнее, ведь выбрасывающими по умолчанию считаются все функции, кроме деструкторов. Зато можно поместить внутрь скобок содержательное выражение времени компиляции:</p><p>Тут мы явно написали, какая функция noexcept, а какая — нет, хотя могли бы вычислить. noexcept допустимо использовать как операцию, определяющую, может ли выбрасывать содержимое скобок:</p><p>Вывод программы будет таким:</p><p>Заметьте, что мы пишем noexcept(g()), а не noexcept(g). Последнее выражение всегда false, поскольку само по себе выражение g ничего не делает, а значит, ничего не выбрасывает.</p><p>Если в функции, помеченной как noexcept, всё же возникло исключение, то оно приведёт к вызову std::terminate и завершению программы.</p><h3>А нужен ли noexcept?</h3><p>Вряд ли что-то может ответить на поставленный вопрос красноречивее <a href="https://quick-bench.com/q/If95ysFZAk5uu8PXKyIa9kJXF0A">бенчмарка</a>. В этом бенчмарке, который мы провели в сервисе QuickBench, созданы три практически одинаковых класса:</p><p>F_except, у которого есть обычный конструктор перемещения;</p><ul><li>F_noexcept, у которого конструктор перемещения помечен как noexcept;</li><li>F_noexcept2 — как F_noexcept, но с деструктором, помеченным как noexcept(false). Деструктор нужно явно помечать как noexcept(false), иначе он считается невыбрасывающим.</li></ul><p>Объекты этих классов добавляются в std::vector. Его метод push_back обеспечивает строгую гарантию исключений. Как вы уже знаете, в этом случае перемещение вектора возможно только тогда, когда перемещение его элементов не выбрасывает исключений.</p><figure><img src="https://media.tproger.ru/uploads/2022/11/image3.png" alt="" /><figcaption>Добавление элемента в вектор в среднем в 1,6 раза быстрее, если конструктор перемещения элементов помечен как noexcept. Добавление замедляется, если деструктор элементов может выбрасывать</figcaption></figure><p>Отличие заметное, хотя не такое существенное из-за оптимизаций в std::vector. noexcept позволил выбрать более эффективный алгоритм, но только в том случае, когда он применялся и для конструктора перемещения, и для деструктора.</p><h3>Волшебный default</h3><p>Рассмотрим три класса:</p><p>Казалось бы, разницы между ними нет, но попробуем изучить конструкцию T*() на предмет выбрасывания исключений:</p><p>В этом примере мы также добавили четвёртый класс. Вывод программы такой:</p><p>Нас подвёл только один вариант, в котором конструктор объявлен как T2() {}. Такой конструктор будет считаться выбрасывающим, хотя на вид он аналогичен записи T2() = default.</p><p>Невыбрасывающий конструктор часто позволяет получить более эффективный код и применить больше оптимизаций. В легковесных классах он вообще может не генерировать никаких инструкций, в то время как выбрасывающий потребует лишней работы. Предпочитайте конструкцию = default пустым скобкам.</p><h3>Список исключений</h3><p>В некоторых языках программирования c исключениями всё строже. Каждая функция снабжается списком исключений, которые она может выбрасывать. Была такая попытка и в C++ с самого момента его стандартизации:</p><p>У такого подхода есть преимущества:</p><ul><li>компилятор может убедиться, что все исключения обрабатываются;</li><li>вы видите, что может, а что не может выбрасывать функция по её сигнатуре;</li><li>обработчик можно искать в compile-time.</li></ul><p>Однако на деле не всё так радужно. Всплыл ряд недостатков:</p><ul><li>Некоторые исключения, например, std::bad_alloc может выбрасывать почти любая функция. Везде писать throw(std::bad_alloc) утомительно.</li><li>Непонятно, как быть с указателями на функции и std::function. Если мы хотим заранее всё знать об исключениях, то спецификации исключений должны быть в типе функции. Тогда неясно, как их преобразовывать.</li><li>Можно сделать спецификации нестрогими, но тогда непонятно, что они дают.</li></ul><p>С похожими недостатками сталкиваются и в других языках программирования. В C++ недостатки перевесили, и комитет по стандартизации решил от явных спецификаций отказаться, оставляя только noexcept. В Стандарте C++11 они объявлены устаревшими (<b>deprecated</b>), а позже вовсе исключены из языка.</p><h2>Стоит ли избегать исключений</h2><p>«Мы не используем исключения в C++», — <a href="https://google.github.io/styleguide/cppguide.html#Exceptions">cтайлгайд Google</a>.</p><h3>Не нужны или незаменимы</h3><p>Исключения в C++ — удобный инструмент, не лишённый недостатков. Может быть, самый существенный из них — в том, что исключения прозрачны. Глядя на сигнатуру функции, нельзя понять, какие исключения она выбрасывает. Это можно понять, глядя в документацию. Но документация не проверяется автоматически, а значит, может ошибаться.</p><p>Код с исключениями иногда трудней читать и модифицировать. Может, где-то через функцию пролетает исключение, задуманное разработчиком, но узнать об этом, глядя на код, невозможно. Вы модифицируете функцию, внося, казалось бы, несущественные изменения, но тем самым нарушаете гарантии безопасности.</p><p>Вспомните пример с четырьмя реализациями push_back. Программисту, который не знал нашей мотивации — обеспечение гарантий безопасности, — будет совершенно неочевидно, почему мы обновили поле mem в начале функции, а не в другом месте. Ему покажется это прихотью, и он изменит способ на противоположный — просто для красоты кода. При этом корректная обработка исключений перестанет работать.</p><p>Но если пользоваться исключениями аккуратно, то данного недостатка можно избежать.</p><p>В некоторых местах без исключений трудно: например, это единственный способ прервать конструктор. Можно, конечно, ввести для объекта невалидное состояние, указывающее, что во время конструктора произошла ошибка. Но это будет усложнением класса.</p><p>Помимо конструкторов исключения практически незаменимы, когда вы делаете много похожих действий, каждое из которых может завершиться неудачей:</p><p>Без исключений только первая строка блока try записывалась бы так:</p><p>При этом мы опустили вывод диагностики о местоположении ошибки и её сути. Конечно, можно было придумать сложный прокси-объект, который ведёт себя как JSON, но на самом деле находится в ошибочном состоянии и не выполняет никаких действий, и тем самым сократить количество строк.</p><p>Но решайте сами, следовать ли стайлгайду Google в этой части. Ещё один аргумент в пользу исключений в C++ вы найдёте ниже.</p><h3>Строгий запрет</h3><p>Некоторые компании и программисты предпочитают вообще не использовать исключения. Об этом лучше сказать компилятору специальной опцией. Это развяжет оптимизатору руки и позволит генерировать более простой и эффективный код в очень многих случаях.</p><p>Для GCC и clang это опция -fno-exceptions. Её использование не значит, что исключений не возникнет. В частности, никто не может избавить вас от std::bad_alloc и других исключений, выбрасываемых из библиотек. Однако чаще всего исключение будет приводить к вызову std::terminate.</p><p>Если на радикальные меры идти не хочется, ваш друг — noexcept. Он работает не хуже, чем -fno-exceptions, если его поставить везде, где нужно. noexcept также хорошо помогает оптимизатору и убирает оверхед при вызове функций, помеченных этим ключевым словом.</p><h3>Назад в будущее</h3><p>У исключений есть альтернатива, известная ещё корифеям, — возврат флага или ошибки из функции. Если вы пишете процедуру, то проще всего вернуть флаг успеха:</p><p>Чтобы компилятор проверял, что вызывающая функция точно обрабатывает ошибку, добавим [[nodiscard]]:</p><p>Подобного сервиса — проверки, что ошибка обрабатывается, — нет даже у исключений. Если булевого флага недостаточно, функция может возвращать объект ошибки, содержащий информацию о её причинах. В этом случае [[nodiscard]] можно поставить прямо в класс:</p><p>Когда функция должна сама по себе вернуть значение, такой способ не подходит. Решение есть — специальный тип std::expected, но он будет добавлен в язык только в 2023 году. Предполагается, что этот объект будет хранить либо возвращённое значение, либо код ошибки, когда операция не удалась.</p><h3>Универсальный солдат</h3><p>Если ждать до 2023-го не хочется, можно реализовать expected самостоятельно или использовать простую замену: передать функции объект ошибки по ссылке, предлагая записать в него информацию.</p><p>Это самый универсальный и удобный способ для пользователя функции. Так мы предлагаем ему выбор: хочет ли он ловить исключение или предпочитает обрабатывать объект ошибки. Такой способ предусмотрен во многих библиотеках, в том числе в std. Рассмотрим примеры.</p><ul><li><b>std::filesystem.</b> Функции работы с файловой системой часто могут завершаться с ошибкой. Например, вы хотите переименовать файл, но его только что удалили. В этом и подобных случаях выбрасывается std::filesystem::filesystem_error, подкласс std::system_error, который в свою очередь расширяет подкласс std::runtime_error и std::exception.<br />Но если вы указали дополнительный параметр типа std::error_code&amp;, то исключение не будет выброшено — вместо этого ошибка запишется в переданный объект:std::error_code ec;std::filesystem::rename(old_path, new_path, ec);if (!ec) {    std::cerr &lt;&lt; “Ошибка переименования” &lt;&lt; std::endl;}</li><li><b>Потоки ввода-вывода.</b> Для них используется немного другой механизм. По умолчанию потоки не выбрасывают, и вы вынуждены проверять все операции на корректность.<br />Если вызвать метод потока exceptions, то можно попросить поток сигнализировать при появлении флагов ошибок. Будет выбрасываться исключение std::ios_base::failure (также подвид std::system_error). У метода есть эффект, даже если он вызван уже после возникновения ошибки:#include  #include   int main(){    std::ifstream f(“abracadabra”);     try {        f.exceptions(f.failbit);    }    catch (const std::ios_base::failure&amp; e) {        std::cout &lt;&lt; “Ошибка потока: ” &lt;&lt; e.what() &lt;&lt; ‘\n’                  &lt;&lt; “Код: ” &lt;&lt; e.code() &lt;&lt; ‘\n’;    }}Передавать в метод exceptions флаг eofbit не рекомендуется. Достижение конца файла — штатная, а не исключительная ситуация. Последующие чтения всё равно установят флаг fail.</li></ul><h3>А что со скоростью?</h3><p>Бытует мнение, что исключения — это всегда медленно. Ведь у всех этих конструкций try наверняка большой оверхед. Даже если try-блоков нет, компилятор всё равно должен быть в любой момент готов обработать исключение при вызове любой функции, не помеченной как noexcept.</p><p>Проверим, так ли это. Лучший способ — бенчмарк. Будем измерять две функции, выполняющие похожую работу:</p><ul><li>void f()  — не помечена как noexcept. Рекурсивно вызывает себя. После 10 миллионов вызовов выбрасывает исключение.</li><li>bool g()  — помечена как noexcept. Рекурсивно вызывает себя. После 10 миллионов вызовов возвращает false, и цепочка вызовов прерывается.</li></ul><p>Результат во многом зависит от компилятора, оптимизаций и наличия у функций атрибута noinline. Но в большинстве случаев версия с исключениями по крайней мере не хуже. Вот <a href="https://quick-bench.com/q/kbj1qmgLB3ghQXp9BoAhAfht_ug">бенчмарк, где версия с исключениями работает в полтора раза быстрее</a>. При использовании clang <a href="https://quick-bench.com/q/SjO2pfUIZco4DsBOG9FDVCVTUZI">преимущество</a> скромнее — всего 10%.</p><p>Может показаться, что пример искусственный. В какой-то степени так и есть, но это не меняет тенденции. Без исключений в C++ нам нужно было проверять каждый результат возврата, а это дорогостоящая инструкция условного перехода. Трудно поверить, но при использовании исключений такая инструкция при вызове не требуется. Иногда это может дать неплохой выигрыш.</p><p>Сравним дизассемблер. Для наглядности добавим больше рекурсивных вызовов в каждую функцию. Так выглядит вариант с исключениями:</p><figure><img src="https://media.tproger.ru/uploads/2022/11/image1-1.png" alt="" /><figcaption>Каждый рекурсивный вызов превратился в одну инструкцию call, последний — в jmp</figcaption></figure><p>А вот начало функции с проверками. Не будем приводить код полностью:</p><figure><img src="https://media.tproger.ru/uploads/2022/11/image4.png" alt="" /><figcaption>Каждый рекурсивный вызов превратился в три инструкции — call, test и условный переход</figcaption></figure><p>Неудивительно, что исключения в этом случае победили. Однако не нужно думать, что так будет всегда. Главный минус исключений с точки зрения производительности — в том, что они мешают оптимизации.</p><p>Профессионалы советуют: везде, где можно, ставьте noexcept. Иначе производительность может сильно пострадать. Это причина, почему многие крупные компании вообще не используют исключения.</p><h2>Как работают исключения</h2><p>Исключения в компиляторах можно реализовать по-разному. Реализации стремятся к нулевому оверхеду, который формулируется так:</p><p>Мы не должны платить производительностью до тех пор, пока исключение не выбрасывается. Код, в котором исключение не произошло, должен работать так же быстро, как если бы исключение не могло произойти.</p><p>Современные компиляторы (а вернее, генераторы кода) в полной мере этому принципу не следуют, в чём можно убедиться, изучив ассемблерный код, который они выдают. Но возникающий оверхед минимальный, а где-то отсутствует вовсе. Подумаем, что обязан делать процессор для того, чтобы обеспечить возможность исключений.</p><h3>Посадочные площадки</h3><p>Исключение может возникнуть практически где угодно, а именно при любом вызове функции, не помеченной как noexcept. Во всех этих случаях компилятор должен знать, что ему делать дальше. Рассмотрим две строчки:</p><p>Они могут прерваться в девяти местах:</p><ul><li>два раза — при конвертации const char* в std::string; возможное исключение bad_alloc;</li><li>три раза — при сложении строк; возможное исключение bad_alloc;</li><li>один раз — в std::to_string; возможное исключение bad_alloc;</li><li>один раз — в std::vector::at; возможное исключение std::out_of_range;</li><li>два раза — в std::ostream::operator &lt;&lt;; возможное исключение std::ios_base::failure. Даже если мы не устанавливали для потока флаг выброса исключений, компилятор всё равно должен быть готов к такому повороту событий — операция &lt;&lt; не помечена как noexcept.</li></ul><p>Для этих девяти мест компилятор должен предусмотреть пути отхода. А именно установить, какие временные объекты он должен удалить в каждом случае и что делать дальше. Для этого в бинарном коде программы создаётся посадочная площадка (<i>landing pad</i>). Процессор приземляется на ней только в случае исключения.</p><p>Вызов функции обычно превращается в процессорную инструкцию call. При этом в стек кладётся адрес места, куда нужно вернуться после инструкции ret, завершающей вызов. Если вызываемая функция помечена как noexcept, то этого достаточно. Если нет, процессору нужно знать, куда идти, если в процессе выполнения вызова произошло непойманное исключение. Он идёт на посадочную площадку.</p><p>Таким образом для возможности обработки исключений каждый вызов требует максимум одну дополнительную инструкцию — запоминание адреса экстренной посадочной площадки. Сущая мелочь.</p><h3>Выбор обработчика</h3><p>Мы так увлеклись оверхедом, что совсем забыли: исключения нужны, чтобы их ловить. А ловят их обработчики. Чтобы исключение нашло свой обработчик, при выполнении raise нужно сохранить информацию о типе. Причём эта информация будет обрабатываться во время выполнения программы (<i>run-time</i>).</p><p>В C++ для этого есть свой механизм — динамическая идентификация типа данных, или RTTI. Чтобы мы могли поймать исключение по базовому типу, в <i>run-time</i> должна быть доступна информация обо всех предках выброшенного исключения.</p><p>Таким образом, raise делает следующее:</p><ul><li>сохраняет объект исключения;</li><li>сохраняет информацию о типе;</li><li>возможно, сохраняет что-то ещё (например, ссылку на деструктор объекта исключения);</li><li>если надо, запускает terminate;</li><li>если не надо, запускает раскрутку стека.</li></ul><p>Раскрутка стека делает следующее:</p><ul><li>приземляется на посадочную площадку;</li><li>удаляет временные и автоматические объекты;</li><li>проверяет все доступные обработчики на соответствие типа;</li><li>если надо, запускает terminate;</li><li>если не надо (и обработчик не найден), продолжает раскрутку стека.</li></ul><p>Обработчик делает следующее:</p><ul><li>выполняет пользовательский код;</li><li>если исключение не переброшено, то деаллоцирует его.</li></ul><h3>И снова бенчмарк</h3><p>Чтобы проверить, что информация о типе действительно обрабатывается в <i>run-time,</i> проведём ещё один любопытный бенчмарк. Создадим тип F очень простого вида:</p><p>А также шаблон template&lt;int&gt; struct G. Его особенность — в том, что у типа G&lt;0&gt; очень много потомков, 2048 штук. В функции f всё просто: будем бросать исключение типа F и затем ловить исключение типа F. В функции g будем бросать исключение типа G&lt;0&gt;, а ловить по типу одного из его многочисленных потомков — G&lt;1111&gt;.</p><p>Если наше предположение верно, то программе потребуется гораздо меньше времени для поиска обработчика исключения типа F. Напротив, чтобы убедиться, что обработчик типа G&lt;1111&gt; соответствует выброшенному исключению типа G&lt;0&gt;, придётся потрудиться.</p><p>Запустим <a href="https://quick-bench.com/q/D1vtkBdUC_sf2yTERPmgh9AIivg">бенчмарк</a> и убедимся в своей правоте:</p><figure><img src="https://media.tproger.ru/uploads/2022/11/image2.png" alt="" /><figcaption>Слева обработка исключения типа G&lt;0&gt;, справа — типа F</figcaption></figure><p>Поимка объекта по глубокому производному классу заняла в 32 раза больше времени. Таким образом мы убедились, что проверка исключения на соответствие типу действительно происходит в <i>run-time</i>.</p><p>Мы рассмотрели подкапотную исключений и увидели, что часть важной работы происходит в <i>run-time</i>, что несвойственно для C++. Может быть, это даже не очень эффективно, но, как было сказано выше, при обработке исключений эффективность отодвигается на второй план. Ведь это исключительная ситуация, и в том, чтобы потратить несколько десятков тысяч лишних инструкций, нет ничего страшного.</p><h2>Заключение</h2><p>Мы проделали большую работу. Разобрались в тонкостях исключений в C++, гарантиях, спецификациях. Узнали, что, вопреки распространённому мнению, исключения — не всегда медленно.</p><p>Да, исключения мешают оптимизатору, но если не забыть noexcept для критически важных функций, то этот эффект сведётся к минимуму.</p><p>Некоторые компании избегают исключений в своём коде. Но только вам решать, отказываться от них или нет. При умелом использовании исключения не принесут вреда, а польза может быть огромна. В одном можете быть уверены: после прочтения этой статьи умелое использование исключений — в ваших руках.</p><h2>Полезные ссылки</h2><ul><li><a href="https://practicum.yandex.ru/cpp/?utm_source=pr&amp;utm_medium=content&amp;utm_content=23_11_22&amp;utm_campaign=pr_content_cpp_tproger">Курс «Разработчик C++» в Яндекс Практикуме</a></li><li><a href="https://practicum.yandex.ru/cpp-backend/?utm_source=pr&amp;utm_medium=content&amp;utm_content=23_11_22&amp;utm_campaign=pr_content_cpp-backend_tproger">Дополнительный курс «C++ для бэкенда» в Яндекс Практикуме</a></li><li><a href="https://ru.cppreference.com/w/cpp/language/try_catch">Блок try в энциклопедии cppreference</a></li><li><a href="https://ru.cppreference.com/w/cpp/language/throw">Конструкция throw в энциклопедии cppreference</a></li><li><a href="https://ru.cppreference.com/w/cpp/language/noexcept_spec">Спецификации исключений в энциклопедии cppreference</a></li><li><a href="https://en.cppreference.com/w/cpp/error/exception">Стандартные типы исключений в энциклопедии cppreference</a></li><li><a href="https://www.youtube.com/watch?v=ItemByR4PRg">Доклад «Исключения C++ через призму компиляторных оптимизаций» на конференции C++ Russia 2019 Piter</a></li><li><a href="https://quick-bench.com/">Сервис микробенчмарков Quick C++ Benchmark</a></li><li><a href="https://google.github.io/styleguide/cppguide.html#Exceptions">Раздел «Исключения» стайлгайда Google</a></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Как создать собственные Python-декораторы и правильно их использовать</title>
      <link>https://tproger.ru/articles/kak-sozdat-sobstvennye-python-dekoratory-i-pravilno-ih-ispolzovat</link>
      <comments>https://tproger.ru/articles/kak-sozdat-sobstvennye-python-dekoratory-i-pravilno-ih-ispolzovat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Михаил Залешин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-sozdat-sobstvennye-python-dekoratory-i-pravilno-ih-ispolzovat</guid>
      <description><![CDATA[<p>Статья рассчитана на тех, кто владеет основами Python и хочет научиться создавать собственные декораторы для повышения качества кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-sozdat-sobstvennye-python-dekoratory-i-pravilno-ih-ispolzovat">Как создать собственные Python-декораторы и правильно их использовать</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 05 Oct 2022 07:42:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>Статья рассчитана на тех, кто владеет основами Python, знаком с декораторами и хочет научиться создавать собственные декораторы для повышения качества кода. Если вы забыли, что такое декораторы, — повторите тему по первым разделам статьи.</p><h2>Что такое декораторы в Python</h2><p>Декоратор — конструкция языка Python для расширения возможностей функций и классов без изменения их кода.</p><p>Есть смартфон. Сделаем его устойчивым к падениям — наденем на него чехол. Чехол не изменяет прежние возможности смартфона, и добавляет к нему качество — ударопрочность. Чехол — декоратор смартфона.</p><p>Один и тот же чехол подходит для всех смартфонов нужной модели. Универсальность — важное свойство декораторов.</p><h2>Анатомия декоратора в Python</h2><p>Создадим декоратор @hello_decorator:</p><p>Декоратор в Python — функция, которая принимает функцию/класс и возвращает функцию/класс. В примере выше декоратор hello_decorator() принимает функцию f(), и возвращает функцию wrapper().</p><p>Пояснения:</p><ol><li>При объявлении функции wrapper() используем встроенный в Python декоратор @wraps. Этот декоратор копирует свойства __name__, __doc__ и другие из функции f() в функцию wrapper(), чтобы при отладке программы все выглядело так, будто wrapper() и есть функция f();</li><li>Аргументы функции wrapper(): *args и **kwargs. Аргумент *args собирает позиционные аргументы, а **kwargs — именованные. Например, в вызове: wrapper(1, ‘a’, x=5, y=None) значение args — кортеж (1, ‘a’), а kwargs — словарь {‘x’: 5, ‘y’: None}. Если позиционных аргументов при вызове функции нет, args — пустой, и если нет именованных аргументов, пустой — kwargs;</li><li>В первой строке тела функции wrapper() в консоль выводится «<i>Hello from decorator!»</i> — единственный «побочный эффект» декоратора. Далее вызывается декорируемая функция f();</li><li>Функция f() внутри wrapper() принимает параметры *args и **kwargs. Операторы * и ** перед именами параметров в вызове функции имеют противоположный эффект случаю, когда их используют при объявлении аргументов. Например, если args=(1, ‘a’) и kwargs={‘x’: 5, ‘y’: None}, вызов f(*args, **kwargs) равносилен: f(1, ‘a’, x=5, y=None). Комбинация «звездочных» аргументов и «звездочных» операторов позволяет универсально передать аргументы из функции wrapper() в f();</li><li>В последней строке функции hello_decorator() возвращаем функцию-обертку wrapper(). Так мы указываем, что нужно подставить на место декорируемой функции f(). Вызывать функцию wrapper() не нужно — возвращаем саму функцию.</li></ol><p>Применим декоратор @hello_decorator к функции sum2():</p><p>Функция sum2() принимает два аргумента и возвращает их сумму. Декорированный sum2() дополнительно выводит на консоль «<i>Hello from decorator!»</i>.</p><p>Синтаксис @hello_decorator введен в Python для удобства, и равносилен такой записи:</p><h2>Настраиваемый логгер-декоратор</h2><p>Наметим логирующий декоратор @Logger для функций с учетом следующих пожеланий:</p><ol><li>Логи отправляются на консоль;</li><li>Вложенность: если логируемая функция вызывает другую логируемую функцию, делаем при выводе на консоль отступ для последней;</li><li>Уровни логов DEBUG, INFO, CRIT: при декорировании можно указать уровень лога функции, который работает в комбинации с настройкой детальности отображения логов. Если уровень лога функции выше или равен текущей детальности, отображаем лог, иначе — игнорируем;</li><li>Для логирования вызова декорированной функции используем шаблон: LOG_LEVEL [TIMESTAMP] FUNC_NAME(ARGS, KWARGS);</li><li>Логирование исключений: если функция выбрасывает исключение, логируем его с уровнем CRIT;</li><li>Внутренние логи: логгер должен работать и в режиме декоратора, и как функция для вывода в лог произвольных сообщений.</li></ol><p>Начнем с примера использования. Так мы не перегружаем внимание внутренней сложностью и повышаем шансы создать удачный интерфейс модуля. На этом принципе основана разработка через тестирование — test-driven development (TTD).</p><p>Что хотим получить:</p><p>После вызова main() хотим увидеть в консоли:</p><p>Наблюдения по поводу декоратора @Logger:</p><ol><li>Logger — не функция, а класс. Экземпляры этого класса можно вызвать. Результат вызова — декоратор;</li><li>У класса Logger есть вложенный класс-перечисление LogLevel с полями DEBUG, INFO, CRIT;</li><li>Детальность логирования verbosity определяется в конструкторе класса Logger, уровень логирования функции log_level задается при ее декорировании;</li></ol><p>У класса Logger есть метод log_msg(), который можно использовать напрямую внутри функций.</p><p>Напишем скелет класса Logger:</p><p>Конструктор класса Logger:</p><p>Метод Logger.log_msg():</p><p>Вспомогательный метод Logger.log_func():</p><p>Ключевой метод-декоратор Logger.__call__():</p><h2>Выводы</h2><ol><li>Проектировать проще сверху-вниз: сначала решите, как хотите пользоваться модулем, а потом реализуйте его;</li><li>Если декоратор требует централизованного контроля, используйте класс;</li><li>Если декоратору нужны дополнительные параметры, используйте замыкание — оберните функцию-декоратор во внешнюю функцию (см. Logger.__call__());</li><li>Экземпляры классов можно использовать как функции за счет __call__();</li><li>Менеджеры контекста удобны, когда нужно «подчищать» состояние.</li></ol>]]></content:encoded>
    </item>
    <item>
      <title>Гайд по магическим методам в Python</title>
      <link>https://tproger.ru/articles/gajd-po-magicheskim-metodam-v-python</link>
      <comments>https://tproger.ru/articles/gajd-po-magicheskim-metodam-v-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Константин Демин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/gajd-po-magicheskim-metodam-v-python</guid>
      <description><![CDATA[<p>Руководство по магическим методам, или dunder-методам Питона, которые предназначены для перегрузки Python-операторов или встроенных методов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/gajd-po-magicheskim-metodam-v-python">Гайд по магическим методам в Python</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 28 Sep 2022 13:52:19 GMT</pubDate>
      <content:encoded><![CDATA[<p>Магические методы в языке программирования Python нужны, чтобы реализовывать свойства объектов при их взаимодействии.<br />В статье раскроем их «магию» и остановимся на каждом из методов подробно.</p><h2>Создание и удаление объектов</h2><p>Любое описание объекта в объектно-ориентированном программировании начинается с создания объекта и его удаления. Давайте подробнее остановимся на каждом из них:</p><p>__new__(cls[, ...]) — метод создания типа класса. Он принимает первым аргументом тип класса, в котором он вызывается, и, как правило, возвращает этот же тип. В основном используется, чтобы настраивать создание экземпляра класса тех объектов, которые наследуются от неизменяемых типов (например, int, str, или tuple).</p><p>__init__(self[, ...]) — конструктор класса. Используется при определении объектов.</p><p>__init_subclass__(cls) — позволяет переопределить создание подклассов объекта. Например, добавлять дополнительные атрибуты:</p><p>__del__(self) — деструктор класса. Вызывается автоматически сборщиком мусора, практически никогда не используется, за исключением, когда пользователя необходимо предупредить о незакрытых дескрипторах.</p><h2>Общие свойства объектов</h2><p>Любой объект может содержать дополнительную информацию, полезную при отладке или приведении типов. Например:</p><p>__repr__(self) — информационная строка об объекте. Выводится при вызове функции repr(...) или в момент отладки. Для последнего этот метод и предназначен. Например:</p><p>__str__(self) — вызывается при вызове функции str(...), возвращает строковый объект. Например:</p><p>__bytes__(self) — аналогично __str__(self), только возвращается набор байт.</p><p>__format__(self, format_spec) — вызывается при вызове функции format(...) и используется для форматировании строки с использованием <a href="https://docs.python.org/3/library/string.html#formatspec">строковых литералов</a>.</p><h2>Методы сравнения объектов между собой</h2><p>__lt__(self, other) — определяет поведение оператора сравнения «меньше», &lt;.</p><p>__le__(self, other) — определяет поведение оператора сравнения «меньше или равно», &lt;=.</p><p>__eq__(self, other) — определяет поведение оператора «равенства», ==.</p><p>__ne__(self, other) — определяет поведение оператора «неравенства», !=.</p><p>__gt__(self, other) — определяет поведение оператора сравнения «больше», &gt;.</p><p>__ge__(self, other) — определяет поведение оператора сравнения «больше или равно», &gt;=.</p><p>__hash__(self) — вызывается функцией hash(...) и используется для определения контрольной суммы объекта, чтобы доказать его уникальность. Например, чтобы добавить объект в set, frozenset, или использовать в качестве ключа в словаре dict.</p><p>__bool__(self) — вызывается функцией bool(...) и возвращает True или False в соответствии с реализацией. Если данный метод не реализован в объекте, и объект является какой-либо последовательностью (списком, кортежем и т.д.), вместо него вызывается метод __len__. Используется, в основном, в условиях if, например:</p><h2>Доступ к атрибутам объекта</h2><p>Доступ ко всем свойствам объекта также контролируются отдельными методами:</p><p>__getattr__(self, name) — вызывается методом getattr(...) или при обращении к атрибуту объекта через x.y, где x — объект, а y — атрибут.</p><p>__setattr__(self, name, value) — вызывается методом setattr(...)или при обращении к атрибуту объекта с последующим определением значения переданного атрибута. Например: x.y = 1, где x — объект, y — атрибут, а 1 — значение атрибута.</p><p>__delattr__(self, name) — вызывается методом delattr(...)или при ручном удалении атрибута у объекта с помощью del x.y, где  x — объект, а y — атрибут.</p><p>__dir__(self) — вызывается методом dir(...) и выводит список доступных атрибутов объекта.</p><h2>Создание последовательностей</h2><p>Любой объект может реализовать методы встроенных последовательностей (словари, кортежи, списки, строки и так далее). Доступ к значениям последовательности переопределяется следующими методами:</p><p>__len__(self) — вызывается методом len(...) и возвращает количество элементов в последовательности.</p><p>__getitem__(self, key) — вызывается при обращении к элементу в последовательности по его ключу (индексу). Метод должен выбрасывать исключение TypeError, если используется некорректный тип ключа, KeyError, если данному ключу не соответствует ни один элемент в последовательности. Например:</p><p>__setitem__(self, key, value) — вызывается при присваивании какого-либо значения элементу в последовательности. Также может выбрасывать исключения TypeError и KeyError. Например:</p><p>__delitem__(self, key) — вызывается при удалении значения в последовательности по его индексу (ключу) с помощью синтаксиса ключевого слова del.</p><p>__missing__(self, key) — вызывается в случаях, когда значения в последовательности не существует.</p><p>__iter__(self) — вызывается методом iter(...) и возвращает итератор последовательности, например, для использования объекта в цикле:</p><p>__reversed__(self) — вызывается методом reversed(...) и аналогично методу __iter__ возвращает тот же итератор, только в обратном порядке.</p><p>__contains__(self, item) — вызывается при проверке принадлежности элемента к последовательности с помощью in или not in.</p><h2>Числовые магические методы</h2><p>Данные методы делятся на несколько групп: унарные операторы, обычные арифметические, отражённые арифметические операторы, составные присваивания и преобразования типов.</p><h3>Унарные операторы</h3><p>__neg__(self) — определяет поведение для отрицания (-a)</p><p>__pos__(self) — определяет поведение для унарного плюса (+a)</p><p>__abs__(self) — определяет поведение для встроенной функции abs(...)</p><p>__invert__(self) — определяет поведение для инвертирования оператором ~</p><h3>Обычные арифметические операторы</h3><p>__add__(self, other) — сложение, оператор +</p><p>__sub__(self, other) — вычитание, оператор -</p><p>__mul__(self, other) — умножение, оператор *</p><p>__matmul__(self, other) — умножение матриц, оператор @</p><p>__truediv__(self, other) — деление, оператор /</p><p>__floordiv__(self, other) — целочисленное деление, оператор //</p><p>__mod__(self, other) — остаток от деления, оператор %</p><p>__divmod__(self, other) — деление с остатком, определяет поведение для встроенной функции divmod(...)</p><p>__pow__(self, other[, modulo]) — возведение в степень, оператор **</p><p>__lshift__(self, other) — двоичный сдвиг влево, оператор &lt;&lt;</p><p>__rshift__(self, other) — двоичный сдвиг вправо, оператор &gt;&gt;</p><p>__and__(self, other) — двоичное И, оператор &amp;</p><p>__xor__(self, other) — исключающее ИЛИ, оператор ^</p><p>__or__(self, other) — двоичное ИЛИ, оператор |</p><h3>Отражённые арифметические операторы</h3><p>Если в обычной арифметике между объектами a и b, объектом, который мы изменяем, является a, и объектом, с которым мы работаем, являетсяb, то в отражённой арифметике наоборот — b является изменяемым, a — объектом, с которым мы работаем, и который передается в качестве аргумента. Например:</p><p>Список методов похож на тот, что используется в обычной арифметике, за исключением того, что добавляется префикс «r» ко всем методам:</p><p>__radd__(self, other) — сложение, оператор +</p><p>__rsub__(self, other) — вычитание, оператор -</p><p>__rmul__(self, other) — умножение, оператор *</p><p>__rmatmul__(self, other) — умножение матриц, оператор @</p><p>__rtruediv__(self, other) — деление, оператор /</p><p>__rfloordiv__(self, other) — целочисленное деление, оператор //</p><p>__rmod__(self, other) — остаток от деления, оператор %</p><p>__rdivmod__(self, other) — деление с остатком</p><p>__rpow__(self, other[, modulo]) — возведение в степень, оператор **</p><p>__rlshift__(self, other) — двоичный сдвиг влево, оператор &lt;&lt;</p><p>__rrshift__(self, other) — двоичный сдвиг вправо, оператор &gt;&gt;</p><p>__rand__(self, other) — двоичное И, оператор &amp;</p><p>__rxor__(self, other) — исключающее ИЛИ, оператор ^</p><p>__ror__(self, other) — двоичное ИЛИ, оператор |</p><h3>Составное присваивание</h3><p>Эти методы — комбинация «обычного» оператора и присваивания. Возвращают тот же тип объекта, который будет присвоен переменной слева. Например:</p><p>__iadd__(self, other) — сложение с присваиванием, оператор +=</p><p>__isub__(self, other) — вычитание с присваиванием, оператор -=</p><p>__imul__(self, other) — умножение с присваиванием, оператор *=</p><p>__imatmul__(self, other) — умножение матриц с присваиванием, оператор @=</p><p>__itruediv__(self, other) — деление с присваиванием, оператор /=</p><p>__ifloordiv__(self, other) — целочисленное деление с присваиванием, оператор //=</p><p>__imod__(self, other) — остаток от деления с присваиванием, оператор %=</p><p>__ipow__(self, other[, modulo]) — возведение в степень с присваиванием, оператор **=</p><p>__ilshift__(self, other) — двоичный сдвиг влево с присваиванием, оператор &lt;&lt;=</p><p>__irshift__(self, other) — двоичный сдвиг вправо с присваиванием, оператор &gt;&gt;=</p><p>__iand__(self, other) — двоичное И с присваиванием, оператор &amp;=</p><p>__ixor__(self, other) — исключающее ИЛИ с присваиванием, оператор ^=</p><p>__ior__(self, other) — двоичное ИЛИ с присваиванием, оператор |=</p><h3>Преобразования типов</h3><p>Помимо всего прочего, в Python множество методов, которые позволяют переопределять поведение встроенных функций преобразования типов, таких как int(...), float(...) и т.д. Например:</p><p>__complex__(self) — преобразование типа в комплексное число</p><p>__int__(self) — преобразование типа к int</p><p>__float__(self) — преобразование типа к float</p><p>__index__(self) — преобразование типа к int, когда объект используется в срезах (выражения вида [start:stop:step])</p><p>__round__(self[, ndigits]) — округление числа с помощью функции round(...)</p><p>__trunc__(self) — вызывается методом math.trunc(...)</p><p>__floor__(self) — вызывается методом math.floor(...)</p><p>__ceil__(self) — вызывается методом math.ceil(...)</p><h2>Вызываемые объекты</h2><p>__call__(self[, args...]) — позволяет любому экземпляру класса вести себя как обычная функция. Например:</p><p>__await__(self) — возвращает итератор, превращая класс в корутину, результат выполнения которой можно получить с помощью await. Подробнее об этом можно узнать в <a href="https://peps.python.org/pep-0492/">PEP 492</a>.</p><h2>Контекстные менеджеры</h2><p>Любой объект может быть представлен как <a href="https://docs.python.org/3/library/stdtypes.html#typecontextmanager">контекстный менеджер</a>, который вызывается с помощью with или async with. Данная конструкция позволяет выполнить какие-либо действия по настройке объекта и при выходе из контекстного менеджера, произвести какие-либо действия по очистке, не смотря на то, было ли вызвано исключение в блоке контекстного менеджера.</p><p>__enter__(self) — определяет начало блока контекстного менеджера, вызванного с помощью with</p><p>__exit__(self, exc_type, exc_value, traceback) — определяет конец блока контекстного менеджера. Может использоваться для контролирования исключений, очистки, или любых действий, которые должны быть выполнены после блока внутри with. Если блок выполнился успешно, то все три аргумента (exc_type, exc_value и traceback) будут установлены в значение None.</p><p>Например:</p><p>__aenter__(self) — аналогично __enter__, только функция возвращает корутину (результат которой можно получить с помощью await)</p><p>__aexit__(self, exc_type, exc_value, traceback) — аналогично __exit__, только функция возвращает корутину (результат которой можно получить с помощью await)</p><p>Например:</p><h2>Неиспользуемые методы</h2><p>Некоторые методы, после полного перехода с Python 2 на Python 3 стали устаревшими и больше не используются.</p><p>__unicode__ — полностью исчез в версии Python 3, вместо него используются отдельные методы __str__и __bytes__</p><p>__div__ — так как в Python 3 теперь по умолчанию «правильное деление», данного метода не существует</p><p>__cmp__ — более не существует, вместо него используются __lt__, __le__, __eq__, __ne__, __gt__и __ge__</p><p>__nonzero__ — переименован в __bool__</p><h2>Упрощение работы с магическими методами</h2><p>Большая часть из вышеописанных методов реализуется библиотеками, они позволяют использовать так называемые «обрёртки» над классами, в которых будут реализованы необходимые магические методы.</p><p>Пример таких библиотек:</p><ul><li><a href="https://docs.python.org/3/library/dataclasses.html">dataclasses</a></li><li><a href="https://attrs.org">attrs</a></li></ul><p>Принципы и идеология этих (и других библиотек) схожи в одном – они позволяют реализовать всю необходимую логику работы с объектом, не дублируя код для каждого отдельного объекта.</p><p>Например:</p><p>Данный объект будет иметь в себе:</p><ul><li>Три атрибута first_name, last_name и age, которые также будут передаваться в конструктор класса;</li><li>Будет реализован метод __repr__ для вывода информации для отладки;</li><li>Будут реализованы все магические методы сравнения (такие как __lt__, __eq__ и так далее), а также метод __hash__.</li></ul><p>Чаще всего вы будете пользоваться уже готовыми решениями, как пример выше, но для того чтобы понять, как работает язык Python изнутри, необходимо знать, как работают магические методы, за что они отвечают, что будет, если вызвать ту или иную функцию на объект, и что с этим объектом произойдёт.</p>]]></content:encoded>
    </item>
    <item>
      <title>Диалоговые платформы и open-source фреймворки для разработки ИИ-ботов: в чем разница и что выбрать</title>
      <link>https://tproger.ru/articles/dialogovye-platformy-i-open-source-frejmvorki-dlja-razrabotki-ii-botov-v-chem-raznica-i-chto-vybrat</link>
      <comments>https://tproger.ru/articles/dialogovye-platformy-i-open-source-frejmvorki-dlja-razrabotki-ii-botov-v-chem-raznica-i-chto-vybrat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Andrey Grabarnik (Just AI)]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/dialogovye-platformy-i-open-source-frejmvorki-dlja-razrabotki-ii-botov-v-chem-raznica-i-chto-vybrat</guid>
      <description><![CDATA[<p>Рассказываем, в чём разница между диалоговыми платформами и open source фреймворками в разработке ИИ-ботов и что из них стоит выбрать.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/dialogovye-platformy-i-open-source-frejmvorki-dlja-razrabotki-ii-botov-v-chem-raznica-i-chto-vybrat">Диалоговые платформы и open-source фреймворки для разработки ИИ-ботов: в чем разница и что выбрать</a>»</p>]]></description>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 12 Jul 2022 12:02:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Голосовые роботы с чит-чатом, или функцией «болталки», свободно поддерживающие разговор с клиентом, умные чат-боты с продвинутой логикой, виртуальные операторы с искусственным интеллектом (ИИ) создаются как правило на базе диалоговых платформ и open-source фреймворков. Разбираемся, в чем сходство и различие инструментов и что выбрать для своего проекта.</p><h2>Общее платформ и фреймворков</h2><p>Для начала определимся с терминами.</p><p>Диалоговые платформы — это стек технологий для прототипирования, разработки, тестирования, развертывания, контроля качества и последующей поддержки всего жизненного цикла разговорного ИИ-решения.</p><p>Фреймворки — это набор инструментов с открытым исходным кодом и высокой степенью контроля над разработкой, все составляющие фреймворка связаны общей идеологией.</p><p>Платформы и фреймворки относятся к самым сложным способам разработки в сфере разговорного ИИ. Чтобы работать с ними, нужны сильные навыки программирования. Оба инструмента позволяют обходить нетипичные ситуации и выводить логику работы бота на более сложный уровень. И платформы, и фреймворки доступны для разных языков программирования.</p><h2>В чем различия</h2><p>Платформы предлагают готовые компоненты, которые сокращают срок разработки. При этом модульность инструмента позволяет подключать внешние сервисы по API и тем самым улучшать работу бота.</p><p>Среди сервисов могут быть собственное NLU-ядро компании (Natural Language Understanding, понимание естественного языка), телефония, ASR (Automatic Speech Recognition, автоматическое распознавание речи) и TTS (Text-to-Speech, синтез речи), CRM- и BI-системы, текстовые каналы.</p><p>Фреймворки не ограничиваются конкретной архитектурой и поэтому не всегда предлагают готовые модули интеграции с текстовыми и телефонными каналами. Если нужного канала нет, его придется создать с нуля или обратиться к платформе.</p><p>Например, если код бота написан на базе фреймворка JAICF, проект можно опубликовать в платформе JAICP, где есть необходимые интеграции.</p><figure><img src="https://help.jaicf.com/assets/images/jaicf-components.png" alt="" /><figcaption>https://help.jaicf.com/</figcaption></figure><p>Таким образом, разработка на фреймворке всегда сложнее и занимает больше времени. Выбирать этот инструмент имеет смысл, если у компании есть собственные сервисы, аналогичные компонентам платформы.</p><p>Фреймворки оставляют разработчику больше свободы действий, так как единственное ограничение — сам язык программирования. Это позволяет гибко оперировать классами и форматами ввода и вывода данных, изменять код под цели проекта и кастомизировать ответы автоматически, в зависимости от класса.</p><p>Фреймворки бесплатны для коммерческого использования, так как они имеют открытый исходный код, диалоговые платформы — нет. Если в базовой версии фреймворка нет нужных функций, интеграций или каналов, их можно добавить за отдельную плату или разработать с нуля.</p><p>Еще одно различие связано с развертыванием готового проекта. Бота на базе фреймворка необходимо самостоятельно разместить в среде выполнения, например, на собственных серверах в облаке или контуре. Платформа предлагает готовые решения по хостингу, масштабированию и балансировке. То же самое относится к NLU. Если бот по проекту понимает естественный язык, NLU-модель требуется развернуть в среде выполнения и, соответственно, масштабировать и балансировать нагрузку.</p><h2>Проекты на базе платформ и фреймворков</h2><p>На базе фреймворка можно сделать бота любого уровня. С помощью платформ можно создавать ботов техподдержки, голосовых ассистентов с разветвленной логикой и чит-чатом, ИИ-операторов для входящей и исходящей телефонии. Многие считают платформы оптимальным инструментом для разработки умного IVR (Interactive Voice Response, интерактивное голосовое меню) и ботов для телефонных обзвонов, так как инфраструктура работы с телефонией уже реализована внутри инструмента.</p><p>Кроме того, платформы часто оснащены полезными фичами для естественного звучания робота в телефонного канале, такими как гибридный синтез, фоновый шум колл-центра, <a href="https://just-ai.com/ru/umnye-zvonki">умные перебивания</a> в зависимости от реплики абонента в случае JAICP.</p><h2>Как выбирать инструмент</h2><p>Примеры платформ: <a href="https://just-ai.com/ru/platforma-jaicp">JAICP</a>, <a href="https://www.sap.com/products/conversational-ai.html#:~:text=SAP%20Conversational%20AI%20is%20a,to%20automate%20tasks%20and%20workflows.">SAP Conversational AI</a>, Intelligent Virtual Assistants <a href="https://ipavlov.ai/banking-ai/ru/tpost/695x9dgf51-platforma-sozdaniya-tsifrovih-assistento">(iVa) Platform</a></p><p>Примеры фреймворков: <a href="https://deeppavlov.ai/">Deep Pavlov</a> — фреймворк для Python,<a href="https://github.com/just-ai/jaicf-kotlin"> JAICF</a> — для Kotlin, <a href="https://www.jovo.tech/framework">Jovo</a> — для TypeScript.</p><figure><img src="https://static.tildacdn.com/tild6362-3762-4430-a666-613463663732/_DeepPavlov_-5.png" alt="" /><figcaption>https://ipavlov.ai/</figcaption></figure><ol><li>Какая команда выделена под проект? Если разработчики компании работают на Python или JavaScript, то целесообразно искать удобное для них решение. По платформам: есть ли внутри хороший SDK и грамотные абстракции?</li><li>Есть ли служба поддержки или сообщество, куда можно обратиться за советом и поддержкой? По фреймворку: обязательно зайдите на Github и найдите дату последнего обновления, оцените активность сообщества.</li><li>NLU. Хватит ли функционала платформы, чтобы научить бота понимать естественную речь, или обучение предлагается только с помощью простейших паттернов? Предусмотрена ли функция интеграции со сторонними NLU по API? По фреймворку: проверьте, что инструмент имеет собственное ядро NLU, так как это самый сложный и важный компонент для разработки в области разговорного ИИ.</li><li>Достаточно ли возможностей инструмента, чтобы бот соответствовал всем изначальным требованиям? Так, для определенных категорий бизнесов, таких как банки и медицинские учреждения, обязательной является установка в контур, а данную возможность предоставляют не все платформы.</li></ol><p>Время на проект. Если проект нужно реализовать в короткие сроки, то простота и скорость разработки на инструменте могут стать определяющим фактором. Срок разработки будет зависеть от того, есть ли готовые компоненты и интеграции, как в платформе, или абсолютно все предстоит разработать с нуля, как в случае с фреймворком.</p>]]></content:encoded>
    </item>
    <item>
      <title>Кто вы — наставник или код-ревьюер? Экспресс-профориентация</title>
      <link>https://tproger.ru/quiz/nastavnik-code-reviewer</link>
      <comments>https://tproger.ru/quiz/nastavnik-code-reviewer?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Чуватова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/quiz/nastavnik-code-reviewer</guid>
      <description><![CDATA[<p>Всего за девять вопросов вы найдете своё предназначение. Для сдачи теста потребуется: желание помогать другим и честные ответы.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/quiz/nastavnik-code-reviewer">Кто вы — наставник или код-ревьюер? Экспресс-профориентация</a>»</p>]]></description>
      <category><![CDATA[Карьера]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Партнёрский материал]]></category>
      <category><![CDATA[Викторины]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 28 Jun 2022 06:14:37 GMT</pubDate>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 6: REST API, middleware, обработка ошибок</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-6-rest-api-middleware-obrabotka-oshibok</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-6-rest-api-middleware-obrabotka-oshibok?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-6-rest-api-middleware-obrabotka-oshibok</guid>
      <description><![CDATA[<p>Продолжаем изучать Golang с помощью видеокурса. Очередной урок посвящён обработке ошибок через middleware.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-6-rest-api-middleware-obrabotka-oshibok">Продвинутый Golang. Урок 6: REST API, middleware, обработка ошибок</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 29 Sep 2021 10:12:28 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этом уроке мы займёмся обработкой ошибок. Сделаем это одним из способов, а именно через middleware.</p><p><a href="https://github.com/theartofdevel/go-advanced-lessons">Репозиторий проекта на GitHub</a></p><p>Предыдущие уроки:</p><p>Урок 1 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server/">REST API и веб-сервер</a>.</p><p>Урок 2 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie/?autoload=1">REST API, логирование</a>.</p><p>Урок 3 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija/?autoload=1">REST API. Конфигурация</a>.</p><p>Урок 4 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb/?autoload=1">REST API, MongoDB, первая часть</a>.</p><p>Урок 5 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-5-rest-api-mongodb-prodolzhenie/?autoload=1">REST API, MongoDB, вторая часть</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 5: REST API, MongoDB, продолжение</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-5-rest-api-mongodb-prodolzhenie</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-5-rest-api-mongodb-prodolzhenie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-5-rest-api-mongodb-prodolzhenie</guid>
      <description><![CDATA[<p>Учимся взаимодействовать с MongoDB в Go. Реализуем методы и проверяем работоспособность системы.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-5-rest-api-mongodb-prodolzhenie">Продвинутый Golang. Урок 5: REST API, MongoDB, продолжение</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 28 Sep 2021 06:51:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>В предыдущем уроке начали работать с MongoDB, написали клиента и реализовали пару методов. В этом уроке сделаем оставшиеся методы и проверим работоспособность системы.</p><p><a href="https://github.com/theartofdevel/go-advanced-lessons">Репозиторий проекта на GitHub</a></p><p>Предыдущие уроки:</p><p>Урок 1 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server/">REST API и веб-сервер</a>.</p><p>Урок 2 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie/?autoload=1">REST API, логирование</a>.</p><p>Урок 3 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija/?autoload=1">REST API. Конфигурация</a>.</p><p>Урок 4 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb/?autoload=1">REST API, MongoDB, первая часть</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 4: REST API, MongoDB</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb</guid>
      <description><![CDATA[<p>В этом видеоуроке мы создадим клиента для работы с MongoDB, используя драйвер для языка Golang.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-4-rest-api-mongodb">Продвинутый Golang. Урок 4: REST API, MongoDB</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 06 Sep 2021 04:57:48 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этом ролике мы начнём работать с MongoDB, напишем клиента и реализуем пару методов.</p><p><a href="https://github.com/theartofdevel/go-advanced-lessons">Репозиторий с кодом уроков</a></p><p>Предыдущие уроки:</p><p>Урок 1 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server/">REST API и веб-сервер</a>.</p><p>Урок 2 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie/?autoload=1">REST API, логирование</a>.</p><p>Урок 3 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija/?autoload=1">REST API. Конфигурация</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 3: REST API. Конфигурация</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija</guid>
      <description><![CDATA[<p>В этом ролике мы добавим конфигурацию проекта, напишем config на YAML и запустим приложение с использованием unix-сокета.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-3-rest-api-konfiguracija">Продвинутый Golang. Урок 3: REST API. Конфигурация</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 24 Aug 2021 12:30:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этом ролике мы добавим конфигурацию проекта, напишем config на YAML и запустим приложение с использованием unix-сокета.</p><p>Для конфигурации мы будем использовать библиотеку Clean Env.</p><p><a href="https://github.com/ilyakaznacheev/cleanenv">Используемая библиотека для конфигурации</a>.</p><p><a href="https://github.com/theartofdevel/go-advanced-lessons">Ссылка на репозиторий с кодом уроков</a>.</p><p>Предыдущие уроки:</p><p>Урок 1 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server/">REST API и веб-сервер</a>.</p><p>Урок 2 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie/?autoload=1">REST API, логирование</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 2: REST API, логирование</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie</guid>
      <description><![CDATA[<p>Второй видеоурок из продвинутого курса по Golang посвящён логированию с использованием логгера logrus.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-2-rest-api-logirovanie">Продвинутый Golang. Урок 2: REST API, логирование</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 20 Aug 2021 01:55:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>В новом видео мы поговорим про логирование в REST API сервисе на примере <a href="https://github.com/sirupsen/logrus">логгера logrus</a>.</p><p>Существует много разных логгеров. Мне понравился logrus. Я написал для него обёртку, он справляется со своей задачей. Вы можете взять любой другой логгер. Это не важно. Важно использовать логирование и использовать его по назначению. Это позволяет понять, что сейчас происходит, что конкретно приложение делает.</p><p>Предыдущие уроки:</p><p>Урок 1 — <a href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server/">REST API и веб-сервер</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Продвинутый Golang. Урок 1: REST API и веб-сервер</title>
      <link>https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server</link>
      <comments>https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[theartofdevel]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server</guid>
      <description><![CDATA[<p>Первый урок продвинутого курса по Go, в котором будет REST API, работа с БД, многопоточность, конфигурация, логирование и многое другое.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/prodvinutyj-golang-urok-1-rest-api-i-veb-server">Продвинутый Golang. Урок 1: REST API и веб-сервер</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 13 Aug 2021 14:20:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы начали новую серию уроков по языку Go. В них будет про REST API, работу с базами данных, многопоточность, конфигурацию и логирование, а также многое другое.</p><p>Также я устроил конкурс. Разыгрываю 2 купона на IDE Goland.</p><p>Правила:</p><ol><li>Подписаться на <a href="https://www.youtube.com/c/TheArtofDevelopment">ютуб-канал</a>.</li><li>Написать комментарий под этим видеоуроком по Golang.</li><li>Присоединиться к группе Telegram или VK (ссылки в описании к видео)</li><li>Написать реализацию функции замыкания (closure), выложить на github.com в виде репозитория или Gist.</li><li>Ссылку на Gist или репозиторий прислать в личку в VK или в Telegram.</li></ol><p>Два победителя (выберет сайт random.org) получат купон на IDE Goland от компании JetBrains.</p><p>Приятного просмотра и желаю удачи в конкурсе!</p>]]></content:encoded>
    </item>
    <item>
      <title>Альтернатива if/else и switch: литералы объектов в JavaScript</title>
      <link>https://tproger.ru/translations/alternativa-if-else-i-switch-literaly-obektov-v-javascript</link>
      <comments>https://tproger.ru/translations/alternativa-if-else-i-switch-literaly-obektov-v-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Олег Борисенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/alternativa-if-else-i-switch-literaly-obektov-v-javascript</guid>
      <description><![CDATA[<p>Литералы объектов заменяют громоздкие условия в JavaScript списком пар ключ-значение: код читается лучше, а вызовов toLowerCase() становится меньше.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/alternativa-if-else-i-switch-literaly-obektov-v-javascript">Альтернатива if/else и switch: литералы объектов в JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 22 Apr 2021 14:57:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сложные условия в JS всегда были источником лишнего кода. Однако использование литералов объектов в JavaScript может избавить вас от этой проблемы. Давайте разберёмся, как это работает.</p><p>Литерал объекта в JavaScript — это список пар ключ-значение, перечисленных через запятую и обёрнутый фигурными скобками.</p><p>Допустим у нас есть функция, которая принимает на вход рифмованную фразу на <a href="https://habr.com/ru/company/englishdom/blog/439476/#:~:text=Кокни%20—%20один%20из%20самых%20известных,Британии%20и%20в%20частности%20Лондона.">английском сленге кокни</a> и возвращает её значение. Если использовать конструкцию if/else, то код будет выглядеть следующим образом:</p><p>Выглядит так себе. Этот код не только плохо читается, но и использует повторяющийся вызов функции toLowerCase().</p><p>Чтобы уменьшить количество кода, мы можем использовать дополнительную переменную или конструкцию switch.</p><p>Такой код выглядит чище, но это не предел. К тому же, в случае использования более сложных условий, можно случайно пропустить break и спровоцировать баги.</p><h2>Альтернатива</h2><p>Мы можем достичь той же функциональности используя объект. Вот пример, который выглядит гораздо аккуратнее:</p><p>Мы используем объект, ключи которого выполняют роль условий, а значения — результатов. Затем, с помощью квадратных скобок, мы проверяем наличие нужной строки. Так как полученная строка может быть null или undefined, то мы используем оператор Nullish coalescing (??). Таким образом мы избавляемся от null-значения, но не исключаем случай, что результат может быть равен нулю или false.</p><p>Это немного надуманный пример, но он иллюстрирует то, как использование ?? помогает избежать багов.</p><p>Подробнее о способах обработки <a href="https://tproger.ru/translations/how-to-handle-undefined-in-javascript/">undefined в JavaScript</a>.</p><h2>Сложная логика</h2><p>Для организации более сложных условий вы можете использовать в качестве значений свойств функции.</p><p>В этом коде мы выбираем необходимую функцию по ключу, а затем вызываем её с двумя аргументами. Так как мы используем опциональную цепочку, то функция вызовется только, если она существует. В противном случае вернётся дефолтное значение.</p><h2>Вывод</h2><p>Каждая условная конструкция имеет свою область применения. Для литералов объектов в JavaScript это длинные списки условий и сложные условия, которые можно реализовать с помощью функций.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как стать Senior разработчиком: определение, навыки, материалы</title>
      <link>https://tproger.ru/articles/kak-stat-senior-razrabotchikom</link>
      <comments>https://tproger.ru/articles/kak-stat-senior-razrabotchikom?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-stat-senior-razrabotchikom</guid>
      <description><![CDATA[<p>Кто такой Senior разработчик, что входит в его обязанности и как им стать? Небольшое руководство для будущих старших программистов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-stat-senior-razrabotchikom">Как стать Senior разработчиком: определение, навыки, материалы</a>»</p>]]></description>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 12 Apr 2021 08:16:32 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сфер в IT много, но путь становления Senior-разработчика плюс-минус одинаков. В этой статье мы поговорим о том, кто такой старший разработчик, какие к нему предъявляются требования и как дорасти до столь солидной должности.</p><ol><li><a href="https://tproger.ru/#part1">Кто такой Senior программист?</a></li><li><a href="https://tproger.ru/#part2">Что должен знать и уметь сеньор разработчик?</a></li><li><a href="https://tproger.ru/#part3">Дополнительные материалы</a></li></ol><h2>Кто такой Senior программист?</h2><p>Если не вдаваться в дебри специализаций, это одна из ступеней развития разработчика в цепочке <b>Junior → Middle → Senior</b>. Перед джуном также может указываться стажёр, а после сеньора — тимлид, но мы рассмотрим именно трёхэтапную схему.</p><h3>Junior разработчик</h3><p>Сразу оговоримся, что чётких научных измерений в этой системе нет. Для каких-то компаний джун — это вчерашний студент с минимальными знаниями и нулевым опытом, а кто-то ищет младших разработчиков с опытом работы от 2-х лет.</p><p>Но практически все сходятся в том, что это наименее ответственное звено, в развитие которого нужно вкладываться, отслеживать и координировать его действия. Скажем, это человек, который, работая, продолжает учиться.</p><h3>Middle разработчик</h3><p>По сути, это «подросший» Junior, который освоил необходимый стек технологий, уверенно обращается с Git, командной строкой, самостоятельно и в срок решает поставленные задачи.</p><p>Кроме того, Middle — это командный игрок, который может и чужой код прочесть, и дельное замечание по нему сделать, если требуется.</p><p>Где-то между джуном и мидлом есть ещё так называемые «уверенные джуны», но это, скорее, не промежуточный этап, а просто попытка компаний оставить уже обросшего опытом специалиста на позиции и зарплате Junior-разработчика. Как только почувствовали, что знаете и можете гораздо больше, чем джун, сразу же обсудите это с руководством.</p><h3>Senior разработчик</h3><p>Вот он, гвоздь программы, способный взять на себя проект и даже руководить отдельным подразделением. Senior Developer обладает обширным багажом знаний, способен стать ментором для Trainee или Junior разработчика, а также улучшать эффективность команды за счёт реализации своих идей.</p><p>Грубо говоря, если вы не нуждаетесь в сторонней помощи и, напротив, сами можете предоставить такую помощь, в числе первых берёте на абордаж новые технологии и не боитесь использовать их на практике, вы Senior программист.</p><h2>Что должен знать и уметь сеньор разработчик?</h2><p>Так как же стать Senior разработчиком? Не стойте на месте: осваивайте новые языки и технологии, записывайтесь на вебинары, бросайтесь в принципиально новые подходы, дабы не пасти задних, и развивайтесь всесторонне.</p><p>Довольно обобщённые советы, не правда ли? Что ж, попытаемся их структурировать и раскрыть подробнее.</p><h3>1. Технические навыки</h3><p>Во-первых, это понимание алгоритмов и умение писать чистый код. Во-вторых, для одной задачи существует множество решений, но именно Senior Developer способен определить, какое из них оптимально в конкретном случае.</p><p>Перед началом работы всегда спрашивайте себя:</p><ol><li>Можно ли разбить таск на подзадачи, и если да, то как?</li><li>Какой подход к решению этой задачи наиболее оптимален?</li><li>Смогу ли я подключить других сотрудников, и как в таком случае организовать рабочий процесс?</li></ol><p>Если для решения проблемы нужно освоить новую СУБД, фреймворк или сторонний сервис, вы осваиваете это.</p><p>Но что реально отличает сеньора от остальных, так это опыт поддержки проектов, даже если речь идёт о всеми забытом и припорошенном пылью легаси коде.</p><p>Кроме того, чтобы думать, как Senior, нужно расширять способы обработки информации. В этом поможет книга Иана Макдермотта и Джозефа О`Коннора <a href="https://www.livelib.ru/book/1000545869-iskusstvo-sistemnogo-myshleniya-neobhodimye-znaniya-o-sistemah-i-tvorcheskom-podhode-k-resheniyu-problem-ian-makdermott">«Искусство системного мышления»</a>. Также есть краткая выжимка основных мыслей в формате аудиокниги:</p><h3>2. Коммуникативные навыки</h3><p>Не будем распыляться о том, что старший разработчик должен хорошо знать английский язык. В любом случае держите <a href="https://tproger.ru/articles/english-for-it/">бесплатные материалы по английскому для айтишников</a>.</p><p>Как вы уже поняли, сеньоры часто ведут проекты самостоятельно, поэтому очень важно уметь обрабатывать ТЗ заказчика, задавать правильные вопросы, которые выльются в исчерпывающие ответы, устанавливать сроки выполнения и выстраивать план работ. Разумеется, проджект-менеджеров никто не отменял, но в идеале старший разработчик может справляться и без их помощи.</p><p>А ещё программирование — это почти всегда работа в команде, поэтому нужно уметь организовать процесс, отталкиваясь от навыков и запросов других сотрудников. Вы должны быть на «ты» с гибкими методологиями разработки, и такие термины, как <a href="https://tproger.ru/explain/agile-is-simple/">Agile, Scrum и Kanban</a>, не должны вас пугать.</p><h3>3. Навыки ментора и интервьюера</h3><p>Подбор персонала и обучение новичков — задачи, которые нередко ложатся на плечи сеньоров. Как человек, прошедший обучение, становление и набивший достаточное количество шишек, Senior-программист прекрасно знает, какие вопросы задавать, чтобы выделить сильные и слабые стороны кандидата, а также как организовать рабочий процесс для тех, кто уже прошёл собеседование.</p><p>Нелишним будет прочесть книги <a href="https://www.livelib.ru/book/1002948897-hbr-guide-emotsionalnyj-intellekt-kollektiv-avtorov">«HBR Guide. Эмоциональный интеллект»</a> и <a href="https://www.livelib.ru/book/1000499433-kak-razgovarivat-s-kem-ugodno-kogda-ugodno-i-gde-ugodno-larri-king">«Как разговаривать с кем угодно, когда угодно и где угодно»</a>.</p><h3>4. Навыки профессионального роста</h3><p>Если вы думаете, что дослужитесь до звания Senior Developer и закончите на этом свой учебный путь, вы ошибаетесь. Старшие разработчики как никто знают, сколь важно постоянно развиваться, осваивая всё новые и новые горизонты. Они никогда не ограничиваются одним набором инструментов, ведь в погоне за оптимизацией рабочего процесса так или иначе приходится обращаться к чему-то новому.</p><p>Если у вас есть боязнь применения новых знаний на практике, также известная как синдром вечного студента, вам нужно её преодолеть, чтобы стать Senior разработчиком.</p><p>Принципиальная разница между джуном и сеньором в том, что первый, как правило, воспринимает обучение как обязаловку, в то время как второй видит в этом ценную необходимость, ключ от закрытых дверей.</p><p>От сеньора редко можно услышать что-то вроде «Ненавижу этот инструмент», поскольку он уже изучил его, выделил все плюсы и минусы, знает, когда его стоит применять, а когда это бесполезная трата времени и ресурсов.</p><h3>5. Навыки работы с сообществом</h3><p>Мало того что старшие программисты хорошо выполняют свою работу, так они ещё и «подтягивают» айтишное комьюнити. Часто выступают с докладами, организовывают митапы, пишут документации и полезные статьи, которые помогают развиваться другим.</p><p>Иными словами, Senior разработчик не закрывается в своём рабочем пространстве, втихую накапливая знания и скрывая их от остальных. Скорее, это те самые гуру, раздающие дельные советы на Stack Overflow. Они любят делиться своим опытом, так как это:</p><ul><li>помогает взращивать новые поколения талантливых разработчиков и способствует развитию IT в целом;</li><li>в ходе общения позволяет почерпнуть полезную информацию и от самого сообщества.</li></ul><h2>Вместо эпилога</h2><p>Всё сказанное уже сказано, поэтому просто держите несколько дополнительных материалов, чтобы лучше понять, как стать Senior разработчиком:</p><ul><li><a href="https://tproger.ru/experts/junior-middle-senior-developers-differences/">Отличия junior, middle и senior разработчиков — объясняют эксперты</a></li><li><a href="https://habr.com/ru/post/503454/">Практическое руководство как стать Senior Frontend Developer</a></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Задания с собеседования на позицию Middle Backend разработчика с примерами на Python</title>
      <link>https://tproger.ru/articles/zadanija-s-sobesedovanija-na-poziciju-middle-backend-razrabotchika-s-primerami-na-python</link>
      <comments>https://tproger.ru/articles/zadanija-s-sobesedovanija-na-poziciju-middle-backend-razrabotchika-s-primerami-na-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/zadanija-s-sobesedovanija-na-poziciju-middle-backend-razrabotchika-s-primerami-na-python</guid>
      <description><![CDATA[<p>Реальные задачи, которые дают Python-разработчикам в highload-проект, чтобы понять, как кандидат мыслит и выбирает подход к решению.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/zadanija-s-sobesedovanija-na-poziciju-middle-backend-razrabotchika-s-primerami-na-python">Задания с собеседования на позицию Middle Backend разработчика с примерами на Python</a>»</p>]]></description>
      <category><![CDATA[Собеседование]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 01 Feb 2021 09:47:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>Когда вы собеседуете разработчика на позицию Middle и выше, то задавать на собеседовании простые задачи на совсем базовые знания языка не следует. Важнее понять, насколько данный разработчик может самостоятельно работать, как мыслит и рассуждает, какие вопросы задает и насколько хорошо умеет выбирать технологию или подход для решения поставленной задачи. Соответственно, некоторые задачи, которые мы рассмотрим ниже, могут иметь несколько правильных решений, при этом интервьюер может скорректировать условия задачи так, чтобы проверить те или иные знания кандидата. Ниже приведены некоторые реальные задачи из собеседований на Python разработчика в SUNLIGHT (highload проект с более чем 1 млн сессий в день).</p><h2>Задача #1</h2><p>Ваша компания отправляет СМС с трекинговой ссылкой, но ссылка достаточно длинная и из-за этого СМС выходит за 70 символов (длина 1 СМС). Необходимо спроектировать сервис-«укорачиватель ссылок», чтобы сэкономить деньги компании. Интервьюер при этом выступает заказчиком со стороны бизнеса и ему можно задавать вопросы по сути задачи.</p><h2>Задача #2</h2><p>Загрузка из «плохого» API большого количества данных и их синхронизация с табличкой в БД (например, Postgres). Считаем, что на входе мы скачиваем JSON-массив из N (&gt;100k) объектов (dict) заданной структуры (primary key поле + некоторое количество строковых полей). Считаем, что нам надо раз в некоторое время запускать функцию, которая создаст записи, которые есть в JSON, но их нет в базе, а далее обновит строковые поля там, где что-то поменялось, и пометить удаленными записи, которых нет в JSON, но они все еще есть в базе.</p><h2>Задача #3</h2><p>Набросать код, который будет рассчитывать угол между часовой и минутной стрелкой в заданное время. Данная задача покажет логическое мышление кандидата, и как быстро он сможет придумать решение.</p><h2>Задача #4</h2><p>Что такое итераторы/генераторы/генераторные выражения, чем они отличаются и когда и как все это нужно использовать.</p><h2>Задача #5</h2><p>Серия вопросов по СУБД (в частности Postgres). По всем пунктам можно погрузиться в достаточно низкоуровневые детали реализации, но важно и в целом оценить осведомленность кандидата об основных принципах работы базы, достаточных для большинства задач.</p><p>Что такое транзакция? Приведите пример, где это может пригодиться. Расскажите про свойства транзакций и уровень изолированности.</p><p>Что такое server side cursor и зачем он нужен?</p><p>Что такое VACUUM и зачем он нужен в PostgreSQL?</p><p>Что такое EXPLAIN? Какая разница между ним и EXPLAIN ANALYZE?</p>]]></content:encoded>
    </item>
    <item>
      <title>7 советов DevOps-инженера, которые будут полезны любому программисту</title>
      <link>https://tproger.ru/articles/7-sovetov-devops-inzhenera-kotorye-budut-polezny-ljubomu-programmistu</link>
      <comments>https://tproger.ru/articles/7-sovetov-devops-inzhenera-kotorye-budut-polezny-ljubomu-programmistu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/7-sovetov-devops-inzhenera-kotorye-budut-polezny-ljubomu-programmistu</guid>
      <description><![CDATA[<p>Подборка практических рекомендаций для разработчиков, которые хотят прокачать тему DevOps: от перехода на VSCode до приёмов работы в Linux и WSL.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/7-sovetov-devops-inzhenera-kotorye-budut-polezny-ljubomu-programmistu">7 советов DevOps-инженера, которые будут полезны любому программисту</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 28 Jan 2021 06:54:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодня я поделюсь с вами советами, которые, я надеюсь, помогут вам в вашей ежедневной работе. Статья рассчитана на начинающих и не очень разработчиков, которые хотят прокачать тему DevOps. Некоторые советы возможно вы уже читали ранее или слышали от коллег, а что-то будет в новинку.</p><p>Все советы написаны с расчетом на пользователей ОС семейства Linux, но если вы используете ОС Windows почти все рекомендации вам также пригодятся, особенно если вы знакомы с подсистемой WSL.</p><h2>Попробуйте VSCode в качестве вашей новой IDE</h2><p>Все еще используете Sublime Text, Emacs или IDEA? Попробуйте VSCode, написанный на веб-движке <a href="https://ru.wikipedia.org/wiki/Electron">Electron</a>. Огромное количество удобных расширений позволяет быстро и удобно работать с исходным кодом, вашим любимым языком программирования, git-репозиториями и т.п.</p><h2>Запомните комбинации клавиш</h2><p>Все еще ставите комментарии в коде по старинке вручную? Изучите горячие клавиши своей IDE и старайтесь их применять почаще. К примеру, для VSCode базовые комбинации можете подсмотреть <a href="https://code.visualstudio.com/docs/getstarted/keybindings#_basic-editing">здесь.</a></p><p>Сдвинуть строчку влево или вправо? Легко!  Комбинация «Ctrl+]» сдвинет вправо, «Ctrl+[» — влево.</p><p>Закомментировать 3 строчки? Еще легче. Выделяем строки (необязательно полностью) и нажимаем «Ctrl+/».</p><p>Вот увидите, как уменьшится время, затрачиваемое на написание кода, работать станет гораздо комфортнее.</p><h2>Прокачайте навыки использования Git</h2><p>Чаще всего в качестве системы контроля версий используется Git. Если у вас нет опыта или вы знаете только git clone &lt;repo&gt;, то посоветую вам <a href="https://learngitbranching.js.org/?locale=ru_RU">ресурс</a> с довольно понятным и наглядным обучением.</p><h2>Попробуйте Docker</h2><p>Упаковка вашего приложения в Docker поможет cохранить вашу систему чистой, а рабочее окружение для приложения – одинаковым. Тем более, что есть множество публичных базовых образов именно с нужным вам окружением. А если, например, вам нужно поднять не только приложение, но и рядом стоящую инфраструктуру, то пригодится docker-compose.</p><h2>Создайте свои собственные короткие команды (алиасы)</h2><p>Если вы пользуетесь консолью для, например, запуска какого-нибудь контейнера Docker, то вам знакома команда docker run &lt;opt1&gt; &lt;opt2&gt; &lt;image&gt;. Зачем каждый раз набирать или искать эту команду в истории?</p><p>Создайте алиас для свой команды:</p><ol><li>Откройте ~/.bash_profile.</li><li>Добавьте новую строчку с вашим алиасом &lt;alias&gt;=&lt;your long command&gt;.</li></ol><p>К примеру, так можно запускать одноразовый контейнер в интерактивном режиме по команде docrun &lt;image&gt; (такой контейнер будет удален сразу же после остановки):</p><p>alias docrun="docker run -it --rm $@"</p><p>Для передачи дополнительных параметров, таких как имя образа и ключи запуска, используется bash-переменная «$@», в которую передается все, что идет после  алиаса.</p><p>Еще, в качестве примера, можно создать алиас на переход в часто используемые директории:</p><p>alias fdir1="cd ~/mycoolproj1"</p><p>Далее используя наш алиас fdir1, мы сразу будем попадать в наш проект. Удобно? Удобно!</p><h2>Попробуйте заменить интерактивную оболочку</h2><p>Замените обычный bash на zsh с дополнением oh-my-zsh, которые позволят вам использовать различные плагины, упрощающие жизнь. Автодополнение команд и их ключей, напоминание об алиасах, умная история команд и настройка внешнего вида консоли по своему вкусу.</p><h2>Используйте meld для сравнения файлов или даже директорий</h2><p>Возможно я не умею готовить консольный diff, но его мне всегда не хватает для сравнения двух директорий или файлов, а также для их быстрого слияния.</p><p>Порекомендую программу <a href="https://meldmerge.org/">Meld</a> с графическим интерфейсом, которая интуитивно понятна в использовании. Есть дистрибутив как для Windows, так и для Linux.</p><p>На этом все, спасибо за внимание!</p><p>Ну, а если вам самим есть чем поделиться с сообществом – добро пожаловать в комментарии.</p>]]></content:encoded>
    </item>
    <item>
      <title>Собеседование на позицию Middle JavaScript разработчика: примеры задач и необходимые знания</title>
      <link>https://tproger.ru/problems/sobesedovanie-na-poziciju-middle-javascript-razrabotchika-primery-zadach-i-neobhodimye-znanija</link>
      <comments>https://tproger.ru/problems/sobesedovanie-na-poziciju-middle-javascript-razrabotchika-primery-zadach-i-neobhodimye-znanija?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/problems/sobesedovanie-na-poziciju-middle-javascript-razrabotchika-primery-zadach-i-neobhodimye-znanija</guid>
      <description><![CDATA[<p>Разбор задач с интервью на позицию Middle JavaScript-разработчика и советы по подготовке: сам язык, структуры данных и подход к решению на собеседовании.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/problems/sobesedovanie-na-poziciju-middle-javascript-razrabotchika-primery-zadach-i-neobhodimye-znanija">Собеседование на позицию Middle JavaScript разработчика: примеры задач и необходимые знания</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Собеседование]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Задачки]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 27 Jan 2021 13:06:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многие разработчики не любят, когда на собеседовании их просят писать код, но иногда это неизбежно. В этом материале я разберу несколько задач, с которыми вы можете столкнуться при прохождении интервью на позицию Middle JavaScript разработчика.</p><p>Какие знания понадобятся:</p><ul><li>JavaScript;</li><li>базовые структуры данных;</li><li>большое O.</li></ul><p>Как подойти к решению задач:</p><ul><li>размышляйте вслух;</li><li>не торопитесь и не сдавайтесь;</li><li>работайте с энтузиазмом.</li></ul><h2>JavaScript</h2><p>Не могу не подчеркнуть, насколько важно в первую очередь хорошо изучить сам язык. Это поможет написать элегантное решение, не запутавшись в его тонкостях. Для этого вы должны не только знать все элементы и правила языка, но и иметь глубокое понимание их работы.</p><p>Ресурсы для изучения:</p><ul><li>«Современный учебник JavaScript» (перевод Ильи Кантора);</li><li>Eloquent JavaScript («Красноречивый JavaScript») (автор Marijn Haverbeke);</li><li>JavaScript Ninja (автор John Resig).</li></ul><h2>Структуры данных</h2><p>Если вы не знаете структуры данных, шансов прийти к оптимальному решению (и решению вообще) намного меньше. Поэтому нужно обязательно научиться работать со следующими вещами:</p><ul><li>массивами;</li><li>объектами/Хэш-мапами;</li><li>связанными списками;</li><li>стеками;</li><li>очередями;</li><li>деревьями;</li><li>графами.</li></ul><h2>Большое О</h2><p>О большое описывает эффективность выполнения кода — то есть то, насколько быстро выполняется алгоритм с учётом входных данных (time complexity).</p><p>После того, как вы напишете свое решение, важно назвать его сложность и время выполнения.</p><p>Большое О к задаче пишется комментарием над решением.</p><p>Пример:</p><p>// O(n) time | O(1) space</p><p>Определив эффективность своего кода, обсудите с собеседующим, что еще можно улучшить. По возможности предложите альтернативные способы решения задачи. Это покажет, что вы не просто выдаете заученные варианты, а действительно понимаете, как работает код.</p><h2>Размышляйте вслух</h2><p>Перед тем, как начать писать код, задачу нужно решить в уме, проговаривая весь мыслительный процесс. Так вы не запутаетесь, а собеседующий сможет оценить ваш ход мыслей.</p><h2>Не торопитесь и не сдавайтесь</h2><p>Одна из частых ошибок – отдавать готовое решение без перепроверки. Перед сдачей работы обязательно пройдитесь по своему коду ещё раз, анализируя его вслух.</p><p>Не поддавайтесь панике и не опускайте руки, если подойти к решению никак не получается. Это ещё не конец, и вы всё ещё можете успешно пройти интервью. Обдумайте проблему и попросите у собеседующего небольшую подсказку – это лучше, чем молча сидеть и надеяться, что вам помогут.</p><h2>Работайте с энтузиазмом</h2><p>Покажите неподдельную заинтересованность в решении задачи – это разрядит обстановку и облегчит процесс и для вас, и для собеседующего. Почувствуйте азарт, действуйте с пламенем в глазах и пылом в сердце – тогда и сконцентрироваться на задаче будет намного проще.</p><h2>Задачи уровня Middle</h2><h3>Сумма двух чисел</h3><p>Напишите функцию, которая принимает два аргумента: массив из уникальных целых чисел и сумму в виде целого числа. Если сумма двух любых чисел массива из аргумента равна числу, которое приходит вторым аргументом, функция должна вернуть новый массив из этих двух чисел в любом порядке. Если решения нет, вернуть пустой массив. Текущее число само с собой складывать нельзя.</p><p>Пример входных данных:</p><p>На выходе:</p><p>[-1, 11] или [11, -1], так как -1 + 11 = 10 = targetSum</p><h3>Преобразование массива в объект с группировкой и фильтрацией элементов</h3><p>Напишите функцию, которая на вход принимает массив из студентов, где студент — это объект с полями «имя», «возраст» и «номер группы» {name: string, age: number, groupId: number}, а на выходе возвращает объект, где ключ — это номер группы, а значение — массив из студентов старше 17 лет.</p><h3>Проверка строки на палиндром</h3><p>Напишите функцию, которая на вход принимает строку, состоящую из букв нижнего регистра, а на выход возвращает boolean, который отвечает, является ли данная строка палиндромом или нет.</p><p>Палиндром — слово или текст, одинаково читающееся в обоих направлениях.</p><h3>Найти ближайшее значение в бинарном дереве</h3><p>Напишите функцию, которая принимает два аргумента — бинарное дерево и значение в виде числа, а возвращает ближайшее значение, найденное в бинарном дереве.</p><p>Node = { value: number | null, left: Node | null, right: Node | null }</p><h2>Итог</h2><p>На собеседовании вы можете столкнуться с совершенно другими задачами. Пугаться их не стоит. Основная цель этих заданий в том, чтобы разработчик озвучил свой способ мышления. На ошибки и не до конца реализованную в коде задачу скорее всего закроют глаза. Интервьюеру важно увидеть, как вы рассуждаете, какие подходы используете в зависимости от ситуации, как быстро можете читать и понимать чужой код. Именно на основе этой информации он будет строить с вами дальнейший разговор и принимать решение о найме.</p><p>Поэтому дерзайте и удачи на собеседовании 🙂</p>]]></content:encoded>
    </item>
    <item>
      <title>Стоит прочитать: обзор книги Маркуса Винанда «SQL Performance explained»</title>
      <link>https://tproger.ru/books/obzor-knigi-markusa-vinanda-sql-performance-explained</link>
      <comments>https://tproger.ru/books/obzor-knigi-markusa-vinanda-sql-performance-explained?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/books/obzor-knigi-markusa-vinanda-sql-performance-explained</guid>
      <description><![CDATA[<p>Книга объясняет, что разработчику нужно знать о SQL и реляционных базах, и показывает, где на самом деле кроется корень проблем с производительностью.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/books/obzor-knigi-markusa-vinanda-sql-performance-explained">Стоит прочитать: обзор книги Маркуса Винанда «SQL Performance explained»</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Стоит прочитать]]></category>
      <category><![CDATA[Книги]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 25 Dec 2020 06:01:40 GMT</pubDate>
      <content:encoded><![CDATA[<p>Очень многие веб-ресурсы (сайты, интернет-магазины, соц. сети и так далее) применяют для хранения данных реляционные базы данных (Oracle, MySQL, PostgreSQL и другие) с использованием языка SQL. Считаю, что книга обязательна к прочтению всем разработчикам (и не только), кто работает с языком SQL и с базами данных (БД). Книга станет путеводителем в мир SQL и анализа реляционных баз данных.</p><p>Многие разработчики сталкиваются с проблемами производительности SQL. Даже бытует мнение, что SQL медленный. Да, отчасти, это действительно так, но только отчасти. Корень проблемы лежит в том, как построена БД и как вы запрашиваете данные. На помощь приходит индексирование базы данных.</p><p>Например, самый простой SQL-запрос читается как предложение на английском языке:</p><p>Написание простых запросов к базе данных, в основном, не требует от разработчика глубоких знаний о её внутренней работе. И когда дело доходит до производительности, разработчик всегда «тыкает пальцем» в базу данных, указывая на медленное выполнение запроса и снимая с себя ответственность. Однако, опыт говорит об обратном, разработчик должен иметь представление о внутренней работе SQL и индексировании данных. Только в этом случае он сможет написать правильный запрос и получить быстрый ответ от базы данных. Фактически решение о необходимости индексирования должно ложиться на плечи разработчика. Этот этап является одним из важнейших этапов при работе с SQL, так как только при индексировании база данных будет правильно понимать «как получить требуемую информацию».</p><p>На страницах книги «SQL Performance explained» автор рассказывает о том, что нужно знать разработчику об индексах, а точнее о B-tree индексах, как с ними работать и применять на практике.</p><h2>Итак, подробнее о книге</h2><p>Структура книги хорошо адаптирована для разработчиков, большинство глав соответствуют определённой части оператора SQL:</p><ul><li>Anatomy of an Index.</li><li>The Where Clause.</li><li>Performance and Scalability.</li><li>The Join Operation.</li><li>Clustering Data.</li><li>Sorting and Grouping.</li><li>Partial Results.</li><li>Insert, Delete and Update.</li><li>Execution Plans.</li></ul><p>Это поможет быстро ориентироваться по содержанию книги, когда вы будете применять знания на практике.</p><p>Глава Anatomy of an Index не содержит в себе никаких примеров SQL-запросов, но описывает структуру индекса. Это понимание обязательно для последующих глав. Здесь вы найдёте что такое Slow Index.</p><p>Глава The Where Clause является самой объёмной и содержит максимум описания оператора WHERE. Прочтение этой главы помогло мне отойти от простых выборок по одной колонке таблицы. Применяю сложные запросы с выборкой по нескольким колонкам из нескольких таблиц, используя диапазоны (для выборки дат) и другие условия. После изучения данной главы я смог оптимизировать текущий код нагруженного интернет-магазина и повысить производительность поиска товаров в три раза. Первоначально сложная фильтрация товаров выполнялась в несколько запросов и влекла за собой проблему с паджинацией на странице. После добавления индексов и фильтрации товаров по одному SQL-запросу, скорость выдачи увеличилась в три раза.</p><p>В главе Performance and Scalability описывается как правильно выполнить анализ производительности запросов, выявить узкие места в запросе и рассказывается о масштабировании базы без изменения железа сервера. Автор на примере выборки количества строк показывает какая разница между выполнением запроса в таблицах с разными объёмами данных и как на это влияет применение индексов.</p><p>Главы The Join Operation и Sorting and Grouping объясняют применение индексов для быстрого соединения таблиц, сортировки и группировки данных.</p><p>Сортировка и группировка — весьма ресурсоёмкие операции. Основная проблема заключается в том, что база данных должна временно буферизовать результаты. Операции сортировки и группировки должны прочитать весь ввод, прежде чем смогут произвести первый вывод.</p><p>И тут на помощь приходят индексы, которые хранят данные предварительно отсортированным способом.</p><p>Если требуется получить больше данных, которые хранятся в нескольких связанных таблицах, операция JOIN позволит избежать множественных запросов к базе данных. Моя практика показывает, что менее ресурсоёмким является один большой запрос в несколько таблиц, в отличие от нескольких мелких (беря во внимание большие объёмы данных).</p><p>В главе Clustering Data автор объясняет понятие кластеризации данных и как это влияет на производительность запросов. Кластеризация данных означает хранение данных, к которым последовательно осуществляется доступ, близко друг к другу, чтобы для доступа к ним требовалось меньше операций ввода-вывода. Тут индексы также приходят на помощь и дают возможность кластеризовать данные для повышения производительности запросов.</p><p>Глава Partial Results. В этой главе показано, как использовать конвейерный порядок для эффективного получения частичных результатов. Данный подход поможет повысить производительность запросов. Рассмотрим, например, кейс с социальной сетью и выдачей последних 10 сообщений пользователю. Применение обычной сортировки всех сообщений и ограничение по лимиту выдачи вызовет серьёзные проблемы с производительностью, так как объём таблицы с сообщениями может быть огромным.</p><p>В главе Insert, Delete and Update подробно объясняется, как влияет применение индексов на производительность запросов записи. Использование индексов должно быть продуманным, поскольку всё имеет свою цену.</p><p>Я помню, что долго изучал причину длительного импорта товаров при использовании запроса INSERT. Конечно, я старался максимально использовать индексы в таблицах для увеличения производительности в выборке данных. Из книги я почерпнул, что чем больше индексов в таблице, тем медленнее выполняется INSERT, так как база данных должна убедиться, что новая запись также найдена через индексы. По этой причине он должен добавить новую запись в каждый индекс этой таблицы.</p><p>Запрос INSERT — единственный запрос, который не получает никакой выгоды от использования индексов в базе, потому что не использует условие WHERE. Данное поведение показало мне «цену» большого количества индексов. Теперь я стараюсь не злоупотреблять индексами в таблицах.</p><p>Запрос DELETE очень похож на запрос выборки данных, но с дополнительным шагом для удаления строк. Шаг удаления аналогичен вставке новой строки. И тут нужно понимать, что есть шаг, который не «любит» индексы, но без них будет выполняться полное чтение таблицы для нахождения нужных строк. Таким образом, для запроса DELETE  индексы просто необходимы.</p><p>Запрос UPDATE содержит в себе как удаление старой записи, так и вставку новой. Но есть главное отличие: данный запрос затрагивает не все столбцы, соответственно, и не все индексы таблицы, что повышает производительность запроса по отношению к INSERT и DELETE</p><p>Глава Execution Plans поможет разработчику изучить функциональность запроса EXPLAIN и провести полный анализ запросов и данных. Пожалуй, это одна из важнейших глав в книге, которая направлена на приобретение навыка анализа и оптимизации запросов. Изучив данную главу, я смог выполнить анализ своего проекта (интернет-магазин), выявить узкие места при выборке данных, добавить необходимые индексы и получить значительный прирост в производительности.</p><p>Каждая из описанных глав содержит отличные примеры, которые помогут вам полностью разобрать и усвоить теоретический материал.</p><h2>Выводы</h2><p>Самые важные главы книги «SQL Performance explained» для меня:</p><ul><li>Anatomy of an Index.</li><li>Performance and Scalability.</li><li>Execution Plans.</li></ul><p>Изучив эти главы, я смог чётко уяснить: для чего нужны индексы и как они работают, как нужно писать запросы и строить структуру БД для значительного повышения производительности.</p><p>Узнал много интересной информации о Slow indexes (да, бывают и такие!), Explain запросах и как с ним работать. И самое главное: как строить, развивать и улучшать БД для хранения и получения большого объёма данных.</p><p>В заключение хочу ещё раз отметить, что данная книга обязательна к прочтению всем разработчикам. Она поможет ответить на большинство вопросов, связанных с производительностью БД, «прокачает» вас как разработчика нагруженных систем и проектов и даст хорошее понимание о работе SQL.</p>]]></content:encoded>
    </item>
    <item>
      <title>Всё о сортировке в Python: исчерпывающий гайд</title>
      <link>https://tproger.ru/translations/python-sorting</link>
      <comments>https://tproger.ru/translations/python-sorting?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Картофельный Повелитель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/python-sorting</guid>
      <description><![CDATA[<p>Как сортировать списки, словари и объекты в Python. Примеры sorted(), .sort(), lambda-ключей и кастомной сортировки. Разберитесь раз и навсегда.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/python-sorting">Всё о сортировке в Python: исчерпывающий гайд</a>»</p>]]></description>
      <category><![CDATA[Алгоритмы и структуры данных]]></category>
      <category><![CDATA[Сортировка и поиск]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 16 Dec 2020 08:30:20 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://tproger.ru/articles/python-roadmap">Сортировка в Python</a> выполняется функцией sorted(), если это итерируемые объекты, и методом list.sort(), если это список. Рассмотрим подробнее, как это работало в старых версиях и как работает сейчас.</p><p>• sorted() возвращает новый отсортированный список из любого итерируемого объекта, а list.sort() изменяет список на месте
• Параметр key позволяет задать функцию сортировки — от простых lambda до operator.itemgetter() и attrgetter()
• Сортировка в Python стабильна (Timsort) — элементы с одинаковыми ключами сохраняют исходный порядок
• Параметр reverse=True сортирует по убыванию без потери стабильности
• Для сложных сортировок можно комбинировать несколько последовательных вызовов sorted() благодаря стабильности алгоритма</p><p>Примечание Вы читаете улучшенную версию некогда выпущенной нами статьи.</p><h2>Основы сортировки</h2><p>Так как отсортировать список Python? Для сортировки по возрастанию достаточно вызвать функцию сортировки Python sorted(), которая вернёт новый отсортированный список:</p><p>Для сортировки списка Python также можно использовать метод списков list.sort(), который изменяет исходный список (и возвращает None во избежание путаницы). Обычно Python sort list не так удобен, как использование sorted(), но если вам не нужен исходный список, то так будет немного эффективнее:</p><p>Прим.перев. В Python вернуть None и не вернуть ничего — одно и то же.</p><p>Ещё одно отличие заключается в том, что метод list.sort() определён только для списков, в то время как функция sorted Python работает со всеми итерируемыми объектами. Грубо говоря, функция sort Python сортирует список и сохраняет его в отсортированном виде, в то время как функция sorted Питон создаёт новый отсортированный список без изменения исходного.</p><p>Прим.перев. При итерировании по <a href="https://tproger.ru/explain/python-dictionaries">словарю</a> Python возвращает его ключи. Если вам нужны их значения или пары «ключ-значение», используйте методы dict.values() и dict.items() соответственно.</p><p>Рассмотрим основные функции сортировки Python.</p><h2>Функции-ключи</h2><p>С версии Python 2.4 у list.sort() и sorted() появился параметр key для указания функции, которая будет вызываться на каждом элементе до сравнения. Вот регистронезависимое сравнение строк:</p><p>Значение key должно быть функцией, принимающей один аргумент и возвращающей ключ для сортировки. Работает быстро, потому что функция-ключ вызывается один раз для каждого элемента.</p><figure><img src="https://media.tproger.ru/uploads/2023/03/2.764x92-1.png" alt="" /></figure><p>Часто можно встретить код, где сложный объект сортируется по одному из его индексов. Например:</p><p>Тот же метод работает для объектов с именованными атрибутами:</p><h2>Функции модуля operator</h2><p>Показанные выше примеры функций-ключей встречаются настолько часто, что Python предлагает удобные функции, чтобы сделать всё проще и быстрее. Модуль <a href="https://docs.python.org/3/library/operator.html#module-operator">operator</a> содержит функции itemgetter(), attrgetter() и, начиная с Python 2.6, methodcaller(). С ними всё ещё проще:</p><p>Функции operator дают возможность использовать множественные уровни сортировки массива Python. Отсортируем учеников сначала по оценке, а затем по возрасту:</p><p>Используем функцию methodcaller() для сортировки учеников по взвешенной оценке:</p><h2>Сортировка по возрастанию и сортировка по убыванию в Python</h2><p>list.sort() и sorted() есть параметр reverse, принимающий boolean-значение. Он нужен для обозначения сортировки по убыванию. Отсортируем учеников по убыванию возраста:</p><h2>Стабильность сортировки и сложные сортировки в Python</h2><p>Начиная с версии Python 2.2, сортировки гарантированно стабильны: если у нескольких записей есть одинаковые ключи, их порядок останется прежним. Пример:</p><p>Обратите внимание, что две записи с 'blue' сохранили начальный порядок. Это свойство позволяет составлять сложные сортировки путём постепенных сортировок. Далее мы сортируем данные учеников сначала по возрасту в порядке возрастания, а затем по оценкам в убывающем порядке, чтобы получить данные, отсортированные в первую очередь по оценке и во вторую — по возрасту:</p><p>Алгоритмы сортировки Python вроде <a href="https://ru.wikipedia.org/wiki/Timsort">Timsort</a> проводят множественные сортировки так эффективно, потому что может извлечь пользу из любого порядка, уже присутствующего в наборе данных.</p><h2>Декорируем-сортируем-раздекорируем</h2><ol><li>Сначала исходный список пополняется новыми значениями, контролирующими порядок сортировки.</li><li>Затем новый список сортируется.</li><li>После этого добавленные значения убираются, и в итоге остаётся отсортированный список, содержащий только исходные элементы.</li></ol><p>Вот так можно отсортировать данные учеников по оценке:</p><p>Это работает из-за того, что кортежи сравниваются лексикографически, сравниваются первые элементы, а если они совпадают, то сравниваются вторые и так далее.</p><p>Не всегда обязательно включать индекс в декорируемый список, но у него есть преимущества:</p><ol><li>Сортировка стабильна — если у двух элементов одинаковый ключ, то их порядок не изменится.</li><li>У исходных элементов не обязательно должна быть возможность сравнения, так как порядок декорированных кортежей будет определяться максимум по первым двум элементам. Например, исходный список может содержать комплексные числа, которые нельзя сравнивать напрямую.</li></ol><p>Ещё эта идиома называется <a href="https://ru.wikipedia.org/wiki/Преобразование_Шварца">преобразованием Шварца</a> в честь Рэндела Шварца, который популяризировал её среди Perl-программистов.</p><p>Для больших списков и версий Python ниже 2.4, «декорируем-сортируем-раздекорируем» будет оптимальным способом сортировки. Для версий 2.4+ ту же функциональность предоставляют функции-ключи.</p><h2>Использование параметра cmp</h2><p>Все версии Python 2.x поддерживали параметр cmp для обработки пользовательских функций сравнения. В Python 3.0 от этого параметра полностью избавились. В Python 2.x в sort() можно было передать функцию, которая использовалась бы для сравнения элементов. Она должна принимать два аргумента и возвращать отрицательное значение для случая «меньше чем», положительное — для «больше чем» и ноль, если они равны:</p><p>Можно сравнивать в обратном порядке:</p><p>При портировании кода с версии 2.x на 3.x может возникнуть ситуация, когда нужно преобразовать пользовательскую функцию для сравнения в функцию-ключ. Следующая обёртка упрощает эту задачу по Python:</p><p>Чтобы произвести преобразование, оберните старую функцию:</p><p>В Python 2.7 функция cmp_to_key() была добавлена в модуль functools.</p><h2>Поддержание порядка сортировки</h2><p>В стандартной библиотеке Python нет модулей, аналогичных типам данных C++ вроде set и map. Python делегирует эти задачи сторонним библиотекам, доступным в <a href="https://pypi.org">Python Package Index</a>: они используют различные методы для сохранения типов list, dict и set в отсортированном порядке. Поддержание порядка с помощью специальной структуры данных может помочь избежать очень медленного поведения (квадратичного времени выполнения) при наивном подходе с редактированием и постоянной пересортировкой данных. Вот некоторые из модулей, реализующих эти типы данных:</p><ul><li><a href="http://www.grantjenks.com/docs/sortedcontainers/">SortedContainers</a> — реализация сортированных типов list, dict и set на чистом Python, по скорости не уступает реализациям на C. Тестирование включает 100% покрытие кода и многие часы стресс-тестирования. В документации можно найти полный справочник по API, <a href="http://www.grantjenks.com/docs/sortedcontainers/performance.html">сравнение производительности</a> и руководства по внесению своего вклада.</li><li><a href="https://pypi.python.org/pypi/rbtree">rbtree</a> — быстрая реализация на C для типов dict и set. Реализация использует структуру данных, известную как красно-чёрное дерево.</li><li><a href="https://pypi.python.org/pypi/treap">treap</a> — сортированный dict. В реализации используется Декартово дерево, а производительность улучшена с помощью Cython.</li><li><a href="https://pypi.python.org/pypi/bintrees">bintrees</a> — несколько реализаций типов dict и set на основе деревьев на C. Самые быстрые основаны на АВЛ и красно-чёрных деревьях. Расширяет общепринятый API для предоставления операций множеств для словарей.</li><li><a href="https://pypi.python.org/pypi/Banyan">banyan</a> — быстрая реализация dict и set на C.</li><li><a href="https://pypi.python.org/pypi/skiplistcollections">skiplistcollections</a> — реализация на чистом Python, основанная на списках с пропусками, предлагает ограниченный API для типов dict и set.</li><li><a href="https://pypi.python.org/pypi/blist">blist</a> — предоставляет сортированные типы list, dict и set, основанные на типе данных «blist», реализация на Б-деревьях. Написано на Python и C.</li></ul><h2>Прочее</h2><p>Для сортировки с учётом языка используйте locale.strxfrm() в качестве ключевой функции или locale.strcoll() в качестве функции сравнения. Параметр reverse всё ещё сохраняет стабильность сортировки. Этот эффект можно сымитировать без параметра, использовав встроенную функцию reversed() дважды:</p><p>Чтобы создать стандартный порядок сортировки для класса, просто добавьте реализацию соответствующих методов сравнения:</p><p>Для типов, сравнение которых работает обычным образом, рекомендуется определять все 6 операторов. <a href="https://tproger.ru/translations/demystifying-decorators-in-python">Декоратор</a> классов functools.total_ordering упрощает их реализацию. Функциям-ключам не нужен доступ к внутренним данным сортируемых объектов. Они также могут осуществлять доступ к внешним ресурсам. Например, если оценки ученика хранятся в словаре, их можно использовать для сортировки отдельного списка с именами учеников:</p><h2>Часто задаваемые вопросы</h2><h3>Чем отличается sorted() от .sort() в Python?</h3><p>Функция sorted() принимает любой итерируемый объект и возвращает новый отсортированный список, не изменяя исходные данные. Метод list.sort() работает только со списками и сортирует их на месте, возвращая None. Если исходный порядок элементов больше не нужен, .sort() работает немного быстрее за счёт отсутствия создания нового объекта.</p><h3>Как отсортировать список словарей по ключу?</h3><p>Используйте параметр key с lambda-функцией или operator.itemgetter(). Например, sorted(data, key=lambda x: x['age']) отсортирует список словарей по значению ключа 'age'. Для множественной сортировки передайте кортеж: key=lambda x: (x['grade'], x['age']).</p><h3>Что такое стабильная сортировка в Python?</h3><p>Стабильная сортировка гарантирует, что элементы с одинаковыми ключами сохраняют свой исходный порядок. Python использует алгоритм Timsort, который стабилен начиная с версии 2.2. Это позволяет строить сложные сортировки последовательными вызовами sorted() — сначала по вторичному ключу, затем по первичному.</p><h3>Как отсортировать список в обратном порядке?</h3><p>Передайте параметр reverse=True в sorted() или list.sort(). Например: sorted([3, 1, 2], reverse=True) вернёт [3, 2, 1]. Этот параметр сохраняет стабильность сортировки — элементы с одинаковыми ключами не поменяют взаимный порядок.</p><p>Надеемся, теория по Python list sort и соответствующие задачи по Питону с разбором были для вас полезны. Вас также может заинтересовать статьи:</p><ul><li><a href="https://tproger.ru/curriculum/python-how-to-learn">Хочу научиться программировать на Python. С чего начать?</a></li><li><a href="https://tproger.ru/curriculum/python-how-to-learn-2">Хочу научиться программировать на Python: инструкция для продолжающих</a></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>16 вопросов мидлу: что должен знать Middle-разработчик</title>
      <link>https://tproger.ru/articles/middle-developer-should-know</link>
      <comments>https://tproger.ru/articles/middle-developer-should-know?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Александровна]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/middle-developer-should-know</guid>
      <description><![CDATA[<p>Шкала trainee, junior, middle, senior, lead принята в индустрии, но точных границ уровней нет. Какие hard и soft skills ждут от мидла и завышены ли требования.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/middle-developer-should-know">16 вопросов мидлу: что должен знать Middle-разработчик</a>»</p>]]></description>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Собеседование]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 05 Oct 2020 11:31:05 GMT</pubDate>
      <content:encoded><![CDATA[<p>Что должен знать Middle-разработчик? Этот вопрос актуален как для тех, кто претендует на вакансию мидла, так и для тех, кто посматривает в сторону продвижения.</p><p>Стоит отметить, что несмотря на то что в IT выработана шкала для оценки программистов (trainee, junior, middle, senior, lead), с которой более или менее все согласны, с точным определением каждого уровня общего мнения среди компаний пока нет. Поэтому каждая компания устанавливает внутренние требования к навыкам каждого уровня.</p><p>Тем не менее есть основной набор знаний и навыков, по которому в целом можно ориентироваться, оценивая себя. Многие специалисты утверждают, что сегодня требования к мидлам несколько завышены:</p><p>Позиция Middle-разработчика предполагает не только уверенное знание языка программирования, но и широчайшие познания в смежных технологиях. Позиция Middle накладывает на специалиста ответственность не только программировать «всё что угодно», но и обучать, оптимизировать код, разбирать и поддерживать чужой код, как будто родной. Фактически сейчас для вакансий Middle-разработчика предъявляются требования, как несколько лет назад для Senior.</p><p>Мы выяснили, что должен знать Middle-программист в 2020 году, и собрали общие вопросы с собеседований, по ответам на которые работодатели определяют, «дорос» кандидат до мидла или нет.</p><h2>Hard skills</h2><h3>Каков ваш уровень владения языком программирования и сопутствующими инструментами?</h3><p>От программиста-джуна ожидается знание всех конструкций языка, знание большей части стандартной библиотеки. Знакомство с фреймворками (хотя бы с одним), инструментами коллективной разработки (систем контроля версий, таск- и баг- трекеров, естественно, IDE и др.). Требования к мидлам существенно выше. Как правило, ожидается доскональное знание базы (в том числе синтаксиса используемых языков), опыт работы и уверенные теоретические знания стандартных библиотек, необходимых фреймворков и инструментов.</p><h3>В каких проектах вы участвовали? Сколько из них вели сами?</h3><p>Не стоит удивляться: опытные джуниоры уже вполне могут вести свои проекты. Не без поддержки более опытных коллег, но это не отменяет самостоятельности младшего разработчика. Так что если вы продвигаетесь по карьерной лестнице, в вашем портфолио уже должны поселиться свои проекты.</p><p>Показывайте решения, которые лучше других продемонстрируют ваши знания и навыки. Выбирайте самые сложные и комплексные таски, рассказывайте, как вы их выполняли и как планировали выполнение.</p><h3>Каков ваш навык работы с базами данных?</h3><p>Работаете с бэкендом? В таком случае вы должны свободно обращаться к базам данных, иметь навыки работы с одной или несколькими СУБД.</p><p>Знать и уметь строить оптимальную архитектуру БД. БД зачастую является самым узким местом производительности проекта. Менять архитектуру БД на живом проекте очень проблематично и дорого.</p><p>Это основное из того, что должен знать Middle-разработчик. Не стоит забывать и о нормализации, денормализации и характеристиках каждой нормальной формы.</p><h3>Расскажите о своём уровне владения  выбранной IDE</h3><p>Нужно уметь без проблем подключать к проекту библиотеки, знать главные преимущества и недостатки среды, свободно настраивать и использовать горячие клавиши. Это то, что должен знать Middle-разработчик на уровне IDE.</p><h3>Другие технические навыки</h3><p>Тут всё зависит от конкретной специальности, но эксперты сходятся в том, что знания должны быть продвинутыми: больше, чем одна библиотека и пара хинтов.</p><p>Что касается технических навыков Middle-разработчика по frontend JavaScript, то специалист такого уровня должен:знать один из популярных JS-фреймворков (Vue, Angular), а также популярную библиотеку (React);знать SASS/SCSS/Less/PostCSS;уметь самостоятельно настроить систему сборки проекта Grunt/Gulp/Webpack, работать с голым JS (ES5, ES6, ES7);знать и понимать RESTful интерфейсы;уметь пользоваться средствами совместного использования кода (Git + GitFlow).<br />Также есть дополнительные навыки, которые дают преимущества. Большим плюсом в работе будут:опыт разработки серверного кода под Node.js, TypeScript;понимание SOLID, Dependency Injection, знание Linux (Debian-like);опыт работы с TDD/BDD, тест-фреймворки (Mocha, Should, Chai, Jest);знание Docker (большим плюсом будет Kubernetes/Docker Swarm);опыт в PostgreSQL/MySQL etc.</p><p>Самое важное для мидла, это знать, как изнутри работает используемая тобой технология, твой фреймворк. Не то, как он производит рендер (с помощью VDOM и т. п.), а как код написан, как происходит магия. Открой GitHub, посмотри исходники React, как, например, this.setState заставляет компонент делать рендер.</p><h3>Какие методологии разработки вы знаете? По каким из них работали?</h3><p>Одной теории недостаточно. Вы уже должны были участвовать в проектах «под ключ» и понять, как работает та или иная методология. Хорошо, если у вас был опыт работы по нескольким методологиям: если так, обязательно проведите сравнение плюсов и минусов, исходя из практики.</p><h3>Что ещё должен знать Middle-разработчик</h3><p>Знать и уметь писать оптимальные запросы по выборке данных (DML-запросы).Немного знать про ACID, понимать, что это такое и зачем нужно.Знать три столпа ООП (наследование, полиморфизм и инкапсуляция).Знать, что такое шаблоны проектирования и зачем они нужны. Понимать различия между ними.Понимать, что такое SOLID и когда он уместен.Знать и понимать, что такое тестирование кода и как оно реализуется.Желателен опыт в написании тестов. Это заставляет мозг разработчика генерировать совершенно другой код. Я специально поставил этот пункт ниже SOLID и шаблонов проектирования: сначала SOLID и шаблоны, а только потом тесты. Тогда будет получаться код, который будет легче покрываться тестами.</p><h2>Soft skills</h2><h3>Насколько вы коммуникабельны?</h3><p>Если в случае с Junior-программистом коммуникативные навыки просто важны, то для Middle-разработчика они обязательны.</p><p>Думаю, что Middle-программист не должен принимать решения по архитектуре проекта, согласовывать требования с заказчиком и решать вопросы по блокерам, так как это входит в компетенции аналитика, PM и сеньора.</p><p>Несмотря на это, во многих компаниях перед мидлом часто ставится задача самостоятельно разобраться, чего хочет заказчик, как решить его проблему и какую архитектуру построить. На базе этих сведений формируется полноценное ТЗ, которое затем передаётся команде разработчиков.</p><h3>Ваши навыки работы в команде</h3><p>С точки зрения soft скиллов Middle-разработчику, конечно, нужно осваивать компетенции: ведение переговоров, общение с командой, иметь представление об управлении командой. Эти навыки пригодятся в дальнейшем карьерном росте.</p><h3>Умеете ли вы разрешать конфликты?</h3><p>Этот навык также является частью коммуникации. Разногласия могут возникнуть и с заказчиком, и внутри команды. Если вам поручили роль наставника над младшими специалистами в проекте — в обязанности войдёт и урегулирование конфликтов.</p><p>Несмотря на то что мы, разработчики, чаще сами по себе и сами в себе — нужно развивать коммуникации и другие мягкие навыки. Не во всех компаниях есть менеджер, который выполняет роль медиатора между отделами — чтобы тебя поняли и чтобы ты просто смог нормально рассказать о своей работе, нужны — эмпатия, умение договариваться, гибкость в процессах.</p><h3>Каков ваш уровень английского языка?</h3><p>Уровень владения английским языком Middle-программиста должен быть не ниже Upper-Intermediate, так как этот специалист постоянно участвует в созвонах, конференциях, должен свободно читать и составлять техническую документацию.</p><h3>Способны ли вы доступно доносить свои идеи, убеждать в их правильности?</h3><p>Что касается «софтскиллс», они необходимы для выполнения многочисленных ролей в команде — разработчик, ментор, докладчик, тимлид и другие. Что для этого понадобится специалисту? Убеждать коллег и клиентов. Иногда даже продавать — идеи, решения, преимущества. Презентовать свои идеи. Обучать. Планировать свою деятельность, ставить задачи перед подчинёнными, контролировать их выполнение. Для этого и много другого нужно развивать соответствующие навыки.</p><h3>Можно ли вас охарактеризовать, как внимательного человека?</h3><p>Очень хорошее качество, которое надо развивать разработчику, — это внимательность. Она позволит вовремя заметить, что с «канарейкой» что-то не то. Грамотный Middle-разработчик должен непрерывно отслеживать «жизненные показатели» проекта, чтобы вовремя понять, что «чирикает» после недавнего коммита он уже не так бодро.</p><h3>Если возникла проблема, как вы будете её решать?</h3><p>Перед разработчиком среднего уровня стоит задача самостоятельно находить, обрабатывать и использовать информацию, и только в самых сложных ситуациях обращаться к коллегам-сеньорам.</p><p>На первом месте здесь стоит умение искать информацию. Перед собеседованием вспомните основные источники информации, к которым вы обращаетесь в первую очередь (после документации, разумеется).</p><h3>Можете ли вы оценить время выполнения задачи?</h3><p>Это то, что должен уметь Middle-разработчик, особенно если речь идёт о работе в команде: в этом случае необходимо оценить время, исходя из знаний и навыков каждого участника проекта. Следует <a href="https://tproger.ru/experts/time-estimation/">учиться правильно определять сроки выполнения работы</a>.</p><p>Мидл уже имеет достаточный опыт работы, что позволяет ему декомпозировать задачи более-менее правильно. Мидл умеет декомпозировать, а значит он может точнее оценивать задачи. Он должен уметь соблюдать сроки. Это умение нельзя переоценить.</p><h3>Планируете ли вы развиваться до сеньора или тимлида?</h3><p>Плох тот солдат, что не мечтает стать генералом. Перефразируем: плох тот мидл, что не мечтает стать сеньором/архитектором/тимлидом. Мидл должен всегда хотеть развиваться. Жажда саморазвития будет вытаскивать мидла из его рутины (а у него будет рутина) и отправлять развиваться дальше.</p><h2>Выводы</h2><p>Так завышены ли сегодня требования к мидлам? На этот счёт мнения расходятся, но почти все специалисты сходятся во мнении, что каждая компания диктует свои правила, и то, что должен знать Middle-разработчик, будет напрямую зависеть в том числе от должности, на которую он претендует.</p><p>Не завышены ли требования к мидлам? Возможно, кто-то из IT-специалистов сталкивался с тем, что, уже несколько лет будучи мидлом, на собеседовании в какой-либо компании его оценивают как джуниора. Связано это, как правило, с двумя факторами: первое — разные требования в разных компаниях. Второе — часто в уровень квалификации, кроме перечисленных выше hard и soft skills, ещё включают погружённость сотрудника в предметную область, знание специфики бизнеса. Эти знания делают сотрудника ценным для текущей организации, но могут значить меньше в новой для него организации.</p><p>Подводя итоги, Middle-разработчик должен свободно плавать в теории, хорошо знать необходимый стек технологий и иметь по нему богатый опыт, не просто коммуницировать, но также оценивать трудозатраты, сроки, обучать и управлять ходом разработки. Опыт работы с базами данных, Git и уверенное владение английским языком обязательны по умолчанию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Личная поваренная книга SwiftUI-рецептов</title>
      <link>https://tproger.ru/articles/swiftui-cookbook</link>
      <comments>https://tproger.ru/articles/swiftui-cookbook?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/swiftui-cookbook</guid>
      <description><![CDATA[<p>Единый проект для исследования SwiftUI: как держать черновики в одном месте, видеть все тестовые View при запуске и выбирать нужное.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/swiftui-cookbook">Личная поваренная книга SwiftUI-рецептов</a>»</p>]]></description>
      <category><![CDATA[Мобильная разработка]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Swift]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 18 Sep 2020 13:11:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Александр, старший iOS-разработчик Noveo</p><p>Когда начинаешь исследовать какую-то обширную тему в программировании, количество скачанных проектов и созданных черновиков постепенно превышает все мыслимые и немыслимые пределы. А потом и вовсе всё перемешивается и теряется. Вроде помнил, что ты с этим работал, а где, когда? Я пробовал работать в Playground’ах, но они, на мой взгляд, не такие стабильные, как обычный проект, отваливается подсветка, нет возможности нормально делать Debug. С недавних пор я завел единый проект для исследования SwiftUI, и все небольшие вещи закидываю туда. Это помогает держать все в одном месте, к тому же поиск внутри проекта намного удобней. Хоть SwiftUI и предоставляет Preview для быстрого просмотра View и даже позволяет их отлаживать, все же этого не всегда хватает. Хочется и на устройстве проверить. А если держать все эти View внутри одного проекта, надо при создании новой вьюхи проставлять ее как основную в SceneDelegate, что довольно быстро начинает утомлять. Как было бы круто, если бы мы могли видеть все наши тестовые View при запуске приложения и имели возможность выбрать, с чем работать. Фантастика, скажете вы? Отнюдь ?</p><p>Задачу, думаю, можно решить более чем одним путем. Навскидку — прикрутить Sourcery, но интересно было решить ее без вспомогательных инструментов.</p><p>Итак, что из себя представляет View и её Preview:</p><p>Как мы видим, предпросмотр для View обеспечивается структурой, которая имплементирует PreviewProvider. Если кто не знал, можно даже внутри одного файла создавать сколько угодно структур/классов, которые будут имплементировать PreviewProvider, и все они отобразятся в зоне предпросмотра. Может пригодиться, если захотим разбить наш ContentView_Previews на несколько с разными настройками (хотя можно это же сделать и внутри одной структуры, имплементирующей PreviewProvider, но речь не об этом).</p><p>Что из себя представляет PreviewProvider? Это протокол</p><p>Главное, что можно извлечь из кода, — это не простой протокол, а PAT: Protocol with Associated Type, что сразу усложняет дело. Я перепробовал много вариантов, как обеспечить нужную функциональность с минимальными усилиями.</p><p>Начнем с того, как вообще можно подобные вещи делать в real time? В Objective-C мы могли делать все что угодно с помощью reflection — получать список всех классов, исследовать их свойства. В Swift это все дело сильно ограничили, и Mirror не даст нам всего необходимого. Поэтому пришлось смотреть в сторону objc_getClassList: это метод из рантайма Obj-C, который позволяет получить список всех классов. К сожалению, такого нет для Swift-структур, поэтому оставалось только обходится тем, что дали.</p><p>Разберем решение по частям.</p><p>Нормально работать с системным протоколом PreviewProvider не получится из-за того, что он PAT, поэтом создадим Erase-версию этого протокола:</p><ol><li>Как видно, я стер тип у Previews, создав обертку anyPreviews, которая будет возвращать AnyView. Я не очень люблю такие штуки, потенциальная потеря производительности, но т.к. это не production-код, то на это можно закрыть глаза.</li><li>name — свойство, возвращающее имя нашей View, как оно будет отображатьcя в списке; учитывая, что все Preview имеют автоматом генерируемые имена ViewName_Previews, можно _Previews отрезать.</li><li>Я добавил свойство starred, т.к. число View будет все увеличиваться, а начиная работать с новым куском кода, хочется увидеть его сверху в списке. Это можно сделать, переопределив у превьюхи для новой View это свойство, возвращая true.</li></ol><p>Сам список выглядит довольно просто.</p><p>Все View сортируются по имени и разбиваются на 2 списка, starred и обычные. Выглядеть все будет примерно так:</p><figure><img src="https://media.tproger.ru/uploads/2020/09/list.png" alt="" /></figure><p>Ну, и в SceneDelegate просто меняем основную вьюху:</p><p>let contentView = PreviewsList()</p><p>Остался последний момент: как же сделать так, чтобы наши Preview попали в этот список.</p><ul><li>поменять struct на class;</li><li>добавить поддержку PreviewHolder.</li></ul><p>Т.е. вместо</p><p>станет</p><p>Опционально можно переопределять name и starred.</p><p>Это решение написано за пару часов, чтобы по-быстрому испытать идею. При желании его можно наворотить по полной, проставляя теги, дату создания для Preview, показывать список не основным, а в Debug-окне, что позволит использовать его даже на боевом проекте (не забываем отключать в Release-сборке). В общем, все зависит от вашей фантазии ?</p><p>Скачать проект с базовой реализацией можно здесь: <a href="https://github.com/sparklone/swiftui-views-preview-list">Github</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Что почитать по NGINX — подборка книг на русском и английском + дополнительные материалы</title>
      <link>https://tproger.ru/translations/nginx-books-and-sites</link>
      <comments>https://tproger.ru/translations/nginx-books-and-sites?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Штукатуров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/nginx-books-and-sites</guid>
      <description><![CDATA[<p>Актуальные книги по NGINX на русском и английском языках плюс дополнительные ресурсы о настройке и принципах работы этого веб-сервера.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/nginx-books-and-sites">Что почитать по NGINX — подборка книг на русском и английском + дополнительные материалы</a>»</p>]]></description>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Системное администрирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 05 Dec 2019 12:13:40 GMT</pubDate>
      <content:encoded><![CDATA[<p>Хотя NGINX считается простым сервером без лишних функций, работать с ним исключительно интуитивно получается не всегда, иногда нужна дополнительная информация. В этой подборке вы найдёте самые актуальные книги по NGINX на русском и английском языках. А если этого окажется недостаточно — полезные ресурсы из сети Интернет.</p><h2>Подборка книг по NGINX</h2><h3>На русском</h3><p>Разобрались с теорией и хотите больше практики? Тогда вам пригодится эта книга. Здесь вы сможете получить огромное количество практических примеров и рекомендаций по настройке и использованию серверов. Сервер NGINX обычно выбирают за эффективный механизм распределения нагрузки — в этой книге вы найдёте приёмы, которые позволят сделать этот механизм ещё эффективнее. Что ещё? A/B-тестирование, управление трафиком, интеграция с облачными платформами AWS, Azure и Google, отладка. Однако прежде чем браться за чтение, желательно ознакомиться с основными принципами работы системы.</p><p>Подойдёт как для новичков, так и для опытных администраторов. Автор рассказывает о настройке и управлении сервером, детально рассматривая каждый пункт настроек.</p><h3>На английском</h3><p>Автор успел поработать в таких компаниях, как Яндекс, Booking.com и AVG, а сейчас владеет собственной консалтинговой фирмой. NGINX Essentials — 150 страниц полезной информации, без рассказов о том, что такое интернет и где находится Anykey. Чтобы продемонстрировать принципы, по которым действует система, автор использует реальный проект. Единственное, на что можно посетовать — недостаточно глубокое погружение в тонкости настроек сервера.</p><p>Монументальный труд, выдержавший четыре издания. В книге показано, как оптимизировать сетевую нагрузку, наладить взаимодействие между NGINX и Apache (или совсем отказаться от последнего) и в полной мере использовать возможности системы в веб-приложениях. Но если вы раньше не работали с NGINX, возможно, вам придётся обращаться к другим источникам.</p><p>Ещё одна книга не для новичков. Содержит пошаговые рекомендации по миграции с Apache и настройке максимальной производительности сервера NGINX. Помимо советов по настройке вы узнаете, как измерить производительность системы. Подача материала лаконичная, с практическими примерами.</p><p>Издание посвящено исключительно интеграции NGINX с фаерволлом ModSecurity 3.0. Вы узнаете, как установить и настроить наборы правил OWASP Core Rule Set и TrustWave SpiderLabs Commercial Rule Set. Возможно, благодаря узкой специализации авторам удалось максимально подробно осветить все аспекты настройки системы.</p><p>Книга поможет безболезненно перейти с Cisco ACE на сервер NGINX. Примеры настроек с детальным разбором прилагаются. Есть разделы, посвящённые установке Linux и поднятию x86 сервера.</p><h2>Дополнительные ресурсы</h2><p>Не нашли ответа на свой вопрос в книгах? Хотите попробовать новую систему, но не готовы настраивать собственный сервер? Не знаете, что предпочесть, NGINX или Apache? Вот подборка сайтов, которые вам помогут.</p><p>Прим. пер. Полезных сайтов по NGINX десятки, если не сотни. Мы отобрали 6 ресурсов, с которых стоит начать изучение системы, а уж насколько глубокой окажется кроличья нора, будет зависеть только от вашего любопытства.</p><ol><li>NGINX Project — сайт проекта. Отсюда стоит начать.</li><li><a href="https://nginx.org/en/docs/">NGINX Documentation</a> — полная документация по системе.</li><li>NGINX Pitfalls and Common Mistakes — учиться лучше на чужих ошибках, и здесь собраны самые распространённые.</li><li><a href="https://forum.nginx.org/">NGINX Forum</a> — не нашли ответа на свой вопрос? Спросите на форуме!</li><li>NGINX vs. Apache (Pro/Con Review, Uses, &amp; Hosting for Each) — хорошая подробная статья, посвящённая сравнению NGINX и Apache.</li><li><a href="https://github.com/sportebois/nginx-rate-limit-sandbox">NGINX Rate Limit, Burst and nodelay sandbox</a> — песочница, в которой можно поиграть с настройками и оценить возможности сервера NGINX.</li></ol>]]></content:encoded>
    </item>
    <item>
      <title>Кэширование кода для JavaScript-разработчиков на примере Chrome</title>
      <link>https://tproger.ru/translations/javascript-code-caching-for-devs</link>
      <comments>https://tproger.ru/translations/javascript-code-caching-for-devs?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/javascript-code-caching-for-devs</guid>
      <description><![CDATA[<p>Кэширование байт-кода в Chrome ускоряет загрузку сайтов. В статье рассмотрены два уровня кэширования: оперативная память (V8 Isolate) и диск.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/javascript-code-caching-for-devs">Кэширование кода для JavaScript-разработчиков на примере Chrome</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 13 Oct 2019 08:04:39 GMT</pubDate>
      <content:encoded><![CDATA[<p>Кэширование кода (также называемое кэшированием байт-кода) является важным инструментом оптимизации. Оно уменьшает время запуска часто посещаемых сайтов за счёт кэширования результатов парсинга и компиляции. Большинство <a href="https://blog.mozilla.org/javascript/2017/12/12/javascript-startup-bytecode-cache/">популярных</a> <a href="https://bugs.webkit.org/show_bug.cgi?id=192782">браузеров</a> реализует кэширование в некоторой форме, и Chrome не исключение. О том, как <a href="https://v8.dev/blog/improved-code-caching">Chrome</a> и <a href="https://v8.dev/blog/code-caching">V8</a> кэшируют скомпилированный код, уже много всего написано и <a href="https://www.youtube.com/watch?v=YqHOUy2rYZ8">сказано</a>.</p><p>В этой статье вы найдёте несколько советов JS-разработчикам, которые хотят с помощью кэширования кода улучшить загрузку сайтов. Мы будем говорить о реализации кэширования в Chrome / V8, но большинство советов можно использовать и для кэширования кода других браузеров.</p><h2>Обзор кэширования кода</h2><h3>Кэш оперативной памяти</h3><p>У Chrome есть два уровня кэширования скомпилированного в V8 кода (классических скриптов и скриптов модулей): быстрый и «лучший из возможного» кэш в оперативной памяти, обеспечиваемый средствами V8 (кэш Isolate), а также полный сериализованный кэш на диске.</p><p>Кэш Isolate работает со скриптами, скомпилированными в том же V8 Isolate (т. е. тот же процесс, грубо говоря «одни и те же страницы сайта при навигации по одной и той же вкладке»). Это «лучшее из возможного» в том смысле, что кэш оперативной памяти, насколько это возможно, быстр и минимален: он использует уже имеющиеся данные за счёт потенциально более низкой частоты обращений и отсутствия кэширования между процессами.</p><ul><li>Когда V8 компилирует скрипт, скомпилированный байт-код сохраняется в хеш-таблице (в куче V8), ключ которого определяется исходным кодом сценария.</li><li>Когда Chrome просит V8 скомпилировать другой скрипт, V8 сначала проверяет, соответствует ли исходный код этого скрипта чему-либо в хеш-таблице. Если соответствует, просто возвращается существующий байт-код.</li></ul><p>Этот кэш быстрый и эффективный, но на практике у него лишь 80% частоты попаданий.</p><h3>Кэш на диске</h3><p>Кэш кода на диске управляется Chrome (в частности, с помощью Blink) и заполняет пробел, который кэш Isolate не может заполнить: совместное использование кэшей кода между процессами и между несколькими сеансами Chrome. Он использует преимущества существующего кэша HTTP-ресурсов, который управляет кэшированием и очисткой данных с истёкшим сроком действия, полученных по сети.</p><ol><li>Когда JS-файл запрашивается впервые (т. е. выполняется «холодный» запуск), Chrome загружает его и даёт V8 для компиляции. Он также сохраняет файл в кэше браузера на диске.</li><li>Когда JS-файл запрашивается во второй раз (т. е. выполняется «тёплый» запуск), Chrome берёт файл из кэша браузера и снова передаёт его в V8 для компиляции. Однако на этот раз скомпилированный код сериализуется и прикрепляется к кэшированному файлу скрипта в качестве метаданных.</li><li>В третий раз (т. е. «горячий» запуск) Chrome извлекает как файл, так и метаданные файла из кэша и передаёт их в V8. Тот в свою очередь десериализует метаданные и может пропустить компиляцию.</li></ol><p>В итоге:</p><figure><img src="https://media.tproger.ru/uploads/2019/10/wecewdewd.png" alt="" /></figure><p>Основываясь на этом описании, можно оптимизировать использование кэшей кода на вашем сайте.</p><h2>Совет 1: не делайте ничего</h2><p>Лучшее, что JS-разработчик может сделать для оптимизации кэширования кода, — ничего не делать. Но ничего не делать можно по-разному: пассивно и активно.</p><p>Кэширование кода в конце концов является частью реализации браузера. По сути, это увеличение производительности за счёт дополнительных расходов памяти, реализация и эвристика которых могут постоянно меняться. Мы, как разработчики V8, должны делать всё возможное, чтобы эти эвристики работали для каждого. Если чрезмерно оптимизировать кэширование кода, можно очень разочароваться уже после нескольких релизов, когда эти детали изменятся. Кроме того, другие механизмы JavaScript могут иметь различные эвристики для своей реализации кэширования кода. Так что во многих отношениях лучший совет для получения кэшированного кода похож на совет по написанию JS: пишите чистый идиоматический код и сделайте всё возможное, чтобы оптимизировать его кэширование.</p><p>Помимо пассивного бездействия, вы также должны стараться активно «ничего не делать». Любая форма кэширования по своей природе зависит от того, что ничего не меняется, поэтому бездействие — лучший способ сохранить кэшированные данные. Активно ничего не делать можно разными способами.</p><h3>Не меняйте код</h3><p>Всякий раз, когда вы отправляете новый код, он ещё не кэширован. Когда браузер делает HTTP-запрос для URL-адреса сценария, он может включать дату последней выборки этого URL-адреса. Если сервер знает, что файл не изменился, он может отправить ответ «304 Not Modified», который сохраняет кэш кода «горячим». В противном случае ответ «200 OK» обновляет кэшированный ресурс и очищает кэш кода, возвращая его обратно в «холодный» режим.</p><p>Всегда хочется сразу же отправить последние изменения кода, особенно если вы хотите измерить влияние конкретного изменения, но для кэша лучше позволить коду существовать как он есть, по крайней мере, обновлять его как можно реже. Подумайте о том, чтобы установить ограничение «≤ Х развёртываний в неделю», где Х — это слайдер, который вы можете регулировать для баланса между кэшированием и устареванием данных.</p><h3>Не меняйте URL’ы</h3><p>Кэш кода связан с URL-адресом скрипта, так как это облегчает поиск, ведь нет необходимости читать фактическое содержимое скрипта. Это означает, что изменение URL-адреса (включая любые параметры запроса) создаёт новую запись в кэше ресурсов, а вместе с ним и новую запись «холодного» кэша.</p><p>Конечно, это также можно использовать для принудительной очистки кэша, хоть это и является частью реализации. Однажды мы можем решить связать кэши с исходным текстом, а не с исходным URL, и этот совет перестанет работать.</p><h3>Не меняйте поведение выполнения</h3><p>Одна из самых свежих оптимизаций кэширования кода заключается в <a href="https://v8.dev/blog/improved-code-caching#increasing-the-amount-of-code-that-is-cached">сериализации скомпилированного кода только после его выполнения</a>. Это делается для того, чтобы попытаться поймать <a href="https://ru.wikipedia.org/wiki/Отложенная_инициализация">лениво скомпилированные функции</a>, которые инициализируются только во время выполнения, а не во время начальной компиляции.</p><p>Эта оптимизация работает лучше всего, когда каждый запуск скрипта выполняет один и тот же код или хотя бы одинаковые функции. Это может быть проблемой, если у вас есть, например A/B-тесты, которые зависят от выбора времени выполнения:</p><p>В этом случае только A() или B() компилируются и выполняются при «горячем» запуске и вводятся в кэш кода, но любой из них может быть выполнен и в последующих запусках. Вместо этого надо попытаться сохранить выполнение детерминированным, чтобы сохранить его в кэшированном пути.</p><h2>Совет 2: сделайте что-нибудь</h2><p>Конечно, совет не делать ничего (пассивно или активно) не очень удовлетворит вас. Помимо того что вы ничего не делаете, учитывая текущую эвристику и реализацию, есть несколько вещей, которые вы можете сделать. Эвристика может измениться, сам этот совет может измениться и нет никакой альтернативы для профилирования.</p><h3>Отделите библиотеки от кода</h3><p>Кэширование кода имеет свои особенности: изменения в любой части сценария делают недействительной кэш-память для всего сценария. Если ваш бандл состоит из стабильных и изменяющихся частей в одном скрипте, например библиотеки и бизнес-логики, то изменения в коде бизнес-логики изменяют кэш кода библиотеки.</p><p>Вместо этого вы можете отделить код стабильной библиотеки в самостоятельный скрипт и включить его отдельно. Затем код библиотеки может быть кэширован один раз и оставаться в кэше при изменении бизнес-логики.</p><p>Это даёт дополнительные преимущества, если библиотеки совместно используются на разных страницах вашего сайта, поскольку к сценарию прикреплён кэш кода. Кэш кода для библиотек также разделяется между страницами.</p><h3>Объедините библиотеки с кодом</h3><p>Кэширование кода выполняется после выполнения каждого скрипта. Это означает, что кэш будет включать в себя именно те функции в этом скрипте, которые были скомпилированы по завершении его выполнения. Это ведёт к нескольким важным последствиям для библиотечного кода:</p><ul><li>кэш кода не будет включать функции из более ранних скриптов;</li><li>кэш кода не будет включать в себя лениво скомпилированные функции, вызываемые более поздними скриптами.</li></ul><p>Если библиотека состоит из полностью лениво скомпилированных функций, эти функции не будут кэшироваться, даже если они будут использоваться позже.</p><p>Одним из решений этой проблемы является объединение библиотек в единый сценарий, таким образом кэширование кода «видит», какие части библиотеки используются. Это полная противоположность совету выше, универсального решения нет. Конечно, не рекомендуется объединять все ваши JS-скрипты в один большой бандл. В целом разделение его на несколько более мелких скриптов будет полезнее (например, множественные сетевые запросы, потоковая компиляция, интерактивность страниц и т. д.).</p><h3>Используйте преимущество эвристики IIFE</h3><p>Только те функции, которые скомпилированы к моменту, когда завершится выполнение скрипта, учитываются в кэше, поэтому существует много видов функций, которые не будут кэшироваться, несмотря на выполнение в более поздний момент. Обработчики событий (даже onload()), цепочки промисов, неиспользуемые библиотечные функции и всё, что лениво компилируется без вызова к моменту, когда &lt;/script&gt; виден — всё это остаётся ленивым и не кэшируется.</p><p>Один из способов сделать эти функции кэшированными — заставить их компилироваться. Распространённым способом принудительной компиляции является использование эвристики IIFE. IIFE (<a href="https://en.wikipedia.org/wiki/Immediately_invoked_function_expression">immediately-invoked function expressions</a>) — это шаблон, в котором функция вызывается сразу после создания:</p><p>Так как IIFE вызываются немедленно, большинство движков JavaScript пытаются обнаружить их и немедленно скомпилировать, чтобы избежать затрат на ленивую компиляцию с последующей полной компиляцией. Существуют различные эвристики для раннего обнаружения IIFE (до анализа функции), наиболее распространённой из которых является символ «(» перед ключевым словом function.</p><p>Поскольку эта эвристика применяется рано, она запускает компиляцию, даже если функция на самом деле вызывается не сразу:</p><p>Функции, которые должны находиться в кэше, можно принудительно ввести в него, заключив их в скобки. Но это может привести к тому, что время загрузки будет страдать, если совет будет применён неправильно. Это своего рода злоупотребление эвристикой, поэтому не следует так делать без необходимости.</p><h3>Группируйте небольшие файлы вместе</h3><p>В Chrome — минимальный размер для кэшей кода, сейчас это <a href="https://cs.chromium.org/chromium/src/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc?l=91&amp;rcl=2f81d000fdb5331121cba7ff81dfaaec25b520a5">1 КБ исходного кода</a>. Сценарии меньше не кэшируются, так как затраты будут больше выгоды.</p><p>Если на вашем сайте много таких небольших сценариев, подсчёт затрат может больше не применяться аналогичным образом. Лучше объединить их так, чтобы они превышали минимальный размер кэширования кода, и получить выгоду от общего сокращения затрат скрипта.</p><h3>Избегайте встроенных скриптов</h3><p>Теги скриптов, исходный код которых встроен в HTML, не имеют внешнего исходного файла, с которым они связаны. Поэтому они не могут быть кэшированы с помощью этого механизма. Chrome пытается кэшировать встроенные скрипты, прикрепляя их к ресурсу документа HTML, но эти кэши становятся зависимыми всего документа HTML — он не должен изменяться и разделяться между страницами.</p><p>Простые сценарии не стоит встраивать в HTML, лучше выносить их в отдельные файлы.</p><h3>Используйте кэши сервис-воркера</h3><p>Сервис-воркер — это механизм, позволяющий вашему коду перехватывать сетевые запросы на ресурсы на вашей странице. Они позволяют вам создавать локальный кэш из некоторых ваших ресурсов и обслуживать ресурс из кэша всякий раз, когда их запрашивают. Это особенно полезно для страниц, которые продолжают работать в offline-режиме вроде PWA.</p><p>Ниже типичный пример сайта, использующего сервисный воркер. Регистрация воркера в основном файле сценария:</p><p>Воркер добавляет обработчики событий для установки (создание кэша) и извлечения (обслуживание ресурсов).</p><p>Эти кэши могут включать в себя кэшированные ресурсы JS. Но поскольку ожидается, что кэши воркеров будут преимущественно использоваться для PWA, для них используется немного другая эвристика по сравнению с обычным «автоматическим» кэшированием в Chrome. Во-первых, они сразу же создают кэш кода при каждом добавлении ресурса JS. Это означает, что кэш доступен уже при второй загрузке (а не только при третьей, как в обычном случае). Во-вторых, генерируется «полный» кэш для этих скриптов — функции больше не компилируются лениво. Всё компилируется и помещается в кэш. Преимущество заключается в быстрой и предсказуемой производительности, без каких-либо зависимостей порядка выполнения, хоть и за счёт увеличения использования памяти. Обратите внимание, что такая эвристика применяется только к кэшам сервисных воркеров, а не к другому использованию Cache API. В настоящее время Cache API вообще не выполняет кэширование кода, когда используется вне сервисных воркеров.</p><h2>Трассировка</h2><p>Ни один из советов выше не поможет ускорить работу вашего сайта. К сожалению, информация о кэшировании сейчас не предоставляется в DevTools, поэтому наиболее надёжный способ выяснить, какие из сценариев вашего сайта кэшируются, — использовать чуть более низкий уровень chrome://tracing.</p><p>chrome://tracing записывает инструментальные трассировки Chrome в течение некоторого периода времени с такой визуализацией :</p><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-visualization.png" alt="" /></figure><p>Трассировка записывает поведение всего браузера, включая другие вкладки, окна и расширения, поэтому она лучше всего работает, когда выполняется в чистом профиле пользователя, с отключёнными расширениями и без открытия других вкладок браузера:</p><p>При записи вы должны выбрать, какие категории трассировать. В большинстве случаев вы можете просто выбрать набор категорий «Web developer» (Веб-разработчик), но категории можно выбрать и вручную. Важная категория для кэширования кода — v8.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-categories-1.png" alt="" /></figure><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-categories-2.png" alt="" /></figure><p>После записи с категорией v8 найдите фрагменты v8.compile в трассировке (в качестве альтернативы вы можете ввести v8.compile в поле поиска интерфейса). Эти компоненты показывают компилируемый файл и некоторые метаданные о компиляции.</p><p>При холодном запуске сценария информация о кэшировании кода отсутствует. Это означает, что сценарий не участвовал в создании или использовании данных кэша.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-cold-run.png" alt="" /></figure><p>При тёплом запуске есть две записи v8.compile на сценарий: одна для фактической компиляции (как указано выше) и одна (после выполнения) для создания кэша. Вы можете узнать последнюю, так как она имеет поля метаданных cacheProduceOptions и producedCacheSize.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-warm-run.png" alt="" /></figure><p>При горячем запуске вы увидите запись v8.compile для использования кэша с полями метаданных cacheConsumeOptions и consumedCacheSize. Все размеры выражены в байтах.</p><figure><img src="https://media.tproger.ru/uploads/2019/10/chrome-tracing-hot-run.png" alt="" /></figure><p>Для большинства разработчиков кэширование кода должно «просто работать». И работает это лучше всего (как и любой кэш), когда всё остаётся неизменным, и с использованием эвристики, которая может меняться между версиями. Тем не менее, кэширование кода имеет особенности, которые можно использовать, и ограничения, которых стоит избегать. Тщательный анализ с использованием chrome://tracing может помочь вам настроить и оптимизировать использование кэша вашим сайтом.</p>]]></content:encoded>
    </item>
    <item>
      <title>Что нового в C++ 20 и какие изменения ожидают язык в будущем?</title>
      <link>https://tproger.ru/translations/c-20-is-feature-complete</link>
      <comments>https://tproger.ru/translations/c-20-is-feature-complete?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/c-20-is-feature-complete</guid>
      <description><![CDATA[<p>Комитет по стандартам завершает работу над C++ 20 — шестой редакцией стандарта языка. Обзор ключевых нововведений и ожидаемых изменений в будущем.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/c-20-is-feature-complete">Что нового в C++ 20 и какие изменения ожидают язык в будущем?</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 10 Aug 2019 14:42:52 GMT</pubDate>
      <content:encoded><![CDATA[<p>Те, кто знаком с C++, обычно делятся на два лагеря: одни любят его за обширность и универсальность, другие ненавидят за раздутую сложность и предпочитают придерживаться альтернативных языков. В любом случае, сейчас у вас есть шанс пересмотреть своё мнение о С++. Недавно был собран комитет по стандартам языка, чтобы завершить работу над последней версией языкового стандарта (C++ 20) и определить<a href="https://www.reddit.com/r/cpp/comments/cfk9de/201907_cologne_iso_c_committee_trip_report_the/"> новую функциональность, которая появится в следующем крупном релизе</a>.</p><p>После C++ 17 это будет шестая редакция стандарта. Этот язык прошёл долгий путь от своего бытия «надмножеством C». Программирование на C++ включает в себя множество стилей: от простейшего «C с классами» до написания кода, который выглядит как произведение искусства. <a href="https://ru.wikipedia.org/wiki/C%2B%2B20">Список новых возможностей</a> длинный, а объединённых предложений по спецификациям ещё больше. Каждого из этих дополнений хватит на свою собственную статью.</p><p>Для общего представления о том, что будет с C++ в следующем году, вкратце рассмотрим некоторые из новых функций, изменений и дополнений, которые ждут в C++ 20. От улучшенной проверки типов и сообщений об ошибках компилятора до Python-подобной обработки строк и планов по замене системы #include.</p><h2>Делаем язык безопасней</h2><p>Когда язык меньше ограничивает детали реализации, это обеспечивает большую гибкость для разработчиков. А также большой потенциал для «недоразумений», которые в будущем могут привести к ошибкам. По сей день это самая большая ценность и одновременно слабость C, и C++ всё ещё достаточно похож на него корнями. Ограничения — действенное, но непопулярное решение. Хорошо, что в C++ есть компромиссы, которые оставляют гибкость на уровне языка и добавляют ограничения на усмотрение разработчика.</p><h3>Рекомендации компилятору: явные константы</h3><p>Ещё в C++ 11 было введено ключевое слово constexpr как дополнение к обычному объявлению const, определяющему<a href="https://en.cppreference.com/w/cpp/language/constant_expression"> константное выражение</a>, которое можно вычислять во время компиляции. Это открывает множество возможностей оптимизации для компилятора, например позволяет явно заявить, что функция будет возвращать постоянное значение. Это помогает более чётко показать намерение функции, избегая потенциальных проблем в будущем. Например:</p><p>Технически между этими двумя функциями выше нет разницы, и любая из них будет возвращать константное значение, допустимое для присваивания переменной типа const. Разница лишь в том, что функция bar() делает это в явном виде. В случае foo() это скорее побочный эффект и без полного контекста не очевидно, что возвращаемое значение функции должно быть константой. Использование constexpr устраняет любые сомнения и позволяет избежать возможных побочных эффектов, что сделает код более стабильным в долгосрочной перспективе.</p><p>constexpr подвергался нескольким переработкам ещё до релиза C++ 20. Особенно это касается снятия ранее существующих ограничений на использование этих переработок. Самое главное, новый стандарт позволяет использование <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1064r0.html">функций типа virtual constexpr</a>. Разработчики могут использовать <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1002r0.pdf">try/catch внутри constexpr</a> (при условии отсутствия исключений внутри) и <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1330r0.pdf">менять члены внутри объединения</a>.</p><p>Более того, <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0980r0.pdf">std::string</a>, <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1004r1.pdf">std::vector</a>, а также <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1032r1.html">множество других ранее пропущенных </a><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1032r1.html"> в стандартной библиотеке</a><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1032r1.html"> функций</a> будут полностью использовать constexpr. Если необходимо проверить, действительно ли фрагмент кода выполняется внутри определения константы, сделать это можно с помощью функции <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0595r2.html">std::is_constant_evaluated()</a>, которая возвращает соответствующее логическое значение.</p><p>Стоит обратить внимание, constexpr-код подразумевает, что он может быть вычислен во время компиляции и является допустимым константным выражением. Но это не обязательно, и нет гарантии, что вычисление произойдёт во время компиляции, оно может быть отложено на время выполнения. Это в основном важно для проведения оптимизации компилятором и не влияет на поведение программы, но также показывает, что constexpr — это в первую очередь отметка о намерении сделать функцию константной.</p><p>В коде выше first будет определяться во время компиляции, поскольку все задействованные выражения и значения являются константами и известны во время компиляции. А second будет определяться во время выполнения, поскольку сам non_const_factor не является константой. Это не меняет того факта, что foo() по-прежнему будет возвращать константное значение, только компилятор пока не может точно определить, какое именно. Чтобы компилятор знал значение, в C++ 20 вводится ключевое слово <a href="https://en.cppreference.com/w/cpp/language/consteval">consteval</a>, чтобы объявить <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r3.html">функцию как непосредственную</a> (immediate). Объявление foo() как consteval вместо constexpr теперь вызовет ошибку. Фактически, непосредственные функции действительно известны только во время компиляции и это превращает conteval-функции в альтернативу для макрофункций.</p><p>С другой стороны проверки константного выражения находится новое ключевое слово <a href="https://gist.github.com/EricWF/128781c188b1a4fca7581e7ea943d58b">constinit</a>, которое говорит компилятору, что объект будет статически инициализирован с постоянным значением. Если вы знакомы с <a href="https://isocpp.org/wiki/faq/ctors#static-init-order">проблемой порядка статической инициализации</a>, то это попытка её решить.</p><p>Константные выражения — не единственные изменения в C++ 20, призванные улучшить проверку времени компиляции и обеспечить стабильность.</p><h3>Концепция концептов</h3><p>С технической точки зрения это не совсем новая вещь, концепты (Concepts) превратились из <a href="https://en.cppreference.com/w/cpp/experimental/constraints">экспериментальной функции</a> в <a href="https://en.cppreference.com/w/cpp/language/constraints">полноценную часть языкового стандарта</a>. Это позволяет добавлять семантические ограничения к шаблонам и в конечном счёте делает общие всплывающие подсказки при программировании более конкретными.</p><p>В некоторой степени связанные с <a href="https://en.cppreference.com/w/cpp/header/type_traits">особенностями типа</a> (type traits), концепты обеспечивают соответствие используемых в шаблоне данных указанному набору критериев и проверяют это в начале процесса компиляции. Например вместо проверки объекта на is_integral используется объект типа Integral. В результате, если определённое требование концепта не выполнено, компилятор может выдать короткое и значимое сообщение об ошибке вместо дампинга большого количества ошибок и предупреждений откуда-то из глубин кода шаблона, что не будет иметь особого смысла без дальнейшего копания в этом коде.</p><p>Компилятор знает, какие данные необходимы. Он также чётко показывает разработчикам, какие данные ожидаются, в первую очередь помогая избежать сообщений об ошибках и избегая недоразумений. С другой стороны концепты можно использовать для ограничения возвращаемого типа шаблонных функций, ограничивая переменные концептом, а не универсальным автоматическим типом, который можно рассматривать как версию void * на C++.</p><p>Некоторые базовые концепты будут предоставлены в <a href="https://en.cppreference.com/w/cpp/header/concepts">стандартной библиотеке</a>, и если вы не хотите ждать обновлённых компиляторов, GCC имеет экспериментальные концепты, реализованные ещё с версии 6. Вы можете включить их с помощью параметра командной строки -fconcepts. Обратите внимание, что в первоначальном проекте и текущей справочной документации имена концептов были определены по типу CamelCase, но <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1754r0.pdf">они будут изменены на snake_case</a>, чтобы сохранить согласованность со всеми другими стандартными идентификаторами.</p><h3>Диапазоны — новые итераторы</h3><p>Диапазоны (Ranges) по сути являются итераторами, которые охватывают последовательность значений в коллекциях, таких как списки или векторы. Но вместо того, чтобы постоянно перемещать начало и конец итератора, диапазоны просто сохраняют их внутри.</p><p>Как и <i>концепты</i>, д<i>иапазоны</i> также перешли из<a href="https://en.cppreference.com/w/cpp/experimental/ranges"> экспериментального состояния</a> в<a href="https://en.cppreference.com/w/cpp/ranges"> стандарт языка</a>. <i>Диапазоны</i> зависят от <i>концептов</i> и используют их для улучшения обработки старого итератора, позволяя добавлять ограничения к обработанным значениям. Помимо типов, ограничивающих значения, <i>диапазоны</i> воспринимают <i>представления</i> (Views) как особую форму <i>диапазона</i>, позволяющую манипулировать данными или фильтровать их, возвращая изменённую версию данных исходного <i>диапазона</i> в качестве ещё одного <i>диапазона</i>. Например, есть вектор целых чисел, и необходимо получить все чётные значения в квадрате — <i>диапазоны</i> и п<i>редставления</i> помогут вам в этом.</p><p>Со всеми этими изменениями компилятор окажет гораздо большую помощь при проверке типов и представит более полезные сообщения об ошибках.</p><h2>Форматирование строк</h2><p>Если говорить про сообщения об ошибках и про вывод в целом, <a href="https://github.com/fmtlib/fmt">библиотека libfmt</a> в соответствии с <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html">предложением её автора</a> будет интегрирована в языковой стандарт как std::format. Это обеспечит функциональность форматирования строк как в Python. По сравнению со всей неловкостью метода cout и тем фактом, что использование printf() в контексте C++ — это нечто неправильное, такое дополнение будет очень желанным.</p><p>Форматирование в стиле Python предлагает почти те же функции, что и printf(), только с другим синтаксисом формата строки. Оно устраняет избыточность и предлагает некоторые полезные дополнения, такие как двоичное целочисленное представление и центрированный вывод с или без символов заполнения. Самое большое преимущество — это возможность определять правила форматирования для пользовательских типов. На первый взгляд такой метод похож на __str__() в Python или toString() в Java, но при этом он также добавляет собственные типы форматирования.</p><p>В качестве примера рассмотрим strftime(). Хоть это и функция C, которая ведёт себя как snprintf(), отличие состоит в том, что она определяет пользовательские конкретные символы преобразования для своей строки формата и ожидает struct tm в качестве аргумента. Правильно реализованный std::format может быть расширен, чтобы вести себя так же, как <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1361r1.pdf">грядущее дополнение</a> к <a href="https://en.cppreference.com/w/cpp/chrono">библиотеке std::chrono</a>.</p><h3>Расположение</h3><p>В C++ 20 появилась ещё одна <a href="https://en.cppreference.com/w/cpp/experimental/source_location">экспериментальная функция</a> — <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1208r5.pdf">source_location</a>, обеспечивающая удобный доступ к имени файла, номеру строки или имени функции из текущего контекста вызова. В сочетании с std::format это основной кандидат для реализации пользовательской функции ведения журнала и практически современная альтернатива макросам препроцессора вроде __FILE__ и __LINE__.</p><h2>Модули</h2><p>Постепенное исключение препроцессора является долгосрочной целью в будущем C++ (consteval заменяет макрофункции, source_location заменяет один из наиболее часто используемых макросов). <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1103r3.pdf">Модули</a> — новый способ разделения исходного кода, который в конечном счёте призван заменить всю систему #include.</p><p>Пока одни разработчики говорят, что давно пора внести такие изменения, другие относятся к добавлению модулей достаточно критично​. Некоторые разработчики<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1427r0.pdf"> заявили о своей обеспокоенности</a> по поводу их текущего состояния. Каково бы ни было ваше собственное мнение, можно с уверенностью сказать, что это серьёзное изменение всей сущности языка и в то же время достаточно сложное мероприятие, которое не произойдёт быстро. Если вы хотите познакомиться с модулями поближе,<a href="https://gcc.gnu.org/wiki/cxx-modules"> GCC</a> и<a href="https://clang.llvm.org/docs/Modules.html"> Clang</a> уже в некоторой степени их поддерживают.</p><h2>Многое другое</h2><p>Это только малая часть всего списка, например <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4723.pdf">Coroutines</a> — ещё одна важная функция, которая будет <a href="https://en.cppreference.com/w/cpp/language/coroutines">добавлена в C++ 20</a>. Но чтобы описать все изменения, потребуется ещё хотя бы одна статья. Поэтому просто перечислим их:</p><ul><li>новая <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1135r5.html">библиотека синхронизации</a>;</li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0660r0.pdf">кооперативное прерывание потоков</a>;</li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1614r1.html">оператор &lt;=&gt;</a> для трёхсторонних сравнений;</li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1099r4.html">using enum</a> для уменьшения шума от разделении пространств имен;</li><li>пакет дополнений к лямбда-выражениям;</li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1152r3.html">частичный отказ от volatile</a> (части языка, важные для volatile, не изменятся).</li></ul><p>В целом C++ ждёт много изменений, и некоторые функции заслуживают того, чтобы ими восхищаться. Некоторые из этих новых функций и расширений были на других языках целую вечность (если не с самого начала). Поэтому интересно наблюдать, как некоторые из этих языков, которые когда-то находились под влиянием C++, теперь сами влияют на будущее этого языка.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как настроить полифилл globalThis в универсальном JavaScript</title>
      <link>https://tproger.ru/translations/universal-javascript-and-globalthis-feature</link>
      <comments>https://tproger.ru/translations/universal-javascript-and-globalthis-feature?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Туренко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/universal-javascript-and-globalthis-feature</guid>
      <description><![CDATA[<p>Свойство globalThis даёт единый доступ к глобальному this в любой среде JavaScript, а корректный полифилл требует учёта области видимости модулей.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/universal-javascript-and-globalthis-feature">Как настроить полифилл globalThis в универсальном JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 27 Jun 2019 09:52:59 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://github.com/tc39/proposal-global">Предложенное</a> свойство globalThis предполагает введение единого механизма доступа к глобальному значению this в любой среде JavaScript. Это похоже на обычный полифилл, но всё же немного отличается, и понять, что это на самом деле, довольно сложно.</p><p>В статье описаны трудности реализации правильного полифилла globalThis. Для него существуют следующие требования:</p><ul><li>должен работать в любой среде JavaScript, включая браузеры, воркеры и расширения браузеров. А также в Node.js, <a href="https://deno.land/">Deno</a> и <a href="https://github.com/GoogleChromeLabs/jsvu">standalone бинарных файлах</a> на движке JavaScript;</li><li>должен поддерживать <a href="https://developer.mozilla.org/ru/docs/Словарь/Sloppy_mode">грязный</a> и строгий (strict) режимы работы, а также <a href="https://developers.google.com/web/fundamentals/primers/modules">модули JavaScript</a>;</li><li>должен работать независимо от контекста, в котором запущен код (т. е. полифилл должен выдавать правильный результат, даже если его на этапе сборки обернут упаковщиком в строгий режим работы).</li></ul><p>Обратите внимание, что в модулях JavaScript есть область-посредник между глобальной областью видимости и вашим кодом. Область видимости модуля скрывает значение this глобальной области видимости. Поэтому ключевое слово this, которое видно на верхнем уровне в модулях, на самом деле имеет свойство undefined.</p><p>TL;DR globalThis != глобальный объект, но globalThis == this из глобальной области видимости.</p><h2>Альтернативы globalThis</h2><p>Так сложилось, что для доступа к глобальному объекту в разных средах JavaScript требуется разный синтаксис. Например, в веб-среде можно использовать window, self или frames, при этом для веб-воркеров (и сервис-воркеров) работать будет только self.</p><p>Node.js не работает со всем вышеперечисленным, в его случае нужно использовать global.</p><p>Ключевое слово this может использоваться в функциях, запущенных в грязном режиме, но в модулях и функциях, запущенных в строгом режиме, для него будет возвращаться значение undefined.</p><p>Проблема выше решается использованием Function(‘return this’)(), но в средах с отключенной функцией eval() вроде <a href="https://en.wikipedia.org/wiki/Content_Security_Policy">CSP</a> нельзя использовать Function подобным образом.</p><p>Примечание setTimeout(‘globalThis = this’, 0) не следует использовать по тем же причинам, что и eval() и Function. Кроме того, функция setTimeout не является частью <a href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout">ECMAScript</a>, а следовательно, не будет доступна во всех средах выполнения JavaScript. Вдобавок к этому, setTimeout асинхронна, и даже если бы она везде поддерживалась, использовать её в полифилле, от которого зависит другой код, было бы неразумно.</p><h2>Примитивный полифилл</h2><p>Похоже, вышеперечисленные методы можно было бы объединить в один полифилл, как например этот:</p><p>Но, к сожалению, такой полифилл не будет работать в функциях во время исполнения кода в строгом режиме, в модулях JavaScript и в небраузерных средах (кроме поддерживающих GlobalThis).</p><h2>Надёжный полифилл</h2><p>А можно ли вообще написать надёжный полифилл globalThis? Возьмём в качестве примера среду, в которой:</p><ol><li>Нельзя полагаться на значение globalThis, window, self, global или this.</li><li>Нельзя использовать конструктор Function или eval().</li><li>Можно полагаться на целостность остальной встроенной функциональности JavaScript.</li></ol><p>В таком случае есть решение, но оно не идеально.</p><p>Если установить значение функции на globalThis и вызвать его как метод, то можно получить доступ к this используя следующую функцию:</p><p>Как можно сделать что-то подобное, не полагаясь на globalThis или на специфичную связанную сущность, которая на него ссылается? Нельзя же просто сделать следующее:</p><p>Функция foo() больше не является методом, а поэтому в строгом режиме или в модулях JavaScript у ключевого слова this будет значение undefined. Однако, это не относится к <a href="https://learn.javascript.ru/getters-setters">геттерам и сеттерам</a>.</p><p>Скрипт выше устанавливает геттер на полифилл globalThis, получает к нему доступ, чтобы в итоге ссылаться на globalThis, затем очищает полифилл, удаляя геттер. Таким образом у нас появляется доступ к globalThis при любых обстоятельствах, но этот метод опирается на глобальный this в первой строке (где написано globalThis). Можно ли как-то избавиться от этой зависимости? Как можно установить глобально доступный геттер без прямого доступа к globalThis?</p><p>Вместо установки геттера на globalThis, нужно установить его на то, что глобально наследует объект — Object.prototype.</p><p>Примечание В спецификации ECMAScript не указано, что глобальное this наследует именно Object.prototype — только указано, что это строго должен быть объект. Функция Object.create(null) создаёт объект, который не наследуется от Object.prototype. Движок JavaScript мог бы использовать такой объект как глобальное this, не нарушая требования спецификации, но в этом случае фрагмент кода выше всё равно не сработал бы. Однако, в современных движках разработчики, похоже, согласны с тем, что глобальное this должно включать Object.prototype в своей цепочке прототипов.</p><p>Во избежание проблем с Object.prototype в современных средах JavaScript, где полифилл globalThis уже доступен, изменим его следующим образом</p><p>Или можно использовать __defineGetter__ :</p><p>Как вам такое? Перед вами самый ужасающий полифилл из когда-либо существовавших. Такой подход полностью противоречит общепринятой практике, согласно которой нельзя изменять объекты, которыми вы не владеете.</p><p>Не стоит играться с встроенными прототипами вообще — это объясняется в <a href="https://mathiasbynens.be/notes/prototypes">JavaScript Engine Fundamentals: optimizing prototypes</a>.</p><p>С другой стороны, единственный способ сломать этот полифилл — изменить object, Object.defineProperty (или Object.prototype._ _defineGetter) перед его запуском.</p><h2>Тестирование полифилла</h2><p>Этот полифилл — интересный пример универсального JavaScript: чистый и независимый код, который не полагается на какие-либо встроенные компоненты той или иной среды выполнения, поэтому работает везде, где работает ECMAScript. Итак, одна цель достигнута, теперь посмотрим, как он будет работать.</p><p>Обратите внимание на <a href="https://mathiasbynens.be/demo/globalthis">пример HTML-страницы</a> для полифилла, который журналирует globalThis, используя классический скрипт <a href="https://mathiasbynens.be/demo/globalthis.js">globalThis</a> вместе с <a href="https://mathiasbynens.be/demo/globalthis.mjs">globalThis.mjs</a> (с одинаковым исходным кодом). Этот пример может быть использован для проверки работы полифилла в браузерах. globalThis нативно поддерживается в <a href="https://v8.dev/blog/v8-release-71#javascript-language-features">Chrome 71</a>, Firefox 65, Safari 12.1 (+iOS Safari 12.2). Чтобы протестировать нужные части полифилла, откройте демо-страницу в старых версиях браузеров.</p><p>Примечание Полифилл не поддерживается в Internet Explorer 10 и старше. В этих браузерах строка __magic__.globalThis = __magic__ по каким-то причинам не делает globalThis доступным глобально, несмотря на то, что __magic__ служит рабочей ссылкой на глобальное this. В итоге выясняется, что __magic__ !== window, хотя оба относятся к [object Window], что как бы намекает, что браузеры могут запутаться в определении глобального объекта и глобального this. Внесение правок в полифилл для отката к одной из альтернатив позволяет ему работать в IE 10 и 9. Для поддержки в IE 8 нужно обернуть Object.defineProperty в try-catch, подобным образом откатившись в блок catch (это также поможет избежать проблемы в IE 7 с глобальным кодом, который не наследуется от Object.Prototype). Попробуйте поиграть с демо-версией, поддерживающей старые версии IE.</p><p>Для тестирования полифилла в Node.js и отдельных движках JavaScript скачайте те же самые файлы с расширением .js/.mjs.</p><p>Теперь можно тестировать в Node.js.</p><p>Для тестирования полифилла в отдельной оболочке JavaScript-движка используйте <a href="https://github.com/GoogleChromeLabs/jsvu">jsvu</a> для установки любого желаемого движка, а затем запустите скрипты напрямую. Например, протестируем в V8, v7.0 (без поддержки globalThis) и v7.1 (с поддержкой globalThis):</p><p>Таким же образом можно тестировать JavaScriptCore, SpiderMonkey, Chakra и другие JavaScript-движки. Ниже приведён пример использования JavaScriptCore:</p><h2>Заключение</h2><p>Написание универсального JavaScript может быть непростым делом и часто требует творческих решений. Новая функция globalThis облегчает написание универсального JavaScript, которому нужен доступ к глобальному значению this. Повсеместное использование globalThis сложнее, чем кажется, но есть работающее решение.</p><p>Используйте полифилл только тогда, когда это действительно необходимо. <a href="https://developers.google.com/web/fundamentals/primers/modules">Модули JavaScript</a> облегчают импорт и экспорт функциональности без изменения глобального состояния, и большинству современных JavaScript-кодов не нужен доступ к глобальному this.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как работают импорты в Python</title>
      <link>https://tproger.ru/translations/guide-into-python-imports</link>
      <comments>https://tproger.ru/translations/guide-into-python-imports?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Картофельный Повелитель]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/guide-into-python-imports</guid>
      <description><![CDATA[<p>Полное руководство по импортам в Python: как работает import, sys.path и __init__.py, чем отличаются абсолютные и относительные импорты. Начните разбираться!</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/guide-into-python-imports">Как работают импорты в Python</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 26 Jun 2019 09:16:29 GMT</pubDate>
      <content:encoded><![CDATA[<p>Порой бывает трудно правильно реализовать import с первого раза, особенно если мы хотим добиться правильной работы на плохо совместимых между собой версиях Python 2 и Python 3. Попытаемся разобраться, что из себя представляют импорты в Python и как написать решение, которое подойдёт под обе версии языка. Если вы только начинаете изучение Python, сначала прочитайте <a href="https://tproger.ru/articles/python-how-to-learn">руководство по изучению Python с нуля</a>.</p><p><b>Ключевые выводы</b><br />— Оператор import ищет модули по списку путей в sys.path, который включает директорию запущенного скрипта<br />— Импортирование пакета равноценно выполнению его файла __init__.py<br />— В Python 3 все папки считаются пакетами (неявные пакеты пространств имён), файл __init__.py больше не обязателен<br />— Абсолютные импорты предпочтительнее относительных — они понятнее и работают одинаково при любом способе запуска</p><h3>Содержание</h3><ul><li><a href="https://tproger.ru/#1">Ключевые моменты</a></li><li><a href="https://tproger.ru/#2">Основные определения</a></li><li><a href="https://tproger.ru/#3">Пример структуры директорий</a></li><li><a href="https://tproger.ru/#4">Что делает import</a></li><li><a href="https://tproger.ru/#5">Основы import и sys.path</a></li><li><a href="https://tproger.ru/#6">Чуть подробнее о sys.path</a></li><li><a href="https://tproger.ru/#7">Всё о __init__.py</a></li><li><a href="https://tproger.ru/#8">Использование объектов из импортированного модуля или пакета</a></li><li><a href="https://tproger.ru/#9">Используем dir() для исследования содержимого импортированного модуля</a></li><li><a href="https://tproger.ru/#10">Импортирование пакетов</a></li><li><a href="https://tproger.ru/#11">Абсолютный и относительный импорт</a></li><li><a href="https://tproger.ru/#12">Примеры</a></li><li><a href="https://tproger.ru/#13">Python 2 vs Python 3</a></li><li><a href="https://tproger.ru/#14">Прочие темы, не рассмотренные здесь, но достойные упоминания</a></li></ul><h2>Ключевые моменты</h2><ul><li>Выражения import производят поиск по списку путей в sys.path.</li><li>sys.path всегда включает в себя путь скрипта, запущенного из командной строки, и не зависит от текущей рабочей директории.</li><li>Импортирование пакета по сути равноценно импортированию  __init__.py этого пакета.</li></ul><h2>Основные определения</h2><ul><li>Модуль: любой файл *.py. Имя модуля — имя этого файла.</li><li>Встроенный модуль: «модуль», который был написан на Си, скомпилирован и встроен в интерпретатор Python, и потому не имеет файла *.py.</li><li>Пакет: любая папка, которая содержит файл __init__.py. Имя пакета — имя папки.С версии Python 3.3 любая папка (даже без __init__.py) считается пакетом.</li><li>Объект: в Python почти всё является объектом — функции, классы, переменные и т. д.</li></ul><h2>Пример структуры директорий</h2><p>Обратите внимание, что в корневой папке test/ нет файла __init__.py.</p><h2>Что делает import</h2><p>При импорте модуля Python выполняет весь код в нём. При импорте пакета Python выполняет код в файле пакета __init__.py, если такой имеется. Все объекты, определённые в модуле или __init__.py, становятся доступны импортирующему.</p><h2>Основы import и sys.path</h2><p>Вот как оператор import производит поиск нужного модуля или пакета согласно документации Python:</p><p>При импорте модуля</p><p>интерпретатор сначала ищёт встроенный модуль с таким именем. Если такого модуля нет, то идёт поиск файла spam.py</p><p>в списке директорий, определённых в переменной sys.path .</p><p>sys.path инициализируется из следующих мест:</p><ul><li>директории, содержащей исходный скрипт (или текущей директории, если файл не указан);</li><li>директории по умолчанию, которая зависит от дистрибутива Python;</li><li>PYTHONPATH (список имён директорий; имеет синтаксис, аналогичный переменной окружения PATH).</li></ul><p>Программы могут изменять переменную sys.path</p><p>после её инициализации. Директория, содержащая запускаемый скрипт, помещается в начало поиска перед путём к стандартной библиотеке. Это значит, что скрипты в этой директории будут импортированы вместо модулей с такими же именами в стандартной библиотеке.</p><p>Источник: <a href="https://docs.python.org/2/tutorial/modules.html#the-module-search-path">Python 2</a> и <a href="https://docs.python.org/3/tutorial/modules.html#the-module-search-path">Python 3</a></p><p>Технически документация не совсем полна. Интерпретатор будет искать не только файл (модуль) spam.py, но и папку (пакет) spam.</p><p>Обратите внимание, что Python сначала производит поиск среди встроенных модулей — тех, которые встроены непосредственно в интерпретатор. Список встроенных модулей зависит от дистрибутива Python, а найти этот список можно в sys.builtin_module_names (<a href="https://docs.python.org/2/library/sys.html#sys.builtin_module_names">Python 2</a> и <a href="https://docs.python.org/3/library/sys.html#sys.builtin_module_names">Python 3</a>). Обычно в дистрибутивах есть модули sys (всегда включён в дистрибутив), math, itertools, time и прочие.</p><p>В отличие от встроенных модулей, которые при поиске проверяются первыми, остальные (не встроенные) модули стандартной библиотеки проверяются после директории запущенного скрипта. Это приводит к сбивающему с толку поведению: возможно «заменить» некоторые, но не все модули стандартной библиотеки. Допустим, модуль math является встроенным модулем, а random — нет. Таким образом, import math в start.py импортирует модуль из стандартной библиотеки, а не наш файл math.py из той же директории. В то же время, import random в start.py импортирует наш файл random.py.</p><p>Кроме того, импорты в Python регистрозависимы: import Spam и import spam — разные вещи.</p><p>Функцию pkgutil.iter_modules() (<a href="https://docs.python.org/2/library/pkgutil.html#pkgutil.iter_modules">Python 2</a> и <a href="https://docs.python.org/3/library/pkgutil.html#pkgutil.iter_modules">Python 3</a>) можно использовать, чтобы получить список всех модулей, которые можно импортировать из заданного пути:</p><h2>Чуть подробнее о sys.path</h2><p>Чтобы увидеть содержимое sys.path, запустите этот код:</p><p>Документация Python описывает sys.path так:</p><p>Список строк, указывающих пути для поиска модулей. Инициализируется из переменной окружения PYTHONPATH и директории по умолчанию, которая зависит от дистрибутива Python.При запуске программы после инициализации первым элементом этого списка, path[0], будет директория, содержащая скрипт, который был использован для вызова интерпретатора Python. Если директория скрипта недоступна (например, если интерпретатор был вызван в интерактивном режиме или скрипт считывается из стандартного ввода), то path[0] является пустой строкой. Из-за этого Python сначала ищет модули в текущей директории. Обратите внимание, что директория скрипта вставляется перед путями, взятыми из PYTHONPATH.Источник: Python 2 и Python 3</p><p>Документация к интерфейсу командной строки Python добавляет информацию о запуске скриптов из командной строки. В частности, при запуске python &lt;script&gt;.py.</p><p>Если имя скрипта ссылается непосредственно на Python-файл, то директория, содержащая этот файл, добавляется в начало sys.path, а файл выполняется как модуль main.Источник: Python 2 и Python 3</p><p>Итак, повторим порядок, согласно которому Python ищет импортируемые модули:</p><ol><li>Модули стандартной библиотеки (например, math, os).</li><li>Модули или пакеты, указанные в sys.path:Если интерпретатор Python запущен в интерактивном режиме:sys.path[0] — пустая строка "". Это значит, что Python будет искать в текущей рабочей директории, из которой вы запустили интерпретатор. В Unix-системах эту директорию можно узнать с помощью команды pwd.Если мы запускаем скрипт командой python</li></ol><p>Обратите внимание, что при запуске скрипта для sys.path важна не директория, в которой вы находитесь, а путь к самому скрипту. Например, если в командной строке мы находимся в test/folder и запускаем команду python ./packA/subA/subA1.py, то sys.path будет включать в себя test/packA/subA/, но не test/.</p><p>Кроме того, sys.path общий для всех импортируемых модулей. Допустим, мы вызвали python start.py. Пусть start.py импортирует packA.a1, а a1.py выводит на экран sys.path. В таком случае sys.path будет включать test/ (путь к start.py), но не test/packA (путь к a1.py). Это значит, что a1.py может вызвать import other, так как other.py находится в test/.</p><h2>Всё о __init__.py</h2><p>У файла __init__.py есть две функции:</p><ol><li>Превратить папку со скриптами в импортируемый пакет модулей (до Python 3.3).</li><li>Выполнить код инициализации пакета.</li></ol><h3>Превращение папки со скриптами в импортируемый пакет модулей</h3><p>Чтобы импортировать модуль (или пакет) из директории, которая находится не в директории нашего скрипта (или не в директории, из которой мы запускаем интерактивный интерпретатор), этот модуль должен быть в пакете.</p><p>Как было сказано ранее, любая директория, содержащая файл __init__.py, является пакетом. Например, при работе с Python 2.7 start.py может импортировать пакет packA, но не packB, так как в директории test/packB/ нет файла __init__.py.</p><p>Это не относится к Python 3.3 и выше благодаря появлению <a href="https://www.python.org/dev/peps/pep-0420/">неявных пакетов пространств имён</a>. Проще говоря, в Python 3.3+ все папки считаются пакетами, поэтому пустые файлы __init__.py больше не нужны.</p><p>Допустим, packB — пакет пространства имён, так как в нём нет __init__.py. Если запустить интерактивную оболочку Python 3.6 в директории test/, то мы увидим следующее:</p><h3>Выполнение кода инициализации пакета</h3><p>В момент, когда пакет или один из его модулей импортируется в первый раз, Python выполняет __init__.py в корне пакета, если такой файл существует. Все объекты и функции, определённые в __init__.py, считаются частью пространства имён пакета.</p><p>Рассмотрим следующий пример:</p><p>test/packA/a1.py</p><p>test/packA/__init__.py</p><p>test/start.py</p><p>Вывод после запуска python start.py:</p><p>Примечание Если a1.py вызовет import a2, и мы запустим python a1.py, то test/packA/__init__.py не будет вызван, несмотря на то, что a2 рода бы является частью пакета packA. Это связано с тем, что когда Python выполняет скрипт (в данном случае a1.py), содержащая его папка не считается пакетом.</p><h2>Использование объектов из импортированного модуля или пакета</h2><p>Есть 4 разных вида импортов:</p><ol><li>import &lt;пакет&gt;</li><li>import &lt;модуль&gt;</li><li>from &lt;пакет&gt; import &lt;модуль или подпакет или объект&gt;</li><li>from &lt;модуль&gt; import &lt;объект&gt;</li></ol><p>Пусть X — имя того, что идёт после import:</p><ul><li>Если X — имя модуля или пакета, то для того, чтобы использовать объекты, определённые в X, придётся писать X.объект.</li><li>Если X — имя переменной, то её можно использовать напрямую.</li><li>Если X — имя функции, то её можно вызвать с помощью X().</li></ul><p>Опционально после любого выражения import X можно добавить as Y. Это переименует X в Y в пределах скрипта. Учтите, что имя X с этого момента становится недействительным. Частым примером такой конструкции является import numpy as np.</p><p>Аргументом для import может быть как одно имя, так и их список. Каждое из имён можно переименовать с помощью as. Например, следующее выражение будет действительно в start.py: import packA as pA, packA.a1, packA.subA.sa1 as sa1.</p><p>Пример: нужно в start.py импортировать функцию helloWorld() из sa1.py.</p><ul><li>Решение 1: from packA.subA.sa1 import helloWorld. Мы можем вызвать функцию напрямую по имени: x = helloWorld().</li><li>Решение 2: from packA.subA import sa1 или то же самое import packA.subA.sa1 as sa1. Для использования функции нам нужно добавить перед её именем имя модуля: x = sa1.helloWorld(). Иногда такой подход предпочтительнее первого, так как становится ясно, из какого модуля взялась та или иная функция.</li><li>Решение 3: import packA.subA.sa1. Для использования функции перед её именем нужно добавить полный путь: x = packA.subA.sa1.helloWorld().</li></ul><p>Прим. перев. После переименования с помощью as новое имя нельзя использовать в качестве имени пакета или модуля для последующих импортов. Иными словами, команда вроде следующей недействительна: import packA as pA, pA.a1.</p><h2>Используем dir() для исследования содержимого импортированного модуля</h2><p>После импортирования модуля можно использовать функцию dir() для получения списка доступных в модуле имён. Допустим, мы импортируем sa1. Если в sa1.py есть функция helloWorld(), то dir(sa1) будет включать helloWorld:</p><h2>Импортирование пакетов</h2><p>Импортирование пакета по сути равноценно импортированию его __init__.py. Вот как Python на самом деле видит пакет:</p><p>После импорта становятся доступны только те объекты, что определены в __init__.py пакета. Поскольку в packB нет такого файла, от import packB (в Python 3.3.+) будет мало толку, так как никакие объекты из этого пакета не становятся доступны. Последующий вызов модуля packB.b1 приведёт к ошибке, так как он ещё не был импортирован.</p><h2>Абсолютный и относительный импорт</h2><p>При абсолютном импорте используется полный путь (от начала корневой папки проекта) к желаемому модулю.</p><p>При относительном импорте используется относительный путь (начиная с пути текущего модуля) к желаемому модулю. Есть два типа относительных импортов:</p><ol><li>При явном импорте используется формат from .&lt;модуль/пакет&gt; import X, где символы точки . показывают, на сколько директорий «вверх» нужно подняться. Одна точка . показывает текущую директорию, две точки .. — на одну директорию выше и т. д.</li><li>Неявный относительный импорт пишется так, как если бы текущая директория была частью sys.path. Такой тип импортов поддерживается только в Python 2.</li></ol><p>В документации Python об относительных импортах в Python 3 написано следующее:</p><p>Единственный приемлемый синтаксис для относительных импортов — from .[модуль] import [имя]. Все импорты, которые начинаются не с точки ., считаются абсолютными.Источник: What's New in Python 3.0</p><p>В качестве примера допустим, что мы запускаем start.py, который импортирует a1, который импортирует other, a2 и sa1. Тогда импорты в a1.py будут выглядеть следующим образом:</p><p>Абсолютные импорты:</p><p>Явные относительные импорты:</p><p>Неявные относительные импорты (не поддерживаются в Python 3):</p><p>Учтите, что в относительных импортах с помощью точек . можно дойти только до директории, содержащей запущенный из командной строки скрипт (не включительно). Таким образом, from .. import other не сработает в a1.py. В результате мы получим ошибку ValueError: attempted relative import beyond top-level package.</p><p>Как правило, абсолютные импорты предпочтительнее относительных. Они позволяют избежать путаницы между явными и неявными импортами. Кроме того, любой скрипт с явными относительными импортами нельзя запустить напрямую:</p><p>Имейте в виду, что относительные импорты основаны на имени текущего модуля. Так как имя главного модуля всегда "__main__", модули, которые должны использоваться как главный модуль приложения, должны всегда использовать абсолютные импорты.Источник: Python 2 и Python 3</p><h2>Примеры</h2><h3>Пример 1: sys.path известен заранее</h3><p>Если вы собираетесь вызывать только python start.py или python other.py, то прописать импорты всем модулям не составит труда. В данном случае sys.path всегда будет включать папку test/. Таким образом, все импорты можно писать относительно этой папки.</p><p>Пример: файлу в проекте test нужно импортировать функцию helloWorld() из sa1.py.</p><p>Решение: from packA.subA.sa1 import helloWorld (или любой другой эквивалентный синтаксис импорта).</p><h3>Пример 2: sys.path мог измениться</h3><p>Зачастую нам требуется как запускать скрипт напрямую из командной строки, так и импортировать его как модуль в другом скрипте. Как вы увидите далее, здесь могут возникнуть проблемы, особенно в Python 3.</p><p>Пример: пусть start.py нужно импортировать a2, которому нужно импортировать sa2. Предположим, что start.py всегда запускается напрямую, а не импортируется. Также мы хотим иметь возможность запускать a2 напрямую.</p><p>Звучит просто, не так ли? Нам всего лишь нужно выполнить два импорта: один в start.py и другой в a2.py.</p><p>Проблема: это один из тех случаев, когда sys.path меняется. Когда мы выполняем start.py, sys.path содержит test/, а при выполнении a2.py sys.path содержит test/packA/.</p><p>С импортом в start.py нет никаких проблем. Так как этот модуль всегда запускается напрямую, мы знаем, что при его выполнении в sys.path всегда будет test/. Тогда импортировать a2 можно просто с помощью import packA.a2.</p><p>С импортом в a2.py немного сложнее. Когда мы запускаем start.py напрямую, sys.path содержит test/, поэтому в a2.py импорт будет выглядеть как from packA.subA import sa2. Однако если запустить a2.py напрямую, то в sys.path уже будет test/packA/. Теперь импорт вызовет ошибку, так как packA не является папкой внутри test/packA/.</p><p>Вместо этого мы могли бы попробовать from subA import sa2. Это решает проблему при запуске a2.py напрямую, однако теперь создаёт проблему при запуске start.py. В Python 3 это приведёт к ошибке, потому что subA не находится в sys.path (в Python 2 это не вызовет проблемы из-за поддержки неявных относительных импортов).</p><p>Обобщим информацию:</p><figure><img src="https://media.tproger.ru/user-uploads/33794/2023-12-28/f82fd325-8540-45e1-8523-396d2c846330.png" alt="" /></figure><p>Использование относительного импорта from .subA import sa2 будет иметь тот же эффект, что и from packA.subA import sa2.</p><p>Вряд ли для этой проблемы есть чистое решение, поэтому вот несколько обходных путей:</p><p>1. Использовать абсолютные импорты относительно директории test/ (т. е. средняя колонка в таблице выше). Это гарантирует, что запуск start.py напрямую всегда сработает. Чтобы запустить a2.py напрямую, запустите его как импортируемый модуль, а не как скрипт:</p><ol><li>В консоли смените директорию на  test/.</li><li>Запустите python -m packA.a2.</li></ol><p>2. Использовать абсолютные импорты относительно директории test/ (средняя колонка в таблице). Это гарантирует, что запуск start.py напрямую всегда сработает. Чтобы запустить a2.py напрямую, можно изменить sys.path в a2.py, чтобы включить test/packA/ перед импортом sa2.</p><p>Примечание Обычно этот метод работает, однако в некоторых случаях переменная __file__ может быть неправильной. В таком случае нужно использовать встроенный пакет inspect. Подробнее в <a href="https://stackoverflow.com/a/11158224">этом ответе</a> на StackOverflow.</p><p>3. Использовать только Python 2 и неявные относительные импорты (последняя колонка в таблице).</p><p>4. Использовать абсолютные импорты относительно директории test/ и добавить её в переменную среды PYTHONPATH. Это решение не переносимо, поэтому лучше не использовать его. О том, как добавить директорию в PYTHONPATH, читайте в <a href="https://stackoverflow.com/q/3402168">этом ответе</a>.</p><h3>Пример 3: sys.path мог измениться (вариант 2)</h3><p>А вот ещё одна проблема посложнее. Допустим, модуль a2.py никогда не надо запускать напрямую, но он импортируется start.py и a1.py, которые запускаются напрямую.</p><p>В этом случае первое решение из примера выше не сработает. Тем не менее, всё ещё можно использовать остальные решения.</p><h3>Пример 4: импорт из родительской директории</h3><p>Если мы не изменяем PYTHONPATH и стараемся не изменять sys.path программно, то сталкиваемся со следующим основным ограничением импортов в Python: при запуске скрипта напрямую невозможно импортировать что-либо из его родительской директории.</p><p>Например, если бы нам пришлось запустить python sa1.py, то этот модуль не смог бы ничего импортировать из a1.py без вмешательства в PYTHONPATH или sys.path.</p><p>На первый взгляд может показаться, что относительные импорты (например from .. import a1) помогут решить эту проблему. Однако запускаемый скрипт (в данном случае sa1.py) считается «модулем верхнего уровня». Попытка импортировать что-либо из директории над этим скриптом приведёт к ошибке ValueError: attempted relative import beyond top-level package.</p><p>Для решения этой проблемы лучше её не создавать и избегать написания скриптов, которые импортируют из родительской директории. Если этого нельзя избежать, то предпочтительным обходным путём является изменение sys.path.</p><h2>Python 2 vs Python 3</h2><p>Мы разобрали основные отличия импортов в Python 2 и Python 3. Они ещё раз изложены здесь наряду с менее важными отличиями:</p><ol><li>Python 2 поддерживает неявные относительные импорты, Python 3 — нет.</li><li>В Python 2, чтобы папка считалась пакетом и её можно было импортировать, она должна содержать файл __init__.py. С версии Python 3.3 благодаря введению неявных пакетов пространств имён все папки считаются пакетами вне зависимости от наличия __init__.py.</li><li>В Python 2 можно написать from &lt;модуль&gt; import * внутри функции, а в Python 3 — только на уровне модуля.</li></ol><h2>Ещё немного полезной информации по импортам</h2><ul><li>Можно использовать переменную __all__ в __init__.py, чтобы указать, что будет импортировано выражением from &lt;модуль&gt; import *. Смотрите документацию для <a href="https://docs.python.org/2/tutorial/modules.html#importing-from-a-package">Python 2</a> и <a href="https://docs.python.org/3/tutorial/modules.html#importing-from-a-package">Python 3</a>.</li><li>Можно использовать if __name__ == '__main__' для проверки, был ли скрипт импортирован или запущен напрямую. Документация для <a href="https://docs.python.org/2/library/__main__.html">Python 2</a> и <a href="https://docs.python.org/3/library/__main__.html">Python 3</a>.</li><li>Можно установить проект в качестве пакета (в режиме разработчика) с помощью pip install -e &lt;проект&gt;, чтобы добавить корень проекта в sys.path. Подробнее в этом ответе на <a href="https://stackoverflow.com/questions/23984973/how-to-run-tests-without-installing-package">StackOverflow</a>.</li><li>from &lt;модуль&gt; import * не импортирует имена из модуля, которые начинаются с нижнего подчеркивания _. Подробнее читайте в документации <a href="https://docs.python.org/2/tutorial/modules.html#more-on-modules">Python 2</a> и <a href="https://docs.python.org/3/tutorial/modules.html#more-on-modules">Python 3</a>.</li><li>Смотрите также: <a href="https://tproger.ru/translations/python-data-types/">типы данных в Python</a>, <a href="https://tproger.ru/explain/python-dictionaries/">словари Python</a>, а также полное <a href="https://tproger.ru/articles/python-how-to-learn">руководство по изучению Python</a>.</li></ul><h2>Часто задаваемые вопросы</h2><h3>Чем отличается import от from ... import в Python?</h3><p>Конструкция import module импортирует модуль целиком, и для доступа к его объектам нужно писать module.object. Конструкция from module import object импортирует конкретный объект напрямую в текущее пространство имён, и его можно использовать без префикса. Второй вариант удобнее для часто используемых объектов, но первый лучше читается в больших проектах.</p><h3>Почему возникает ошибка ModuleNotFoundError при импорте?</h3><p>Ошибка ModuleNotFoundError означает, что Python не нашёл указанный модуль ни в одном из путей sys.path. Чаще всего причина в том, что модуль не установлен (pip install), скрипт запущен из неправильной директории, или структура пакетов нарушена (нет __init__.py в Python 2).</p><h3>Нужен ли файл __init__.py в Python 3?</h3><p>Начиная с Python 3.3, файл __init__.py не обязателен для того, чтобы папка считалась пакетом (неявные пакеты пространств имён). Однако он по-прежнему полезен для выполнения кода инициализации, определения переменной __all__ и экспорта объектов на уровне пакета.</p><h3>Как избежать циклических импортов в Python?</h3><p>Циклические импорты возникают, когда два модуля импортируют друг друга. Для решения проблемы можно перенести общий код в третий модуль, использовать отложенный импорт внутри функции (вместо уровня модуля) или реорганизовать структуру проекта так, чтобы зависимости были однонаправленными.</p>]]></content:encoded>
    </item>
    <item>
      <title>Разбираемся с Async/Await в JavaScript на примерах</title>
      <link>https://tproger.ru/translations/understanding-async-await-in-javascript</link>
      <comments>https://tproger.ru/translations/understanding-async-await-in-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/understanding-async-await-in-javascript</guid>
      <description><![CDATA[<p>Асинхронное программирование в JavaScript на примерах кода: чем неудобны вложенные колбэки, ведущие к Callback Hell, и что даёт Async/Await.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/understanding-async-await-in-javascript">Разбираемся с Async/Await в JavaScript на примерах</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 25 Jun 2019 11:34:19 GMT</pubDate>
      <content:encoded><![CDATA[<p>Callback — это не что-то замысловатое или особенное, а просто функция, вызов которой отложен на неопределённое время. Благодаря асинхронному характеру JavaScript, обратные вызовы нужны были везде, где результат не может быть получен сразу.</p><p>Ниже приведён пример асинхронного чтения файла на Node.js:</p><p>Проблемы начинаются, когда нужно выполнить несколько асинхронных операций. Просто представьте себе подобный сценарий:</p><ul><li>Выполняется запрос в БД на некого пользователя Arfat. Нужно считать его поле profile_img_url и загрузить соответствующее изображение с сервера someServer.ru.</li><li>После загрузки изображения необходимо его конвертировать, допустим из PNG в JPEG.</li><li>В случае успешной конвертации нужно отправить письмо на почту пользователя.</li><li>Это событие нужно занести в файл transformations.log и указать дату.</li></ul><p>Обратите внимание на вложенность обратных вызовов и пирамиду из }) в конце. Подобные случаи принято называть <a href="http://callbackhell.com/">Callback Hell</a> или <a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_%28programming%29">Pyramid of Doom</a>. Вот основные недостатки:</p><ul><li>Такой код сложно читать.</li><li>В таком коде сложно обрабатывать ошибки и одновременно сохранять его «качество».</li></ul><p>Для решения этой проблемы в JavaScript были придуманы <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">промисы</a> (англ. promises). Теперь глубокую вложенность коллбэков можно заменить ключевым словом then:</p><p>Код стал читаться сверху вниз, а не слева направо, как это было в случае с обратными вызовами. Это плюс к читаемости. Однако и у промисов есть свои проблемы:</p><ul><li>Всё ещё нужно работать с кучей .then.</li><li>Вместо обычного try/catch нужно использовать .catch для обработки всех ошибок.</li><li>Работа с несколькими промисами в цикле не всегда интуитивно понятна и местами сложна.</li></ul><p>В качестве демонстрации последнего пункта попробуйте выполнить такое задание:</p><p>Предположим, что у вас есть цикл for, который выводит последовательность чисел от 0 до 10 со случайным интервалом (от 0 до n секунд). Используя промисы нужно изменить цикл так, чтобы числа выводились в строгой последовательности от 0 до 10. К примеру, если вывод нуля занимает 6 секунд, а единицы 2 секунды, то единица должна дождаться вывода нуля и только потом начать свой отсчёт (чтобы соблюдать последовательность).</p><p>Стоит ли говорить, что в решении этой задачи нельзя использовать конструкцию async/await либо .sort функцию? Решение будет в конце.</p><h2>Async функции</h2><p>Добавление async-функций в ES2017 (ES8) сделало работу с промисами легче.</p><ul><li>Важно отметить, что async-функции работают поверх промисов.</li><li>Эти функции не являются принципиально другими концепциями.</li><li>Async-функции были задуманы как альтернатива коду, использующему промисы.</li><li>Используя конструкцию async/await, можно полностью избежать использование цепочек промисов.</li><li>С помощью async-функций возможно организовать работу с асинхронным кодом в синхронном стиле.</li></ul><p>Как видите, знание промисов всё же необходимо для понимания работы async/await.</p><h3>Синтаксис</h3><p>Синтаксис состоит из двух ключевых слов: async и await. Первое делает функцию асинхронной. Именно в таких функциях разрешается использование await. Использование await в любом другом случае вызовет ошибку.</p><p>Обратите внимание, что async вставляется в начале объявления функции, а в случае стрелочной функции — между знаком = и скобками.</p><p>Async-функции могут быть помещены в объект в качестве методов или же просто использоваться в объявлении класса.</p><p>Примечание Конструкторы класса и геттеры/сеттеры не могут быть асинхронными.</p><h3>Семантика и правила выполнения</h3><p>Async-функции похожи на обычные функции в JavaScript, за исключением нескольких вещей:</p><h4>Async-функции всегда возвращают промисы</h4><p>Функция fn возвращает строку 'hello'. Т. к. это асинхронная функция, значение строки обёртывается в промис (с помощью конструктора).</p><p>Код выше можно переписать и без использования async:</p><p>В таком случае, вместо async, код вручную возвращает промис.</p><h4>Тело асинхронной функции всегда обёртывается в новый промис</h4><p>Если возвращаемое значение является примитивом, async-функция возвращает это значение, обёрнутое в промис. Но если возвращаемое значение и есть объект промиса, его решение возвращается в новом промисе.</p><p>Что происходит, когда внутри асинхронной функции возникает какая-нибудь ошибка?</p><p>Если ошибка не будет обработана, foo() вернёт промис с реджектом. В таком случае вместо Promise.resolve вернётся Promise.reject, содержащий ошибку.</p><p>Суть async-функций в том, что что бы вы не возвращали, на выходе вы всегда будете получать промис.</p><h4>Асинхронные функции приостанавливаются при каждом await выражении</h4><p>await сказывается на выражениях. Если выражение является промисом, то async-функция будет приостановлена до тех пор, пока промис не выполнится. Если же выражение не является промисом, то оно конвертируется в промис через Promise.resolve и потом завершается.</p><p>Как работает fn функция?</p><ol><li>После вызова fn функции первая строка конвертируется из const a = await 9; в const a = await Promise.resolve(9);.</li><li>После использования await, выполнение функции приостанавливается, пока a не получит своё значение (в данном случае это 9).</li><li>delayAndGetRandom(1000) приостанавливает выполнение fn функции, пока не завершится сама (после 1 секунды). Это, фактически, можно назвать остановкой fn функции на 1 секунду.</li><li>Также delayAndGetRandom(1000) через resolve возвращает случайное значение, которое присваивается переменной b.</li><li>Случай с переменной c идентичен случаю переменной a. После этого опять происходит пауза на 1 секунду, но теперь delayAndGetRandom(1000) ничего не возвращает, т. к. этого не требуется.</li><li>Под конец эти значения считаются по формуле a + b * c. Результат обёртывается в промис с помощью Promise.resolve и возвращается функцией.</li></ol><p>Примечание Если такие паузы напоминают вам <a href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5">генераторы</a> в ES6, то на это есть <a href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5">свои причины</a>.</p><h2>Решение задачи</h2><p>Вот решение задачи, поставленной в начале статьи, с использованием async/await.</p><p>В функции finishMyTask используется await для ожидания результатов таких операций, как queryDatabase, sendEmail, logTaskInFile и т. д. Если сравнить это решение с решением, использовавшим промисы, то вы обратите внимание на их сходство. Однако версия с async/await упрощает синтаксические сложности. В этом способе нет кучи коллбэков и цепочек .then/.catch.</p><p>Вот то решение с выводом чисел. Тут есть два способа:</p><p>С использованием async-функций решение поставленной задачи упрощается до безобразия:</p><h2>Обработка ошибок</h2><p>Как было сказано выше, необработанные ошибки обёртываются в неудачный (rejected) промис. Но в async-функциях всё ещё можно использовать конструкцию try-catch для синхронной обработки ошибок.</p><p>canRejectOrReturn() — это асинхронная функция, которая будет удачно завершатся с 'Число подошло', либо неудачно завершаться с Error('Простите, число больше, чем нужно.').</p><p>Поскольку в коде выше ожидается выполнение canRejectOrReturn, то его собственное неудачное завершение вызовет исполнение блока catch. Поэтому функция foo завершится либо с undefined (т. к. в блоке try ничего не возвращается), либо с 'Ошибка обработана'. Поэтому у этой функции не будет неудачного завершения, т. к. try-catch блок будет обрабатывать  ошибку самой функции foo.</p><p>Вот другой пример:</p><p>Обратите внимание, что в коде выше из foo возвращается (без ожидания) canRejectOrReturn. foo завершится либо с 'число подошло', либо с реджектом Простите, число больше, чем нужно.‘). Блок catch никогда не будет исполняться.</p><p>Это происходит из-за того, что foo возвращает промис, который передан от canRejectOrReturn. Следовательно, решение функции foo становится решением canRejectOrReturn. Такой код можно представить всего в двух строках:</p><p>Вот что получится, если использовать await и return разом:</p><p>В коде выше foo будет удачно завершаться и с  'число подошло', и с 'Ошибка обработана'. В таком коде реджектов не будет. Но в отличие от одного из примеров выше, foo завершится со значением canRejectOrReturn, а не с undefined.</p><p>Вы можете убедиться в этом сами, убрав строку return await canRejectOrReturn():</p><h2>Популярные ошибки и подводные камни</h2><p>Из-за сложных манипуляций с промисами и async/await концепциями вы можете встретиться с различными тонкостями, что может привести к ошибкам.</p><h3>Не забывайте await</h3><p>Частая ошибка заключается в том, что перед промисом забывается ключевое слово await:</p><p>Обратите внимание, здесь не используется ни await, ни return. Функция foo всегда будет завершаться с undefined (без задержки в 1 секунду). Тем не менее, промис будет выполняться. Если промис будет выдавать ошибку либо реджект, то будет вызываться UnhandledPromiseRejectionWarning.</p><h3>async-функции в обратных вызовах</h3><p>async-функции часто используются в .map или .filter в качестве коллбэков. Вот пример — допустим, существует функция fetchPublicReposCount(username), которая возвращает количество открытых репозиториев на GitHub. Есть 3 пользователя, чьи показатели нужно взять. Используется такой код:</p><p>И для того, чтобы получить количество репозиториев пользователей (['ArfatSalman', 'octocat', 'norvig']), код должен выглядеть как-то так:</p><p>Обратите внимание на слово await в обратном вызове функции .map. Можно было бы ожидать, что переменная counts будет содержать число — количество репозиториев. Но как было сказано ранее, все async-функции возвращают промисы. Следовательно, counts будет массивом промисов. .map вызывает анонимной коллбэк для каждого пользователя.</p><h3>Слишком последовательное использование await</h3><p>Допустим, есть такой код:</p><p>В переменную count помещается количество репозиториев, потом это количество добавляется в массив counts. Проблема этого кода в том, что пока с сервера не придут данные первого пользователя, все последующие пользователи будут находиться в ожидании. Получается, что в один момент времени обрабатывается только один пользователь.</p><p>Если на обработку одного пользователя будет уходить 300 мс, то на всех пользователей уйдёт почти секунда. В этом случае затрачиваемое время будет линейно зависеть от количества пользователей. Поскольку получение количества репозиториев не зависит друг от друга, то можно распараллелить эти процессы. Тогда пользователи будут обрабатываться одновременно, а не последовательно. Для этого понадобятся .map и Promise.all.</p><p>Promise.all на входе получает массив промисов и возвращает промис. Возвращаемый промис завершается после окончания всех промисов в массиве либо при первом реджекте. Возможно, все эти промисы не запустятся строго одновременно. Чтобы добиться строгого параллелизма, взгляните на <a href="https://github.com/sindresorhus/p-map">p-map</a>. А если нужно, чтобы async-функции были более адаптивными, посмотрите на <a href="https://github.com/tc39/proposal-async-iteration">Async Iterators</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Удивительные возможности современного C++, которые полезно знать каждому разработчику</title>
      <link>https://tproger.ru/translations/cpp-surprising-features</link>
      <comments>https://tproger.ru/translations/cpp-surprising-features?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Туренко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/cpp-surprising-features</guid>
      <description><![CDATA[<p>Обзор функциональных возможностей языка, появившихся начиная с 11 версии: ключевое слово auto избавляет от ручного объявления типов данных.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/cpp-surprising-features">Удивительные возможности современного C++, которые полезно знать каждому разработчику</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 21 Jun 2019 08:43:15 GMT</pubDate>
      <content:encoded><![CDATA[<p>Было время, когда С++ не хватало динамизма, и увлечься этим языком было трудно. Но всё изменилось, когда было принято решение развить стандарт C++.</p><p>С 2011 года язык стал более динамичным и постоянно развивается. Не поймите неправильно, C++ — всё ещё один из самых сложных языков. Но с того времени он стал для программиста удобнее, чем в предыдущих версиях.</p><p>В статье мы рассмотрим некоторые интересные функциональные возможности языка.</p><h2>Ключевое слово auto</h2><p>Когда в 11 версии C++ только появилось auto, жизнь стала намного легче.</p><p>Идея auto состояла в том, чтобы заставить компилятор C++ определять тип ваших данных во время компиляции, вместо того чтобы заставлять вас каждый раз объявлять тип. Это было удобно, если у вас были типы данных вроде map&lt;string, vector &lt;pair &lt;int, int&gt;&gt;&gt; ?</p><p>Посмотрите на строку номер 5. Вы не можете объявить что-либо без инициализатора. Строка 5 не сообщает компилятору, каким может быть тип данных.</p><p>Изначально auto было несколько ограничено. Затем, в более поздних версиях языка, у него появилось больше возможностей.</p><p>В строках 7 и 8 была использована инициализация в скобках. Эта функция также была добавлена в 11 версии C++.</p><p>Не забывайте, что в случае использования auto у компилятора должен быть способ определить ваш тип.</p><p>Теперь встаёт хороший вопрос, что произойдёт, если мы напишем auto a = {1, 2, 3}? Это ошибка компиляции? Это вектор?</p><p>На самом деле, в 11 версии C++ был представлен std::initializer_list&lt;type&gt;. Инициализированный список в скобках будет считаться легковесным контейнером, если объявлен как auto.</p><p>И как упоминалось ранее, определять типы объектов компилятором полезно, когда у вас есть сложные структуры данных:</p><p>Не забудьте проверить строку 25! Выражение auto [v1, v2] = itr.second — новая функция в 17 версии C++. Это называется структурным связыванием. В предыдущих версиях приходилось извлекать каждую переменную отдельно. Но структурное связывание сделало этот процесс более удобным.</p><p>Более того, если вы хотите получить данные, используя ссылку, то просто добавьте символ — auto &amp;[v1, v2] = itr.second.</p><h2>Лямбда-выражение</h2><p>В 11 версии C++ появились лямбда-выражения. Это что-то вроде анонимных функций в JavaScript. Они являются безымянными функциональными объектами и захватывают переменные в различных областях на основе некоторого краткого синтаксиса. Они также могут быть присвоены переменным.</p><p>Лямбды будут полезны, если вам нужно сделать в коде быстрое и небольшое изменение, и вы не хотите писать для этого отдельную функцию. Другое довольно распространённое использование функции — сравнение.</p><p>Приведённый выше пример может многое сказать.</p><p>Во-первых, обратите внимание, как фигурные скобки упрощают вам жизнь. Затем следуют универсальные begin(), end(), которые тоже были добавлены в 11 версии. После идёт лямбда-выражение в качестве компаратора ваших данных. Параметры лямбда-выражения объявлены с помощью auto, что было добавлено в 14 версии С++. До этого auto нельзя было использовать в качестве параметров функции.</p><p>Обратите внимание, мы начинаем лямбда-выражение с квадратных скобок [ ]. Они определяют область действия лямбды — сколько у неё полномочий над локальными переменными и объектами.</p><p>Как определено в этом потрясающем <a href="https://github.com/AnthonyCalandra/modern-cpp-features#lambda-expressions">репозитории</a> по современному C++:</p><ul><li>[ ] — ничего не захватывает. Таким образом, вы не можете использовать любую локальную переменную внешней области видимости в лямбда-выражении. Вы можете использовать только параметры.</li><li>[=] — захватывает локальные объекты (локальные переменные, параметры) в области видимости по значению. Вы можете использовать, но не изменять их.</li><li>[&amp;] — захватывает локальные объекты (локальные переменные, параметры) в области видимости по ссылке. Вы можете изменить их, как в примере, приведённом ниже.</li><li>[this] — захватывает этот указатель по значению.</li><li>[a, &amp;b] — захватывает объект a по значению, объект b по ссылке.</li></ul><p>Так что, если внутри лямбда-функции вы хотите преобразовать данные в какой-то другой формат, вы можете применить её, воспользовавшись преимуществами области видимости. Например:</p><p>В приведённом выше примере, если вы захватили локальные переменные по значению ([factor]) в лямбда-выражении, то вы не можете изменить factor в 5 строке. Вы просто не имеете права делать это. Не злоупотребляйте своими правами!</p><p>Наконец, обратите внимание, что мы берём переменную val в качестве ссылки. Это гарантирует, что любое изменение внутри лямбда-функции фактически изменяет vector.</p><h2>Инициализатор в if и switch</h2><p>Вам точно понравится эта возможность в С++ 17.</p><p>Очевидно, теперь вы можете выполнять инициализацию переменных и проверять условие сразу внутри блоков if или switch. Это поможет сделать код лаконичным и чистым. Общая форма:</p><h2>Компиляция и constexpr</h2><p>Скажем, у вас есть какое-то выражение для оценки, и его значение не изменится после инициализации. Вы можете предварительно рассчитать значение, а затем использовать его в качестве макроса. Или, как предложил C++ 11, можно использовать constexpr.</p><p>Программисты стремятся максимально сократить время выполнения программ. Поэтому если некоторые операции можно отдать на выполнение компилятору, это стоит сделать.</p><p>Приведённый выше код — распространённый пример использования constexpr.</p><p>Поскольку мы объявили функцию вычисления Фибоначчи как constexpr, компилятор может предварительно вычислить fib(20) во время компиляции. Так что после неё он может заменить строку с</p><p>на</p><p>Обратите внимание, что переданный аргумент является константным значением. Важный момент: в функциях, объявленных constexpr, передаваемые аргументы также должны быть constexpr или const. В противном случае они будут вести себя как обычные функции, и во время компиляции предварительный расчёт выполняться не будет.</p><p>Переменные также могут быть constexpr. В этом случае, как вы можете догадаться, эти переменные должны вычисляться во время компиляции. Иначе вы получите ошибку компиляции.</p><p>Интересно, что позже в C++ 17 были представлены constexpr-if и constexpr-lambda.</p><h2>Кортежи</h2><p>Как и пара, кортеж представляет собой набор значений фиксированного размера для различных типов данных.</p><p>Иногда удобнее использовать std::array вместо кортежа. Такой массив подобен обычному массиву в C вместе с несколькими функциями стандартной библиотеки C++. Эта структура данных была добавлена в 11 версии C++.</p><h2>Вывод типов шаблонных параметров для классов</h2><p>Очень подробное название для функции. Идея состоит в том, что с 17 версии типы шаблонных параметров будут выводиться и для стандартных шаблонных классов. Ранее это поддерживалось только для функций.</p><p>Прим. перев. В этом примере для первого элемента кортежа будет выведен тип const char *, а не std::string.</p><p>Выводимый тип задаётся неявно. Это становится ещё удобнее для кортежей.</p><p>Эта функция не имеет никакого смысла, если вы слабо знакомы с шаблонами в C++.</p><h2>Умные указатели</h2><p>Указатели могут быть адскими.</p><p>Из-за свободы, которую предоставляют такие языки, как C++, иногда становится очень легко выстрелить себе в ногу. И во многих случаях именно указатели ответственны за вред, нанесённый компьютеру.</p><p>К счастью, в C++11 появились <a href="https://tproger.ru/problems/write-a-class-for-smart-pointer/">умные указатели</a>, которые намного удобнее, чем простые. Они помогают программистам предотвращать утечки памяти, освобождая её, когда это возможно. Они также обеспечивают исключительную безопасность.</p><p>Если же вам удобнее визуальное представление гайда, обратите внимание на нашу <a href="https://tproger.ru/articles/razrabotka-na-c-s-nulja-v-2022-godu-dorozhnaja-karta/">дорожную карту по C++ для начинающих</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Анимированное руководство по CSS flex</title>
      <link>https://tproger.ru/translations/flex-properties-on-css</link>
      <comments>https://tproger.ru/translations/flex-properties-on-css?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/flex-properties-on-css</guid>
      <description><![CDATA[<p>Свойство flex задаёт flex-grow, flex-shrink и flex-basis и определяет, как элемент растягивается или сжимается; разбор начинается с flex-wrap.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/flex-properties-on-css">Анимированное руководство по CSS flex</a>»</p>]]></description>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 15 Jun 2019 07:11:19 GMT</pubDate>
      <content:encoded><![CDATA[<p>Свойство <a href="https://developer.mozilla.org/ru/docs/Web/CSS/flex">CSS flex</a> — это сокращенное свойство, определяющее способность гибкого элемента растягиваться или сжиматься для заполнения собой доступного свободного пространства. Это свойство устанавливает flex-grow, flex-shrink и flex-basis.</p><p>Изначально свойство flex не подходит для обёртки элементов — его работа во многом схожа с работой overflow:hidden.</p><p>Первое, с чего стоит начать работу с flex, — это flex-wrap.</p><h2>Flex Wrap</h2><p>Добавьте свойство flex-wrap: wrap и посмотрите на поведение flex-элементов.</p><p>По умолчанию оно должно растягивать контейнер по высоте и спускать элементы вниз.</p><p>Примечание Высота не указана (auto/unset), но контейнер может растягиваться.</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/cf1c35a4-9d6f-4204-beb4-aac1549701a5.gif" alt="" /></figure><p>Это частый случай, когда вам нужно поместить произвольное количество элементов неопределённого размера в видимую область контейнера/экрана.</p><p>Направление элементов можно отзеркалить свойством flex-direction: row-reverse:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/cb8d76ef-c461-465a-b996-0e76479a2c66.gif" alt="" /></figure><p>Возможно, такое свойство подойдёт для чтения справа налево. Ещё можно использовать float:right для всех элементов, которые находятся на одной линии с flex-end. Это свойство отличается от row-reverse, т. к. в нём сохраняется корректный порядок элементов.</p><h2>Justify Content</h2><p>Свойство justify-content определяет горизонтальное положение элементов. Это свойство похоже на Flex Wrap, но в justify сохраняется первоначальный порядок элементов.</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/a84aef91-2daf-4c69-ae7c-ad0a549e8f7b.gif" alt="" /></figure><p>В следующем примере  (justify-content: center) все элементы вне зависимости от их ширины имеют горизонтальное позиционирование по центру родительского контейнера. Это похоже на свойства position: relative; margin: auto.</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/d481c58b-bac2-4e4d-9cfe-c1c17ca05288.gif" alt="" /></figure><p>Значение space-between добавляет пробелы между всеми inner-элементами, тем самым растягивая строку на всю ширину контейнера:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/e08b38c1-428d-44fd-8d2f-11bce65cbeed.gif" alt="" /></figure><p>На первый взгляд следующий пример может показаться идентичным примеру выше. Разница между ними хорошо видна, если элементов будет не целый алфавит, а всего несколько. В следующем примере появляются внешние отступы у крайних нижних элементов.</p><p>Пример выше со значением space-between не имеет отступов на угловых элементах. Пример ниже со значением space-around делает равные внешние отступы на всех элементах.</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/7fb74b01-ee33-4a10-ac39-a820cb666f04.gif" alt="" /></figure><p>Идентичный прошлому пример с растянутым элементом middle:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/568e0aeb-92e0-4e61-8b45-e70a9e285498.gif" alt="" /></figure><p>Как видите, вам всё равно придётся экспериментировать с flex-элементами, чтобы достичь правильного результата именно для вашего случая. Результат, естественно, зависит и от размеров содержимого.</p><h2>Align Content</h2><p>Все примеры выше имели свойство justify-content. Но элементы также можно позиционировать по вертикали. Свойство justify-content (выше) и свойство align-content (ниже) похожи. Разница только в их направлении — вертикальном или горизонтальном.</p><p>Вот пример flex-элементов при вертикальном позиционировании:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/67a8214f-54b6-41b4-97ea-635506003bc6.gif" alt="" /></figure><p>У space-evenly есть несколько особенностей значения:</p><ul><li>Свойство выделяет достаточно высоты для корректного отображения элементов.</li><li>Вертикальные отступы у строк одинаковы.</li></ul><p>Конечно, высоту родительского элемента всё ещё можно менять вручную, элементы в этом случае будут адаптироваться.</p><h2>На практике</h2><p>Вряд ли на вашем сайте будет адаптивная строка алфавита, как в примерах этой статьи. Когда дело дойдёт до фактического создания и корректировки шаблона, вы, наверное, будете экспериментировать на нескольких элементах с большим размером. Вот несколько идей по этому поводу.</p><h3>Комбинация vertical align и justify content</h3><p>В этом случае контент будет отцентрирован во всех направлениях.</p><h3>Space evenly</h3><p>Используя значение space-evenly сразу для align-content и justify-content, можно получить следующий результат:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/5214320c-cd78-4b17-b6c1-81fdd334e66b.gif" alt="" /></figure><p><a href="https://media.tproger.ru/uploads/2019/06/css-flex-9.gif"></a>В этом случае нужно стараться делать элементы одной ширины. Пример выше немного странный из-за нечётного количества элементов. Для более красивой масштабируемости количество элементов должно быть чётным.</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/374c0d07-18ab-4f37-ab11-36891fb5f15c.gif" alt="" /></figure><p><a href="https://media.tproger.ru/uploads/2019/06/css-flex-10.gif"></a>К тому же, если количество элементов будет чётным, то масштабирование будет более чистым и чувствительным без сеток на CSS и танцев с бубном на JS.</p><p>Центрирование элементов по вертикали внутри других элементов создаёт проблемы последние десять лет. Flex может их решить. Если использовать свойство space-evenly в двух направлениях, отступы будут появляться автоматически даже при разной высоте элемента:</p><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/32443b99-00f8-4fed-b190-37ff5e977ac2.gif" alt="" /></figure><p><a href="https://media.tproger.ru/uploads/2019/06/css-flex-11.gif"></a>Если вы посмотрите на flex в целом, то заметите, что это, наверное, самое полезное сочетание flex-свойств.</p><h2>Размер элементов</h2><p>Убедитесь, что вы явно указываете размер элементов. Если этого не сделать, некоторые свойства увеличения во flex попросту не будут работать. Используйте min-width, max-width, и width/height соответственно. Эти свойства могут кардинально повлиять на масштабируемость вашего контента.</p><h2>Примеры комбинаций свойств в одной анимации</h2><ul><li>flex-direction: row; justify-content: [value];</li></ul><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/8c10ca84-588b-405e-8f44-6b7da03e4d95.gif" alt="" /></figure><ul><li>flex-direction: column; justify-content: [value];</li></ul><figure><img src="https://media.tproger.ru/user-uploads/23823/2023-10-15/fcf14af0-1a8b-4bed-85d7-f291b2715676.gif" alt="" /></figure><p>Хотя рекомендуется использовать flex внутри CSS-сеток, это совсем не обязательно. Flex хорош и сам по себе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Зачем нужен Python Global Interpreter Lock и как он работает</title>
      <link>https://tproger.ru/translations/global-interpreter-lock-guide</link>
      <comments>https://tproger.ru/translations/global-interpreter-lock-guide?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/global-interpreter-lock-guide</guid>
      <description><![CDATA[<p>GIL позволяет управлять интерпретатором Python только одному потоку одновременно, что заметно сказывается на производительности многопоточных программ.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/global-interpreter-lock-guide">Зачем нужен Python Global Interpreter Lock и как он работает</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 08 Jun 2019 06:20:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Python Global Interpreter Lock (GIL) — это своеобразная блокировка, позволяющая только одному потоку управлять интерпретатором Python. Это означает, что в любой момент времени будет выполняться только один конкретный поток.</p><p>Работа GIL может казаться несущественной для разработчиков, создающих однопоточные программы. Но во многопоточных программах отсутствие GIL может негативно сказываться на производительности процессоро-зависымых программ.</p><p>Поскольку GIL позволяет работать только одному потоку даже в многопоточном приложении, он заработал репутацию «печально известной» функции.</p><p>В этой статье будет рассказано о том, как GIL влияет на производительность приложений, и о том, как это самое влияние можно смягчить.</p><h2>Что за проблему в Python решает GIL?</h2><p>Python подсчитывает количество ссылок для корректного управления памятью. Это означает, что созданные в Python объекты имеют переменную подсчёта ссылок, в которой хранится количество всех ссылок на этот объект. Как только эта переменная становится равной нулю, память, выделенная под этот объект, освобождается.</p><p>Вот небольшой пример кода, демонстрирующий работу переменных подсчёта ссылок:</p><p>В этом примере количество ссылок на пустой массив равно 3. На этот массив ссылаются: переменная a, переменная b и аргумент, переданный функции sys.getrefcount().</p><p>Проблема, которую решает GIL, связана с тем, что в многопоточном приложении сразу несколько потоков могут увеличивать или уменьшать значения этого счётчика ссылок. Это может привести к тому, что память очистится неправильно и удалится тот объект, на который ещё существует ссылка.</p><p>Счётчик ссылок можно защитить, добавив блокираторы на все структуры данных, которые распространяются по нескольким потокам. В таком случае счётчик будет изменяться исключительно последовательно.</p><p>Но добавление блокировки к нескольким объектам может привести к появлению другой проблемы — взаимоблокировки (англ. deadlocks), которая получается только если блокировка есть более чем на одном объекте. К тому же эта проблема тоже снижала бы производительность из-за многократной установки блокираторов.</p><p>GIL — эта одиночный блокиратор самого интерпретатора Python. Он добавляет правило: любое выполнение байткода в Python требует блокировки интерпретатора. В таком случае можно исключить взаимоблокировку, т. к. GIL будет единственной блокировкой в приложении. К тому же его влияние на производительность процессора совсем не критично. Однако стоит помнить, что GIL уверенно делает любую программу однопоточной.</p><p>Несмотря на то, что GIL используется и в других интерпретаторах, например в Ruby, он не является единственным решением этой проблемы. Некоторые языки решают проблему потокобезопасного освобождения памяти с помощью сборки мусора.</p><p>С другой стороны это означает, что такие языки часто должны компенсировать потерю однопоточных преимуществ GIL добавлением каких-то дополнительных функций повышения производительности, например JIT-компиляторов.</p><h2>Почему для решения проблемы был выбран именно GIL?</h2><p>Итак, почему же это не очень «хорошее» решение используется в Python? Насколько для разработчиков это решение критично?</p><p><a href="https://www.youtube.com/watch?v=KVKufdTphKs&amp;feature=youtu.be&amp;t=12m11s">По словам Larry Hastings</a>, архитектурное решение GIL — это одна из тех вещей, которые сделали Python популярным.</p><p>Python существует с тех времён, когда в операционных системах не существовало понятия о потоках. Этот язык разрабатывался в расчёте на лёгкое использование и ускорение процесса разработки. Всё больше и больше разработчиков переходило на Python.</p><p>Много расширений, в которых нуждался Python, было написано для уже существующих библиотек на C. Для предотвращения несогласованных изменений, язык C требовал потокобезопасного управления памятью, которое смог предоставить GIL.</p><p>GIL можно было легко реализовать и интегрировать в Python. Он увеличивал производительность однопоточных приложений, поскольку управление велось только одним блокиратором.</p><p>Те библиотеки на C, которые не были потокобезопасными, стало легче интегрировать. Эти расширения на C стали одной из причин, почему Python-сообщество стало расширяться.</p><p>Как можно понять, GIL — фактическое решение проблемы, с которой столкнулись разработчики CPython в начале жизни Python.</p><h2>Влияние GIL на многопоточные приложения</h2><p>Если смотреть на типичную программу (не обязательно написанную на Python) — есть разница, ограничена ли эта программа производительностью процессора или же I/O.</p><p>Операции, ограниченные производительностью процессора (англ. CPU-bound) — это все вычислительные операции: перемножение матриц, поиск, обработка изображений и т. д.</p><p>Операции, ограниченные производительностью I/O (англ. I/O-bound) — это те операции, которые часто находятся в ожидании чего-либо от источников ввода/вывода (пользователь, файл, БД, сеть). Такие программы и операции иногда могут ждать долгое время, пока не получат от источника то, что им нужно. Это связано с тем, что источник может проводить собственные (внутренние) операции, прежде чем он будет готов выдать результат. Например, пользователь может думать над тем, что именно ввести в поисковую строку или же какой запрос отправить в БД.</p><p>Ниже приведена простая CPU-bound программа, которая попросту ведёт обратный отсчёт:</p><p>Запустив это на 4х-ядерном компьютере получим такой результат:</p><p>Ниже приведена та же программа, с небольшим изменением. Теперь обратный отсчёт ведётся в двух параллельных потоках:</p><p>И вот результат:</p><p>Как видно из результатов, оба варианта затратили примерно одинаковое время. В многопоточной версии GIL предотвратил параллельное выполнение потоков.</p><p>GIL не сильно влияет на производительность I/O-операций в многопоточных программах, т. к. в процессе ожидания от I/O блокировка распространяется по потокам.</p><p>Однако программа, потоки которой будут работать исключительно с процессором (например обработка изображения по частям), из-за блокировки не только станет однопоточной, но и на её выполнение будет затрачиваться больше времени, чем если бы она изначально была строго однопоточной.</p><p>Такое увеличение времени — это результат появления и реализации блокировки.</p><h2>Почему GIL всё ещё используют?</h2><p>Разработчики языка получили уйму жалоб касательно GIL. Но такой популярный язык как Python не может провести такое радикальное изменение, как удаление GIL, ведь это, естественно, повлечёт за собой кучу проблем несовместимости.</p><p>В прошлом разработчиками были предприняты попытки удаления GIL. Но все эти попытки разрушались существующими расширениями на C, которые плотно зависели от существующих GIL-решений. Естественно, есть и другие варианты, схожие с GIL. Однако они либо снижают производительность однопоточных и многопоточных I/O-приложений, либо попросту сложны в реализации. Вам бы не хотелось, чтобы в новых версиях ваша программа работала медленней, чем сейчас, ведь так?</p><p>Создатель Python, Guido van Rossum, в сентябре 2007 года высказался по поводу этого в статье «<a href="https://www.artima.com/weblogs/viewpost.jsp?thread=214235">It isn’t Easy to remove the GIL</a>»:</p><blockquote>«Я был бы рад патчам в Py3k только в том случае, если бы производительность однопоточных приложений или многопоточных I/O-приложений не уменьшалась.»</blockquote><p>С тех пор ни одна из предпринятых попыток не удовлетворяла это условие.</p><h2>Почему GIL не был удалён в Python 3?</h2><p>Python 3 на самом деле имел возможность переделки некоторых функций с нуля, хотя из-за этого многие расширения на С попросту сломались бы и их пришлось бы переделывать. Именно из-за этого первые версии Python 3 так слабо расходились по сообществу.</p><p>Но почему бы параллельно с обновлением Python 3 не удалить GIL?</p><p>Его удаление сделает однопоточность в Python 3 медленней по сравнению с Python 2 и просто представьте, во что это выльется. Нельзя не заметить преимущества однопоточности в GIL. Именно поэтому он всё ещё не удалён.</p><p>Но в Python 3 действительно появились улучшения для существующего GIL. До этого момента в статье рассказывалось о влиянии GIL на многопоточные программы, которые затрагивают только процессор или только I/O. А что насчёт тех программ, у которых часть потоков идут на процессор, а часть на I/O?</p><p>В таких программах I/O-потоки «страдают» из-за того, что у них нет доступа к GIL от процессорных потоков. Это связано со встроенным в Python механизмом, который принуждал потоки освобождать GIL после определённого интервала непрерывного использования. В случае, если никто другой не используют GIL, эти потоки могли продолжать работу.</p><p>Но тут есть одна проблема. Почти всегда GIL занимается процессорными потоками и остальные потоки не успевают занять место. Этот факт был изучен David Beazley, визуализацию этого можно увидеть <a href="http://www.dabeaz.com/blog/2010/01/python-gil-visualized.html">здесь</a>.</p><p>Проблема была решена в Python 3.2 в 2009 разработчиком Antoine Pitrou. Он добавил механизм подсчёта потоков, которые нуждаются в GIL. И если есть другие потоки, нуждающиеся в GIL, текущий поток не занимал бы их место.</p><h2>Как справиться GIL?</h2><p>Если GIL у вас вызывает проблемы, вот несколько решений, которые вы можете попробовать:</p><p>Многопроцессность против многопоточности. Довольно популярное решение, поскольку у каждого Python-процесса есть собственный интерпретатор с выделенной под него памятью, поэтому с GIL проблем не будет. В Python уже есть модуль multiprocessing, который упрощает создание процессов к такому виду:</p><p>После запуска получаем такой результат:</p><p>Можно заметить приличное повышение производительности по сравнению с многопоточной версией. Однако показатель времени не снизился до половины. Всё из-за того, что управление процессами само по себе сказывается на производительности. Несколько процессов более сложны, чем несколько потоков, поэтому с ними нужно работать аккуратно.</p><p>Альтернативные интерпретаторы Python. У Python есть много разных реализаций интерпретаторов. CPython, Jyton, IronPython и PyPy, написанные на C, Java, C# и Python соответственно. GIL существует только на оригинальном интерпретаторе — на CPython.</p><p>Вы просто можете использовать преимущества однопоточности, в то время, пока одни из самых ярких умов прямо сейчас работают над устранением GIL из CPython. <a href="https://github.com/larryhastings/gilectomy">Вот одна из попыток</a>.</p><p>Зачастую, GIL рассматривается как нечто-то сложное и непонятное. Но имейте ввиду, что как python-разработчик, вы столкнётесь с GIL только если будете писать расширения на C или многопоточные процессорные программы.</p><p>На этом этапе вы должны понимать все аспекты, необходимые при работе с GIL. Если же вам интересна низкоуровневая структура GIL — посмотрите  <a href="https://youtu.be/Obt-vMVdM8s">Understanding the Python GIL</a> от David Beazley.</p>]]></content:encoded>
    </item>
    <item>
      <title>10 принципов ООП, о которых стоит знать каждому программисту</title>
      <link>https://tproger.ru/translations/10-oop-principles</link>
      <comments>https://tproger.ru/translations/10-oop-principles?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/10-oop-principles</guid>
      <description><![CDATA[<p>Пять принципов SOLID и другие правила объектно-ориентированной разработки: в чём их суть и какие преимущества они дают при проектировании классов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/10-oop-principles">10 принципов ООП, о которых стоит знать каждому программисту</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 21 May 2019 07:17:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многим опытным разработчикам, вероятно, знакома методология <a href="https://ru.wikipedia.org/wiki/Объектно-ориентированное_программирование">объектно-ориентированного программирования</a> (ООП). Кроме известных её принципов (абстракция, инкапсуляция, полиморфизм, наследование и т. д.) существуют и другие — менее известные, но не менее важные и полезные для реализации. Некоторые из них собраны в специальный блок и известны по акрониму <a href="https://ru.wikipedia.org/wiki/SOLID_(объектно-ориентированное_программирование)">SOLID</a>. Эта статья расскажет об этих и других существующих принципах объектно-ориентированной разработки и о том, какие преимущества они предлагают.</p><h2>Принцип единственной ответственности (SRP)</h2><p>Соответствует букве S акронима SOLID. Согласно <a href="https://ru.wikipedia.org/wiki/Принцип_единственной_ответственности">этому принципу</a>, не должно быть более одной причины для изменения класса, или класс должен всегда обрабатывать одну функциональность.</p><p>Основное преимущество состоит в том, что такой подход уменьшает связь между отдельным компонентом программного обеспечения и кодом. Если вы добавляете более одной функциональности в один класс, это вводит связь между двумя функциями, и даже если вы меняете только одну из них, есть шанс сломать другую, связанную с ней. Что в свою очередь требует больше раундов тестирования для избежания каких-либо неожиданностей в продакшене.</p><p>Курс по теме From 0 to 1: Design Patterns — 24 That Matter.</p><h2>Принцип открытости/закрытости (OCP)</h2><p>Соответствует букве O акронима SOLID. <a href="https://ru.wikipedia.org/wiki/Принцип_открытости/закрытости">Принцип</a> можно выразить так: «Классы, методы или функции должны быть открыты для расширения (добавления новой функциональности) и закрыты для модификации». Такой подход запрещает кому-либо изменять уже опробованный и протестированный код, а значит, он не ломается. В этом и состоит основное преимущество такого подхода.</p><p>Ниже приведён пример кода на Java, который нарушает этот принцип:</p><p>А вот пример после рефакторинга. Теперь соблюдается принцип открытости/закрытости: при добавлении новой реализации Shape не нужно менять код GraphicEditor.</p><p>Курс по теме SOLID Principles of Object-Oriented Design and Architecture.</p><h2>Принцип подстановки Барбары Лисков (LSP)</h2><p>Соответствует букве L акронима SOLID. Согласно <a href="https://ru.wikipedia.org/wiki/Принцип_подстановки_Барбары_Лисков">этому принципу</a> подтипы должны быть заменяемыми для супертипа. Другими словами, методы или функции, работающие с суперклассом, должны иметь возможность без проблем работать также и с его подклассами.</p><p>LSP тесно связан с принципом единственной ответственности и принципом разделения интерфейса.</p><p>Если класс реализует больше функциональности, чем подкласс, то последний может не поддерживать некоторые функции и тем самым нарушает данный принцип.</p><p>Ниже приведён пример такого кода на Java:</p><p>Функция resize() провоцирует неявную ошибку при работе с экземпляром класса Square, потому что позволяет устанавливать отличные друг от друга значения ширины и высоты. Согласно принципу LSP, функции, использующие ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом. Поэтому для корректной работы функция resize() должна проверять, является ли передаваемый объект экземпляром класса Square, и в этом случае не позволять установить разные значения ширины и высоты. Отсюда идёт нарушение принципа.</p><p>Курс по теме SOLID Principles of Object-Oriented Design.</p><h2>Принцип разделения интерфейса (ISP)</h2><p>Соответствует букве I акронима SOLID. <a href="https://ru.wikipedia.org/wiki/Принцип_разделения_интерфейса">Этот принцип</a> подразумевает, что интерфейс, который не используется, не должен быть реализован.</p><p>В основном это происходит, когда один интерфейс содержит несколько функциональностей, и клиенту нужна только одна из них, а другие — нет.</p><p>Написание интерфейса — сложная задача. Когда он готов, вы не сможете изменить его, не нарушив всю реализацию.</p><p>Ещё одно преимущество этого принципа в Java заключается в том, что интерфейс имеет недостаток. Необходимо сначала реализовать все методы, прежде чем какой-либо класс сможет их использовать. Поэтому наличие единственной функциональности означает меньшее количество методов для реализации.</p><figure><img src="https://media.tproger.ru/uploads/2019/05/0_kALM7NtYyY7D40b4.jpg" alt="" /></figure><h2>Принцип инверсии зависимостей (DIP)</h2><p>Соответствует букве D акронима SOLID. Прелесть <a href="https://makedev.org/principles/solid/dip.html">этого принципа</a> проектирования в том, что любой класс легко тестируется с помощью фиктивного объекта и проще в обслуживании, потому что код создания объекта централизован, а клиентский код не перегружен им.</p><p>Ниже приведён пример кода Java, который нарушает принцип инверсии зависимости:</p><p>Пример демонстрирует, что AppManager зависит от EventLogWriter. Если вам нужно использовать другой способ уведомления клиента (например push-уведомления, SMS или электронную почту), необходимо изменить класс AppManager.</p><p>Эту проблему можно решить с помощью принципа инверсии зависимостей. Вместо того, чтобы AppManager запрашивал EventLogWriter, последний следует внедрить в AppManager явно. Плюсом реализации общего интерфейса позволить внедрять любую реализацию для других способов уведомления.</p><p>Курс по теме Using SOLID Principles to Write Better Code — A Crash Course.</p><p>Теперь перейдём к принципам, которые не входят в пятёрку SOLID, но не менее важны.</p><h2>DRY (Don’t Repeat Yourself)</h2><p>Переводится как «не повторяйся» и буквально означает, что нужно уходить от дублирующего кода и по возможности использовать <a href="https://ru.wikipedia.org/wiki/Абстракция_данных">абстракцию</a> для общих вещей.</p><p>Если есть одинаковый блок кода в более чем двух местах, вынесите его в отдельный метод. Если вы используете жёстко запрограммированное значение более одного раза, сделайте его общедоступной константой. Преимущество этого принципа заключается в упрощении поддержки вашего кода.</p><p>Но важно не злоупотреблять этим принципом. Например, один и тот же код не подойдёт для проверки <a href="https://ru.wikipedia.org/wiki/Идентификационные_карты">OrderId</a> и <a href="https://ru.wikipedia.org/wiki/Номер_социального_страхования">SSN</a>. Их форматы могут не совпадать, и на выходе функция выдаст некорректный результат. В качестве решения можно предусмотреть в методе проверку форматов для подобных наборов чисел.</p><p>Курс по теме Basics of Software Architecture &amp; Design Patterns для Java на Udemy.</p><h2>Инкапсуляция изменяющегося кода</h2><p>Сервисы стремительно развиваются. Продакшн подразумевает постоянные изменения кода и его поддержку. Отсюда следует второй принцип ООП — <a href="https://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)">инкапсуляция</a> кода, который с большой вероятностью будет изменён в будущем.</p><p>Преимущество этого принципа ООП заключается в том, что инкапсулированный код легко тестировать и поддерживать.</p><p>Воспользуйтесь алгоритмом, по которому переменные и методы по умолчанию имеют спецификатор private. Затем шаг за шагом увеличиваете доступ при необходимости (с private на protected, с protected на public).</p><p>Одним из вариантов инкапсуляции является <a href="https://ru.wikipedia.org/wiki/Фабричный_метод_(шаблон_проектирования)">Фабричный метод</a>. Он инкапсулирует код создания объекта и обеспечивает гибкость для последующего создания новых объектов без влияния на существующий код.</p><p>Курс по теме Design Pattern Library.</p><h2>Композиция вместо наследования</h2><p>Существует два основных способа повторного использования кода: <a href="https://ru.wikipedia.org/wiki/Наследование_(программирование)">наследование</a> и <a href="https://javadevblog.com/chto-takoe-kompozitsiya-primer-kompozitsii-v-java.html">композиция</a>. Оба они имеют свои преимущества и недостатки, но, как правило, предпочтение рекомендуется отдавать последнему, если это возможно. Обусловлено это тем, что <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">композиция гибче наследования</a>.</p><p>Композиция позволяет изменять поведение класса прямо во время выполнения через установку его свойств. Реализуя интерфейсы, вы, таким образом, используете полиморфизм, который обеспечивает более гибкую реализацию.</p><p><a href="https://www.amazon.co.uk/dp/0134685997?linkCode=gs2&amp;tag=javarevisit01-21">«Effective Java»</a> Джошуа Блоха также советует отдавать предпочтение композиции вместо наследования. Если вы всё ещё не уверены, вы также можете посмотреть <a href="http://javarevisited.blogspot.sg/2015/06/difference-between-inheritance-and-Composition-in-Java-OOP.html">здесь</a>, чтобы узнать, почему композиция лучше, чем наследование для повторного использования кода и его функциональности.</p><p>Курс по теме <a href="https://www.coursera.org/learn/object-oriented-java?ranMID=40328&amp;ranEAID=JVFxdTr9V80&amp;ranSiteID=JVFxdTr9V80-tXSoUQ9RKFa_h18oEQIS6w&amp;siteID=JVFxdTr9V80-tXSoUQ9RKFa_h18oEQIS6w&amp;utm_content=10&amp;utm_medium=partners&amp;utm_source=linkshare&amp;utm_campaign=JVFxdTr9V80">Object-Oriented Programming in Java</a>.</p><h2>Программирование для интерфейса</h2><p>Этот принцип подразумевает, что следует по возможности программировать для <a href="https://ru.wikipedia.org/wiki/Интерфейс_(объектно-ориентированное_программирование)">интерфейса</a>, а не для его реализации. Это даст вам гибкий код, который может работать с любой новой реализацией интерфейса.</p><p>Другими словами, нужно использовать тип интерфейса для переменных, возвращаемых типов или типа аргумента метода. Например, использовать для хранения объекта суперкласс, а не подкласс.</p><p>Имеется в виду</p><p>вместо</p><p>Это также рекомендовано во многих книгах по Java, в том числе в <a href="https://www.amazon.co.uk/dp/0134685997?linkCode=gs2&amp;tag=javarevisit01-21">Effective Java</a> и <a href="https://www.amazon.co.uk/dp/0596007124?linkCode=gs2&amp;tag=javarevisit01-21">Head First design pattern</a>.</p><p>Ниже приведён пример для интерфейса в Java:</p><p>Курс по теме  Refactoring to Design Patterns».</p><h2>Принцип делегирования</h2><p>Не делайте всё самостоятельно, делегируйте это в соответствующий класс. Классическим примером <a href="https://ru.wikipedia.org/wiki/Делегирование_(программирование)">этого принципа</a> являются методы equals() и hashCode() в Java. Если нужно сравнить два объекта, это действие поручается соответствующему классу вместо клиентского.</p><p>Основным преимуществом этого принципа является отсутствие дублирования кода и довольно простое изменение поведения. Этот принцип относится также к делегированию событий (событие делегируется соответствующему обработчику).</p><p>Курс по теме  <a href="https://medium.com/swlh/5-free-object-oriented-programming-online-courses-for-programmers-156afd0a3a73">5 Free Object-Oriented Programming Online Courses for Programmers</a>.</p><h2>Заключение</h2><p>Эти принципы разработки помогают писать гибкий код, стремящийся к <a href="https://ru.wikipedia.org/wiki/Связность_(программирование)">высокой связности</a> и низкому зацеплению. Как только вы это освоите, следующим шагом будет изучение шаблонов проектирования для решения общих проблем разработки приложений и программного обеспечения.</p><p>Курс по теме <a href="https://tproger.ru/video/design-patterns/">Шаблоны проектирования</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Google открыла бесплатный онлайн-курс разработки Android-приложений на Kotlin</title>
      <link>https://tproger.ru/news/android-kotlin-course</link>
      <comments>https://tproger.ru/news/android-kotlin-course?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Екатерина Никитина]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/android-kotlin-course</guid>
      <description><![CDATA[<p>Курс на английском рассчитан на тех, у кого есть опыт программирования и знакомство с основами Kotlin, и учит работе с компонентами Jetpack.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/android-kotlin-course">Google открыла бесплатный онлайн-курс разработки Android-приложений на Kotlin</a>»</p>]]></description>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Google]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 16 May 2019 12:40:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>Google представила <a href="https://tprg.ru/6Gf8">бесплатный онлайн-курс</a>, который поможет разработчикам Android-приложений перейти на Kotlin. Он полностью на английском и нацелен на подготовленных слушателей.</p><p>Google любит Kotlin: готова не только <a href="https://tproger.ru/news/first-kotlin-android/">назвать его первым языком для Android-разработки</a>, но и что-то сделать по этому поводу.</p><h2>Кому подойдёт курс?</h2><p>Тем, у кого уже есть опыт программирования и кто знаком с основами Kotlin. Для тех, кто с Kotlin не знаком, есть <a href="https://tprg.ru/yVn3">курс молодого бойца</a>, тоже от Google.</p><h2>Чему научит?</h2><p>Как разрабатывать Android-приложения с применением стандартных для отрасли инструментов. Лекторы расскажут, зачем и как использовать компоненты Jetpack, покажут, как можно решать общие задачи проще и быстрее с помощью библиотек.</p><p>Обучение рассчитано на два месяца. Но жёсткого расписания нет: уроки можно проходить в удобное время и в удобном темпе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Знакомство с недорогим и функциональным микроконтроллером ESP8266: прошивка и пример использования</title>
      <link>https://tproger.ru/articles/about-esp8266</link>
      <comments>https://tproger.ru/articles/about-esp8266?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/about-esp8266</guid>
      <description><![CDATA[<p>Микроконтроллер ESP8266 — недорогая и функциональная микросхема, от производителя Espressif, которая поддерживает WiFi-интерфейса.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/about-esp8266">Знакомство с недорогим и функциональным микроконтроллером ESP8266: прошивка и пример использования</a>»</p>]]></description>
      <category><![CDATA[Hardware]]></category>
      <category><![CDATA[Arduino]]></category>
      <category><![CDATA[Интернет вещей]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 08 May 2019 09:29:30 GMT</pubDate>
      <content:encoded><![CDATA[<p>ESP8266 — китайский микроконтроллер (далее МК) от производителя Espressif с поддержкой WiFi-интерфейса. Управлять всем этим можно не только с браузера, но и из приложений на Android/iOS/Desktop. Если МК будет применяться там, куда не достаёт WiFi-сеть, то ESP8266 может работать в режиме точки доступа.</p><p>Примечание В этой статье не будут рассмотрены практические примеры применения ESP8266. Речь пойдёт об основных преимуществах и возможностях этого МК.</p><p>Содержание:</p><ol><li>Разновидности МК серии ESP и их платХарактеристикиКак работать с микроконтроллером ESP8266?ПрошивкаВ чём прошивается?ОсновыРаспределение памяти</li></ol><p>Микроконтроллер ESP8266 работает с внешней flash-памятью по интерфейсу SPI. Её объём варьируется от 512 Кбайт до 4 Мбайт. При желании и умении микросхему памяти можно будет перепаять на версию до 32 Мбайт.</p><h2>Разновидности МК серии ESP и их плат</h2><p>Существует около полутора десятка версий МК серии ESP и огромное количество плат с ними. Рассмотрим самые популярные из них.</p><h3>Микроконтроллеры ESP8266</h3><h4>ESP-01</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/f051fe83-9608-4f4d-bcec-6900ee38af2f.png" alt="" /><figcaption>Микроконтроллер ESP-01</figcaption></figure><ul><li>Имеет 8 разведённых контактов (VCC, GND, UTXD, URXD, CH_PD, GPIO0, GPIO2, GPIO6) и PCB-антенну (печатный проводник на самой плате).</li><li>Из разведённых выводов тут присутствуют только 3 GPIO, но не стоит видеть в этом одни минусы. Если нужно будет управлять одним реле или получать данные с датчика температуры, вам не понадобятся все выводы МК, достаточно будет лишь пары. К тому же, существуют платы и шилды с возможностью простой коммутации именно к этой версии МК. Например такая:</li></ul><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/26588f48-4670-4786-b509-02b1515ccf6f.png" alt="" /><figcaption>Плата WiFi-реле</figcaption></figure><h4>ESP-03</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/00620776-1089-4c7e-ab78-fe0c7e5531d9.png" alt="" /></figure><ul><li>Здесь появляется керамическая антенна. Она считается немного эффективней своего печатного собрата.</li><li>Также на плате разведены все доступные выводы GPIO. В <a href="https://habr.com/ru/post/368863/">этой статье</a> описана разница между типами антенн для микроконтроллеров ESP8266.</li></ul><h4>ESP-07</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/8613ad6b-c112-434c-a17d-0542462b7185.png" alt="" /></figure><ul><li>В этой версии в глаза сразу бросается металлический экран (который перед этим появляется на ESP-06).</li><li>На борту керамическая антенна и разъём для внешней антенны.</li></ul><h4>ESP-12</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/49796175-6e1d-4c42-a19b-2c8154bab3a1.png" alt="" /></figure><p>В свою очередь, существует несколько вариантов этой версии: ESP-12S, ESP-12F, ESP-12E. Вторая и третья версии имеют на торце дополнительно 6 разведённых контактов.</p><h3>Платы</h3><h4>WeMos D1 mini</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/cbc999ab-bf25-46dd-945e-28f1bb891718.png" alt="" /></figure><ul><li>Имеет распайку девяти GPIO-контактов.</li><li>На плате имеется небезызвестный мост CH34x (такие часто ставят на клоны Arduino).</li><li>Установлен МК с 4 Мбайт flash-памяти.</li><li>Недостаток для некоторых — придётся самому паять контакты на плату (идут в комплекте).</li><li>Конструкционно совместима с различными выпускаемыми шилдами реле/датчиками.</li></ul><h4>NodeMCU v0.9/v1</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/07e995a4-079b-41cd-b871-6aac2f79a49c.png" alt="" /></figure><p>Первое поколение плат серии NodeMCU. На ней распаяны все 11 GPIO-портов. Некоторые из них обладают дополнительными функциями (UART, I2C, SPI, PWM, ADC). Хотя на плате впаяны контакты, она занимает всю ширину беспаечной макетной платы, что затрудняет работу на ней. МК имеет 4 Мбайт flash-памяти. Также имеется мост CH340.</p><h4>NodeMCU v3</h4><figure><img src="https://media.tproger.ru/user-uploads/34561/2023-12-28/6b9edacb-2ff6-4c5b-b00f-b119daff85e9.png" alt="" /></figure><p>Финальная версия платы этой серии. Существует и v2 «Amica», которая меньше по габаритам. v3 носит название «LoLin» и отличается от предыдущей версии только размерами и незначительными деталями (например дополнительной распайкой шины питания). Кроме традиционного моста CH340/CH341 на платы ставят чип CP2102, так что внимательней с выбором драйвера на них.</p><h2>Характеристики</h2><p>Все эти (и не только эти) платы выполнены на чипсете микроконтроллера ESP8266EX, а следовательно, характеристики у них одинаковые:</p><ul><li>Протоколы: 802.11 b/g/n/e/i.</li><li>Диапазон частот: 2.4 ГГц – 2.5 ГГц.</li><li>Процессорное ядро: Tensilica L106 32 разряда.</li><li>Диапазон напряжений питания: 2.5 В – 3.6 В.</li><li>Среднее потребление тока: 80 мA.</li><li>Режимы WiFi: Station/SoftAP/SoftAP+Station.</li><li>Безопасность: WPA/WPA2.</li><li>Шифрование: WEP/TKIP/AES.</li><li>Обновление прошивки: через UART, по радиоканалу (OTA — Over The Air).</li><li>Сетевые протоколы: IPv4, TCP/UDP/HTTP/FTP.</li><li>Поддержка WiFi Direct (P2P), P2P Discovery, P2P GO (Group Owner) mode, GC (Group Client) mode, P2P Power Management.</li><li>Встроенные аппаратные ускорители: CCMP (CBC-MAC, режим счётчика), TKIP (MIC, RC4), WAPI (SMS4), WEP (RC4), CRC.</li><li>Поддержка <a href="http://wikihandbk.com/wiki/ESP8266:%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B/%D0%9A%D0%B0%D0%BA_%D1%81%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D1%8C_%D0%B2%D0%B5%D0%B1-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_%D0%B1%D0%B0%D0%B7%D0%B5_ESP8266">LUA-скриптов</a>.</li></ul><h2>Как работать с микроконтроллером ESP8266?</h2><p>Есть два способа работы с ним:</p><ul><li>управление через AT-команды и автономная работа со своей прошивкой. В первом случае ESP8266 работает только в паре с другими МК, во втором — может работать независимо (хотя никто не запрещает присоединить её к другому МК).</li><li>«Из коробки» МК поставляется с прошивкой для работы через AT-команды. Для этого ESP8266 подключается к любому другому МК по UART-интерфейсу. Для демонстрации работы AT-команд ESP8266 можно подключить к компьютеру через USB-UART переходник и запустить монитор последовательного порта (например из Arduino IDE). Про то, как работать с этими командами, можно прочитать в <a href="https://radioprog.ru/post/397">этой статье</a>.</li></ul><h2>Прошивка</h2><p>В большинстве случаев намного удобней прошивать МК и работать с ним со своей прошивкой. Однако тут тоже есть свои нюансы. Вот 3 варианта событий:</p><ul><li>У вас «голый» ESP8266, например ESP-01. Вам потребуется USB-UART переходник, который нужно подключить к МК. Этот переходник обязательно должен быть на 3-вольтовой логике, т. к. иначе можно легко отправить ваш МК в кибер-Вальхаллу. Про процесс подключения можно прочитать <a href="https://esp8266.ru/esp8266-podkluchenie-obnovlenie-proshivki/">здесь</a>.</li><li>Второй случай идентичен первому, кроме того, что вместо переходника можно использовать любую плату Arduino. Достаточно специальным образом подключить ESP8266 к UART-контактам Arduino, а её саму «отключить», замкнув контакт аппаратного сброса (RESET) на землю. Естественно, питать ESP8266 нужно будет от шины питания 3.3 В. В этом случае в качестве переходника USB-UART будет выступать мост (чаще всего CH340) на самой плате Arduino. Этот процесс также описан в статье <a href="https://esp8266.ru/esp8266-podkluchenie-obnovlenie-proshivki/">выше</a>.</li><li>Лучший вариант — это плата с USB-UART мостом на борту (как NodeMCU, WeMos и прочие). В этом случае ничего дополнительного делать не нужно — просто подключайте плату через USB.</li></ul><h2>В чём прошивается?</h2><p>Например <a href="https://github.com/nodemcu/nodemcu-flasher">NodeMCU Flasher</a> (которая подходит не только для плат NodeMCU) или <a href="https://github.com/espressif/esptool">ESPTool</a> (необходим <a href="https://www.python.org/downloads/">Python</a>).</p><p>Однако в этой статье работа с МК и процесс прошивки будут рассмотрены в Arduino IDE.</p><p>Изначально среда Arduino IDE не предназначена для работы с МК серии ESP. Чтобы это исправить, идём в Файл → Настройки и в поле Дополнительные ссылки для Менеджера плат вставляем эту ссылку:</p><p>Потом открываем Инструменты → Плата → Менеджер плат и в открывшемся списке в самом низу находим плату «esp8266 by ESP8266 Community» (если с этим возникли трудности — используем поиск вверху окна). Устанавливаем последнюю версию платы (около 150 Мбайт).</p><p>После установки в списке плат появится немалое количество плат. Если не нашли свою плату или не знаете её названия — выбирайте Generic ESP8266 Module. Теперь можно выбрать свой МК в списке COM-портов.</p><p>У ESP8266 две скорости передачи: основная — её вы указываете при инициализации последовательного порта, и скорость, на которой передаётся отладочная информация. Она передаётся сразу после подачи питания на МК. Обычно это скорости 115200 бод и 74800 бод 0 соответственно.</p><h2>Основы</h2><p>Если вы не владеете базовыми знаниями работы с платформой Arduino, наверстать упущенное можно в нашей статье про <a href="https://tproger.ru/curriculum/arduino-quick-start/">основы использования Arduino</a>.</p><p>После скачивания платы ESP8266 с помощью менеджера, в примерах появится большое количество скетчей. Рассмотрим один из них (Файл → Примеры → ESP8266WebServer → HelloServer):</p><p>Что делает этот скетч? МК подключается к вашей WiFi-сети и запускает сервер. В монитор последовательного порта выведется локальный IP-адрес (к примеру, 192.168.0.105). Вбиваем этот IP в адресную строку браузера, после чего в нём должна отобразиться строка «Привет от ESP8266!». Таким же образом можно проверить адрес /inline.</p><p>Ознакомиться с API МК можно в их официальном <a href="https://github.com/esp8266/Arduino">репозитории</a>.</p><h2>Распределение памяти</h2><p>Внешняя память распределена на следующие разделы:</p><ul><li>скетчи (память для прошивки);</li><li>файловая система SPIFFS;</li><li>OTA-Update (прошивка, переданная «по воздуху»);</li><li>EEPROM (да-да, её у МК тоже нет, поэтому она имитируется на flash-памяти);</li><li>конфигурация WiFi.</li></ul><h3>Файловая система SPIFFS</h3><p>Один из плюсов внешней flash-памяти — файловая система. В неё можно с лёгкостью записать файлы (веб-странички, медиа-файлы и прочее) на микроконтроллеры ESP8266. На аппаратном уровне это можно было бы реализовать подключив к МК модуль SD-карт. Однако это решение требует свободных портов.</p><p>Размер файловой системы (от 32 Кбайт до 15 Мбайт) зависит от самого объема flash-памяти и от конфигурации, выставленной в Инстурменты → Flash size. Например, конфигурация 4M (2M SPIFFS) предназначена для МК с общим объёмом flash-памяти 4 Мбайт, 2 Мбайт из которых будут выделены под файловую систему.</p><p>SPIFFS не работает с папками — она содержит только список файлов. Соответственно, если загрузить в неё папку style, в которой будет файл header.css, то в файловую систему систему запишется файл с именем /style/header.css. Об этом стоит помнить, потому что длина файлового имени не должна превышать 31 символ (читается 32, но символ с кодом 0 отведён под завершение строки).</p><p>Для загрузки файлов потребуется инструмент <a href="https://github.com/esp8266/arduino-esp8266fs-plugin/releases/download/0.3.0/ESP8266FS-0.3.0.zip">ESP8266FS</a>, интегрирующийся в Arduino IDE. Инструкция по установке:</p><ol><li>Качаем инструмент по ссылке выше.</li><li>В папке Arduino IDE создайте папку tools (если её там ещё нет).</li><li>В папку tools распакуйте скачанный архив. Должно быть так:<br />…/Arduino/tools/ESP8266FS/tool/esp8266fs.jar.</li><li>После запуска среды в папке скетча (Скетч → Показать папку скетча) нужно создать папку data. Её содержимое будет загружаться в файловую систему.</li><li>После кликаем в Инструменты → ESP8266 Sketch Data Upload, чтобы загрузить данные в файловую систему. После загрузки в панели статуса высветится «SPIFFS Image Uploaded». Перед процессом загрузки в файловую систему обратите внимание: если открыт монитор последовательного порта — закрываем его.</li></ol><p>Как работать с SPIFFS и файлами в ней, можно узнать в <a href="http://wikihandbk.com/wiki/ESP8266:%D0%9F%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/Arduino/%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BE%D0%B9_%D0%B2_%D0%B0%D0%B4%D0%B4%D0%BE%D0%BD%D0%B5_ESP8266_%D0%B4%D0%BB%D1%8F_IDE_Arduino">этой статье</a>.</p><h3>Энергонезависимая память EEPROM</h3><p>Работа с внешней памятью немного отличается от <a href="https://arduinoplus.ru/arduino-eeprom/">стандартной</a>.</p><ul><li>Перед считыванием или записью данных нужно инициализировать EEPROM, указав при этом выделяемую под неё память (от 4 до 4096 байт) функцией EEPROM.begin(size).</li><li>Привычная функция EEPROM.write(addr, value) записывает данные не на саму энергонезависимую память, а в оперативную.</li><li>Чтобы внесённые данные записались в память, нужна функция EEPROM.commit() (или же EEPROM.end(), чтобы заодно очистить данные из оперативной памяти).</li><li>Метод EEPROM.read(addr) возвращает байт из памяти по адресу.</li></ul><h3>Прошивка «по воздуху» OTA-Update</h3><p>Для этого вам потребуется установленный <a href="https://www.python.org/downloads/">Python</a>.</p><p>В скетч нужно добавить файл: #include &lt;ArduinoOTA.h&gt;. Инициализируем и настраиваем OTA следующими строками:</p><p>После этого достаточно в метод loop() добавить строку: ArduinoOTA.handle().</p><p>Прошиваем МК по проводу. В случае успешной загрузки в списке портов появится новый хост с именем «esp8266-xxxxxx», где esp8266 — указанное выше имя хоста, а xxxxxx — локальный IP-адрес МК. Выбираем его.</p><p>Теперь микроконтроллер ESP8266 можно прошивать «по воздуху».</p>]]></content:encoded>
    </item>
    <item>
      <title>12 концепций, которые прокачают ваш JavaScript</title>
      <link>https://tproger.ru/translations/javascript-important-concepts</link>
      <comments>https://tproger.ru/translations/javascript-important-concepts?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/javascript-important-concepts</guid>
      <description><![CDATA[<p>Присвоение примитивных и ссылочных типов и другие основы языка: эти концепции не охватывают всего, но важны для любого JS-программиста.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/javascript-important-concepts">12 концепций, которые прокачают ваш JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 18 Apr 2019 08:46:55 GMT</pubDate>
      <content:encoded><![CDATA[<p>Несмотря на уровень знаний вы должны понимать основополагающие концепции языка. Эти 12 концепций, конечно, не охватывают всего, что должен знать разработчик, но важны для любого JS-программиста.</p><h2>Присвоения примитивных и ссылочных типов</h2><p>Понимание того, как JavaScript присваивает значение переменной, является залогом работающего кода. Без этого вы можете легко написать код, который непреднамеренно будет изменять значения переменных.</p><p>JavaScript всегда использует присвоение по значению. И это очень важно: когда присваиваемое значение является одним из 5 JavaScript’овых примитивов (Boolean, null, undefined, String и Number) — присваивается фактическое значение. Однако, когда присваиваемое значение является типом Array, Function или Object, присваивается ссылка на объект в памяти.</p><p>Например, в коде ниже переменной var2 присваивается значение var1. Т. к. var1 является примитивом (String), то переменной var2 присваивается строковое значение var1, и она может рассматриваться как отдельная (независимая) переменная. Соответственно, изменения var2 никак не отразятся на var1.</p><p>А теперь попробуем то же самое с типом Object.</p><p>Если бы вы ждали от кода выше такого же поведения, как и с примитивами, — это, вероятнее всего, вызвало бы ошибки. Подобное может быть особенно неудобным, если вы решите создать функцию, которая будет изменять какой-нибудь Object.</p><h2>Замыкания</h2><p>Замыкания — важный паттерн для приватизации переменной. В примере ниже createGreeter возвращает анонимную функцию, которой доступна переданная переменная greeting со значением «Hello». После эта переменная будет доступна для sayHello.</p><p>Или же более «правдоподобный» пример. У вас может быть некая функция apiConnect(apiKey), которая возвращает некоторые методы с использованием API ключа. В таком случае этот ключ нужно передать только один раз.</p><h2>Деструктуризация</h2><p>Деструктуризация — это просто способ извлечения свойств из объектов.</p><p>Если вам нужно извлечь свойство, дав ему другое имя, — делайте так:</p><p>В следующем примере деструктуризация применяется для «чистой» передачи объекта person в функцию introduce. Иначе говоря, деструктуризация может использоваться для непосредственного извлечения передаваемых параметров. Для тех, кто разрабатывает на React, это может показаться знакомым.</p><h2>Spread</h2><p>Нельзя не сказать об этой концепции. Spread — это специальный оператор JavaScript, который позволяет расширять выражения в тех местах, где предусмотрено использование нескольких аргументов.</p><p>В следующем примере Math.max() не может принять массив arr, т. к. функции с таким аргументом не существует. Math.max() принимает числа отдельными аргументами. Оператор spread(три точки — ...) используется для извлечения отдельных элементов из массива.</p><h2>Rest-параметры</h2><p>Rest-параметры позволяют передавать произвольное количество аргументов, принимая их в виде массива.</p><h2>Методы массивов</h2><p>С помощью методов массива в JavaScript можно добиться крутых (а порой и элегантных) способов трансформации данных. На StackOverflow часто можно наткнуться на вопросы о том, как работать с массивом объектов.</p><p>Ниже будут перечислены некоторые методы массива, сгруппированные по назначению. Однако это далеко не весь список.</p><h3>map, filter, reduce</h3><p>В методах map(), filter() и reduce() иногда можно запутаться. Они полезны для трансформации массива или возвращения его агрегатного значения.</p><ul><li>map(): возвращает массив, в котором каждый элемент изменяется с помощью переданной функции.const arr = [1, 2, 3, 4, 5, 6];const mapped = arr.map(el =&gt; el + 20);console.log(mapped);// [21, 22, 23, 24, 25, 26]</li><li>filter(): возвращает массив с теми элементами, в которых переданная функция возвращает true.const arr = [1, 2, 3, 4, 5, 6];const filtered = arr.filter(el =&gt; el === 2 || el === 4);console.log(filtered);// [2, 4]</li><li>reduce(): работа с элементами с сохранением промежуточного результата.const arr = [1, 2, 3, 4, 5, 6];const reduced = arr.reduce((total, current) =&gt; total + current);console.log(reduced);// 21</li></ul><h3>find, findIndex, indexOf</h3><p>Эти методы очень похожи. Используйте их следующим образом:</p><ul><li>find(): возвращает первый элемент массива, удовлетворяющий определенному условию.const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];const found = arr.find(el =&gt; el &gt; 5);console.log(found);// 6Опять же, обратите внимание, что все числа после 5 удовлетворяют условию, но возвращаться будет только первое значение. Это полезно в случаях, когда вы используете break при первом совпадении.</li><li>findIndex(): метод идентичен find(), но вместо возвращаемого значения здесь возвращается индекс первого подходящего элемента.const arr = ['Nick', 'Frank', 'Joe', 'Frank'];const foundIndex = arr.findIndex(el =&gt; el === 'Frank');console.log(foundIndex);// 1</li><li>indexOf(): метод идентичен findIndex(), но вместо функции он принимает искомое значение. Используйте этот метод в тех случаях, когда вам не нужна функция для проверки элемента на совпадение.const arr = ['Nick', 'Frank', 'Joe', 'Frank'];const foundIndex = arr.indexOf('Frank');console.log(foundIndex);// 1</li></ul><h3>push, pop, shift, unshift</h3><p>Вот отличные методы, которые позволяют добавлять или убирать элементы в массиве.</p><ul><li>push(): этот метод относительно простой. Он добавляет элемент в конец массива. Метод модифицирует массив и одновременно возвращает добавленный элемент.let arr = [1, 2, 3, 4];const pushed = arr.push(5);console.log(arr);// [1, 2, 3, 4, 5]console.log(pushed);// 5</li><li>pop(): метод удаляет последний элемент массива. Как и в прошлом случае, метод изменяет массив и одновременно возвращает удалённый элемент.let arr = [1, 2, 3, 4];const popped = arr.pop();console.log(arr);// [1, 2, 3]console.log(popped);// 4</li><li>shift(): этот метод удаляет первый элемент массива. Метод изменяет массив и возвращает удалённый элемент.let arr = [1, 2, 3, 4];const shifted = arr.shift();console.log(arr);// [2, 3, 4]console.log(shifted);// 1</li><li>unshift(): добавляет один или несколько элементов в начало массива. Как и прошлые методы, он изменяет массив, но возвращает новую длину массива.let arr = [1, 2, 3, 4];const unshifted = arr.unshift(5, 6, 7);console.log(arr);// [5, 6, 7, 1, 2, 3, 4]console.log(unshifted);// 7</li></ul><h3>splice, slice</h3><p>Эти методы либо изменяют, либо возвращают подмассив элементов.</p><ul><li>splice(): метод изменяет массив, удаляя или заменяя существующий элемент, и/или добавляет новый. Метод только изменяет массив. Код ниже можно объяснить так: в позиции 1 массива удалить 0 элементов и вставить b.let arr = ['a', 'c', 'd', 'e'];arr.splice(1, 0, 'b')</li><li>slice(): возвращает подмассив элементов массива, начиная и заканчивая на определённой позиции. Если конечная позиция не указана, возвращается остаток массива. Важно понимать, что этот метод не модифицирует массив, а только возвращает подмассив.let arr = ['a', 'b', 'c', 'd', 'e'];const sliced = arr.slice(2, 4);console.log(sliced);// ['c', 'd']console.log(arr);// ['a', 'b', 'c', 'd', 'e']</li></ul><h3>sort</h3><p>Метод сортирует массив, основываясь на функции, которая принимает первый и второй элемент. Этот метод изменяет сам массив. Если в массиве порядок элементов не был изменен, метод возвращает 0, если изменён — 1.</p><h2>Генераторы</h2><p>Не забываем и про них. Генератор определяет, какое значение будет возвращено при следующем вызове next().</p><p>А вот пример использования генератора для бесконечных значений:</p><h2>Разница операторов сравнения (===) и (==)</h2><p>Не стоит пренебрегать разницей этих операторов. Оператор (==) перед операцией сравнения будет выполнять преобразование типов, а (===) делать преобразования не будет.</p><h2>Сравнение объектов</h2><p>Частая ошибка у новичков в JavaScript — это неправильное сравнение объектов. Дело в том, что переменные объектов хранят  в памяти ссылку на объект, а не сам объект. Один из способов сравнения двух объектов — предварительное преобразование их в JSON строку. Однако у этого способа есть недостаток: не факт, что порядок в объекте сохранится. Более безопасный способ сравнения объектов — использование специальной библиотеки, которая сравнивает объекты на более глубоком уровне (к примеру <a href="https://lodash.com/docs/4.17.11#isEqual">isEqual от loadash</a>)</p><p>Хоть следующие объекты кажутся одинаковыми, они ссылаются на разные объекты в памяти.</p><p>А вот в следующем примере сравнение возвращает true, потому что второй объект приравнивается к первому и они оба ссылаются на один объект.</p><p>Важно понимать разницу между примитивами и ссылочными типами для правильной установки переменной, которая будет ссылаться на объект в памяти.</p><h2>Callback-функции</h2><p>Многие избегают callback-функции (функции обратного вызова). А зря — ведь это довольно просто! В следующем примере console.log() будет передан в myFunc() в качестве callback-функции.</p><h2>Промисы</h2><p>Как только вы начнёте понимать работу callback’ов в JavaScript, возможно, совсем скоро вы окажетесь в «аду обратных вызовов». На помощь приходят промисы (англ. Promises). Оберните свою асинхронную логику в промисы: resolve — для успехов, reject — для фейлов. Используйте then для обработки успеха и catch — для обработки фейлов.</p><h2>Async Await</h2><p>Как только вы разберётесь в промисах, вам может понравиться async await — это«синтаксический сахар» поверх промисов. Ниже пример async функции с await и промисом.</p><h2>Заключение</h2><p>Если вы не знали ни об одной из этих 12 концепций — не стоит откладывать их изучение в долгий ящик. А если знали — практика лишней никогда не будет.</p>]]></content:encoded>
    </item>
  </channel>
</rss>