melikhov.dev – Telegram
melikhov.dev
4.63K subscribers
110 photos
2 videos
2 files
203 links
Фронтенд, фронт-бек и около. Всё, что в голову пришло. Иногда котики.
Download Telegram
А я говорил (яжеговорил), что лямбды это дорого. Лямбды это способ гибко быстро масштабироваться при очень редких пиковых нагрузках, но держать на них большой проект — решение очень спорное.
А монолит.. Ну тут как с чиплетами, раз нужна скорость, значит придётся собирать всё вместе, поближе.

https://www.primevideotech.com/video-streaming/scaling-up-the-prime-video-audio-video-monitoring-service-and-reducing-costs-by-90
👍91
В свежем выпуске подкаста напомнил соведущим, что error.stack это не стандарт. Тут же Лёша отыскал пропозал по Error.prototype.stack. TS вот согласен, поля stack в объекте Error может и не быть:


interface Error {
name: string;
message: string;
stack?: string;
}


Как мы много узнали про ошибки и исключения в тот момент, когда TS 4.4 переключил catch(e) из any в unknown :) Пришлось разбираться и вспоминать, что в исключении может лететь не ошибка, а что угодно, например строка ( throw 'Oops' ). Набор полей там неизвестен, обработчики исключений необходимо писать, чтобы они сами не падали в исключения (а я встречал и такое).

На самом деле V8 даёт несколько довольно интересных механизмов по работе с трейсами. Например, можно увеличить глубину стектрейса через Error.stackTraceLimit или через флаг v8 --stack-trace-limit.

Ещё из интересного — так как остальные движки представляют стек просто как строку, то V8 решил не ломать это поведение, но даёт нам доступ к методу Error.prepareStackTrace(error, structuredStackTrace) который вызывается в момент обращения к свойству stack. И вот тут уже внутри метода можно работать с нормальным объектом structuredStackTrace и переписать весь вывод так, как нам нравится. Но опять же, напомню, что только в V8.

Подробнее в блоге V8
👍22
Многопоточность (мультитрединг) в JS

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

Да, у нас есть возможность порождать веб-воркеры и воркер-треды (одно в браузере, второе в node.js, соответственно). Можно соорудить воркер-пул и отбрасывать туда тяжёлые задачи, асинхронно забирая ответы. Многопоточность ли это? Скорее многопроцессность. Мы порождаем независимый процесс с полностью независимым контекстом (и независимым event loop) и общаемся с ним через сообщения с сериализованными данными или transferable objects (ArrayBuffer и ImageBitmap, например). Т.е. можно перебрасывать некоторый набор специальных объектов между контекстами (не шарить, а именно безопасно передавать из контекста в контекст).

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

Надо будет сделать отдельный пост про проблемы SharedArrayBuffer.

UPD

В комментах посоветовали эту книгу https://dmkpress.com/catalog/computer/programming/java/978-5-93700-129/ . Я сначала немного бомбанул на название, но авторы оказались честны «Надо признать, что, вопреки названию книги, сам язык не содержит никаких встроенных средств для создания потоков
👍35🔥12
Нет пароля — нет проблем?

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

Тл;др: выводы печальные — работает всё очень не очень. Лонг стори шот.

Как-то раз собрались разные ребята из разных больших компаний и решили порешать проблему паролей. И назвались они FIDO Alliance. После нескольких итераций родился стандарт FIDO2, который состоит собственно из протокола CTAP2 и браузерной апихи WebAuthn. Протокол этот позволяет браузеру общаться с хардварными аутентификаторами, встроенными в наши телефоны/ноутбуки, либо переносимыми (YubiKey). Идея простая как палка — когда мы хотим где-то зарегистрироваться, то создаётся пара ключей: публичный и приватный. Публичный ключ уезжает на сайт, приватный загружается в вашу железку (почти как SSH-ключи, только никаких файлов на диске не валяется). Каждая такая пара ключей уникальна для каждого сайта. Красть с сервера теперь нечего — ваш публичный ключ никуда не подложить.

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

