melikhov.dev – Telegram
melikhov.dev
4.63K subscribers
110 photos
2 videos
2 files
203 links
Фронтенд, фронт-бек и около. Всё, что в голову пришло. Иногда котики.
Download Telegram
Одна из самых больших проблем в запоминании принципов 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
Forwarded from artalog (artalar)
Давно жду этот пропосал и одно из самых интересных нововведений там это доп синтаксис для иммутабельного изменения глубоколежащего свойства.
👍101
Как устроена локализация в крупных проектах

В маленьких проектах всё просто — пишем все тексты в интерфейсах на одном языке и в ус не дуем. Если же проект вырастает и начинает экспансию, то вместе с ней приходит боль. Все тексты нужно заменять на ключи, выносить в файлики и базы данных (такой кортеж из ключа и набора переводов на разные языки) и заменять ключ на перевод по выбору пользователя.
Если говорить о фронтенде, то тут сразу отпадает вариант с динамической подстановкой текстов из загруженной в бандл бд — наборы переводов для всех языков просто раздуют бандл. Это значит, что нам нужно подготовить переведённые бандлы в момент сборки, чтобы пользователь грузил только те тексты, которые нужны в его выбранной локали. А есть же ещё RTL-языки, ох.
Это всё решаемо и это только полбеды.

Вторая половина беды состоит в том, что процесс перевода человеком — это синхронный процесс. В крупном проекте не можете выкатить релиз не переведя его на хотя бы базовый набор языков. Особенно, если ваш основной язык не международный. Фоллбечить немецкий в русский это просто смешно 🙂 Значит, вам нужно чтобы к релизу были готовы два языка — локальный + английский. Очевидно, что за переводы не должен отвечать разработчик или дизайнер, для этого есть ребята, прокачанные в мультиязычном написании текстов.

Для работы с переводчиками используют так называемые translation management systems которые в чём-то похожи на content managment systems. Это специальный UI, где переводчики видят какие свежие ключи приехали, как они лежат в контексте (неплохо, когда вместе с ключами едет подсказка их применения), переводят и аппрувят их.

Процесс выглядит примерно так

1. Разработчик расставляет в коде интерфейса ключи, а не тексты. Рядом заводит файлик, в котором описывает связки ключ -> { дефолтный текст, множестенные формы, подсказки переводчику }
2. В момент билда все ключи загружаются в TMS, находятся изменения и переводчик получает задачу на переводы
3. Билд стоит в ожидании перевода на базовый минимум языков
4. Переводчик в интерфейсе TMS делает свою работу и жмёт аппрув
5. По готовности переводов CI выкачивает их и собирает локализованные бандлы, билд едет на прод. Что не перевели — фоллбечится в английский (скорее всего). Остатки доедут в следующий релиз.

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

Для примера TMS систем рекомендую посмотреть на Weblate
👍17🔥7
Интерфейс Weblate
🤡4👍2
Кот Кеша прощается с летом под https://youtu.be/-1I50VfyH1I
❤‍🔥27
Доехало видео с ЯЛФ.
TL;DR на картинке

https://youtu.be/dh3rKUYNlUw
15👍7