<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://blog.krasman.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 14:35:08 GMT</lastBuildDate><atom:link href="https://blog.krasman.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Найм в IT. Как правильно проводить технические собеседования?]]></title><description><![CDATA[Ранее я описывал свое видение организации процесса найма и построения команды в общих чертах. В данной статье хочу рассказать подробнее про этап технического интервью. Тема горячая и всегда бурно обсуждаемая в комьюнити. Споры про "Зачем спрашивать а...]]></description><link>https://blog.krasman.dev/najm-v-it-kak-pravilno-provodit-tehnicheskie-sobesedovaniya</link><guid isPermaLink="true">https://blog.krasman.dev/najm-v-it-kak-pravilno-provodit-tehnicheskie-sobesedovaniya</guid><category><![CDATA[hiring]]></category><category><![CDATA[Job Interview]]></category><category><![CDATA[Technical interview]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Sun, 02 Jul 2023 21:07:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688331351796/60e557e8-4fa5-4474-99d5-ff9531dc8db7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ранее я описывал свое видение <a target="_blank" href="https://blog.krasman.dev/najm-v-it-postroenie-processa">организации процесса найма и построения команды</a> в общих чертах. В данной статье хочу рассказать подробнее про этап технического интервью. Тема горячая и всегда бурно обсуждаемая в комьюнити. Споры про "Зачем спрашивать алгоритмы если я верстаю кнопочки на Реакте?", похоже, будут жить вечно и всегда будут актуальны.</p>
<p>Для наглядности будут показаны примеры конкретных задач для позиции Senior Frontend Developer. Как и в прошлый раз, сама роль и стек технологий не играет большой роли. Если бы я нанимал DevOps или QA - подход был бы примерно тот же.</p>
<p>За начальное условие мы берем уже как-то <a target="_blank" href="https://blog.krasman.dev/najm-v-it-postroenie-processa">настроенный процесс найма</a> с воронкой из нескольких этапов, где мы уже отсеяли большую часть входного потока кандидатов на этапе L0 (HR) и L1 (технический скрининг). Если человек не может внятно ответить как работает Event loop в JS и bubbling/capturing DOM-событий - воронка должна это отловить как можно раньше. До того как он попадет на главное 2-3 часовое тех. интервью с самыми экспертными ребятами из вашей команды.</p>
<h2 id="heading-struktura-intervyu">Структура интервью</h2>
<ol>
<li><p>Знакомство. Предыдущий опыт (5-15 минут)</p>
</li>
<li><p>Валидация знаний по основному стеку из резюме (10-20 минут)</p>
</li>
<li><p>Практические задачи (50-90 минут)</p>
</li>
<li><p>System design (15-30 минут)</p>
</li>
<li><p>Понимание agile-процессов (10-15 минут)</p>
</li>
<li><p>Продаем кандидату нашу вакансию (10-20 минут)</p>
</li>
</ol>
<p>Иметь четкий план на интервью всегда лучше, чем импровизация. Поэтому, перед тем как собеседовать кандидатов, интервьюеру будет полезно проделать определенную домашнюю работу и стандартизировать процесс. Я обычно стараюсь поместить кандидатов в более-менее одинаковые условия по формату, сложности задач, продолжительности интервью. Так легче объективно оценивать уровень экспертности инженеров и сравнивать их между собой.</p>
<p>В целом, все интервью построено по нарастанию сложности вопросов и задач. Это не значит, что в начале мы спрашиваем элементарные вещи из документации или туториалов. Для этого в воронке найма есть предыдущий этап скрининга. На главном техническом интервью не должно быть легких вопросов, которые гуглятся за 5 секунд. Особенно на позицию Senior.</p>
<p>Практические задачи - обязательная и самая важная часть интервью, она же самая стрессовая для кандидата. Поэтому, я подвожу интервью к ней плавно, давая человеку время почувствовать себя в своей тарелке, привыкнуть к обстановке и набрать уверенности.</p>
<h3 id="heading-znakomstvo-predydushij-opyt">Знакомство. Предыдущий опыт</h3>
<p>Знакомство - процесс обоюдный. Перед тем как начать спрашивать кандидата про его опыт, неплохо хотя бы парой фраз рассказать кто вы и чем занимаетесь в данной компании.</p>
<p>В начале интервью даем кандидату время, чтобы он спокойно без стресса рассказал то, что он точно знает - чем он занимался на последнем месте работы. Меня в первую очередь интересует не стек технологий, проект или архитектурные решения, а такие вещи как: а) Размер команды? б) Роль и зона ответственности кандидата? в) Кто принимал основные технические решения в команде? г) Есть ли менеджерский опыт или только технический? д) Как были устроены процессы внутри команды?</p>
<p>Я считаю это более важным, потому что для человека проще изучить новую библиотеку для state management, чем перейти из микро-стартапа где всего 10 человек в крупную компанию, где 10 человек только коммитят с тобой в одну репу каждый день. Не все готовы работать с огромным объемом чужого кода и искать компромиссы. Не все готовы работать по четким процессам с оценками и сроками. Если для вас это актуально - будет не лишним обратить внимание насколько вы подходите друг другу с кандидатом концептуально.</p>
<p>Так же, я всегда обращаю внимание на тон, в котором человек отзывается о коллегах и начальстве на предыдущем месте. Поверьте, лучше не нанимать людей, у которых всегда кто-то виноват, а он за всех все сделал бы лучше. Техническая экспертиза в таком случае для меня уже не играет роли т.к я всегда настроен на долгосрочное сотрудничество и хочу быть уверен, что мы сможем работать в одной команде на протяжении нескольких лет.</p>
<h3 id="heading-validaciya-znanij-po-osnovnomu-steku-iz-rezyume">Валидация знаний по основному стеку из резюме</h3>
<p>Продолжаем разговор в области, которую кандидат знает хорошо(но это не точно...). Можно довольно быстро померить глубину технических знаний, если начать задавать advanced-вопросы по тому стеку, который человек считает основным для себя. Даже если этот стек не пересекается с нашим. Пример:</p>
<p>У нас на проекте React, но человек больше работал с Angular - не страшно. Я спрошу как работает change detection и zone.js в Angular. Зачем в Angular модули (NgModule) и как работает Dependency Injection. Если человек эксперт в своем текущем стеке - он так же глубоко изучит и новый. Плюс это лучше выявляет синьйорность, чем работал/не работал с библиотекой X.</p>
<p>Так же работает в обратную сторону. Если знания поверхностные даже в технологиях, с которыми человек работает каждый день - половину следующих вопросов из категории "сложные" можно будет даже не задавать и сэкономить себе время.</p>
<p>Другой пример - TypeScript. В резюме есть у каждого frontend-разработчика, но большинство его знают и используют процентов на 30. Затипизировать пропсы в React-компоненте могут многие, но это мало что говорит об уровне знаний языка. Вот пара примеров, как можно быстро определить эксперта в TypeScript.</p>
<p><strong>Пример 1:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> arr1 = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">3</span>]
<span class="hljs-keyword">const</span> arr2 = arr1.filter(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item !== <span class="hljs-literal">null</span>)
</code></pre>
<p>Какой тип <code>arr1</code>? Ответ - <code>(number | null)[]</code></p>
<p>Какой тип <code>arr2</code>? Ответ - <code>(number | null)[]</code></p>
<p>Как нам сделать так, чтобы TypeScript понял, что мы отфильтровываем <code>null</code> в рантайме и вывел <code>arr2</code> тип как <code>number[]</code> в compile-time? А если без кастинга <code>as</code> ?</p>
<details>
  <summary>Решение</summary>
<pre>function isNonNullable(value: T | null | undefined): value is T {
  return value !== undefined &amp;&amp; value !== null
}
const arr1 = [1, 2, null, 3]
const arr2 = arr1.filter(isNonNullable)
</pre>
</details>

<p><strong>Пример 2:</strong></p>
<p>Написать хелпер, который может из любого интерфейса достать поля определенного типа:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Project = {
  name: <span class="hljs-built_in">string</span>
  description: <span class="hljs-built_in">string</span>
  start_date: <span class="hljs-built_in">Date</span>
  end_date: <span class="hljs-built_in">Date</span>
}

<span class="hljs-keyword">type</span> ProjectDates = PickByType&lt;Project, <span class="hljs-built_in">Date</span>&gt;
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//    start_date: Date;</span>
<span class="hljs-comment">//    end_date: Date;</span>
<span class="hljs-comment">// }</span>

<span class="hljs-keyword">type</span> ProjectInputFields = PickByType&lt;Project, <span class="hljs-built_in">string</span>&gt;
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//    name: string;</span>
<span class="hljs-comment">//    description: string;</span>
<span class="hljs-comment">// }</span>
</code></pre>
<details>
  <summary>Решение</summary>
<pre>
type PickByType&lt;Target, Condition&gt; = Pick&lt;
  Target,
  { [Key in keyof Target]: Target[Key] extends Condition ? Key : never }[keyof Target]
&gt;
</pre>
</details>