Несмотря на то, что технология предполагалась для полной замены паролей, она, в основном, везде была внедрена как второй фактор (хотя сама по себе является двухфакторной, ваши ключики защищены биометрией или пин-кодом). Почему так? Да просто из-за излишней сложности для обычных нормальных людей. Ключи надо привязывать к аккаунтам во всём множестве, 1 устройство — 1 ключ. Т.е. на айфоне, и на макбуке и на айпаде надо создавать отдельные ключи (не копии). YubiKey немного спасает, но в него влазит мало ключей, потерять его легко, стоит дорого, а пользоваться неудобно.

Ну и год назад Apple на WWDC сказали, что вот, теперь у них готово решение, которое называется Passkeys. Что это такое? Это фактически те же самые WebAuthn ключи, но переносимые через облако. Вы создали 1 ключ на айфоне — теперь он доступен на всех Apple-устройствах. Google и Microsoft присоединились к этой идее переносимых через облако приватных ключей.

Стало ли лучше? И да и нет. Если вы живёте на экосистеме Apple, пользуетесь Safari и iCloud то всё у вас будет замечательно. С Chrome уже похуже (синхронизацию допиливают). С Firefox просто никак. В облаках Google и Microsoft тоже раздрай. В Linux нет и не планируется. Чужих облаков в этой системе пока нет (1Password обещает скоро запилить своё). Синхронизации между разными облаками тоже нет. Более того, переводя ключи в облако, Apple понизила безопасность WebAuthn убрав аттестацию аутентификатора. И, что ещё хуже, начиная с iOS 16 они отключили возможность хранить ключи локально в Secure Enclave телефона, обязательно требуется подключенный iCloud. Сволочи.

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

Так что вот, решение какое-то есть, но вопросов к нему многовато.
👍525
Нет пароля — нет проблем?.pdf
8.4 MB
Слайды с доклада
🔥224👍3👏3
Взываю к помощи коллективного разума.

Смотрите, имеем node.js-процесс. Процесс спаунит чайл-процессы. Убийство родителя по ctrl+c приводит и к смерти чайлд-процессов. Но почему отправка SIGINT не приводит к такому результату и чайлды не умирают? Ни SIGINT, ни SIGTERM ни злобный SIGKILL не трогают чайлдов.
Так в чём здесь отличие ctrl+c?

UPD коллективный разум нашёл ответ https://stackoverflow.com/questions/8398845/what-is-the-difference-between-ctrl-c-and-sigint/8406413#8406413

Баш сам пробрасывает SIGINT по всей группе процессов.
👍16🔥3
Опять Гугл всё сломал

В мае Гугл запустил новый домен первого уровня .zip и неплохо так насолил безопасникам. Дело в том, что браузеру тоже можно скормить урл с userinfo частью (<протокол>//<userinfo>@<домен>), а значит, можно подсунуть пользователю адрес вида
https://github.com∕kubernetes∕kubernetes∕archive∕refs∕tags∕@v1.27.1.zip
.

Заметили подставу? А если бы вам не сказали, что в ссылке есть проблема?

Переход по такой ссылке направит нас на
v1.27.1.zip
. Более того, собачку в адресе можно скрыть в интерфейсе, уменьшив шрифт.

А чтобы жизнь была ещё веселей, Гугл добавил TLD .mov. А мессенджеры превратили это в ссылки. Так и живём.

Подробнее о проблеме и вариантах атак: https://medium.com/@bobbyrsec/the-dangers-of-googles-zip-tld-5e1e675e59a5

UPD

В фишинговой ссылке из примера используется два хака: символ @ и «другой» слеш ∕ (U+2215)
😁23🤯20😱42🤬2👍1😢1
Каждый год перед запуском ctf мучительно ищу подходящий RSA-ключ чтобы настроить каждый сервер. 1Password сделал свой ssh-agent но только в облачной версии, на которую я по понятным причинам переходить не хочу. Хранить на юбиках? Ну так ещё проще потерять юбик, чем облако.
Вот и продолжаю хранить в файликах и подбирать подходящий. Ещё один вариант — сделать мастер пароль и спрятать его в 1Password, а при утере ключа — ну и фиг с ним, залить новый.

Эй, подписчики, а вы как храните свои ключи?
Кто поленился настроить UFW и выставил ctf портами наружу — тот я :) Спасибо Саше Шоронову, что посканировал порты и пришёл с багой. Сколько не ставь Basic Auth в nginx, если рядом нода слушает сеть, то ой. А делал бы на юникс сокетах — горя бы не знал, но что-то подвела привычка (когда писал первую ctf уже жил в парадигме, что в контейнере nginx не нужен, и нода сама слушает сеть, как бы это не странно звучало).

