Дэн Щербаков ⚛️
96 subscribers
20 photos
49 links
Канал для фронтенд-разработчиков о том, как развиваться и увеличивать зарплату.

Senior Frontend Developer с 6 годами опыта. За этот период увеличил зарплату почти в 7 раз.

Начинайте тут: https://yangx.top/code_lab/280
加入频道
Место структур данных в программировании

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

Построим осмысленную логическую цепочку.

Данные - это отдельный, обособленный и главный элемент разработки.
Чтобы обрабатывать данные, существуют алгоритмы.
Эффективность алгоритмов зависит от формы данных. Например, связанный список эффективен, когда нужно получить данные с начала или конца списка (O1), а бинарное дерево эффективнее для поиска вглубь, так как отсекает по половине данных за каждую итерацию.
Таким образом, структуры данных служат для упорядочивания данных, чтобы эффективно выполнять бизнес-задачи. А алгоритмы, в свою очередь, изучаются как правильные инструменты обработки этих структур.
Концепции фронтенда: повторяем главное

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

- Из чего вообще состоит работа фронтенд-разработчика?
1. Пишем программный интерфейс для получения, обработки и отправки данных с бэкенда (CRUD).
2. Пишем визуальный интерфейс для вывода этих данных.
3. Добиваемся максимальной оптимизации с обеих сторон.

- Для чего нужны фронтенд-фреймворки?
1. Стандартизация. Фреймворки реализуют подход DRY: вводят универсальные решения для повторяющихся задач. Попутно это снимает с компаний необходимость писать велосипеды и обучать им новичков.
2. Компонентный подход. Мы делим приложение на изолированные компоненты - и это снижает нагрузку на мозги. Ты держишь в уме только свой кусочек сложной системы.

- Что такое реактивность?
Грубо говоря, это способность систем реагировать на изменение стейта приложения.

- Зачем нужен Vuex/Redux?
Vuex выносит объект данных - стейт - в отдельный стор, который прокидывается глобально и распределяется на отдельные модули. Это сделано для облегчения поддержки сложных систем. Также стейт-менеджер обеспечивает надежность работы с данными: мутировать их можно только с помощью внутренних методов стейт-менеджера, либо нельзя вовсе (данные не мутируются, а подменяются новыми на основе старых).
Неотвеченное с собеседований

Большую часть я просто не смог вспомнить, а кое-что не использовал.
Итак...

- Как сравнить два объекта?
Проще и современнее всего - через Object.is(obj1, obj2).
Cтарый способ - привести оба объекта к JSON и выполнить строгое сравнение.

- Для чего нужны атрибуты asynс и defer в теге <script>?
Оба атрибута делают загрузку кода неблокирующей. То есть, DOM загружается независимо от JS в файле.
async - полностью независимая загрузка кода.
defer - загрузка кода после загрузки контента, до события DOMContentLoaded.
Можно использовать вместе для поддержки старых браузеров (defer)
CORS

Рассказывать буду тезисно.

CORS - это система безопасности обмена данными.

CORS переводится как Cross-Origin Resource Sharing.

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

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

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

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

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

В SOLID это - буква "D": dependency inversion principle (принцип инверсии зависимостей).

#инкапсуляция #паттерны #SOLID
Open Close principe

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

Например, есть задача сделать модальное окно.

Разделим его на две условные части:
- Контейнер с полупрозрачным оверлеем
- Область контента

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

Создадим компонент контента и пробросим в модальное окно.

С такой архитектурой компонент модального окна будет легко переиспользован в проекте.

#паттерны #SOLID
Принцип подстановки Барбары Лисков

Всю ночь раскидывал мозгами над этим принципом. И, кажется, понял. 💡

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

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

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

Строгость следования этому принципу - вопрос открытый.

#SOLID
Принцип разделения интерфейсов

Он же Interface segregation principle.

В чем его суть?

Зайду с двух сторон.

Представим код на TypeScript. Дана сущность "Юзер", делящаяся по возможностям на "Пользователь", "Модератора" и "Админа". Её описывает общий интерфейс, содержащий методы для всех подтипов. Но это не имеет смысла: реализуя этот интерфейс, пользователь должен будет содержать пустой метод возможности админа. Либо методы админа будут опциональными - что лишит типизацию строгости. Чтобы этого избежать, разделим "толстый" интерфейс на три более узких и прокинем все в сущность через логическое "или".

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

Принцип разделения интерфейсов, следовательно, о разделении кода и избавлении от лишних зависимостей.

#SOLID
Главная проблема программистов-самоучек

...и ее решение. Не окончательное, не для всех. Но тем не менее.

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

Но объем технологий вовсе не безграничен.

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

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

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

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