<p>Решение занимает пару строк и пару минут времени. Если кандидат не справляется - пока ничего страшного, идем дальше. Но если справляется - скорее всего перед вами настоящий технарь, который копается в технологиях глубже чем большинство. Хороший знак.</p>
<h3 id="heading-prakticheskie-zadachi">Практические задачи</h3>
<p>Существует много хейта в сторону leetcode-задач. Основная претензия - нерелевантность реальным задачам на работе. React-разработчику не нужно знать алгоритмы и т.п. С алгоритмами история отдельная - ты их либо знаешь либо нет, и это действительно не самый лучший показатель для найма. Но если задача универсальная и не требует каких-то специфических знаний кроме знаний языка - это как раз то, что нужно для объективной оценки software-инженера. Причем, как hard, так и soft-навыков. Помимо программирования, кандидат демонстрирует навык обработки и уточнения требований к задаче. Быстро ли он сдается, если что-то не получается. Проявляет ли он раздражение при этом или самоиронию. Все это не менее важно чем скорость и корректность написанного кода.</p>
<p>Я обычно начинаю с задачки на логику, без программирования. Например - Какой угол составляют часовая и минутная стрелка в 15:15? Отличный способ размяться для кандидата и настроиться на практическую часть интервью. Задача совсем не про программирование, но на практике, те кто ее заваливает, так же не блещут и в следующих задачах с кодом. Далее начинаем писать код и постепенно повышаем сложность. Пользоваться гуглом в процессе разрешается.</p>
<p><strong>Задача #1</strong></p>
<p>Реализовать функцию <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce">reduce()</a> из стандартной библиотеки JS.</p>
<p>Не должно занимать много времени. Если закапываемся на этом месте - можно досрочно завершать интервью и сэкономить всем полтора часа времени.</p>
<p>После успешной реализации, прошу затипизировать функцию как в стандартной библиотеке TypeScript с выводами типов аргументов и результата. Еще одна проверка на экспертность в TS. Так же не является приговором если человек не справится.</p>
<p><strong>Задача #2</strong></p>
<p>Реализовать функцию <a target="_blank" href="https://lodash.com/docs/4.17.15#throttle">throttle()</a> из <a target="_blank" href="https://lodash.com/">lodash</a>. Можно без третьего аргумента, чтобы сэкономить время.</p>
<p>Хороший знак, если программист сразу видит решение через рекурсию, а не спустя 30 минут тщетных попыток покрыть все кейсы дополнительными флагами и условиями в коде.</p>
<p><strong>Задача #3</strong></p>
<p>Выполнить code-review уже <a target="_blank" href="https://codesandbox.io/s/lively-morning-skjsi?file=/src/Select/Select.tsx">реализованного, адаптивного React-компонента</a>. Найти проблемы и отрефакторить.</p>
<p>В отличие от предыдущих, данная задача максимально приближена к реалиям. Для меня очень важно под каким углом человек анализирует код на ревью. Если он фокусируется на code-style, названии переменных, замене if/else на тернарный оператор - это скорее тревожный звонок. Синьйор в первую очередь должен найти концептуальную ошибку в данном коде. Если просто представить как мы будем использовать этот компонент в реальном приложении, станет очевидно, что интерфейс компонента некорректный и его нужно полностью менять.</p>
<p>Как всегда, задача со звездочкой - затипизировать компонент как можно универсальней для compile-time на TypeScript.</p>
<h3 id="heading-system-design">System design</h3>
<p>Опциональная секция. Имеет смысл обсуждать если практическая часть пройдена уверенно.</p>
<p>У frontend-разработчиков я обычно прошу спроектировать небольшое React-приложения, описать его структуру и выделить слои для разделения логики. В React-мире нет какой-то канонической архитектуры, скорей наоборот. Поэтому настоящего синьйора/архитектора определить довольно просто. Если он решает любую задачу по шаблону как в туториалах: компоненты + хуки + Redux - это точно не он. Опытный программист уже видел проекты, которые писались по такому шаблону и у него должен сработать инстинкт самосохранения. Настоящий инженер при проектировании не ищет самый короткий путь как реализовать исходные требования, т.к понимает, что это лишь отправная точка, а, в первую очередь, старается создать благоприятные условия для дальнейших изменения в проекте для себя и коллег по команде. В первую очередь он хочет спокойно спать по ночам при добавлении новой фичи в проект и не гадать, какие модули он мог случайно задеть в процессе.</p>
<p>Когда я собеседовал Golang-разработчиков, просил спроектировать бэкенд архитектуру для сервиса с котировками акций, в который будет ходить фронтенд для отрисовки realtime-графиков. Принцип тот же самый: по туториалу такую задачу не решить, только на опыте.</p>
<h3 id="heading-ponimanie-agile-processov">Понимание agile-процессов</h3>
<p>Секция так же опциональная. Актуально для компаний где эти процессы есть)</p>
<p>Для таких компаний при найме будет полезно обсудить на берегу с кандидатом, насколько он готов играть по четким правилам и насколько он командный игрок. Есть такой тип инженеров, которые считают любые процессы лишней бюрократией и всячески пытаются их избежать. Вместо этого они хотят писать код без оценок и сроков, по принципу: когда сделаем - тогда сделаем.</p>
<p>Для моей компании процессы и сроки были очень важны, поэтому я прощупывал этот момент на собеседовании. Помимо матчасти про Scrum/Kanban, можно попросить рассказать детально весь цикл разработки от оценки до релиза на прод на текущем месте работы. Очень важно, чтобы горизонт разработчика не ограничивался мержем ветки в master и он понимал весь жизненный цикл разработки продукта.</p>
<h3 id="heading-prodaem-kandidatu-nashu-vakansiyu">Продаем кандидату нашу вакансию</h3>
<p>Если мы дошли до этого этапа - значит все хорошо и мы скорее всего будем делать человеку офер. Теперь наша задача - повысить вероятность того, что он его примет. На самом деле мы уже многое для этого сделали: подготовились к интервью, провели его по четкому плану без заминок и неловких пауз, порешали(успешно!) вместе интересные технические задачи - это всегда дает позитивный заряд.</p>
<p>Осталось рассказать про вашу команду и проекты, делая фокус на том что вас выделяет на фоне остальных. Например, у вас передовые технологии: k8s / GRPC / CI/CD / e2e-тесты / screenshot-тесты / micro-frontends / module-federation и т.д - большой плюс. Если у вас по-настоящему крутые процессы - расскажите про это. Мало у кого они есть. Если у вас подобралась звездная команда - используйте это как рекламу. Люди тянутся к тем у кого можно чему-то научиться.</p>
<p>Но главное - вы сами на протяжении всего интервью должны быть рекламой вашей компании. Нужно показать, что вы компетентны, но не душнить. Задавать сложные вопросы, но на легкой волне, не напрягая излишне обстановку. Не стоит сидеть 3 часа с серьезным лицом и гонять человека по списку вопросов как на экзамене. Харизма, чувство юмора - все можно пустить в ход если для вас это естественно в обычной жизни. Будет лучше, если кандидат в вашем лице увидит живого человека, с которым он с удовольствием пообщался бы после интервью. А не профессора, от которого хочется убежать как только получил зачет.</p>
<h2 id="heading-zaklyuchenie">Заключение</h2>
<p>Пишите в комментариях, если интересно продолжение цикла статей. Делитесь своим опытом и подписывайтесь на <a target="_blank" href="https://t.me/bro_science_dev">канал в Telegram</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Релокация в Дубай. Фриланс виза]]></title><description><![CDATA[Данную статью можно рассматривать как полный гайд по релокации в Дубай для тех, кто работает удаленно или на себя как фрилансер. Мой процесс легализации завершился в марте 2023, но я планирую в будущем поддерживать актуальность гайда, обновляя информ...]]></description><link>https://blog.krasman.dev/relokaciya-v-dubaj-frilans-viza</link><guid isPermaLink="true">https://blog.krasman.dev/relokaciya-v-dubaj-frilans-viza</guid><category><![CDATA[Dubai]]></category><category><![CDATA[digitalnomad]]></category><category><![CDATA[freelance visa dubai]]></category><category><![CDATA[Freelancing]]></category><category><![CDATA[taxes]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Sat, 01 Apr 2023 16:36:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1680345787457/2f034a3a-1d1c-43a5-8191-68e0760fa14d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Данную статью можно рассматривать как полный гайд по релокации в Дубай для тех, кто работает удаленно или на себя как фрилансер. Мой процесс легализации завершился в марте 2023, но я планирую в будущем поддерживать актуальность гайда, обновляя информацию по мере изменения каких-либо местных регламентов. Дата последнего апдейта: 01.12.2023</p>
<h2 id="heading-chto-takoe-frilans-viza-v-oae">Что такое фриланс виза в ОАЭ?</h2>
<p>Если очень коротко - это просто отдельный вид визы резидента ОАЭ (аналог ВНЖ), выданный на срок от 1 до 2 лет, с возможностью дальнейшего продления неограниченное количество раз.</p>
<p>Виза оформляется на основании Freelance Permit - официальная лицензия на ведение профессиональной деятельности в своей области с территории ОАЭ в качестве фрилансера. Статус лицензированного фрилансера позволяет работать с любыми компаниями внутри и за пределами ОАЭ в рамках своей специальности (IT, маркетинг, дизайн, продажи и т.д). Подтверждать профессию наличием диплома или опытом работы не нужно.</p>
<h2 id="heading-chto-daet-status-rezidenta-oae">Что дает статус резидента ОАЭ?</h2>
<ol>
<li><p>Право на долгосрочное проживание в стране для себя и семьи</p>
</li>
<li><p>Оформление Emirates ID - местное удостоверение личности</p>
</li>
<li><p>Возможность открыть личный счет в местном банке</p>
</li>
<li><p>Возможность заключать долгосрочный контракт аренды жилья. Не резиденты по закону могут арендовывать только на короткий срок (booking, airbnb и т.д).</p>
</li>
<li><p>Доступ к школам и детским садам (на платной основе)</p>
</li>
<li><p>Статус налогового резидента ОАЭ после 90 дней проживания в стране</p>
</li>
</ol>
<h2 id="heading-chto-nuzhno-dlya-oformleniya-frilans-vizy">Что нужно для оформления фриланс визы?</h2>
<p>Список требований и документов максимально простой:</p>
<ol>
<li><p>Паспорт и фото в электронном виде</p>
</li>
<li><p>Оплатить все сборы за Freelance Permit, Establishment Card и саму визу на 2 года ~3900$. Freelance Permit и Establishment Card придется продлевать каждый год. Стоимость продления на второй год будет ~2600$.</p>
</li>
<li><p>Местная сим-карта (можно оформить после подачи заявления)</p>
</li>
<li><p>Пройти медосмотр</p>
</li>
</ol>
<p>До прохождения медосмотра все можно делать удаленно. На медосмотр и биометрию нужно уже приехать в Дубай.</p>
<h2 id="heading-process-legalizacii-po-shagam">Процесс легализации по шагам</h2>
<p>Ниже представлен гайд по шагам от подачи заявки онлайн, до полной легализации своей деятельности в Дубае с открытием банковских счетов и налогами.</p>
<p>Весь процесс по стандартной процедуре занимает от 2 месяцев. Находиться в Дубае нужно при этом минимум месяц. Есть вариант доплатить за ускоренный вариант оформления визы, но сроки мне неизвестны.</p>
<h3 id="heading-1-oformlenie-freelance-permit-onlajn">1. Оформление Freelance Permit онлайн</h3>
<p>На данном шаге находиться в ОАЭ необязательно. Все что нужно - зарегистрироваться на сайте <a target="_blank" href="https://gofreelance.ae/">https://gofreelance.ae/</a> и следовать их инструкции. Обязательно указываем, что нам нужно так же сделать Establishment Card. Оплачиваем 2600$ и через несколько дней в личный кабинет приходит ваш Freelance Permit с Establishment Card в электронном виде.</p>
<p>Сроки: до 7 дней</p>
<h3 id="heading-2-podaem-zayavku-na-vizu">2. Подаем заявку на визу</h3>
<p>Все еще необязательно находиться в ОАЭ. В том же личном кабинете подаем заявку на Employee визу на 1 или 2 года. Нюанс в том, что для наемных сотрудников и фрилансеров используется одна форма для подачи заявки. Поэтому там где обычных employee просят загрузить Employment Contract и Degree Certificate - просто загружаем копию своего Freelance Permit. Health insurance оставляем пустым. Страховку можно оформить позже по желанию. Оплачиваем сбор за визу ~1300$ за 2 года. Далее ждем приглашения на медосмотр.</p>
<p>Срок ожидания: 7-14 дней</p>
<h3 id="heading-3-medosmotr-i-poluchenie-vizy">3. Медосмотр и получение визы</h3>
<p>С этого момента нужно уже находиться в Дубае. Медосмотр занимает 30 минут. Результат подгружается в личный кабинет автоматически после прохождения. Никаких дополнительных действий не требуется. Просто ждем результатов и приглашения на биометрию.</p>
<p>В течение недели после прохождения медосмотра вы уже получаете визу в электронном виде. Вклеивать визу в паспорт не нужно, она будет прикреплена к Emirates ID.</p>
<p>Сроки: ~7 дней</p>
<h3 id="heading-4-poluchenie-emirates-id">4. Получение Emirates ID</h3>
<p>Без EID, даже будучи резидентом, нельзя полноценно пользоваться банковскими или другими гос. услугами. После получения визы в личном кабинете автоматически создастся заявка на EID и будет назначена дата для сбора биометрии. После чего в течение недели EID доставляют курьерской службой по адресу проживания.</p>
<p>Важный момент - у вас может смениться адрес проживания в Дубае или номер телефона с момента подачи заявки на визу. Поэтому после биометрии, как только получаете трекинговый номер доставки, сразу можно позвонить в курьерскую службу и актуализировать номер телефона и адрес доставки. А еще лучше приехать к ним в офис и забрать EID лично, предварительно предупредив по телефону. Это самый быстрый способ на практике.</p>
<p>Сроки: ~2-4 недели</p>
<h3 id="heading-5-banki">5. Банки</h3>
<p>После получения EID можно открывать счета в банках. Для того, чтобы закрыть все свои возможные потребности для комфортного проживания в ОАЭ, вам понадобится:</p>
<ol>
<li><p>Счет + банковская карта в местной валюте AED для повседневных трат</p>
</li>
<li><p>Счета в USD / EUR если получаете доход в этой валюте из другой страны и хотите платить с него налоги (а точнее не платить) в ОАЭ</p>
</li>
<li><p>Чековая книжка. В Дубае при заключении контракта на аренду жилья нужно выписывать чеки</p>
</li>
</ol>
<p>Этап открытия счета в банке немного сложнее чем все предыдущие. По состоянию на начало 2023 года, несмотря на полную легализацию, наличие резидентства и EID, банки не очень охотно открывают счета клиентам с комбинацией: гражданин РФ + фрилансер. Причины, в целом, понятны. Вот два банка, в которых я в итоге открыл счета:</p>
<ol>
<li><p><a target="_blank" href="https://www.bankfab.com/">https://www.bankfab.com/</a></p>
<p> Подавать заявку нужно в мобильном приложении. Если придти в отделение с паспортом РФ - фрилансеру скорее всего откажут. Если просят salary certificate - скачиваем его из личного кабинета, где оформляли freelance permit. Стоит 60 AED.</p>
<p> Открывают счет AED, debit card и чековую книжку. Валютные счета не открывают.</p>
</li>
<li><p><a target="_blank" href="https://www.emiratesnbd.com/">https://www.emiratesnbd.com/</a></p>
<p> Тут все наоборот. Заявку в мобильном приложении могут обрабатывать пару недель, а потом сказать, что фрилансерам счета открывают только в отделении. В отделениях есть некий рандом. В один день могут попросить огромную стопку документов с подтверждением адреса проживания, utility bills, bank statement и т.д. В другой день в этом же отделении достаточно показать EID и salary certificate из личного кабинета.</p>
<p> Открывают счет AED + debit card, чековую книжку и валютные счета без пластиковых карт. Можно конвертировать валюту в мобильном приложении. Swift будет работать, но только не в РФ.</p>
</li>
</ol>
<p><strong>апдейт от 01.12.2023:</strong></p>
<p>Теперь открыть мультивалютную карту и чековую книжку можно очень просто в необанке <a target="_blank" href="https://www.wio.io">https://wio.io</a> . Все в приложении, нужен только Emirates ID без каких-либо справок.</p>
<h3 id="heading-6-nalogi">6. Налоги</h3>
<p>В ОАЭ с налогами все очень просто. Если ты обычный employee в местной компании - налог 0% на любую сумму дохода. Если ты фрилансер - у тебя мини-компания с единственным сотрудником, поэтому доход может облагаться в теории VAT(НДС) 5% и Corporate Tax 9%. Если мы работаем с иностранными заказчиками и не продаем услуги внутри ОАЭ - VAT будет 0%. Для Corporate Tax есть правило <a target="_blank" href="https://tax.gov.ae/en/taxes/corporate.tax/corporate.tax.topics/small.business.relief.23.aspx"><strong>Small Business Relief</strong></a>, которое освобождает от налога если суммарная выручка в год не превышает AED 3 000 000 (~$817 000). До оборота 100 000$ в год никакую отчетность можно вообще не подавать в налоговые органы. Если получаем доход на счет в ОАЭ, имеем разрешение на работу и можем обосновать поступления рабочими контрактами или инвойсами - эти деньги уже ваши и полностью белые.</p>
<p>Есть так же документ - сертификат налогового резидента ОАЭ. Выдается по запросу. Местным налоговым органам он не нужен, но может понадобится по месту гражданства. Если вы, например, начнете переводить деньги в РФ (через банки в странах СНГ или другим способом) и у местных органов в РФ возникнут вопросы по происходжению этих денег и уплате налогов, т.к вы не живете в РФ и не являетесь налоговым резидентом там, нужно будет просто предъявить тот самый сертификат налогового резидента ОАЭ, который докажет, что с этих денег вы уже заплатили налоги в другой стране.</p>
<h2 id="heading-mozhno-li-zhit-vne-oae-sohranyaya-status-rezidenta">Можно ли жить вне ОАЭ сохраняя статус резидента?</h2>
<p>Ответ - да. Для того, чтобы сохранять статус резидента ОАЭ по фриланс визе нужно выполнять одно простое условие - не находиться за пределами страны дольше 6 месяцев подряд. Другими словами, если вам не очень нравится местная жара или хочется попутешествовать - можно просто посещать ОАЭ раз в 5 месяцев на пару недель и работать удаленно из любой другой страны большую часть года.</p>
<p>Пока вы не стали налоговым резидентом в другой стране (как правило после суммарного проживания &gt; 183 дней в году в одном месте), ваши доходы в ОАЭ по прежнему облагаются только местными налогами: 0% НДФЛ, 0% или 5% VAT в зависимости от суммы дохода.</p>
<h2 id="heading-zaklyuchenie">Заключение</h2>
<p>Спасибо, что дочитали. Надеюсь сэкономил кому-то время на поиск информации. Если остались вопросы по теме - пишите в комментариях под этим постом или в <a target="_blank" href="https://t.me/bro_science_dev">Телеграм канале</a>. Всем удачной релокации)</p>
]]></content:encoded></item><item><title><![CDATA[TypeScript Patterns. Строковые шаблоны]]></title><description><![CDATA[Продолжаем цикл статей про лучшие практики типизации в TypeScript. Сегодня разберем примеры использования Template Literal Types, не самой популярной и, на мой взгляд, сильно недооцененной фичи языка.
Пример. Уровень - easy
Короткий пример, аналогичн...]]></description><link>https://blog.krasman.dev/typescript-patterns-strokovye-shablony</link><guid isPermaLink="true">https://blog.krasman.dev/typescript-patterns-strokovye-shablony</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[patterns]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Tue, 07 Feb 2023 17:57:23 GMT</pubDate><content:encoded><![CDATA[<p>Продолжаем цикл статей про лучшие практики типизации в TypeScript. Сегодня разберем примеры использования <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">Template Literal Types</a>, не самой популярной и, на мой взгляд, сильно недооцененной фичи языка.</p>
<h2 id="heading-primer-uroven-easy">Пример. Уровень - easy</h2>
<p>Короткий пример, аналогичный примерам из <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">документации</a>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> PositionY = <span class="hljs-string">'top'</span> | <span class="hljs-string">'middle'</span> | <span class="hljs-string">'bottom'</span>
<span class="hljs-keyword">type</span> PositionX = <span class="hljs-string">'left'</span> | <span class="hljs-string">'center'</span> | <span class="hljs-string">'right'</span>

