melikhov.dev – Telegram
melikhov.dev
4.63K subscribers
110 photos
2 videos
2 files
203 links
Фронтенд, фронт-бек и около. Всё, что в голову пришло. Иногда котики.
Download Telegram
Для меня архитектура — это как сделать сегодня так, чтобы не было мучительно больно завтра. Если про завтра не думать, то и архитектура не нужна. Например, по таким законам живут MVP и одноразовые промо-странички. https://news.1rj.ru/str/artalog/340
О да
«Существует ловушка, в которую попадает каждый программист: элегантный код. Я часто удивляюсь, замечая, что даже опытные разработчики квалифицируют фрагмент кода как элегантный или красивый.

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

—- в этой части автор ругает миддлвары. Пропустим очевидное —-

«В заключение я хотел бы сказать, что хороший код не умный и не элегантный. Он читаемый, надежный, наивный, или одним словом — простой. И вы, наверное, знаете, как сложно сделать код простым :)»

https://insertafter.com/en/blog/no_more_middlewares.html
👍18👎1
В пятницу буду на Я люблю фронтенд (мы собрали осколки того то, что от нас осталось, склеили кое-как и дотащили конфу до продакшена) рассуждать о том, какой стек для бэкенда на node.js я бы выбрал в 2022. Будет онлайн, всё как всегда бесплатно, знания бесценны.

Ах да, наконец-то скажем, кто и как победил в ctf.
24👍6
В Barking store наткнулся на шапочку архитектора.
🔥10🤔2
Одна из самых больших проблем в запоминании принципов SOLID (если вы зачем-то хотите их запомнить) — это буква L, (LSP, Liskov substitution principle, или принцип подстановки Барбары Лисков). Каноническое определение его скучно до зевоты: Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

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

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

Чтобы подружить квадрат и прямоугольник, мы должны исключить наличие в родительском классе методов, противоречащих методам потомка. Но тогда зачем нам вообще нужен такой убогий класс — родитель? И вот тут начинают смотреть в сторону композиции вместо наследования, чтобы не натягивать сову на глобус.

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

https://news.1rj.ru/str/xufostation/772
🔥17👍6👎1
Цитата с Хабра

«Вообще, при разработке, наследование можно рассматривать с двух позиций: подклассы — это подтипы, со всеми ограничениями контрактного программирования и принципа Лисков, и подклассы — это способ переиспользовать код, со всеми своими потенциальным проблемами. Т.е. можно либо думать и проектировать ответственности классов и контракты и не переживать про клиентский код. Либо думать про то, какой может быть клиентский код, как классы будут использоваться, и быть готовым к потенциальным проблемам, но в меньшей степени заботится о соблюдении принципа подстановки. Решение как обычно за разработчиком, самое главное, чтобы выбор в конкретной ситуации был осознанный и было понимание какие плюсы, минусы и подводные камни сопровождают то или иное решение.»
👍6
После сегодняшнего обсуждения лямбда-боли:
——
думаю, что мы придём к тому, что будет одна лямбда для всего http и отдельным опциональным лямбдам там, где нужен s2s
——

Предложил сразу встроить фастифай и позже отправить это в кубер :troll_mode_activated:
😁3
Кстати, если среди читающих есть опытные пользователи лямбд — поделитесь в комментах: пакуете ли вы все HTTP-лямбды в одно приложение с внутренним роутингом, слушающее корневой роут в API Gateway, или разделяете на множество мелких «контроллеров», обслуживающих отдельные роуты?
Первый раз за несколько месяцев написал
let
. Но потом одумался и вынес код в функцию.
🔥24😁13👍31👏1
После того как команда Платформы разделила монорепу фронтенда (неудобно же работать в монорепе!) на полирепу с микрофронтами, каждая бизнесовая команда тихонько начала пилить свои маленькие монорепы (ну удобно же работать в монорепе!).
😁24🔥9🤯3👏1
«Простота — это великая добродетель, но она требует напряженной работы, чтобы её достичь и образование, чтобы оценить. И к сожалению, сложность продается легче.» — Дейкстра.



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