Создайте в голове визуальную модель архитектуры веба. Разделите React и Redux, REST и HTTP, клиент и сервер... Осознайте, в какой из блоков модели входят те или иные технологии. Визуализируйте каждый блок отдельно, разделяя на подблоки. Масштабируйте вглубь, пока это имеет смысл.

Это станет прорывом в вашем самообучении.

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

Попробуйте. Это изменит все.
Решаем задачки. Имитация Promise.all

Имеется недописанная функция parallel. Она принимает массив функций и коллбэк, вызываемый, когда все функции будут обработаны. Возвращает она массив результатов функций - в той последовательности, в которой они были переданы. Это важно!

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

Вот код:

function parallel(funcArray, doneAll) {

}

var a = function(done) {
setTimeout(function() {
done('result a');
}, 300);
};

var b = function(done) {
setTimeout(function() {
done('result b');
}, 200);
};

parallel([a,b], function(results) {
console.log(results); // ['result a', 'result b']
});

Решение - в следующем посте.

#задачи
Имитация Promise.all. Решение.

Исследуем код. Какие есть подсказки:
- массив функций (а значит, его нужно перебирать)
- коллбэк doneAll (который, получается, должен вызываться, когда функция закончит работу)
- две функции, содержащие setTimeout (значит, работаем с Event Loop)
- коллбэк done внутри этих функций (который выполняется при каждой асинхронной операции)
- результат в виде массива результатов вызова функций в том же порядке, что и были переданы

Прежде всего, нужно пробежаться по массиву funcArray. Код асинхронен, поэтому нельзя сразу вернуть результат. Мы не используем .map - вместо этого пробежимся на месте с помощью forEach. Создадим переменную result с пустым массивом для результата.

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

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

Но как узнать, что работа закончена и можно возвращать result? Синхронный код не умеет отслеживать выполнение setTimeout.

Раз не умеет синхронный - сумеет асинхронный!

Поэтому поместим эту логику тоже в коллбэк done. Введем в синхронной части счетчик count и инкрементируем его внутри done. Если значение счетчика сравняется с длиной переданного массива - выполним во все том же done коллбэк doneAll.

Предлагаю реализовать эту логику самостоятельно. Если не получится, погуглите реализацию Promise.all под капотом.

А вот моё решение: https://codepen.io/gretten/pen/zYzKLZL
Решаем задачки. "Бесконечное каррирование"

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

sum(1)() // 1
sum(1)(2)() // 3
sum(1)(2)(-1)() // 2

Решение напишу здесь же. Чтобы запутать читателя не допустить ошибок и показать код нагляднее, использую форму c if-ами и TS:

const sum = (a: number): number => {
return (b: number): number => {
if (b) {
return sum(a + b)
} else {
return a
}
}
}

Следите за пальцами.

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

Для решения вспомним, что такое каррирование. Это прием, при котором функция возвращает функцию-обертку с фиксированным аргументом. Мы вызываем обертку с новым аргументом - таким образом сокращая написание кода и возможности для ошибок.

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

Бесконечное количество аргументов означает цикл.

В функциональном программировании циклы делают через рекурсию - её и используем.

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

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

#задачи
Пятиминутка мотивации

Я разработчик-самоучка с 2.5 годами опыта. Сегодня я получил job offer от крупной продуктовой компании. Для этого понадобилось три дня по 2-3 собеседования. Бессрочный договор, ДМС, приятная даже по московским меркам зарплата и другие плюшки в комплекте.

На собеседовании в ту компанию было три блока вопросов: вёрстка, JS, React. Я ответил правильно лишь на один вопрос по верстке - но ни разу не ошибся в остальных.

Задачи по JS:
- очередность вызова console.log в синхронном коде, макротасках и микротасках (на понимание Event Loop).
- что выведут обработчики событий после навешивания в цикле (на понимание асинхронности). Гуглите задачу "армия функций".
- что такое preventDefault и stopPropagation
- что такое this
И еще парочка подобных задач.

По React:
- почему React - библиотека, а не фреймворк?
- что такое хуки?
- назовите популярные хуки Реакта
- как работают хуки, например, useCallback
- какие есть стадии и методы жизненного цикла?
И еще немного поболтали про Реакт в целом

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

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

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

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

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

2. У меня есть четкий карьерный план. Я знаю, как последовательно стать senior разработчиком, лидом и руководителем проектов - какие компетенции отделяют их от middle. И знаю, кем хочу быть через пять лет. Или через 10.

3. Я ❤️ свою работу. Я подписан на каналы рисерчеров от фронтенда, читаю книги по архитектуре, практикую новые технологии просто из интереса к ним. А еще я люблю, когда моё изделие приносит радость тысячам пользователей - и отличные деньги компании, с которой работаю.