<span class="hljs-keyword">type</span> Position = <span class="hljs-string">`<span class="hljs-subst">${PositionY}</span>_<span class="hljs-subst">${PositionX}</span>`</span> <span class="hljs-comment">// type Position = "top_left" | "top_center" | "top_right" | "middle_left" | "middle_center" | "middle_right" | "bottom_left" | "bottom_center" | "bottom_right"</span>

<span class="hljs-keyword">const</span> positionY = <span class="hljs-string">'top'</span>
<span class="hljs-keyword">const</span> positionX = <span class="hljs-string">'left'</span>
<span class="hljs-keyword">const</span> position1: Position = <span class="hljs-string">`<span class="hljs-subst">${positionY}</span><span class="hljs-subst">${positionX}</span>`</span> <span class="hljs-comment">// - Type Error. Компилятор строго следит за тем, как мы конкатенируем строку в рантайме.</span>
<span class="hljs-keyword">const</span> position2: Position = <span class="hljs-string">`<span class="hljs-subst">${positionY}</span>_<span class="hljs-subst">${positionX}</span>`</span> <span class="hljs-comment">// - Valid</span>
</code></pre>
<p>Суть очень простая - литерал <code>`` </code> в типах работает так же как в JS с возможностью шаблонизации и использования переменных внутри <code>${}</code>. Юнион типы внутри шаблона перемножаются и создают один общий строковый юнион со всеми вариациями переменных. Это позволяет навести дополнительные мосты между runtime-кодом и compile-time типизацией в тех местах, где мы обычно идем в обход компилятора и используем кастинг <code>as</code>.</p>
<h2 id="heading-primer-uroven-medium">Пример. Уровень - medium</h2>
<p>Напишем type-safe функцию для парсинга URL:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseUrl</span>&lt;
  <span class="hljs-title">Protocol</span> <span class="hljs-title">extends</span> '<span class="hljs-title">http</span>' | '<span class="hljs-title">https</span>',
  <span class="hljs-title">Host</span> <span class="hljs-title">extends</span> `<span class="hljs-title">$</span></span>{<span class="hljs-built_in">string</span>}.${<span class="hljs-built_in">string</span>}<span class="hljs-string">`,
  Path extends string,
  Result extends { protocol: Protocol; host: Host; pathname: Path }
&gt;(url: `</span>${Protocol}:<span class="hljs-comment">//${Host}/${Path}`): Result {</span>
  <span class="hljs-keyword">const</span> { protocol, host, pathname } = <span class="hljs-keyword">new</span> URL(url)
  <span class="hljs-keyword">return</span> { protocol, host, pathname } <span class="hljs-keyword">as</span> Result
}