В общем, не будьте мной, не ленитесь в виртуалках прикрывать порты. А unix-сокеты так-то всё ещё хороши.


sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'

UPD

Тут просят пояснить, что за набор букв я
написал. Смотрите, запущенный сервер на node.js слушает либо unix-сокет (который описывается как файл на файловой системе) либо сетевой порт. Какой-нибудь 3000, например. Перед нодой стоит какой-нибудь nginx, слушает порты 80 и 443 и закидывает трафик на 3000 порт локлхоста который и слушает node.js. Это называется «реверс-прокси». Если не закрыть порты фаерволом, оставив только 80, 443 и какой-нибудь 22 для ssh, то можно будет просканировать все порты снаружи, найти наш 3000 и постучаться на ноду в обход nginx, забив на все его правила.
Если же nginx гонит трафик на node через unix-сокет, то даже при открытых портах снаружи через сеть туда уже будет не достучаться.
😱1511👍4
Вот загадка, почему на macOS не идёт в комплекте git. Каждый раз этот квест с установкой Xcode Command Line Tools или brew install git (который под капотом ставит Xcode Command Line Tools).

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

Вот SSH-клиент у нас же есть. Плохонький, но есть.
👍32
В поезде прочитал интересные размышления от François Zaninotto — Is React Having An Angular.js Moment?

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

И вот у нас React предлагает поменять парадигму и перейти на Server Components. И это опасное место.

Теперь наши компоненты уже не просто рисуют отображение по состоянию. Они становятся сложнее. Теперь прямо в компонентах мы можем использовать fetch не заворачивая его useEffect. И это не браузерный fetch, это патченая его версия. Зачем патченая? Для того, чтобы бороться с лишним ре-фетчем данных и обложить всё кешами из коробки. А кешировать забрасывая данные в контекст мы больше не можем — нет в серверных компонентах контекста.

Вообще useState, useContext и useEffect — все они не работают. Мы можем их включить, используя use client, но это теперь не поведение по умолчанию.

CSS-in-JS решения тоже пока не работают в серверных компонентах. (Тут я конечно немного радуюсь, что «классические» решения всё так же хороши).

С отладкой тоже пока не очень, но тут наверное допилят (должны допилить!).

Экосистемы для RSC просто нет. Знакомые нам либы не работают (react-query, swr, react-hook-form и т.д.). Короче, всё, что на хуках.
Если они нам нужны, то мы должны заворачивать всё в обёртки с use client.

Почти всё сломано. А ещё и контекст отобрали, как теперь лёгкий DI пилить?

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

Разработчикам SPA всё это не нравится. Они обеспокоены. Для SSR они готовы брать другие решения. Почему Реакт отговаривает от SPA? Почему официальная документация рекомендует Next, а Next рекомендует RSC? Может быть это способ помочь Vercel заработать на React? Ведь для RSC нужен бэкенд, а бэкенд нужно где-то запускать.