Считаю, эти сильные стороны имеет смысл рассказывать. И, конечно, использовать. Ведь это не просто слова.
Дэн Щербаков ⚛️ pinned «Убедите за пять минут нанять вас? Был такой вопрос на собеседовании в крупнейший банк. Я тогда отшутился. А сейчас, пожалуй, назвал бы три причины. 1. Я разработал методику легкого обучения в разработке. Я вижу фронтэнд как систему, решающую узкий скоуп…»
Учим строки

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

Что вообще есть в скоупе строк?

- Кавычки. Три варианта: "str", 'str', интерполяция. Третий - для шаблонных строк.
- Спецсимволы. Всегда экранируются \. Перевод каретки, кавычки внутри строк, знак экранирования и т.д.
- Длина строки. Свойство length.
- Доступ к символам.
str[Index: int] -> char | undefined
str.charAt(index: int) -> char | ""
- Перебор (без преобразования в массив): for...of
- Иммутабельность. Строки нельзя мутировать - можно только заменять, как массивы в Реакте.
- Работа с регистром (капслок): toLowerCase, toUpperCase.

- Поиск подстроки:
- IndexOf и lastIndexOf. Принимает искомую подстроку и начальный индекс, возвращает индекс первого символа (включая 0, который неявно приводится к false) найденной подстроки или -1. Может работать в цикле. Первый ищет с начала строки, второй - с конца.

indexOf(target: string, startIndex: int) -> int | -1
lastIndexOf(target: string, position: int) -> int | -1
~str.indexOf(…) -> -int | 0 // побитовый трюк для легаси-кода

- includes, startsWith, endsWith
Сигнатурно аналогичны indexOf. Возвращают булево значение в зависимости, найдено совпадение или нет.

Получение подстроки. Возвращает результат поиска.

- slice(start: int, end: int) -> string | ""
возвращает подстроку, включая start, но не включая end. Может принимать отрицательные числа для поиска с конца.

- substring(start: int, end: int) -> string | ""
возвращает подстроку между start и end. Не понимает отрицательных чисел.

- substr(start: int, length: int) -> string | ""
принимает начальный индекс и длину искомого отрезка. Умеет искать с конца (отрицательный индекс).

- Триммер: trim(). Удаляет пробелы по обе стороны строки.
- Повторитель: repeat(count). Повторяет строку count раз.
Бонус: иллюстрация работы метода slice :)
What's next?

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

Вот что я хочу освежить, дополнить или изучить в этом году:

- Тестирование. Jest, разные виды тестирования компонентов и сопутствующих систем. TDD.
- Бандлинг. Webpack и аналоги.
- Typescript. Узнать его глубже. Дженерики и другие сложные элементы типизации. Разработка от интерфейсов.
- Верстка. Словить тренды, вспомнить старое.
- Middleware. Танки, саги, вот это всё.
- Авторизация. JWT и подобное.
- Архитектура. Чтобы делать проекты с нуля, руководить их разработкой, общаться с бизнесом.
- UI/UX. Чтобы аргументированно общаться с дизайнерами.
- Ну и пару книг об искусстве переговоров.

Список неполон, но это и не обязательно. Во фронте вообще планы должны быть гибкими :)
Аgile. От хаоса к решению.

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

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

Цикл повторяется, масштабируясь до отдельных фич.
Таким образом команды вступают в борьбу с хаосом - с неопределенностью, непониманием, расплывчатыми перспективами - и приходят к четким решениям.

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

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

Изучим относительные единицы измерения по методике работы с хаосом.

Шаг 1. Бизнес-задача: изучить востребованную на рынке часть modern CSS - относительные единицы: em, rem и другие.

Шаг 2. Поставим вопросы:

- Что это?
- Какие задачи решает?
- В чём выгода использования?
- Как использовать?

Шаг 3. Ответы, кейсы.
Это относительные единицы размерности:
Em - emphemeral unit,
rem - root em,
vh - viewport height,
vw - viewport width.

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

Шаг 4. Детали реализации. Эти единицы устанавливаются относительно некоего абсолютного указателя - размерности шрифта или размера viewport:
Установленная браузером по умолчанию (16px) (em)
Унаследованная от родительского блока (em)
Переданная напрямую (em)
Унаследованная от переменной :root (rem)
Установленная реальной шириной экрана (vw)
...высотой (vh)

Книга “Css для профи“ советует:
“Если сомневаетесь, используйте rem для размера шрифта, пиксели - для border-ов, а em - для большинства других свойств”.

Простейший кейс с em: https://codepen.io/gretten/pen/dyRdWQK

Задача выполнена. Остальное - вопрос опыта.