<span class="hljs-comment">// TS теперь в compile-time проверяет корректность шаблона URL при вызове функции</span>
parseUrl(<span class="hljs-string">'http_://example.com/home'</span>) <span class="hljs-comment">// Type Error: Аргумент типа ""http_://example.com/home"" нельзя назначить параметру типа ""http://example.com/home" | "https://example.com/home""</span>
parseUrl(<span class="hljs-string">'https://example@com/home'</span>) <span class="hljs-comment">// Type Error: Аргумент типа ""https://example@com/home"" нельзя назначить параметру типа "`https://${string}.${string}/home`".</span>
parseUrl(<span class="hljs-string">'example.com/home'</span>) <span class="hljs-comment">// Type Error: Аргумент типа ""example.com/home"" нельзя назначить параметру типа "`http://${string}.${string}/${string}` | `https://${string}.${string}/${string}`"</span>


<span class="hljs-keyword">const</span> url1 = parseUrl(<span class="hljs-string">'https://example.com/home/page'</span>) <span class="hljs-comment">// Валидный вариант </span>
<span class="hljs-comment">// Так же TS за нас выводит возвращаемый тип для url1 как:</span>
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//    protocol: "https";</span>
<span class="hljs-comment">//    host: "example.com";</span>
<span class="hljs-comment">//    pathname: "home/page";</span>
<span class="hljs-comment">// }</span>
</code></pre>
<p>Впечатляет, правда? Я не встречал ничего подобного в других строго-типизированных языках. Если знаете какой язык умеет так же - напишите в комментариях.</p>
<h2 id="heading-primer-uroven-hard">Пример. Уровень - hard</h2>
<p>Типизируем стандартный метод <code>String.split()</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">declare</span> <span class="hljs-keyword">type</span> SplitString&lt;
  S <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>,
  D <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span> <span class="hljs-comment">//</span>
&gt; = S <span class="hljs-keyword">extends</span> <span class="hljs-string">''</span>
  ? []
  : S <span class="hljs-keyword">extends</span> <span class="hljs-string">`<span class="hljs-subst">${D}</span><span class="hljs-subst">${infer Tail}</span>`</span> <span class="hljs-comment">// infer позволяет создать переменную внутри Conditional Types</span>
  ? [...SplitString&lt;Tail, D&gt;] <span class="hljs-comment">// с помощью рекурсии склеиваем куски в один массив</span>
  : S <span class="hljs-keyword">extends</span> <span class="hljs-string">`<span class="hljs-subst">${infer Head}</span><span class="hljs-subst">${D}</span><span class="hljs-subst">${infer Tail}</span>`</span>
  ? [Head, ...SplitString&lt;Tail, D&gt;]
  : [S]

<span class="hljs-comment">// Переопределяем глобальный тип метода String.split() </span>
<span class="hljs-comment">// из стандартной библиотеки языка</span>
<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
  <span class="hljs-keyword">interface</span> String {
    split&lt;S <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>, D <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>&gt;(<span class="hljs-built_in">this</span>: S, delimiter: D): SplitString&lt;S, D&gt; <span class="hljs-comment">// this: S - линкуем строковый литерал в переменную</span>
  }
}

<span class="hljs-comment">// Используем .split() как обычно в коде</span>
<span class="hljs-keyword">const</span> parts1 = <span class="hljs-string">'/category/section/page'</span>.split(<span class="hljs-string">'/'</span>) <span class="hljs-comment">// тип const parts1: ["category", "section", "page"]</span>
<span class="hljs-keyword">const</span> parts2 = <span class="hljs-string">'my_name@example.com'</span>.split(<span class="hljs-string">'@'</span>) <span class="hljs-comment">// тип const parts2: ["my_name", "example.com"]</span>
</code></pre>
<p>По-моему - бомба! TS невероятен, но это еще не все. Добавим еще немного магии и сделаем type-safe функцию для создания http-хендлеров для роутинга:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">declare</span> <span class="hljs-keyword">type</span> ExtractParam&lt;Part <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>&gt; = Part <span class="hljs-keyword">extends</span> <span class="hljs-string">`{<span class="hljs-subst">${infer Param}</span>}`</span> ? Param : <span class="hljs-built_in">never</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">declare</span> <span class="hljs-keyword">type</span> PathParams&lt;PathPattern <span class="hljs-keyword">extends</span> <span class="hljs-built_in">string</span>&gt; = {
  [Key <span class="hljs-keyword">in</span> ExtractParam&lt;SplitString&lt;PathPattern, <span class="hljs-string">'/'</span>&gt;[<span class="hljs-built_in">number</span>]&gt;]: <span class="hljs-built_in">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createRouteHandler</span>&lt;<span class="hljs-title">Path</span> <span class="hljs-title">extends</span> <span class="hljs-title">string</span>, <span class="hljs-title">Params</span> <span class="hljs-title">extends</span> <span class="hljs-title">PathParams</span>&lt;<span class="hljs-title">Path</span>&gt;&gt;(<span class="hljs-params">
  path: Path,
  handler: (params: { [P <span class="hljs-keyword">in</span> keyof Params]: Params[P] }) =&gt; {}
</span>) </span>{
  <span class="hljs-comment">// Implementation....</span>
}

createRouteHandler(<span class="hljs-string">'/users/{id}'</span>, <span class="hljs-function">(<span class="hljs-params">params</span>) =&gt;</span> {
  <span class="hljs-comment">// тип params автоматически выводится как</span>
  <span class="hljs-comment">// {</span>
  <span class="hljs-comment">//  id: string;</span>
  <span class="hljs-comment">// }</span>
})

createRouteHandler(<span class="hljs-string">'/users/{userID}/orders/{orderID}'</span>, <span class="hljs-function">(<span class="hljs-params">params</span>) =&gt;</span> {
  <span class="hljs-comment">// тип params автоматически выводится как</span>
  <span class="hljs-comment">// {</span>
  <span class="hljs-comment">//  userID: string;</span>
  <span class="hljs-comment">//  orderID: string;</span>
  <span class="hljs-comment">// }</span>
})
</code></pre>
<p>Буум! Очередной слой логики покрыт compile-time проверками с минимальными усилиями со стороны разработчика.</p>
<h2 id="heading-zaklyuchenie">Заключение</h2>
<p>На мой взгляд, TypeScript задизайнен просто шедеврально. Он позволяет делать невероятные вещи, в сравнении с классическими строго-типизированными языками, предоставляя очень мощный и выразительный синтаксис. Кому интересно узнать больше advanced-приемов в TS - подписывайтесь на мой <a target="_blank" href="https://t.me/bro_science_dev">канал в Telegram</a>.</p>
]]></content:encoded></item><item><title><![CDATA[TypeScript Patterns. Номинальные типы]]></title><description><![CDATA[В этом посте я хочу начать цикл практических статей про advanced-level практики в TypeScript (далее TS).
Далеко не все осознают насколько мощный инструмент достался frontend-сообществу и какие он открывает возможности для того, чтобы писать удобный, ...]]></description><link>https://blog.krasman.dev/typescript-patterns-nominalnye-tipy</link><guid isPermaLink="true">https://blog.krasman.dev/typescript-patterns-nominalnye-tipy</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[frontend]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Fri, 03 Feb 2023 20:54:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675457328906/d8b31632-98c0-44ed-a986-622f1186146f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>В этом посте я хочу начать цикл практических статей про advanced-level практики в TypeScript (далее TS).</p>
<p>Далеко не все осознают насколько мощный инструмент достался frontend-сообществу и какие он открывает возможности для того, чтобы писать удобный, выразительный и супер-надежный код. Особенно это актуально в больших проектах. По моим ощущениям, большинство разработчиков используют TS процентов на 30% от его потенциала. Надеюсь данный цикл статей поможет немного исправить ситуацию.</p>
<h2 id="heading-vvodnyj-primer">Вводный пример</h2>
<p>Мы хотим использовать в своем приложении API с данными о погоде за определенные даты. URL Get-запроса выглядит так - <a target="_blank" href="https://archive-api.open-meteo.com/v1/era5?latitude=52.52&amp;longitude=13.41&amp;start_date=2021-01-01&amp;end_date=2021-01-03&amp;hourly=temperature_2m">https://archive-api.open-meteo.com/v1/era5?latitude=52.52&amp;longitude=13.41&amp;start_date=2021-01-01&amp;end_date=2021-01-03&amp;hourly=temperature_2m</a>. Как мы можем описать в TS интерфейс функции, которая будет выполнять этот запрос? Обычно это делают следующим образом:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Объявляем функцию и тип параметров</span>
<span class="hljs-keyword">type</span> GetWeatherRequestParams = {
  latitude: <span class="hljs-built_in">number</span>
  longitude: <span class="hljs-built_in">number</span>
  start_date: <span class="hljs-built_in">string</span>
  end_date: <span class="hljs-built_in">string</span>
  hourly: <span class="hljs-string">'temperature_2m'</span> | <span class="hljs-string">'windspeed_10m'</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWeather</span>(<span class="hljs-params">{ longitude, latitude, start_date, end_date, hourly }: GetWeatherRequestParams</span>) </span>{
  <span class="hljs-keyword">const</span> baseUrl = <span class="hljs-string">'https://archive-api.open-meteo.com/v1/era5'</span>
  <span class="hljs-keyword">return</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span>?latitude=<span class="hljs-subst">${latitude}</span>&amp;longitude=<span class="hljs-subst">${longitude}</span>&amp;start_date=<span class="hljs-subst">${start_date}</span>&amp;end_date=<span class="hljs-subst">${end_date}</span>&amp;hourly=<span class="hljs-subst">${hourly}</span>`</span>)
}

<span class="hljs-comment">// Вызываем функцию где-то в нашем приложении</span>
<span class="hljs-keyword">const</span> response = getWeather({
  latitude: <span class="hljs-number">52.52</span>,
  longitude: <span class="hljs-number">13.41</span>,
  hourly: <span class="hljs-string">'temperature_2m'</span>,
  start_date: <span class="hljs-string">'2021-01-01'</span>,
  end_date: <span class="hljs-string">'2021-01-03'</span>,
})
</code></pre>
<p>Все работает, мы получили данные в ответе. Теперь другая ситуация. Мы интегрируем эту функцию с виджетом календаря, который возвращает нам две даты типа <code>Date</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onDateChange</span>(<span class="hljs-params">start_date: <span class="hljs-built_in">Date</span>, end_date: <span class="hljs-built_in">Date</span></span>) </span>{
  <span class="hljs-keyword">const</span> response = getWeather({
    latitude: <span class="hljs-number">52.52</span>,
    longitude: <span class="hljs-number">13.41</span>,
    hourly: <span class="hljs-string">'temperature_2m'</span>,
    start_date: start_date.toISOString(), <span class="hljs-comment">// приводим Date к строковому представлению, в соответствии объявленному типу выше</span>
    end_date: end_date.toISOString(),
  })

  <span class="hljs-built_in">console</span>.log(response)
}
</code></pre>
<p>Запускаем, наш код выполняет запрос <a target="_blank" href="https://archive-api.open-meteo.com/v1/era5?latitude=52.52&amp;longitude=13.41&amp;start_date=2023-01-01T20:00:00.000Z&amp;end_date=2023-01-07T20:00:00.000Z&amp;hourly=temperature_2m">https://archive-api.open-meteo.com/v1/era5?latitude=52.52&amp;longitude=13.41&amp;start_date=2023-01-01T20:00:00.000Z&amp;end_date=2023-01-07T20:00:00.000Z&amp;hourly=temperature_2m</a>, и мы получаем ответ с ошибкой из API - <code>{"error":true,"reason":"Invalid date format. Make sure to use 'YYYY-MM-DD'"}</code>. Оказывается, нужно передавать дату в строгом формате <code>YYYY-MM-DD</code>, но наш тип <code>start_date: string, end_date: string</code> пропускает любую строку. В этом месте у нас runtime разошелся с compile-time. Для компилятора, передача любой строки в функцию <code>getWeather</code> считается корректной, но в реальной жизни это не так. Как мы можем решить эту проблему?</p>
<p>Сразу скажу, что вариант писать комментарии к функции или к отдельным ее аргументам, показывая пример какой именно формат даты можно передавать - плохая идея. Мы полностью доверяемся человеческому фактору и начинаем общаться внутри команды некими "тайными записками", в надежде, что их кто-нибудь прочитает. Поэтому нам нужна статическая проверка в compile-time, которая будет отличать разные форматы внутри одного примитивного типа (String) и обрабатывать их как несовместимые.</p>
<p>Мы хотим чтобы все даты в формате <code>YYYY-MM-DD</code> были отдельным типом, который был бы не равен типу <code>string</code>, но в то же время вел себя как <code>string</code>, если мы хотим использовать его для конкатенации строк или приведению в lower или upper-case.</p>
<h2 id="heading-nominalnye-tipy">Номинальные типы</h2>
<p>Если мы просто создадим отдельный тип от строки, то нужного эффекта не будет.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> DateString = <span class="hljs-built_in">string</span>

<span class="hljs-keyword">type</span> GetWeatherRequestParams = {
  ...
  start_date: DateString
  end_date: DateString
}

...

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onDateChange</span>(<span class="hljs-params">start_date: <span class="hljs-built_in">Date</span>, end_date: <span class="hljs-built_in">Date</span></span>) </span>{
  <span class="hljs-keyword">const</span> response = getWeather({
    ...
    start_date: start_date.toISOString(), <span class="hljs-comment">// Все еще нет ошибки в compile-time</span>
    end_date: end_date.toISOString(),
  })
}
</code></pre>
<p>Чтобы сделать тип <code>DateString</code> несовместимым с типом <code>string</code>, нужно подмешать в него что-то уникальное, например вот так:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> NominalType: unique symbol
<span class="hljs-keyword">type</span> DateString = <span class="hljs-built_in">string</span> &amp; { <span class="hljs-keyword">readonly</span> [NominalType]: <span class="hljs-string">'DateString'</span> }

...

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onDateChange</span>(<span class="hljs-params">start_date: <span class="hljs-built_in">Date</span>, end_date: <span class="hljs-built_in">Date</span></span>) </span>{
  <span class="hljs-keyword">const</span> response = getWeather({
    ...
    start_date: start_date.toISOString(), <span class="hljs-comment">// TS падает с ошибкой в compile-time - `Тип "string" не может быть назначен для типа "DateString"`</span>
    end_date: end_date.toISOString(),
  })
}
</code></pre>
<p>Мы создали номинальный тип <code>DateString</code>, который является уникальным по отношению к базовому типу <code>string</code>. Теперь в функцию <code>getWeather()</code> нельзя передать в качестве дат случайные строки. Но где нам теперь брать даты в нужном формате и с нужным типом?</p>
<p>Чтобы сделать работу с датами в нашем приложении полностью безопасной, необходимо добавить хелпер для приведения даты из стандартного формата в <code>DateString</code>. Этот хелпер должен быть единственным источником дат в формате <code>DateString</code> во всем приложении. Другими словами, у разработчика на нашем проекте не должно быть другого варианта, кроме вызова этого хелпера, если в коде какая-то из функций требует на вход дату в формате <code>DateString</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Объявляем общий хелпер</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">dateToDateString</span>(<span class="hljs-params">date: <span class="hljs-built_in">Date</span></span>): <span class="hljs-title">DateString</span> </span>{
  <span class="hljs-keyword">const</span> year = date.getFullYear()
  <span class="hljs-keyword">const</span> month = date.getMonth().toString().padStart(<span class="hljs-number">2</span>, <span class="hljs-string">'0'</span>)
  <span class="hljs-keyword">const</span> day = date.getDate().toString().padStart(<span class="hljs-number">2</span>, <span class="hljs-string">'0'</span>)

  <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${year}</span>-<span class="hljs-subst">${month}</span>-<span class="hljs-subst">${day}</span>`</span> <span class="hljs-keyword">as</span> DateString <span class="hljs-comment">// это будет единственное место в нашем проекте, где мы используем кастинг as DateString</span>
}

...

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onDateChange</span>(<span class="hljs-params">start_date: <span class="hljs-built_in">Date</span>, end_date: <span class="hljs-built_in">Date</span></span>) </span>{
  <span class="hljs-keyword">const</span> response = getWeather({
    ...
    start_date: dateToDateString(start_date), <span class="hljs-comment">// преобразуем Date в DateString - нет ошибки в compile-time</span>
    end_date: dateToDateString(end_date),
  })
}
</code></pre>
<p>Теперь у нас есть прочный мост между compile-time и runtime. Если мы где-то в приложении ожидаем в runtime дату строго в формате <code>YYYY-MM-DD</code> - компилятор не позволит нам собрать проект, пока мы не убедимся при помощи хелпера <code>dateToDateString()</code>, что мы преобразовали дату в нужный формат.</p>
<h2 id="heading-drugie-primery">Другие примеры</h2>
<ol>
<li>Добавляем номинальные типы в стандартную библиотеку TS или внешние пакеты</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ISODateString = <span class="hljs-built_in">string</span> &amp; { <span class="hljs-keyword">readonly</span> [NominalType]: <span class="hljs-string">'ISODateString'</span> } <span class="hljs-comment">// объявляем отдельный тип для дат в формате 2023-01-07T20:00:00.000Z</span>

<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
  <span class="hljs-keyword">interface</span> Date {
    toISOString(): ISODateString <span class="hljs-comment">// типизируем стандартный класс Date как источник дат в формате ISODateString</span>
  }
}

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> 'moment' {
  <span class="hljs-keyword">interface</span> Moment {
    toISOString(keepOffset?: <span class="hljs-built_in">boolean</span>): ISODateString <span class="hljs-comment">// типизируем библиотеку moment.js</span>
  }
}