Но отвечая на заглавный вопрос, проходит ли React свой «момент Angular», автор говорит — нет, не проходит. Потому что мы всё ещё можем писать «по-старому». Без Server Components.

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

В заключении Франсуа призывает команды React и Next одуматься и притушить рекламный поток для RSC. Не выпячивать это решение как противовес «классике» и единственное возможное будущее, чтобы не навредить экосистеме React.
👍658😱1💘1
Студент прислал вопрос — почему этот код тормозит?


console.time ("answer time");
const max = 100000000000;

console.log(' max=', max);
let i = 0;
while(i < max) {
const y = Math.pow(2,100);
const z = Math.pow(15, 100000);
i++;
}
console.timeEnd("answer time");


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

Что же, открываем Deopt Explorer и ищем проблему. На самом деле, вот этот код будет тормозить точно так же:


const max = 100000000000;

let i = 0;
while(i < max) {
i++;
}


Deopt Explorer сразу даёт нам причину деоптимизации —
i++ (overflow)
. Что за дела, спросите вы? Это же далеко не Number.MAX_SAFE_INTEGER, запаса достаточно. Дело в том, что V8 (как и другие JS-движки) умеет эффективно работать со SMI (small integers). На 64-битных платформах это соответственно диапазон от -2³¹ до 2³¹-1.

Проверим?


%DebugPrint(2147483647);

DebugPrint: Smi: 0x7fffffff (2147483647)


А вот мы вышли за границы SMI:


%DebugPrint(2147483648);

DebugPrint: 2147483648.0
0x148993d415e9: [Map] in ReadOnlySpace
- type: HEAP_NUMBER_TYPE
- instance size: 16
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x148993d415b9 <undefined>
- prototype_validity cell: 0
- instance denoscriptors (own) #0: 0x148993d41269 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)>
- prototype: 0x148993d41339 <null>
- constructor: 0x148993d41339 <null>
- dependent code: 0x148993d41251 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0


Такой подход называется pointer tagging — за счёт отдельного бита мы можем точно сказать, что в данном случае у нас не указатель на heap, а непосредственно значение примитива. Т.е. брать значение прямо из стека, а не бегать за ним в кучу. Получается, что как только мы выходим за границу SMI мы уже начинаем работать с объектом в куче и теряем в производительности.

В итоге как обычно попали в ловушку синтетических тестов и протестировали не то, что хотели протестировать, но многое поняли :)

UPD

Бенчмарки для оригинального цикла: 1:35.161 (m:ss.mmm)

Для решения с вложенным циклом (чтобы указатели остались в smi): 48.156s

А если мы поможем JIT и вложенный цикл уберём в функцию, то: 32.325s
🔥5819🤯8👍6❤‍🔥1
Нашёл неплохую статью с подробным объяснением того, как JS работает с памятью

TL;DR:

All JavaScript values are allocated on the heap accessed by pointers no matter if they are objects, arrays, strings or numbers (except for small integers i.e. smi in V8 due to pointer tagging).

The stack only stores temporary, function-local and small variables (mostly pointers) and that's largely unrelated to JavaScript types.

https://www.zhenghao.io/posts/javanoscript-memory
👍291
Немного удивлён, что никто не обратил внимания на фразу «На 64-битных платформах диапазон значений для SMI от -2³¹ до 2³¹-1». А для 32-битных как? А почему?

32-битные системы хранят указатели и SMI вот так (речь про v8)

|----- 32 bits -----|
Pointer: |_____address_____w1|
Smi: |___int31_value____0|


64-битные вот так:


|----- 32 bits -----|----- 32 bits -----|
Pointer: |________________address______________w1|
Smi: |____int32_value____|0000000000000000000|


Т.е. в 32-битных системах мы теряем один бит на то, чтобы запомнить (протегировать) — указатель у нас или SMI, в SMI влазит только int31. В 64-битных системах метка (тег) переезжает в «свободные» 32-бита и мы можем хранить int32.