Кажется, что раз решение сложное — то в него вложено больше труда и значит оно должно быть лучше.

Это, конечно, не так. Процитирую Паскаля: «если бы у меня было больше времени, я бы написал письмо покороче».

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

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



Отличная статья об этом феномене на примере научных статьей в области машинного обучения. Спасибо Игорю за наводку.
👍1310
Вот Виктор Хомяков на Субботнике подтверждает мнение, что JS не лучший выбор для FP с точки зрения перфоманса и лимитов памяти (ох уж эти цепочки map().filter().reduce() и прочие спреды). Да, Ramda может немного упростить жизнь но фундаментальные противоречия отсутствия оптимизации хвостовой рекурсии (кроме Сафари) и иммутабельных структур на уровне движка непреодолимы.

Мутируйте, господа, если работает с большими обьёмами. Мутировать не стыдно, мутировать — это нормально, когда мы втыкаемся в проблемы с производительностью.
Или, хотя бы, дробите на чанки.
👍122
По поводу того, как же мы будем жить без Server Push (словно кто-то с ним жил, ха). Начиная с Chrome 103 (и только Chrome) на всех пользователей раскатали поддержку стандарта 103 Early Hints который заключается в такой простой идее: если ваш сервер очень тугодумный и не может мгновенно выплюнуть на запрос ответ 200 с HTML, то вы можете использовать время ожидания с пользой, ответить сначала статусом 103, в тело положить Link со ссылками на важные ресурсы, браузер побежит их качать, а тут и 200 долетит с основным телом ответа.

HTTP/1.1 103 Early Hints
Link: </style.css>; rel="preload"; as="style"
Link: <https://cdn.test>; rel="preconnect"

HTTP/1.1 200 OK
Link: </style.css>; rel="preload"; as="style"
Link: <https://cdn.test>; rel="preconnect"
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
...

Что тут важно: если вы можете ответить быстро, то ничего городить не нужно. Присылайте 200 и на важные ресурсы пропишите link rel=preload и link rel=preconnect. Браузер сам решит, как дальше оптимизировать загрузку. Т.е. Early Hints это такой костылик для случаев когда у вас удивительно медленный серверный рендер и за время тугодумия сервера браузер может что-то полезное скачать.

Upd

Когда написал пост, внезапно осознал ещё один важный кейс: edge-прокси. Собственно, для чего Cloudflare одними из первых и внедрили экспериментальную поддержку. Работает это так, что на проксирующем сервере, расположенном близко к клиенту, доклеивается заголовок
103
, выплёвывается ответ, а дальше запрос бежит на ваш далеко расположенный сервер.
👍121
Где бы монорепа точно пригодилась

В webpack module federation есть такая штука как shared dependencies. Мы указываем в головном приложении, что у нас есть один экземпляр
react
, один
react-dom
, один
react-router-dom
и что эти синглтоны шарятся между всеми модулями.
Вот как-то так