<span class="hljs-keyword">const</span> date3: ISODateString = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toString() <span class="hljs-comment">// TS Error - Тип "string" не может быть назначен для типа "ISODateString"</span>
<span class="hljs-keyword">const</span> date2: ISODateString = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toDateString() <span class="hljs-comment">// TS Error - Тип "string" не может быть назначен для типа "ISODateString"</span>
<span class="hljs-keyword">const</span> date1: ISODateString = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString() <span class="hljs-comment">// корректно</span>

<span class="hljs-keyword">const</span> date4: ISODateString = moment().toString() <span class="hljs-comment">// TS Error - Тип "string" не может быть назначен для типа "ISODateString"</span>
<span class="hljs-keyword">const</span> date5: ISODateString = moment().toISOString() <span class="hljs-comment">// корректно</span>
</code></pre>
<ol>
<li>Делаем id разных сущностей уникальными, чтобы нельзя было по ошибке передать id от одной сущности в функцию, которая ожидает id совершенно другой сущности.</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> NominalType: unique symbol
<span class="hljs-keyword">type</span> NominalString&lt;TypeIdentifier&gt; = <span class="hljs-built_in">string</span> &amp; { <span class="hljs-keyword">readonly</span> [NominalType]: TypeIdentifier }

<span class="hljs-keyword">type</span> ProjectId = NominalString&lt;<span class="hljs-string">'ProjectId'</span>&gt;
<span class="hljs-keyword">type</span> Project = {
  id: ProjectId
  name: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">type</span> ProductId = NominalString&lt;<span class="hljs-string">'ProductId'</span>&gt;
<span class="hljs-keyword">type</span> Product = {
  id: ProductId
  description: <span class="hljs-built_in">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getProductPhotos</span>(<span class="hljs-params">id: ProductId</span>) </span>{...}

<span class="hljs-keyword">const</span> project: Project = {...} <span class="hljs-comment">// сокращаю детали для наглядности</span>
<span class="hljs-keyword">const</span> product: Product = {...}

<span class="hljs-keyword">const</span> photos1 = getProductPhotos(project.id) <span class="hljs-comment">// TS Error: Аргумент типа "ProjectId" нельзя назначить параметру типа "ProductId".</span>

<span class="hljs-keyword">const</span> photos2 = getProductPhotos(product.id) <span class="hljs-comment">// ошибки нет, тип соответствует</span>
</code></pre>
<ol>
<li>Делаем работу с JSON-сроками более безопасной.</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> JSONstring&lt;T <span class="hljs-keyword">extends</span> unknown&gt; = NominalString&lt;<span class="hljs-string">'JSONstring'</span>&gt;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">encodeJSON</span>&lt;<span class="hljs-title">T</span> <span class="hljs-title">extends</span> <span class="hljs-title">unknown</span>&gt;(<span class="hljs-params">data: T</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.stringify(data) <span class="hljs-keyword">as</span> JSONstring&lt;T&gt;
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJSON</span>&lt;<span class="hljs-title">T</span> <span class="hljs-title">extends</span> <span class="hljs-title">unknown</span>&gt;(<span class="hljs-params"><span class="hljs-built_in">string</span>: JSONstring&lt;T&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">string</span>) <span class="hljs-keyword">as</span> T
}

...

<span class="hljs-keyword">const</span> productJSON = encodeJSON(product) <span class="hljs-comment">// Тип productJSON автоматически вывелся как JSONstring&lt;Product&gt;. Это уже строка, но TS будет помнить что в ней закодирован объект типа Product</span>

<span class="hljs-keyword">const</span> parsedProduct = parseJSON(productJSON) <span class="hljs-comment">// Тип parsedProduct автоматически вывелся как Product, что полностью соответствует рантайму.</span>
</code></pre>
<p>Теперь нам не нужно делать кастинг <code>as</code> при вызовах <code>encodeJSON()</code> и <code>parseJSON()</code>. Для <code>encodeJSON()</code> источником правды является исходный тип, который мы в него передаем. Для <code>parseJSON()</code> источником правды является результат <code>encodeJSON()</code>, который запомнил исходный тип структуры, которую он кодировал.</p>
<h2 id="heading-zaklyuchenie">Заключение</h2>
<p>Таким образом, использование номинальных типов позволяет нам сократить разрыв между runtime и compile time в тех местах, где мы обычно оставляем пространство для ошибок и надеемся на человеческий фактор. Это дешевый и довольно эффективный способ перенести дополнительные проверки на TS, сделав кодовую базу чище и надежней.</p>
<p>Пишите в комментариях свое мнение на данную тему и насколько вам в целом интересно продолжение цикла статей про TypeScript.</p>
]]></content:encoded></item><item><title><![CDATA[Найм в IT: Построение процесса]]></title><description><![CDATA[На мой взгляд, найм является ключевым фактором в построении успешной команды разработки. В то же время, во многих IT-компаниях не уделяют этому процессу должного внимания и считают его фоновым, стараясь отвлекаться на него лишь по мере необходимости....]]></description><link>https://blog.krasman.dev/najm-v-it-postroenie-processa</link><guid isPermaLink="true">https://blog.krasman.dev/najm-v-it-postroenie-processa</guid><category><![CDATA[IT]]></category><category><![CDATA[hiring]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Sun, 29 Jan 2023 15:36:34 GMT</pubDate><content:encoded><![CDATA[<p>На мой взгляд, найм является ключевым фактором в построении успешной команды разработки. В то же время, во многих IT-компаниях не уделяют этому процессу должного внимания и считают его фоновым, стараясь отвлекаться на него лишь по мере необходимости. В этой статье я хочу поделиться практическими советами, которые могут помочь снизить вероятность ошибок при найме, стандартизировать процесс и определить кого на самом деле вы ищете. В качестве примеров я буду использовать найм на технические позиции, но принципы можно рассматривать как общие для всех ролей в команде.</p>
<h2 id="heading-paralleli-so-sportom">Параллели со спортом</h2>
<p>Мне нравится рассматривать продуктовую IT-компанию, как профессиональную спортивную команду в конкурентном виде спорта. Хорошие примеры - футбол, баскетбол, хоккей. Если подумать, между ними не так мало общего:</p>
<ul>
<li><p>Рынок талантов. Спортивные клубы с большими бюджетами борются за лучших атлетов. Для достижения чемпионских целей нужно построить чемпионскую команду. Зарплаты в топовых лигах все время растут. После проведения 3-5 сезонов в высшей лиге, спортсмену больше не приходится самому искать работу, предложения с новыми контрактами поступают сами. Очень похоже на IT-рынок.</p>
</li>
<li><p>Конкуренция и цена ошибки. Спортивные команды, занявшие последние места по итогам сезона или регулярного чемпионата, либо выбывают из лиги, либо не проходят в стадию Play-off. В IT молодые стартапы, проигравшие конкуренцию или не пробившиеся на рынок - просто закрываются.</p>
</li>
<li><p>Командная работа. Один игрок, даже самый звездный, не может выполнить задачу без помощи партнеров. Каждый зависит от каждого и все работают на общий результат.</p>
</li>
</ul>
<p>Поэтому мне кажется, что IT-компаниям имело бы смысл поизучать более пристально опыт коллег из мира спорта, т.к эта сфера намного старше чем IT. В своих примерах ниже я буду делать отсылки на эту тему.</p>
<h2 id="heading-etap-0-kto-my-i-kogo-my-hotim-nanyat">Этап 0. Кто мы и кого мы хотим нанять?</h2>
<p>Для того, чтобы построить эффективную команду, будет неплохо ответить на базовые вопросы:</p>
<ul>
<li><p>Какие цели стоят перед компанией на ближайший год и как они связаны с разработкой продукта? Примеры: сделать и выпустить MVP за 6 месяцев, вырасти горизонтально в два раза по команде и деливери, оптимизировать процесс и снизить time to market.</p>
</li>
<li><p>Насколько технически сложен создаваемый продукт? Какой минимальный технический уровень специалистов требуется для работы в команде (можно определить стандартными рангами: junior, middle, senior)?</p>
</li>
<li><p>В чем мы видим наше конкурентное преимущество: скорость и объем функциональности / уникальность продукта / можем сделать дешевле чем конкуренты?</p>
</li>
</ul>
<p>Эти вопросы, помогут правильно определить входные критерии по экспертизе кандидатов.</p>
<p>Пример из мира спорта. Если команда не обладает чемпионскими амбициями или ресурсами - она не пытается привлекать самых звездных игроков. И наоборот, если уровень конкуренции и ставки высоки (финальная стадия play-off), вы не можете позволить выпускать на поле новичков, для того чтобы они набирали опыт, или игроков не в лучшей форме. На важные игры выходят только лучшие и те кто готов приносить результат прямо сейчас.</p>
<p>Результатом данного этапа должен быть список ролей (вакансий), которых вы готовы нанимать с требованиями по опыту и критериями отбора. Пример: На бэкенд нанимаем от middle и выше, обязательно знание языка N и фреймворка M. На фронтенд нанимаем только senior из-за сложности проекта. Язык B обязателен, фреймворк C желателен, но необязателен т.к это не мейнстрим и для уровня senior инструмент вторичен. В QA готовы брать junior-ов т.к много ручной работы и есть экспертиза по менторству в команде. И т.д...</p>
<p>Почему нельзя сказать "просто нанимаем самых лучших"? Во-первых, это очень сложно и дорого. Не стоит ставить непосильные задачи, если в этом нет необходимости. Во-вторых, если даже вам удастся вначале нанять лучших, но вы не сможете обеспечить их задачами, соответствующими их уровню - они начнут терять мотивацию и со временем уйдут. По моим наблюдениям, людям удается сохранять мотивацию и стимул для роста на длинной дистанции, когда выполняются несколько условий:</p>
<ol>
<li><p>Задачи, которые они решают являются для них вызовом.</p>
</li>
<li><p>В команде их окружают люди их уровня или выше.</p>
</li>
<li><p>Понятны общие цели, которые команда пытается достичь как коллектив.</p>
</li>
</ol>
<p>Именно поэтому важно четко определить уровень экспертизы для каждой роли и синхронизировать требования по ним с HR.</p>
<h2 id="heading-etap-1-voronka-kandidatov">Этап 1. Воронка кандидатов</h2>
<p>Все практики, о которых я рассказываю в этой статье, работают хорошо только на больших цифрах. Чем четче критерии отбора, тем больше необходимо создать входной поток из кандидатов, чтобы после всех раундов просева, до офера кто-то доходил более-менее регулярно. Для меня, как нанимающего менеджера, который участвует в процессе найма на поздних стадиях воронки, это что-то вроде магии, которую колдует HR-отдел. Вакансии бывают сложные и простые, вилки з/п высокие и не очень, на рынке кризис или расцвет, но кто-то обеспечивает стабильный поток кандидатов, а кто-то нет. Если потока из CV нет - на следующих стадиях воронки начинает снижаться планка требований и мы начинаем нарушать правила из этапа 0, чтобы хоть кого-нибудь нанять. Это всегда проигрышная стратегия в долгую.</p>
<p>Нужно вкладываться в рекрутинг, привлекать HR, которые способны обеспечивать стабильную воронку, вводить KPI на объем кандидатов или закрываемых вакансий. Это окупится для компании.</p>
<p>Пример из спорта. В штате спортивных клубов есть агенты, которые ищут талантливых спортсменов по всему миру. Они летают на матчи других лиг, изучают статистику, пытаются высмотреть таланты на ранней стадии их карьеры и предоставляют отчеты менеджменту своего клуба.</p>
<h2 id="heading-etap-2-process-najma">Этап 2. Процесс найма</h2>
<p>Я не планирую в данной статье детально разбирать все этапы воронки найма, т.к количество раундов интервью зависит от роли и размера компании. Тут важны общие принципы, которые я для себя сформулировал так:</p>
<p><strong>1) Тщательно назначайте интервьюеров, они - это лицо компании для внешнего мира</strong></p>
<p>В каждом этапе интервью участвуют две стороны и происходит два действия одновременно: кандидат пытается продать себя и свои навыки компании, компания пытается продать себя кандидату. Предположим HR-отдел обеспечил качественный поток кандидатов и вы готовы нанять каждого пятого (это очень хорошая конверсия), но офер принимают далеко не все. В чем может быть причина? Возможно, интервьюеры недостаточно хорошо продают. Это может быть как в прямом смысле - мало рассказывают о внутренней кухне компании и перспективах поработать в сильной команде над решением амбиционных задач, или косвенно - кандидат не впечатлен уровнем компетенций или коммуникацией с теми кто его собеседовал. Лучшие хотят работать с лучшими, поэтому вам нужно показать внешнему миру лучших. Если вакансия скромнее, как минимум интервью нужно доверить тем, кто с удовольствием коммуницирует с незнакомыми людьми, чтобы атмосфера интервью была живой.</p>
<p><strong>2) В конце цепочки технических интервью, принимать решение о найме следует непосредственному руководителю для данной позиции</strong></p>
<p>Интервью линейного менеджера с его потенциальным подчиненным, это короткая демо-версия их будущих профессиональных отношений. Опытный руководитель обратит внимание не только на то, справился ли кандидат с техническими задачами на интервью, но и на такие моменты как: Пришел ли кандидат вовремя на встречу? Насколько он серьезно относится к процессу? Как он отзывается о бывших коллегах и предыдущих местах работы? Играет ли он с вами в команде в процессе интервью или демонстрирует раздражение теми вопросами, которые ему задают или саботирует их? Будущий руководитель может представить по эмоциональной окраске беседы, как кандидат будет вести себя в коллективе и комфортно ли будет ему делегировать ответственные задачи, за результат которых будет отвечать он сам руководитель. Пример из спорта: игрок не может попасть в команду, без участия тренера.</p>
<p><strong>3) Полезно вести статистику воронки, сколько кандидатов доходит до каждого уровня и сколько проходит дальше</strong></p>
<p>Каждый этап воронки дороже предыдущего т.к требует участия более квалифицированных интервьюеров. Если на каком-то этапе у нас отваливаются 90% кандидатов - это повод подкрутить предыдущий этап (или добавить если его еще нет), дать обратную связь и синхронизировать критерии. Найм это живой процесс. Пример из спорта: статистика используется на всех уровнях, без нее не принимается практически ни одно решение.</p>
<p><strong>4) Обратная связь</strong></p>
<p>По каждому кандидату нужно фиксировать обратную связь на всех этапах интервью с момента как произошла первая коммуникация с ним. Поможет любая CRM вроде <a target="_blank" href="https://huntflow.ai/">Huntflow</a>. Если человек прошел на третий этап интервью, интервьюер должен иметь обратную связь с предыдущих двух этапов чтобы подготовиться. Если кандидат получает отказ на любом из этапов - следует отправить ему обратную связь и объяснить причины. Больше всего кандидаты не любят, когда не получают вообще никакого ответа. Это негативно сказывается на HR-бренде компании. Даже просто скопировать фидбек с последнего этапа интервью и отправить как есть будет лучше в таком случае. Кандидат отметит вашу компанию среди остальной массы и через год, набравшись опыта, может придти к вам снова.</p>
<p><strong>5) Техническое интервью должно включать практические задачи, желательно из реальной жизни</strong></p>
<p>Сразу пример из спорта. Невозможно определить уровень игры футболиста, если вы даже не видели его в спортивной форме на поле. Скауты смотрят матчи игроков, устраивают просмотры новичков в своих клубах, подписывают контракты с игроками на 10 дней, чтобы увидеть их в деле и потом предложить долгосрочный контракт. Я советую применять тот же подход в IT. Поговорить абстрактно об опыте работы, перечислить технологии с которыми кандидат работал, спросить пару заковыристых вопросов по языку или фреймворку - на мой взгляд это самый бесполезный вариант технического интервью. При таком подходе легко ошибиться в обе стороны: пропустить слабых и отказать сильным у которых мало опыта с конкретным стеком технологий (про важность стека я писал в <a target="_blank" href="https://krasman.hashnode.dev/chto-yavlyaetsya-glavnym-sekretom-uspeha-effektivnoj-razrabotki-it-produkta#heading-glavnoe-zabluzhdenie-razrabotchikov">предыдущей статье</a>). Намного сложнее ошибиться, когда кандидат на ваших глазах сел и что-то сделал руками. Оффлайн интервью или онлайн с шарингом экрана - не так важно. Главное проверить что человек действительно хорош в том, чем он будет заниматься у вас в компании. Devops - дайте ему консоль или доступ в какой-нибудь k8s-кластер. QA - попросите написать тест-кейсы или протестировать несложный экран по заготовленным тест-кейсам. Разработчик - дайте несколько задач с <a target="_blank" href="https://leetcode.com/">leetcode</a> или свои собственные, желательно не сильно завязанные на стек технологий. В практике человек раскрывается намного шире. Вы почувствуете по тому, как он работает руками, хотите вы его нанять или нет. Усилит ли он команду с первых дней работы или в него придется вкладываться и обучать.</p>
<p><strong>6) Ищите профессионалов, а не просто экспертов</strong></p>
<p>В чем отличие? Экспертность это про hard-skills: глубокие знания технологий и инструментов, опыт решения нестандартных задач. Это важно, но совсем не достаточно. Профессионализм - это про soft-skills. Приходить вовремя на встречи, чувствовать ответственность за результат, стараться не блокировать других своей работой, помочь коллегам если они не успевают что-то сделать к дедлайну. Выходить за пределы своего скоупа ответственности, если есть возможность принести пользу. Быть проактивным. Об этом я уже упоминал в <a target="_blank" href="https://krasman.hashnode.dev/chto-yavlyaetsya-glavnym-sekretom-uspeha-effektivnoj-razrabotki-it-produkta#heading-kadry-i-najm">предыдущей статье</a>. Как проверить эти качества на интервью? Для этого не нужны отдельные вопросы или задачи. Как я уже писал выше в п.2, процесс найма уже содержит достаточное количество коммуникаций нанимающей стороны и кандидата. Это своего рода уже испытательный срок. Интервьюерам нужно лишь считывать важные сигналы в процессе этой коммуникации и фиксировать все, что заслуживает внимания, в обратной связи. Таким образом эмоциональный окрас не выветрится к моменту, когда нужно будет принимать решение по оферу.</p>
<h2 id="heading-etap-3-onbording">Этап 3. Онбординг</h2>
<p>Когда предыдущие этапы настроены правильно, выход нового сотрудника на работу становится будничным событием. Не стоит недооценивать важность этого этапа. Он должен быть так же формализован и проработан.</p>
<p>Первое впечатление новичка о компании составляется по уровню организованности внутри компании. Ему очень важно в первые дни быстро решить технические вопросы, познакомиться с проектом и начать работать над реальной задачей. Если этот процесс проходит хаотично - есть вероятность демотивировать вашего нового коллегу уже на старте.</p>
<p>Создайте базу знаний для своих сотрудников в Wiki и сделайте так, чтобы был всегда человек, который курирует новичка в его испытательный срок, тот кто может помочь ему быстрее влиться в процесс. Главный критерий - как быстро с момента прихода в команду человек начнет приносить какую-то реальную пользу команде.</p>
<h2 id="heading-etap-4-ispytatelnyj-srok">Этап 4. Испытательный срок</h2>
<p>Многие относятся к испытательному сроку как к формальности в трудовом договоре, но я, например, считаю по-другому. Мы уже обсудили почему ошибки в найме стоят очень дорого, и если уж мы их допускаем - испытательный срок, это последний рубеж, когда мы можем эти ошибки отловить и уменьшить их последствия. На испытательном сроке нам нужно решить три задачи:</p>
<ol>
<li><p>Успеть обеспечить человека реальными задачами, чтобы объективно оценить его профпригодность. Для этого и нужны отлаженный процесс онбординга.</p>
</li>
<li><p>Продемонстрировать компанию с лучшей стороны, чтобы сотрудник захотел остаться после испытательного срока. Тут тоже помогут процессы.</p>
</li>
<li><p>Собрать обратную связь по результатам работы. Принять волевое решение расстаться если сработаться по каким-то причинам не получилось. Увольнения не самый приятный процесс, но как и в спорте, он необходим для того, чтобы весь механизм продолжал работать и общий уровень внутри команды не опускался ниже заданной планки. Помните, слабые игроки в команде, тянут остальных вниз. Люди с токсичным культурным кодом наносят непоправимый ущерб всей команде и опускают остальных на свой уровень.</p>
<p> Ваша задача не нанять всех кого сможете и научиться как-то худо-бедно работать с каждым. Ваша задача - найти именно своих людей и создать условия, чтобы такие люди хотели задержаться в вашей команде.</p>
</li>
</ol>
<h2 id="heading-zaklyuchenie">Заключение</h2>
<p>На этом все. Пишите ваши мысли в комментариях. В следующих статьях я хочу рассказать про свой опыт проведения технических интервью для frontend-разработчиков с примерами задач. Чтобы не пропустить - подписывайтесь на мой <a target="_blank" href="https://t.me/bro_science_dev">телеграм канал</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Что является главным секретом успеха эффективной разработки IT-продукта?]]></title><description><![CDATA[Создание нового продукта с нуля (MVP), проверка гипотез, быстрые изменения на основе обратной связи или последующая поддержка, если продукт доказал свою жизнеспособность - процессы довольно сложные и дорогостоящие. Если верить статистике, 90% стартап...]]></description><link>https://blog.krasman.dev/chto-yavlyaetsya-glavnym-sekretom-uspeha-effektivnoj-razrabotki-it-produkta</link><guid isPermaLink="true">https://blog.krasman.dev/chto-yavlyaetsya-glavnym-sekretom-uspeha-effektivnoj-razrabotki-it-produkta</guid><category><![CDATA[development]]></category><category><![CDATA[process management]]></category><category><![CDATA[hiring management]]></category><dc:creator><![CDATA[Alexey Krasman]]></dc:creator><pubDate>Sun, 22 Jan 2023 19:49:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674414842690/76bedac2-edff-402f-9ae6-39a412447a8f.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Создание нового продукта с нуля (MVP), проверка гипотез, быстрые изменения на основе обратной связи или последующая поддержка, если продукт доказал свою жизнеспособность - процессы довольно сложные и дорогостоящие. Если верить <a target="_blank" href="https://www.embroker.com/blog/startup-statistics/#:~:text=About%2090%25%20of%20startups%20fail.&amp;text=10%25%20of%20startups%20fail%20within%20the%20first%20year.&amp;text=Across%20all%20industries%2C%20startup%20failure,be%20close%20to%20the%20same.&amp;text=Failure%20is%20most%20common%20for,70%25%20falling%20into%20this%20category.">статистике</a>, 90% стартапов из всех проваливаются, 70% закрываются в первые 5 лет. Причины могут быть разные. Самая частая - ошибка в исследовании потребностей рынка (42%). Другими словами, продукт, который мы делаем, в реальном мире не так уж нужен. Данная проблема находится вне плоскости разработки, вследствие чего, мы не будем ее разбирать в данной статье. Вторую по частоте причину можно сформулировать примерно так - слишком рано заканчивается финансирование. Продукт закрывается до того, как выйдет на рынок или нащупает устойчивую бизнес-модель (29%). В данном случае, эффективность разработки может играть уже решающую роль для выживания продукта и успеха всей компании. Скорость и предсказуемость сроков итераций разработки, качество выдаваемого продукта пользователям, time to market новых фичей или гипотез - все это становится ключевыми факторами успеха.</p>
<p>Те же самые принципы актуальны для зрелой компании, которая смогла занять свою нишу на рынке. Особенно если компания на волне успеха начинает бесконтрольно масштабироваться, раздувает штат и становится неповоротливой. В конечном итоге она может проиграть конкуренцию более молодым игрокам, которым удается быстрее развивать свой продукт под потребности рынка или сделать его дешевле, за счет более эффективной разработки.</p>
<h3 id="heading-glavnoe-zabluzhdenie-razrabotchikov">Главное заблуждение разработчиков</h3>
<p>В эффективности процесса разработки заинтересованы все, в том числе сами инженеры. Чем проще и боле безболезненно команде удается деливерить изменения в продукт, тем комфортней становятся рабочие будни и выше боевой дух всех членов команды.</p>
<p>К сожалению, очень часто я вижу одну и ту же ошибку, которую совершают люди, с техническим складом ума, излишне фокусируясь на одном аспекте, кажущемся им решающим. На практике же, он может не давать никакого эффекта кроме психологического, оставляя все вышеперечисленные проблемы нерешенными. Начнем с самого яркого примера.</p>
<h3 id="heading-vybor-steka-tehnologij"><strong>Выбор стека технологий</strong></h3>
<p>Самое интуитивное решение для инженера - найти самый лучший, новый, современный/разрекламированный инструмент и поверить в то, что на нем невозможно писать плохой код. Что можно реализовать ту же задачу в разы быстрее чем раньше, что багов будет меньше и т.д. Перечислю только то, что я непосредственно наблюдал на своей практике.</p>
<p><strong>Ruby on Rails</strong> - MVC фреймворк нового поколения, где обо всем подумали за разработчика. <strong>Функциональное программирование (Clojure как пример) -</strong> отдельный мир для избранных, где не нужно страдать. <strong>TDD -</strong> максимальное покрытие кода тестами, ошибки отлавливаем раньше. Test First помогает лучше проектировать модули. <strong>NoSQL</strong> - гибкость и масштабируемость. <strong>GraphQL -</strong> не нужно тратить время на новые ручки API, фронтенд сам заберет все, что ему нужно. Знакомо? Можно продолжить. <strong>React.js -</strong> спаситель фронтенда, на который нужно срочно перейти с любого другого фреймворка и забыть о проблемах на клиенте. <strong>Redux -</strong> теперь уже точно на фронтенде полное счастье. По другому быть просто не может.</p>
<p>Неужели это все неправда? И да и нет. Технологии могут быть прекрасными, но они сами по себе не решают изначальную проблему эффективности разработки продукта, т.к это всего-лишь инструменты. Представьте, что вы строите многоэтажное здание в суровом климате с непростым природным ландшафтом и капризной почвой. У вас есть современные краны, пневмонагнетатели, лазеры для установки уровней и весь необходимый инструментарий в руках рабочих. Достаточно ли этого, чтобы построить устойчивое здание, которое простоит десятки лет и не разрушится под воздействием переменчивой окружающей среды? Очевидно, что нет. Ключевым фактором будет точный расчет архитекторов и инженеров нагрузки на опоры, исследование почвы на предмет пригодности к возведению высотных зданий и учет перепада температуры в зимнее и летнее время.</p>
<p>В разработке программного продукта ситуация примерно такая же. Что приводит нас к следующему фактору эффективности.</p>
<h3 id="heading-arhitektura">Архитектура</h3>
<p>Если коротко - с посредственной архитектурой, используя даже самые лучшие инструменты, разработка будет эффективной очень недолго. Сроки в какой-то момент перестанут быть предсказуемыми, станут появляться дополнительные итерация баг-фикса и стабилизации релизов. Технический долг будет расти и отнимать все больше времени. И наоборот - даже с не самым современным стеком, инвестировав в архитектуру, можно обеспечить стабильное деливери для бизнеса. Может быть не на максимально возможных скоростях, но, что более важно, в предсказуемые сроки.</p>
<p>Короткий пример из мира фронтенда: популярная связка <strong>React.js + Redux.</strong> В какой-то момент хайп достиг такого уровня, что все новые проекты использовали данный стек, в полной уверенности в своем успехе. Проблема в том, что <strong>React</strong> это всего лишь view-библиотека для отрисовки компонентов, а <strong>Redux</strong> не умеет ничего, кроме сохранения состояния в один громадный глобальный стор. Эти две библиотеки, в теории, давали возможность написать что угодно, но не предлагали никаких идей по организации структуры приложения, модульности, разделению логики по слоям (view, business логика, application логика, API), выделению доменов и bounded contexts. В результате каждый React-проект был велосипедом, рандомно структурированным, с огромнымным скоплением логики на View-уровне или в Redux-сторе.</p>
<p>Какой можно сделать из этого вывод? В первую очередь нужно позаботиться о том, чтобы в долгоживущем проекте на старте было достаточно экспертизы для создания надежной архитектуры, и только потом выбирать инструменты. Подчеркну - именно экспертизы. Какую должность занимает человек при этом не так важно.</p>
<p>Но достаточно ли этого, чтобы вывести разработку на максимальные обороты, или есть еще что-то важнее архитектуры и инструментов? Кажется, что есть.</p>
<h3 id="heading-upravlenie-i-processy">Управление и процессы</h3>
<p>Этот пункт менее актуален в микро-проектах на старте, когда вся команда помещается в один опенспейс или в один daily-синк в Zoom. Но это не значит, что им можно пренебрегать. У нас есть архитектура и мощный стек, который помогает инженерам закрывать задачи быстро и с хорошим качеством. Но как организован процесс разработки? Проводит ли команда оценку перед тем как начать разработку? Распараллелен ли процесс разработки или все блокируют всех? В какой момент задаются неудобные вопросы по требованиям: в момент когда уже разработка началась или перед тем как дать оценку? Как распределяются задачи внутри команды? Нет ли дисбаланса, когда одна часть команды перегружена, а другая скучает? Рассмотрим пример:</p>
<p>Нужно добавить в продукт новый раздел с отзывами или комментариями, например, на товар. Эта фича включает в себя: новый дизайн, новое API, новый экран или виджет на фронте.</p>
<p><strong>Вариант №1 - берем и делаем</strong></p>
<ol>
<li><p>Ждем дизайн (2 дня)</p>
</li>
<li><p>Посмотрели - вроде все хорошо. Начинаем делать API (4 дня)</p>
</li>
<li><p>API готово. Начинаем делать фронтенд (еще 4 дня). В конце сами себя спрашиваем "А должны новые отзывы появляться автоматически после загрузки или только при следующем заходе на страницу? Кажется мы это в API не предусмотрели...". Потратили еще 1 день на обсуждения и дополнительный костыль в виде периодического передергивания ручки с фронта. Параллельно уже боимся, что на проде API может стать плохо от такого объема запросов, но переделывать уже времени нет.</p>
</li>
<li><p>Отдаем в тестирование, фиксим баги (еще 3 дня).</p>
</li>
<li><p>Все протестировано можно мержить в мастер. Создаем огромный Merge Request, начинается код-ревью остальными участниками команды. Замечаний очень много, исправлять их долго и страшно т.к фича уже протестирована. Внесение серьезных изменений потребует повторного тестирования. (еще +3 дня)</p>
<p> <strong>Итого</strong> - 17 дней, уже имеем техдолг, а мы еще не на проде.</p>
</li>
</ol>
<p><strong>Вариант №2 - оценка, контракты, параллельная разработка</strong></p>
<ol>
<li><p>Дизайн не зависит от занятости команды, поэтому он рисуется заранее по первому запросу от бизнеса "в стол". На момент, когда команда готова взять новую фичу, дизайн уже готов.</p>
</li>
<li><p>Смотрим дизайн, задаем вопросы (в том числе про автоматическое появление новых отзывов). Описываем контракты для нового API, шарим их с фронтенд командой. Тратим дополнительно на эту подготовку 1 день.</p>
</li>
<li><p>Стартуем разработку бэкенд/фронтенд параллельно по контрактам, договорившись какие ручки API нужно выдать в первую очередь для интеграции. Как только появляются первые рабочие end to end-сценарии, QA начинает их тестировать и репортить баги, которые закрываются параллельно с разработкой. Декомпозируем фичу на мелкие задачи и выкладываем их на код ревью атомарно, по мере готовности. Оперативно ревьюим и правим замечания мелкими порциями (5 дней на все).</p>
</li>
<li><p>На этом этапе у нас уже закончена разработка и исправлена большая часть багов. Исправляем то, что осталось. (2 день)</p>
</li>
<li><p>Весь код уже прошел ревью на предыдущих этапах. Можно мержить в мастер.</p>
<p> <strong>Итого:</strong> 8 дней, без техдолга, с предварительной оценкой и пониманием когда фича будет готова к релизу на прод, с точностью до пары дней.</p>
</li>
</ol>
<p>Кто за это отвечает? Чаще всего - тимлид. Хотя, как и в прошлом пункте, должность тут вторична.</p>
<p>Казалось бы, что на этом можно закончить, т.к теперь разработка уже несется на всех парах как локомотив, верно? Почти верно. У нас уже есть четкий план, инструкции, роли и инструменты, но не хватает самого главного.</p>
<h3 id="heading-kadry-i-najm">Кадры и найм</h3>
<p>Ингридиент, который я субъективно считаю самым важным из всех. Он может на практике, как нивелировать отсутствие любого из вышеперечисленных пунктов, так и не позволить им заработать уже на начальном этапе. То, с чего нужно начинать разработку любого серьезного продукта. Найм и формирование профессиональной команды.</p>
<p>Данная тема настолько обширная, что заслуживает отдельной статьи. Сейчас же, я лишь сформулирую основные тезисы:</p>
<ol>
<li><p>Профессионализм и ответственность - качество номер один.</p>
</li>
<li><p>Проактивность. Человек на любой позиции в команде чувствует неудовлетворенность если видит какую-то нерешенную проблему. Он не ждет задачи сверху, а проявляет инициативу сам.</p>
</li>
<li><p>Soft-skills так же ценны как hard-skills, иногда даже ценнее.</p>
</li>
<li><p>Умение работать на команду. Довольно сложно проверить на этапе интервью, но возможно.</p>
</li>
</ol>
<p>Почему это так важно? Это не всегда очевидно, но самая дорогая часть разработки это не сам процесс написания кода, тестирования или баг-фикса. Дороже всего обходятся лишние итерации на стыке разных стадий разработки и разных ролей в команде. С какого раза бэкенд-инженер выдаст по-настоящему рабочее API фронтенд-команде? Сколько раз нужно будет тестировщику перепроверять и возвращать на доработку одну и ту же задачу, перед тем как она будет пригодна для продакшна? Можем ли мы закрыть все вопросы по требованиям за одну встречу перед стартом разработки или будем постоянно стопориться в процессе спринта и на ходу додумывать корнер-кейсы?</p>
<p>Профессионалы, обладающие всеми перечисленными качествами, будут интуитивно экономить вам время на каждом из этапов, и их об этом даже не придется просить. Им небезразличен результат. Такая команда может эффективно деливерить даже с неидеальной архитектурой, не самым мощным стеком и даже без выверенных процессов. Поэтому, я считаю, что кадры - это самый главный фактор успеха эффективной разработки, на основе которого строится все остальное.</p>
<h3 id="heading-zaklyuchenie">Заключение</h3>
<p>Мой топ по мере важности выглядит так:</p>
<ol>
<li><p>Кадры и найм</p>
</li>
<li><p>Управление и процессы</p>
</li>
<li><p>Архитектура</p>
</li>
<li><p>Стек технологий</p>
</li>
</ol>
<p>В этой иерархии бывают исключения. Иногда выбор стека может существенно повлиять на структуру команды и процессы разработки. Например, кроссплатформенный фреймворк для мобильного приложения, как альтернатива отдельным командам iOS + Android, позволяет существенно сократить time to market в мобильном продукте, одновременно снизив стоимость разработки и тестирования. Об этом, возможно, будет отдельный пост. Но даже в этом случае, расставить приоритеты в предложенном порядке, с моей точки зрения, не будет ошибкой.</p>
<p>Спасибо всем, кто дочитал до конца. Надеюсь, материал оказался вам чем-то полезен. Данный пост носит сугубо обзорный характер. В будущих статьях планирую раскрыть каждый пункт более подробно. Оставайтесь на связи.</p>
]]></content:encoded></item></channel></rss>