Кстати, с этим связан один неприятный момент. Когда в V8 решили включить Pointer Compression (сжатие указателей), то оказалось, что да, мы можем хранить два 32-битных указателя в одном 64-битном слове (там применяется достаточно сложный алгоритм), но вот снова положить два int31 — это приводит к деградации производительности (1% на Octane). А два int32 не помещаются, негде хранить тег.

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

UPD

В комментах @cevek подсказал, что всё же включили компрессию для SMI, и в браузере в SMI будет именно int31, а не int32 «как раньше».


%DebugPrint(2147483647)
DebugPrint: 2147483647.0
0x5f700000339: [Map] in ReadOnlySpace


С нодой интереснее, в ноде компрессия указателей изначально не была включена из-за проблем совместимости с нативными аддонами, потому мы продолжаем видеть в SMI int32. Есть такое обсуждение.
🔥14🤯62👍1
Красота какая едет к нам в TS 5.2 (да и в ECMAScript тоже). С новой декларацией using мы сможем вешать коллбеки на событие того, что ресурс больше не используется.

Закончили работу — освободили ресурс почти автоматически (всё равно код придётся написать).

Подробнее тут
🔥472🤯2🆒1
Страдал без трёхпанельного мёрджа в VSCode, а, оказывается, он на месте, просто надо галочку поставить.

Могли бы и из коробки включать!
😁26👍13🔥8👎1🤔1🤬1
В ноде есть встроенный механизм песочницы для запуска опасного кода. vm.runInNewContext(<код>, <контекст>) и погнали. Лучше и безопаснее eval, но... со своими проблемами. Всё ещё можно вылезти из песочницы и ударить в глобал. this.constructor.constructor и побежали веселиться.

Но вот ещё про одну забавную проблему расскажу.

Как можно подвесить процесс через песочницу? Очень просто:


vm.runInNewContext('while(true){}',{});


Но вы скажете, что есть же параметр timeout:


vm.runInNewContext('while(true){}',{timeout: 5});


Уже лучше. Но проблема в том, что timeout не ловит микротаски. Выносим луп в микротаску и снова DoS


vm.runInNewContext(
'Promise.resolve().then(()=>{while(true) {}});',
{},
{timeout:5}
);


Ок, проблема известная, в 15-й ноде дырку заделали фанерой.


vm.runInNewContext(
'Promise.resolve().then(()=>{while(true) {}});',
{},
{timeout:5, microtaskMode: 'afterEvaluate'}
);


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


vm.runInNewContext(
`
setTimeout(() => {
while(true) {};
}, 10000);`,
{setTimeout},
{timeout:5, microtaskMode: 'afterEvaluate'}
);


Всё попало, шеф. У нас DoS.

В общем, посмотрите лучше на vm2 или на более интересную изоляцию через isolated-vm
20🔥10👍4👀1
Метапакеты

Часто с возрастом появляется желание иметь пакет для пакетов — положить в него всё нужное и накатывать одной командой. Чтобы он привозил и eslint, и prettier и browserslist. Вот только npm не предназначен для метапакетов. npm не гарантирует, что транзитивные зависимости будут лежать плоско на первом уровне. Вот вообще не гарантирует. А пакеты на это завязываются.
Классика — проблемы с плагинами eslint которые не могут лежать, на втором уровне. Ну ок, это кажется починили в новой системе конфигов (я ещё не попроовал).

Или вот browserslist-useragent перенёс browserslist в peerDependencies. Что это значит? Где тут проблемы метапакетов?

Предположим такую схему зависимостей:


dependencies
<метапакет>
browserslist-useragent: 4.2.2
browserslist: 4.2.2

devDependencies
webpack
browserslist: 4.1.1


Предположим у вас npm 6 или вы работаете в режиме --legacy-peer-deps. Ставим зависимости и оказываемся в ситуации


