Заметки Андрея Романова2022-02-20T00:00:00Zhttps://andrew-r.ru/notes/feed.xmlАндрей Романовme@andreyromanov.comО школе2015-09-12T16:47:00Zhttps://andrew-r.ru/notes/o-shkole/<p>Две главные проблемы, с которыми я столкнулся в школе — это отсутствие у детей интереса к учёбе и необходимость тупого заучивания материала без понимания смысла этого материала.</p> <p>Очень часто нам рассказывали материал по какой-либо теме, заставляя писать кучу определений и формул, но на простой вопрос «а как это применяется в реальной жизни?» дать ответа не могли. В итоге приходилось учить материал, которому мы не видели никакого применения. С тем же успехом можно было заучивать произвольный набор цифр и букв. Конечно же интереса и мотивации к учёбе не было.</p> <p>Чтобы они появились, нужно было всего лишь добавить пару слов о том, как только что рассказанная информация может быть применена на практике. Особенно это относится к математике. Если бы мне сразу сказали, что с помощью производной можно найти скорость изменения какой-либо величины, а потом добавили, что с помощью производной можно найти и скорость, и ускорение тела, если дан закон изменения координаты тела от времени — то у меня бы сразу отпали все вопросы о том, зачем же мне учить правила нахождения производной, если они всё равно нигде не будут применяться.</p> <p>Часто говорят, что в школе детей учат учиться, или что детей учат думать и рассуждать. Благодаря ЕГЭ в старших классах детям приходится дрочить на одни и те же типовые задания. Ну и где тут полёт мысли и чудеса логики? Вместо того, чтобы вникать в суть предмета, человеку приходится учить наизусть огромное количество формул (которые вообще-то рациональнее подсмотреть в справочнике). Решение типовых заданий ума не прибавляет, оно лишь прибавляет умение решать эти самые задания. Дайте школьникам задания другого типа — и вот они растерянно смотрят на вас и говорят, что они не знают, как это решать.</p> <p>К чему это я? К чёрту заучивание формул! Дайте детям возможность пользоваться справочниками. В конце концов, сейчас везде есть интернет — ну зачем учить наизусть то, к чему можно получить доступ в любую минуту?</p> <p>Я не говорю, что формулы в принцип не надо знать. Надо знать, что есть вот такая-то формула и что её можно применить в таком-то случае. <strong>Надо понимать суть</strong>. Формулы — лишь инструмент, который можно применять осмысленно, а можно применять и бездумно. Вот и получается, что нынешние школьники забивают гвозди микроскопами, даже не осознавая истинного предназначения микроскопа.</p>Правильное выравнивание по ширине контейнера2015-09-18T16:17:00Zhttps://andrew-r.ru/notes/flex-grid/<p>Вёрстка сетки с выравниванием элементов по ширине родительского контейнера — довольно распространённая задача. На первый взгляд эту задачу решить просто — задать инлайн-блокам нужную ширину и отступы, убрать отступы там, где их быть не должно. Но всё становится сложнее, если размер блоков заранее не известен.</p> <p>Между тем, если вам не нужно поддерживать IE9-, вы можете использовать прекрасное решение на флексбоксе. И сразу кодпен с решением:</p> <iframe height="460" scrolling="no" src="https://codepen.io/andrew-r/embed/yYORwm/?height=461&theme-id=0&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" style="width: 100%; max-width: 760px;">See the Pen <a href="http://codepen.io/andrew-r/pen/yYORwm/">Grid</a> by Andrew Romanov (<a href="http://codepen.io/andrew-r">@andrew-r</a>) on <a href="http://codepen.io/">CodePen</a>. </iframe> <p>Немного комментариев. В данном случае в одной строке сетки может размещаться максимум три элемента — пусть это число будет числом n, запомните его. Зададим контейнеру следующие свойства:</p> <pre><code class="language-css"><span class="hljs-selector-class">.grid</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; } </code></pre> <p>Казалось бы, задача решена — элементы выравниваются по ширине. Однако если в последней строке элементов будет меньше, чем нужно для её полного заполнения — например, только два блока с шириной в 33%, то эти два блока тоже будут выровнены по ширине, что не очень красиво.</p> <p><img src="https://andrew-r.ru/notes/flex-grid/assets/grid.png" alt="Наивное решение" /></p> <p>Чтобы блоки в последней строке выравнивались по левому краю, добавим в самый конец контейнера n—1 (помните? n — максимальное количество элементов в строке сетки) пустых элементов. Этим элементам зададим следующие стили:</p> <pre><code class="language-css"><span class="hljs-comment">/* Псевдоселектор :empty автоматически выберет пустые элементы */</span> <span class="hljs-selector-class">.grid__item</span><span class="hljs-selector-pseudo">:empty</span> { <span class="hljs-attribute">flex</span>: <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">33%</span>; <span class="hljs-comment">/* ширина наименьшего модуля сетки */</span> <span class="hljs-attribute">height</span>: <span class="hljs-number">0</span>; <span class="hljs-comment">/* убедимся в том, что пустые элементы не будет видно в любом случае */</span> } </code></pre> <p>На экране этих блоков видно не будет, но браузер будет считать их за полноценные элементы сетки, и если в последней строке останется два видимых элемента, то один из двух пустых блоков подставится в эту же строку и займёт третьего элемента. Этакий элемент-фантом. Таким образом даже при недостатке видимых элементов последняя строка сетки всегда будет заполнена, а значит видимые элементы будут выравниваться по левому краю, что нам и требовалось.</p>Индикаторы правильности заполнения полей формы на CSS2015-10-03T08:35:00Zhttps://andrew-r.ru/notes/css-validation/<p>Недавно наткнулся на интересную технику, позволяющую проверять правильность заполнения полей прямо в CSS.</p> <p>С приходом HTML5 валидация данных на стороне клиента превратилась в довольно простую задачу, которую приятно решать. Всё благодаря атрибутам <code>required</code>, <code>pattern</code>, <code>maxlength</code> (<code>maxlength</code> доступен и в HTML4), а также благодаря новым типам полей ввода (например, <code>email</code>, <code>url</code>).</p> <p>Новые возможности появились не только в HTML. С помощью CSS можно стилизовать разные состояния поля ввода: поле с неправильно введёнными данными и поле с правильно введёнными данными. Происходит это с помощью псевдоклассов <code>:valid</code> и <code>:invalid</code>.</p> <p>Сразу рабочий пример:</p> <iframe style="max-width: 760px; width: 100%;" height="365" scrolling="no" src="https://codepen.io/andrew-r/embed/rOyGjw/?height=365&theme-id=0&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true">See the Pen <a href="http://codepen.io/andrew-r/pen/rOyGjw/">input validation</a> by Andrew Romanov (<a href="http://codepen.io/andrew-r">@andrew-r</a>) on <a href="http://codepen.io/">CodePen</a>. </iframe> <p>В этом примере также демонстрируется возможность скрытия кнопки регистрации, пока все поля не будут заполнены правильно.</p> <p><a href="http://caniuse.com/#feat=form-validation">Поддержка браузерами</a> — IE10+.</p>Зачем нужен PostCSS2015-10-20T06:30:00Zhttps://andrew-r.ru/notes/why-postcss/<div class="sidenote"> <p class="sidenote__paragraph">Когда я публикую в <a href="https://vk.com/forwebdev">Форвебе</a> какой-либо материал о PostCSS, в комментариях сразу начинается срач в духе «да зачем вообще нужен этот PostCSS, есть же препроцессоры!», «PostCSS вообще лажа, не понимаю, почему вокруг него подняли такой шум». Все эти комментарии пишут люди, до конца не понимающие суть задач, которые должен решать PostCSS. Однако каждый из них спешит поделиться своим мнением и рассказать всему миру, что PostCSS — это какая-то ненужная дичь, которую нам пытаются тщательным образом впарить. В этой заметке я попробую «на пальцах» объяснить, чем PostCSS лучше препроцессоров и вообще зачем он нужен.</p> <aside class="sidenote__note">Также читайте моё <a href="http://forwebdev.ru/css/about-postcss/">интервью с Андреем Ситником</a>, в котором мы поговорили об истории создания PostCSS, применении PostCSS в разработке и о планах проекта на будущее.</aside> </div> <p>На самом деле всё довольно просто. Почему появились препроцессоры? Разработчикам не хватало возможностей, предусмотренных спецификацией CSS. Никто не любит выполнять рутинную работу, и верстальщики — не исключение. Препроцессоры дают нам замечательные возможности по ускорению и упрощению написания CSS: это переменные, вложенные блоки кода, инклуды, примеси и многое другое. Всё вроде бы круто, но проблема в том, что набор возможностей препроцессоров фактически перестал пополняться. Что год назад, что сейчас мы используем одни те же возможности препроцессоров. Развития никакого нет. Более того, все основные препроцессоры (Sass/Less/Stylus) по большему счёту ничем и не отличаются, кроме синтаксиса. Готов поспорить, что через год в препроцессорах не появится ничего принципиально нового.</p> <p>Если мыслить в плоскости препроцессоров, то мы достигли вершины возможностей. Но PostCSS кардинально отличается от препроцессоров. Нельзя сравнивать PostCSS и препроцессоры, рассматривая PostCSS как ещё один препроцессор.</p> <p>Что такое PostCSS? Это парсер. Просто парсер стилей. Он ничего не делает кроме того, что получает на входе написанные вами стили и разбирает их на составляющие, а затем собирает обратно. Вот между этими двумя этапами вклиниваются плагины для PostCSS, которые и выполняют всю полезную работу.</p> <p>PostCSS создан для того, чтобы преобразовывать стили нужным нам образом. Преобразованием стилей занимаются плагины. Плагин берёт разобранный на составляющие код, который ему передал PostCSS, и как-то его обрабатывает. Самый простой пример, знакомый всем — это Автопрефиксер. Он получает на входе ваши стили, ищет в них плохо поддерживаемые свойства. Как только он находит такое свойство — скажем, <code>display: flex;</code> — он идёт на caniuse.com и ищет информацию о том, какие браузеры понимают это свойство только с префиксами. Если вы изначально указали в настройках, что вам нужно поддерживать эти браузеры, он добавляет в ваши стили то же самое свойство <code>display: flex</code>, но уже с нужными префиксами. Далее он обратно передаёт модифицированный код в PostCSS, и тот собирает его обратно в обычный CSS. Такова суть работы плагинов.</p> <p>Если препроцессоры предоставляют нам строго ограниченный набор возможностей, то PostCSS даёт полную свободу. Вы можете использовать как <a href="http://postcss.parts/">готовые плагины</a> (которых, к слову, уже довольно много), так и свои собственные. Написать плагин не составит труда, если вы программируете на JavaScript.</p> <p>PostCSS имеет модульную архитектуру, а это значит что вам не потребуется устанавливать сразу все плагины за один раз — достаточно установить лишь сам PostCSS и нужные вам плагины. Ничего лишнего, в отличие от препроцессоров.</p> <p>С PostCSS отпадает смысл использовать препроцессоры. Если вам требуется вложенность — подключите нужный плагин и сможете её использовать. Переменные, циклы, инклуды — всё это уже реализовано и доступно для использования прямо сейчас. Но всё это не так интересно, ведь это уже пройденный с препроцессорами этап. PostCSS предоставляет нам гораздо более интересные возможности. Например, чтобы не инлайнить картинки в CSS вручную, можно лишь указать путь к картинке, и плагин для PostCSS сам заинлайнит её. Но это всё мелочи. С помощью <a href="https://github.com/css-modules/css-modules">CSS-модулей</a> PostCSS позволяет добиться полной изоляции стилей для каждого компонента на странице. Если без этого плагина вы не могли использовать два одинаковых класса на странице для стилизации разных компонентов, то с этим плагином можно благополучно забыть о проблеме конфликта имён классов. Хорошее применение такой плагин может найти и в вёрстке встраиваемых виджетов, подключаемых на разных сайтах. Это позволит избежать конфликтов стилей виджета со стилями сайта.</p> <p>Короче говоря, на PostCSS можно самому написать свой препроцессор с <s>блекджеком и куртизанками</s> кучей новых фич. Возможности ограничены лишь вашей фантазией. Благодаря большому количеству плагинов можно даже не писать ничего своего, а сразу использовать готовое. На PostCSS можно делать всё — от модульных минификаторов до CSS на русском.</p> <p>В заключение скажу, что нет смысла для каждой задачи, связанной со стилями, использовать отдельный инструмент. PostCSS с определённым набором плагинов способен справиться с огромным количеством задач, которые раньше мы решали с помощью препроцессоров, онлайн-сервисов, консольных утилит и вообще вручную.</p>Совет самому себе2015-11-16T02:31:00Zhttps://andrew-r.ru/notes/18/<p>На днях мне исполнится восемнадцать лет. Возможно, лет через пять я перечитаю это и посмеюсь. А возможно ничего и не поменяется.</p> <p>Если бы я мог вернуться в прошлое и дать совет самому себе, то я бы сказал следующее:</p> <blockquote> <p>Чувак, в этой жизни все будут пытаться заставить тебя играть по их правилам, и с этим ничего не поделать. Смирись и меньше времени уделяй вещам, которые от тебя хотят остальные. Не парься по поводу учёбы в школе или в ВУЗе. Рассчитывай только на себя, занимайся самообразованием. Уделяй своё время тому, что тебе действительно интересно, а не тому, что интересно другим.</p> </blockquote>«Интерес и важность», часть 12016-01-23T14:47:00Zhttps://andrew-r.ru/notes/interest-and-importance/<p class="subtitle">Собрал основные тезисы лекции Людвига Быстроновского (<span class="nobr">арт-директора</span> студии Лебедева).</p> <div class="sidenote"> <p class="sidenote__paragraph">Существует огромное множество сложных систем, но самая сложная из них — это человек. Технологии меняются, а люди — нет. Поэтому в первую очередь дизайнер должен понимать, как люди думают, что привлекает их внимание и заставляет принимать решения.</p> <aside class="sidenote__note">Людвиг советует: «Эксперимент в социальной психологии», Стенли Милграм</aside> </div> <p>Как человек учится: знания → умение → навык → привычка.</p> <p>С каждым шагом осознанность уменьшается. Привычка — то, что доведено до полного автоматизма и не требует особых мыслительных процессов.</p> <p>Простой секрет того, как стать профессионалом: работай над мелочами. В одном проекте уделил внимание типографике, в другом — вёрстке, в третьем — цвету. Что-то не получается? Работай над этим снова и снова, из проекта в проект, пока результат не удовлетворит. Прокачался в вёрстке, прокачался в типографике — постепенно из всех этих мелочей складывается сумма навыков — то, что отличает профессионала от любителя.</p> <p>Дизайнер должен руководствоваться здравым смыслом. Дизайнер должен сомневаться в работоспособности собственного решения. Дизайнер должен проверять свои гипотезы. Слепая вера во что-то — тупиковый путь. Правильный путь — предложить решение задачи и проанализировать его преимущества и недостатки, а ещё лучше — проверить его в жизни.</p> <p>Из этого вытекает следующий пункт — нужно выращивать, а не строить. Можно годы работать над проектом, а потом понять, что он никому не нужен. Начните с малого: пускай вы сделаете маленькую часть, но она будет рабочей. Сразу же тестируйте её на пользователях. Зачастую от намеченного пути приходится отклоняться, и вроде бы второстепенные фичи неожиданно приобретают популярность среди пользователей. Выращивая, а не строя, вы поймёте раньше, что идёт не так и где нужно свернуть с намеченного пути.</p> <p>Интерактивные прототипы — отличная вещь для тестирования логики и структуры. Дизайнер может нарисовать чистовой вариант со сложной и непродуманной навигацией, и ему придётся всё переделывать. А может сделать прототип и прийти к верному решению с навигацией ещё до того, как будет нарисован чистовой вариант.</p> <p>Опенспейсы — плохое место для работы. В любой момент тебя может дёрнуть коллега и отвлечь от важной задачи. Чтобы избежать этого, нужно работать дома или в отдельном кабинете, а опенспейс использовать для общения с коллегами в условленное время (например, с 16:00 до 17:00 ежедневно).</p> <p>Важное всегда неочевидно. Во всём нужно стремиться выяснить важное, всё остальное — шум.</p> <p>Интерес всегда побеждает важность. Если человеку что-то интересно, он забьёт на все важные вещи. Чтобы сделать задачу интересной, нужно её усложнить — это вносит элемент соревнования, бросает человеку вызов.</p> <p>Нужно уметь говорить «нет». Всем, кто не умеет — читать книгу Джима Кемпа «Нет. Лучшая стратегия ведения переговоров».</p> <p>Даже если всё плохо — просто продолжай. Не останавливайся, если не видишь выхода из сложившейся ситуации. Рано или поздно ты к нему придёшь.</p> <p>Нужно уметь управлять видением мира и эмоциями. Можно смотреть на ситуацию затуманенными злобой глазами, а можно взглянуть на неё с трезвой и ясной головой. Второе предпочтительнее. Чтобы отвлечься или отвлечь кого-то от злобы, можно использовать юмор — так делают даже в спецназе в особо стрессовых ситуациях.</p>Организация шрифтов в проекте2016-01-23T16:17:00Zhttps://andrew-r.ru/notes/how-to-organise-fonts/<p>Старт любого проекта — ужасная рутина. Даже если у вас есть готовый стартовый шаблон (<a href="https://github.com/andrew%E2%80%94r/startanul">вроде моего</a>), всё равно нужно писать под каждый проект свои базовые стили, добавлять нужные шрифты и так далее.</p> <p>В этой заметке я расскажу вам, как облегчить себе жизнь с помощью простой техники организации шрифтов в проекте.</p> <p>Каждую гарнитуру я храню в отдельной папке с названием, соответствующим имени гарнитуры. Внутри лежат файлы шрифтов, называемые по их начертанию:</p> <pre><code>/fonts/proxima-nova/regular.woff /fonts/proxima-nova/bold.woff /fonts/garamond/regular.woff /fonts/garamond/italic.woff </code></pre> <p>Наглядно и понятно, и нет необходимости дублировать имя гарнитуры в названиях файлов. Подключать шрифты с такой структурой — одно удовольствие. Для начала сделаем универсальный код, который вставим в наш базовый шаблон:</p> <pre><code class="language-css"><span class="hljs-comment">/* Name regular */</span> <span class="hljs-keyword">@font-face</span> { <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Name'</span>; <span class="hljs-attribute">src</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">'/fonts/name/regular.woff'</span>) <span class="hljs-built_in">format</span>(<span class="hljs-string">'woff'</span>); <span class="hljs-attribute">font-style</span>: normal; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">400</span>; } <span class="hljs-comment">/* Name bold */</span> <span class="hljs-keyword">@font-face</span> { <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Name'</span>; <span class="hljs-attribute">src</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">'/fonts/name/bold.woff'</span>) <span class="hljs-built_in">format</span>(<span class="hljs-string">'woff'</span>); <span class="hljs-attribute">font-style</span>: normal; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">800</span>; } <span class="hljs-comment">/* Далее в том же духе */</span> </code></pre> <p>Как только возникает необходимость подключить какой-то шрифт, просто закидываем его в папку <code>/fonts</code>, а в стилях заменяем все вхождения <code>Name</code> и <code>name</code> на название гарнитуры с прописной и со строчной буквы соответственно.</p>«Интерес и важность», часть 22016-01-24T16:22:00Zhttps://andrew-r.ru/notes/interest-and-importance-2/<p class="subtitle">Собрал основные тезисы лекции Людвига Быстроновского (<span class="nobr">арт-директора</span> студии Лебедева).</p> <p>Интерес — это переключение внимания. Как только ты переключаешь внимание человека, ему становится интересно. Один из способов вызова интереса — нарушение непреложных законов и закономерностей. Создайте определённое ожидание, а затем нарушьте его. В армии солдаты каждый день едят на обед мясо. Мясо, мясо, мясо, а потом бах — рыба. Ожидание нарушено, интерес вызван.</p> <p>Не нужно бояться ошибаться. Как часто вы обращаете внимание на неудачи и ошибки других людей? Как часто вы запоминаете их надолго? Правильно, нечасто. Секрет в том, что окружающим пофиг на вас и ваши ошибки. Поэтому можно ошибаться хоть до конца жизни, главное выносить из этого какой-то урок.</p> <p>Как, а главное зачем нужно выходить из зоны комфорта? Отрежьте себе ногу. Вышли из зоны комфорта? Вышли. Просто? Да. Эффективно и действенно? Нет. Чтобы выйти из зоны комфорта и дать себе толчок к развитию, увеличивайте скорость. Делаете три проекта в месяц? Увеличьте скорость, поставьте цель делать пять проектов в месяц.</p> <p>Делайте паузы в работе. Регулярная работа с паузами будет продуктивна. Её можно сравнить с приёмом пищи: нельзя постоянно откладывать его на потом, а в последнюю ночь перед дедлайном наесться до отвала. Так же и с работой: выполняйте её небольшими порциями; делайте паузы — давайте мозгу время осмыслить то, что вы делаете. Если не делать пауз, вы рискуете упустить важные детали. Паузы дают вам время переварить всё то, что копится в голове во время работы.</p> <p>Записывайте. Записывайте всё, что внезапно приходит в голову. Записывайте важные мысли. Делайте это регулярно. Помимо того, что записи всегда будут храниться рядом и не потеряются, вы разовьёте в себе навык чёткого формулирования своих мыслей.</p> <p>У любого решения кроме пользы есть и вред. Нужно научиться думать о вреде, а не только о пользе. Крутое на первый взгляд решение может оказаться на поверку не таким уж и хорошим.</p> <p>Как привлечь заказчиков? Не нужно рекламировать себя. Сделайте так, чтобы заказчик нуждался в ваших услугах. Сделайте так, чтобы ему было интересно посетить ваш сайт и почитать его. Живите в мире заказчика, а не в своём мире.</p>Не создавайте ожиданий2016-02-08T04:41:00Zhttps://andrew-r.ru/notes/fuck-the-expectations/<p class="subtitle">В чём проблема ожиданий? Они несут разочарование.</p> <p>Люди склонны к гиперболизации. Причём эта гиперболизация чаще проявляется вместе с мыслями о чём-то положительном. Например, пишет тебе старый заказчик и рассказывает о своём новом крутом проекте, который принесёт миллионы долларов (да, я бы отнёсся к этому со скептицизмом, но это специально преувеличенный пример). Заказчик предлагает поучаствовать в проекте на условиях фиксированного оклада + хорошего процента от будущей прибыли. В этот момент важно контролировать мысли — мозг может «отрубиться» и начать рисовать красивые картинки, в которых вы едете на шикарной машине по побережью какого-нибудь тропического острова. Чем больше рисуется таких картинок, тем они становятся идеальнее. Человек перестаёт думать о реальности и уходит в мечты.</p> <p>Я предпочитаю в принципе не допускать таких мыслей. Неважно, о чём идёт речь — о каком-то предстоящем событии, о перспективах на работе или о чём-либо другом — не нужно допускать появления определённых ожиданий. Ожидания имеют свойство не оправдывать себя, поэтому чем идеальнее вы создадите картинку в своей голове, тем сильнее будет разочарование.</p> <p>Не уходите от реальности в область фантазий. Не создавайте ожиданий.</p>Как быть пунктуальнее2016-02-14T17:16:00Zhttps://andrew-r.ru/notes/punctuality/<p>Часто за собой замечал, что даже если выйду вовремя, то опоздаю на пять-десять минут. Есть простое решение этой проблемы.</p> <p>Я перевожу все часы на десять минут вперёд. Тогда даже если я опоздаю на пять минут по своим часам, на самом деле я приду за пять минут до нужного времени.</p>Ленты соцсетей — зло2016-03-18T06:05:00Zhttps://andrew-r.ru/notes/social-feeds/<p>Каждый день я трачу на чтение ленты ВК/твиттера как минимум час, а потом вечером сокрушаюсь, что не хватило времени на почитать книгу или отдохнуть.</p> <p>Вчера я пришёл к выводу, что сколько бы ни была лента интересной, она всё равно не несёт ощутимой пользы. Раньше я отмазывался сам перед собой тем, что, мол, я подписан на полезные и интересные сообщества, да и юмор время от времени не помешает. На самом деле это херня.</p> <p>Лента новостей — потраченное время, которое можно провести с большей пользой за книгой. Посему с сегодняшнего дня я отказываюсь от ленты новостей и как минимум в ближайший месяц не планирую её открывать и тратить на неё время.</p> <p>Чтобы сделать жизнь осмысленнее и продуктивнее, нужно выбрасывать из неё лишнее. Ленты соцсетей — определённо лишнее.</p>Скачивание изображений в IE10+2016-03-23T03:30:00Zhttps://andrew-r.ru/notes/ie10-file-download/<p>Возникла необходимость реализовать скачивание изображений при клике по ссылке.</p> <p>К счастью, в спецификации HTML5 появился новый атрибут <code>download</code>, указывающий браузеру на то, что ресурс по ссылке следует скачать. К несчастью, этот атрибут не поддерживается в Сафари и IE.</p> <p>Для IE удалось найти относительно простое альтернативное решение, основанное на <a href="https://developer.mozilla.org/ru/docs/Web/API/Blob">блобах</a>:</p> <pre><code class="language-javascript"><span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'a[download]'</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =></span> { <span class="hljs-comment">// следующий код сработает только в IE10+</span> <span class="hljs-keyword">if</span> (navigator && navigator.<span class="hljs-property">msSaveOrOpenBlob</span>) { event.<span class="hljs-title function_">preventDefault</span>(); <span class="hljs-keyword">const</span> href = event.<span class="hljs-property">target</span>.<span class="hljs-property">href</span>; <span class="hljs-keyword">const</span> xhr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>(); xhr.<span class="hljs-title function_">open</span>(<span class="hljs-string">'GET'</span>, href, <span class="hljs-literal">true</span>); xhr.<span class="hljs-property">responseType</span> = <span class="hljs-string">'blob'</span>; xhr.<span class="hljs-property">onload</span> = <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> filename = href.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/^.*\//g</span>, <span class="hljs-string">''</span>); <span class="hljs-keyword">return</span> navigator.<span class="hljs-title function_">msSaveOrOpenBlob</span>(xhr.<span class="hljs-property">response</span>, filename); }; xhr.<span class="hljs-title function_">send</span>(); } }); </code></pre>Как я попробовал отказаться от лент соцсетей и почему эта затея провалилась2016-05-04T16:34:00Zhttps://andrew-r.ru/notes/social-feeds-fail/<p class="subtitle">Какое-то время назад я понял, что постоянно лезу в соцсети, чтобы поскроллить ленту и прочесть новые записи. Всё бы ничего, но это отнимало слишком много времени (в том числе рабочего). Мне это надоело, и я сгоряча решил поступить радикально: полностью отказаться от чтения новостных лент.</p> <p>Первые несколько дней у меня это почти получалось: я машинально открывал ленту и на второй-третьей записи вспоминал, что я запретил себе делать это. Мне удалось повысить продуктивность и использовать освобождённое время с пользой: я снова взялся за чтение книг, которые постоянно откладывал на потом.</p> <p>Но постепенно мой первоначальный пыл утих, и я стал думать, как можно читать ленты соцсетей без особого ущерба для продуктивности. Стоит уточнить, что в лентах соцсетей я в основном отслеживаю новые материалы по фронтенду, программированию и дизайну (это часть работы в рамках форвеба).</p> <p>Мой основной жизненный принцип (о нём я, возможно, как-нибудь расскажу отдельно) — избавляться от всего лишнего. В рамках соцсетей он работает так: если я вдруг вижу в ленте что-то, никак не связанное с моими интересами, я это удаляю из ленты навсегда. Есть небольшая тонкость: публикация может быть вроде бы и интересной, но пользы она никакой не несёт (например, юмор или какие-нибудь офигительные истории). Такое тоже нужно удалять несмотря на кажущуюся интересность материала. Такая фильтрация очень сильно сократила количество записей в моей ленте, и если раньше я утром тратил на соцсети около часа, то теперь могу пролистать все записи за 15 минут.</p> <p>Ну и по возможности я стараюсь читать ленты в пути — заняться в дороге всё равно особо больше нечем.</p>Поиск работы фронтендером в 2016 году2016-09-30T14:23:00Zhttps://andrew-r.ru/notes/searching-for-job-2016/<p>Этой осенью я искал новую работу. Собеседовался в четырёх компаниях: в <a href="http://inn.ru/">Иннове</a>, <a href="https://www.tinkoff.ru/">Тинькофф Банке</a>, <a href="https://netology-group.ru/">Нетологии</a> и <a href="http://rambler-co.ru/">Рамблере</a>. Ну и напоследок успел пообщаться с ребятами из <a href="https://www.yandex.ru/">Яндекса</a>. В этой заметке расскажу о своих впечатлениях и приведу все запомнившиеся задачи с собеседований.</p> <h2>Иннова</h2> <p>В Иннове я работал удалённо последние 10 месяцев. С компанией общался насчёт перехода с удалёнки в офис. Мне сразу сказали, что нужно будет пройти серию интервью с разными сотрудниками. В итоге я пообщался с тимлидом <a href="http://4gamer.ru/">Фогеймера</a> (это проект, над которым я работал в Иннове), с эйчаром и с кем-то из руководства (так и не понял, с кем).</p> <p>Впечатление от собеседований осталось двоякое. Техническое собеседование с тимлидом и общение с эйчаром были вполне приятные, а вот общение с руководством было довольно странным. Задавали вопросы в духе «какие у вас планы на ближайшие десять лет», «какие у вас глобальные мечты», «чем вы можете быть полезны компании».</p> <p>Плюсы:</p> <ul> <li>крутые проекты и интересные задачи (по собственному опыту);</li> <li>обучение английскому языку за счёт компании;</li> <li><a href="http://map.inn.ru/">классный офис</a> с летней верандой, располагается на Таганской</li> </ul> <p>Минусы:</p> <ul> <li>длинный процесс найма.</li> </ul> <h2>Нетология</h2> <p>Пожалуй, наиболее приятные впечатления остались от Нетологии. Первое собеседование было с двумя штатными фронтендерами и с руководителем отдела разработки. Ребята (фронтендеры) поспрашивали меня минут десять, а затем мы полчаса просто непринуждённо общались на тему технологий и разработки. Затем пришёл руководитель отдела, с которым мы отдельно поговорили о моём опыте и о Нетологии. После первой встречи мне дали тестовое задание. Суть задания — сделать на Реакте и Редаксе редактируемый список сотрудников. Я не стал заморачиваться с вёрсткой и сделал только логику, сопроводив это рассказом о моём прошлом опыте вёрстки и ссылками на выполненные работы. После выполнения тестового задания посетил ещё две встречи — одну с продуктологом, другую с одним из основателей Нетологии.</p> <p>Плюсы:</p> <ul> <li>чуваки улучшают российское онлайн-образование;</li> <li>офис на Тульской (одна станция до кольца);</li> </ul> <p>Минусы:</p> <ul> <li>нет ДМС;</li> <li>длинный процесс найма;</li> <li>на время испытательного срока понижают оклад.</li> </ul> <h2>Тинькофф Банк</h2> <p>Довольно прогрессивный банк, делающий ставку на онлайн-продукты. Было одно техническое собеседование, но с большим количеством вопросов на знание JS. По вёрстке почти ничего не спрашивали, по штукам вроде Реакта тоже. После собеседования попросили прислать анкету для проверки меня службой безопасности банка. Работа в банке выглядит заманчиво, но есть пара нюансов. По некоторым данным, в банке с отделом фронтенда всё не очень хорошо (в плане менеджмента). Ну и говорят, что довольно большую часть работы банк отдаёт на аутсорс.</p> <p>Плюсы:</p> <ul> <li>корпоративный фитнес-центр;</li> <li>большой отдел разработки, есть у кого учиться;</li> <li>хорошая зарплата;</li> <li>короткий процесс найма;</li> </ul> <p>Минусы:</p> <ul> <li>офис на Водном Стадионе, что не очень близко к центру;</li> <li>часть работы отдают на аутсорс.</li> </ul> <h2>Рамблер</h2> <p>Раньше я видел <a href="http://rambler-co.ru/jobs">список вакансий</a> компании, но мне казалось, что я до их уровня ещё не дорос. Довольно неожиданно мне написали оттуда с предложением пообщаться о работе. Была одна встреча, с двумя эйчарами и одним разработчиком. Сперва эйчары попросили рассказать меня о своём опыте (куда без этого), затем разработчик давал разные задачи. Затем мне вкратце рассказали о компании и о том, чем нужно будет заниматься. В компании сейчас активно используется Реакт, вроде как делается своя библиотека компонентов, ну и в целом развивается инфраструктура.</p> <p>Плюсы:</p> <ul> <li>довольно большая компания с большим количеством сотрудников и разных проектов;</li> <li>офис на Тульской;</li> <li>корпоративный английский;</li> <li>бесплатные завтраки для сотрудников (по непроверенным данным);</li> <li>короткий процесс найма.</li> </ul> <p>Минусы:</p> <ul> <li>не все команды занимаются интересными задачами, где-то довольно жёсткие сроки.</li> </ul> <h2>Яндекс</h2> <p>Собеседовался на стажировку, поэтому всё было не так сложно и страшно. Первая встреча была в офисе, с ребятами из команд Метрики и Поиска. По сути я пришёл на два часа, и ребята из каждой команды общались со мной по часу. Первый час я делал задания на знание JS. Второй час был посвящён разговорам о жизни и о технологиях, под конец ради интереса мне дали задание на знание вёрстки (единственный раз за все собеседования во всех компаниях). Примерно через неделю было два созвона по скайпу с теми же ребятами. На этот раз никаких заданий не было, а мне рассказывали о задачах и процессах в их командах.</p> <p>Плюсы:</p> <ul> <li>крупная и известная компания;</li> <li>серьёзные высоконагруженные проекты;</li> <li>командировки в разные города;</li> <li>n-ая сумма денег на бейджике для оплаты еды в близлежащих заведениях;</li> </ul> <p>Минусы:</p> <ul> <li>высокий порог вхождения из-за БЭМ-стека.</li> </ul> <h2>Задания</h2> <p>Что выведется в консоль? Как добиться правильного вывода в консоль, не убирая <code>setTimeout</code>?</p> <pre><code class="language-javascript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) { <span class="hljs-built_in">setTimeout</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(i); <span class="hljs-comment">// ?</span> }, <span class="hljs-number">0</span>); } </code></pre> <hr /> <p>Что выведется в консоль?</p> <pre><code class="language-javascript"><span class="hljs-keyword">var</span> a = (b = { <span class="hljs-attr">value</span>: <span class="hljs-number">1</span>, }); <span class="hljs-keyword">var</span> b = { <span class="hljs-attr">value</span>: <span class="hljs-number">2</span>, }; a.<span class="hljs-property">value</span> = <span class="hljs-number">3</span>; <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a.<span class="hljs-property">value</span>); <span class="hljs-comment">// ?</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(b.<span class="hljs-property">value</span>); <span class="hljs-comment">// ?</span> </code></pre> <hr /> <p>Что выведется в консоль?</p> <pre><code class="language-javascript"><span class="hljs-keyword">var</span> a = { <span class="hljs-attr">v</span>: <span class="hljs-number">1</span>, }; <span class="hljs-keyword">var</span> b = { <span class="hljs-attr">v</span>: <span class="hljs-number">2</span>, }; <span class="hljs-keyword">function</span> <span class="hljs-title function_">logValue</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">v</span>); } logValue.<span class="hljs-title function_">bind</span>(a).<span class="hljs-title function_">bind</span>(b)(); <span class="hljs-comment">// ?</span> </code></pre> <hr /> <p>Что такое <code>call</code>, <code>apply</code>, <code>bind</code>, зачем они нужны.</p> <hr /> <p>Напишите полифил функции <code>bind</code>.</p> <hr /> <p>Дан массив целых чисел, найдите наибольшее из них.</p> <hr /> <p>Даны два массива целых чисел, найдите общие элементы.</p> <hr /> <p>Напишите функцию, принимающую на вход строку и проверяющую, является ли эта строка палиндромом. Приведите несколько вариантов решения.</p> <hr /> <p>Напишите функцию, принимающую массив произвольных слов и на выходе дающую двумерный массив анаграмм:</p> <pre><code>['стол', 'барокко', слот', 'кот', 'кошка', 'ток', 'коробка'] // -> [ ['стол', 'слот'], ['кот', 'ток'], ['барокко', 'коробка'], ] </code></pre> <hr /> <p>Есть два класса: принтер с методом <code>print()</code> и сканер с методом <code>scan()</code>. Нужно получить класс МФУ, имеющий оба этих метода.</p> <hr /> <p>Напишите функцию, принимающую на вход время (в любом формате) и возвращающую угол между стрелками аналоговых часов.</p> <hr /> <p>Дано поле морского боя с размеченными на нём кораблями. Спроектируйте решение для подсчёта количества кораблей на этом поле.</p> <hr /> <p>Напишите функцию для сложения чисел, поддерживающую неограниченное количество вызовов:</p> <pre><code class="language-javascript"><span class="hljs-title function_">sum</span>(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)(); <span class="hljs-comment">// -> 5</span> <span class="hljs-title function_">sum</span>(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)(<span class="hljs-number">4</span>)(); <span class="hljs-comment">// -> 10</span> </code></pre> <hr /> <p>Напишите функцию, которая принимает массив с неограниченной вложенностью и делает из него плоский массив:</p> <pre><code>[1, [2, [3, 4], 5], 6, [7]] // -> [1, 2, 3, 4, 5, 6, 7] </code></pre> <hr /> <p>Чем поведение скрипта с атрибутом defer отличается от async?</p> <hr /> <p>Есть массив с адресами картинок. Как загрузить все картинки и выполнить какую-либо операцию после окончания загрузки всех картинок?</p> <hr /> <p>Дан односвязный список. Напишите функцию, которая вернёт значение n-ого с конца списка элемента.</p> <pre><code class="language-javascript">{ <span class="hljs-attr">value</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">next</span>: { <span class="hljs-attr">value</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">next</span>: { <span class="hljs-attr">value</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">next</span>: <span class="hljs-literal">null</span>, }, }, }; </code></pre> <hr /> <p>Расскажите всё, что вы знаете о событиях в JS.</p> <hr /> <p>В соцсети выложили фото котят, под ним динамически добавляются комментарии. У каждого комментария есть кнопка «лайк», при нажатии на которую нужно отправлять запрос к АПИ. Как эффективно реализовать сценарий с нажатием кнопки?</p> <hr /> <p>Как расположить элемент по центру экрана с помощью CSS (назовите все известные вам варианты).</p> <hr /> <p>Опишите процесс отрисовки страницы браузером.</p>Пока, 20162016-12-18T14:52:00Zhttps://andrew-r.ru/notes/bye-2016/<p class="subtitle">Как водится, пора подводить итоги года.</p> <h2>Омск и военкомат</h2> <p>В начале года стартап, в котором я работал, развалился, и я стал искать новую работу в Омске. Меня позвали в местный Сбертех, и их служба безопасности попросила какую-то справку из военкомата. Проблема была в том, что я в военкомате на учёте не стоял и даже не имел приписного удостоверения.</p> <p>Делать нечего, я подготовился и ближе к концу мая пошёл в военкомат вставать на учёт. Это было ошибкой, потому что в то же время мне пришло письмо из Яндекса о том, что я прошёл отбор в ШРИ и меня ждут в июле в Москве. К счастью, мне каким-то непонятным образом удалось решить все дела с военкоматом примерно за месяц, и я освободился до конца июня.</p> <h2>Школа разработки интерфейсов</h2> <p>В начале года был анонсирован набор в ШРИ Яндекса. Я без особой надежды сделал тестовое задание, отправил его и благополучно забыл об этом. Ближе к лету мне прислали приглашение, и в июле я уже был в Москве. Яндекс любезно оплатил всем иногородним участникам проживание в хостеле, и это было круто в плане постоянного общения с другими ребятами. Вообще, общение и совместная работа — пожалуй, самые ценные вещи, которые были за эти три месяца.</p> <p>Сама учёба тоже была очень полезна — я, наконец, разобрался с некоторыми темами, которые постоянно откладывал на потом. На втором этапе мы полтора месяца делали свой продукт в команде с дизайнером и менеджером. Изначально я хотел выстроить правильный процесс разработки и делать кодревью, но сжатые сроки всё испортили и в итоге мы просто фигачили код, комментируя и обсуждая всё устно по ходу дела. Зато мы успели сделать законченный продукт, которым даже можно пользоваться, пусть и с некоторыми неудобствами.</p> <p><img src="https://andrew-r.ru/notes/bye-2016/assets/yandex_01.jpg" alt="Московский офис Яндекса" /></p> <p><img src="https://andrew-r.ru/notes/bye-2016/assets/yandex_02.jpg" alt="Двор московского офиса Яндекса" /></p> <p><img src="https://andrew-r.ru/notes/bye-2016/assets/yandex_03.jpg" alt="Шахматы на крыше московского офиса Яндекса" /></p> <p><img src="https://andrew-r.ru/notes/bye-2016/assets/yandex_04.jpg" alt="Отражение заката в окнах московского офиса Яндекса" /></p> <h2>Переезд в Москву</h2> <p>Спустя два месяца учёбы в ШРИ я неожиданно для себя стал ходить по собеседованиям. В итоге я получил несколько оферов и выбрал Рамблер, так как он казался мне наиболее привлекательным из всех. По окончанию ШРИ я уехал в Омск на пару недель, и десятого октября я вышел на работу в Рамблер.</p> <p><img src="https://andrew-r.ru/notes/bye-2016/assets/rambler.png" alt="Бейджик сотрудника Рамблера" /></p> <h2>Работа в Авито</h2> <p>После выхода на работу в Рамблер я понял, что мои ожидания были чрезмерно завышены. Урок на будущее: прежде чем идти работать в какую-то компанию, стоит неформально пообщаться с кем-нибудь из её сотрудников. Я задумался о смене места работы, и тут мне очень удачно позвонили из Авито и пригласили на собеседование (а всё благодаря вам, Рашит и Яна, спасибо).</p> <p>Я прошёл все этапы (собеседования с руководителем отдела фронтенда, с тимлидами, с эйчаром) и в начале ноября получил офер, который с радостью принял. Забавно совпало, что из Рамблера я уволился в свой день рождения, 21 ноября. С 23 ноября я работаю в Авито, и ни одного плохого слова в адрес компании или сотрудников сказать не могу — всё круто!</p> <h2>Какие планы на 2017?</h2> <p>Пусть опубликованные здесь цели будут дополнительным стимулом к их выполнению:</p> <ul> <li>начну заниматься английским с преподавателем;</li> <li>переделаю приложение, сделанное в ШРИ, чтобы не было стыдно его показывать;</li> <li>оживлю <a href="http://frontendbookshelf.ru/">Книжную полку фронтендера</a>;</li> <li>пройду все курсы из профессии «Фронтенд-разработчик» на <a href="https://ru.hexlet.io/u/andrew-r">Хекслете</a>;</li> <li>начну больше читать бумажные книги (хватит уже всё время в экраны пялиться) и, возможно, выкладывать сюда рекомендации книг;</li> <li><a href="https://github.com/andrew%E2%80%94r/sicp">прорешаю СИКП</a> хотя бы наполовину (попробую, но не уверен в этом).</li> </ul>О состоянии потока2016-12-24T19:27:00Zhttps://andrew-r.ru/notes/about-flow/<p>Людвиг Быстроновский <a href="https://www.youtube.com/watch?v=CsFJzkNG5EY">в одной из своих лекций</a> сказал, что состояние потока — зло.</p> <p>Человек в этом состоянии фокусируется на одной задаче, а всё кроме неё становится для него неинтересной ерундой. Время обедать — человек не хочет отвлекаться и ест что попало, параллельно продолжая решать задачу. Время идти домой домой — «ещё немного осталось, надо добить, здесь на пять минут», а пять минут растягиваются на несколько часов.</p> <p>Возможные последствия этого — стресс, недосып, да и вообще забивание на всё, что не связано с потоком (семья и личная жизнь, правильное питание, здоровье и так далее).</p> <p>Избежать этих проблем просто: каждой задаче нужно уделять небольшое количество времени, после чего обязательно делать перерыв. Людвиг уделяет каждой задаче не больше 15 минут в день (конечно этого мало, если начинать делать задачу за два дня до дедлайна, но он начинает заранее и занимается задачей каждый день не больше 15 минут).</p> <p>Не злоупотребляйте потоком: живите нормальной жизнью, получая удовольствие не только от работы, но и от всего остального.</p>Чему учиться в 20** году, если вы — фронтенд-разработчик2017-01-02T19:47:00Zhttps://andrew-r.ru/notes/frontend-learning-20xx/<p>Изучайте разные парадигмы программирования (императивное, функциональное, логическое, автоматное программирование). Не нужно следовать только одной парадигме — каждая из них может оказаться полезной в зависимости от задачи.</p> <p>Научитесь писать (и пишите!) тесты. Автоматическое тестирование:</p> <ul> <li>намного дешевле ручного;</li> <li>помогает выявлять баги ещё на этапе разработки;</li> <li>вселяет в вас уверенность в своём коде;</li> <li>поможет вам убедиться, что вы ничего не сломали очередным рефакторингом.</li> </ul> <p>Не изучайте новые фреймворки и библиотеки; изучайте подходы, лежащие в их основе.</p> <p>Изучайте структуры данных и связанные с ними алгоритмы: списки, графы (в частности, деревья), битовые карты, хеш-таблицы. Это расширит ваш кругозор и вы станете видеть более эффективные и простые способы решения ежедневных задач.</p> <p>Учитесь думать о задачах в мире бизнеса, а не в мире разработки. Помните о том, что программистам платят за решение задач бизнеса, а не за количество написанных строчек кода. Думайте о бизнесе, прежде чем переписывать проект с нуля на очередном модном фреймворке. Думайте о бизнесе, когда нужно определиться со списком поддерживаемых браузеров — если клиенты бизнеса пользуются IE8, ваша работа поддержать его, а не выводить надпись «Ваш браузер устарел, обновитесь!!!».</p> <p>Универсальный совет — изучайте фундаментальные, проверенные временем подходы и приёмы, а не меняющиеся каждые n месяцев инструменты.</p>Мой опыт работы в Рамблере2017-03-06T19:29:00Zhttps://andrew-r.ru/notes/work-at-rambler/<p><em>Дисклеймер: Рамблер — большая компания, и описанный мной опыт относится только к конкретному отделу в конкретный период времени. С момента написания этой заметки многое изменилось в лучшую сторону.</em></p> <div class="sidenote"> <p class="sidenote__paragraph">Так вышло, что с 10 октября по 23 ноября 2016 года я работал фронтенд-разработчиком в отделе рекламных технологий <a href="https://rambler-co.ru/">Рамблера</a>.</p> <aside class="sidenote__note"> Впечатления от собеседования я описал в <a href="https://andrew-r.ru/notes/searching-for-job-2016">отдельной заметке</a> </aside> </div> <p>Помимо меня в отделе было ещё семь фронтендеров: один джун, один тимлид и пять мидлов.</p> <p>Я занимался поддержкой и развитием <a href="https://leto.rambler-co.ru/">«Лета»</a>, системы для размещения баннеров на площадках RAMBLER&Co. Система была написана на PHP, Реакте и Редаксе, ну и писалась она в довольно сжатые сроки (впрочем, так бывает всегда).</p> <h2>Про первый рабочий день</h2> <p>В первый рабочий день меня встретил местный эйчар, мы с ним получили бейджик-пропуск на ресепшене и отправились подписывать документы. Ознакомление со всеми нормативными актами, положениями и прочей бюрократией заняло около часа. После подписания всех бумажек мне выдали макбук и листок с конфиденциальными данными вроде паролей и ссылок (например, на местную соцсеть для сотрудников, в ленте которой можно было обнаружить объявления о продаже кроссовок).</p> <p><img src="https://andrew-r.ru/notes/work-at-rambler/assets/badge.png" alt="Бейдж-пропуск" /></p> <p>После получения всего необходимого оборудования меня, наконец, проводили к рабочему месту и познакомили с тимлидом и командой, после чего начались трудовые будни в <em>хорошей компании™</em>.</p> <h2>Про работу</h2> <p>Нормально развернуть проект не получилось. Ну, формально получилось, но на деле локально не грузилось большинство картинок, потому что картинки хранились не в какой-нибудь общедоступной CDN, а непонятно где. Зачем-то прямо в репозитории хранились билд-файлы и композеровские пакеты, и всё это ломалось на каждый чих. С фронтендом всё тоже было не в порядке. В проекте не было Автопрефиксера! А команда, которая писала проект, никогда раньше не работала с Реактом и Редаксом, что вылилось в огромные компоненты длиной более чем в тысячу строк, в дебрях которых напрямую изменялось состояние и делались другие страшные вещи, о которых я рассказывать не буду, иначе вы сегодня не заснёте. Тестов почти не было (ну, была парочка на весь проект).</p> <p>Кодревью чаще всего ограничивалось комментариями «ты здесь забыл убрать <code>console.log()</code>». Сомневаюсь, что я писал настолько хороший код. В Иннове мой тимлид (спасибо, Антон) всегда оставлял комментарии не только про код, но и про решение в целом (архитектура, масштабируемость и все дела).</p> <p>В целом от продукта оставалось неприятное ощущение — не только из-за кода, но и из-за дизайна и пользовательского опыта в целом. Орфографические и пунктуационные ошибки в интерфейсе были нормой. Чтобы их исправить, нужно было согласовывать обновлённый текст с менеджерами. Не было никакой стандартизации, кнопки были разных размеров, текст тоже, всё было хаотично разбросано по странице.</p> <p>Дизайнер, как оказалось, в нашем отделе был один, а проектов в отделе было штук пять. И, как мне сказали, он не был способен к стандартизации, каждая страница у него получалась уникальная и неповторимая. Вследствие всего этого макеты для некоторых задач делали менеджеры, а не дизайнер.</p> <p>Мой тимлид был чуть ли не единственным (кроме меня), кто понимал, что за задница здесь происходит. Он пытался постепенно менять всё в лучшую сторону, но он был один, а других сотрудников были десятки, и большинство из них были довольны текущим положением дел, поэтому всё двигалось довольно медленно и с болью. Он поддерживал меня во всех начинаниях, от рефакторинга до создания библиотеки компонентов, за что ему большое спасибо.</p> <h2>Про увольнение</h2> <p>Проработав чуть больше месяца, я уволился — по большей части из-за того, что я не был готов фактически в одиночку брать на себя ответственность за весь проект и успевать пилить продуктовые и инфраструктурные задачи. Если не принимать во внимание другие факторы, увольнение было довольно глупым решением — я испугался ответственности и сразу сдался, а мог бы переделать проект по-человечески (наверное, до сих пор в этом не уверен). Но если принимать во внимание другие факторы (такие как, например, офер от Авито), я об увольнении не жалею.</p> <p>Когда я сообщил о том, что собираюсь уходить, тимлид с техдиром долго уговаривали меня остаться и пытались понять, почему я ухожу. Я рассказал, что мне не понравилось и привёл в пример Яндекс и Авито, сказав, какие вещи там лучше. Кажется, я немного обидел техдира, когда он на вопрос «в Авито, по твоему мнению, специалисты опытнее наших?» получил ответ «да». Помимо прочего, мне предложили зарплату почти в полтора раза больше прежней, так что если вдруг захотите получать больше, сообщите начальнику, что собираетесь уволиться ;—).</p> <h2>Про условия работы в целом</h2> <p>Компания согласилась оплатить мой перелёт из Омска в Москву, что довольно неплохо. Правда по условиям трудового договора я был обязан вернуть деньги за билет, если проработаю в компании меньше года. В итоге выделенную на билет сумму просто вычли из моей последней зарплаты, ибо год я не проработал. Но и тут без фейлов не обошлось. Через какое-то время после увольнения мне позвонили и сказали, что вычли 10 000 ₽ (столько выделили на перелёт), а билет стоил 9 500 ₽, и, мол, мне нужно приехать в офис Рамблера и заполнить какую-то бумажку, чтобы получить обратно разницу.</p> <p>В компании есть собственная библиотека, но воспользоваться ей мне не довелось.</p> <p>Для сотрудников есть куча всевозможных скидок, их список хранится в большом гуглодоке. На деле оказалось, что половина скидок неактуальна, а большинство из них и вовсе не превышают 10%.</p> <p>Каждому разработчику выдают для работы макбук и внешний монитор.</p> <p><img src="https://andrew-r.ru/notes/work-at-rambler/assets/cookies.jpg" alt="Брендированные печеньки" /></p> <p>«Кофе, чай, печеньки» — не ведитесь на это. Печеньки бывали только с утра и расходились они буквально за полчаса. Зато однажды печеньки были с логотипом компании! Что касается чая и кофе, на нашем этаже была кухня с двумя кофемашинами и двумя чайниками. Кофемашины периодически нужно было очищать от кофейной гущи, а ещё в них заканчивалась вода и нужно было самому доливать её туда из огромной бутыли, используемой обычно в кулерах. Здесь, конечно, можно сказать «да ты совсем зажрался», но я просто сравниваю с Яндексом или Авито, в них почему-то о таких вещах беспокоиться не приходится.</p> <p>Где-то я слышал про бесплатные завтраки. Они оказались скидочной картой в соседнее кафе номиналом около 120 ₽ (точно не помню), на это можно было вроде как купить кашу и напиток. Эти карты были одноразовые, так что если вы желали завтракать каждый день, нужно было каждый день утром идти в другой корпус, чтобы получить карту.</p> <p><img src="https://andrew-r.ru/notes/work-at-rambler/assets/workplace.jpg" alt="Рабочее место" /></p> <p><img src="https://andrew-r.ru/notes/work-at-rambler/assets/openspace.jpg" alt="Опенспейс" /></p> <p>Рабочее пространство — опенспейс без перегородок между рабочими местами. Просто столы в ряд и разработчики с обеих сторон.</p> <p>Корпоративный английский был, но платить за него нужно было самому, а компания предоставляла определённую скидку. Ну и занятия велись в группах.</p> <p>Митапы и конференции можно было посещать в счёт рабочего времени. Компания даже оплачивала билет на конференцию при обосновании необходимости посещения этой конференции.</p> <h2>Результаты</h2> <p>За время работы я исправил n-ое количество багов, отрефакторил кучу кода, запилил несколько небольших продуктовых задач и заложил основу для библиотеки реиспользуемых UI-компонентов. Библиотека компонентов была самой интересной задачей, о ней я расскажу подробнее в отдельной заметке.</p>Минутка рефакторинга № 12017-03-17T18:34:00Zhttps://andrew-r.ru/notes/minute-of-refactoring-01/<p>Я тут подумал, что неплохо было бы раз в неделю-две делать публичное ревью кода, дабы делиться знаниями и практиковаться в улучшении кода. Я закинул удочку в <a href="https://vk.com/forwebdev?w=wall-66170841_50998">Форвеб</a> и первым прислать код на ревью не побоялся <a href="https://vk.com/melodyn">Сергей Мелодин</a>. Спасибо, Сергей! Перейдём к ревью.</p> <p>Задача:</p> <p><em>Раз в t секунд последовательно выводить в консоль порции по n значений из массива, пока не будут выведены все элементы.</em></p> <p>Исходное решение:</p> <pre><code class="language-javascript"><span class="hljs-keyword">var</span> myArr = [<span class="hljs-string">"Lorem"</span>, <span class="hljs-string">"ipsum"</span>, <span class="hljs-string">"dolor"</span>, <span class="hljs-string">"sit"</span>, <span class="hljs-string">"amet"</span>, <span class="hljs-string">"consectetur"</span>, <span class="hljs-string">"adipiscing"</span>, <span class="hljs-string">"elit"</span>, <span class="hljs-string">"sed"</span>, <span class="hljs-string">"do"</span>, <span class="hljs-string">"eiusmod"</span>, <span class="hljs-string">"tempor"</span>, <span class="hljs-string">"incididunt"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"labore"</span>, <span class="hljs-string">"et"</span>, <span class="hljs-string">"dolore"</span>, <span class="hljs-string">"magna"</span>, <span class="hljs-string">"aliqua"</span>, <span class="hljs-string">"Ut"</span>, <span class="hljs-string">"enim"</span>, <span class="hljs-string">"ad"</span>, <span class="hljs-string">"minim"</span>, <span class="hljs-string">"veniam"</span>, <span class="hljs-string">"quis"</span>, <span class="hljs-string">"nostrud"</span>, <span class="hljs-string">"exercitation"</span>, <span class="hljs-string">"ullamco"</span>, <span class="hljs-string">"laboris"</span>, <span class="hljs-string">"nisi"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"aliquip"</span>, <span class="hljs-string">"ex"</span>, <span class="hljs-string">"ea"</span>, <span class="hljs-string">"commodo"</span>, <span class="hljs-string">"consequat"</span>]; <span class="hljs-keyword">var</span> i = myArr.<span class="hljs-property">length</span>; <span class="hljs-keyword">var</span> x = <span class="hljs-number">0</span>; <span class="hljs-keyword">var</span> step = <span class="hljs-number">10</span>; <span class="hljs-comment">// шаг смещения</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">offset</span>(<span class="hljs-params">x</span>){ <span class="hljs-keyword">var</span> y = x; <span class="hljs-keyword">while</span>(x<y+step && x<i){ <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(myArr[x]); x++; }; }; <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>){ <span class="hljs-title function_">offset</span>(x); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); x = x+step; <span class="hljs-keyword">if</span>(x>i){ <span class="hljs-built_in">clearInterval</span>(timer) }; }; <span class="hljs-keyword">var</span> timer = <span class="hljs-built_in">setInterval</span>(start, <span class="hljs-number">1000</span>); </code></pre> <p>Начнём с того, что тестовые данные можно сгенерировать динамически, благо значения элементов массива нам не важны:</p> <pre><code class="language-javascript"><span class="hljs-comment">// было</span> <span class="hljs-keyword">var</span> myArr = [<span class="hljs-string">"Lorem"</span>, <span class="hljs-string">"ipsum"</span>, <span class="hljs-string">"dolor"</span>, <span class="hljs-string">"sit"</span>, <span class="hljs-string">"amet"</span>, <span class="hljs-string">"consectetur"</span>, <span class="hljs-string">"adipiscing"</span>, <span class="hljs-string">"elit"</span>, <span class="hljs-string">"sed"</span>, <span class="hljs-string">"do"</span>, <span class="hljs-string">"eiusmod"</span>, <span class="hljs-string">"tempor"</span>, <span class="hljs-string">"incididunt"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"labore"</span>, <span class="hljs-string">"et"</span>, <span class="hljs-string">"dolore"</span>, <span class="hljs-string">"magna"</span>, <span class="hljs-string">"aliqua"</span>, <span class="hljs-string">"Ut"</span>, <span class="hljs-string">"enim"</span>, <span class="hljs-string">"ad"</span>, <span class="hljs-string">"minim"</span>, <span class="hljs-string">"veniam"</span>, <span class="hljs-string">"quis"</span>, <span class="hljs-string">"nostrud"</span>, <span class="hljs-string">"exercitation"</span>, <span class="hljs-string">"ullamco"</span>, <span class="hljs-string">"laboris"</span>, <span class="hljs-string">"nisi"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"aliquip"</span>, <span class="hljs-string">"ex"</span>, <span class="hljs-string">"ea"</span>, <span class="hljs-string">"commodo"</span>, <span class="hljs-string">"consequat"</span>]; <span class="hljs-comment">// стало</span> <span class="hljs-keyword">var</span> myArr = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); </code></pre> <p>Причёсываем код для лучшей читаемости (исправляем отступы и добавляем недостающие пробелы). Выносим интервал в отдельную переменную для гибкости. Интервал и количество выводимых значений за одну итерацию обозначаем прописными буквами, как константы. Количество выводимых за одну итерацию значений при этом переименовываем из <code>step</code> в <code>ITEMS_PER_ITERATION</code>. Заменяем <code>var</code> на <code>const</code> или <code>let</code> в зависимости от того, переприсваивается ли переменная в дальнейшем. Убираем точки с запятой после объявления функций, циклов и условий, они там не нужны. Переименовываем <code>timer</code> в <code>intervalId</code>, что точнее отражает суть значения. Избавляемся от микрооптимизации в виде сохранения длины <code>myArr</code> в переменную <code>i</code> — во-первых, название переменной никак не отражает её суть, во-вторых, преждевременная оптимизация лишь портит читаемость и усложняет код:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span> = <span class="hljs-number">10</span>; <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">INTERVAL</span> = <span class="hljs-number">1000</span>; <span class="hljs-keyword">const</span> intervalId = <span class="hljs-built_in">setInterval</span>(start, <span class="hljs-variable constant_">INTERVAL</span>); <span class="hljs-keyword">const</span> myArr = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); <span class="hljs-keyword">let</span> x = <span class="hljs-number">0</span>; <span class="hljs-keyword">function</span> <span class="hljs-title function_">offset</span>(<span class="hljs-params">x</span>) { <span class="hljs-keyword">const</span> y = x; <span class="hljs-keyword">while</span>(x < y + <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span> && x < myArr.<span class="hljs-property">length</span>){ <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(myArr[x]); x++; } } <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">offset</span>(x); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); x = x + <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span>; <span class="hljs-keyword">if</span> (x > myArr.<span class="hljs-property">length</span>) { <span class="hljs-built_in">clearInterval</span>(intervalId); } } </code></pre> <p>Сейчас есть две функции <code>start()</code> и <code>offset()</code>, с первого взгляда непонятно, что они делают. Самое ужасное — они обе изменяют переменную <code>x</code>. Но не всё так просто! На первый взгляд может показаться, что они изменяют одну и ту же переменную. Однако в функции <code>offset()</code> <code>x</code> — это аргумент, и он создаёт локальную переменную <code>x</code>. Такие переменные называются <em>связанными</em> — они непосредственно связаны с функцией, в которой они используются. Если бы у <code>offset()</code> не было аргументов, мы бы работали с внешней переменной <code>x</code>. Если переменная не связана, она называется <em>свободной</em>.</p> <p>Итак, избавимся от излишней сложности со свободными и связанными переменными. Функция <code>offset()</code> по сути должна выводить в консоль <code>ITEMS_PER_ITERATION</code> элементов массива. Изменим её <em>сигнатуру</em> — аргумент <code>x</code> заменим на <code>array</code>, а саму функцию переименуем в <code>logArrayItems</code>. Ну и исправим функцию <code>start()</code>, чтобы она правильно работала с <code>logArrayItems</code>:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span> = <span class="hljs-number">10</span>; <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">INTERVAL</span> = <span class="hljs-number">1000</span>; <span class="hljs-keyword">const</span> intervalId = <span class="hljs-built_in">setInterval</span>(start, <span class="hljs-variable constant_">INTERVAL</span>); <span class="hljs-keyword">const</span> myArr = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); <span class="hljs-keyword">let</span> x = <span class="hljs-number">0</span>; <span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItems</span>(<span class="hljs-params">array</span>) { array.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); } <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">logArrayItems</span>(myArr.<span class="hljs-title function_">slice</span>(x, x + <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span>)); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); x = x + <span class="hljs-variable constant_">ITEMS_PER_ITERATION</span>; <span class="hljs-keyword">if</span> (x > myArr.<span class="hljs-property">length</span>) { <span class="hljs-built_in">clearInterval</span>(intervalId); } } </code></pre> <p>Теперь выглядит попроще, но всё далеко от идеала. Наше решение не выглядит целостным. Чтобы это исправить, обернём его в функцию <code>logArrayItemsSequentially</code>, которая будет принимать массив, элементы которого надо вывести в консоль, и настройки — количество выводимых за итерацию элементов и интервал в миллисекундах. Хорошей практикой считается обязательные параметры передавать в функцию как есть, а все опциональные параметры передавать в виде объекта (при этом опциональным параметрам нужно задать значения по умолчанию). Это позволяет сократить код при обычном использовании функции, но не потерять гибкость и оставить возможность переопределения настроек:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItemsSequentially</span>(<span class="hljs-params">array, options = {}</span>) { <span class="hljs-keyword">const</span> { itemsPerIteration = <span class="hljs-number">10</span>, interval = <span class="hljs-number">1000</span>, } = options; <span class="hljs-keyword">const</span> intervalId = <span class="hljs-built_in">setInterval</span>(start, interval); <span class="hljs-keyword">let</span> x = <span class="hljs-number">0</span>; <span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItems</span>(<span class="hljs-params">array</span>) { array.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); } <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">logArrayItems</span>(array.<span class="hljs-title function_">slice</span>(x, x + itemsPerIteration)); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); x = x + itemsPerIteration; <span class="hljs-keyword">if</span> (x > array.<span class="hljs-property">length</span>) { <span class="hljs-built_in">clearInterval</span>(intervalId); } } } <span class="hljs-keyword">const</span> sampleArray = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); <span class="hljs-title function_">logArrayItemsSequentially</span>(sampleArray<span class="hljs-comment">/*, здесь могли быть переопределённые настройки */</span>); </code></pre> <p>Отлично, теперь у нас есть целостное решение — функция, которую можно даже опубликовать на NPM (но лучше не надо). Снаружи её использование выглядит просто и понятно. Наведём порядок изнутри.</p> <p>Для начала избавимся от функции <code>logArrayItems</code> — она используется лишь в одном месте:</p> <pre><code class="language-javascript"><span class="hljs-comment">// было</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItems</span>(<span class="hljs-params">array</span>) { array.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); } <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">logArrayItems</span>(array.<span class="hljs-title function_">slice</span>(x, x + itemsPerIteration)); <span class="hljs-comment">// ...</span> } <span class="hljs-comment">// стало</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>) { array.<span class="hljs-title function_">slice</span>(x, x + itemsPerIteration).<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); <span class="hljs-comment">// ...</span> } </code></pre> <p>У нас всё ещё остаётся изменяемая переменная-счётчик, что не очень хорошо. Изменение значений переменных всегда усложняет чтение и понимание кода, а в некоторых случаях усложняет тестирование.</p> <p>Давайте рассмотрим суть нашего алгоритма. Наш алгоритм работает <em>итеративно</em> — каждый раз он выводит в консоль порцию массива и переходит к остальным элементам, повторяя те же действия, пока все элементы массива не будут выведены на экран. Сейчас такое поведение реализовано с помощью <code>setInterval</code> и счётчика <code>x</code>, указывающего на индекс первого не выведенного в консоль элемента массива.</p> <p>Такое же поведение мы можем реализовать намного проще. Нам не нужны вложенные функции, счётчики и <code>setInterval</code> — нам нужны рекурсия и <code>setTimeout</code>! Тело функции <code>logArrayItemsSequentially</code> будет обрабатывать только одну итерацию, а переход к следующей итерации будет делаться рекурсивным вызовом <code>logArrayItemsSequentially</code>. Разберём решение:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItemsSequentially</span>(<span class="hljs-params">array, options = {}</span>) { <span class="hljs-keyword">const</span> { itemsPerIteration = <span class="hljs-number">10</span>, interval = <span class="hljs-number">1000</span>, } = options; <span class="hljs-comment">// элементы, которые нужно вывести в консоль на текущей итерации</span> <span class="hljs-keyword">const</span> currentPortion = array.<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, itemsPerIteration); <span class="hljs-comment">// оставшиеся элементы</span> <span class="hljs-keyword">const</span> tail = array.<span class="hljs-title function_">slice</span>(itemsPerIteration); <span class="hljs-comment">// сразу делаем вывод в консоль</span> currentPortion.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); <span class="hljs-comment">// если остаток не пустой, передаём его в следующую итерацию,</span> <span class="hljs-comment">// выполнение которой откладываем с помощью setTimeout</span> <span class="hljs-keyword">if</span> (tail.<span class="hljs-property">length</span>) { <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> <span class="hljs-title function_">logArrayItemsSequentially</span>(tail, options), interval); } } <span class="hljs-keyword">const</span> sampleArray = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); <span class="hljs-title function_">logArrayItemsSequentially</span>(sampleArray<span class="hljs-comment">/*, здесь могли быть переопределённые настройки */</span>); </code></pre> <p>На мой взгляд, на этом можно остановиться. Мы получили универсальное и простое решение. Для наглядности приведу исходное и отрефакторенное решения:</p> <pre><code class="language-javascript"><span class="hljs-comment">/** * Исходное решение */</span> <span class="hljs-keyword">var</span> myArr = [<span class="hljs-string">"Lorem"</span>, <span class="hljs-string">"ipsum"</span>, <span class="hljs-string">"dolor"</span>, <span class="hljs-string">"sit"</span>, <span class="hljs-string">"amet"</span>, <span class="hljs-string">"consectetur"</span>, <span class="hljs-string">"adipiscing"</span>, <span class="hljs-string">"elit"</span>, <span class="hljs-string">"sed"</span>, <span class="hljs-string">"do"</span>, <span class="hljs-string">"eiusmod"</span>, <span class="hljs-string">"tempor"</span>, <span class="hljs-string">"incididunt"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"labore"</span>, <span class="hljs-string">"et"</span>, <span class="hljs-string">"dolore"</span>, <span class="hljs-string">"magna"</span>, <span class="hljs-string">"aliqua"</span>, <span class="hljs-string">"Ut"</span>, <span class="hljs-string">"enim"</span>, <span class="hljs-string">"ad"</span>, <span class="hljs-string">"minim"</span>, <span class="hljs-string">"veniam"</span>, <span class="hljs-string">"quis"</span>, <span class="hljs-string">"nostrud"</span>, <span class="hljs-string">"exercitation"</span>, <span class="hljs-string">"ullamco"</span>, <span class="hljs-string">"laboris"</span>, <span class="hljs-string">"nisi"</span>, <span class="hljs-string">"ut"</span>, <span class="hljs-string">"aliquip"</span>, <span class="hljs-string">"ex"</span>, <span class="hljs-string">"ea"</span>, <span class="hljs-string">"commodo"</span>, <span class="hljs-string">"consequat"</span>]; <span class="hljs-keyword">var</span> i = myArr.<span class="hljs-property">length</span>; <span class="hljs-keyword">var</span> x = <span class="hljs-number">0</span>; <span class="hljs-keyword">var</span> step = <span class="hljs-number">10</span>; <span class="hljs-comment">// шаг смещения</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">offset</span>(<span class="hljs-params">x</span>){ <span class="hljs-keyword">var</span> y = x; <span class="hljs-keyword">while</span>(x<y+step && x<i){ <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(myArr[x]); x++; }; }; <span class="hljs-keyword">function</span> <span class="hljs-title function_">start</span>(<span class="hljs-params"></span>){ <span class="hljs-title function_">offset</span>(x); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); x = x+step; <span class="hljs-keyword">if</span>(x>i){ <span class="hljs-built_in">clearInterval</span>(timer) }; }; <span class="hljs-keyword">var</span> timer = <span class="hljs-built_in">setInterval</span>(start, <span class="hljs-number">1000</span>); <span class="hljs-comment">/** * Отрефакторенное решение */</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">logArrayItemsSequentially</span>(<span class="hljs-params">array, options = {}</span>) { <span class="hljs-keyword">const</span> { itemsPerIteration = <span class="hljs-number">10</span>, interval = <span class="hljs-number">1000</span>, } = options; <span class="hljs-keyword">const</span> currentPortion = array.<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, itemsPerIteration); <span class="hljs-keyword">const</span> tail = array.<span class="hljs-title function_">slice</span>(itemsPerIteration); currentPortion.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item)); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'pause'</span>); <span class="hljs-keyword">if</span> (tail.<span class="hljs-property">length</span>) { <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> <span class="hljs-title function_">logArrayItemsSequentially</span>(tail, options), interval); } } <span class="hljs-keyword">const</span> sampleArray = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">36</span> }, <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> index); <span class="hljs-title function_">logArrayItemsSequentially</span>(sampleArray<span class="hljs-comment">/*, здесь могли быть переопределённые настройки */</span>); </code></pre>Зависимости в компонентном вебе2017-04-16T18:44:00Zhttps://andrew-r.ru/notes/deps-in-component-web/<p>14 апреля я побывал в Екатеринбурге на конференции DUMP 2017. Больше всего мне понравился доклад <a href="https://twitter.com/tadatuta">Владимира Гриненко</a> об управлении зависимостями в компонентном вебе, так что я вкратце перескажу здесь его суть.</p> <p>Компонентный подход довольно распространён. Бутстрап, БЭМ, Реакт — всё это про компоненты, из которых строится интерфейс. Компоненты обычно состоят из скриптов и стилей. Если мы хотим использовать какой-либо компонент, нам нужно импортировать его логику и стили:</p> <pre><code class="language-javascript"><span class="hljs-comment">/* application.js */</span> <span class="hljs-keyword">import</span> <span class="hljs-title class_">Button</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/button.js'</span>; <span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/link.js'</span>; </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* application.css */</span> <span class="hljs-keyword">@import</span> <span class="hljs-string">'../../ui/button.css'</span>; <span class="hljs-keyword">@import</span> <span class="hljs-string">'../../ui/link.css'</span>; </code></pre> <p>Недостатки такого подхода:</p> <ul> <li>многословность;</li> <li>на каждую технологию (скрипты, стили, etc) нужно писать свои импорты;</li> <li>пути к файлам захардкожены, меняется путь — нужно переписывать импорты.</li> </ul> <p>При таком подходе мы делаем много ручной работы, которую за нас <span class="nobr">по-хорошему</span> должен делать сборщик проекта. В идеале хотелось бы просто сообщать сборщику, какие компоненты мы будем использовать, чтобы он сам находил и подключал их файлы.</p> <p>Владимир предлагает более простой и декларативный подход, избавляющий нас от вышеперечисленных проблем и дающий дополнительные преимущества. Этот подход — декларация зависимостей в терминах компонентов, а не в терминах конкретных файлов с реализацией. Избавляемся от всего лишнего, оставляем суть:</p> <pre><code class="language-javascript"><span class="hljs-comment">/* было, application.js */</span> <span class="hljs-keyword">import</span> <span class="hljs-title class_">Button</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/button.js'</span>; <span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/link.js'</span>; <span class="hljs-comment">/* было, application.css */</span> @<span class="hljs-keyword">import</span> <span class="hljs-string">"../../ui/button.css"</span>; @<span class="hljs-keyword">import</span> <span class="hljs-string">"../../ui/link.css"</span>; <span class="hljs-comment">/* стало, application.decl.js */</span> [<span class="hljs-string">'button'</span>, <span class="hljs-string">'link'</span>] </code></pre> <p>Теперь нам не нужно указывать, где конкретно находятся файлы зависимостей, и не нужно импортировать файлы для каждой технологии. Мы просто указываем, какие компоненты хотим использовать, а сборщик уже сам ищет и подключает нужные файлы.</p> <h2>Алгебра деклараций</h2> <p>Декларации зависимостей можно легко объединять, вычитать и находить их пересечение. Например, это может быть полезно для выноса общих зависимостей в один файл:</p> <pre><code class="language-javascript"><span class="hljs-comment">/* login.js */</span> [<span class="hljs-string">'header'</span>, <span class="hljs-string">'input'</span>, <span class="hljs-string">'checkbox'</span>, <span class="hljs-string">'button'</span>, <span class="hljs-string">'footer'</span>][ <span class="hljs-comment">/* landing.js */</span> (<span class="hljs-string">'header'</span>, <span class="hljs-string">'slider'</span>, <span class="hljs-string">'gallery'</span>, <span class="hljs-string">'button'</span>, <span class="hljs-string">'footer'</span>) ][ <span class="hljs-comment">/* пересечение, которое можно вынести в common.js */</span> (<span class="hljs-string">'header'</span>, <span class="hljs-string">'button'</span>, <span class="hljs-string">'footer'</span>) ]; </code></pre> <h2>Композиция</h2> <p>Зависимости между компонентами тоже можно объявлять в виде деклараций. Было:</p> <pre><code class="language-javascript"><span class="hljs-comment">/* header.js */</span> <span class="hljs-keyword">import</span> <span class="hljs-title class_">Button</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/button.js'</span>; <span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../../ui/link.js'</span>; <span class="hljs-comment">// ...</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Header</span> {} </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* header.css */</span> <span class="hljs-keyword">@import</span> <span class="hljs-string">'../../ui/button.css'</span>; <span class="hljs-keyword">@import</span> <span class="hljs-string">'../../ui/link.css'</span>; </code></pre> <p>Стало:</p> <pre><code class="language-javascript"><span class="hljs-comment">/* header.deps.js */</span> [<span class="hljs-string">'button'</span>, <span class="hljs-string">'link'</span> <span class="hljs-comment">/*, ... */</span>]; <span class="hljs-comment">/* header.js */</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Header</span> {} </code></pre> <h2>Декларативное множественное наследование</h2> <p>Здесь уже начинаются страшные и со стороны непонятные БЭМ-штуки, которые, тем не менее, оказываются очень полезными и удобными, если в них разобраться.</p> <p>В БЭМе есть модификаторы, позволяющие применять декларативное множественное наследование для компонентов. Например, кнопка может быть представлена в разных вариациях: синяя, большая, задизейбленная, с иконкой. Кнопка может находиться одновременно во всех этих состояниях. Эти состояния в БЭМе выражаются через модификаторы. Каждая модификация блока хранится отдельно от основной реализации. Подход декларации зависимостей в терминах компонентов избавляет от необходимости импортирования нужных модификаций блока: сборщик сам может определить и подключить используемые модификации блока.</p> <h2>Уровни переопределения</h2> <p>Ещё одна мощнейшая штука, которая есть в БЭМе — уровни переопределения. Наглядная демонстрация принципа:</p> <p><img src="https://andrew-r.ru/notes/deps-in-component-web/assets/redefinition-levels.png" alt="Пример уровней переопределения" /></p> <p>Расписывать здесь суть уровней переопределения я не стану, ибо она уже описана в документации БЭМа. Приведу реальный случай, в котором уровни переопределения сильно облегчают жизнь.</p> <p>Яндекс любит проверять разные продуктовые гипотезы и проводить эксперименты. Например, команда поиска может выдвинуть такую гипотезу: «пользователи станут чаще переходить по рекламным ссылкам, если рекламу показывать на красном фоне». Чтобы проверить эту гипотезу, нужно провести А/Б-тестирование, то есть одной половине пользователей показать рекламупо-старому, а другой половине показать рекламу на красном фоне. После этого нужно сравнить результаты и проверить, действительно ли во втором случае пользователи кликали чаще.</p> <p>Скорее всего, вы бы решили эту задачу так: в коде, отвечающем за показ рекламного блока, добавили бы условие наподобие «если пользователь входит в такую-то группу, добавляем сюда красный фон». Проблема в том, что таких экспериментов может быть очень много, и при таком подходе весь ваш код будет состоять из сплошных условий, которые ещё и нужно не забывать удалять. Явно немасштабируемый подход.</p> <p>Вместо написания очередного условия можно просто создать новый уровень переопределения, на котором будет добавляться красный фон. Каждый уровень переопределения хранится отдельно от основной реализации, благодаря этому при окончании эксперимента можно просто удалить файлы с уровнем эксперимента и не трогать исходники.</p> <p>Возвращаясь к теме декларации зависимостей, в случае с уровнями переопределения мы опять же выигрываем:</p> <pre><code class="language-css"><span class="hljs-comment">/* было */</span> <span class="hljs-keyword">@import</span> <span class="hljs-string">'common/button.css'</span>; <span class="hljs-keyword">@import</span> <span class="hljs-string">'project/button.css'</span>; <span class="hljs-keyword">@import</span> <span class="hljs-string">'experiment/button.css'</span>; </code></pre> <pre><code class="language-javascript"><span class="hljs-comment">/* стало */</span> [<span class="hljs-string">'button'</span>]; </code></pre> <h2>А как это использовать?</h2> <p>Для Реакта есть <a href="https://github.com/bem/bem-react-core">bem-react-core</a>, для всего остального есть <a href="https://github.com/bem-sdk">bem-sdk</a>.</p>Почему в проектах 2017 года не нужна jQuery?2017-06-05T09:22:00Zhttps://andrew-r.ru/notes/2017-06-05-jquery/<p>Потому что:</p> <ul> <li>для работы с DOM есть, как ни странно, <a href="https://dom.spec.whatwg.org/">спецификация DOM4</a> с <code>.closest()</code>, <code>.append()</code>, <code>.prepend()</code> и другими удобными методами (<a href="http://webreflection.github.io/dom4">полифил</a>);</li> <li>для анимаций есть CSS и <a href="https://w3c.github.io/web-animations">Web Animations API</a> (<a href="https://github.com/web-animations/web-animations-js">полифил</a>);</li> <li>для общения с сервером есть <a href="https://fetch.spec.whatwg.org/">fetch</a> (<a href="https://github.com/github/fetch">полифил</a>);</li> <li>готовых библиотек на чистом JS предостаточно (<a href="https://plainjs.com/">plainjs.com</a>, <a href="http://microjs.com/">microjs.com</a>), и чаще всего они легковеснее и качественнее jQuery-плагинов.</li> </ul>Что за атрибут inert и зачем он нужен?2017-06-22T19:22:00Zhttps://andrew-r.ru/notes/tldr-inert/<p>Как можно догадаться из названия, этот атрибут помечает элемент как инертный (или неактивный, но не путайте с <code>disabled</code>). Для таких элементов (и всего дерева их потомков) отключается срабатывание пользовательских событий (например, фокус по нажатию tab, выделение текста, клики). Ассистивные технологии вроде экранных читалок просто игнорируют такие элементы. Также <a href="https://html.spec.whatwg.org/multipage/interaction.html#inert">спецификация</a> рекомендует разработчикам браузеров игнорировать инертные элементы при поиске по содержимому страницы.</p> <p>По сути, этот атрибут сочетает в себе поведение <code>tabindex="-1"</code>, <code>aria-hidden</code> и <code>pointer-events: none</code>. Им следует помечать скрытые модальные окна, выпадающие меню, невидимые слайды карусели и другие подобные элементы интерфейса. Это улучшит доступность ваших интерфейсов: при навигации с клавиатуры или при использовании экранных читалок инертные элементы просто будут игнорироваться.</p> <p>Полезные ссылки:</p> <p>— <a href="https://html.spec.whatwg.org/multipage/interaction.html#inert">атрибут <code>inert</code> в спецификации whatwg</a>; — <a href="https://github.com/wicg/inert">история появления, способы применения, описание пробелов в спецификации и полифил</a>; — <a href="https://youtu.be/fGLp_gfMMGU">выпуск A11ycasts с Робом Додсоном, посвящённый атрибуту <code>inert</code></a>.</p>Визуальная семантика, или чем разметка отличается от стилей2017-07-25T15:43:00Zhttps://andrew-r.ru/notes/visual-semantics/<p>Иногда новички во фронтенде не понимают истинного предназначения разметки (HTML) и стилей (CSS). Однажды в комментариях к одной из публикаций <a href="https://vk.com/forwebdev">Форвеба</a> возник вопрос:</p> <blockquote> <p>Что в 2017 лучше использовать для вёрстки таблиц, <span class="nobr">HTML-таблицы</span> или <span class="nobr">CSS-гриды?</span></p> </blockquote> <p>Что не так с этим вопросом? Давайте разберёмся.</p> <h2>Разметка — про семантику</h2> <p>С точки зрения компьютера текст это просто последовательность символов. HTML создан для того, чтобы дать компьютеру больше информации о структуре текста. <em>Семантичная разметка</em> — такая разметка, которая позволяет компьютеру исходную последовательность символов разобрать на смысловые единицы вроде секций, заголовков, списков, таблиц, форм.</p> <h2>Стили — про внешний вид</h2> <p>Голый текст это хорошо, но ещё лучше, когда его можно оформить — задать размер шрифта и интерлиньяж, ограничить ширину, выделить заголовки, покрасить ссылки в нужный цвет. За всю эту внешнюю красоту отвечают стили (CSS), и они чаще всего никак не влияют на семантику, то есть на смысл текста. Они лишь дают возможность изменить его внешний вид.</p> <p>Хороший пример — <a href="http://www.csszengarden.com/">CSS Zen Garden</a>. Один и тот же <span class="nobr">HTML-документ</span>, смысл которого не меняется, оформлен более чем двумястами разными способами.</p> <h2>Если разметка не отвечает за визуальное представление, что в HTML делают элементы вроде <code><i></code> или <code><strong></code>?</h2> <p>Дело в том, что эти элементы тоже несут смысл. Например, так определяется элемент <code><strong></code> в спецификации HTML:</p> <blockquote> <p>Элемент <code><strong></code> означает важность, серьёзность или срочность его содержимого.</p> </blockquote> <p>Заметьте, ни слова не сказано о выделении содержимого жирным начертанием шрифта. Жирное начертание для <code><b></code> и <code><strong></code>, фоновое выделение для <code><mark></code> и другое подобное оформление задаётся стандартыми стилями браузера (<span class="nobr">user-agent</span> stylesheet). Эти стили никак не относятся к семантике разметки. Более того, в разных условиях одни и те же HTML-элементы могут быть визуально представлены <span class="nobr">по-разному</span> (например, элементы форм на Windows и macOS). Несмотря на разный внешний вид, кнопка на Windows и кнопка на macOS несут один и тот же смысл.</p> <h2>Разделяйте семантику и представление</h2> <p>Вернёмся к вопросу из начала статьи:</p> <blockquote> <p>Что в 2017 лучше использовать для вёрстки таблиц, <span class="nobr">HTML-таблицы</span> или <span class="nobr">CSS-гриды</span>?</p> </blockquote> <p>Таблицы нужно размечать как таблицы, то есть тегами <code><table></code>, <code><td></code> и так далее. Как они будут выглядеть на экране у пользователя — дело ваше, вы полностью контролируете их внешний вид через стили. Ничего не мешает разметить <span class="nobr">HTML-таблицу</span> и стилизовать её <span class="nobr">CSS-гридами</span> или флексами.</p> <p>Запомните: HTML — про семантику, CSS — про внешний вид.</p>Какие каналы читать в Телеграме2017-09-16T12:42:00Zhttps://andrew-r.ru/notes/telegram-channels/<p class="subtitle">Делюсь списком Телеграм-каналов, которые я с интересом читаю. Разработка, дизайн, языки, редактура, журналистика, путешествия, разумность.</p> <p><a href="https://t.me/forwebdev">For Web</a> — полезности для разработчиков интерфейсов: фронтенд, дизайн и программирование. Признаюсь, слукавил: этот канал я не читаю, я в него пишу. Кстати, ещё я пишу о фронтенде и не только в свой <a href="https://t.me/andrew_r_notes">канал с заметками</a>.</p> <p><a href="https://t.me/webstandards_ru">Веб-стандарты</a> — ежедневные новости и события фронтенда.</p> <p><a href="https://t.me/breakfastjs">Breakfast.js</a> — ежедневная утренняя порция фронтенд-новостей от Дмитрия Мананникова.</p> <p><a href="https://t.me/devSchachtChannel">devSchachtChannel</a> — анонсы новых статей и подкастов от devSchacht.</p> <p><a href="https://t.me/iamakulov_channel">Иван Акулов про фронтенд</a> — заметки про фронтенд, UX и смежные темы.</p> <p><a href="https://t.me/webo_ru">webo_ru</a> — Виталий Харисов из Яндекса об оптимизации сайтов. Есть <a href="https://t.me/webo_en">английская версия</a>.</p> <p><a href="https://t.me/codehipsters">Code Hipsters</a> — «квинтэссенция людей и технологий, призванная разрушать стереотипы». Фронтенд, машинное обучение, большие данные, философия программирования.</p> <p><a href="https://t.me/teamleading">Про руководство разработчиками</a> — руководитель службы разработки интерфейсов Олег Мохов из екатеринбуржского Яндекса о своей работе и её особенностях.</p> <p><a href="https://t.me/ask_catwomenko">HR отвечает</a> — Вероника Ильина о поиске работы, хороших резюме, собеседованиях и специфике работы эйчаров.</p> <p><a href="https://t.me/PROprgmr">Всё о программировании (с Козулей)</a> — Владислав Козуля о карьере, стартапах и своём опыте разработки.</p> <p><a href="https://t.me/isqualog">isqualog</a> — Софья Ильинова о фронтенде, дизайне, работе и жизни.</p> <p><a href="https://t.me/internet9000">Internet 9000</a> — Сергей Сурганов о дизайне и технологиях.</p> <p><a href="https://t.me/techsparks">TechSparks</a> — Андрей Себрант с новостями хайтека.</p> <p><a href="https://t.me/emaildev">Записки Коха</a> — Артур Кох о email-разработке и смежных темах.</p> <p><a href="https://t.me/evilmartians">Evil Martians</a> — Злые марсиане о стартапах, веб-разработке, интернет-бизнесе, бэкенде, фронтенде, мобильной разработке, devops и data science.</p> <p><a href="https://t.me/ctodaily">запуск завтра</a> — техдир «Медузы» Самат Галимов о своих буднях и технологиях.</p> <p><a href="https://t.me/dmitriisorin">Про Сидней/Atlassian</a> — Дмитрий Сорин об опыте переезда в Австралию и работе в Atlassian.</p> <p><a href="https://t.me/dangry">Интерфейсы без шелухи</a> — Антон Жиянов о продуктоводстве, интерфейсах, здравом смысле и разработке софта.</p> <p><a href="https://t.me/priunil">Чёт приуныл</a> — Ян Хацкевич с анонсами новых статей из своего личного блога о дизайне.</p> <p><a href="https://t.me/design_without_cats">Design without cats</a> — Андрей Болонев о дизайне без котиков. Мысли, цитаты из статей, реальные кейсы.</p> <p><a href="https://t.me/desprod">Design & Productivity</a> — Костя Горский про дизайн, продуктивность и жизнь. Мало ссылок и много мыслей.</p> <p><a href="https://t.me/govdesign">GOV</a> — о дизайне государства.</p> <p><a href="https://t.me/ilyabirman_channel">Канал Ильи Бирмана</a> — заметки, находки и советы о дизайне.</p> <p><a href="https://t.me/ldwg_channel">Л</a> — Людвиг Быстроновский о дизайне, искусстве, продуктивности.</p> <p><a href="https://t.me/bureaugorbunov">Бюро Горбунова</a> — дизайн-советы, наблюдения и книги.</p> <p><a href="https://t.me/proproduct">No Flame No Game</a> — Аня Булдакова о продуктовом менеджменте.</p> <p><a href="https://t.me/glvrdru">Главред</a> — Максим Ильяхов с советами и статьями о тексте, редактуре, информационном стиле и рекламе.</p> <p><a href="https://t.me/gzombify">ГЗОМ</a> — о тексте, тонкостях и пунктирностях русского языка, грамматике и стилистике, работе редактора, работе мозга — затейливо и с внятной аргументацией.</p> <p><a href="https://t.me/milchinchannel">Мильчин-канал</a> — инъекции «Справочника издателя и автора» Аркадия Мильчина и Людмилы Чельцовой. Канал для редакторов, дизайнеров и зануд.</p> <p><a href="https://t.me/ispravil">Исправил</a> — Никита Юкович о русском языке и о том, как не ошибаться.</p> <p><a href="https://t.me/angrytranslator">Angry Translator</a> — Даниил Орловский о тонкостях английской грамматики, типичных и нетипичных ошибках, лексике.</p> <p><a href="https://t.me/srazunet">Сразу нет</a> — Иван Колпаков из «Медузы» о журналистике.</p> <p><a href="https://t.me/mindfool">Разумная внимательность</a> — Рахим Давлеткалиев об осознанности, медитации, разуме и реальности.</p> <p><a href="https://t.me/za_bugrom">За бугром</a> — размышления иммигранта о жизни в США.</p> <p><a href="https://t.me/planktonchallenge">Планктон челлендж</a> — вся Европа за год, будучи офисным планктоном. Сергей Плащинский с фотографиями, видео и отчётами о поездках.</p> <p><a href="https://t.me/chechannel">Че Ченел</a> — Илья и Стася Чекальские о путешествиях, переездах и жизни в Польше.</p> <p><a href="https://t.me/tinkoffjournal">Тинькофф-журнал</a> — новые cтатьи об управлении деньгами. Как экономить, вкладывать, защищать свои права и общаться с банками.</p>Почему CSS-модули не могут заменить БЭМ2017-10-08T14:07:00Zhttps://andrew-r.ru/notes/bem-vs-css-modules/<p class="subtitle">Часто слышу, как разработчики говорят «БЭМ не нужен, ведь есть <span class="nobr">CSS-модули</span>». Это не так.</p> <p>Корень этого заблуждения кроется в том, что люди воспринимают БЭМ как <span class="nobr">CSS-методологию</span>. На самом деле БЭМ это набор универсальных принципов, которые можно применять независимо от используемых технологий, будь то CSS, Sass, HTML, JavaScript или React. БЭМ решает множество задач, в число которых входят именование <span class="nobr">CSS-классов</span>, подход к разделению интерфейса на независимые части и изоляция стилей для этих независимых частей.</p> <p><span class="nobr">CSS-модули</span> это инструмент, который решает только проблему изоляции стилей. Все остальные проблемы остаются нерешёнными: вам всё ещё нужны какие-то правила для разделения интерфейса на независимые части и всё ещё нужно придумывать названия классов. Поэтому <span class="nobr">CSS-модули</span> можно и нужно применять вместе с БЭМом.</p> <p>Эволюция выглядит так:</p> <pre><code class="language-css"><span class="hljs-comment">/* Классический БЭМ с длинными именами классов для обеспечения изоляции */</span> <span class="hljs-selector-class">.shop-cart-button</span> {} <span class="hljs-selector-class">.shop-cart-button_size_small</span> {} <span class="hljs-selector-class">.shop-cart-button_size_large</span> {} </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* CSS-модули с неограниченной свободой творчества в именах классов */</span> <span class="hljs-selector-class">.button</span> {} <span class="hljs-selector-class">.is-small</span> {} <span class="hljs-selector-class">.is-large</span> {} <span class="hljs-selector-class">.button</span> {} <span class="hljs-selector-class">.size-small</span> {} <span class="hljs-selector-class">.size-large</span> {} <span class="hljs-selector-class">.button</span> {} <span class="hljs-selector-class">.small</span> {} <span class="hljs-selector-class">.large</span> {} </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* БЭМ и CSS-модули */</span> <span class="hljs-selector-class">.button</span> {} <span class="hljs-selector-class">.button_size_small</span> {} <span class="hljs-selector-class">.button_size_large</span> {} </code></pre> <p>Сразу отвечу на вопрос «а чем плох пример с классами <code>.button</code>, <code>.small</code> и <code>.large</code>?». Он плох тем, что классы <code>.small</code> и <code>.large</code> сами по себе не несут информации о том, к чему они относятся. Нельзя понять, стилизуют ли они отдельный элемент или описывают состояние существующего элемента. Также такие названия классов рано или поздно снова приведут вас к проблеме уникальности имён. Например, вы пишете стили для модального окна. Вам нужно стилизовать полупрозрачный оверлей поверх страницы и само модальное окно. Оба этих элемента могут быть в двух состояниях: виден или скрыт. Кажется, что класс <code>.visible</code> отлично подходит, но проблема в том, что для оверлея и для окна этот класс должен содержать разные стили. Можно придумать костыль в виде селекторов <code>.overlay.visible</code> и <code>.window.visible</code>, но это именно костыль, потому что вы наращиваете специфичность. С БЭМом всё просто и без ненужного роста специфичности: <code>.overlay_visible</code> и <code>.window_visible</code>.</p>Тед Дзюба о трёх инструментах инженера2017-12-09T20:51:00Zhttps://andrew-r.ru/notes/2017-12-09-tools-of-systems-engineering/<p><em>По мотивам публикации <a href="http://widgetsandshit.com/teddziuba/2010/12/the-3-basic-tools-of-systems-engineering.html">The 3 Basic Tools of Systems Engineering</a>.</em></p> <p>Цель инженера — решить задачу, а не написать код. Для решения технических задач есть три основных инструмента.</p> <h2>Деньги</h2> <p>Лучший инструмент для решения задач, потому что экономит время и не требует написания кода. Чаще всего применяется для решения проблем масштабируемости и производительности.</p> <h2>Время</h2> <p>Деньги решают не все проблемы (и денег не всегда хватает). Время следует тратить в первую очередь на поиск уже существущих инструментов для решения задачи. Не бойтесь пользоваться результатами работы других людей для достижения собственных целей.</p> <h2>Код</h2> <p>Крайняя мера. Пишите код, только если не удалось решить задачу деньгами и временем на поиск готового решения. Каждая строчка кода — обязательство: её нужно спроектировать, протестировать и поддерживать. Кстати, Тед советует писать приёмочные тесты, так как они при меньших затратах дают лучший результат, чем юнит-тесты.</p> <p>Важно использовать эти инструменты именно в указанном порядке. Худшее, что можно сделать — начать решать задачу с помощью кода.</p>Форматирование чисел в браузере2017-12-10T15:00:00Zhttps://andrew-r.ru/notes/2017-12-10-number-formatting/<p class="subtitle">Если вам нужно отформатировать числа в браузере, не подключайте для этого сторонние библиотеки и не пишите велосипеды, а используйте нативный <code>Intl.NumberFormat</code>.</p> <p>Простой пример:</p> <pre><code class="language-js"><span class="hljs-keyword">const</span> numberFormatter = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Intl</span>.<span class="hljs-title class_">NumberFormat</span>(); numberFormatter.<span class="hljs-title function_">format</span>(<span class="hljs-number">12345.67</span>); <span class="hljs-comment">// -> 12 345,67</span> </code></pre> <p>По умолчанию <code>NumberFormat</code> использует правила системной локали. При необходимости можно указать нужную локаль: <code>new Intl.NumberFormat(’en-US’)</code>. Вторым аргументом передаются опции вроде минимального/максимального количества знаков после запятой, подробнее в документации.</p> <p><code>Intl</code> поддерживается начиная с IE 11 и Safari 10, на мобильных поддержка хуже, поэтому нужно при открытии страницы проверять поддержку и при её отсутствии подгружать полифил.</p> <p>Проще всего использовать сервис <a href="https://polyfill.io/">Polyfill.io</a> от Financial Times:</p> <pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span> </code></pre> <p>Polyfill.io проверяет по юзерагенту, какие фичи не поддерживает браузер, и отдаёт полифилы только для них.</p>Закон Хика2017-12-24T12:25:00Zhttps://andrew-r.ru/notes/2017-12-24-hicks-law/<p class="subtitle">Чем больше вариантов выбора, тем больше времени нужно на принятие решения.</p> <p>В веб-дизайне закон Хика работает так: покажете в навигации сайта ссылки на все разделы — пользователь уйдёт, потому что не захочет тратить кучу времени на поиск и выбор нужного раздела. Покажете огромную форму оформления заказа — он испугается того, что её придётся заполнять половину дня.</p> <p>Основные инструменты борьбы с этим — категоризация и скрытие сложности. Ссылки в навигации группируются по категориям и прячутся в выпадающие меню. Длинные процессы вроде оформления заказа разбиваются на небольшие шаги, и пользователь видит только то, что относится к текущему шагу.</p> <p>Подробнее о том, на какие метрики влияет закон Хика и как его применять в веб-дизайне, в статье <a href="https://www.interaction-design.org/literature/article/hick-s-law-making-the-choice-easier-for-users">Hick’s Law: Making the choice easier for users</a>.</p>Как блокировка ВК на Украине отразилась на статистике Форвеба2017-12-24T15:51:00Zhttps://andrew-r.ru/notes/2017-12-24-vk-ukraine-block/<img alt="Скриншот статистики сообщества For Web ВКонтакте" src="https://andrew-r.ru/notes/2017-12-24-vk-ukraine-block/assets/info.jpg" height="1008" width="1280" />Утилитарные функции2017-12-24T20:47:00Zhttps://andrew-r.ru/notes/2017-12-24-utils/<p>В программировании есть два подхода. Первый — не использовать утилитарные функции и каждый раз заново писать код, решающий типичную задачу вроде группировки элементов массива. Второй — максимально использовать уже написанные утилитарные функции и выносить повторяющийся код в новые. К большому сожалению, в джаваскрипте довольно скудная стандартная библиотека, что бы ни говорили сторонники первого подхода («зачем тебе лодаш, если <code>forEach</code>, <code>map</code> и <code>filter</code> уже <span class="nobr">давным-давно</span> реализованы нативно?»). <span class="nobr">Из-за</span> этого приходится подключать сторонние библиотеки или писать свои велосипеды, что, конечно, плохо.</p> <p>Тем не менее, мне ближе второй подход, потому что:</p> <ul> <li>утилитарные функции позволяют максимально сконцентрироваться на задаче и не отвлекаться на мелочи вроде написания очередного редьюса;</li> <li>утилитарные функции лучше выражают намерения программиста: <code>flatten</code>, <code>pluck</code> или <code>chunk</code> выглядят куда содержательнее, чем <code>array.reduce((result, item) => /* ... */)</code>;</li> <li>каждое дублирование кода вместо использования утилитарной функции повышает вероятность ошибки и требует дополнительных тестов.</li> </ul>Пока, 20172018-01-01T14:06:00Zhttps://andrew-r.ru/notes/bye-2017/<p class="subtitle">Моя главная ошибка в 2017 году — отсутствие чётко поставленных глобальных целей и, как следствие, постоянное распыление на всё подряд. Когда хочется сделать кучу вещей, хорошо не получается ни одна из них.</p> <h2>Прошлогодние планы</h2> <p>В <a href="http://andrew-r.ru/notes/?go=all/bye-2016/">прошлом году</a> составил себе план на 2017. Вот что получилось.</p> <p><strong>Начать заниматься английским с преподавателем</strong> . Прошёл 11 занятий в <a href="https://skyeng.ru/invite/4d5459354f44557a">SkyEng</a> (по ссылке получите два занятия в подарок после первой оплаты), занятия были очень похожи на школьные: каждое занятие на определённую тему (еда, спорт, etc) с комплексными упражнениями на грамматику, чтение, произношение и аудирование. Я хотел подтянуть именно грамматику, поэтому прекратил занятия в SkyEng и купил два учебника Реймонда Мёрфи по грамматике — Essential Grammar in Use и English Grammar in Use. Какое-то время я пытался регулярно проходить по одному юниту из учебника в день, но не хватило дисциплины и спустя ~35 юнитов я забросил это.</p> <p><strong>Переделать приложение, сделанное в ШРИ, чтобы не было стыдно его показывать.</strong> Был заведён <a href="https://github.com/dudeka-team/potracheno/projects/1">роадмап</a>, проект оживили, прикрутили сторибук для описывания и демонстрации ui-компонентов, перевели проект на второй вебпак (иронично, но на следующий же день после перехода вышел третий вебпак), почти выпилили material ui, починили <a href="https://wastd.ru/">https</a>. На этом энтузиазм закончился.</p> <p><strong>Оживить <a href="http://frontendbookshelf.ru/">«Книжную полку фронтендера»</a>.</strong> Убрал страницу «о проекте» и оставил только список книг, фильтры-селекты изменил на чекбоксы, выкинул всё визуально лишнее. Избавился от Stylus в пользу PostCSS. Разделил данные и их представление: данные хранятся в <a href="https://github.com/andrew%E2%80%94r/frontendbookshelf-data">отдельном репозитории</a>, книги/теги добавляются через CLI, а <a href="https://github.com/andrew%E2%80%94r/frontendbookshelf">представление</a> подключает данные в виде NPM-пакета и использует их на этапе сборки. В итоге получился <a href="https://github.com/andrew%E2%80%94r/frontendbookshelf/pull/37">жирный пулреквест</a>. После его мёржа энтузиазм снова закончился, поэтому вряд ли у проекта будет дальнейшее развитие.</p> <p><strong>Пройти все курсы из профессии «Фронтенд-разработчик» на <a href="https://ru.hexlet.io/u/andrew-r">Хекслете</a>.</strong> Взялся за прохождение курсов в самом начале года, прошёл все, кроме совсем прикладных вроде курса по реакту. За год ребята добавили в профессию ещё несколько базовых курсов, которые я проходить уже не стал (возможно, зря).</p> <p><strong>Больше читать бумажные книги и, возможно, выкладывать сюда рекомендации книг.</strong> У меня есть проблема: я не могу выйти из книжного без интересной книги. Из-за этого количество непрочитанных книг у меня дома всё время растёт. Кажется, единственная книга, которую я публично порекомендовал за прошедший год — это <a href="https://vk.com/andrew_r?w=wall109267511_2851">«Битва за рунет»</a>. Чтобы выкладывать в блог рекомендации, нужно делать конспекты, а меня это не очень увлекает. Тем не менее, я давно хочу как минимум для себя завести каталог прочитанных книг, так что «книжная полка фронтендера», возможно, переродится.</p> <p><strong>Прорешать <span class="caps">СИКП</span> хотя бы наполовину.</strong> Ну тут я капитально облажался. Я забыл о главном принципе (прогрессивного джипега) и настроился решать каждое задание. Дойдя до каких-то математических заданий в первой главе, я в ужасе отложил книгу и не возвращался к ней полгода. Только спустя полгода я догадался просто пропустить те задания и перейти ко второй главе, которая более приближена к реальности (и некоторые курсы Хекслета основаны как раз на ней). В итоге за год я так особо и не продвинулся в чтении.</p> <h2>Рандомные события</h2> <p>Побывал на куче конференций и митапов: dump в Екатеринбурге, pitercss_conf в Питере, WSD в Минске, FrontFest в Москве, несколько раз сходил на moscowcss, moscowjs, Rambler Front&. Польза от митапов только в общении со знакомыми, польза от конференций сомнительна, потому что знакомых там обычно немного, а действительно хороших и интересных докладов ещё меньше. В этом плане выделился FrontFest, на котором были так называемые «квартирники»: один или несколько экспертов свободно общаются с аудиторией на заданную тему. Этот формат живее и интереснее обычных докладов.</p> <p>Четырежды побывал в Питере: на уже упомянутой конференции pitercss_conf, в командировке, в отпуске и на корпоративе. Понял, что зима в Москве и Питере одинаково уныла и пасмурна.</p> <p>Вырос с джуниора до мидла в Avito. Запилил фронтенд нового <a href="http://support.avito.ru/">раздела помощи</a>, интегрировал его через вебвью в ios- и android-приложения Avito. Сделал админку к разделу помощи, всё на Реакте и Редаксе.</p> <p>Завёл пополняемый <a href="https://github.com/andrew%E2%80%94r/ui-developer-tips">список советов для разработчика интерфейсов</a>.</p> <p><a href="https://github.com/w3c/html/commit/c3be4c62ac254c0513e311d2ece71889546d3905">Законтрибьютил</a> в стандарт HTML (коммит года прям, лол).</p> <p>Составил большой <a href="https://andrew-r.ru/notes/telegram-channels">список каналов, которые я рекомендую читать в Телеграме</a>.</p> <p>Вместе с другом помогли знакомым ребятам запилить фронтенд платформы для выхода на ICO и для покупки токенов. В открытом доступе лежат исходники <a href="https://github.com/daonomic/daonomic-ui">библиотеки компонентов</a> и <a href="https://github.com/daonomic/daonomic-client">кабинета для покупателей токенов</a>.</p> <p>Поучаствовал в <a href="https://soundcloud.com/web-standards/episode-96">96 выпуске подкаста «Веб-стандарты»</a>.</p> <p>Пособеседовался в «Альфа-лабораторию». На вопрос об испытательном сроке мне ответили, что вместо испытательного заключают срочный трудовой договор на три месяца; это противоречит 58 статье ТК РФ, нормальный работодатель должен сразу предлагать бессрочный трудовой договор с испытательным сроком, так что будьте внимательны.</p> <p>Завёл <a href="https://t.me/andrew_r_notes">канал в Телеграме</a>.</p> <h2>Что дальше</h2> <p>Сложно сказать. Под конец года усилилось ощущение того, что двигаюсь куда-то не туда. Нет понимания, что всё это принесёт в долгосрочной перспективе. Не буду ставить никаких целей, потому что за год они легко могут потерять свою актуальность и привлекательность. Единственной целью будет разобраться в себе и сформулировать, чего и как я хочу добиться в ближайшее время.</p>Грокаем алгоритмы2018-01-07T08:54:00Zhttps://andrew-r.ru/notes/grokaem-algoritmy/<figure> <img alt="Обложка книги" src="https://andrew-r.ru/notes/grokaem-algoritmy/assets/cover.jpg" height="1200" width="847" /> <figcaption>Адитья Бхаргава, 2017, издательство «Питер», <span class="nobr">ISBN 978-5-496-02541-6</span></figcaption> </figure> <p class="subtitle">Книга для тех, кто хочет получить базовое представление о распространённых алгоритмах и их применении. Алгоритмы рассматриваются на примере реальных, а не абстрактных задач. Всё объясняется доходчиво и с множеством иллюстраций.</p> <h2>Что можно узнать из книги</h2> <p>Как устроена память, как в ней хранятся массивы и связные списки, какие у них преимущества и недостатки.</p> <p>Как работает рекурсия, что такое стек вызовов и как она на него влияет.</p> <p>Как решать задачи с помощью стратегии «разделяй и властвуй», как эта стратегия используется в алгоритмах быстрой сортировки и сортировки слиянием.</p> <p>Как устроены <span class="nobr">хеш-таблицы</span>, что такое <span class="nobr">хеш-функция</span>, коллизии и коэффициент заполнения. Как <span class="nobr">хеш-таблицы</span> применяются: моделирование отношений между объектами, устранение дубликатов, кеширование данных.</p> <p>Как моделировать сети с помощью графов, чем направленные графы отличаются от ненаправленных, как с помощью графа и поиска в ширину найти кратчайшее расстояние между двумя объектами; что такое топологическая сортировка; что такое взвешенные графы, как в них искать кратчайшее расстояние между объектами с помощью алгоритма Дейкстры, в каких случаях алгоритм Дейкстры не работает.</p> <p>Как определить, что задача не имеет быстрого алгоритмического решения (<span class="nobr">NP-полные</span> задачи), как решать такие задачи с помощью приближённых алгоритмов и жадной стратегии.</p> <p>Что такое динамическое программирование, как и где оно применяется.</p> <p>Как строить системы классификации на основе алгоритма k ближайших соседей, где ещё его можно применить, что такое извлечение признаков и регрессия и как они применяются в биржевых торгах, спам-фильтрах и системах рекомендаций.</p> <p>В конце книги приводится краткий обзор 10 алгоритмов и структур данных, которые подробно не рассматривались в книге: бинарное дерево поиска, инвертированные индексы, преобразование Фурье, параллельные алгоритмы и MapReduce, фильтры Блума и HyperLogLog, SHA, обмен ключами <span class="nobr">Диффи-Хеллмана</span>, линейное программирование.</p>Нужны ли CSS-препроцессоры в 2018 году, или насколько мы близки к ванильному CSS2018-01-08T20:35:00Zhttps://andrew-r.ru/notes/preprocessors-vs-vanilla-css/<p class="subtitle">Среди разработчиков сейчас есть тенденция отказа от препроцессоров в пользу ванильного CSS. Давайте разберёмся, готова ли веб-платформа полностью заменить препроцессоры.</p> <p>Начнём с того, что классические препроцессоры вроде Sass/Less/Stylus свой век почти отжили — им на смену пришли современные возможности CSS, а также PostCSS с большой <a href="https://www.postcss.parts/">экосистемой плагинов</a>. PostCSS по сути тоже препроцессор, только модульный — это главная причина его популярности и нужности. Подключаете и используете только нужные плагины, а если нужно что-то нестандартное, всегда можно написать свой плагин.</p> <p>Окей, классические препроцессоры остались в прошлом, и теперь мы наедине с ванильным CSS и PostCSS. Большинство разработчиков на этом и останавливаются — мигрируют с классических препроцессоров на PostCSS, пишут стили в синтаксисе, максимально близком к нативному (и это хорошо, нечего плодить разные синтаксисы), а для поддержки недостающих возможностей подключают плагины.</p> <p>В идеале хотелось бы избавиться от PostCSS и оставить только ванильный CSS (#usetheplatform). Как дела с ключевыми возможностями препроцессоров в веб-платформе?</p> <p><strong>Математические выражения</strong>. Поддерживаются нативно в виде функции <code>calc()</code> в CSS. Более того, <code>calc()</code> поддерживается подавляющим большинством браузеров — проблемы есть только в IE9-, Android Browser 4.3- и Opera Mini. Есть плагин <a href="https://github.com/postcss/postcss-calc">postcss-calc</a>, заранее вычисляющий на этапе сборки всё, что можно.</p> <p><strong>Переменные.</strong> Реализованы в CSS в виде <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/%E2%80%94*">кастомных свойств</a>, и имеют даже более мощную функциональность, чем в препроцессорах — они доступны в JS, а также могут менять свои значения динамически с помощью медиавыражений:</p> <pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> { <span class="hljs-attr">--base-font-size</span>: <span class="hljs-number">16px</span>; } <span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">1280px</span>) { <span class="hljs-selector-pseudo">:root</span> { <span class="hljs-attr">--base-font-size</span>: <span class="hljs-number">18px</span>; } } </code></pre> <p>Кастомные свойства не поддерживаются в IE 11- и в большинстве мобильных браузеров. Базовая функциональность кастомных свойств реализуется плагином <a href="https://github.com/postcss/postcss-custom-properties">postcss-custom-properties</a> — он на этапе сборки вычисляет все значения переменных и подставляет их в места использования, на выходе получается обычный CSS-файл с захардкоженными значениями. Динамическое переопределение значений переменных в медиавыражениях плагином не поддерживается.</p> <p><strong>Вложенность.</strong> Есть <a href="http://tabatkins.github.io/specs/css-nesting/">черновик</a> Таба Аткинса с предложением по добавлению вложенности в CSS, но о полноценной спецификации и поддержке браузерами задумываться, судя по всему, сильно рано. И, на мой взгляд, вложенность в CSS должна поддерживаться только для медиавыражений, чтобы не дублировать селекторы, то есть предыдущий пример я бы хотел написать так:</p> <pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> { <span class="hljs-attr">--base-font-size</span>: <span class="hljs-number">16px</span>; <span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">1280px</span>) { <span class="hljs-attr">--base-font-size</span>: <span class="hljs-number">18px</span>; } } </code></pre> <p>Вложенность селекторов, на мой взгляд, не нужна, потому что она поощряет увеличение специфичности и затрудняет чтение кода.</p> <p><strong>Примеси</strong> (миксины). И здесь не обошлось без <a href="https://tabatkins.github.io/specs/css-apply-rule/">черновика</a> Таба Аткинса. Его предложение заключалось в том, чтобы реиспользовать функциональность кастомных свойств и разрешить хранить в них не только значения, но и целые блок стилей, а затем применять эти блоки с помощью нового правила <code>@apply</code>:</p> <pre><code class="language-css"><span class="hljs-selector-class">.popup</span> { <span class="hljs-attr">--heading-style</span>: { <span class="hljs-attribute">font-weight</span>: bold; <span class="hljs-attribute">text-transform</span>: uppercase; } } <span class="hljs-selector-class">.popup__title</span> { <span class="hljs-keyword">@apply</span> (--heading-style); } </code></pre> <p>На первый взгляд круто, но в таком решении есть проблемы из-за смешения обычных переменных, используемых через <code>var()</code>, и блоков правил, используемых через <code>@apply()</code> — Таб Аткинс подробно <a href="https://www.xanthir.com/b4o00">описал эти проблемы</a> и сразу же предложил новое решение на основе Shadow DOM — псевдоэлемент <code>::part()</code>. Из статьи с описанием проблем мне стало ясно, что у Таба изначально было немного другое видение предназначения примесей.</p> <p>Таб заметил, что кастомные свойства не дают достаточно гибкости для стилизации веб-компонентов. В качестве примера рассмотрим попап — изолированный компонент, к внутренностям которого доступ мы не имеем. Кастомные свойства позволяют настроить внешний вид попапа, например задать цвет его подложки или размер заголовка. Ограничение в том, что внешний пользователь компонента не может доопределить или переопределить стили его частей — например, того же заголовка. Именно эту задачу Таб решил, предложив <code>@apply</code>, и, к счастью, пришёл к лучшему решению с <code>::part()</code>.</p> <p>Вроде здорово, но есть проблема. Основная задача, которую решают примеси — не до- или переопределение стилей, а <em>абстракция</em>. Есть замечательная <a href="http://www.lispcast.com/css-abstraction-combination">статья</a>, объясняющая, что CSS — неэффективный язык, потому что в нём нет средств абстракции и комбинирования. Примеси в первую очередь полезны для абстрагирования стилей и комбинирования этих абстракций. Только примеси позволяют добиться настоящего <a href="http://www.lispcast.com/cascading-separation-abstraction">разделения разметки и стилей</a>:</p> <p><img src="https://andrew-r.ru/notes/preprocessors-vs-vanilla-css/assets/separation-of-markup-and-styling.svg" alt="Разделение разметки и стилей с помощью примесей" /></p> <p>В общем, так как Таба Аткинса унесло в другую степь, перспектива появления примесей в CSS пока что туманна.</p> <h2>Итого</h2> <p>Из трёх ключевых возможностей (переменные, вложенность, примеси) в CSS реализована только одна — переменные, и то поддержка браузерами пока не позволяет их использовать.</p> <p>В простых проектах можно начать использовать только нативные возможности CSS с postcss-плагинами, выступающими в роли полифилов на этапе сборки. Такой подход я попробовал на <a href="https://github.com/andrew%E2%80%94r/madewithlove-landing/">одном из проектов</a>. Нужно было сверстать небольшой лендинг. Все стили я написал в одном файле с использованием CSS-переменных, причём на время разработки даже не пришлось настраивать никакую сборку — было достаточно подключить в разметке исходный файл со стилями, Хром нативно поддерживает CSS-переменные. Сборка потребовалась только для вычисления переменных, простановки вендорных префиксов и минификации — это заняло всего <a href="https://github.com/andrew%E2%80%94r/madewithlove-landing/blob/master/build.js">17 строчек кода</a>.</p> <p>В серьёзных проектах такой подход скорее всего покажет себя плохо. Отсутствие вложенности и примесей как средства абстракции сильно усложняет поддержку кода, поэтому эту нишу препроцессоры занимают прочно.</p>Микрорешения. Проверенный путь к достижению больших целей2018-01-12T21:22:00Zhttps://andrew-r.ru/notes/microresolutions/<figure> <img alt="Обложка книги" src="https://andrew-r.ru/notes/microresolutions/assets/cover.jpg" height="827" width="570" /> <figcaption>Кэролайн Арнольд, 2014, издательство «Манн, Иванов и Фербер», <span class="nobr">ISBN 978-5-00057-202-3</span></figcaption> </figure> <p><strong>TL; DR:</strong> решения вроде «стать более аккуратным» не работают. Вместо них принимайте конкретные, небольшие решения, которые легко осуществить и от которых сразу же есть профит — например, «заправлять постель утром». Привяжите эти решения к каким-либо событиям (сигналам), чтобы сформировать привычку. Фокусируйтесь не больше, чем на 1–2 микрорешениях, пока не выработаете привычку.</p> <p>В оригинале книга называется содержательнее — Small Move, Big Change: Using Microresolutions to Transform Your Life Permanently. В первой части подробно объясняется сама техника принятия микрорешений. Во второй части рассматривается, как на примере разных аспектов жизни пользоваться микрорешениями:</p> <ul> <li>улучшить качество сна;</li> <li>повысить физическую активность;</li> <li>правильно питаться;</li> <li>устранить беспорядок;</li> <li>улучшить личные отношения;</li> <li>сократить расходы;</li> <li>стать пунктуальнее;</li> <li>стать организованнее.</li> </ul> <p>В целом техника полезная, но книга сильно раздута — в первой части просто много воды, во второй части много обычных жизненных историй, иллюстрирующих описанные в начале приёмы.</p> <h2>Выжимка первой части</h2> <p>Принимайте конкретные, разумные и выполнимые решения, которые легко сразу же осуществить. Решение «буду ходить пешком на работу 5 дней в неделю» большое, сразу появятся обстоятельства, которые мешают его выполнению (пошёл дождь, людно, мало времени). Мозг начинает искать отмазки и в итоге откладывает или смягчает данное обещание. Проблема в том, что отказ от решения — это тоже решение, а принятие решений ослабляет самоконтроль и инициативность, потому что все они истощают один и тот же психологический ресурс.</p> <p>Микрорешение должно отражать конкретное и понятное действие, результат которого легко оценить. Не «заниматься спортом больше», а «приседать 100 раз каждое утро». Не «потреблять в день на сто калорий меньше», а, например, если вы полдничаете шоколадкой, «съедать только половину шоколадки».</p> <p>Привычки включаются определёнными сигналами: мы говорим «пожалуйста» (привычка) в ответ на «спасибо» (сигнал). Связывайте микрорешения с сигналами, это поможет задать для них контекст (когда выполнять микрорешение) и сформировать привычку.</p> <p>Микрорешения должны нести конкретную пользу и давать результат сейчас, а не когда-нибудь потом. Не «содержать дом в чистоте» (невозможно воплотить мгновенно), а «заправлять постель каждое утро» (приносит моментальную пользу — заправленную постель).</p> <p>Не пытайтесь принимать разом множество микрорешений: сконцентрируйтесь на 1–2 решениях и выполняйте их, пока они не войдут в привычку. Чем больше решений, тем больше на них нужно энергии.</p>Определяем видимость элемента с IntersectionObserver2018-01-20T10:56:00Zhttps://andrew-r.ru/notes/2018-01-20-intersection-observer/<p><code>IntersectionObserver</code> — это новый браузерный API, позволяющий асинхронно следить за степенью пересечения элемента с вьюпортом или другим элементом. С его помощью можно определить, виден ли элемент на экране, если виден, то насколько (целиком или частично), а также когда именно он оказался виден. Пример того, где это может потребоваться — модуль для ленивой загрузки картинок.</p> <p>Недавно на работе была задача залогировать событие просмотра блока на сайте. Я как представил, что нужно подписываться на событие скролла и вручную считать, входит ли элемент целиком во вьюпорт... А затем вспомнил про <code>IntersectionObserver</code>, прочитал документацию и обрадовался, потому что с ним задача решается гораздо проще и красивее:</p> <pre><code class="language-js"><span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> <span class="hljs-title class_">IntersectionObserver</span>(handleIntersection, { <span class="hljs-attr">root</span>: <span class="hljs-literal">null</span>, <span class="hljs-comment">// отслеживаем пересечение с вьюпортом, а не с элементом, поэтому null</span> <span class="hljs-attr">threshold</span>: <span class="hljs-number">1</span> <span class="hljs-comment">// порог видимости, при котором сработает обзёрвер; 1 означает полную видимость, 0.5 означало бы 50% видимости</span> }); observer.<span class="hljs-title function_">observe</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'target'</span>)); <span class="hljs-keyword">function</span> <span class="hljs-title function_">handleIntersection</span>(<span class="hljs-params">entries</span>) { <span class="hljs-comment">// Обзёрвер срабатывает в том числе когда элемент скрывается из вьюпорта, поэтому нужна дополнительная проверка</span> <span class="hljs-keyword">if</span> (entries[<span class="hljs-number">0</span>].<span class="hljs-property">intersectionRatio</span> === <span class="hljs-number">1</span>) { <span class="hljs-title function_">logBlockView</span>(); } } </code></pre> <p><code>IntersectionObserver</code> поддерживается в последних версиях Chrome, Edge и Firefox, для остальных браузеров есть <a href="https://github.com/w3c/IntersectionObserver/tree/master/polyfill">полифил</a> (6,6 КБ в минифицированном виде). Подробнее об <code>IntersectionObserver</code> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">на MDN</a>.</p>Отправка запросов к серверу через CSS2018-01-21T10:25:00Zhttps://andrew-r.ru/notes/2018-01-21-css-http-request/<p>На собеседованиях иногда просят назвать способы отправки запроса на сервер. Помимо очевидных <code>fetch</code>, <code>XMLHttpRequest</code> и прочих джаваскриптовых штук, есть более экзотические способы вроде <code><img src="..."></code>.</p> <p>Вчера узнал о ещё более экзотическом и извращённом способе отправки запроса на сервер с помощью CSS:</p> <pre><code class="language-css"><span class="hljs-selector-tag">body</span><span class="hljs-selector-pseudo">::after</span> { <span class="hljs-attribute">content</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">'...'</span>); } </code></pre> <p>Этот способ может использоваться для аналитики и трекинга пользователей, у которых отключен JS. Можно отследить клики по ссылкам, ввод текста в инпуты, клики по чекбоксам, длительность ховера на каком-либо элементе; также можно приблизительно определить браузер и операционную систему пользователя (<a href="https://github.com/jbtronics/CrookedStyleSheets">подробнее</a>).</p> <p>Защититься от этого можно только отключив CSS. Параноикам пора переходить на текстовые браузеры :–)</p>Последний рабочий день в Авито2018-02-02T20:05:00Zhttps://andrew-r.ru/notes/2018-02-02-avito/<p>Позавчера был мой последний рабочий день в Авито. За год удалось здорово прокачаться во фронтенде, запилить с нуля <a href="https://support.avito.ru/">новый раздел помощи</a> и интегрировать его в мобильные приложения через вебвью, повлиять на развитие общей библиотеки компонентов и нового фронтенд-стека.</p> <p>Главный вывод, который я сделал — очень многое в работе зависит от команды, это особенно заметно в больших компаниях вроде Авито. Чем сплочённее команда, чем больше в ней единомышленников, тем лучше результат; такой команде не требуется авторитарное управление и навязывание процессов вроде скрама. Если же собрана не очень дружная команда, участники которой не понимают друг друга, результат будет практически нулевым, а попытки навязать команде какие-то процессы скорее всего деморализуют участников и приведут к распаду.</p>UX установки программ на Windows2018-02-03T14:32:00Zhttps://andrew-r.ru/notes/2018-02-03-windows-installers/<p>Обратил внимание на UX установщиков программ на винде: установщик весит всего несколько сотен килобайт и скачивается практически моментально, чтобы пользователь сразу открыл его и запустил установку. А скачивание самой программы сделано частью процесса установки, к длительности которого пользователи уже менее требовательны — раз устанавливается, можно подождать.</p>Почему в CSS нет селекторов, зависящих от текущего состояния раскладки2018-02-04T11:06:00Zhttps://andrew-r.ru/notes/2018-02-04-css-stuck-overflowing/<p>Было бы удобно иметь псевдоклассы вроде <code>:stuck</code> для элементов с <code>position: sticky</code> или <code>:overflowing</code> для элементов, переполненных содержимым. К сожалению, таких псевдоклассов не будет, потому что они могут привести к цикличности — для них можно было бы задать правила, которые отменят действие этих псевдоклассов:</p> <pre><code class="language-css"><span class="hljs-selector-tag">aside</span> { <span class="hljs-attribute">position</span>: sticky; <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>; } <span class="hljs-selector-tag">aside</span>:stuck { <span class="hljs-attribute">position</span>: static; } </code></pre> <p>Подробнее в вики CSSWG: <a href="https://wiki.csswg.org/faq#selectors-that-depend-on-layout">Selectors that Depend on Layout</a>.</p>Glimmer2018-02-04T13:07:00Zhttps://andrew-r.ru/notes/2018-02-04-glimmer/<p>Постоянно выходят какие-то реакт-подобные библиотеки, которые супербыстрые, работают в IE8 и весят совсем мало. Ничего кардинально нового они не несут, так что вместо них расскажу о действительно интересной библиотеке.</p> <p>Glimmer — бывший UI-движок Ember, выросший в отдельный проект. Он отличается от реакта и подобных библиотек тем, что его шаблоны компилируются не в JavaScript, а в JSON. Этот JSON на стороне клиента компилируется в байткод, исполняемый в виртуальной машине Glimmer (в терминах Glimmer это называется last mile compilation). Выглядит этот JSON примерно так:</p> <pre><code class="language-json"><span class="hljs-punctuation">[</span> <span class="hljs-punctuation">[</span>'startProgram'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">0</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span>'text'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">0</span><span class="hljs-punctuation">,</span> <span class="hljs-number">3</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span>'mustache'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">1</span><span class="hljs-punctuation">,</span> <span class="hljs-number">3</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span>'openElement'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">2</span><span class="hljs-punctuation">,</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span>'closeElement'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">2</span><span class="hljs-punctuation">,</span> <span class="hljs-number">3</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span>'endProgram'<span class="hljs-punctuation">,</span> <span class="hljs-punctuation">[</span><span class="hljs-number">0</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">]</span> <span class="hljs-punctuation">]</span> </code></pre> <p>Чем компиляция шаблонов в JSON лучше компиляции в JavaScript? Временем их парсинга, которое критично на мобильных устройствах. Грамматика JSON сильно проще JS, поэтому и парсится JSON гораздо быстрее.</p> <p>Несмотря на скорость парсинга JSON, с ростом количества шаблонов растут и затраты на их парсинг. Ребята из LinkedIn знатно упоролись и решили свести эти затраты к нулю: вместо компиляции в JSON они сразу компилируют шаблоны в байткод и доставляют их в браузер в бинарном формате, так что на стороне клиента last mile compilation не требуется и виртуальная машина Glimmer может сразу исполнять полученный байткод.</p> <p>Байткод в бинарном формате сокращает не только время парсинга, но и размер шаблонов. А в ближайшем будущем можно будет добиться ещё большей скорости, портировав части виртуальной машины Glimmer на WebAssembly.</p>Разделение состояния и представления2018-02-07T15:31:00Zhttps://andrew-r.ru/notes/2018-02-07-state-and-view/<p>Посмотрел хороший <a href="https://youtu.be/3J9EJrvqOiM">доклад</a> от создателя MobX о разделении состояния и представления приложения.</p> <p>Лучший момент в этом докладе — когда Мишель закомментировал <code>ReactDOM.render(...)</code> и как ни в чём не бывало продолжил пользоваться демо-приложением прямо через консоль браузера, без UI.</p> <p>Главные преимущества разделения состояния и представления:</p> <ul> <li>можно писать логику и разрабатывать интерфейс параллельно, они перестают напрямую зависеть друг от друга;</li> <li>тестировать логику гораздо проще, если она не зависит от представления (можно покрыть основные пользовательские сценарии простыми юнит-тестами без всякого оверхеда вроде инструментов для тестирования реакт-компонентов).</li> </ul> <p>Помимо прочего из доклада вы узнаете, как перестать зависеть от lifecycle-методов Реакта (да, можно запрашивать данные не только в <code>componentWillMount</code>).</p> <p>Кроме этого доклада есть подробная статья, в которой пошагово демонстрируется разработка приложения с роутингом, загрузкой данных, аутентификацией и юнит-тестами без привязки к представлению: <a href="https://hackernoon.com/how-to-decouple-state-and-ui-a-k-a-you-dont-need-componentwillmount-cc90b787aa37">How to decouple state and UI (a.k.a. you don’t need componentWillMount)</a>.</p>Правила безопасности при разработке на Реакте2018-02-09T19:47:00Zhttps://andrew-r.ru/notes/react-safety-rules/<p>Реакт очень популярен. Из-за этого многие новички используют его неправильно, не понимая, какую задачу он решает.</p> <p>Основные задачи, которые нужно решить при разработке веб-приложения:</p> <ul> <li>управление состоянием приложения;</li> <li>общение с сервером;</li> <li>роутинг (изменение урла при переходах между страницами);</li> <li>разработка интерфейса. <em>← за это отвечает Реакт</em></li> </ul> <p>Как видите, Реакт отвечает только за построение интерфейса. Реакт не отвечает за программирование бизнес-логики. Реакт не отвечает за роутинг. Реакт не отвечает за общение с сервером. Из всего этого вытекает несколько простых правил.</p> <div class="sidenote"> <p class="sidenote__paragraph"><b>Не пишите бизнес-логику в Реакт-компонентах.</b> Логика приложения должна быть скрыта в отдельном модуле, который предоставляет наружу простой и декларативный API, которым можно пользоваться даже через консоль, без UI. Это позволит вам разрабатывать логику и UI независимо друг от друга, а также сильно упростить тестирование логики.</p> <aside class="sidenote__note"> Как разделять логику и представление приложения? <a href="https://youtu.be/3J9EJrvqOiM">Доклад</a> и <a href="https://hackernoon.com/cc90b787aa37">статья</a> создателя MobX Мишеля Вестрате </aside> </div> <div class="sidenote"> <p class="sidenote__paragraph"><b>Не используйте Реакт для роутинга.</b> React Router это очень популярная библиотека с нескольким десятком тысяч звёзд на Гитхабе, но серьёзно, она провоцирует разработчиков творить ужасные вещи. Она заставит вас нарушить предыдущий пункт и писать бизнес-логику в компонентах. Она заставит вас относиться к URL как к ещё одному источнику состояния приложения. Она заставит вас смешать вёрстку и настройки роутера (а в четвёртой версии появляется возможность раскидать настройки роутера вообще по всему приложению). Роутинг относится к логике приложения. URL должен зависеть от состояния приложения, а не наоборот. Это значит, что URL должен меняться автоматически при изменении состояния приложения. Обращаться к URL для чтения каких-то данных нельзя вообще никогда, кроме момента инициализации приложения (в момент инициализации можно использовать данные из URL для задания исходного состояния приложения).</p> <aside class="sidenote__note"> Примеры нормальных роутеров, автоматически синхронизирующих в обе стороны состояние и URL приложения: <a href="https://github.com/faceyspacey/redux-first-router">redux-first-router</a> и <a href="https://github.com/kitze/mobx-router">mobx-router</a> </aside> </div> <p><strong>Не используйте Реакт для общения с сервером.</strong> Серьёзно, я видел компоненты вроде <code><Fetch url='...' /></code> — пожалуйста, не делайте так. За общение с сервером должен отвечать отдельный модуль, скрывающий детали реализации и предоставляющий абстрактный API вроде <code>articles.get({ id: 1 }).then((article) => {})</code>.</p>Как упростить тестирование Редакс-редьюсеров2018-02-19T21:24:00Zhttps://andrew-r.ru/notes/simplified-redux-reducer-tests/<p>Если вы работаете с Редаксом, вы знаете, что редьюсеры меняют состояние приложения в соответствии с произведёнными действиями. Если после какого-то действия состояние изменится неправильным образом, приложение может сломаться. Чтобы этого не допустить, нужно проверять, правильное ли состояние редьюсер генерирует в ответ на обрабатываемые им действия.</p> <p>В документации Редакса есть <a href="https://redux.js.org/docs/recipes/WritingTests.html#reducers">пример тестирования редьюсеров</a>. Суть очень проста: берём редьюсер, вызываем его с каким-либо исходным состоянием и нужным экшеном и сравниваем полученный результат с ожидаемым. Если ожидаемое и полученное состояния совпали, всё супер, если нет — вы поймали баг.</p> <p>Официальный пример тестирования очень многословен, такое тестирование отнимает силы, время и любовь к жизни. Расскажу, как его упростить благодаря магии <a href="https://facebook.github.io/jest/docs/en/snapshot-testing.html">снепшотов jest</a>.</p> <p>Упрощать будем на примере простого редьюсера:</p> <pre><code class="language-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState = { <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }; <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">counterReducer</span>(<span class="hljs-params">state = initialState, action = {}</span>) { <span class="hljs-keyword">switch</span> (action.<span class="hljs-property">type</span>) { <span class="hljs-keyword">case</span> <span class="hljs-string">'INCREMENT'</span>: { <span class="hljs-keyword">if</span> (state.<span class="hljs-property">disabled</span>) { <span class="hljs-keyword">return</span> state; } <span class="hljs-keyword">return</span> { ...state, <span class="hljs-attr">counter</span>: state.<span class="hljs-property">counter</span> + <span class="hljs-number">1</span>, }; } <span class="hljs-keyword">case</span> <span class="hljs-string">'DISABLE'</span>: { <span class="hljs-keyword">return</span> { ...state, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }; } <span class="hljs-keyword">case</span> <span class="hljs-string">'ENABLE'</span>: { <span class="hljs-keyword">return</span> { ...state, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }; } <span class="hljs-attr">default</span>: { <span class="hljs-keyword">return</span> state; } } } </code></pre> <p>Если следовать официальной документации, тесты будут выглядеть так:</p> <pre><code class="language-javascript"><span class="hljs-keyword">import</span> reducer, { initialState } <span class="hljs-keyword">from</span> <span class="hljs-string">'./'</span>; <span class="hljs-title function_">describe</span>(<span class="hljs-string">'counter reducer'</span>, <span class="hljs-function">() =></span> { <span class="hljs-title function_">it</span>(<span class="hljs-string">'should return the initial state'</span>, <span class="hljs-function">() =></span> { <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(initialState)).<span class="hljs-title function_">toEqual</span>(initialState); }); <span class="hljs-title function_">it</span>(<span class="hljs-string">'should increment if not disabled'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> modifiedState = { ...initialState, <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }; <span class="hljs-keyword">const</span> action = { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span> }; <span class="hljs-keyword">const</span> expectedState = { ...modifiedState, <span class="hljs-attr">counter</span>: <span class="hljs-number">1</span>, }; <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toEqual</span>(expectedState); }); <span class="hljs-title function_">it</span>(<span class="hljs-string">'should not increment if disabled'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> modifiedState = { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }; <span class="hljs-keyword">const</span> action = { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span> }; <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toEqual</span>(modifiedState); }); <span class="hljs-title function_">it</span>(<span class="hljs-string">'should disable if enabled'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> modifiedState = { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }; <span class="hljs-keyword">const</span> action = { <span class="hljs-attr">type</span>: <span class="hljs-string">'DISABLE'</span> }; <span class="hljs-keyword">const</span> expectedState = { ...modifiedState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }; <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toEqual</span>(expectedState); }); <span class="hljs-title function_">it</span>(<span class="hljs-string">'should enable if disabled'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> modifiedState = { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }; <span class="hljs-keyword">const</span> action = { <span class="hljs-attr">type</span>: <span class="hljs-string">'ENABLE'</span> }; <span class="hljs-keyword">const</span> expectedState = { ...modifiedState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }; <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toEqual</span>(expectedState); }); }); </code></pre> <p>Получилось многословно и императивно. В каждом тесткейсе для нас имеют значение только его название, исходное состояние, действие и ожидаемое состояние. Всё остальное — шум. Чтобы избавиться от него, напишем небольшую функцию-обёртку, скрывающую в себе весь бойлерплейт:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createStateTransitionTester</span>(<span class="hljs-params">{ reducer, initialState }</span>) { <span class="hljs-comment">// сразу запоминаем редьюсер и его исходное состояние, чтобы не повторять их в каждом тесткейсе</span> <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">testCaseName, params</span>) =></span> { <span class="hljs-keyword">const</span> { modifiedState = initialState, <span class="hljs-comment">// по умолчанию считаем, что переход совершается из начального состояния</span> action, } = params; <span class="hljs-comment">/** * Ожидаемое состояние может зависеть от исходного состояния, * поэтому даём возможность генерации ожидаемого состояния на основе исходного */</span> <span class="hljs-keyword">const</span> expectedState = <span class="hljs-keyword">typeof</span> params.<span class="hljs-property">expectedState</span> === <span class="hljs-string">'function'</span> ? params.<span class="hljs-title function_">expectedState</span>(modifiedState) : params.<span class="hljs-property">expectedState</span>; <span class="hljs-title function_">test</span>(testCaseName, <span class="hljs-function">() =></span> { <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toEqual</span>(expectedState); }); }; } </code></pre> <p>С ней тесты становятся более декларативными и лаконичными:</p> <pre><code class="language-javascript"><span class="hljs-keyword">import</span> { createStateTransitionTester } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/utils'</span>; <span class="hljs-keyword">import</span> reducer, { initialState } <span class="hljs-keyword">from</span> <span class="hljs-string">'./'</span>; <span class="hljs-title function_">describe</span>(<span class="hljs-string">'counter reducer'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> testStateTransition = <span class="hljs-title function_">createStateTransitionTester</span>({ reducer, initialState }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should return the initial state'</span>, { <span class="hljs-attr">expectedState</span>: initialState, }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should increment if not disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }, <span class="hljs-attr">expectedState</span>: <span class="hljs-function">(<span class="hljs-params">modifiedState</span>) =></span> ({ ...modifiedState, <span class="hljs-attr">counter</span>: <span class="hljs-number">1</span>, }), }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should not increment if disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }, <span class="hljs-attr">expectedState</span>: <span class="hljs-function">(<span class="hljs-params">modifiedState</span>) =></span> modifiedState, }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should disable if enabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'DISABLE'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }, <span class="hljs-attr">expectedState</span>: <span class="hljs-function">(<span class="hljs-params">modifiedState</span>) =></span> ({ ...modifiedState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }), }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should enable if disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'ENABLE'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }, <span class="hljs-attr">expectedState</span>: <span class="hljs-function">(<span class="hljs-params">modifiedState</span>) =></span> ({ ...modifiedState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }), }); }); </code></pre> <p>Стало лучше, но описывать каждый раз ожидаемое состояние довольно утомительно, особенно если в нём много полей. Я долгое время смиренно жил с этим решением, пока мой коллега <a href="https://twitter.com/pofigizm">Денис</a> не предложил использовать jest-снепшоты для автоматической генерации снимка ожидаемого состояния. При таком подходе вручную нужно описывать только экшен и исходное состояние. При первом запуске ожидаемое состояние для всех тесткейсов запишется в снепшоты, и результаты дальнейших запусков тестов будут сравниваться с сохранёнными снепшотами. Здесь сделано допущение, что при первом запуске редьюсер работает правильно и без багов, иначе первые снимки ожидаемого состояния получатся некорректными.</p> <p>Для использования такого подхода нужно самую малость доработать функцию <code>createStateTransitionTester</code>:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createStateTransitionTester</span>(<span class="hljs-params">{ reducer, initialState }</span>) { <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">testCaseName, { modifiedState = initialState, action }</span>) =></span> { <span class="hljs-comment">// expectedState больше не нужен</span> <span class="hljs-title function_">test</span>(testCaseName, <span class="hljs-function">() =></span> { <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">reducer</span>(modifiedState, action)).<span class="hljs-title function_">toMatchSnapshot</span>(); <span class="hljs-comment">// .toEqual(expectedState) → .toMatchSnapshot()</span> }) }; } </code></pre> <p>Благодаря этому тесты стали ещё лаконичнее, а писать их теперь не так больно:</p> <pre><code class="language-javascript"><span class="hljs-keyword">import</span> { createStateTransitionTester } <span class="hljs-keyword">from</span> <span class="hljs-string">'~/utils'</span>; <span class="hljs-keyword">import</span> reducer, { initialState } <span class="hljs-keyword">from</span> <span class="hljs-string">'./'</span>; <span class="hljs-title function_">describe</span>(<span class="hljs-string">'counter reducer'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> testStateTransition = <span class="hljs-title function_">createStateTransitionTester</span>({ reducer, initialState }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should return the initial state'</span>); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should increment if not disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }, }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should not increment if disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'INCREMENT'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }, }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should disable if enabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'DISABLE'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">false</span>, }, }); <span class="hljs-title function_">testStateTransition</span>(<span class="hljs-string">'should enable if disabled'</span>, { <span class="hljs-attr">action</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">'ENABLE'</span>, }, <span class="hljs-attr">modifiedState</span>: { ...initialState, <span class="hljs-attr">disabled</span>: <span class="hljs-literal">true</span>, }, }); }); </code></pre>Gap Year2018-02-20T18:17:00Zhttps://andrew-r.ru/notes/2018-02-20-gap-year/<p>Если вам или вашим детям скоро заканчивать школу, попробуйте <a href="https://mel.fm/vypuskniku/6903285-gap_year">отложить поступление в ВУЗ на год</a>.</p> <p>Высшее образование в наше время переоценено (в ВУЗ старается поступить подавляющее большинство). Многие выпускники толком не понимают, чем хотят заниматься в этой жизни (либо им кажется, что они понимают). Сразу после выпуска связывать себя минимум четырьмя годами учёбы не самый разумный вариант, если сперва можно опробовать несколько разных сфер и понять, куда хочется двигаться дальше.</p> <p>Я поступил в ВУЗ сразу после школы, как и большинство моих одногруппников. При этом более чем из 30 человек в группе реально интересовались программированием ну от силы 5 человек. При таком уровне заинтересованности нет вообще никакого смысла тратить бюджетные деньги, а главное, время студентов. Зачем тогда все поступали, да ещё и на специальность, в которой не заинтересованы? Потому что так принято — после школы приличный человек должен идти в ВУЗ. А из этого следует то, что выпускники поступают не куда им действительно хочется, а куда хватает баллов.</p>Blogtrottr2018-02-25T11:23:00Zhttps://andrew-r.ru/notes/2018-02-25-blogtrottr/<p>Пару лет назад я пытался пользоваться RSS-читалкой для отслеживания новых материалов по фронтенду, но забросил это, потому что помимо неё я читал email-рассылки и ленты ВК/Твитера — получалось слишком много источников информации. Недавно обнаружил идеальный способ подписки на RSS: сервис <a href="https://blogtrottr.com/">Blogtrottr</a> позволяет подписаться на любой фид и получать по почте дайджест материалов этого фида с нужной частотой. Я так подписался на Хабр и несколько технических блогов.</p> <p>Это отлично работает с концепцией пустого инбокса, когда каждое письмо во входящих считается невыполненным делом. Вместо постоянного обновления бесконечной RSS-ленты просто приходят новые письма, посмотрел письмо — дело сделано, можно удалять.</p> <p>Вообще, этот сервис — хороший пример идеального интерфейса, потому что интерфейса нет (не нужно ставить себе отдельную программу для чтения RSS).</p>Как капитализм может помочь техническому отделу большой компании2018-02-28T20:20:00Zhttps://andrew-r.ru/notes/capitalism-at-large-companies/<p>В мою <a href="https://github.com/andrew--r/frontend-case-studies">коллекцию материалов о разработке фронтенда в реальных компаниях</a> недавно прислали пулреквест с рекомендацией статьи <a href="http://matt.chadburn.co.uk/notes/teams-as-services.html">Free-market software development</a> Мэтта Чедбёрна из Financial Times (ранее The Guardian и BBC). Я проникся этой статьёй, потому что она совпадает с моими наблюдениями за время работы в Avito.</p> <p>Во многих крупных компаниях есть отдельные инфраструктурные команды, закрывающие потребности продуктовых команд. Инфраструктурные команды отвечают за базы данных, дизайн-системы, непрерывную интеграцию, developer experience и подобные глобальные штуки. Выглядит здорово — внутри компании есть целые команды, которые готовы закрыть любую потребность продуктовых разработчиков. На практике всё немного сложнее — если всех обязать пользоваться результатом работы инфраструктурных команд, начнутся проблемы.</p> <p>Навязывание внутренних решений может повлечь за собой демотивацию продуктовых разработчиков и потерю ориентиров у инфраструктурных команд. Продуктовым разработчикам не во всех случаях может подходить внутреннее решение, а отсутствие выбора снижает мотивацию. Инфраструктурные команды выступают в роли монополистов, а без конкуренции развиваться сложнее.</p> <p>В этом случае приходит на помощь принцип свободного рынка. Нужно дать продуктовым командам свободу выбора технологий и сервисов, тогда они смогут подбирать для своих задач наиболее подходящие решения. В свою очередь, это подстегнёт внутрениие инфраструктурные команды — ведь если все будут пользоваться внешними решениями, во внутренней команде нужды не будет и её расформируют. Это один из основных принципов капитализма: если в условиях свободного рынка люди действуют исходя из личной выгоды, в конечном итоге они приносят пользу всему обществу.</p> <p>Во второй части статьи Мэтт рассказывает о том, что инфраструктурные команды в таких условиях должны стремиться к уровню SaaS: не просто разрабатывать внутренний продукт, но и продвигать его, документировать, помогать пользователям с проблемами. Отличным примером служит <a href="https://cdn.polyfill.io/v2/docs/">сервис полифилов Financial Times</a>, изначально разработанный для внутренних нужд. Он настолько классно оформлен как продукт, что его сделали доступным снаружи компании и выложили в опенсорс.</p>Стоит ли тестировать Реакт-компоненты2018-03-07T06:30:00Zhttps://andrew-r.ru/notes/react-component-tests/<p>Наткнулся на очередную <a href="https://medium.com/devschacht/berry-de-witte-unit-testing-your-react-application-with-jest-and-enzyme-6ef3658fdc93">статью о юнит-тестировании Реакт-компонентов</a>. К сожалению, в таких статьях часто призывают бросать всё и начинать тестировать компоненты, но редко объясняют, зачем и кому это нужно. Попробую восполнить этот пробел.</p> <p>Можно разделить тестирование компонентов на два вида: снепшотное тестирование разметки компонентов и тестирование логики компонентов.</p> <h2>Снепшотное тестирование комопнентов</h2> <p>Снепшотное тестирование разметки компонентов — бесполезная вещь в подавляющем большинстве случаев. Оно полезно, если вы разрабатываете общую библиотеку компонентов в крупной компании вроде Яндекса или Авито. А если вы продуктовый разработчик, такие тесты себя не окупят — они слишком низкоуровневые. Они отслеживают только изменения разметки компонентов, а по одному только диффу разметки сложно сказать, поломается ли компонент в браузере пользователя.</p> <p>Интерфейс меняется чаще логики, поэтому если у вас есть свободное время, в первую очередь покройте тестами логику приложения. Если вы покрыли логику тестами и у вас всё ещё остаётся свободное время, прикрутите тестирование скриншотами — оно гораздо полезнее снепшотов, потому что вместо снимков разметки делаются снимки отрендеренного компонента с учётом особенностей окружения (браузера).</p> <h2>Тестирование логики компонентов</h2> <p>Этот вид тестирования полезен в случаях, когда у вас есть компоненты со встроенной логикой: например, выпадающие списки, саджесты или таблицы с сортировкой. Но я бы всё равно посоветовал вынести логику наружу компонента, написать на неё обычные юнит-тесты, а корректность работы интерфейса проверять автотестами и скриншотами — это надёжнее простых снимков разметки.</p>Prettier2018-03-10T18:48:00Zhttps://andrew-r.ru/notes/prettier/<p><a href="https://prettier.io/">Prettier</a> — инструмент форматирования кода c поддержкой множества языков, минимумом конфигурации и максимумом навязанных правил.</p> <p>Изначально я к нему отнёсся очень скептически, считая, что мне достаточно <a href="https://www.npmjs.com/package/eslint-config-airbnb">eslint-конфига от Airbnb</a>. Я считал, что всё, что с ним не совпадает — ересь. Несколько недель назад я всё же решился попробовать Prettier, и не зря.</p> <p>Prettier отлично сочетается с любым конфигом eslint, разделяя ответственность: за форматирование отвечает Prettier, за умными штуками вроде неиспользуемых переменных следит eslint.</p> <p>Prettier навязывает правила форматирования, с некоторыми из которых вы будете несогласны. Но о вкусах не спорят, и чем больше людей используют Prettier, тем меньше вариативности в коде разных проектов. Не нужно спорить с коллегами о форматировании какого-то куска кода, Prettier решает все споры за вас.</p> <p>Самая кайфовая вещь, которой можно проникнуться только начав использовать Prettier, это автоформатирование при сохранении файла. Стало очень заметно, сколько усилий и времени я раньше тратил на визуальное оформление кода. А теперь всё просто — наколбасил в одну строку нечитаемый кусок кода, сохраняешь файл и он автоматически облагораживается. Так что всем советую.</p>Не нужно читать технические книги, потому что они быстро устаревают (нет)2018-03-10T20:17:00Zhttps://andrew-r.ru/notes/obsolete-tech-books/<p>Если вы считаете, что техническая литература быстро устаревает, значит вы читаете не ту литературу.</p> <p>Это касается не только литературы. Если какая-то технология быстро устаревает, значит она недостойна внимания. <em>Вы же не хотите прожить жизнь в погоне за хайпом, упуская действительно важные вещи?</em></p> <p>Я не призываю прекращать следить за новыми технологиями, просто этому не нужно уделять много внимания. Вышел новый фреймворк? Да и фиг с ним, скорее всего через полгода никто о нём не вспомнит. А если он действительно чего-то стоит, о нём продолжат говорить и вы это заметите.</p> <p>Что касается книг: конечно, они быстро устаревают, если они об инструментах (библиотеках, фреймворках, etc). Читайте проверенные временем книги, посвящённые подходам и концепциям, а не инструментам. Например, «Алгоритмы» Томаса Кормена впервые изданы в 1990 (28 лет назад), а <abbr title="Структура и интерпретация компьютерных программ">СИКП</abbr> в 1985 (33 года назад). Несмотря на это, новые издания этих книг до сих пор можно найти в книжных, их читают и советуют.</p>Не спешите обновлять библиотеки2018-03-12T17:35:00Zhttps://andrew-r.ru/notes/dont-update-dependencies-immediately/<p>Пару часов назад вышел mobx 4, я прочитал список изменений и радостно решил обновиться. И сразу же обнаружил, что в релизе поломаны аннотации типов для flow.</p> <p>Та же история у меня была с обновлением Реакта с 15.5.x до 15.6.0: я в день релиза обновился в рамках одной из рабочих задач, и вечером тестировщица вернула мне задачу с комментарием «на ios не работает». Проблема оказалась в коде Реакта, её пофиксили через два дня в версии 15.6.1.</p> <p>Мораль проста — в любом крупном (и даже не очень) релизе есть баги. Так что если в вас нет духа авантюризма, перед обновлением мажорной версии любой библиотеки ждите пару недель, пока не исправят упущенные проблемы.</p>javascript:void(0)2018-03-15T19:29:00Zhttps://andrew-r.ru/notes/2018-03-15-javascript-void-0/<p>Очень распространённая ошибка доступности, которая встречается даже в популярных библиотеках — использование ссылок с <code>href="javascript:void(0)"</code>. С точки зрения семантики и доступности такие ссылки не имеют никакого смысла. Вместо них нужно использовать обычные кнопки.</p> <p>Запомните простое правило: если при нажатии должен меняться URL, это <code><a></code>. Во всех остальных случаях используйте <code><button></code>.</p>О состоянии фронтенд-экосистемы2018-03-23T18:27:00Zhttps://andrew-r.ru/notes/2018-03-23-the-state-of-frontend/<p>css-loader создан для того, чтобы Вебпак обрабатывал <code>@import</code> и <code>url()</code> в CSS так же, как <code>import</code> в JavaScript. ОДНАКО! В его конфигурации есть опция <code>minimize</code>, которая позволяет включить минификацию стилей. Под капотом для минификации используется cssnano, который, в свою очередь, тоже можно настроить. А в продвинутом режиме cssnano (напомню, минификатор стилей) начинает применять Автопрефиксер, который вообще никакого отношения к минификации не имеет.</p> <pre><code class="language-js">{ <span class="hljs-attr">loader</span>: <span class="hljs-string">'css-loader'</span>, <span class="hljs-comment">// пусть Вебпак обрабатывает @import и url() в CSS</span> <span class="hljs-attr">options</span>: { <span class="hljs-attr">minimize</span>: { <span class="hljs-comment">// хм, почему бы заодно не включить cssnano</span> <span class="hljs-attr">preset</span>: <span class="hljs-string">'advanced'</span>, <span class="hljs-comment">// здесь этого не написано, но стили обработаются Автопрефиксером, потому что?..</span> }, }, } </code></pre> <p>Принцип единственности ответственности? Не, не слышали.</p>Импорт модулей относительно проекта, а не текущего файла2018-04-04T00:00:00Zhttps://andrew-r.ru/notes/2018-04-04-relative-imports/<p>Обычный подход к импорту модуля в проекте — указание пути к нему относительно текущего файла:</p> <pre><code class="language-js"><span class="hljs-keyword">import</span> smoosh <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../utils/flatten'</span>; </code></pre> <p>Этот подход хрупкий и неудобный:</p> <ul> <li>сложно найти все импорты какого-либо модуля, потому что они выглядят по-разному в зависимости от местоположения;</li> <li>перенос файла с импортом в другую директорию уровнем выше или ниже ломает все импорты в этом файле;</li> <li>импорты вида <code>'../../../utils'</code> сложны для чтения и понимания, разработчику приходится мысленно резолвить путь до модуля, чтобы понять, где он лежит.</li> </ul> <p>Эти проблемы решаются использованием путей относительно корня проекта, например:</p> <pre><code class="language-js"><span class="hljs-keyword">import</span> smoosh <span class="hljs-keyword">from</span> <span class="hljs-string">'~/utils/flatten'</span>; </code></pre> <p>Здесь ~ — это алиас корня проекта (например, <code>/Users/andrew-r/work/personal-site/source</code>). Недостаток такого подхода заключается в том, что он не работает из коробки. Чаще всего этот подход используют с Вебпаком, в конфигурации которого можно указать нужные алиасы (<a href="https://webpack.js.org/configuration/resolve/#resolve-alias">документация</a>):</p> <pre><code class="language-js"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>); <span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = { <span class="hljs-attr">entry</span>: <span class="hljs-string">'./source/index.js'</span>, <span class="hljs-comment">/* ... */</span> <span class="hljs-attr">resolve</span>: { <span class="hljs-attr">alias</span>: { <span class="hljs-string">'~'</span>: path.<span class="hljs-title function_">resolve</span>(__dirname, <span class="hljs-string">'./source'</span>), }, }, }; </code></pre>Дизайн-подорожник2018-04-09T21:38:00Zhttps://andrew-r.ru/notes/2018-04-10-design-plantain/<p>Александр Быков пишет серию статей с пошаговыми примерами улучшения дизайна городской среды. Уже вышли три статьи:</p> <ul> <li><a href="https://zen.yandex.ru/media/id/5a78aa06f03173b1df500be8/5a9019a6256d5cdcf7814bdc">Убитый фасад в Новороссийске</a></li> <li><a href="https://zen.yandex.ru/media/id/5a78aa06f03173b1df500be8/5a97b81877d0e69928b56b41">Входная группа в брежневке</a></li> <li><a href="https://zen.yandex.ru/media/id/5a78aa06f03173b1df500be8/5ac1c8cc610493acb72f9d00">Петербург — хорошо. Но можно лучше</a></li> </ul> <p>После прочтения становится ясно, что жить не в дерьме не так уж сложно. Учитесь культуре дизайна с помощью подобных примеров и расширяйте зону комфорта за пределы своей квартиры (дома ведь, например, мало кому приходит в голову кидать мусор на пол?). Чем больше людей начнут замечать изъяны городского дизайна, тем раньше все эти изъяны устранят.</p>История пройденных собеседований2018-04-25T20:39:00Zhttps://andrew-r.ru/notes/job-interviews-history/<p>Многие компании вроде того же Яндекса хранят историю общения с кандидатами на разные вакансии: если вы повторно собеседуетесь куда-то, скорее всего у эйчаров уже есть представление о ваших навыках, зарплатных ожиданиях, сильных и слабых сторонах.</p> <p>Кандидатам стоит перенять у компаний эту практику и тоже начать записывать историю своего общения с компаниями. Такие конспекты могут здорово помочь вам в следующий раз, когда вы будете искать работу.</p> <h2>Что стоит записывать?</h2> <p><strong>Заданные вам вопросы и задачи</strong>. По ним можно проанализировать свои решения, увидеть слабые места и лучше подготовиться к следующим собеседованиям.</p> <p><strong>Общую информацию о компании вроде структуры и процессов</strong>. Это расширит ваш кругозор и сэкономит время на повторных собеседованиях.</p> <p><strong>Детали и специфику работы/проекта</strong>. Обычно в вакансиях не пишут о реальной специфике работы, а отделываются общими фразами вроде «вы будете разрабатывать гибкие и современные приложения» (на самом деле может оказаться, что вам предстоит верстать формочки на джейквери и бутстрапе). Это тоже ценная информация, которой можно поделиться с друзьями, сэкономив их время на прохождение первого интервью и выправив их ожидания от вакансии.</p> <p><strong>Предлагаемые условия работы.</strong> Зарплата, возможность удалёнки, релокация, фрукты в офисе — всё это будет удобно сравнивать при выборе из нескольких предложений.</p> <p>Старайтесь записать всё сразу после прохождения собеседования, чтобы важные детали не успели забыться.</p>Красная таблетка2018-05-11T17:00:00Zhttps://andrew-r.ru/notes/red-pill/<p><em>Андрей Курпатов, 2018, издательство «Капитал», <span class="nobr">ISBN 978-5-906940-62-9</span></em></p> <p>Эта книга — большой набор противоречий, открывающих глаза на то, как на самом деле устроен человек, какую роль в нашей жизни играют мозг и сознание, как избавиться от страданий (спойлер: <span class="spoiler">никак, но и не нужно тратить время на попытки избавления от них</span>) и начать жить чуть более осознанно.</p> <p>Автор на протяжении всей книги последовательно разрушает убеждения, в которых большинство людей боятся даже усомниться. Три главы подряд читателю на примере множества научных исследований раскрываются ужасные факты о нашем поведении и мышлении, эти факты приправляются довольно очевидными, но отрезвляющими утверждениями вроде того, что жизнь у нас одна и только нам решать, как её прожить. Чем ближе к четвёртой главе, тем сильнее кажется, что дальше уже некуда и пора бы автору, наконец, рассказать, что делать со всем этим накопленным багажом разочарований и открытий.</p> <p>Четвёртая глава — самая практичная: в ней автор рассказывает, почему дефицит — это хорошо, нужно ли добиваться успеха (успех — вещь мимолётная и поэтому спорная) и как это делать (спойлер: <span class="spoiler">стремление быть лучшим заложено в нас природой; реализуйте те возможности, которые у вас есть здесь и сейчас, не ждите каких-то туманных перспектив; не стройте наполеоновских планов — просто каждый день делайте небольшие дела лучше, чем их делают другие</span>), в чём корень прокрастинации и почему соцсети похожи на наркотики, как с помощью зефирок предсказать будущую успешность ребёнка, что такое мышление и почему большую часть времени мы на самом деле не думаем, почему с возрастом многие толстеют, почему нельзя доверять воспоминаниям и, в конце концов, зачем мы живём.</p>Почему стоит перейти с поиска Google на DuckDuckGo2018-05-13T08:35:00Zhttps://andrew-r.ru/notes/2018-05-13-from-google-to-duckduckgo/<p><em>По мотивам <a href="https://www.quora.com/Why-should-I-use-DuckDuckGo-instead-of-Google">Why should I use DuckDuckGo instead of Google?</a></em></p> <p>Помимо агрессивного трекинга и других неприятных вещей, поиск Google на основе нашего поведения и прошлых запросов специально подсовывает нам такие результаты, на которые мы с большей вероятностью будем кликать. Звучит полезно и здорово, но на самом деле это называется <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C_%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D0%BE%D0%B2">пузырём фильтров</a>: попадая в этот пузырь, мы теряем доступ к материалам, которые не согласуются с нашей нынешней или прошлой точками зрения. Это закрывает нам доступ к новым для нас идеям и развивает в нас склонность к подтверждению своей точки зрения.</p> <p>Та же история и с умными лентами соцсетей: они тоже помещают нас в пузырь, в котором мы видим только приятные нам материалы, скрывая от нас альтернативные мнения.</p>Программирование без дураков2018-05-13T16:00:00Zhttps://andrew-r.ru/notes/weniger-schlecht-programmieren/<p><em>Катрин Пассиг, Йоханнес Яндер, 2017, издательство «Питер», <span class="nobr">ISBN 978-5-496-02023-7</span></em></p> <p>Эта книга — большое руководство по всему, что поможет вам стать хорошим программистом. Джуниоры из неё узнают о разных концепциях, правилах и инструментах, мидлы освежат память и, скорее всего, тоже обнаружат ранее неизвестные им вещи.</p> <p>Книга разбита на четыре части.</p> <p>Первая часть посвящена тому, как справиться с колебаниями между завышенной и заниженной самооценкой и перейти от неосознанной некомпетенции к осознанной. В ней рассказывается, какие проблемы присущи исключительно неопытным программистам, а какие присущи всем без исключения, и как некоторые слабые стороны вроде некомпетентности в некоторых случаях могут оказаться полезными.</p> <p>Вторая часть посвящена искусству написания и чтения кода, а также работе в команде. Авторы объясняют:</p> <ul> <li>нужно ли учить английский (ответ прекрасно проиллюстрирован примером кода с комментарием на немецком);</li> <li>как избежать холиваров на тему форматирования кода;</li> <li>как называть переменные и функции (особенно порадовал список глаголов, часто употребляемых в именах функций, с объяснением их значений);</li> <li>когда и что нужно комментировать;</li> <li>как читать незнакомый код;</li> <li>где искать помощь, если что-то непонятно;</li> <li>как эффективно помогать коллегам;</li> <li>как выжить в команде.</li> </ul> <p>В третьей части рассматривается вопрос поиска ошибок и их предупреждения. Рассматриваются:</p> <ul> <li>принципы поиска ошибок, следуя которым в не загоните себя в тупик;</li> <li>способы отладки;</li> <li>распространённые примеры плохого кода;</li> <li>польза, необходимость и техники рефакторинга;</li> <li>польза и виды тестирования;</li> <li>основные ситуации, в которых риск появления ошибки выше, чем обычно;</li> <li>компромиссы и ситуации, в которых они оправданы.</li> </ul> <p>Четвёртая часть посвящена инструментам и концепциям программирования. В ней рассказывают:</p> <ul> <li>как обращаться с библиотеками и сторонним кодом;</li> <li>какие основные инструменты нужны программисту (редакторы, языки, менеджеры пакетов, фреймворки, среды разработки и тому подобное);</li> <li>зачем нужны системы контроля версий, какие они бывают и как ими пользоваться;</li> <li>что каждому программисту нужно знать о командной строке;</li> <li>что такое <abbr title="Объектно-ориентированное программирование">ООП</abbr>, когда нужно и не нужно его применять;</li> <li>какие существуют форматы для хранения данных, как базы данных помогают хранить информацию и в чём разница между реляционными и NoSQL базами данных;</li> <li>что нужно знать о безопасности (что её нет, хе-хе), какие наиболее частые ошибки безопасности и почему безопасность — это процесс.</li> </ul> <p>Также в конце четвёртой части есть обзор никак не связанных, но полезных концепций вроде типизации, исключений, транзакций, ID/GUID/UUID, stateful/stateless и многого другого. А в заключение даётся ответ на то, кто же такой хороший программист, и какие ещё книги почитать, чтобы им стать.</p>Почему браузер читает CSS-селекторы справа налево2018-06-04T21:45:00Zhttps://andrew-r.ru/notes/css-selectors-matching/<p>Если вы не знали об этом, браузер читает селекторы справа налево, то есть обрабатывая селектор <code>#widget .heading span</code> он в первую очередь найдёт все спаны, затем выберет только те, которые лежат внутри <code>.heading</code>, а затем оставит только спаны, лежашие внутри <code>#widget</code>.</p> <p>Звучит нелогично, но причина такого поведения в том, что задачи разработчика и браузера несколько отличаются. Задача разработчика при чтении или составлении селектора — получение соответствующего ему набора элементов. А для понимания задачи браузера нужно немного контекста.</p> <p>Прежде чем отрисовать страницу, браузер строит <em>дерево отображения (render tree)</em>. Оно состоит из <em>объектов отображения</em> — визуальных элементов страницы, расположенных в том же порядке, в котором они должны быть выведены на страницу. Дерево отображения строится на основе <abbr title="Document Object Model">DOM</abbr>, но в него не попадают невизуальные элементы вроде <code>head</code> или элементы с <code>display: none;</code>, а сложным элементам вроде <code>select</code> могут соответствовать несколько объектов отображения.</p> <p>Отрисовка страницы состоит из нескольких этапов, но нас интересует один — <em>компоновка</em> объектов отображения. На этом этапе браузер проходит по дереву отображения и рассчитывает размеры и положение каждого объекта на странице. Чтобы посчитать для объекта отображения эти значения, браузеру нужно знать стили, которые применены к этому объекту. Для этого ему нужно найти в таблице стилей все блоки правил, применяемые к элементу, соответствующему объекту отображения. И здесь мы подошли к ответу на исходный вопрос.</p> <p>Ключевая разница — разработчику нужно найти соответствующие <abbr title="Cascading Style Sheets">CSS</abbr>-селектору элементы, а браузеру нужно найти соответствующие элементу <abbr title="Cascading Style Sheets">CSS</abbr>-селекторы. В таком случае браузеру гораздо эффективнее читать селектор справа налево, чтобы сразу отсеять большинство неподходящих селекторов.</p> <p>Рассмотрим тот же пример с селектором <code>#widget .heading span</code>. Предположим, что браузеру нужно определить, подходит ли этот селектор к элементу <code>div</code>. Если бы браузер читал селектор слева направо, ему бы пришлось найти на странице элемент с идентификатором <code>widget</code>, внутри него найти все элементы с классом <code>.heading</code>, а затем найти внутри все <code>span</code> и проверить, есть ли среди них тот самый <code>div</code>. Это очень много работы. А благодаря чтению справа налево браузер может сразу определить, что <code>span</code> и <code>div</code> — принципиально разные элементы и поэтому селектор не подходит.</p>Кастомные элементы ГитХаба2018-06-19T17:54:00Zhttps://andrew-r.ru/notes/2018-06-19-github-custom-elements/<p>ГитХаб ещё <a href="https://www.webcomponents.org/community/articles/interview-with-joshua-peek">с 2014 года использует веб-компоненты в продакшене</a>. Часть компонентов инженеры ГитХаба выложили в опенсорс:</p> <ul> <li><a href="https://github.com/github/custom-element-boilerplate">custom-element boilerplate</a>, стартовый шаблон для веб-компонента;</li> <li><a href="https://github.com/github/time-elements">time-elements</a> расширяет возможности стандартного <code><time></code>;</li> <li><a href="https://github.com/github/clipboard-copy-element">clipboard-copy</a> при клике копирует заданный текст в буфер обмена;</li> <li><a href="https://github.com/github/auto-check-element">auto-check</a> автоматически валидирует значение поля через указанную ручку серверного API;</li> <li><a href="https://github.com/github/markdown-toolbar-element">markdown-toolbar</a> реализует кнопки для форматирования текста в markdown в <code><textarea></code>;</li> <li><a href="https://github.com/github/image-crop-element">image-crop</a> реализует интерфейс обрезания фоточек;</li> <li><a href="https://github.com/github/include-fragment-element">include-fragment</a> подгружает фрагмент HTML и заменяет себя им;</li> <li><a href="https://github.com/github/task-lists-element">task-lists</a> реализует список задач с поддержкой drag’n’drop;</li> <li><a href="https://github.com/github/auto-complete-element">auto-complete</a> реализует поле ввода с автодополнением и подгрузкой вариантов с сервера;</li> <li><a href="https://github.com/github/details-menu-element">details-menu</a> реализует выпадающее меню на основе элемента <code><dialog></code>;</li> <li><a href="https://github.com/github/details-dialog-element">details-dialog</a> реализует модальное окно на основе элемента <code><dialog></code>.</li> </ul> <p>Кажется, это и есть будущее разработки интерфейсов. jQuery-плагины морально устарели, React/Vue/Angular-компоненты сильно всё усложняют и плохо влияют на производительность, а кастомные элементы максимально просты (подключаешь скрипт и используешь их в любом месте разметки) и работают на основе нативных возможностей браузера.</p>Организация файлов по фичам, а не техническим аспектам2018-07-06T18:30:00Zhttps://andrew-r.ru/notes/features-not-tech-aspects/<p>Раньше я разделял файлы проекта по техническим аспектам. На примере проекта с Редаксом это выглядит примерно так:</p> <pre><code>src ├── actions ├── api # всё общение с сервером ├── components # всё, что относится к представлению ├── config # централизованная конфигурация всех фич ├── constants ├── containers ├── reducers ├── selectors └── utils # все вспомогательные функции </code></pre> <p>На практике такая структура оказалась для меня неудобна и немасштабируема.</p> <p>Во-первых, она завязана на технические аспекты используемых библиотек (в данном случае структура завязана на Редакс), и при смене библиотеки (например, при замене Редакса на Мобикс) придётся перелопачивать всю структуру.</p> <p>Во-вторых, программисты работают в первую очередь с <em>фичами</em> вроде авторизации или списка покупок. При такой структуре код, относящийся к одной фиче, становится разбросан по всему проекту, и программисту приходится бегать между директориями в поисках нужных файлов, а при рефакторинге и удалении кода скорее всего что-то будет теряться (если в проекте нет статической типизации).</p> <p>Здравой альтернативой оказалась организация стуктуры проекта по фичам (и сущностям предметной области). На примере того же приложения с Редаксом новая структура выглядит так:</p> <pre><code>src ├── modules │ ├── auth │ │ ├── actions.js │ │ ├── api.js │ │ ├── config.js │ │ ├── constants.js │ │ ├── reducer.js │ │ ├── selectors.js │ │ └── utils/ │ └── purchases │ ├── actions.js │ ├── api.js │ ├── config.js │ ├── constants.js │ ├── reducer.js │ ├── selectors.js │ └── utils/ └── view ├── components/ └── pages/ </code></pre> <p>При таком подходе любые технические аспекты, относящиеся к одной фиче, лежат рядом друг с другом и их не нужно искать по всему приложению. Сами фичи становятся изолированными модулями, предоставляющими наружу какой-то API, при этом детали их реализации и конкретные используемые библиотеки остаются под капотом.</p>Атлант расправил плечи2018-07-11T19:16:00Zhttps://andrew-r.ru/notes/atlas-shrugged/<p><em>Айн Рэнд, 2018, издательство «Альпина Паблишер»</em></p> <p>Жутко длинный роман (> 1000 страниц), очень доступно рассказывающий о вреде социализма и пользе капитализма. Может служить иллюстрацией к разделу <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BC#%D0%9A%D1%80%D0%B8%D1%82%D0%B8%D0%BA%D0%B0_2">«Критика»</a> статьи о социализме на Википедии.</p> <p>Стоит прочитать, если:</p> <ul> <li>идея «от каждого по способностям, каждому по потребностям» вам кажется хорошей;</li> <li>вы считаете эгоизм пороком;</li> <li>вы ставите благо общества выше своих личных интересов.</li> </ul> <p>На мой взгляд, книга написана слишком многословно. Похоже, что в какой-то момент Айн Рэнд покусал Лев Толстой, потому что в третьей части есть монолог главного героя на несколько десятков страниц, который она писала аж два года; его, как и всю книгу, можно сократить, не потеряв смысла и художественной ценности.</p>Юридические вопросы2018-08-07T17:54:00Zhttps://andrew-r.ru/notes/2018-08-07-legal-issues/<p>Всем приходится сталкиваться с юридическими вопросами, самые частые случаи — отношения между работником и работодателем, заказчиком и фрилансером, гражданином и государством. Большинство материалов на юридические темы пишутся ужасно канцелярским языком, так что разобраться в них нелегко. К счастью, есть исключения, их и порекомендую.</p> <p>Ребята из <a href="https://journal.tinkoff.ru/">Т—Ж</a> делают крутое СМИ про деньги, в котором простым языком разбирают юридические вопросы вроде аренды/покупки недвижимости, налоговых вычетов и всего подобного.</p> <p>Ребята из «Модульбанка» ведут <a href="https://delo.modulbank.ru/">издание для предпринимателей "Дело"</a>, в котором так же просто рассматриваются взаимоотношения предпринимателя и государства.</p> <p>Юристы Владимир Беляев и Дарья Тимохина делятся лаконичными <a href="http://outlaw.center/documents.html">шаблонами документов без воды и канцелярита</a>.</p>«Цель» Голдратта2018-08-16T14:50:00Zhttps://andrew-r.ru/notes/goldratt-the-goal/<p>Элияху Голдратт — автор теории ограничений. Её суть заключается в развитии систем и процессов через поиск и устранение <em>бутылочных горлышек</em> (ограничений), которые на самом деле определяют эффективность всей системы или процесса.</p> <p>Теория ограничений хороша тем, что она обобщена и применима не только к производству, но и вообще к любой сфере жизни, вплоть до отношений (что и демонстрируется в книжках). Она не даёт готовых решений ваших проблем, но учит помнить о конечной цели, находить препятствия на пути к ней и устранять их. А <em>мыслительные процессы</em> помимо прочего могут помочь ещё и в проведении переговоров и решении конфликтов.</p> <p>«Цель» и «Цель-2» — доступное и увлекательное введение в основы теории. Книги написаны в жанре бизнес-романа, поэтому в них минимум сухой теории и они читаются легко. Первая книга даёт представление о теории в целом, вторая книга учит практическому применению <em>мыслительных процессов</em> (логических инструментов теории ограничений).</p> <p>Формат бизнес-романа оказался неплох для поверхностного знакомства, но для применения описанных вещей на практике скорее всего потребуется отдельное погружение в теорию.</p>Как составить личный финансовый план и как его реализовать2018-08-25T17:47:00Zhttps://andrew-r.ru/notes/finplan/<p><em>Владимир Савенок, 2011, издательство «Манн, Иванов и Фербер», <span class="nobr">ISBN 978-5-906940-62-9</span></em></p> <p>Книга о том, как взять под контроль свои финансы, начать откладывать и не потерять сбережения. По сути обо всём этом должны рассказывать в школе <span class="nobr">¯\<em>(ツ)</em>/¯</span></p> <p>Люди часто покупают что-то в кредит и потом не вылезают из долгов. В книге объясняется, как этого избежать и системно подойти к реализации любой финансовой цели, будь то покупка автомобиля или достойная пенсия.</p> <p>Всё сводится к контролю доходов и расходов и регулярному инвестированию (это возможно при любом уровне дохода). Автор доступно и поверхностно рассказывает об инструментах инвестирования вроде облигаций, ПИФов, ETFов, акций, валют и драгоценных металлов, и объясняет риски и преимущества каждого инструмента.</p>Почему не стоит использовать компоненты высшего порядка в Реакте2018-09-19T07:00:00Zhttps://andrew-r.ru/notes/react-hocs/<p>Вокруг Реакта сложилось достаточно много паттернов, один из них — компоненты высшего порядка. Они призваны сделать удобным реиспользование общей функциональности между компонентами: эта функциональность выносится в компонент высшего порядка, а компоненты, требующие её, перед использованием просто оборачиваются в <abbr title="Higher-order component">HOC</abbr>. В теории звучит здорово, но на практике у компонентов высшего порядка есть большой недостаток: они похожи на наследование.</p> <h2>Концептуальная проблема</h2> <p>В чём проблема наследования? В том, что каждый уровень в иерархии классов влияет на следующие уровни. Метод любого класса будет распространён на все дочерние классы, которые, в свою очередь, объявляют собственные методы и могут переопределять унаследованные. Чтобы понять, что доступно в конкретном классе, нужно держать в голове всю цепочку родительских классов с их собственными и переопределенными методами.</p> <p>Это и есть общая проблема наследования и компонентов высшего порядка: на конкретную программную сущность (класс или компонент) влияют внешние сущности (родительский класс или компонент высшего порядка), и чтобы ей корректно пользоваться, разработчику приходится знать и помнить результаты этого влияния.</p> <div class="sidenote"> <p class="sidenote__paragraph">У каждого компонента в Реакте есть свой API, называемый пропами. Пропы — мост между компонентом и внешним миром. Любой компонент высшего порядка — дополнительный слой между обёрнутым компонентом и внешним миром, который влияет на обе стороны: он может передавать какие-то пропы обёрнутому компоненту или ожидать какие-то пропы от внешнего мира. В итоге разработчик вынужден помнить об этом неявном изменении контракта между обёрнутым компонентом и внешним миром.</p> <aside class="sidenote__note"> Помимо прочего, компоненты высшего порядка усложняют статическую типизацию компонентов с Flow или TypeScript </aside> </div> <h2>Альтернатива: рендер-пропы</h2> <p>Компоненты высшего порядка — не единственный паттерн для реиспользования общей функциональности. Общую функциональность можно вынести в отдельный компонент и сделать её доступной через рендер-пропы: при таком подходе любые данные передаются явно и ничего не нарушает контракт компонентов с внешним миром. Пример (код максимально упрощён и далёк от реальных кейсов в угоду наглядности):</p> <pre><code class="language-javascript"><span class="hljs-comment">/** * Подход с компонентами высшего порядка */</span> <span class="hljs-keyword">import</span> { message } <span class="hljs-keyword">from</span> <span class="hljs-string">'antd'</span>; <span class="hljs-keyword">const</span> <span class="hljs-title function_">Button</span> = (<span class="hljs-params">props</span>) => <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> {<span class="hljs-attr">...props</span>} /></span></span>; <span class="hljs-keyword">const</span> <span class="hljs-title function_">withMessage</span> = (<span class="hljs-params">WrappedComponent</span>) => <span class="hljs-function">(<span class="hljs-params">{ messageType, messageText, ...restProps }</span>) =></span> ( <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">WrappedComponent</span> {<span class="hljs-attr">...restProps</span>} <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> message[messageType](messageText)} /></span> ); <span class="hljs-keyword">const</span> <span class="hljs-title class_">ButtonWithMessage</span> = <span class="hljs-title function_">withMessage</span>(<span class="hljs-title class_">Button</span>); <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ButtonWithMessage</span> <span class="hljs-attr">messageType</span>=<span class="hljs-string">"success"</span> <span class="hljs-attr">messageText</span>=<span class="hljs-string">"Hello!"</span>></span> Say hello <span class="hljs-tag"></<span class="hljs-name">ButtonWithMessage</span>></span></span>; </code></pre> <pre><code class="language-javascript"><span class="hljs-comment">/** * Подход с рендер-пропами */</span> <span class="hljs-keyword">import</span> { message } <span class="hljs-keyword">from</span> <span class="hljs-string">'antd'</span>; <span class="hljs-keyword">const</span> <span class="hljs-title function_">Button</span> = (<span class="hljs-params">props</span>) => <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> {<span class="hljs-attr">...props</span>} /></span></span>; <span class="hljs-keyword">const</span> <span class="hljs-title function_">MessageProvider</span> = (<span class="hljs-params">{ type, text, children }</span>) => <span class="hljs-title function_">children</span>({ <span class="hljs-attr">showMessage</span>: <span class="hljs-function">() =></span> message[type](text) }); <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">MessageProvider</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"success"</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"hello"</span>></span> {({ showMessage }) => <span class="hljs-tag"><<span class="hljs-name">Button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{showMessage}</span>></span>Say hello<span class="hljs-tag"></<span class="hljs-name">Button</span>></span>} <span class="hljs-tag"></<span class="hljs-name">MessageProvider</span>></span></span>; </code></pre>Автоматизация релизов с помощью semantic-release2018-09-30T12:19:00Zhttps://andrew-r.ru/notes/semantic-release/<p>На работе мне регулярно нужно публиковать NPM-пакет и вести для него список изменений. Раньше я делал это вручную, и поэтому на это уходило ощутимое количество времени: нужно было сформировать список изменений, определить следующую версию, выполнить <code>npm publish</code>, дождаться прогона всех тестов и самой публикации.</p> <p>Если вы столкнулись (или в будущем столкнётесь) с такой же проблемой, сразу внедряйте <a href="https://semantic-release.gitbook.io/semantic-release/">semantic-release</a>: он автоматизирует всю рутину и максимально исключает человеческий фактор (например, неправильный выбор следующей версии пакета). semantic-release встраивается в CI, так что для публикации новой версии пакета достаточно просто запушить код в репозиторий.</p> <p>Что особенно круто — процесс публикации максимально абстрагирован и разбит на шаги, а вся специфичная логика реализуется через плагины. Это позволяет делать публикацию куда угодно, а не только в NPM, да и вообще кастомизировать процесс публикации: например, можно реализовать отправку списка изменений в Slack после публикации.</p> <p>Единственное, что от вас потребуется — один раз настроить инструмент и затем выполнять соглашения по именованию коммитов, по которым он будет автоматически определять следующую версию пакета и формировать список изменений. Кстати, требования к именованию коммитов — ещё одно косвенное преимущество: история изменений становится чище и читабельнее.</p>Секрет продуктивности от Bell Labs2018-10-05T06:50:00Zhttps://andrew-r.ru/notes/how-to-be-a-star-engineer/<p>Начал осваивать материалы, рекомендуемые на <a href="http://mtdv.io/">mtdv.io</a>. Первой стала статья How to be a star engineer Роберта Келли, написанная по мотивам его исследования в Bell Labs в середине 80-х годов.</p> <p>Что отличает продуктивных работников, считающихся «звёздами», от обычных крепких середняков? Наивно кажется, что дело в таланте, высоком IQ или в чём-то подобном. Исследование показало, что их отличают далеко не врождённые трудноприобретаемые качества, а подход к работе. Келли обнаружил девять стратегий, отличавших звёзд от обычных работников:</p> <ul> <li><em>инициативность</em>, готовность брать на себя ответственность и выходить за пределы должностных обязанностей;</li> <li><em>нетворкинг</em>, создание и поддержка сети знакомых, экспертиза которых замещает пробелы в знаниях;</li> <li><em>самоорганизация</em>, приоритизация задач с целью принесения максимальной пользы для компании;</li> <li><em>перспектива</em>, умение рассматривать задачи и проблемы с разных точек зрения;</li> <li><em>сотрудничество с руководством</em>, проактивная поддержка руководителей вместо простого исполнения их указаний;</li> <li><em>командная работа</em>, понимание целей и проактивная поддержка команды;</li> <li><em>лидерство</em>, влияние на окружающих для достижения общих целей;</li> <li><em>организационная смекалка</em>, умение использовать внутренние возможности компании (можно считать это умением играть в корпоративную политику);</li> <li><em>навыки презентации</em>, умение чётко и понятно доносить свои мысли до любой аудитории.</li> </ul> <p>Суть в том, что этим девяти стратегиям можно научиться: по результатам исследования Bell Labs запустили внутреннюю программу повышения продуктивности для сотрудников, где в течение нескольких недель они осваивали каждую из стратегий. Прохождение программы повысило продуктивность участников как по их собственному мнению, так и по мнению их менеджеров и коллег.</p> <p>Учитывая то, что исследованию уже практически 30 лет, поразительно, что ни о чём подобном не рассказывают в школах или вузах.</p> <p>Материалы:</p> <ul> <li><a href="https://vlsicad.ucsd.edu/Research/Advice/star_engineer.pdf">How to be a star engineer</a> (PDF, ~350 КБ), сама статья</li> <li><a href="https://hbr.org/1993/07/how-bell-labs-creates-star-performers">How Bell Labs Creates Star Performers</a>, больше подробностей об исследовании и его результатах</li> <li><a href="https://www.goodreads.com/book/show/1694152.How_to_Be_a_Star_at_Work">How to be a star at work</a>, книга по мотивам исследования</li> <li><a href="http://qcseminars.com/files/2011/01/How-to-Be-a-Star-At-Work1.pdf">Выжимка книги</a> (PDF, ~60КБ)</li> </ul>Почему не нужно стремиться к 100% покрытию кода юнит-тестами2018-10-22T14:02:00Zhttps://andrew-r.ru/notes/unit-tests-coverage/<p>Бытует мнение, что 100% покрытие кода юнит-тестами обеспечивает безопасность и уверенность в его корректности. Это заблуждение: процент покрытия кода никак не связан с качеством набора тестов.</p> <p>Рассмотрим простую функцию и её юнит-тест:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">isEmptyString</span>(<span class="hljs-params">string</span>) { <span class="hljs-keyword">if</span> (string.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) { <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; } <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; } <span class="hljs-title function_">it</span>(<span class="hljs-string">'works'</span>, <span class="hljs-function">() =></span> { <span class="hljs-title function_">expect</span>(<span class="hljs-title function_">isEmptyString</span>(<span class="hljs-string">'test'</span>)).<span class="hljs-title function_">toBe</span>(<span class="hljs-literal">false</span>); }); </code></pre> <p>Такой тест покрывает 75% строк и 50% путей выполнения:</p> <pre><code> PASS ./index.spec.js ✓ works (4ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 75 | 50 | 100 | 75 | | index.js | 75 | 50 | 100 | 75 | 3 | ----------|----------|----------|----------|----------|-------------------| </code></pre> <p>Довольно посредственный результат. Давайте его улучшим:</p> <pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">isEmptyString</span>(<span class="hljs-params">string</span>) { <span class="hljs-keyword">return</span> string.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>; } </code></pre> <p>Ух! Везде соточки:</p> <pre><code> PASS ./index.spec.js ✓ works (5ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 100 | 100 | 100 | 100 | | index.js | 100 | 100 | 100 | 100 | | ----------|----------|----------|----------|----------|-------------------| </code></pre> <p>Ловкость рук и никакого мошенничества. Мы смогли довести покрытие до 100%, просто поменяв сам тестируемый код, но не тесты! Метрики покрытия улучшились, а вот качество набора тестов осталось таким же.</p> <p>Можно пойти дальше и, как полагается, порефакторить тесты:</p> <pre><code class="language-javascript"><span class="hljs-title function_">it</span>(<span class="hljs-string">'works'</span>, <span class="hljs-function">() =></span> { <span class="hljs-title function_">isEmptyString</span>(<span class="hljs-string">'test'</span>); }); </code></pre> <p>Результат снова потрясающий:</p> <pre><code> PASS ./index.spec.js ✓ works (2ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 100 | 100 | 100 | 100 | | index.js | 100 | 100 | 100 | 100 | | ----------|----------|----------|----------|----------|-------------------| </code></pre> <p>Мы выкинули единственную проверку из теста, сделав его, по сути, ещё более бесполезным, но покрытие так и осталось 100%.</p> <h2>Мораль</h2> <p>Покрытие кода работает хорошо как индикатор проблем: если оно низкое, вероятно, тестов написано слишком мало. Но использовать покрытие как индикатор качества тестов нельзя: высокого покрытия можно добиться даже без единой проверки.</p>Альтернатива DuckDuckGo2018-10-28T10:50:00Zhttps://andrew-r.ru/notes/2018-10-28-startpage/<p>Раньше я уже писал, <a href="https://andrew-r.ru/notes/2018-05-13-from-google-to-duckduckgo">почему стоит перейти с поиска Google на DuckDuckGo</a>. Но многие программисты при переходе на DuckDuckGo замечают, что качество поиска сильно падает и искать ответы на технические вопросы становится сложнее.</p> <p>Уже несколько месяцев я использую <a href="https://www.startpage.com/">StartPage</a>: приватный поисковик, который рекомендовал даже Эвард Сноуден. Его преимущество перед DuckDuckGo отлично описано на главной странице:</p> <blockquote> <p>You can’t beat Google when it comes to online search. So we’re paying them to use their brilliant search results in order to remove all trackers and logs. The result: The world’s best and most private search engine.</p> </blockquote>Принцип: сначала платить себе2018-11-20T11:50:00Zhttps://andrew-r.ru/notes/2018-11-20-pay-yourself-first/<p>Сначала платить себе — универсальный жизненный принцип, подробно <a href="https://bureau.ru/bb/soviet/20160421">описанный Николаем Товеровским</a>. Суть в том, что у каждого из нас есть обязательства перед другими людьми или желание кому-то помочь. Но ни помочь, ни выполнить обязательства не получится, если в первую очередь не позаботиться о себе.</p> <p>Например, нельзя заниматься благотворительностью, когда у самого ни гроша в кармане. Нельзя стать более ценным специалистом, не выделяя время на самообразование.</p> <p>Мне этот принцип особенно помогает не уходить с головой в рабочую рутину, учиться и делать сайд-проекты.</p>Как сначала платить себе2018-11-29T07:23:00Zhttps://andrew-r.ru/notes/2018-11-29-how-to-pay-yourself-first/<p>Прошлая заметка была о том, <a href="https://andrew-r.ru/notes/2018-11-20-pay-yourself-first">зачем платить себе</a>, а в этой я расскажу о двух техниках, которые мне помогают в этом.</p> <p>На каждую неделю я составляю краткий план, в который входят мои личные задачи — например, по сайд-проектам или учёбе. Этот план фиксирован, в течение недели он не пополняется — это защищает от раздувания, которое приведёт к его невыполнению. Я составляю план в блокноте, который лежит на рабочем столе — он всегда перед глазами, и в моменты прокрастинации заставляет вспомнить о наличии более полезных дел. План хорошо работает и как визуализация прогресса за неделю.</p> <p>План — половина дела, его ещё нужно выполнять. Очевидный способ — заниматься делами в свободное время после работы. Проблема в том, что после работы мы устаём, и хочется потупить в сериал, а не проходить какой-нибудь сложный курс или делать сайд-проект. Решение — выделять себе утром перед работой время на личные дела и учёбу. Это решение работает, но требует дисциплины и соблюдения режима (нужно рано ложиться и рано вставать). Главное не пытаться резко перестроиться с подъёма в 9 утра на подъём в 5 утра, иначе постигнет разочарование :—)</p>Пока, 20182018-12-29T16:20:49Zhttps://andrew-r.ru/notes/bye-2018/<p>Пользуюсь возможностью порефлексировать и вспомнить значимые для меня события за прошедший год.</p> <h2>Событие года</h2> <p><img src="https://andrew-r.ru/notes/bye-2018/assets/omsk.jpg" alt="Вид из окна в Омске" /></p> <p><em>Обычный вид из окна в Омске</em></p> <p>Летом окончательно попрощался с Омском и перевёз маму в Москву. Самым сложным в этом оказалась трёхдневная поездка на поезде из Омска в Москву.</p> <h2>Мысль года</h2> <blockquote> <p>Понял, что для меня самые морально изнуряющие вещи в программировании — продумывание того, что может пойти не так, и постоянные мысли о недостатках принятых решений. А потом такое мышление в негативном ключе ещё и на обычную жизнь постепенно переносится.</p> </blockquote> <p>Твитер <a href="https://twitter.com/andrew__romanov/status/1025892579529961472">подтвердил</a>, что это распространённая проблема среди программистов.</p> <h2>Работа</h2> <p>В конце января я ушёл из Avito. За год я здорово прокачался, запилил с нуля новый <a href="https://support.avito.ru/">раздел помощи</a> и интегрировал его в мобильные приложения через вебвью, повлиял на развитие общей библиотеки компонентов и нового фронтенд-стека. Последние несколько месяцев работы я вошёл в зону комфорта, дни были похожи друг на друга, а в пятницу было сложно вспомнить, что вообще произошло за неделю помимо обычной работы. В итоге я решил кардинально сменить обстановку.</p> <p>В феврале я присоединился к небольшой команде, которая делает <a href="https://sticker.place/">Sticker.place</a> и ещё пару продуктов. Забавно, что с техдиром мы познакомились ещё в 2015 году благодаря фрилансу. В то время я даже не мог представить, что спустя три года мы будем вместе работать в офисе в Москве.</p> <p>В этом году мы с нуля запилили новый продукт, <a href="https://aivatar.co/">Aivatar</a>, мобильное приложение для генерации персонализированных стикеров. Я полностью отвечал за фронтенд: практически весь интерфейс самого приложения написан на веб-технологиях и работает через вебвью. Для приложения понадобилось сделать несколько админок, в которых мне удалось немного поработать с бэкендом на Java.</p> <p>Параллельно я занимался ещё одним проектом, о котором рассказывать не буду, потому что NDA :–)</p> <p>Впечатления от работы в стартапе после большой компании двоякие — везде свои плюсы и минусы, но в целом я доволен. Три дня в неделю я работаю удалённо: благодаря этому сразу открылись глаза на то, как много времени тратится впустую на дорогу из дома в офис. Работа в маленькой компании ощущается гораздо свободнее, гибче и продуктивнее — это оказалось для меня ценным.</p> <h2>Вокруг работы</h2> <p>Сайд-проект года — <a href="http://github.com/andrew--r/frontend-case-studies">frontend case studies</a>, коллекция материалов о боевом опыте разработки интерфейсов в зарубежных и российских компаниях. Не люблю абстрактные туториалы, зато люблю, когда разработчики рассказывают о решении реальных рабочих задач.</p> <p>Сделал редизайн личного сайта и переписал его сперва с <a href="https://blogengine.ru/">Эгеи</a> на Jekyll, а затем с Jekyll на Gulp. Практически то же самое с <a href="https://forwebdev.ru/">сайтом Форвеба</a>: тоже сделал редизайн, отказался от WordPress в пользу статики на Gulp, удалил большинство статей из-за неактуальности и низкой ценности. Предвосхищая вопрос «почему не Gatsby»: я не верю в статические сайты на Реакте и вообще считаю повальное увлечение Реактом и его экосистемой вредным для веба ¯\_(ツ)_/¯</p> <p>Сделал на коленке международную визитку — <a href="https://andreyromanov.com/">andreyromanov.com</a>. Когда-нибудь, наконец, соберусь и ворвусь на зарубежный рынок (пора бы уже).</p> <p>Поучаствовал в <a href="https://soundcloud.com/frontend-weekend/fw-42">42 выпуске подкаста Frontend Weekend</a>. Кстати, считаю этот подкаст самым интересным среди русскоязычных подкастов о фронтенде.</p> <p><a href="https://bureau.ru/school/certificate/prep/andrey-romanov/">Прошёл подготовительные курсы</a> в Школу Бюро Горбунова. В саму школу я не особо собирался, но когда узнал о лёгких подготовительных курсах, решил поучаствовать, потому что нравится подход к работе и философия бюрошников.</p> <p>Написал кучу заметок в <a href="https://andrew-r.ru/notes">блог</a> и <a href="https://t.me/andrew_r_notes">Телеграм-канал</a>.</p> <p>Боролся с тайпингами в mobx в <a href="https://github.com/mobxjs/mobx/pull/1393">марте</a> и <a href="https://github.com/mobxjs/mobx/pull/1521">апреле</a>.</p> <p>Обнаружил, что в WKWebView на iOS не работает стилизация скролбаров, и <a href="https://github.com/Fyrd/caniuse/pull/4586">указал это на caniuse</a>.</p> <p>Упоролся по эргономике рабочего места. Теперь я использую <a href="https://smartstool.ru/catalog/smartstulya/smartstool-km01l-kolennyij-stul-bez-spinki-s-gazliftom.html">коленный стул</a>, который не даёт сутулиться. Ноутбук ставлю на специальную подставку, чтобы экран был на уровне глаз, а для ввода использую клавиатуру Microsoft Natural Ergonomic 4000 и Apple Magic Trackpad 2.</p> <h2>Поездки</h2> <p>Пока что не до путешествий. В этом году ездил в Омск, Тулу и дважды в Питер — первый раз на pitercss_conf, а второй просто отдохнуть.</p> <h2>Концерты</h2> <p>В этом году вживую увидел Андрея Макаревича, Роджера Уотерса, Brothers Moving, Animal Джаz × 3, Ritchie Blackmore’s Rainbow и Uriah Heep. Блэкмор оказался жутким халявщиком, не оправдавшим потраченных на концерт денег.</p> <h2>Фильмы</h2> <p>Благодаря проекту «КАРО Арт» удалось посмотреть в оригинале и на большом экране фильмы, которые уже не встретишь в прокате: «Сталкер», «Волосы», «Заводной апельсин», «Пролетая над гнездом кукушки», «Космическая одиссея», «Левиафан». Хочется, чтобы такие проекты жили, несмотря на очередные идиотские законы, ограничивающие прокат старых фильмов.</p> <h2>Книги</h2> <p>Вроде удалось нарастить темп чтения книг, но пока что чтение бесполезных статей преобладает, буду над этим работать. В этом году прочитал <a href="https://andrew-r.ru/notes/grokaem-algoritmy">«Грокаем алгоритмы»</a>, <a href="https://andrew-r.ru/notes/microresolutions">«Микрорешения»</a>, <a href="https://andrew-r.ru/notes/red-pill">«Красная таблетка»</a>, <a href="https://andrew-r.ru/notes/weniger-schlecht-programmieren">«Программирование без дураков»</a>, <a href="https://andrew-r.ru/notes/atlas-shrugged">«Атлант расправил плечи»</a>, <a href="https://andrew-r.ru/notes/goldratt-the-goal">«Цель»</a> Голдратта, <a href="https://andrew-r.ru/notes/finplan">«Как составить личный финансовый план и как его реализовать»</a>, «Чёрный лебедь». На середине забросил «Чистую архитектуру», потому что навалились дела — в новом году дочитаю.</p> <h2>Рандомные события</h2> <p>Впервые побывал внутри католического собора и послушал Вивальди на органе.</p> <p>Узнал о существовании мягких зубных щёток.</p> <p>Стал <abbr title="Индивидуальный предприниматель">ИП</abbr> и даже поработал в этом статусе с Яндексом (не фронтендером, просто вакансию для Яндекса публиковал в Форвебе).</p>Наука сна2019-01-08T18:48:00Zhttps://andrew-r.ru/notes/dreamland/<figure> <img alt="Обложка книги" src="https://andrew-r.ru/notes/dreamland/assets/cover.png" height="594" width="422" /> <figcaption>Дэвид Рэндалл, 2014, издательство «Манн, Иванов и Фербер», <span class="nobr">ISBN 978-5-00057-189-7</span></figcaption> </figure> <p>Легко читаемый и интересный обзор истории сна и его аспектов, подкреплённый результатами многочисленных исследований. Мы тратим на сон треть жизни, так что полезно в нём немного разобраться.</p> <h2>Мы спим противоестественно</h2> <p>Наши предки ложились спать с закатом, а их сон был разделен на две половины: около полуночи они просыпались и бодрствовали примерно час, а затем снова засыпали до утра.</p> <p>Люди спали так тысячелетия, пока в наших домах не появилось искусственное освещение, которое нарушило привычный порядок сна. Искусственное освещение сбивает биоритмы и препятствует выработке мелатонина — гормона, помогающего регулировать сон. Именно из-за этого рекомендуется приглушать дома свет и не пользоваться гаджетами за полчаса до отхода ко сну.</p> <p>Одно из исследований показало прямую зависимость между уровнем искусственного освещения в городе и риском развития рака груди у женщин.</p> <h2>Совместный сон вреден</h2> <p>Хоть партнёры и семейные пары предпочитают спать в одной кровати, на самом деле это чаще всего ухудшает качество их сна и приводит чуть ли не к разводам.</p> <h2>Сон с ребёнком</h2> <p>Есть разные мнения о том, как стоит спать маленьким детям: вместе с родителями или отдельно. Единственный риск сна вместе с родителями — потенциальные травмы, которые родители могут нанести малышу во сне. В остальном особой разницы нет. Повзрослев, ребёнок сам захочет спать в отдельной кровати. А до этого момента главное соблюдать постоянство в ритуале укладывания ребёнка.</p> <h2>Толкование снов</h2> <p>Теория Фрейда оказалась несостоятельна: сны вовсе не наполнены скрытыми смыслами. Кевин Холл более тридцати лет записывал и систематизировал сны различных людей, после чего обнаружил, что наши сны в основном предсказуемы и однообразны. В снах мы чаще всего видим то, что нас беспокоит.</p> <p>Интересно, что врачи нашли способ «режиссирования» снов, называемый терапией методом репетиции мысленных образов. С его помощью ветераны войн избавлялись от ночных кошмаров не менее эффективно, чем лекарственными средствами.</p> <h2>Влияние на когнитивные способности</h2> <p>Во время сна мозг переваривает и структурирует всю полученную за день информацию, создаёт новые логические цепочки, отсеивает всё лишнее, освобождая место для новой информации. Многие исследования подтвердили, что сон способствует решению проблем и гибкости мышления. Даже короткий дневной сон восстанавливает когнитивные способности.</p> <p>Целая глава в книге посвящена анализу последствий недостатка сна в армии. Если вкратце, солдаты и в мирное время спят мало, а во время боевых операций и вовсе пренебрегают сном. Это приводит к повышенной эмоциональности, раздражительности и снижению способности принимать рациональные решения.</p> <h2>Лунатизм</h2> <p>Лунатизм не так безобиден, как кажется: страдающие им люди иногда совершают преступления (вплоть до убийств). Пока нет устоявшейся законодательной базы для преступлений, совершённых во сне: разные суды выносят как оправдательные, так и обвинительные приговоры.</p> <h2>Биоритмы</h2> <p>Уровень нашей активности зависит от времени суток. Считается, что большинство из нас чувствуют подъём энергии и бодрость с девяти утра до двух часов дня, а затем многим хочется подремать. С шести до десяти часов вечера большинство чувствует новый прилив сил.</p> <p>Биоритмы синхронизируются с дневным светом, из-за чего и наступает джетлаг: человек слишком быстро перемещается в другой часовой пояс, а его организм не успевает перестроиться и из-за этого страдает. Джетлаг особенно влияет на спорстменов: они показывают себя на соревнованиях и играх хуже, чем обычно, если специально не борются с ним.</p> <p>У подростков биоритмы сдвинуты на три часа, из-за чего им тяжелее рано ложиться и рано вставать на занятия. Некоторые американские штаты перенесли время начало занятий в школах с 7 на 8 часов утра, в результате чего добились повышения успеваемости и снижения агрессии и пропусков занятий среди учеников.</p> <h2>Апноэ</h2> <p>Многие люди страдают синдромом обструктивного апноэ во сне. Это значит, что у них временно останавливается дыхание во время сна. Об этом явлении врачи узнали относительно недавно. Из-за апноэ люди спят урывками, днём страдают от усталости и сонливости, а в долгосрочной перспективе и вовсе подвергаются когнитивным нарушениям и ухудшению памяти. С апноэ борются с помощью специальных аппаратов для искусственной вентиляции, которые предотвращают остановку дыхания.</p> <h2>Бессонница</h2> <p>Врачи мало что знают о бессоннице. Её лучше не лечить медикаментами — они либо имеют вредные побочные эффекты, либо провоцируют временное ухудшение кратковременной памяти, из-за чего наутро человек попросту не помнит, что ему было трудно заснуть (то есть по факту обманывают пациента). Вместо медикаментов лучше попробовать психотерапию, направленную на изменение отношения ко сну: она учит не переживать из-за бессонницы и не переоценивать значимость сна, потому что такое отношение само по себе провоцирует недуг.</p>Тонкое искусство пофигизма2019-01-21T14:32:00Zhttps://andrew-r.ru/notes/the-subtle-art-of-not-giving-a-fuck/<figure> <img alt="Обложка книги" src="https://andrew-r.ru/notes/the-subtle-art-of-not-giving-a-fuck/assets/cover.jpg" height="819" width="570" /> <figcaption>Марк Мэнсон, 2018, издательство «Альпина Паблишер», <span class="nobr">ISBN 978-5-9614-6290-6</span></figcaption> </figure> <h2>О страдании</h2> <blockquote> <p>Сама жизнь — форма страдания. Богачи страдают из-за богатства. Бедняки страдают из-за бедности. Одинокие люди страдают из-за того, что у них нет семьи. Семейные люди страдают из-за проблем в семье. Люди, ищущие мирских удовольствий, страдают из-за мирских удовольствий. Люди, воздерживающиеся от мирских удовольствий, страдают от воздержания.</p> </blockquote> <blockquote> <p>Мы страдаем, потому что страдание биологически полезно. С его помощью природа заставляет нас меняться. Такими нас создала эволюция: мы всегда живём с неудовлетворённостью и беспокойством, поскольку умеренно неудовлетворённое и беспокойное существо прилагает максимум усилий, чтобы создать новшества и выжить.</p> </blockquote> <blockquote> <p>Перестаньте спрашивать себя: «Чем я хочу насладиться?». Задайте другой вопрос: «Какая боль мне по душе?». Дорога к счастью усеяна постыдными провалами и кучами дерьма.</p> </blockquote> <h2>Об уникальности и соцсетях</h2> <blockquote> <p>То, чего стоит человек, определяется его отношением не к позитивному, а к негативному опыту. [...] Человек, который себя уважает, способен честно отнестись к своим недостаткам и постараться исправиться. Но люди, которые полагают, что они всего достойны, не способны ни признать проблемы открыто и честно, ни улучшить свою жизнь сколько-нибудь прочным и осмысленным образом.</p> </blockquote> <blockquote> <p>Чем больше свободы выражения нам дают, тем больше мы хотим освободиться от нужды иметь дело с людьми, которые не согласны с нами или расстраивают нас. Чем чаще мы сталкиваемся с противоположными точками зрения, тем больше мы недовольны, что эти точки зрения существуют. Чем легче и свободнее от проблем становится наша жизнь, тем более уникальными мы считаем оставшиеся проблемы.</p> </blockquote> <blockquote> <p>Поток крайностей в современных медиа и соцсетях заставляет нас верить, что исключительность — это норма. А поскольку обычно с нами ничего особенного не происходит, лавина сообщений об исключительном наводит тоску и отчаяние.</p> </blockquote> <blockquote> <p>Технологии решили старые экономические проблемы, принеся нам новые психологические проблемы. В интернете мы находим не только лёгкий доступ к информации, но и лёгкий доступ к неуверенности, стыду и сомнениям в себе.</p> </blockquote> <h2>О ценностях</h2> <p>Три уровня самопознания:</p> <ol> <li>Обычное понимание своих эмоций.</li> <li>Способность спрашивать себя, почему мы испытываем определённые эмоции.</li> <li>Осознание личных ценностей: почему мы считаем что-то успехом/неудачей, по какому принципу себя оцениваем, по какому критерию судим о себе и об окружающих.</li> </ol> <p>Ценности лежат в основе всего, что мы делаем. Если ценности гнилые, то всё, что на них основано — мысли, чувства, действия — пойдёт наперекосяк.</p> <p>Взгляд на проблемы определяется ценностями и критериями успеха/неудачи. Одни ценности ведут к хорошим проблемам, которые легко и регулярно решаются. Другие ведут к плохим проблемам.</p> <h3>Гнилые ценности</h3> <p>Некоторые расхожие ценности создают людям неприятные проблемы, которые почти не решаются.</p> <ol> <li><em>Удовольствие</em>. Исследования показывают: люди, которые сосредотачивают силы на поверхностных удовольствиях, становятся более тревожными, эмоционально нестабильными и депрессивными. Не нужно гнаться за удовольствием, оно должно возникать само собой, когда мы наладили всё остальное.</li> <li><em>Материальный успех</em>. Как только люди удовлетворяют базовые потребности, корреляция между счастьем и земным успехом быстро стремится к нулю.</li> <li><em>Постоянная правота</em>. Людям присущи постоянные ошибки. Полезнее считать себя невежей и постоянно учиться новому, а не считать себя правым во всём и закрываться от других точек зрения.</li> <li><em>Позитивный настрой</em>. Когда мы заставляем себя сохранять позитив в любой обстановке, мы отрицаем наличие жизненных проблем. А когда мы отрицаем наличие проблем, мы лишаем себя возможности решить их и ощутить счастье. Жизнь такова, что иногда дела идут плохо и мы чувствуем себя хреново. И это нормально. Негативные эмоции — необходимый компонент эмоционального здоровья.</li> </ol> <h2>Принцип «Делайте что-нибудь»</h2> <blockquote> <p>Не сидите сложа руки. Делайте <em>что-нибудь</em>. Ответы придут.</p> </blockquote> <p>Этот принцип перекликается с принципом Людвига Быстроновского «Просто продолжай». В основе лежит то, что действие — не только следствие мотивации, но и его причина.</p> <h2>О здоровых отношениях</h2> <blockquote> <p>Только в нездоровых отношениях люди пытаются решать проблемы друг друга, чтобы ощутить душевный комфорт. В здоровых же отношениях люди решают собственные проблемы из заботы друг о друге.</p> </blockquote> <blockquote> <p>Чтобы отношения были здоровыми, люди должны обладать желанием и умением говорить и слышать «да» и «нет». А если никогда не будет отказов, границы рухнут; проблемы и ценности одного человека возобладают над проблемами и ценностями другого. Поэтому конфликт не только нормален: он <em>абсолютно необходим</em> для сохранения здоровых отношений. Если близкие люди не способны открыто и честно разобраться со своими разногласиями, значит, отношения основаны на лжи и манипуляции и постепенно станут токсичными.</p> </blockquote> <h2>О выборе</h2> <blockquote> <p>«Больше» не всегда «лучше». Зачастую мы счастливее, когда у нас чего-то меньше. Когда мы перегружены возможностями и перспективами, мы страдаем от парадокса выбора. Чем больше возможностей, тем меньше нас удовлетворяет выбранное (а вдруг мы упустили более выгодный вариант?).</p> </blockquote> <blockquote> <p>Если серьёзно вкладываться в одного человека, одно место, одну работ и одну деятельность, это лишит заманчивой широты опыта. Однако широта опыта не позволит вкусить плоды, которые даёт <em>глубина</em> опыта. [...] Чем старше и опытнее вы становитесь, тем больше приедается каждое занятие. [...] Когда мы отказываемся от лишнего и альтернативного в пользу подлинно важного, наши возможности и шансы <em>увеличиваются</em>.</p> </blockquote> <h2>О смысле жизни</h2> <blockquote> <p>Если нет причин делать что-либо, нет и причин ничего <em>не</em> делать и, коль скоро все мы умрём, нет оснований уступать страху, смущению и стыду; избегая почти всю свою короткую жизнь болезненных и дискомфортных для себя вещей мы, по сути, не живём.</p> </blockquote>Форматирование дат и времени в браузере2019-03-11T15:48:00Zhttps://andrew-r.ru/notes/date-time-formatting-in-browser/<p>Не используйте сторонние библиотеки для ручного форматирования дат и времени — в разных локалях разные правила их отображения: русские привыкли видеть 11.03.2019, а англичане 3/11/2019. Используйте встроенный в браузер <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat">Intl API</a>, который сам определяет локаль пользователя и форматирует дату нужным образом.</p> <p>Кстати, недавно пал последний рубеж обороны сторонних библиотек — в Intl API появился <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RelativeTimeFormat">RelativeTimeFormat</a>, позволяющий формировать из дат фразы вида «5 минут назад».</p>Отдельные стрелки на клавиатуре не нужны2019-03-24T16:22:00Zhttps://andrew-r.ru/notes/2019-03-24-goodbye-keyboard-arrows/<p>Простой и действенный лайфхак от Никиты Прокопова: чтобы не дёргать руку от букв к стрелкам на клавиатуре, можно переназначить функции стрелок на клавиши Caps Lock + I/J/K/L и всегда держать руки по центру клавиатуры.</p> <p>Спустя год работы в таком режиме я ни за что не вернусь к стандартной раскладке с отдельными стрелками: теперь очевидно, насколько она неудобна.</p> <p>Попробуйте: <a href="http://tonsky.me/blog/cursor-keys">tonsky.me/blog/cursor-keys</a></p>Learning How to Learn2019-05-03T14:17:00Zhttps://andrew-r.ru/notes/2019-05-03-learning-how-to-learn/<p>Учителя мне часто говорили, что школы и ВУЗы нужны, чтобы научить нас учиться. Чтобы понять, что это не так, достаточно вспомнить распространённую среди студентов практику подготовки к экзамену в ночь перед ним.</p> <p>Курс <a href="https://www.coursera.org/learn/learning-how-to-learn/">Learning How to Learn</a> на Coursera восполняет этот пробел нашей системы образования. В нём рассматриваются нюансы работы нашего мозга и техники эффективного обучения, обоснованные научными исследованиями.</p> <p>Автор курса — <a href="https://barbaraoakley.com/">Барбара Оакли</a>, она же автор книги <a href="https://www.litres.ru/barbara-oakli/dumay-kak-matematik-kak-reshat-lubye-zadachi-bystree-i-effektivnee">"Думай как математик"</a>. Содержимое курса и книги практически идентичны (но в курсе есть тесты и дедлайны), так что можно выбрать, что больше по душе, или одновременно проходить курс и читать книгу для закрепления материала.</p>Как соцсети и интернет мешают учиться и как с этим бороться2019-05-26T19:00:00Zhttps://andrew-r.ru/notes/2019-05-26-social-networks-and-learning/<p>Курс <a href="https://andrew-r.ru/notes/2019-05-03-learning-how-to-learn">Learning How to Learn</a> в каком-то смысле открыл мне глаза на то, как соцсети и интернет мешают эффективно учиться.</p> <p>Во-первых, доступ в интернет провоцирует так называемую иллюзию компетентности: когда изучаемый материал легко доступен, люди часто убеждают себя в том, что они его и так хорошо знают. Это особенно заметно, когда видишь пример решения какой-то задачи: кажется, что всё легко и очевидно, но когда возникает потребность решить задачу самостоятельно, начинаются трудности. Избежать иллюзии компетентности можно только намеренно проверяя свои знания с помощью тестов, задач или объяснений другим людям.</p> <p>Во-вторых, я заметил за собой, что соцсети и в целом интернет переключают мышление в режим бесконечного потребления информации: открыл какую-нибудь статью, начал читать, попутно открыл ещё пять ссылок, взялся за них — и всё по новой. Такой режим мышления распространился и на чтение книг и просмотр докладов. Проблема в том, что так информация практически не усваивается и постоянно перезатирается новой информацией.</p> <p>Справиться с этим помогают <a href="https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%B5%D0%BD%D0%B8%D1%8F">интервальные повторения</a> — они переносят информацию в долговременную память. А максимизировать пользу от повторений помогает не простое перечитывание материала, а его намеренное вспоминание — оно значительно углубляет и укрепляет сформированные нейронные связи.</p>Расстрельный список препаратов2019-06-20T20:40:00Zhttps://andrew-r.ru/notes/2019-06-20-fake-drugs/<p>Если вы не изучали медицину, не очень просто разобраться в том, что за лекарства вам выписал врач. Некоторые из них могут не иметь доказанной эффективности, а значит, нет смысла покупать и принимать их.</p> <p>Чтобы зря не спонсировать фармакологические компании, проверяйте, не входят ли выписанные лекарства в <a href="https://encyclopatia.ru/wiki/%D0%A0%D0%B0%D1%81%D1%81%D1%82%D1%80%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BF%D1%80%D0%B5%D0%BF%D0%B0%D1%80%D0%B0%D1%82%D0%BE%D0%B2">расстрельный список препаратов</a>.</p>Правозащита2019-07-30T19:52:00Zhttps://andrew-r.ru/notes/2019-07-30-rights-protection/<p>Невозможно игнорировать тот <a href="https://zona.media/article/2019/07/30/mosgorduma-212">пиздец, который сейчас происходит в Москве</a>. На недавней акции протеста бесправно задержали больше тысячи человек (в их числе мои знакомые); повезло тем, кого отпустили без протоколов, не повезло тем, кому присудили штрафы и аресты. На некоторых уже шьют уголовные дела.</p> <p>Просто хочу напомнить об организациях, которые безвозмездно борются за наши конституционные права.</p> <p><a href="https://zona.media/">Медиазона</a> фиксирует всё происходящее, каким страшным бы оно ни было. <a href="https://www.agora.legal/">Агора</a>, <a href="https://ovdinfo.org/">ОВД-Инфо</a>, <a href="https://fbk.info/blog/post/513/">ФБК</a> и <a href="https://orpravo.org/">Правозащита Открытки</a> защищают граждан, пострадавших от неправомерных действий полицейских и государства.</p> <p>Все эти организации занимаются действительно важным делом <em>(а не всеми этими стартапами)</em>. Они никогда не выйдут на IPO, у них нет шикарных офисов, о которых пишут на VC и Хабре. По большей части они существуют за счёт пожертвований. Эти ребята действуют в наших, а не своих личных интересах. Если вы разделяете их миссию — вы знаете, что делать.</p>Сокращение рабочей недели2019-09-21T21:02:00Zhttps://andrew-r.ru/notes/reducing-work-week/<blockquote> <p>Работать нужно не 12 часов, а головой. © Стив Джобс</p> </blockquote> <p>Недоумеваю от того, что программисты стремятся увеличивать зарплату, а не уменьшать количество часов в рабочей неделе. Айтишникам с их перегретыми зарплатами на жизнь хватает с достатком, так что можно безболезненно пожертвовать частью достатка в обмен на свободное время.</p> <p>Зачем уменьшать рабочее время? Ходят слухи, что из стандартных 8 рабочих часов продуктивно работать получается от силы 5—6 часов. На что уходит оставшееся время, думаю, каждый сам для себя ответит. Офисным работникам хуже всего, удалёнщикам, которым не нужно трекать время, попроще — принёс дневной объём пользы и пошёл заниматься своими делами, не дожидаясь окончания рабочего дня.</p> <p>Тест-драйв сокращённой рабочей недели можно устроить, не меняя работодателя и не уходя на фриланс: для этого можно использовать накопленный отпуск. С работодателем можно договориться о сокращении рабочего дня или недели в счёт отпуска. Я использовал отпуск, чтобы пару месяцев поработать в формате четырёхдневной рабочей недели с выходными по средам. Рекомендую попробовать.</p>Плоти нологи2019-10-15T20:30:00Zhttps://andrew-r.ru/notes/ploti-nologi/<p>Считаю, что одно из лучших решений для повышения сознательности граждан — переложить отвественность за уплату налогов с работодателей на сотрудников.</p> <p>Многие думают, что с зарплаты отчисляется всего 13% налогов. Более осведомлённые ребята знают, что помимо НДФЛ есть ещё множество страховых взносов, которые зарплату «на руки» в 100 000₽ превращают в ~150 000₽ для работодателя. Но осведомлённость не повышает осознанность; о налогах вспоминают, лишь когда о них заходит разговор.</p> <p>Нужно начислять сотрудникам зарплату до вычета налогов, чтобы они раз в квартал или в конце года сами заносили налоговой кровно заработанные. Чем больше будет разрыв между начислением зарплаты и уплатой налогов, тем обиднее будет расставаться с деньгами. Чувство собственности вынудит задаться вопросами, на что и насколько эффективно тратятся деньги.</p> <p>Ради интереса можете посчитать, <a href="http://salary.com.ru/">сколько денег ваш работодатель платит государству</a>.</p>Синдром text neck2019-11-24T15:10:00Zhttps://andrew-r.ru/notes/text-neck/<p>Прочитал недавно <a href="https://multimedia.scmp.com/lifestyle/article/2183329/text-neck/">статью</a> о вреде наклона головы при пользовании телефоном. Прикреплённая картинка говорит сама за себя.</p> <p>Ирония в том, что я вспомнил об этой статье от дискомфорта в шее, сидя за ноутбуком с такой же наклонённой головой. В очередной раз напоминаю себе и вам, что экран должен располагаться на высоте глаз: нужен внешний монитор или подставка для ноутбука. Раньше дома я использовал подставку <a href="https://www.therooststand.com/">Roost</a>, сейчас пользуюсь ей на работе вместе с внешним монитором, рекомендую. Ну и не рекомендую ставить ноутбук под внешний монитор и посматривать в экран ноутбука, очевидно, это не избавит вас от проблем.</p>Блокировка рекламы на уровне DNS2019-12-01T06:23:00Zhttps://andrew-r.ru/notes/dns-adblock/<p>Безумно изящная идея — блокировать рекламу и трекеры прямо на уровне DNS, а не в браузере. При таком подходе трекеры не могут следить за тем, какие сайты вы посещаете, а рекламные скрипты в принципе не скачиваются, что ускоряет загрузку сайтов и снижает нагрузку на сеть.</p> <p>Я знаю два решения, реализующих этот подход: <a href="https://pi-hole.net/">Pi-hole</a> и <a href="https://nextdns.io/">NextDNS</a>. Pi-hole — опенсорсное решение, требующее самостоятельной нетривиальной настройки, но полностью контролируемое вами и никем другим. NextDNS — по сути Pi-hole as a Service, требует минимальной настройки, но и не даёт гарантий того, что логи ваших DNS-запросов никуда не сливаются.</p> <p>Оба решения предоставляют дашборды с красивой статистикой, из которой можно, например, узнать, куда в фоне стучится ваш смартфон.</p>Может перестанем называть доступностью UX и юзабилити?2019-12-16T22:47:00Zhttps://andrew-r.ru/notes/a11y-ux-usability/<p><em>Осторожно! Слово «доступность» встречается в этой статье 15 раз.</em></p> <div class="sidenote sidenote--image"> <p class="sidenote__paragraph">Последнее время я наблюдаю раскол фронтенд-сообщества на два лагеря: одни активно продвигают доступность, а другие искренне не понимают, зачем им тратить на неё время. Вместо диалога между этими двумя лагерями складывается только неприятие друг друга, сарказм в обе стороны, оскорбления и прочие сопутствующие нашему токсичному сообществу прелести.</p> <aside class="sidenote__note"> <img alt="Иллюстрация токсичности" src="https://habrastorage.org/webt/gc/5s/bi/gc5sbicaanyos3-z0vwr3ccpkfc.png" style="max-width: 150px;" width="512" height="512" /> </aside> </div> <div class="sidenote"> <p class="sidenote__paragraph">Тема доступности отчасти перекликается и с темой быстродействия. Здесь сообщество тоже делится на два лагеря: первые продвигают оптимизацию быстродействия, а вторые задаются вопросом, зачем им это, ведь у них ничего не тормозит и загружается быстро (<s>ох уж эти программисты, которые открывают локалхост на топовом макбуке</s>).</p> <aside class="sidenote__note"> Однажды я зарегистрировал домен <a href="http://bundlepositive.dev/">bundlepositive.dev</a>, чтобы опубликовать на нём призыв остановить фэтшейминг JS-бандлов. Но руки пока не дошли. </aside> </div> <p>К текущей ситуации привели разные проблемы:</p> <ul> <li>противоречия в статьях (сегодня мы <a href="https://habr.com/ru/post/465623/">делаем на <code><details></code> семантичные и доступные аккордеоны</a>, а завтра оказывается, что <a href="https://daverupert.com/2019/12/why-details-is-not-an-accordion/"><code><details></code> не подходит для аккордеонов</a>);</li> <li>отсутствие <a href="https://open-ui.org/">единой терминологии</a> для описании UI-компонентов и их свойств;</li> <li>отсутствие необходимых примитивов в веб-платформе или хотя бы в NPM (чёрт возьми, в каждом проекте приходится подключать жирные библиотеки или писать с нуля дейтпикеры, тултипы, комбобоксы и другие сложные компоненты).</li> </ul> <p>Но есть, как мне кажется, более глобальная проблема.</p> <h2>Искажённое восприятие</h2> <p>У Сбербанка есть <a href="http://specialbank.ru/guide/">руководство по разработке доступных цифровых продуктов</a> для дизайнеров, разработчиков и менеджеров. Начинание отличное, но встречает нас оно следующей фразой:</p> <blockquote> <p>Доступность — качество цифровой среды, характеризующее степень её приспособленности для людей с инвалидностью.</p> </blockquote> <p>Эта фраза подкрепляет и так распространённое убеждение о том, что доступность — исключительно про инвалидов. Более того, во многих статьях и руководствах делается акцент на поддержке скринридеров, чтобы интерфейсом могли пользоваться слепые и слабовидящие люди. Из-за этого доступность часто ассоциируется со скринридерами.</p> <div class="sidenote"> <p class="sidenote__paragraph">Разработчики обычно либо не обладают статистикой применения скринридеров в их продукте, либо знают, что их продуктом не пользуются через скринридеры.</p> <aside class="sidenote__note"> Многие разрабатывают внутренние проекты, у которых нет слабовидящих пользователей </aside> </div> <p><strong>Здесь кроется большая проблема коммуникации между евангелистами доступности и разработчиками.</strong> Мне кажется, на слова <em>доступность</em> и <em>скринридер</em> в нашем сообществе уже распространился феномен баннерной слепоты. И я понимаю, почему: когда тебе часто говорят о том, что ты <em>должен</em> поддерживать скринридеры, а в твоём случае это попросту нецелесообразно, ты поначалу злишься, а затем просто забиваешь.</p> <h2>Что на самом деле значит «доступность»</h2> <p>Мне нравится определение доступности из статьи <a href="https://www.cjcid.com/articles/unexpected-a11y-tips/">Unexpected accessibility tips</a> (<em>вольный перевод</em>):</p> <blockquote> <p>Доступность — не только про инвалидов. [...] Это забота об удобстве использования продукта в любых обстоятельствах, с которыми сталкиваются пользователи, и в любой среде (например, на разных устройствах).</p> </blockquote> <p>Статья немного в другом контексте, но она наводит на отличные примеры ситуаций и ограничений, с которыми должны справляться наши интерфейсы:</p> <ul> <li>поездки в метро (телефон держат одной рукой, координация усложнена из-за движения поезда, интернет нестабильный и медленный);</li> <li>демонстрации через проектор (контрастность снижена, расстояние до картинки сильно больше, чем за монитором);</li> <li>потерялись очки (увеличилась нагрузка на зрение, стало сложнее читать текст);</li> <li>беспроводная мышь разрядилась (остаётся только пользоваться интерфейсом с клавиатуры).</li> </ul> <p>Все эти примеры ни разу не про инвалидность или скринридеры, а про нашу с вами жизнь. И, честно говоря, называть это <em>доступностью</em> я бы перестал — ведь есть более знакомые всем понятия UX и юзабилити.</p> <h2>Заключение</h2> <p>Чтобы никто не истолковал вышесказанное превратно, подведу итог. В моей картине мира доступность — синоним UX и юзабилити. Отсутствие статистики по использованию скринридеров не означает того, что можно отказываться от нативной семантики (заголовки, секционные элементы, ссылки, кнопки, etc) и всё верстать дивами — нативная семантика облегчает жизнь не только скринридерам, но и обычным пользователям (кнопка-div с клавиатуры не нажмётся, ссылка-div не скопируется и не откроется в новой вкладке). А главное, помните, что нам платят деньги не за двигание пикселей по экрану, а за разработку удобных и решающих свои задачи интерфейсов.</p>Пока, 20192019-12-30T17:32:00Zhttps://andrew-r.ru/notes/bye-2019/<p>Фиксирую значимые для меня события за прошедший год и немного рефлексирую.</p> <h2>Работа</h2> <p>В марте этого года я присоединился к команде маркетплейса <a href="https://www.joom.com/ru/about">Joom</a>, и на текущий момент это лучшая компания из тех, где я работал. В Joom сильная команда, интересные и масштабные задачи, а ещё по-настоящему чувствуется забота о сотрудниках.</p> <p>Я отвечаю за систему обработки обращений пользователей в службу поддержки. В Авито я работал над аналогичной системой, но здесь всё интереснее: я пишу систему с нуля, общение с сервером происходит по вебсокету, система поддерживает разные языки, так как Joom работает во множестве разных стран. Большинство фронтенд-проектов живёт в монорепозитории, мы пишем и поддерживаем общие компоненты и утилиты.</p> <figure> <img alt="Печеньки с логотипом React" src="https://andrew-r.ru/notes/bye-2019/assets/react-cookies.jpg" width="738" height="500" /> <figcaption>Так мы отмечали запуск новой React-версии одного из проектов</figcaption> </figure> <h2>Форвеб</h2> <p>Запустил регулярный <a href="http://forwebdev.ru/digest">дайджест новостей</a> для тех, кто не хочет ежедневно следить за соцсетями. Теперь трачу на него час каждые две недели.</p> <p>Неудачно попробовал делегировать оформление публикаций. Придётся попробовать ещё раз, иначе у меня так и не будет времени на что-то новое.</p> <h2>Цель последних трёх лет</h2> <p>В этом году я осуществил цель последних трёх лет: купил квартиру. Исторически сложилось так, что последние 20 лет у моей семьи (то есть у меня и мамы, хах) не было собственного жилья, и я очень рад, что эту проблему удалось решить.</p> <p>Практически сразу же после покупки мы взялись за ремонт, и такая спешка была ошибкой: она привела к незапланированным тратам и хаосу в процессе. Стоило начать с дизайн-проекта и чёткого списка пожеланий и действий.</p> <h2>Поездки</h2> <h3>Калининград</h3> <p>В этом году я дважды побывал в Калининграде: первый раз летом приехал на выходные, второй раз осенью на тимбилдинге со своей командой. Куршская коса восхитительна, а сам город жаль — практически вся историческая застройка была разрушена войной.</p> <p>В первую же поездку в Калининград осуществил своё давнее желание сходить в кино в другом городе: посмотрел в оригинале фильм «Охотник на оленей», который очень удачно оказался в прокате благодаря компании «Иноекино».</p> <figure> <img alt="Селфи на фоне дома советов" src="https://andrew-r.ru/notes/bye-2019/assets/kenigsberg.jpg" style="max-width: 20rem;" width="1000" height="1209" /> <figcaption>Недостроенный Дом Советов в Калининграде</figcaption> </figure> <h3>Сочи</h3> <p>В Сочи я ездил на новогодний корпоратив Joom. Больше всего меня поразили первые минуты после выхода из аэропорта: конец декабря, на улице светит солнце и люди ходят практически без курток. После серой и холодной Москвы это казалось немыслимым.</p> <figure> <img alt="Вид на горы в Сочи" src="https://andrew-r.ru/notes/bye-2019/assets/sochi.jpg" width="1600" height="1200" /> <figcaption>Просто красивый вид из Сочи</figcaption> </figure> <h3>Омск</h3> <p>В Омске я в этом году был трижды: первый раз приезжал летом поностальгировать, второй раз в августе покупал квартиру, третий раз в ноябре приехал на пару недель разобраться с бюрократическими делами. Успел сходить в гости в свою школу, повидаться со знакомыми и друзьями (некоторые из которых вообще случайно оказались в Омске в то же время, что и я), перепрописаться, закрыть ИП, начать ремонт, отдохнуть.</p> <figure> <img alt="Вид на Иртыш в Омске" src="https://andrew-r.ru/notes/bye-2019/assets/omsk.jpg" width="1600" height="1200" /> <figcaption>Кусочек лета из Омска</figcaption> </figure> <h2>Концерты</h2> <p>В этом году помимо уже посещённых ранее Animal Джаz, Машины времени, Uriah Heep и Joe Lynn Turner довелось впервые сходить на Metallica, Muse, Son Lux и Steven Wilson (последний сыграл, наверное, лучший концерт в моей жизни). Я собирался сходить и на Evanescence, но по своей глупости забыл о концерте и вспомнил о нём только когда увидел в соцсетях восторженные отзывы посетителей 🌚.</p> <h2>Книги</h2> <p>В этом году прочитал ненамного больше книг, чем в прошлом (11 против 8), что печально — перейти от статей к книгам оказалось не так просто. Зато вернулся к чтению художественной литературы, не один же научпоп читать. Также прекратил публиковать конспекты и обзоры прочитанных книг: пользы в этом мало, конспекты я никогда не перечитываю.</p> <p>Список этого года:</p> <ul> <li><a href="https://andrew-r.ru/notes/dreamland">Наука сна</a></li> <li><a href="https://andrew-r.ru/notes/the-subtle-art-of-not-giving-a-fuck">Тонкое искусство пофигизма</a></li> <li>Deadline. Роман об управлении проектами</li> <li>Сделай это завтра</li> <li>Управление проектами, людьми и собой</li> <li>11/22/63</li> <li>Думай как математик</li> <li>Learning English</li> <li>Искусство быть невидимым</li> <li>Краткая история времени</li> <li>Мистер Мерседес</li> </ul> <p>Только в этом году принял и начал применять хорошую мысль о том, что можно читать параллельно несколько разных книг, не нужно браться за одну и не начинать ничего, пока не закончишь её. Раньше я сильно тормозил на неинтересных или сложных книгах, теперь же у меня в процессе 3–4 книги, за которые я берусь по настроению.</p> <h2>Android → iOS</h2> <p>В этом году впервые перешёл с Android на iOS. Если не считать очень странную работу с файлами на iOS, всё остальное просто отлично. В iOS чувствуется уважение к пользователю и забота о нём. До сих пор каждый день радуюсь стандартному приложению Bedtime и анализу времени сна.</p> <h2>Психотерапия</h2> <p>Одно из лучших начинаний этого года — регулярные занятия с психотерапевтом. Я созваниваюсь с терапевтом раз в неделю уже более полугода, и благодаря этому я стал лучше разбираться в своих желаниях и чувствах, избавился от некоторых проблем вроде чрезмерной тревожности, осознал наличие и причины других проблем.</p> <h2>Барабаны</h2> <p>В прошлый новый год я решил, что мне нужно повысить технику игры на барабанах. После недолгих поисков нашёл преподавателя и стал ходить к нему раз в неделю. Купил новый пэд и палочки, заказал домой тренировочную ударную установку. Примерно через полгода я прекратил занятия.</p> <p>На каждое занятие я приходил с чувством вины за то, что толком не подготовился. Вместо отработки упражнений я чаще садился играть любимые песни. Поразмышляв над этим, я понял, что барабаны для меня — способ выплеснуть энергию, отвлечься от рутины и получить удовольствие от игры. Поставив себе абстрактную цель «поднять технику игры» и записавшись к преподавателю, я превратил способ отдыха в обязательство, которое меня тяготило.</p> <h2>Английский</h2> <p>Не ставил себе никаких целей по изучению языка, просто подписался на Netflix и стал смотреть все сериалы в оригинале. В какой-то момент заметил, что стал гораздо лучше воспринимать на слух английскую речь: стал различать тексты любимых песен, которые раньше для меня были неразличимым набором звуков. Теперь осталось придумать, как так же ненапряжно прокачать разговорный и письменный английский.</p> <h2>Разное</h2> <p>Под конец года стал выгружать в Things все дела и идеи, которые приходят в голову. Сразу стало легче — не нужно всё это держать в голове и бояться что-то забыть.</p> <p>Занялся <a href="https://andreyromanov.com/">англоязычной версией своего сайта</a>: добавил разделы Bookshelf и Bookmarks, перевёл пару заметок на английский. Составил большой список того, что хочу видеть на сайте, в новом году планирую заняться этим.</p> <p>Начал пользоваться в работе эргономичной клавиатурой <a href="https://kinesis-ergo.com/shop/advantage2/">Kinesis Advantage 2</a> и трекболом <a href="https://www.logitech.com/en-us/product/mx-ergo-wireless-trackball-mouse">Logitech MX Ergo</a>.</p> <p>Прошёл курс Learning How to Learn, рекомендую всем, кто хочет разобраться в механизмах эффективного обучения.</p> <p>Стал вести бюджет в приложении <a href="http://youneedabudget.com/">You Need a Budget</a> вместо excel-таблички. Во-первых, это удобнее — теперь всё записываю через мобильное приложение, во-вторых, YNAB предлагает более осознанный подход к бюджетированию, чем просто пассивная запись расходов в табличку.</p> <p><a href="https://ru.hexlet.io/blog/posts/osnovatel-for-web-andrey-romanov-o-razrabotke-obuchenii-i-perspektivah-frontenda">Пообщался с ребятами из Хекслета</a> в формате интервью.</p> <p>Приобрёл два USB-ключа <a href="https://onlykey.io/">OnlyKey</a>, но пока не до конца разобрался со всеми их возможностями, только немного поигрался.</p> <p>Перешёл с GMail на <a href="http://fastmail.com/">Fastmail</a>, пока всё нравится. Помимо почты пользуюсь их календарём, заметками и адресной книгой.</p> <p>Провёл первый аудит безопасности: удалил все сохранённые пароли из Chrome (которым я больше не пользуюсь), везде поменял пароли и включил 2FA.</p> <p>Начал использовать <a href="http://nextdns.io/">NextDNS</a> для блокировки рекламы и трекеров на уровне DNS.</p> <p>Запилил <a href="https://andrew-r.ru/timeline">визуализацию длительности жизни</a>, чтобы не забывать о том, что время — невосполнимый и самый ценный ресурс.</p> <p>Впервые покатался на гидроцикле и на вертолёте.</p> <p>Посетил хоккейный матч Авангард — Спартак.</p> <h2>Что было не очень</h2> <p>Давно говорю о вреде соцсетей, но меньше ими не пользуюсь. Периодически удаляю и заново устанавливаю Инстаграм, переводил телефон в чёрно-белый режим и возвращался обратно к обычному, пробовал ограничивать время пользования приложениями. Давно чувствую проблемы с вниманием и концентрацией, и пока борьба с соцсетями не привела к их решению.</p> <p>Из-за тех же проблем с вниманием и концентрацией хаотично читаю статьи вместо книг.</p> <p>Брался за чтение технических книг вроде «Дискретной математики для программистов», но не хватило усидчивости и мотивации, чтобы планомерно осваивать материал.</p> <p>Плохо поддерживал связь с друзьями и редко куда-то выбирался с ними. Иногда возникает ощущение, что я просто не понимаю, как это должно работать, из-за чего становится грустно.</p> <p>Слишком быстро ввязывался в отношения, не разбираясь в своих мотивах и не устанавливая правильные границы.</p> <p>Подолгу не отвечал людям в мессенджерах, потому что не было на это сил.</p> <p>Регулярно делал зарядку каждое утро в течение пары месяцев, а потом забил.</p> <p>По будням часто спал меньше восьми часов.</p> <h2>Заключение</h2> <p>Я разочаровался в целеполагании, так что не собираюсь больше ставить себе никаких целей, всё равно к концу года они окажутся неактуальны или более не интересны.</p> <p>Зафиксирую лучше текущие мысли и желания:</p> <ul> <li>пора заняться здоровьем, устранить давние болячки и убедиться в отсутствии новых (или присутствии, здесь уж как повезёт);</li> <li>в жизни стало слишком много рутинной деятельности, нужно освобождать время для обучения, экспериментов и чего-то нового;</li> <li>жить в Москве некомфортно, после ремонта хочу попутешествовать и понять, где было бы комфортнее;</li> <li>надо бы более позитивно смотреть на мир, а то сейчас во всём ищу проблемы и ошибки.</li> </ul>Глобальный .gitignore2020-01-14T19:11:00Zhttps://andrew-r.ru/notes/2020-01-14-global-gitignore/<p>В <code>.gitignore</code> проекта часто добавляют файлы, относящиеся не к проекту, а к окружению и инструментам разработчика. Пример:</p> <pre><code>.idea node_modules dist .DS_Store .vscode </code></pre> <p>Из пяти строк только две касаются непосредственно специфики проекта — <code>node_modules</code> и <code>dist</code>. Чем больше разработчиков работают над проектом, тем больше шума будет в <code>.gitignore</code>.</p> <p>Хорошее на мой взгляд решение — вместо добавления нерелевантных данных в <code>.gitignore</code> проекта завести персональный глобальный <code>.gitignore</code> и добавить в него всё, что касается вашего окружения, будь то настройки <abbr class="caps" title="Integrated development environment">IDE</abbr> или генерируемые операционной системой файлы:</p> <pre><code>$ git config --global core.excludesfile ~/.gitignore_global </code></pre> <p>Достаточно сделать это один раз и забыть о случайных коммитах с лишними файлами и последующем обновлении локальных <code>.gitignore</code>. Если вы работаете в команде и кто-то присылает на кодревью <code>.gitignore</code> со специфичными для его окружения файлами, попросите его завести глобальный <code>.gitignore</code> — так вы избавите его от проблем не только в вашем проекте, но и в любых других.</p>Говорить в мире собеседника2020-01-30T20:09:00Zhttps://andrew-r.ru/notes/2020-01-30-speak-clearly/<p>Мой опыт работы начался с фриланса. Неотъемлемой частью было общение с заказчиками: сбор требований, согласование работы, решение проблем вроде сдвинутых сроков.</p> <p>В те времена я совершал ошибку, которую теперь сам периодически замечаю среди других людей. Я говорил не в мире собеседника.</p> <p>Объясню на примере. Представьте, что человеку нужно сделать сайт, а он в этом ничего не понимает. Он нанимает специалиста и в какой-то момент интересуется, как продвигается работа. Ему отвечают:</p> <blockquote> <p>У меня возникли проблемы с модулем кеширования страниц в WordPress, почитал StackOverflow, там рекомендуют обновиться до PHP 7, тем более в нём появились декларации типов, которые очень полезны в разработке. Я обновил PHP, но из-за этого пришлось решать проблемы с новым механизмом обработки ошибок. К счастью, как раз сегодня закончил с этим разбираться и доделал главную страницу, двигаюсь дальше!</p> </blockquote> <p>Что из этого понятно заказчику:</p> <blockquote> <p><s aria-hidden="true" class="spoiler">У меня возникли проблемы с модулем кеширования страниц в WordPress, почитал StackOverflow, там рекомендуют обновиться до PHP 7, тем более в нём появились декларации типов, которые очень полезны в разработке. Я обновил PHP, но из-за этого пришлось решать проблемы с новым механизмом обработки ошибок. К счастью, как раз сегодня закончил с этим разбираться и</s> доделал главную страницу, двигаюсь дальше!</p> </blockquote> <p>Иногда люди считают, что собеседник обладает не меньшими знаниями, чем они сами, и начинают грузить его ненужной, непонятной и часто страшно звучащей для него информацией. Ситуацию усугубляет ещё и часто сопутствующее этой проблеме неумение отвечать на поставленный вопрос.</p> <p>Чтобы не совершать этих ошибок, можно пользоваться двумя приёмами:</p> <ol> <li>Отвечать на вопросы максимально коротко, не погружаясь в детали. Собеседник при необходимости переспросит или уточнит важные ему детали.</li> <li>В разговоре ставить себя на место собеседника и стараться оценить, будете ли вы ему понятны.</li> </ol> <p>Это сильно упростит жизнь вам и всем, с кем вы будете разговаривать.</p>Один удалённо — все удалённо2020-03-09T18:30:00Zhttps://andrew-r.ru/notes/2020-03-09-one-remote-all-remote/<p>В частично распределённых командах работающие удалённо сотрудники могут испытывать сложности на совещаниях. Их забывают подключать, не дают слова, забивают на слышимость. В результате комфортно только тем, кто присутствует на совещании лично.</p> <p>Подсмотрел у команды <a href="https://miro.com/">Miro</a> принцип, помогающий справиться со сложностями: «один удалённо — все удалённо». Если хоть один участник совещания не может присутствовать лично, все остальные тоже подключаются удалённо. Это обеспечивает равные условия и комфорт: все говорят в собственные микрофоны, все видят друг друга (если камеры включены), никто не чувствует себя отчуждённым.</p>Зачем изучать HTML и CSS: статья Хекслета2020-03-21T10:58:00Zhttps://andrew-r.ru/notes/2020-03-21-hexlet-html-css/<p class="subtitle">В блоге Хекслета вышла обзорная <a href="https://ru.hexlet.io/blog/posts/zachem-izuchat-html-i-css-ili-kogda-roboty-zamenyat-verstalschikov-na-rynke-truda">статья об актуальности и перспективах изучения HTML и CSS</a>. Ребята попросили нескольких специалистов поделиться мнением, и в их числе оказался я. Читайте полную версию статьи в блоге Хекслета, а здесь — мои ответы на вопросы.</p> <h2 class="h3">Как вы считаете, каким специалистам нужно изучать HTML и CSS? Будет ли полезным знание HTML/CSS людям, которые не занимаются вёрсткой и программированием?</h2> <p>В первую очередь изучать HTML и CSS нужно фронтендерам, ведь это то, с чем они работают каждый день. Во вторую очередь это полезно веб-дизайнерам: чтобы проектировать интерфейсы для веба, нужно знать ограничения и возможности платформы. Если дизайнер умеет верстать, это делает его более ценным специалистом: он может гораздо быстрее проверять идеи, делая живые прототипы интерфейса прямо в браузере.</p> <p>Знание основ HTML и CSS пригодится всем, кто пишет и редактирует статьи: не знаю, как сейчас, но раньше в Т—Ж авторы сами верстали свои статьи. Также знание основ пригодится владельцам небольших сайтов: они смогут решать мелкие задачи самостоятельно, не прибегая к помощи программистов.</p> <h2 class="h3">Насколько, по вашему, актуальна профессия «верстальщик»? Чистые верстальщики нужны на рынке труда?</h2> <p>Я начинал карьеру как раз с чистой вёрстки, но работал на фрилансе. Это не лучший формат работы для начинающих: без портфолио и отзывов сложно искать заказы, поток заказов нестабильный, перенимать опыт не у кого. К тому же для успешной работы в формате фриланса важны не только навыки вёрстки, но и так называемые «софтскиллы»: переговоры и общение с заказчиком, оценка сроков, управление временем и рисками.</p> <p>Если говорить о работе в штате какой-либо компании, я давно не видел вакансий именно про вёрстку. На российском рынке в основном ищут именно фронтендеров, которые умеют не только верстать, но и программировать. На западе, насколько я знаю, вообще профессии «верстальщик» как таковой нет: там вёрсткой занимаются либо те же фронтендеры, либо дизайнеры.</p> <p>На мой взгляд, ограничиваться вёрсткой нельзя. При этом вполне реально научиться вёрстке, устроиться в какую-нибудь компанию младшим фронтендером и дальше расти именно во фронтенде.</p> <h2 class="h3">С финансовой точки зрения изучение вёрстки перспективно? Куда лучше идти верстальщику за деньгами: на биржи, в компании на фултайм, ещё куда-то?</h2> <p>Изучение вёрстки перспективно, если на нём не останавливаться и дальше расти во фронтенде. Или если вёрстка поможет вам эффективнее работать по основной специальности. Например, если вы веб-дизайнер.</p> <p>Если всё же рассматривать заработок на чистой вёрстке, мне кажется, больше возможностей для заработка на фрилансе. Во-первых, на фрилансе попросту больше потребность в верстальщиках, чем у компаний. Во-вторых, на фрилансе можно брать деньги за результат, а не за потраченное время, что потенциально выгоднее фултайм-работы с фиксированным окладом.</p> <h2 class="h3">Провокационный вопрос: можно ли работать программистом и не знать HTML и CSS?</h2> <p>Определённо можно. По сути любая специальность, кроме фронтенда, не требует знаний HTML и CSS: бэкенд, devops, data science, мобильная разработка.</p> <p>Провокационным вопрос становится, если заменить «работать программистом» на «работать фронтендером». Я слышал, что в некоторых компаниях есть практика разделения фронтендеров на верстальщиков и программистов клиентской логики. Но полной независимости при таком разделении всё равно не добиться: в разработке интерфейсов всё равно есть задачи на стыке вёрстки и программирования. Пример такой задачи — разработка сложного UI-компонента вроде селекта (выпадающий список опций для выбора) или саджеста (поле ввода с подсказками).</p> <h2 class="h3">Не заменят ли верстальщиков технологии в обозримом будущем? Насколько реально проиграть конкуренцию программе, которая автоматически конвертирует условный макет из PSD в HTML?</h2> <p>Визуальные конструкторы сайтов существуют довольно давно, точно больше пяти лет, но спрос на фронтендеров пока только растёт :–) Основная область применения таких конструкторов — обычные контентные сайты: визитки, лендинги, блоги и журналы. Здорово, что такие конструкторы есть, потому что они позволяют быстро и дёшево собирать несложные страницы, не погружаясь ни в какие технологии.</p> <p>С более сложными интерфейсами никакие конструкторы и плагины для Photoshop/Sketch/Figma пока достойно не справляются, да и вряд ли станут: нужно учитывать слишком много аспектов вроде плавных анимаций, мультиязычности (адаптация интерфейса к RTL-языкам), корректного отображения на разных устройствах (от ноутбуков до умных часов).</p> <h2 class="h3">Вёрстку изучать проще, чем программирование? Сколько времени нужно, чтобы с нуля научиться верстать макеты средней сложности?</h2> <p>Я начинал с изучения вёрстки именно потому, что мне это далось легче, чем программирование. Сложно давать какие-то прогнозы по времени, потому что я изучал вёрстку в других условиях. За последние годы порог входа в вёрстку снизился: браузеры стали работать более или менее одинаково, исчезла необходимость помнить и использовать страшные хаки для обхода проблем в разных браузерах, появились более удобные и логичные API: флексы и гриды вместо флоатов.</p> <p>Мне кажется, лучше всего найти и спросить о времени обучения тех, кто недавно устроился на работу младшим фронтендером.</p>Не просите время у бизнеса2020-03-25T07:13:00Zhttps://andrew-r.ru/notes/2020-03-25-dont-ask-for-time/<p class="subtitle">Рефакторинг, доступность, быстродействие, тесты: эти вещи объединяет фраза «бизнес не выделит на это время».</p> <p>Некоторые инженеры почему-то решают, что на рефакторинг или тесты нужно просить время у заказчика. Это ставит их в неловкое положение. Во-первых, это слабая позиция с точки зрения переговоров — вы пришли к заказчику с просьбой, а не он к вам, очевидна ваша нужда. Во-вторых, заказчикам обычно неинтересны или непонятны технические детали, поэтому просьба в духе «мне нужен день на рефакторинг» звучит для них как «мне нужен день непонятно на что вместо важных и понятных улучшений».</p> <p>Чуть лучше, когда инженеры не просто озвучивают свою потребность в рефакторинге, а подкрепляют её пользой. Например, «мне нужен день на то, чтобы навести порядок внутри системы: благодаря этому я сделаю задачу N за день вместо четырёх, а другим разработчикам будет проще вникнуть в код».</p> <p>Откуда вообще возникают такие просьбы? По моему опыту, так случается, когда задачи выполняются как можно скорее, жертвуя тестами, качеством кода и чем угодно ещё. Такой подход даёт выгоду только в краткосрочной перспективе, в долгосрочной техдолг копится, замедляет и усложняет разработку. Когда становится совсем невмоготу, разработчики осознают, что маленьким локальным рефакторингом не обойтись, и идут просить у бизнеса время на борьбу с техдолгом.</p> <p>Более здоровый подход, работающий в долгосрочной перспективе — когда рефакторинг, тесты, документация и прочие технические работы становятся частью любой продуктовой задачи. Не нужно считать рефакторинг или написание тестов отдельными видами работ, которые нельзя сделать в рамках какого-то улучшения продукта и на которые нужно выделять отдельное время. Заложите эти работы в оценку продуктовой задачи и просто сделайте их вместе с задачей. Тогда, как говорится, и волки будут сыты, и овцы целы.</p>Windows и редактирование RTL-текста2020-05-23T00:00:00Zhttps://andrew-r.ru/notes/2020-05-windows-text-direction/<p class="subtitle">В моём рабочем проекте поддерживается ввод текста на <abbr title="Right-to-left">RTL</abbr>-языках. Реализуется это добавлением атрибута <code>dir="auto"</code> к полям ввода. Однажды я столкнулся с жалобой на очень странное поведение: на Windows переключение раскладки клавиатуры меняло направление всего набранного текста.</p> <p>Ситуация следующая: пользователь пишет текст на арабском, браузер корректно распознаёт направление текста и отображает его справа налево. В какой-то момент пользователю нужно написать фрагмент текста <span class="nobr">по-английски</span>, и при переключении раскладки весь набранный текст меняет направление с <abbr title="Right-to-left">RTL</abbr> на <abbr title="Left-to-right">LTR</abbr>. Приходится копировать набранный текст и перезагружать страницу, чтобы восстановить корректное направление.</p> <p>После исследования контекста и поисков в интернете я выяснил источник проблемы. Стандартное сочетание клавиш для переключения раскладки было изменено пользователем на <kbd>Ctrl</kbd> + <kbd>Shift</kbd>. В то же время в Windows сочетания клавиш <kbd>Ctrl</kbd> + <kbd>Left Shift</kbd> и <kbd>Ctrl</kbd> + <kbd>Right Shift</kbd> <a href="https://superuser.com/a/471699">меняют направление набираемого текста</a> на <abbr title="Left-to-right">LTR</abbr> и <abbr title="Right-to-left">RTL</abbr> соответственно. Поэтому при переключении раскладки также срабатывало изменение направления текста.</p> <p>Я долго искал в интернете способ отключить это сочетание клавиш на Windows (судя по вопросам на форумах, этим озадачился не только я), но так ничего и не нашёл. Поэтому на уровне системы эта проблема лечится, похоже, только сменой сочетания клавиш для переключения раскладки.</p> <p>Эту проблему можно предотвратить на уровне приложения, если вы заранее знаете направление текста, который будет вводиться в конкретное поле. Явное задание направления текста через атрибут <code>dir</code> в разметке не помогает, а вот задание <code>direction</code> через CSS — помогает. Чтобы не заморачиваться каждый раз со стилями, я продолжил пользоваться атрибутом <code>dir</code>, но добавил в таблицу стилей глобальное правило:</p> <pre><code class="language-css"><span class="hljs-selector-attr">[dir=<span class="hljs-string">"rtl"</span>]</span> { <span class="hljs-attribute">direction</span>: rtl; } </code></pre>Публичные CDN и кеширование2020-08-20T00:00:00Zhttps://andrew-r.ru/notes/2020-08-partitioned-http-caches/<p class="subtitle">Один из аргументов в пользу публичных CDN — подключенные с CDN библиотеки загружаются пользователем один раз и кешируются браузером для всех сайтов, использующих их.</p> <p>Больше на это полагаться не стоит. Браузеры считают такой подход к кешированию небезопасным: он позволяет определить, посещал ли пользователь определённые сайты, то есть поощряет слежку за пользователями.</p> <p>Проблему слежки решает <a href="https://github.com/shivanigithub/http-cache-partitioning/">разделение кеша</a> <em>(cache partitioning)</em>: каждый сайт должен иметь собственный кеш для подключаемых на нём ресурсов. Если facebook.com и vk.com подключают с cdnjs.cloudflare.com jQuery одинаковой версии, пользователь при заходе на них скачает jQuery дважды.</p> <p>Оказывается, в <a href="https://andydavies.me/blog/2018/09/06/safari-caching-and-3rd-party-resources/">Safari</a> кеш разделён аж с 2013 года. В <a href="https://chromestatus.com/feature/5730772021411840">Chrome</a> с 77 версии тоже ведётся работа по разделению кеша. Инженеры Mozilla активно помогают в <a href="https://github.com/whatwg/fetch/issues/904">стандартизации</a> этого поведения.</p> <h2>Обновление от 7 октября 2020</h2> <p>Разработчики Chrome <a href="https://developers.google.com/web/updates/2020/10/http-cache-partitioning">объявили</a>, что начиная с 86 версии кеш в Chrome тоже будет разделён.</p>Мама2020-10-23T00:00:00Zhttps://andrew-r.ru/notes/2020-10-23/<p class="subtitle">8 октября внезапно не стало моей мамы.</p> <p>С начала года до августа я доделывал ремонт дома в Омске и к августу мама, наконец, переехала <em>домой</em> после 20 лет скитаний со мной по разным странам и городам. Спустя 13 лет изнурительной работы по шесть дней в неделю преподавателем гитары она, наконец, начала жить ради себя и в своё удовольствие: рисовать картины, вязать мягкие игрушки, писать рассказы, смотреть фильмы, читать книги, гулять по родному городу и фотографировать, приглашать в гости и самой ходить к друзьям и знакомым.</p> <p>Мне очень обидно, что это продлилось так мало. Что нашим совместным планам теперь не сбыться. Что я не обо всём рассказывал маме, думая, что ещё успеется. Что я уехал в Москву в сентябре и с тех пор увиделся с ней только по прилёте в Омск на выходные в конце сентября. Что я раздражался, когда она просила сфотографировать её или сфотографироваться вместе; а теперь для меня так ценны те фотографии, что мы всё же сделали.</p> <p>Мне страшно, потому что мама была моим единственным близким родственником. Мне страшно, что мои воспоминания о ней со временем будут угасать. Мне горько думать о том, как мимолётна наша жизнь и как резко она может оборваться, только начав налаживаться.</p> <p>Строка из песни «Раненый в висок» группы Uma2rman теперь особенно остро отзывается:</p> <blockquote> <p>...кто-то ушёл в мир иной, все остальные в пути.</p> </blockquote> <figure> <img alt="Я и мама" src="https://andrew-r.ru/notes/2020-10-23/assets/photo.jpg" width="960" height="720" /> <figcaption>26 сентября 2020</figcaption> </figure>Идеальный сервис для прослушивания музыки2020-11-28T00:00:00Zhttps://andrew-r.ru/notes/ideal-music-service/<p class="subtitle">На дворе 2020 год, а все сервисы для прослушивания музыки, которые я пробовал (Яндекс.Музыка, Spotify, Deezer, Google Play Music, Apple Music), работают отвратительно.</p> <p>Мой субъективный гигиенический минимум для таких сервисов:</p> <ol> <li><strong>Кроссплатформенность и синхронизация фонотеки между девайсами.</strong> Здесь нечего добавить.</li> <li><strong>Простой и понятный интерфейс</strong>, который в первую очередь показывает твою фонотеку (исполнители, альбомы, треки, жанры), а не рекомендации и подкасты.</li> <li><strong>Стабильность фонотеки.</strong> Привет Яндекс.Музыке, в которой я постоянно обнаруживаю то дубли альбомов, то отсутствие добавленных ранее песен.</li> <li><strong>Переключение треков без задержек.</strong> Отсутствие этого особенно бесит, когда слушаешь что-то вроде Pink Floyd, где на протяжении всего альбома один трек плавно переходит в другой.</li> <li><strong>Возможность скачивания музыки на девайс.</strong> Как целыми альбомами, так и отдельными треками (привет, Spotify, в котором отдельный трек нельзя скачать без диких ухищрений вроде его добавления в специально созданный плейлист для скачивания).</li> <li><strong>Предсказуемая и стабильная работа в офлайне</strong> (например, в самолётах). Снова привет Яндекс.Музыке, у которой в офлайне половина песен пропадает, остальные перемешиваются (в прямом смысле, в альбом заходишь, а там песни в неправильном порядке), а у большинства песен не отображаются обложки альбомов.</li> </ol> <p>Не такие важные для меня, но приятные возможности:</p> <ol> <li><strong>Просмотр текстов песен.</strong> Оценил эту возможность в Яндекс.Музыке.</li> <li><strong>Встроенный поиск треков по записи</strong> (аля Shazam). Тоже удобно, чтобы не держать отдельное приложение.</li> <li><strong>Экспорт и импорт метаданных фонотеки.</strong> Невозможно запомнить все треки, которые когда-то понравились, хотелось бы владеть данными фонотеки и иметь возможность их выгрузить и импортировать в любом сервисе (заодно это бы облегчило переход между сервисами).</li> </ol>Подход Fail Fast2021-01-12T00:00:00Zhttps://andrew-r.ru/notes/fail-fast/<p>В разработке есть довольно здравые и широко применимые подходы, о которых почему-то редко рассказывают в учебных курсах или книгах, и ты со временем либо интуитивно приходишь к ним сам (зачастую не в состоянии осознанно их сформулировать), либо узнаешь о них случайно. Для меня одним из таких подходов оказался Fail Fast. Давно о нём где-то поверхностно услышал и отложил в закладки, а сейчас добрался до исходной <a href="https://www.martinfowler.com/ieeeSoftware/failFast.pdf">статьи Джима Шора Fail Fast</a> (PDF, ~120 КБ).</p> <p>Джим пишет, что одни из самых неприятных багов — те, которые проявляются не сразу и вызывают отложенные последствия, по которым сложно определить исходную проблему. Такие баги обычно вызваны тем, что в процессе выполнения программы ошибки всеми силами глушатся и игнорируются, чтобы сохранить работоспособность программы.</p> <p>Рецепт борьбы с такими багами — подход Fail Fast, предполагающий немедленное сообщение о проблеме при её обнаружении. Джим предлагает покрывать для этого код специальными проверками в неоднозначных местах, где что-то может пойти не так.</p> <p>Как раз недавно столкнулся на работе с проблемой, которую можно было бы предотвратить с помощью этого подхода. Моё приложение ожидает, что при запуске в переменных окружения будет задан определённый внешний URL, который открывается в iframe на одной из страниц приложения. При запуске нового экземпляра приложения эта переменная окружения ошибочно была задана с неправильным названием. Приложение успешно поднялось, а спустя некоторое время пользователи сообщили, что iframe перестал работать.</p> <p>Джим приводит аналогичный пример про конфигурацию прямо в начале своей статьи: при отсутствии значения в конфигурации лучше вообще не запускать приложение и выводить сообщение о проблеме, чем использовать какое-то значение по умолчанию. В первом случае разработчик попробует запустить приложение и получит ошибку вида «Ключ ____ отсутствует в конфигурации», а во втором случае он уйдёт отдыхать и узнает о проблеме от недовольных пользователей, которые не могли решать свои задачи всё время его отсутствия.</p> <p>Важно отметить, что этот подход не про полную остановку программы при обнаружении ошибки, а в первую очередь про то, чтобы сделать проблемы заметными. Джим приводит в пример пакетную обработку данных, при которой можно обойтись отправкой сообщения об ошибке разработчику (например, через <a href="https://sentry.io/">Sentry</a>) и ненавязчивым сообщением пользователю о том, что часть данных обработать не удалось. Основная идея в том, чтобы разработчик узнал о проблеме как можно раньше и чтобы сообщение о проблеме направило его как можно ближе к причине.</p>Список сделанных рабочих задач2021-06-02T00:00:00Zhttps://andrew-r.ru/notes/work-log/<p>Рутинная, но крайне полезная практика — ведение списка сделанных рабочих задач. Я веду такой список с февраля 2017 года и могу точно сказать, чем я занимался на работе в любую из недель с той даты.</p> <p>Каждую неделю я завожу в списке отдельную секцию с временным диапазоном (например, «24–28 мая 2021») в заголовке и в течение недели записываю сделанные задачи.</p> <p>Основная польза в том, что мне больше не нужно в разных ситуациях мучительно вспоминать, чем я занимался и что сделал. А такие ситуации в моём опыте возникают постоянно:</p> <ol> <li>Ежедневные встречи с командой, на которых рассказываешь, чем занимался вчера.</li> <li>Еженедельные отчёты о проделанной командой работе, для которых каждый член команды составляет список сделанных им задач.</li> <li>Ретроспектива: понять, на что ушло время вместо важных запланированных задач.</li> <li>Performance review: выделение основных достижений за последние полгода.</li> <li>Подготовка к поиску работы: список освежает память при составлении резюме и рассказе о прошлом опыте.</li> </ol> <p>В общем, горячо рекомендую завести себе такой список и снять с себя когнитивную нагрузку в описанных (и не только) случаях.</p>Как упростить кодревью2022-02-20T00:00:00Zhttps://andrew-r.ru/notes/easy-code-review/<p class="subtitle">Делюсь проверенными на личном опыте способами упростить ревью вашего кода и в целом повысить культуру кодревью в компании.</p> <h2>📏 Размер пулреквестов — 80% успеха</h2> <p>Чем меньше пулреквесты — тем лучше. Большие PR демотивируют ревьюеров и снижают качество ревью, потому что сложно удерживать объём изменений в голове.</p> <p>Если получается большой пулреквест, возможно вы смешали в нём правки в разных частях системы, требуемые для решения исходной задачи. Такие правки обычно можно разбить на отдельные PR: например, отделить новые UI-компоненты от реализации страницы с их использованием.</p> <p>Ещё один случай больших PR — рефакторинги. Обычно их можно разделить на две части:</p> <ol> <li>Содержательные изменения в какой-то части системы</li> <li>Шаблонное обновление остального кода</li> </ol> <p>Отделите первое от второго, и вы сэкономите время и силы ревьюеров, которым не очень важен бойлерплейт.</p> <p>В завершение про размер пулреквестов: не поддавайтесь соблазну докинуть новую функциональность в уже открытые PR. Новая функциональность — новый пулреквест. Это опять же про самодостаточные изменения и внимание ревьюеров, которое не бесконечно.</p> <h2>📝 Описание изменений</h2> <p>Ревьюерам сильно поможет внятное описание ваших изменений. Какую задачу вы решали, почему решили её именно так, на что стоит обратить внимание.</p> <p>Не ленитесь описать это прямо в PR, не все открывают прилинкованные задачи, и в них может не хватать контекста.</p> <p>В идеале описание нужно сопроводить ссылкой на тестовый стенд, где изменения можно оценить вживую. Если стенда нет, позаботьтесь о том, чтобы проект было легко запустить локально, и сопроводите описание скриншотами конечных изменений (актуально для фронтендеров).</p> <p>Если пулреквест содержит неочевидные решения или хаки, от которых не избавиться, заранее сопроводите их комментариями прямо в коде — для ревьюеров и для будущих поколений, которые будут читать этот код.</p> <p>Не стесняйтесь сопроводить PR собственными комментариями на отдельных участках кода, чтобы:</p> <ul> <li>указать порядок чтения;</li> <li>прояснить мотивацию к конкретным изменениям;</li> <li>явно обратить внимание на сложные места;</li> <li>явно запросить обратную связь, если сомневаетесь в своём коде.</li> </ul> <h2>🕵️ Ревью собственных пулреквестов</h2> <p>Прежде чем запрашивать ревью у других разработчиков, просмотрите свои изменения, будто кто-то другой запросил у вас ревью. Так вы сэкономите своё и чужое время, сразу обнаружив очевидные косяки вроде опечаток или забытого кода для отладки.</p> <h2>🔔 Уведомления о ревью</h2> <p>Чтобы вам и ревьюерам не приходилось вручную следить за запросами ревью, комментариями, апрувами и прочим, настройте уведомления о них в рабочем мессенджере. Например, я использую <a href="https://slack.github.com/">интеграцию GitHub и Slack</a>.</p> <h2>🤝 Явное обозначение намерений в комментариях</h2> <p>У вас будет больше взаимопонимания, если ревьюер явно обозначит, какие замечания считает критичными, а какие можно проигнорировать.</p> <p><a href="https://conventionalcomments.org/">Conventional Comments</a> — хороший пример готовых соглашений для обозначения намерений.</p>