new ModuleFederationPlugin({
name: 'host',
shared: {
react: { singleton: true, requiredVersion: dependencies.react, eager: true },
'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], eager: true },
'react-router-dom': {
singleton: true,
requiredVersion: dependencies['react-router-dom'],
eager: true,
}
},


Теперь каждый подключаемый модуль не будет тащить свою версию реакта, а использовать общую. Красиво, но есть проблема обновлений зависимостей.

Во-первых, само по себе обновление может стать болезненным, если в мажоре библиотеки сломают API (привет react-router) так, что старый код не сможет работать с новым API. Т.е. ставим условный react 19 и наш подключаемый модуль ломается, нам нужно переписать и релизнуть новые версии микрофронтов максимально быстро вслед за хостом (что для многих проектов просто недопустимо).

Во-вторых, весьма болезненно ходить по полирепе, останавливать релизы, вносить правки в зависимости и код каждого отдельного микрофронта, а после массово релизить.

В монорепозитории очевидно можно протащить это за один PR.
🤔3👍2
Где монорепа в целом бесполезна

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

Но мы забыли про деплой. Да, релизится такая монорепа одним скриптом, который катит бэк на сервера бэкенда, а фронт на фронтовые сервера (Next, или просто статика в S3, не важно). И вот тут в момент деплоя возникает рассинхрон. То, что там казалось монолитной сущностью во время разработки, оказывается не монолитом в проде. Пользователь заходит на обновлённый фронт, а бэк ещё не готов и отвечает ошибкой. Или наоборот, старый фронт стучится в новый бэк.

Что тут можно сделать? Ну, например, закрывать серверы парами и делать стики-сессии. Но если на канареечном сервере это приемлимо, то в бою стики это странно. Мы хотим распределять нагрузку, раскидывая запросы на разные бэки, а не делать вечные пары фронт-бек. Другая проблема, что пользователь может не обновить фронт в браузере, а бэк уже обновился. Тут и стики не помогут.

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

Итого, с моей точки зрения, для разнородных сущностей монорепа малополезна. Можно сложить в монорепу бэкенды. Можно отдельно сложить в другую монорепу фронтенды. В отдельный репозиторий положить контракты. В четвёртый и пятый репозитории лягут iOS и Android-фронты. Для UI-компонентов тоже можно завести свою монорепу. Сложить туда и компоненты и атомы дизайн-системы и что ещё пригодится. Но упаси вас господь паблишить компоненты UI-кита как отдельные пакеты, это дорога в ад.
7👍4🤔1
В предыдущем посте затронул тему ui-kit. Не раз встречал желение прикрутить к киту условную Лерну и паблишить компоненты как отдельные сущности. Мотивация понятная — мало кто хочет поднимать UI-kit во всём приложении, когда дизайнер принёс страничку с новым элементом. Гораздо проще было бы поставить непосредственно этот элемент (или обновить существующий). Однако, в проектах, где такой подход принят, я вижу сильную неоднородность фронта: на одной странице можно встретить 3-4 разные итерации развития кнопки. Что-то доехало из корня, что-то транзитивно из более сложных сущностей. В итоге, пользователь недоумевает почему дизайн такой лоскутный (тут кнопка круглая, а тут квадратная, тут галочки синие, а тут зелёные, а тут вообще градиент с тенями вылез), а бандл раздут излишними копиями одной и той же сущности разных мажорных и минорных её версий.

Да, обновлять kit во всём приложении сразу сложнее, но делать это необходимо и делать это нужно регулярно, чтобы не столкнуться с ситуацией, когда ради нового аттрибута кнопки нужно прыгнуть через три мажора. А такое я тоже встречал.
🔥7👍4
Неделю назад жаловался на отсутствие иммутабельных структур в JS, а тут вдруг всплыл интересный пропозал по добавленю в JS иммутабельных Record и Tuple (кортежей).

Если кратко, то предлагается добавить в язык два новых «сложных» иммутабельных примитива, собственно Record и Tuple.
Описываются они так

const record = #{ a: 1, b: 2 }

const tuple = #[1, 2]


Мутировать их нельзя, внутри могут лежать только примитивы, никаких ссылок на объекты и функции (но над этим работают, возможно ссылки разрешат). Так как это примитивы, то сравниваются они по значению, а не по ссылкам


const record1 = #{ a: 1, b: 2 }
const record2 = #{ a: 1, b: 2 }
const record3 = #{ ...record1 }
const record4 = #{ b: 2, a: 1 }

record1 === record2 // true
record1 === record3 // true
record1 === record4 // true ключи сортируются лексически



Ещё раз — это примитивы


typeof #{ a: 1 } === "record"
typeof #[1, 2,3] === "tuple"



Так как кортежи выглядят как массивы, то очень хотелось бы к ним применять методы из Array.prototype, но проблема в том, что многие методы там мутирующие. Так что вместе с этим пропозалом едет второй пропозал с добавлением иммутабельных методов в Array и TypedArray

• Array.prototype.toReversed() -> Array
• Array.prototype.toSorted(compareFn) -> Array
• Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
• Array.prototype.with(index, value) -> Array


И он уже на Stage 3 и приедет к нам прежду всего с новым Safari

Ну и вдогонку доклад https://portal.gitnation.org/contents/record-and-tuple-immutable-data-structures-in-js
24🔥9🥰4👍3