node_modules
<метапакет>
node_modules
browserslist: 4.2.2

webpack
browserslist: 4.1.1
browserslist-useragent: 4.2.2


Вроде не страшно?

А теперь делаем npm prune --production


node_modules
<метапакет>
node_modules
browserslist: 4.2.2

browserslist-useragent: 4.2.2


Всё сломалось! browserslist-useragent больше не видит browserslist и падает.

Да, если у вас npm 7 и выше и вы перешли на новую работу с peerDeps то получите


node_modules
<метапакет>
node_modules
browserslist: 4.2.2

browserslist-useragent: 4.2.2
node_modules
browserslist: 4.2.2


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

Что тут можно сделать? Ну, например, написать скрипт, который контролирует версии базовых зависимостей и сам делает пул-реквесты по их поднятию. Либо остаться на мета-пакете и очень внимательно следить за структурой.
👍223❤‍🔥1
Кто-то уже слышал, кто-то ещё услышит, что Андрей Ситник порекомендовал переезжать с PostCSS на Lightning CSS. Очередная тулза в экосистеме, написанная на Rust. В наших рабочих сборках она уже появилась (под флагом), и вот захожу я в зависимости и вижу:

lightningcss
lightningcss-darwin-arm64


Прикольно. Это тебе не node-pre-gyp выкачивающий готовые билды с S3 и ломающийся на твоём защищённом от внешнего мира CI. А как так сделали?

Довольно изящно. У пакета прописаны опциональные зависимости от бинарников

"optionalDependencies": {
"lightningcss-darwin-x64": "1.21.5",
"lightningcss-linux-x64-gnu": "1.21.5",
"lightningcss-win32-x64-msvc": "1.21.5",
"lightningcss-darwin-arm64": "1.21.5",
"lightningcss-linux-arm64-gnu": "1.21.5",
"lightningcss-linux-arm-gnueabihf": "1.21.5",
"lightningcss-linux-arm64-musl": "1.21.5",
"lightningcss-linux-x64-musl": "1.21.5"
}

У каждого такого пакета с бинарником в package.json указана операционная система и архитектура:
ightning CSS. Очередная тулза в экосистеме, написанная на Rust. В
Так как зависимости указаны опциональными, то npm при их установке тихо отстреливает те, что сфейлились.


npm verb reify failed optional dependency /Users/melikhov/test/node_modules/lightningcss-darwin-x64
npm sill reify mark deleted [ '/Users/melikhov/test/node_modules/lightningcss-darwin-x64' ]


И остаётся только та, что нужна (в моём случае `lightningcss-darwin-arm64`).
🔥80👍116😍2
Вот тут Серёжа печатает про джунов. Как им (представителям эдтеха) видится ситуация с наймом новичков. Мне, как человеку поработавшему в крупных энтерпрайзах и небольших и больших стартапах, нанимавшему и обучавшему — ситуация видится иначе. Сразу скажу, что это мой взгляд, правды тут может быть ни на грош, в бизнесе я ни в зуб ногой.

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

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

Уменьшить этот эффект можно внутренними школами. Взять сразу много разработчиков, посадить их за парты и познакомить со всей спецификой комплексно и сразу. Вместо того чтобы один синьор занимался одним джуном — он обучит сразу 10/20/30. И стоимость погружения в проект снижается. Но подводный камень тут есть, даже подводная скала и имя ей снова деньги. Внутренняя школа стоит дорого, как минимум она стоит времени всех задействованных в ней людей (а нужны люди с хорошими скиллами). И построить такие школы могут только достаточно крупные компании, которые, опять же, мыслят длинными горизонтами планирования. Им нужна база хороших разработчиков на несколько лет вперёд.

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

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

А поинт мой в том, что не стоит думать, что компании такие плохие, не хотят брать новичков. Да нет, хотят, но просто не могут себе это позволить. Баланс не сходится.
👍5212🤡3🤔1💩1