npm install — это RCE
Каждый раз, когда пишем
в деве, в CI, в проде. И почти никогда не читаем, что там внутри.
npm устроен так, что одна зависимость может притянуть сотни.
Где на любом уровне —
Добавляя “удобный” хелпер (`deepClone`, `dateFormat`), можно получить зависимости, у которых на любом уровне может быть:
–
–
– динамический импорт из внешнего URL
– замаскированный base64/pako payload
– инициализация, собирающая
Или просто:
– устаревший код, который тормозит рантайм
– сломанный код при следующем миноре
Многие пакеты не имеют CI, не проходят аудит.
Не так давно были заражены:
Сотни тысяч проектов могли получить трояна через обычный
Каждый
Если ты не читаешь, что ставишь — ты запускаешь произвольный код.
Чем ниже уровень — тем выше цена.
Zero Deps — это не мода. Это контроль.
Каждый раз, когда пишем
npm install, мы запускаем чужой код: в деве, в CI, в проде. И почти никогда не читаем, что там внутри.
npm устроен так, что одна зависимость может притянуть сотни.
Где на любом уровне —
postinstall, бэкдор, телеметрия или просто баг.Добавляя “удобный” хелпер (`deepClone`, `dateFormat`), можно получить зависимости, у которых на любом уровне может быть:
–
postinstall, выполняющий shell-скрипт –
prepare с подменой файлов перед публикацией – динамический импорт из внешнего URL
– замаскированный base64/pako payload
– инициализация, собирающая
ENV, hostname и proxy-данные Или просто:
– устаревший код, который тормозит рантайм
– сломанный код при следующем миноре
Многие пакеты не имеют CI, не проходят аудит.
package-lock не спасёт, если у тебя ^ или обновление в dev-зависимости.Не так давно были заражены:
eslint-config-prettier, eslint-plugin-prettier, @pkgr/core. Сотни тысяч проектов могли получить трояна через обычный
npm install.Каждый
npm install — это акт доверия. Если ты не читаешь, что ставишь — ты запускаешь произвольный код.
Чем ниже уровень — тем выше цена.
Zero Deps — это не мода. Это контроль.
👍3👨💻3❤2
WebSocket не нужен. Почти всегда.
WebSocket создаёт иллюзию простоты:
постоянное соединение, двусторонняя связь, никакой лишней обёртки.
Но на практике это тяжёлый протокол и почти всегда избыточный.
Постоянное соединение — не бесплатное.
Каждое соединение — это:
– выделенный TCP-сокет
– память на буферы и таймеры
– регулярные
– необходимость держать пользовательский state на сервере
При 100k подключениях в idle:
– память: 300–500 МБ (`Node.js`, `uWebSockets.js`)
– CPU: ~7% просто на поддержание соединений
– одна массовая рассылка может триггерить GC и просадку latency
Масштабирование WebSocket — отдельная боль:
– Sticky-сессии обязательны (иначе нельзя писать адресно)
– Горизонтальное масштабирование требует внешнего
– Протокол не работает через CDN
– Никаких встроенных
В большинстве задач WebSocket не нужен вообще.
Альтернативы проще, стабильнее, масштабируются лучше:
– EventSource (SSE)
Односторонний канал
– Long polling
Рабочий вариант. HTTP-запрос держится открытым до появления данных. Поддерживает edge-кеш, не требует сложной инфраструктуры.
– Push через REST + CDN
Если данные можно кешировать даже на 1–2 секунды — дешевле отправить через pull-модель.
– MQTT / raw TCP
Когда реально нужен duplex и контроль — лучше уйти на уровень ниже. Там можно сделать свой протокол с
WebSocket — это не default.
Это инструмент для узкого класса задач:
двусторонняя синхронизация, RTC, редакторы, игры.
Во всех остальных случаях он добавляет ресурсоёмкость, сложность и архитектурные ограничения. Без реальной пользы.
Если можно не держать соединение — не держи.
WebSocket создаёт иллюзию простоты:
постоянное соединение, двусторонняя связь, никакой лишней обёртки.
Но на практике это тяжёлый протокол и почти всегда избыточный.
Постоянное соединение — не бесплатное.
Каждое соединение — это:
– выделенный TCP-сокет
– память на буферы и таймеры
– регулярные
ping/pong пакеты – необходимость держать пользовательский state на сервере
При 100k подключениях в idle:
– память: 300–500 МБ (`Node.js`, `uWebSockets.js`)
– CPU: ~7% просто на поддержание соединений
– одна массовая рассылка может триггерить GC и просадку latency
Масштабирование WebSocket — отдельная боль:
– Sticky-сессии обязательны (иначе нельзя писать адресно)
– Горизонтальное масштабирование требует внешнего
pub/sub – Протокол не работает через CDN
– Никаких встроенных
ack, retry, QoS
В большинстве задач WebSocket не нужен вообще.
Альтернативы проще, стабильнее, масштабируются лучше:
– EventSource (SSE)
Односторонний канал
server → client. HTTP/1.1, простой протокол, балансируется обычным nginx. Подходит для realtime фидов, алертов, обновлений UI.– Long polling
Рабочий вариант. HTTP-запрос держится открытым до появления данных. Поддерживает edge-кеш, не требует сложной инфраструктуры.
– Push через REST + CDN
Если данные можно кешировать даже на 1–2 секунды — дешевле отправить через pull-модель.
REST + Cloudflare/Fastly + max-age — и клиент почти в real-time.– MQTT / raw TCP
Когда реально нужен duplex и контроль — лучше уйти на уровень ниже. Там можно сделать свой протокол с
ack, QoS, batching.WebSocket — это не default.
Это инструмент для узкого класса задач:
двусторонняя синхронизация, RTC, редакторы, игры.
Во всех остальных случаях он добавляет ресурсоёмкость, сложность и архитектурные ограничения. Без реальной пользы.
Если можно не держать соединение — не держи.
❤4👍4🔥2
Мысли GPT
Раньше я спорил с собой. Сейчас чаще — с GPT.
Иногда он формулирует неплохие мысли.
Highload — не про RPS.
Это про поведение системы на пределе.
Пики запросов сами по себе ни о чём, если не видно, что в этот момент творится с CPU, памятью, диском, сетью, очередями.
Важно не просто «держать удар», а понимать — за счёт чего.
Где система начинает деградировать?
Есть ли backpressure?
Как быстро сбои эскалируются?
Что происходит, когда ресурсы заканчиваются: сбрасываем, замедляемся или падаем?
Настоящий highload начинается там, где масштабирование уже не спасает.
И выигрывают не те, у кого больше инстансов.
А те, кто знает, как ведёт себя их код под нагрузкой.
Раньше я спорил с собой. Сейчас чаще — с GPT.
Иногда он формулирует неплохие мысли.
Highload — не про RPS.
Это про поведение системы на пределе.
Пики запросов сами по себе ни о чём, если не видно, что в этот момент творится с CPU, памятью, диском, сетью, очередями.
Важно не просто «держать удар», а понимать — за счёт чего.
Где система начинает деградировать?
Есть ли backpressure?
Как быстро сбои эскалируются?
Что происходит, когда ресурсы заканчиваются: сбрасываем, замедляемся или падаем?
Настоящий highload начинается там, где масштабирование уже не спасает.
И выигрывают не те, у кого больше инстансов.
А те, кто знает, как ведёт себя их код под нагрузкой.
❤3👍2🤔1
JSON — не для highload
Просто потому что он не был для этого задуман.
Формат JSON — компромисс между читаемостью и универсальностью.
Но как только он оказывается в hot-path — на входе, в кэше, в ответе — он начинает течь.
Что не так:
-
-
- Без схемы — нет совместимости, всё проверяется вручную.
- Нет стриминга — только весь объект целиком.
- Типы примитивны — всё превращается в строки и числа.
- Производительность непредсказуемая, особенно на глубоко вложенных структурах.
Вывод:
JSON — это про обмен между людьми и машинами.
Highload — это обмен между системами.
Нужны схемы, бинарные форматы, стриминг и предсказуемость.
JSON туда не лезет. Его туда тянут.
Альтернатива: fast-json-stringify
Компилирует сериализацию по JSON Schema. Без магии, без рекурсии, без сюрпризов.
Но лучше без JSON, zero deps же
Просто потому что он не был для этого задуман.
Формат JSON — компромисс между читаемостью и универсальностью.
Но как только он оказывается в hot-path — на входе, в кэше, в ответе — он начинает течь.
Что не так:
-
JSON.stringify — дорого: рекурсивный обход, аллокации, кастомные .toJSON(), ошибки на BigInt. -
JSON.parse — не потоковый, всегда аллоцирует всё сразу. - Без схемы — нет совместимости, всё проверяется вручную.
- Нет стриминга — только весь объект целиком.
- Типы примитивны — всё превращается в строки и числа.
- Производительность непредсказуемая, особенно на глубоко вложенных структурах.
Вывод:
JSON — это про обмен между людьми и машинами.
Highload — это обмен между системами.
Нужны схемы, бинарные форматы, стриминг и предсказуемость.
JSON туда не лезет. Его туда тянут.
Альтернатива: fast-json-stringify
Компилирует сериализацию по JSON Schema. Без магии, без рекурсии, без сюрпризов.
Но лучше без JSON, zero deps же
👍3❤1
Почему встроенный http — всё ещё лучший выбор для Node.js-сервера
Многие до сих пор тянут Express, Fastify и похожие обёртки. Но если ты сам пишешь системный код, думаешь про производительность и безопасность — разумнее остаться на стандартном
Плюсы встроенного модуля:
– ноль зависимостей;
– полное управление поведением, без скрытой логики;
– работа напрямую с сокетом;
– стабильный API с Node.js 0.x;
– читаемый, отлаживаемый и без сюрпризов.
Каждый фреймворк добавляет слои. Иногда они удобны. Но чаще — маскируют простую задачу под “современный подход”, добавляют неявные правила, просадки производительности, боттлнеки и риски от зависимостей.
Некоторые навязывают архитектурные паттерны, даже когда задача — просто перекладывать JSON.
Если ты не пишешь pet-проект, а отвечаешь за критичный сервис —
Многие до сих пор тянут Express, Fastify и похожие обёртки. Но если ты сам пишешь системный код, думаешь про производительность и безопасность — разумнее остаться на стандартном
http.Плюсы встроенного модуля:
– ноль зависимостей;
– полное управление поведением, без скрытой логики;
– работа напрямую с сокетом;
– стабильный API с Node.js 0.x;
– читаемый, отлаживаемый и без сюрпризов.
Каждый фреймворк добавляет слои. Иногда они удобны. Но чаще — маскируют простую задачу под “современный подход”, добавляют неявные правила, просадки производительности, боттлнеки и риски от зависимостей.
Некоторые навязывают архитектурные паттерны, даже когда задача — просто перекладывать JSON.
Если ты не пишешь pet-проект, а отвечаешь за критичный сервис —
import { createServer } from 'node:http' — это всё, что тебе нужно.❤3👍2
Zero deps — не религия, а привычка проверять
Любая npm-зависимость — это:
- чужой код, который запускается у тебя (иногда сразу через postinstall);
- десятки транзитивных пакетов, о которых ты даже не знаешь;
- риск: от уязвимостей и бэкдоров до банального “сломано”.
Но zero deps — не про страх. Это про привычку.
Что значит zero deps-подход на практике:
1. Если можешь — не ставь. Особенно ради одной функции.
2. Если ставишь — изучи. На чём держится? Легко ли заменить?
3. Если сомневаешься — напиши своё. Примитив? Да. Зато твой.
Zero deps — не догма, а здравый осознанный подход.
Это про поддержку, прозрачность и контроль.
Если ты не знаешь, что именно запускаешь — это не твой код.
Любая npm-зависимость — это:
- чужой код, который запускается у тебя (иногда сразу через postinstall);
- десятки транзитивных пакетов, о которых ты даже не знаешь;
- риск: от уязвимостей и бэкдоров до банального “сломано”.
Но zero deps — не про страх. Это про привычку.
Что значит zero deps-подход на практике:
1. Если можешь — не ставь. Особенно ради одной функции.
2. Если ставишь — изучи. На чём держится? Легко ли заменить?
3. Если сомневаешься — напиши своё. Примитив? Да. Зато твой.
Zero deps — не догма, а здравый осознанный подход.
Это про поддержку, прозрачность и контроль.
Если ты не знаешь, что именно запускаешь — это не твой код.
❤5👍2
Архитектура без фетишей
Видел в проектах:
– IUserUseCase - UserServiceImpl - UserRepository - PgUserAdapter
– UserAggregate, UserEntity, UserDto, UserModel — всё про одного юзера
– CommandBus, EventBus, QueryHandler — в CRUD-е на три метода
Вместо рабочего кода — культ архитектуры.
Что модно (и часто бесполезно):
• Clean Architecture — 4 слоя прокладок. Половина — просто return next().
• DDD — доменная модель ради модели. Предметки нет, названия остались.
• Hexagonal — “порты/адаптеры”, но один порт, один адаптер, всегда вместе.
• CQRS — два класса вместо одного. Зато “правильно”.
• Event-driven всё — даже если можно было вызвать функцию.
• SOLID — звучит строго, но на практике усложняет там, где не нужно.
Что реально работает:
– DRY - один раз написал, используешь, где нужно.
– KISS - чем проще, тем лучше.
– SRP — код делает одну вещь.
– DIP — зависимости подменяемы, логика изолирована.
– Явные зависимости — без сервис-локаторов и “магии”.
– Простая структура — transport → logic → storage.
– Меньше абстракций — проще читать, проще менять.
Не пиши код для Дяди Боба. Пиши для следующего инженера.
Не строй архитектуру как диплом. Строй так, чтобы не было стыдно жить с этим через полгода.
Архитектура — не манифест. Это способ не получить стрелу в колено через месяц.
Видел в проектах:
– IUserUseCase - UserServiceImpl - UserRepository - PgUserAdapter
– UserAggregate, UserEntity, UserDto, UserModel — всё про одного юзера
– CommandBus, EventBus, QueryHandler — в CRUD-е на три метода
Вместо рабочего кода — культ архитектуры.
Что модно (и часто бесполезно):
• Clean Architecture — 4 слоя прокладок. Половина — просто return next().
• DDD — доменная модель ради модели. Предметки нет, названия остались.
• Hexagonal — “порты/адаптеры”, но один порт, один адаптер, всегда вместе.
• CQRS — два класса вместо одного. Зато “правильно”.
• Event-driven всё — даже если можно было вызвать функцию.
• SOLID — звучит строго, но на практике усложняет там, где не нужно.
Что реально работает:
– DRY - один раз написал, используешь, где нужно.
– KISS - чем проще, тем лучше.
– SRP — код делает одну вещь.
– DIP — зависимости подменяемы, логика изолирована.
– Явные зависимости — без сервис-локаторов и “магии”.
– Простая структура — transport → logic → storage.
– Меньше абстракций — проще читать, проще менять.
Не пиши код для Дяди Боба. Пиши для следующего инженера.
Не строй архитектуру как диплом. Строй так, чтобы не было стыдно жить с этим через полгода.
Архитектура — не манифест. Это способ не получить стрелу в колено через месяц.
❤3👍2
Где начинается архитектура?
Часто под архитектурой подразумевают нечто крупное: диаграммы, сервисы, базы, очереди.
Но это лишь один из уровней.
На самом деле архитектура пронизывает всё: от названия переменной до устройства экосистемы из сотен сервисов.
Уровень 1: переменная
Если ты называешь переменную
Это снижает предсказуемость, повышает когнитивную нагрузку, увеличивает шанс ошибки.
Даже такие мелочи могут иметь архитектурные последствия.
Уровень 2: функция
Функция, которая делает несколько вещей, — антипаттерн, антиархитектура.
Плохая декомпозиция = невозможность покрыть тестами, переиспользовать, локализовать баг.
А потом эту функцию копируют, и дефект масштабируется. Это уже "вирусная" архитектура.
Чистая функция — там, где это возможно — хорошее правило.
Уровень 3: модуль (или класс)
Где границы? Что знает о внешнем мире? Где side-effect, где чистая логика?
Если всё перемешано — модуль становится токсичным. Появляется архитектурный долг.
Про SOLID можно вспомнить, но слепо следовать — не стоит. Это не закон, а инструмент.
Уровень 4: приложение
Здесь начинается архитектура, о которой все говорят: деплой, отказоустойчивость, масштабирование.
И здесь же локальные решения начинают конфликтовать между собой.
Вопросы:
- Где entry point и как устроен lifecycle?
- Разделены ли core и инфраструктура?
- API отделён от логики?
- Есть ли единый контейнер зависимостей?
- Поддерживается ли graceful shutdown?
- Что происходит под нагрузкой?
Если у тебя всё завязано на index
Если каждый модуль сам себе открывает базу — ты не управляешь ничем.
Уровень 5: сервисы и экосистема
Здесь архитектура становится явной.
Ты больше не один. Каждый модуль, каждая точка отказа — это чья-то зона ответственности.
Вопросы:
- Как сервисы общаются? Протоколы, форматы, контракты.
- Кто владеет данными и отвечает за консистентность?
- Что делать при недоступности одного из сервисов?
- Как живут запросы, проходящие через 3–5 сервисов?
- Есть ли ретраи, деградация, алерты?
Если сервис лезет в чужую базу — это не архитектура, а хаос.
Если один отказ тянет каскад — это не система, это стек домино.
Принцип
Архитектура — не про масштаб, а про осознанность.
Каждое локальное решение влияет на уровень выше.
Если ты не проектируешь явно — ты проектируешь по умолчанию. И, скорее всего, плохо.
Вывод
Архитектура начинается не на доске.
Она начинается с вопросов:
Если ты задаёшь эти вопросы — ты уже в процессе.
Если нет — никакие блок-схемы не помогут.
И да: плохое имя переменной может быть началом деградации.
Так же как чёткое понимание точек отказа — началом зрелости.
Часто под архитектурой подразумевают нечто крупное: диаграммы, сервисы, базы, очереди.
Но это лишь один из уровней.
На самом деле архитектура пронизывает всё: от названия переменной до устройства экосистемы из сотен сервисов.
Уровень 1: переменная
Если ты называешь переменную
data, tmp или того хуже a, b, ты вносишь неопределённость. Это снижает предсказуемость, повышает когнитивную нагрузку, увеличивает шанс ошибки.
Даже такие мелочи могут иметь архитектурные последствия.
Уровень 2: функция
Функция, которая делает несколько вещей, — антипаттерн, антиархитектура.
Плохая декомпозиция = невозможность покрыть тестами, переиспользовать, локализовать баг.
А потом эту функцию копируют, и дефект масштабируется. Это уже "вирусная" архитектура.
Чистая функция — там, где это возможно — хорошее правило.
Уровень 3: модуль (или класс)
Где границы? Что знает о внешнем мире? Где side-effect, где чистая логика?
Если всё перемешано — модуль становится токсичным. Появляется архитектурный долг.
Про SOLID можно вспомнить, но слепо следовать — не стоит. Это не закон, а инструмент.
Уровень 4: приложение
Здесь начинается архитектура, о которой все говорят: деплой, отказоустойчивость, масштабирование.
И здесь же локальные решения начинают конфликтовать между собой.
Вопросы:
- Где entry point и как устроен lifecycle?
- Разделены ли core и инфраструктура?
- API отделён от логики?
- Есть ли единый контейнер зависимостей?
- Поддерживается ли graceful shutdown?
- Что происходит под нагрузкой?
Если у тебя всё завязано на index
.mjs — это скрипт, не система. Если каждый модуль сам себе открывает базу — ты не управляешь ничем.
Уровень 5: сервисы и экосистема
Здесь архитектура становится явной.
Ты больше не один. Каждый модуль, каждая точка отказа — это чья-то зона ответственности.
Вопросы:
- Как сервисы общаются? Протоколы, форматы, контракты.
- Кто владеет данными и отвечает за консистентность?
- Что делать при недоступности одного из сервисов?
- Как живут запросы, проходящие через 3–5 сервисов?
- Есть ли ретраи, деградация, алерты?
Если сервис лезет в чужую базу — это не архитектура, а хаос.
Если один отказ тянет каскад — это не система, это стек домино.
Принцип
Архитектура — не про масштаб, а про осознанность.
Каждое локальное решение влияет на уровень выше.
Если ты не проектируешь явно — ты проектируешь по умолчанию. И, скорее всего, плохо.
Вывод
Архитектура начинается не на доске.
Она начинается с вопросов:
А можно ли тут сделать чистую функцию?
А читаемый ли код?
А если это упадёт — что произойдёт?
Если ты задаёшь эти вопросы — ты уже в процессе.
Если нет — никакие блок-схемы не помогут.
И да: плохое имя переменной может быть началом деградации.
Так же как чёткое понимание точек отказа — началом зрелости.
👍4🤔2❤1
0. Что даёт TypeScript, кроме типов
Вопрос не праздный. Типы — это главное, но не единственное, что приносит TS в проект.
Вот что ещё он навязывает:
Препроцессор
TS — это не просто типы, это много синтаксического сахара поверх JS.
Он требует компиляции. Всё, что ты пишешь — не исполняется напрямую.
Даже
Именно поэтому:
- нужна настройка
- нужен сборщик (tsc / swc / esbuild);
- нужен transpile target (ES2022? CommonJS?);
- нужна настройка импорта
Неочевидные runtime-эффекты
TS-фичи, которые не имеют смысла в runtime, но могут влиять на поведение:
- декораторы;
-
-
-
- namespace'ы (исчезают полностью, но могут конфликтовать при объединении файлов).
Если не знаешь, как это трансформируется — можно получить странный и не эффективный JS.
Ограничения по фичам языка
TS тормозит внедрение новых JS-фич:
- не всегда поддерживает новый синтаксис сразу;
- вынуждает писать в стиле, удобном для трансформации, а не читаемости;
- часто требует даунгрейда target (ради Node-совместимости);
- декларации типов для новых API появляются с задержкой.
Ты не можешь использовать свежий JS "как есть". Всё должно пройти через типовой компилятор.
Чем ближе ты к нативному JS — тем сильнее TS мешает.
TS становится точкой централизованной сложности
Любая сложность в сборке, типах, импортах, конфигурации — в итоге сводится к TypeScript:
- непонятный
- конфликт версий
- магия
- ESM + TS + Jest + Babel = боль.
Ты начинаешь тратить время не на разработку, а на отладку toolchain'а.
Что имеем в итоге?
TypeScript — это не просто типы.
Это целый инструментальный стек, который требует, чтобы ты думал в его терминах.
Иногда это оправдано. Чаще — нет.
Если всё, что тебе нужно — это типы, можно обойтись JSDoc'ом.
Без компиляции, без лишнего runtime, без скрытого препроцессинга.
Вопрос не праздный. Типы — это главное, но не единственное, что приносит TS в проект.
Вот что ещё он навязывает:
Препроцессор
TS — это не просто типы, это много синтаксического сахара поверх JS.
Он требует компиляции. Всё, что ты пишешь — не исполняется напрямую.
Даже
const x = 1 as const — это синтаксис, который нужно разобрать и превратить обратно в JS.Именно поэтому:
- нужна настройка
tsconfig.json;- нужен сборщик (tsc / swc / esbuild);
- нужен transpile target (ES2022? CommonJS?);
- нужна настройка импорта
Неочевидные runtime-эффекты
TS-фичи, которые не имеют смысла в runtime, но могут влиять на поведение:
- декораторы;
-
emitDecoratorMetadata;-
import type, export type;-
const enum (внезапно исчезает в скомпилированном коде);- namespace'ы (исчезают полностью, но могут конфликтовать при объединении файлов).
Если не знаешь, как это трансформируется — можно получить странный и не эффективный JS.
Ограничения по фичам языка
TS тормозит внедрение новых JS-фич:
- не всегда поддерживает новый синтаксис сразу;
- вынуждает писать в стиле, удобном для трансформации, а не читаемости;
- часто требует даунгрейда target (ради Node-совместимости);
- декларации типов для новых API появляются с задержкой.
Ты не можешь использовать свежий JS "как есть". Всё должно пройти через типовой компилятор.
Чем ближе ты к нативному JS — тем сильнее TS мешает.
TS становится точкой централизованной сложности
Любая сложность в сборке, типах, импортах, конфигурации — в итоге сводится к TypeScript:
- непонятный
moduleResolution;- конфликт версий
@types/...;- магия
paths и baseUrl;- ESM + TS + Jest + Babel = боль.
Ты начинаешь тратить время не на разработку, а на отладку toolchain'а.
Что имеем в итоге?
TypeScript — это не просто типы.
Это целый инструментальный стек, который требует, чтобы ты думал в его терминах.
Иногда это оправдано. Чаще — нет.
Если всё, что тебе нужно — это типы, можно обойтись JSDoc'ом.
Без компиляции, без лишнего runtime, без скрытого препроцессинга.
👍4❤3
1. TypeScript не гарантирует надёжность
TypeScript часто подают как инструмент «защиты от ошибок».
Но это не защита. Это компилятор, который верит тебе на слово.
Он проверяет типы только в пределах того, что ты сам описал.
Он не валидирует входные данные. Не ловит runtime-ошибки. Не гарантирует корректность логики.
Надежность типизации ограничена:
• если API возвращает не то — компилятор не узнает;
• если ты неверно понял схему — типы будут ложными;
• если прилетели некорректные данные — баг всё равно произойдёт;
• если хочешь обойти систему — as unknown as, @ts-expect-error, any и поехали.
TypeScript не мешает ошибаться — он просто делает это чище.
Пример:
Если не валидировать вход — получаешь баг на ровном месте.
А дальше выбор:
• тянуть рантайм-библиотеку (zod, io-ts);
• писать собственный инструмент для проверки;
• или руками валидировать каждый объект.
Возникает вопрос: какой ценой мы получаем мнимое ощущение безопасности при возможном несоответствии типов?
TypeScript — это не про надежность и «безопасность».
Это про удобство разработки внутри доверенного кода.
Без валидации на границах системы типы ничего не гарантируют.
В системе безопасность начинается с входных данных.
Типы — уже потом.
Другие части:
0. Что даёт TypeScript, кроме типов
TypeScript часто подают как инструмент «защиты от ошибок».
Но это не защита. Это компилятор, который верит тебе на слово.
Он проверяет типы только в пределах того, что ты сам описал.
Он не валидирует входные данные. Не ловит runtime-ошибки. Не гарантирует корректность логики.
Надежность типизации ограничена:
• если API возвращает не то — компилятор не узнает;
• если ты неверно понял схему — типы будут ложными;
• если прилетели некорректные данные — баг всё равно произойдёт;
• если хочешь обойти систему — as unknown as, @ts-expect-error, any и поехали.
TypeScript не мешает ошибаться — он просто делает это чище.
Пример:
//Описал тип
type User = { id: string, email: string }
//Ответ пришел
// { "id": 42, "email": null }
// Используешь тип
const user = response as User
// И компилятор тебе поверит.
// До первого user.email.toLowerCase()
Если не валидировать вход — получаешь баг на ровном месте.
А дальше выбор:
• тянуть рантайм-библиотеку (zod, io-ts);
• писать собственный инструмент для проверки;
• или руками валидировать каждый объект.
Возникает вопрос: какой ценой мы получаем мнимое ощущение безопасности при возможном несоответствии типов?
TypeScript — это не про надежность и «безопасность».
Это про удобство разработки внутри доверенного кода.
Без валидации на границах системы типы ничего не гарантируют.
В системе безопасность начинается с входных данных.
Типы — уже потом.
Другие части:
0. Что даёт TypeScript, кроме типов
👍3❤1🔥1
2. TypeScript ломает zero-deps и замедляет цикл разработки
Компиляция — это цена. Даже если ты её не замечаешь.
Для запуска TS-кода тебе нужно:
- tsconfig.json;
- транспиляция (tsc, swc, esbuild);
- типы зависимостей (@types/...);
- поддержка ESM или CommonJS;
- линтер с типами;
- конфиг для тестов, алиасов, runtime-импорта.
Каждый шаг — точка отказа. Любое несовпадение версий, любые нестыковки — и пайплайн падает.
Zero-deps становится невозможен: язык требует инфраструктуру.
Даже с Node.js v23.6.0, где TypeScript встроен на уровне рантайма, остаётся всё то же: конфигурация, типы, source maps, IDE-интеграция.
Это не «убрали сборку». Это встроили другой сборщик. Бремя осталось.
Чистый JS запускается сразу.
TS — только после обвязки.
Дольше цикл разработки
TypeScript ломает быстрый цикл:
написал → запустил → исправил → повторил.
Становится:
написал → собрал → подправил конфиг → проверил типы → запустил → проверил пути → дебажил через мапы → исправил → пересобрал.
Каждый шаг замедляет итерации. Особенно в системных проектах, где:
- код быстро меняется;
- архитектура нестабильна;
- важна предсказуемость запуска.
Ухудшение DX
Developer Experience — это не «удобно писать код в IDE».
Это: быстро читать, быстро запускать, быстро менять.
С TS:
- IDE может не видеть типы до полной сборки;
- tsc может упасть из-за файла, к которому ты не прикасался;
- линтеры требуют доп. плагинов и синхронизации с tsconfig;
- ошибки появляются там, где их нет на runtime.
TS вставляет прослойку между мыслью и результатом.
JS — работает напрямую.
Отладка — через боль
TypeScript ломает прозрачность рантайма:
- source maps не всегда точные;
- стеки уводят в dist/, не в исходник;
- console.log работает не там, где ты пишешь;
- hot reload требует костылей;
- для запуска кода из ./src тебе нужно убедиться, что ./dist актуален.
Тормозит командную работ
Любой merge может:
- поломать сборку;
- привести к конфликту типов;
- зацепить @types/..., которые подтянулись транзитивно;
- потребовать пересогласования alias.
Проект перестаёт быть “просто нодой”.
Он превращается в конвейер, который надо всё время чинить
Что в итоге?
TypeScript даёт мнимую стабильность на длинном горизонте.
Но за это ты платишь: скоростью, простотой, прозрачностью.
Если ты пишешь system-level tooling, оркестратор, сервер или CLI — TypeScript может быть не помощником, а помехой.
Чем ближе к ядру — тем важнее zero-deps.
И тем дороже каждая секунда, потерянная на сборку.
Другие части:
0. Что даёт TypeScript, кроме типов
1. TypeScript не гарантирует надёжность
Компиляция — это цена. Даже если ты её не замечаешь.
Для запуска TS-кода тебе нужно:
- tsconfig.json;
- транспиляция (tsc, swc, esbuild);
- типы зависимостей (@types/...);
- поддержка ESM или CommonJS;
- линтер с типами;
- конфиг для тестов, алиасов, runtime-импорта.
Каждый шаг — точка отказа. Любое несовпадение версий, любые нестыковки — и пайплайн падает.
Zero-deps становится невозможен: язык требует инфраструктуру.
Даже с Node.js v23.6.0, где TypeScript встроен на уровне рантайма, остаётся всё то же: конфигурация, типы, source maps, IDE-интеграция.
Это не «убрали сборку». Это встроили другой сборщик. Бремя осталось.
Чистый JS запускается сразу.
TS — только после обвязки.
Дольше цикл разработки
TypeScript ломает быстрый цикл:
написал → запустил → исправил → повторил.
Становится:
написал → собрал → подправил конфиг → проверил типы → запустил → проверил пути → дебажил через мапы → исправил → пересобрал.
Каждый шаг замедляет итерации. Особенно в системных проектах, где:
- код быстро меняется;
- архитектура нестабильна;
- важна предсказуемость запуска.
Ухудшение DX
Developer Experience — это не «удобно писать код в IDE».
Это: быстро читать, быстро запускать, быстро менять.
С TS:
- IDE может не видеть типы до полной сборки;
- tsc может упасть из-за файла, к которому ты не прикасался;
- линтеры требуют доп. плагинов и синхронизации с tsconfig;
- ошибки появляются там, где их нет на runtime.
TS вставляет прослойку между мыслью и результатом.
JS — работает напрямую.
Отладка — через боль
TypeScript ломает прозрачность рантайма:
- source maps не всегда точные;
- стеки уводят в dist/, не в исходник;
- console.log работает не там, где ты пишешь;
- hot reload требует костылей;
- для запуска кода из ./src тебе нужно убедиться, что ./dist актуален.
Тормозит командную работ
Любой merge может:
- поломать сборку;
- привести к конфликту типов;
- зацепить @types/..., которые подтянулись транзитивно;
- потребовать пересогласования alias.
Проект перестаёт быть “просто нодой”.
Он превращается в конвейер, который надо всё время чинить
Что в итоге?
TypeScript даёт мнимую стабильность на длинном горизонте.
Но за это ты платишь: скоростью, простотой, прозрачностью.
Если ты пишешь system-level tooling, оркестратор, сервер или CLI — TypeScript может быть не помощником, а помехой.
Чем ближе к ядру — тем важнее zero-deps.
И тем дороже каждая секунда, потерянная на сборку.
Другие части:
0. Что даёт TypeScript, кроме типов
1. TypeScript не гарантирует надёжность
❤3👍2
Когда абстракция начинает мешать
Иногда код перестаёт быть инструментом и превращается в лабиринт.
Это происходит, когда абстракция, задуманная для упрощения, начинает скрывать важные детали и тормозить изменения.
Как это заметить:
- Чтобы изменить одну строчку логики, ты бродишь по трём-четырём слоям кода.
- Больше времени уходит на понимание связей, чем на саму задачу.
- В тестах приходится подменять половину проекта моками, потому что абстракции закрыли доступ к данным.
- Новому разработчику сложно объяснить архитектуру без многоуровневой диаграммы.
Абстракция — это контракт.
Это упрощённое представление сложной системы, которое скрывает детали реализации и оставляет только то, что нужно для использования.
Хорошая абстракция помогает работать с кодом на более высоком уровне, не вникая каждый раз в низкоуровневую реализацию.
Она полезна, пока:
1. Снижает когнитивную нагрузку.
2. Не мешает отладке.
3. Не ломает принцип локальности изменений (когда правка затрагивает минимум мест в коде).
Когда абстракция нарушает хотя бы два пункта, она превращается в технический долг.
Чеклист: вредная абстракция
Если хотя бы 3 ответа «да» — пора упрощать:
- Описание абстракции занимает больше одного предложения и включает «и ещё…»?
- Для отладки нужно читать её внутренний код?
- Неясно, где вход и где выход данных?
- Изменение бизнес-логики затрагивает больше трёх файлов?
- Для тестирования требуются сложные моки или фейки?
- Новому разработчику нужно больше часа, чтобы понять, как её использовать?
Пример 1:
Вместо прямого вызова
Пример 2:
Вместо простого HTTP-клиента для внешнего API сделали обёртку с «универсальным» интерфейсом, планируя поддержку разных провайдеров. Но новых провайдеров так и не появилось, а теперь при отладке запроса нужно разбираться в слоях адаптеров, трансформеров и middleware, которые в итоге лишь дублируют логику оригинального SDK.
Что в итоге?
Если абстракция усложняет отладку, замедляет изменения и заставляет править много несвязанных мест, её нужно упростить или удалить.
Хорошая абстракция ускоряет работу, плохая — тормозит.
Еще интересное про абстракции:
Закон дырявых абстракций - почему абстракции «протекают» и когда это важно.
Sebastian Markbåge “Minimal API Surface Area” - как лишние фичи мешают эволюции кода
Sandi Metz The Wrong Abstraction - как распознать «не ту» абстракцию и безопасно её распрямить.
Иногда код перестаёт быть инструментом и превращается в лабиринт.
Это происходит, когда абстракция, задуманная для упрощения, начинает скрывать важные детали и тормозить изменения.
Как это заметить:
- Чтобы изменить одну строчку логики, ты бродишь по трём-четырём слоям кода.
- Больше времени уходит на понимание связей, чем на саму задачу.
- В тестах приходится подменять половину проекта моками, потому что абстракции закрыли доступ к данным.
- Новому разработчику сложно объяснить архитектуру без многоуровневой диаграммы.
Абстракция — это контракт.
Это упрощённое представление сложной системы, которое скрывает детали реализации и оставляет только то, что нужно для использования.
Хорошая абстракция помогает работать с кодом на более высоком уровне, не вникая каждый раз в низкоуровневую реализацию.
Она полезна, пока:
1. Снижает когнитивную нагрузку.
2. Не мешает отладке.
3. Не ломает принцип локальности изменений (когда правка затрагивает минимум мест в коде).
Когда абстракция нарушает хотя бы два пункта, она превращается в технический долг.
Чеклист: вредная абстракция
Если хотя бы 3 ответа «да» — пора упрощать:
- Описание абстракции занимает больше одного предложения и включает «и ещё…»?
- Для отладки нужно читать её внутренний код?
- Неясно, где вход и где выход данных?
- Изменение бизнес-логики затрагивает больше трёх файлов?
- Для тестирования требуются сложные моки или фейки?
- Новому разработчику нужно больше часа, чтобы понять, как её использовать?
Пример 1:
Вместо прямого вызова
db.query(...) вы сделали три уровня: ORM + репозиторий + сервис. На старте это казалось «правильной архитектурой». Через год — для добавления нового поля в запрос вам приходится лезть в мапперы, DTO, конвертеры и тесты для каждой прослойки. Любая правка тянет за собой десяток файлов.Пример 2:
Вместо простого HTTP-клиента для внешнего API сделали обёртку с «универсальным» интерфейсом, планируя поддержку разных провайдеров. Но новых провайдеров так и не появилось, а теперь при отладке запроса нужно разбираться в слоях адаптеров, трансформеров и middleware, которые в итоге лишь дублируют логику оригинального SDK.
Что в итоге?
Если абстракция усложняет отладку, замедляет изменения и заставляет править много несвязанных мест, её нужно упростить или удалить.
Хорошая абстракция ускоряет работу, плохая — тормозит.
Еще интересное про абстракции:
Закон дырявых абстракций - почему абстракции «протекают» и когда это важно.
Sebastian Markbåge “Minimal API Surface Area” - как лишние фичи мешают эволюции кода
Sandi Metz The Wrong Abstraction - как распознать «не ту» абстракцию и безопасно её распрямить.
❤3👍2🔥2
3. Когда TypeScript оправдан
TypeScript — не серебряная пуля. Это инструмент управления сложностью, а не способ «починить» слабую архитектуру. Он помогает согласовывать контракты и делает рефакторинг предсказуемым, но не заменяет системный дизайн.
Когда TS имеет смысл:
- Длинная жизнь продукта и кодовая база, которая растёт годами.
- Несколько команд, параллельные изменения, обилие внутренних и внешних API.
- Есть публичные SDK/клиенты, где обратная совместимость — не пожелание, а требование.
- Стоимость рантайм-ошибки высока: деньги, SLA, безопасность.
Если проект маленький, недолговечный, с узкими интерфейсами — издержки типизации часто превышают пользу.
Что даёт TS:
- Контракты на границах модулей. Явные типы для DTO, событий, адаптеров. Ломаете поле — компилятор показывает фронт разрушений.
- Дешёвый рефакторинг. Переименование, выделение интерфейсов, смена сигнатур — IDE и компилятор ведут тебя за руку.
- Локализация неопределённости. unknown/never/asserts подчёркивают места, где риск и где нужны проверки.
- Документация, которая не врёт. Типы — это живой, проверяемый артефакт, а не устаревающий README.
Чего TS не даёт:
- Архитектуры. Границы контекстов, потоки данных, изоляция побочных эффектов — это решения уровня дизайна.
- Согласованности данных и эволюции схем. Миграции, инварианты и порядок обновления сервисов — за вами.
- Надёжности. Повторные попытки, идемпотентность, дедупликация событий — это протоколы и инфраструктура.
- Производительности. Типы не ускоряют обработку запросов и не уменьшают GC-паузы.
Формула простая: TS снижает стоимость изменения, но не подменяет мышцу архитектурного мышления.
Пример:
Рефакторинг поля total в amount в DTO между сервисами orders и billing:
С TS практически сразу будет обозначен фронт проблем во всех местах, где читали total, включая сериализацию, агрегации и тесты. Без TS часть мест «проскочит», всплывёт в проде при редком кейсе.
Контрпример:
В одном модуле сетевая логика, доменная модель и кэш. TS это оттипизирует, но не разрежет связность. По-прежнему сложно независимо тестировать и разворачивать части. TS помогает согласовать и облегчить рефакторинг, но не исправит архитектуру.
Что в итоге?
Используй TS там, где много кода, много людей и много интерфейсов. Там он окупается: контракты формализованы, рефакторинг дешевле, регрессов меньше. Не жди, что TS решит проблемы дизайна — слабые границы, излишнюю связность и плохие протоколы он не лечит.
Если проект небольшой — строгая дисциплина модулей и простые соглашения дадут такой же эффект с меньшими накладными расходами.
Другие части:
0. Что даёт TypeScript, кроме типов
1. TypeScript не гарантирует надёжность
2. TypeScript ломает zero-deps и замедляет цикл разработки
TypeScript — не серебряная пуля. Это инструмент управления сложностью, а не способ «починить» слабую архитектуру. Он помогает согласовывать контракты и делает рефакторинг предсказуемым, но не заменяет системный дизайн.
Когда TS имеет смысл:
- Длинная жизнь продукта и кодовая база, которая растёт годами.
- Несколько команд, параллельные изменения, обилие внутренних и внешних API.
- Есть публичные SDK/клиенты, где обратная совместимость — не пожелание, а требование.
- Стоимость рантайм-ошибки высока: деньги, SLA, безопасность.
Если проект маленький, недолговечный, с узкими интерфейсами — издержки типизации часто превышают пользу.
Что даёт TS:
- Контракты на границах модулей. Явные типы для DTO, событий, адаптеров. Ломаете поле — компилятор показывает фронт разрушений.
- Дешёвый рефакторинг. Переименование, выделение интерфейсов, смена сигнатур — IDE и компилятор ведут тебя за руку.
- Локализация неопределённости. unknown/never/asserts подчёркивают места, где риск и где нужны проверки.
- Документация, которая не врёт. Типы — это живой, проверяемый артефакт, а не устаревающий README.
Чего TS не даёт:
- Архитектуры. Границы контекстов, потоки данных, изоляция побочных эффектов — это решения уровня дизайна.
- Согласованности данных и эволюции схем. Миграции, инварианты и порядок обновления сервисов — за вами.
- Надёжности. Повторные попытки, идемпотентность, дедупликация событий — это протоколы и инфраструктура.
- Производительности. Типы не ускоряют обработку запросов и не уменьшают GC-паузы.
Формула простая: TS снижает стоимость изменения, но не подменяет мышцу архитектурного мышления.
Пример:
Рефакторинг поля total в amount в DTO между сервисами orders и billing:
С TS практически сразу будет обозначен фронт проблем во всех местах, где читали total, включая сериализацию, агрегации и тесты. Без TS часть мест «проскочит», всплывёт в проде при редком кейсе.
Контрпример:
В одном модуле сетевая логика, доменная модель и кэш. TS это оттипизирует, но не разрежет связность. По-прежнему сложно независимо тестировать и разворачивать части. TS помогает согласовать и облегчить рефакторинг, но не исправит архитектуру.
Что в итоге?
Используй TS там, где много кода, много людей и много интерфейсов. Там он окупается: контракты формализованы, рефакторинг дешевле, регрессов меньше. Не жди, что TS решит проблемы дизайна — слабые границы, излишнюю связность и плохие протоколы он не лечит.
Если проект небольшой — строгая дисциплина модулей и простые соглашения дадут такой же эффект с меньшими накладными расходами.
Другие части:
0. Что даёт TypeScript, кроме типов
1. TypeScript не гарантирует надёжность
2. TypeScript ломает zero-deps и замедляет цикл разработки
❤3👍2
Визуализация архитектуры через C4 Model
C4 — модель визуализации архитектуры, предложенная Саймоном Брауном.
Её задача — дать единый способ описания системы на разных уровнях детализации, так чтобы и бизнес, и разработчики, и эксплуатация видели одну и ту же архитектуру, но с нужной им степенью глубины.
Вместо одной перегруженной диаграммы C4 предлагает четыре отдельных представления:
1. Context
Показывает систему в окружении: кто её использует, какие внешние сервисы есть, как идёт взаимодействие. Это уровень, на котором важны роли пользователей, интеграции и точки входа.
2. Container
Разделение системы на крупные исполняемые блоки: сервисы, базы данных, мобильные приложения, очереди, файлохранилища. Здесь фиксируются протоколы, точки развёртывания, границы отказоустойчивости.
3. Component
Внутреннее устройство контейнера: ключевые модули, их обязанности и связи. Это взгляд разработчика на систему, когда важно понять, в каком модуле живёт логика и как она связана с другими частями.
4. Code
Детализация до уровня классов и функций. В отличие от UML, в C4 этот уровень опционален — его используют только там, где реально полезно держать карту реализации.
Чем полезна модель:
- Делит архитектурное описание на понятные и управляемые слои.
- Даёт общий язык для обсуждения — от бизнеса до инженеров.
- Упрощает актуализацию документации: можно обновить один уровень, не переписывая всё.
- Работает одинаково для монолитов, распределённых систем и микросервисов.
Что важно понимать:
- C4 — это формат описания, а не метод проектирования. Он не подскажет, как правильно разделить сервисы или где ставить границы.
- Модель фиксирует уже принятые решения. Если решение плохое, на диаграмме оно просто будет аккуратно нарисовано.
- Она хорошо работает только при регулярном обновлении. Устаревшая диаграмма хуже, чем её отсутствие.
Пример применения:
Для новой системы можно сделать Context-диаграмму для стейкхолдеров, Container-уровень для DevOps и команды разработки, а Component-уровень — для бэкендеров, которые будут поддерживать сервис.
При этом все уровни связаны: элемент на Context-уровне соответствует контейнеру на следующем, а контейнер — набору компонентов внутри.
Полезное:
https://c4model.com/
C4 — модель визуализации архитектуры, предложенная Саймоном Брауном.
Её задача — дать единый способ описания системы на разных уровнях детализации, так чтобы и бизнес, и разработчики, и эксплуатация видели одну и ту же архитектуру, но с нужной им степенью глубины.
Вместо одной перегруженной диаграммы C4 предлагает четыре отдельных представления:
1. Context
Показывает систему в окружении: кто её использует, какие внешние сервисы есть, как идёт взаимодействие. Это уровень, на котором важны роли пользователей, интеграции и точки входа.
2. Container
Разделение системы на крупные исполняемые блоки: сервисы, базы данных, мобильные приложения, очереди, файлохранилища. Здесь фиксируются протоколы, точки развёртывания, границы отказоустойчивости.
3. Component
Внутреннее устройство контейнера: ключевые модули, их обязанности и связи. Это взгляд разработчика на систему, когда важно понять, в каком модуле живёт логика и как она связана с другими частями.
4. Code
Детализация до уровня классов и функций. В отличие от UML, в C4 этот уровень опционален — его используют только там, где реально полезно держать карту реализации.
Чем полезна модель:
- Делит архитектурное описание на понятные и управляемые слои.
- Даёт общий язык для обсуждения — от бизнеса до инженеров.
- Упрощает актуализацию документации: можно обновить один уровень, не переписывая всё.
- Работает одинаково для монолитов, распределённых систем и микросервисов.
Что важно понимать:
- C4 — это формат описания, а не метод проектирования. Он не подскажет, как правильно разделить сервисы или где ставить границы.
- Модель фиксирует уже принятые решения. Если решение плохое, на диаграмме оно просто будет аккуратно нарисовано.
- Она хорошо работает только при регулярном обновлении. Устаревшая диаграмма хуже, чем её отсутствие.
Пример применения:
Для новой системы можно сделать Context-диаграмму для стейкхолдеров, Container-уровень для DevOps и команды разработки, а Component-уровень — для бэкендеров, которые будут поддерживать сервис.
При этом все уровни связаны: элемент на Context-уровне соответствует контейнеру на следующем, а контейнер — набору компонентов внутри.
Полезное:
https://c4model.com/
👍5❤3