Давай поговорим на чистоту. Вспомни свой самый постыдный баг, который ты допустил за прошлый год. Баг, за который тебе невероятно стыдно, такой баг, что все твои коллеги теперь могут подумать, что ты плохой разработчик. Насколько банальный, что тебе теперь кажется, что каждый хихикает над тобой, выключив камеру на созвоне.
Теперь вспомни такой же постыдный баг, который допустил кто-то из твоих коллег, год назад. Не можешь? Знаешь почему? Потому что ты единственный разработчик, на проекте, который вносит баги в кодовую базу, и тебе уже стоит что-то с этим сделать
Теперь вспомни такой же постыдный баг, который допустил кто-то из твоих коллег, год назад. Не можешь? Знаешь почему? Потому что ты единственный разработчик, на проекте, который вносит баги в кодовую базу, и тебе уже стоит что-то с этим сделать
Я в универе зачитывался серий книг "Игра Эндера". Потрясающая фантастика, и мне очень жаль, что экранизировали только первую книгу. В книге как и в фильме есть цитата главного героя: "вместе с настоящим пониманием, позволяющим победить врага, приходит любовь к нему…"
Последнюю неделю я пишу код на typescript и мне начинается нравиться этот язык. Я всю жизнь писал на java/kotlin языках со строгой типизацией и всегда казалось, что в этих языках уже есть все, что только нужно и сложно придумать что-то еще. Я всегда держался подальше от технологий фронтенда. В этом проблема, когда долго сидишь на одной платформе, оказываешься в пузыре, появляется ложное ощущение, что ты все знаешь.
Typescript показал, что существует много концепций, о которых я даже не задумывался. И речь не только про структурную типизацию. Выразительность TypeScript позволяет делать вещи, которые казались невозможными. Можно не просто указать какое значение вернет функция, можно описать что она будет возвращать в каждом конкретном случае.
Вот например:
Вы можете себе в kotlin представить функцию, которая возвращает разные типы в зависимости от аргумента? Не наследника какого-нибудь sealed класса, а именно что совершенно другой тип.
Эта же мощность мне кажется является и недостатком Typescript. В kotlin начинающего разработчика система типов сдерживает, в Typescript же напротив, позволяет слишком многое.
Последнюю неделю я пишу код на typescript и мне начинается нравиться этот язык. Я всю жизнь писал на java/kotlin языках со строгой типизацией и всегда казалось, что в этих языках уже есть все, что только нужно и сложно придумать что-то еще. Я всегда держался подальше от технологий фронтенда. В этом проблема, когда долго сидишь на одной платформе, оказываешься в пузыре, появляется ложное ощущение, что ты все знаешь.
Typescript показал, что существует много концепций, о которых я даже не задумывался. И речь не только про структурную типизацию. Выразительность TypeScript позволяет делать вещи, которые казались невозможными. Можно не просто указать какое значение вернет функция, можно описать что она будет возвращать в каждом конкретном случае.
Вот например:
type ParseType = "number" | "boolean" | "string";
function parse(
input: string,
type: ParseType
): number | boolean | string {
switch (type) {
case "number":
return parseFloat(input);
case "boolean":
return input === "true";
case "string":
return input;
default:
throw new Error(`Unknown type: ${type}`);
}
}
typeof parse("42", "number") // number
typeof parse("42", "boolean") // boolean
typeof parse("42", "string")) // string
Вы можете себе в kotlin представить функцию, которая возвращает разные типы в зависимости от аргумента? Не наследника какого-нибудь sealed класса, а именно что совершенно другой тип.
Эта же мощность мне кажется является и недостатком Typescript. В kotlin начинающего разработчика система типов сдерживает, в Typescript же напротив, позволяет слишком многое.
Одна из горячих тем для обсуждения сейчас статья, разработчика, который кричит на весь интернет: мельчают джуны и всего из-за вашего AI, а вот в мое время… Теперь они не пытаются разобраться в проблеме, а сразу бегут в ChatGPT, задают вопрос и бездумно копипастят.
По мне так такого рода аргументы, несусветная глупость. Я пришел в IT в 2018 году, знаете, что тогда мне сказал один из сеньоров? Джуны сейчас уже не те, они не пытаются разобраться, а сразу идут в StackOverflow! Готов поспорить, что в нулевых обвиняли во всем IDE с автодополнением. До нулевых – высокоуровневые языки, собаки сутулые никто Си учить не хочет!
Больше похоже на попытку получить просмотры за счет поднятия тургеневской проблемы. Да инструменты меняются, я и сам уже не помню когда на StackOverflow заходил в поиске проблемы.
Что касаемо того, что джуны не разбираются в проблеме. Так и до бума LLM много кто тупо копировал код из StackOverflow, также не копая вглубь. По моим наблюдениям джуны не стали менее компетентными, они такими всегда и были)
По мне так такого рода аргументы, несусветная глупость. Я пришел в IT в 2018 году, знаете, что тогда мне сказал один из сеньоров? Джуны сейчас уже не те, они не пытаются разобраться, а сразу идут в StackOverflow! Готов поспорить, что в нулевых обвиняли во всем IDE с автодополнением. До нулевых – высокоуровневые языки, собаки сутулые никто Си учить не хочет!
Больше похоже на попытку получить просмотры за счет поднятия тургеневской проблемы. Да инструменты меняются, я и сам уже не помню когда на StackOverflow заходил в поиске проблемы.
Что касаемо того, что джуны не разбираются в проблеме. Так и до бума LLM много кто тупо копировал код из StackOverflow, также не копая вглубь. По моим наблюдениям джуны не стали менее компетентными, они такими всегда и были)
Поговорим про версионирование. Я недавно внимательно прочитал статью про SemVer и понял, что все это время, использовал его не правильно в своих либах, штош. Поэтому я решил подробнее разобраться в теме версионирования ПО, какие типы бывают, и как их правильно применять.
Если начать гуглить типы версионирования, что википедия, что ChatGPT, что различные статьи обрушат на вас кучу разных типов. Однако при внимательном прочтении, можно увидеть, что по факту все эти типы сводятся к четырем:
👉 SemVer
👉 С использованием даты
👉 Хеш коммита/номер билда
👉 Гибрид всех что выше
Какой бы подход вы не нашли, он так или иначе сведется к одному из тех, что я описал выше. Теперь давайте подробнее про каждый.
SemVer – базовая база для разраба и одна из лучших вещей, которая придумана для версионирования ПО. Суть в трех цифрах: мажор.минор.патч. Мажор мы меняем при изменении публичного API, в котором не гарантируется обратная совместимость. Минор это новые фичи или фикс багов с гарантией обратной совместимости, другими словами не удаляем старое API, а только дополняем или депрекейтим. "С гарантией" это в идеале конечно, по факту часто бывает "мама анархия", поговорите с фронтендерами, они много вам расскажут про это. Ну а патч для багов и хотфикстов –мой любимый.
После трех цифр можно добавлять всякие обозначения, типо rc, beta, alpha т.д, чтобы дать понять что это какой-то промежуточный, нестабильный релиз.
Дата. Тут все довольно просто, вот когда решили ебануть релиз, берем дату релиза, это и есть версия. Довольно удобно и думать не нужно.
Хеш коммита или номер билда – я думаю тут тоже пояснять не нужно. Просто берем хэш коммита в git от которого решили сделать релиз и его используем как версию. Либо просто берем цифру, инкрементим на каждый билд, это и есть наша версия. Само по себе такое версионирование я нигде не видел, в основном применяется вместе с SemVer.
Гибрид. Это комбинация из нескольких типов. Например, как делает Jet Brains они берут SemVer, но за мажор принимают год выпуска. Часто также используется подход когда берут SemVer и добавляют хеш коммита с которого этот релиз поехал.
Когда что использовать? Расскажу в следующем посте.
Если начать гуглить типы версионирования, что википедия, что ChatGPT, что различные статьи обрушат на вас кучу разных типов. Однако при внимательном прочтении, можно увидеть, что по факту все эти типы сводятся к четырем:
👉 SemVer
👉 С использованием даты
👉 Хеш коммита/номер билда
👉 Гибрид всех что выше
Какой бы подход вы не нашли, он так или иначе сведется к одному из тех, что я описал выше. Теперь давайте подробнее про каждый.
SemVer – базовая база для разраба и одна из лучших вещей, которая придумана для версионирования ПО. Суть в трех цифрах: мажор.минор.патч. Мажор мы меняем при изменении публичного API, в котором не гарантируется обратная совместимость. Минор это новые фичи или фикс багов с гарантией обратной совместимости, другими словами не удаляем старое API, а только дополняем или депрекейтим. "С гарантией" это в идеале конечно, по факту часто бывает "мама анархия", поговорите с фронтендерами, они много вам расскажут про это. Ну а патч для багов и хотфикстов –мой любимый.
После трех цифр можно добавлять всякие обозначения, типо rc, beta, alpha т.д, чтобы дать понять что это какой-то промежуточный, нестабильный релиз.
Дата. Тут все довольно просто, вот когда решили ебануть релиз, берем дату релиза, это и есть версия. Довольно удобно и думать не нужно.
Хеш коммита или номер билда – я думаю тут тоже пояснять не нужно. Просто берем хэш коммита в git от которого решили сделать релиз и его используем как версию. Либо просто берем цифру, инкрементим на каждый билд, это и есть наша версия. Само по себе такое версионирование я нигде не видел, в основном применяется вместе с SemVer.
Гибрид. Это комбинация из нескольких типов. Например, как делает Jet Brains они берут SemVer, но за мажор принимают год выпуска. Часто также используется подход когда берут SemVer и добавляют хеш коммита с которого этот релиз поехал.
Когда что использовать? Расскажу в следующем посте.
Начало туть. Когда какое версионирование использовать?
🗄️ SemVer позволяет грамотно спланировать обновления библиотек в проекте. Ты можешь прикинуть объем работы, исходя из того, какая цифра в версии поменялась. Представим, что мы живем в идеальном мире и у нас не ломают обратную совместимость на минорных релизах.
В таком случае, ты всегда можешь не парится при обновлении минорной версии, вероятность что все сломается, довольно низкая. Когда же ты видишь, что поменялась мажорная версия, ты уже обновляешься аккуратно, возможно будешь делать регресс и вот это все. Именно поэтому SemVer идеально подходит для библиотек, CLI программ и всего что связанно именно с разработкой.
📱Теперь идем в пользовательское ПО. Как думаете пользователей волнует, какая у них версия приложения? В подавляющем большинстве случаев им похуй + похуй. Главная проблема использования SemVer в таком ПО это вопрос, в какой момент увеличивать мажорную версию? Точного ответа на этот вопрос никто не знает, и все обновляются по наитию.
В либах тоже, частенько обновляют мажор по наитию, но это все равно сигнал, что вот это обновление может все сломать. Однако в пользовательском ПО мы не ломаем обратную совместимость. Да и каким образом, типо старые конфиги перестаем поддерживать? Пользователи нам пизды дадут!
Я сейчас работаю на проекте, в котором для версий используется SemVer. Пару лет назад у нас поменялась мажорная версия с 1.x.x на 2.x.x. Причиной послужило внедрение темной темы. С одной стороны изменения и правда обширные и затронули все приложение. С другой, это все тоже приложение, и в целом темную тему можно расценивать как обычную фичу.
🧬 Вся суть версий для пользовательского ПО в двух составляющих: понять в какой версии у нас появился баг и маркетинг. Баги можно отслеживать и без SemVer главное понять какая версия идет за какой. С точки зрения маркетинга тоже все довольно неоднозначно. Когда мы говорим про приложения в рекламных акциях никак не указываются точные версии, всем похеру.
Поэтому я считаю, что для пользовательского ПО лучше всего подходит комбинация Дата + SemVer, то что использует JetBrains. Мы четко можем отличать версии друг от друга, и у нас навсегда уходят споры о том, а достаточно ли это большое изменение чтобы мажор инкрементить?
Вывод:
👉 SemVer – для всего прогерского, библиотеки, CLI, какой-то сервисный софт
👉 Дата + SemVer – для пользовательского ПО
🗄️ SemVer позволяет грамотно спланировать обновления библиотек в проекте. Ты можешь прикинуть объем работы, исходя из того, какая цифра в версии поменялась. Представим, что мы живем в идеальном мире и у нас не ломают обратную совместимость на минорных релизах.
В таком случае, ты всегда можешь не парится при обновлении минорной версии, вероятность что все сломается, довольно низкая. Когда же ты видишь, что поменялась мажорная версия, ты уже обновляешься аккуратно, возможно будешь делать регресс и вот это все. Именно поэтому SemVer идеально подходит для библиотек, CLI программ и всего что связанно именно с разработкой.
📱Теперь идем в пользовательское ПО. Как думаете пользователей волнует, какая у них версия приложения? В подавляющем большинстве случаев им похуй + похуй. Главная проблема использования SemVer в таком ПО это вопрос, в какой момент увеличивать мажорную версию? Точного ответа на этот вопрос никто не знает, и все обновляются по наитию.
В либах тоже, частенько обновляют мажор по наитию, но это все равно сигнал, что вот это обновление может все сломать. Однако в пользовательском ПО мы не ломаем обратную совместимость. Да и каким образом, типо старые конфиги перестаем поддерживать? Пользователи нам пизды дадут!
Я сейчас работаю на проекте, в котором для версий используется SemVer. Пару лет назад у нас поменялась мажорная версия с 1.x.x на 2.x.x. Причиной послужило внедрение темной темы. С одной стороны изменения и правда обширные и затронули все приложение. С другой, это все тоже приложение, и в целом темную тему можно расценивать как обычную фичу.
🧬 Вся суть версий для пользовательского ПО в двух составляющих: понять в какой версии у нас появился баг и маркетинг. Баги можно отслеживать и без SemVer главное понять какая версия идет за какой. С точки зрения маркетинга тоже все довольно неоднозначно. Когда мы говорим про приложения в рекламных акциях никак не указываются точные версии, всем похеру.
Поэтому я считаю, что для пользовательского ПО лучше всего подходит комбинация Дата + SemVer, то что использует JetBrains. Мы четко можем отличать версии друг от друга, и у нас навсегда уходят споры о том, а достаточно ли это большое изменение чтобы мажор инкрементить?
Вывод:
👉 SemVer – для всего прогерского, библиотеки, CLI, какой-то сервисный софт
👉 Дата + SemVer – для пользовательского ПО
Все же помнят фильм Астерикс и Обеликс 2? Готов поспорить, у многих этот фильм ассоциируется с беззаботным детством. В фильме страшно забавный момент, когда архитектору поручили построить дом. Когда же проверяющий пришел проверять работу, он был крайне удивлен тому, что под потолком есть дверь. Он спрашивает архитектора: "нахера дверь под потолком?”, на что архитектор отвечает: "так я же смотрю в будущее, вдруг вам понадобится второй этаж, а дверь уже есть".
Гомерически смешной момент, который крайне точно описывает всю современую разработку. Мы хихикаем над решениеями аритектора, однако когда это происходит в ПО и мы обкладываем ненужными интерфейсами с одной сука реализацией практически все, так это у нас лучшие практики. И проблема в том, что по умолчанию даже LLM их везде пихает, если забыть попросить ее этого не делать. Тяжело….
Гомерически смешной момент, который крайне точно описывает всю современую разработку. Мы хихикаем над решениеями аритектора, однако когда это происходит в ПО и мы обкладываем ненужными интерфейсами с одной сука реализацией практически все, так это у нас лучшие практики. И проблема в том, что по умолчанию даже LLM их везде пихает, если забыть попросить ее этого не делать. Тяжело….
Знаете есть такой тип людей, бесящие фактчекеры. Например, ты говоришь у всех людей одна голова, но они придут к тебе и расскажут про 10 случаев сиамских близнецов, у которых 2 головы. То что это отклонение от нормы и большая редкость их не заботит.
Это же случилось и в прошлым постом. Мой любимый подписчик (без шуток), рассказал свою историю, в стиле "я Д’Артаньян, все пидорасы" о том как он покрывал все интерфейсами, потом пришел заказчик, который попросил перевести все на kmp. Меньше чем за неделю он в соло зарелизил iOS версию с минимальными изменениями. Чем не история успеха?
Проблема тут такая же как и с фактчекерами, если взять 100 проектов, в 10 из них будет такая ситуация, когда покрывали все интерфейсами и оно реально пригодилось. Остальные 90 проектов в которых плодили интерфейсы просто по кайфу, мы конечно замечать не будем.
Короче я решил декомпозировать этот комментарий на составные части и высказать что я думаю:
"<6 слоев градл модулей" – не особо понимаю что значит меньше 6 слоев, наверное имеется ввиду дофига слоев? Если ты один разработчик на проекте то, да крутят у виска заслужено, это попахивает каргокультом. Не представляю зачем больше 3 слоев может быть даже на большом проекте? Однако сам таким страдал когда, был зеленым, поэтому 100% понимания.
"на работе всем похер экзоплееры прямо во вьюмодели залетают и view прокидываются на domain слой" – это не про интерфейсы, это уже про инженерную культуру, а точнее про ее отсутствие. Когда я говорю, что не нужно плодить лишние интерфейсы, я не имею в виду, что можно смешивать UI и Domain. Domain может быть только на классах, но при этом максимально изолирован от платформы, я проверял. И я говорю именно про лишние интерфейсы, а не вообще интерфейсы в целом, не нужно перегибать.
"На работе же уже потратили около 600к$ и больше 2к человеко-часов на переписывание, чтобы хотя бы домейн уровень вынести в кмп" – я возможно ничего не понимаю, но если уже такие бюджеты есть на проекте, и все это время была только одна платформа? Реально, KMP выглядит проще чем написать iOS приложение, которое будет работать в разы стабильнее и плавнее?
Кажется тут проблема не с интерфейсами, а с менеджментом. Ну и перевести небольшой и новый проект на KMP очевидно в разы проще, чем большой с тонной легаси. Тут интерфейсы это вообще капля в море.
"горячая сборка моего проекта около 5 секунд на обеих платформах. Холодная конфигурация около 20 секунд" – не особо понимаю как связаны интерфейсы и сборка. Если проект небольшой, а он судя по всему небольшой если ты на нем один, то разумеется на нем будет быстрая сборка.
У меня тоже есть pet проект, без интерфейсов, в одном модуле, и там сборка кажется секунда, тоже пойдет как аргумент? Все кажется подвержены мифу, что многомодульность вам ускорит сборку, идите посмотрите доклады Зинатулина, он лучше меня расскажет где вы заблуждаетесь.
Дальше идет большой блок про быстрое переписывание платформенных интерфейсов и заед на KMP без проблем. Я сделаю про это отдельный пост, чтобы не доводить этот до уровня статьи.
Это же случилось и в прошлым постом. Мой любимый подписчик (без шуток), рассказал свою историю, в стиле "я Д’Артаньян, все пидорасы" о том как он покрывал все интерфейсами, потом пришел заказчик, который попросил перевести все на kmp. Меньше чем за неделю он в соло зарелизил iOS версию с минимальными изменениями. Чем не история успеха?
Проблема тут такая же как и с фактчекерами, если взять 100 проектов, в 10 из них будет такая ситуация, когда покрывали все интерфейсами и оно реально пригодилось. Остальные 90 проектов в которых плодили интерфейсы просто по кайфу, мы конечно замечать не будем.
Короче я решил декомпозировать этот комментарий на составные части и высказать что я думаю:
"<6 слоев градл модулей" – не особо понимаю что значит меньше 6 слоев, наверное имеется ввиду дофига слоев? Если ты один разработчик на проекте то, да крутят у виска заслужено, это попахивает каргокультом. Не представляю зачем больше 3 слоев может быть даже на большом проекте? Однако сам таким страдал когда, был зеленым, поэтому 100% понимания.
"на работе всем похер экзоплееры прямо во вьюмодели залетают и view прокидываются на domain слой" – это не про интерфейсы, это уже про инженерную культуру, а точнее про ее отсутствие. Когда я говорю, что не нужно плодить лишние интерфейсы, я не имею в виду, что можно смешивать UI и Domain. Domain может быть только на классах, но при этом максимально изолирован от платформы, я проверял. И я говорю именно про лишние интерфейсы, а не вообще интерфейсы в целом, не нужно перегибать.
"На работе же уже потратили около 600к$ и больше 2к человеко-часов на переписывание, чтобы хотя бы домейн уровень вынести в кмп" – я возможно ничего не понимаю, но если уже такие бюджеты есть на проекте, и все это время была только одна платформа? Реально, KMP выглядит проще чем написать iOS приложение, которое будет работать в разы стабильнее и плавнее?
Кажется тут проблема не с интерфейсами, а с менеджментом. Ну и перевести небольшой и новый проект на KMP очевидно в разы проще, чем большой с тонной легаси. Тут интерфейсы это вообще капля в море.
"горячая сборка моего проекта около 5 секунд на обеих платформах. Холодная конфигурация около 20 секунд" – не особо понимаю как связаны интерфейсы и сборка. Если проект небольшой, а он судя по всему небольшой если ты на нем один, то разумеется на нем будет быстрая сборка.
У меня тоже есть pet проект, без интерфейсов, в одном модуле, и там сборка кажется секунда, тоже пойдет как аргумент? Все кажется подвержены мифу, что многомодульность вам ускорит сборку, идите посмотрите доклады Зинатулина, он лучше меня расскажет где вы заблуждаетесь.
Дальше идет большой блок про быстрое переписывание платформенных интерфейсов и заед на KMP без проблем. Я сделаю про это отдельный пост, чтобы не доводить этот до уровня статьи.
Одна из вещей, которая мне нравится на проекте, где я сейчас работаю это минимальная бюрократия. У разработчиков часто есть опасение, что когда идешь работать в банк, то ты утонешь в согласованиях всего и вся. Это лишь на малую часть правда, по большей части все происходит довольно быстро. Ноооо...
Стоило мне один раз, стать виновником инцидента на проде. Мой скрипт на CI отработал не так, как нужно. И я попал в какой-то кафкианский бюрократический ад. Две задачи, страница на wiki, инцидент в специальной системе которую я впервые вижу. Отчасти я понимаю зачем это нужно, но кто мне запретить поныть?
Стоило мне один раз, стать виновником инцидента на проде. Мой скрипт на CI отработал не так, как нужно. И я попал в какой-то кафкианский бюрократический ад. Две задачи, страница на wiki, инцидент в специальной системе которую я впервые вижу. Отчасти я понимаю зачем это нужно, но кто мне запретить поныть?
Есть одна задача, которая дважды попадалась мне на собесах. Как это часто бывает, первый раз я эту задачу полностью завалил, а второй раз написал какую-то кривую хрень. И это при том, что всё решение сводилось к знанию всего одной конкретной структуры данных.
Поэтому сегодня поделюсь с вами этой структурой на примере той самой задачи, вдруг пригодится.
Задача такая: вот у нас есть трекер посещений сайта, с двумя методами:
Метод visit дёргается когда человек заходит на сайт. Метод count должен возвращать количество посещений за последние 5 минут. Для упрощения не нужно париться насчёт многопоточности и считать уникальных посетителей только реализовать эти два метода, причём максимально эффективно. Задача сложнее, чем кажется на первый взгляд, можете попробовать что-то накидать перед тем, как читать дальше.
Не буду долго мучить, все делается через Кольцевой буфер. По сути это такой массив, который работает как круг. Когда буфер заполняется и вы добавляете новый элемент, самый старый автоматически удаляется. Почему он называется кольцевым? Потому что при итерации, дойдя до последнего элемента, мы снова переходим к первому, замыкая круг.
В Kotlin, разумеется, нет такой структуры, однако её очень просто сделать при помощи
Поэтому задаём правило, что нужно удалять, если значение ключа отличается от текущего времени более чем на 300 секунд:
Всё, что осталось – это реализовать наши методы:
Может возникнуть вопрос, нафига нам нужно пробегаться по коллекции в методе count? Это для кейса, когда долго никто не заходил на сайт, в таком случае у нас не будут вытесняться старые элементы.
Второй вопрос, который может возникнуть: мы же итерируемся по целой коллекции это же не оптимально? У нас гарантированно не более 300 элементов, пробежаться по ним также быстро как ты в свой первый раз.
Поэтому сегодня поделюсь с вами этой структурой на примере той самой задачи, вдруг пригодится.
Задача такая: вот у нас есть трекер посещений сайта, с двумя методами:
class WebsiteTracker{
fun visit() {}
fun count(): Int {}
}
Метод visit дёргается когда человек заходит на сайт. Метод count должен возвращать количество посещений за последние 5 минут. Для упрощения не нужно париться насчёт многопоточности и считать уникальных посетителей только реализовать эти два метода, причём максимально эффективно. Задача сложнее, чем кажется на первый взгляд, можете попробовать что-то накидать перед тем, как читать дальше.
Не буду долго мучить, все делается через Кольцевой буфер. По сути это такой массив, который работает как круг. Когда буфер заполняется и вы добавляете новый элемент, самый старый автоматически удаляется. Почему он называется кольцевым? Потому что при итерации, дойдя до последнего элемента, мы снова переходим к первому, замыкая круг.
В Kotlin, разумеется, нет такой структуры, однако её очень просто сделать при помощи
LinkedHashMap
. Фишка этой мапы не только в том, что она сохраняет порядок добавления, а ещё и в том, что у неё есть волшебный метод removeEldestEntry
. Переопределив его, можно задать правило, по которому старые элементы будут автоматически вычищаться при добавлении новых. Поэтому задаём правило, что нужно удалять, если значение ключа отличается от текущего времени более чем на 300 секунд:
val visits = object : LinkedHashMap<Long, Int>() {
override fun removeEldestEntry(
eldest: MutableMap.MutableEntry<Long, Int>
): Boolean = eldest.key < currentTimeMillis() - 300*1000
}
Всё, что осталось – это реализовать наши методы:
fun visit() {
val timeSlot = (currentTimeMillis() / 1000) * 1000
visits[timeSlot] = (visits[timeSlot] ?: 0) + 1
}
fun count(): Int {
val cutoff = сurrentTimeMillis() - timeWindow
visits.entries.removeIf { it.key < cutoff }
return visits.values.sum()
}
Может возникнуть вопрос, нафига нам нужно пробегаться по коллекции в методе count? Это для кейса, когда долго никто не заходил на сайт, в таком случае у нас не будут вытесняться старые элементы.
Второй вопрос, который может возникнуть: мы же итерируемся по целой коллекции это же не оптимально? У нас гарантированно не более 300 элементов, пробежаться по ним также быстро как ты в свой первый раз.
Forwarded from Стой под стрелой (Nikita Prokopov)
Хотите прикол? Если в канал ничего не писать, число подписчиков потихоньку растет. А если писать, то кто-то постоянно отписывается. Так что писать невыгодно. Думайте
История моего отношения к LLM проходила несколько этапов. В начале я со всеми хихикал над тем, какой код выдает GPT 3.5. Да, можно сгенерить змейку, даже код запускается, но на реальных задачах много галлюцинаций, и получить что-то вменяемое крайне сложно. Затем пришла GPT 4.0, которая уже была значительно круче. Однако с ней также было много проблем — в подавляющем большинстве случаев было проще самому накидать код.
Затем внутри OpenAI начались споры, часть команды ушла и основала отдельную компанию, которую назвали Anthropic. Свою модель они назвали Claude. Уже первая версия модели по комментариям многих разрабов, писала код значительно лучше чем основной конкурент. Claude 3.5 уже делала дельные замечания по коду, круто писала тесты, но в сложных вещах могла затупить. Последний раз я активно пытался её использовать примерно осенью.
На прошлой неделе я всё-таки решил попробовать платную версию Claude 3.7. И это прям впечатляет — за последние полгода модели значительно продвинулись. После пары дней использования я даже начал нервничать. Не в том плане, что нас всех заменят моделями и мы будем сидеть без работы, а в том, что умение работать с такими моделями даёт значительное конкурентное преимущество. Я не скажу, что она меня ускорила в разы, но по ощущениям процентов на 50 точно
Фишка даже не в самой модели, а в фиче проектов. Ты можешь с помощью той же Claude нагенерить гайдлайны, как ты хочешь, чтобы она писала код, примеры кода для той или иной ситуации, какие-то архитектурные паттерны. После этого модель для каждого запроса будет учитывать эти гайдлайны и примеры кода. Она реально начинает писать код, как если бы я его писал.
Разумеется, у неё по-прежнему есть ограничения. UI по-прежнему ни одна модель даже близко не может нормально организовать. Помимо этого, если ей дать слишком большую задачу, не разбитую на шаги, она начинает паниковать и генерить бред. Они всё ещё требуют надзора и ревью с вашей стороны — глупо предполагать, что модель за вас сделает всю работу.
Всё я это к чему. У меня в окружении пока все ещё очень скептически относятся к моделькам, особенно сеньоры. Многие утверждают, что у них уникальная ситуация, что в их кейсе они не подходят. Вероятнее всего, вполне себе подходит, просто до конца не разобрались.
Представьте ваше недоумение, когда вы даёте человеку автомобиль, а потом слышите от него комментарий: "Ну я чет на газ нажал, и въебался в стену, с телегой как-то попроще было, у меня все таки уникальный маршрут".
Затем внутри OpenAI начались споры, часть команды ушла и основала отдельную компанию, которую назвали Anthropic. Свою модель они назвали Claude. Уже первая версия модели по комментариям многих разрабов, писала код значительно лучше чем основной конкурент. Claude 3.5 уже делала дельные замечания по коду, круто писала тесты, но в сложных вещах могла затупить. Последний раз я активно пытался её использовать примерно осенью.
На прошлой неделе я всё-таки решил попробовать платную версию Claude 3.7. И это прям впечатляет — за последние полгода модели значительно продвинулись. После пары дней использования я даже начал нервничать. Не в том плане, что нас всех заменят моделями и мы будем сидеть без работы, а в том, что умение работать с такими моделями даёт значительное конкурентное преимущество. Я не скажу, что она меня ускорила в разы, но по ощущениям процентов на 50 точно
Фишка даже не в самой модели, а в фиче проектов. Ты можешь с помощью той же Claude нагенерить гайдлайны, как ты хочешь, чтобы она писала код, примеры кода для той или иной ситуации, какие-то архитектурные паттерны. После этого модель для каждого запроса будет учитывать эти гайдлайны и примеры кода. Она реально начинает писать код, как если бы я его писал.
Разумеется, у неё по-прежнему есть ограничения. UI по-прежнему ни одна модель даже близко не может нормально организовать. Помимо этого, если ей дать слишком большую задачу, не разбитую на шаги, она начинает паниковать и генерить бред. Они всё ещё требуют надзора и ревью с вашей стороны — глупо предполагать, что модель за вас сделает всю работу.
Всё я это к чему. У меня в окружении пока все ещё очень скептически относятся к моделькам, особенно сеньоры. Многие утверждают, что у них уникальная ситуация, что в их кейсе они не подходят. Вероятнее всего, вполне себе подходит, просто до конца не разобрались.
Представьте ваше недоумение, когда вы даёте человеку автомобиль, а потом слышите от него комментарий: "Ну я чет на газ нажал, и въебался в стену, с телегой как-то попроще было, у меня все таки уникальный маршрут".
🗓 Итак, топ постов за февраль. Решил попробовать делать такие дайджесты, посмотрим как пойдет:
👉 Структурная типизация
👉 Мой проеб с внедрением AI
👉 Фишки с типами, о которых не знают джависты
👉 Джуны тупеют
👉 Версионирование
👉 Что такое кольцевой буфер?
👉 Структурная типизация
👉 Мой проеб с внедрением AI
👉 Фишки с типами, о которых не знают джависты
👉 Джуны тупеют
👉 Версионирование
👉 Что такое кольцевой буфер?
Закончим срач, про интерфейсы. Из всех аргументов "за", был только один связанный с практикой. Комент про то, как интерфейсы помогли разработчику перевести свой проект на KMP в краткие сроки.
Однако я считаю, что этот аргумент совершенно не относится к пользе интерфесов. Интерфейсы не дают существенных преимуществ при миграции на KMP, и объём работы остаётся примерно таким же. Поразгоняем эту мысль.
Рассмотрим типичное Android-приложение. Представим, что оно свежее и сразу разрабатывалось на Compose и корутинах. Уже только это решает значительную часть проблем при переходе на KMP. Cтандартный технологический стек для мобилки:
👉 Retrofit + OkHttp для сетевого взаимодействия
👉 Room для работы с БД (если оно вообще нужно)
👉 MVI для презентационного слоя (они уже все на базе корутин)
👉 Timber для логирования
👉 Dagger для внедрения зависимостей
Приложение мы пишем без лишних интерфейсов: Interactor, Presenter и даже Repository всегда просто классы. При условии что реализация только одна, если несколько, то разумеется делаем интерфейс.
Для переезда на KMP нам нужно два основных шага: перевести стек для сети и БД на мультиплатформу и вынести за интерфейсы платформенные компоненты (даты, UUID и подобное). Последнее заранее никто не выносит, если только не закладывали перенос заранее, поэтому в любом случае придется делать.
📞 Сеть. Retrofit изначально требует использования интерфейсов, поэтому эта часть уже готова. Мы просто то меняем зависимость на Ktorfit и заменяем OkHttp на Ktor. Возможно потребуется переписать интеракторы, но вы бы в любом случае их переписывали.
💽 БД. Если ее на проекте нет, то вы уже сделали 90% работы. Если же есть, то Room последней версии уже мультиплатформенная. Если же Room не нравится, то придется мигрировать на SQLDelight. Как бы вы не закрывались интерфейсами, у вас все равно будет дофига работы по мигрированию сущностей на другую либу. Все равно менять код Repository, поэтому профит интерфейса не ясен.
💉 DI. Если у вас был Dagger или Hilt или Anvil – вы в заднице. Теперь вам нужно или делать кастомный DI или переходить на Koin. Кстати если шарите, в комментариях подскажите, какие еще DI контейнеры мультиплатформенные?
📜 Логер. Никто никогда не покрывает интерфейсами логер, поэтому мы через замену текста меняем Timber на Napier.
Еще есть картинки, но кажется уже все давно перешли на Coil. Еще можно вспомнить префы, но на новых проектах сразу используют DataStore, который давно мультиплатформа, вроде ничего не забыл.
Итог: KMP никак не оправдывает избыточное использование интерфейсов, поскольку они здесь второстепенны. Успешный перенос проекта на KMP в первую очередь зависит от актуальности технологического стека. Даже если вы обложитесь интерфейсами по самые уши, но на проекте еще есть RxJava или View, то вы никуда быстро не передите.
Однако я считаю, что этот аргумент совершенно не относится к пользе интерфесов. Интерфейсы не дают существенных преимуществ при миграции на KMP, и объём работы остаётся примерно таким же. Поразгоняем эту мысль.
Рассмотрим типичное Android-приложение. Представим, что оно свежее и сразу разрабатывалось на Compose и корутинах. Уже только это решает значительную часть проблем при переходе на KMP. Cтандартный технологический стек для мобилки:
👉 Retrofit + OkHttp для сетевого взаимодействия
👉 Room для работы с БД (если оно вообще нужно)
👉 MVI для презентационного слоя (они уже все на базе корутин)
👉 Timber для логирования
👉 Dagger для внедрения зависимостей
Приложение мы пишем без лишних интерфейсов: Interactor, Presenter и даже Repository всегда просто классы. При условии что реализация только одна, если несколько, то разумеется делаем интерфейс.
Для переезда на KMP нам нужно два основных шага: перевести стек для сети и БД на мультиплатформу и вынести за интерфейсы платформенные компоненты (даты, UUID и подобное). Последнее заранее никто не выносит, если только не закладывали перенос заранее, поэтому в любом случае придется делать.
📞 Сеть. Retrofit изначально требует использования интерфейсов, поэтому эта часть уже готова. Мы просто то меняем зависимость на Ktorfit и заменяем OkHttp на Ktor. Возможно потребуется переписать интеракторы, но вы бы в любом случае их переписывали.
💽 БД. Если ее на проекте нет, то вы уже сделали 90% работы. Если же есть, то Room последней версии уже мультиплатформенная. Если же Room не нравится, то придется мигрировать на SQLDelight. Как бы вы не закрывались интерфейсами, у вас все равно будет дофига работы по мигрированию сущностей на другую либу. Все равно менять код Repository, поэтому профит интерфейса не ясен.
💉 DI. Если у вас был Dagger или Hilt или Anvil – вы в заднице. Теперь вам нужно или делать кастомный DI или переходить на Koin. Кстати если шарите, в комментариях подскажите, какие еще DI контейнеры мультиплатформенные?
📜 Логер. Никто никогда не покрывает интерфейсами логер, поэтому мы через замену текста меняем Timber на Napier.
Еще есть картинки, но кажется уже все давно перешли на Coil. Еще можно вспомнить префы, но на новых проектах сразу используют DataStore, который давно мультиплатформа, вроде ничего не забыл.
Итог: KMP никак не оправдывает избыточное использование интерфейсов, поскольку они здесь второстепенны. Успешный перенос проекта на KMP в первую очередь зависит от актуальности технологического стека. Даже если вы обложитесь интерфейсами по самые уши, но на проекте еще есть RxJava или View, то вы никуда быстро не передите.
Я думаю, что когда стану уже совсем крутым разрабом, я напишу книгу, которую посвящу моей борьбе с лишними интерфейсами и моделями на каждый слой. Так ее и назову: "Моя борьба". Если переведут на немецкий, вообще бестселер будет
Все же смотрели сериал "Кремниевая Долина"? Для тех кто не смотрел: молодой инженер разрабатывал индекс звучаний — инструмент, позволяющий быстро и точно определять музыкальные заимствования. В процессе работы для ускорения сервиса он создал алгоритм сжатия, который неожиданно оказался на голову выше всех существующих решений. Весь сериал в итоге строится вокруг именно этого алгоритма, а первоначальный сервис, разумеется, никому нахер не был нужен.
Интересно то, что пока разработчик пытался решить одну из проблем своего проекта, он создал решение, которое превзошло оригинальную идею по ценности.
Примерно то же самое произошло с созданием Git. Линус Торвальдс просто хотел решить проблему влития изменений и создал Git под нужды своего проекта. Позже оказалось, что Git на порядок удобнее всех конкурентов. Работая над Linux, Торвальдс создал инструмент, который по распространённости не уступает оригинальному проекту.
Ещё одна похожая история связана с создателем языка Zig. Эндрю Келли, заебавшись от работы с C/C++, хотел создать такой же низкоуровневый язык, как C, но значительно более удобный. Эндрю стремился сделать язык, совместимый с C, и обеспечить кросс-компиляцию "из коробки". В итоге главной ценностью решения оказался не сам язык, а его система сборки.
Система сборки получилась настолько впечатляющей, что когда Uber решили перевести часть своих серверов на архитектуру arm64, они доработали билд-систему Zig и внедрили кросс-компиляцию без необходимости переписывать существующий код.
Эти истории показывают интересную закономерность в разработке: порой самые ценные изобретения возникают как побочный продукт решения других задач. Никогда не знаешь, какая из твоих "промежуточных" разработок может стать следующим стандартом индустрии.
Интересно то, что пока разработчик пытался решить одну из проблем своего проекта, он создал решение, которое превзошло оригинальную идею по ценности.
Примерно то же самое произошло с созданием Git. Линус Торвальдс просто хотел решить проблему влития изменений и создал Git под нужды своего проекта. Позже оказалось, что Git на порядок удобнее всех конкурентов. Работая над Linux, Торвальдс создал инструмент, который по распространённости не уступает оригинальному проекту.
Ещё одна похожая история связана с создателем языка Zig. Эндрю Келли, заебавшись от работы с C/C++, хотел создать такой же низкоуровневый язык, как C, но значительно более удобный. Эндрю стремился сделать язык, совместимый с C, и обеспечить кросс-компиляцию "из коробки". В итоге главной ценностью решения оказался не сам язык, а его система сборки.
Система сборки получилась настолько впечатляющей, что когда Uber решили перевести часть своих серверов на архитектуру arm64, они доработали билд-систему Zig и внедрили кросс-компиляцию без необходимости переписывать существующий код.
Эти истории показывают интересную закономерность в разработке: порой самые ценные изобретения возникают как побочный продукт решения других задач. Никогда не знаешь, какая из твоих "промежуточных" разработок может стать следующим стандартом индустрии.
Наткнулся на интересный манифест, который последнее время много где упоминается, и хотел бы его разобрать. Много кто писал, что бьет прямо в сердце, а мне кажется, его как будто писал подросток-максималист, за все хорошее против всего плохого. Погнали:
👉 Мы убиваем программы, когда добавляем новые фичи, не учитывая вносимой ими дополнительной сложности.
✏️ Согласен, любая новая фича увеличивает когнитивную нагрузку. Сейчас каждая корпорация пытается сделать суперапп, которая на эту нагрузку кладет хер. У меня неделя ушла, чтобы в инсте найти, где мои лайки.
👉 Мы убиваем программы сложными билд-системами.
✏️ Вообще не понял, как это связано. Пользователям вообще похую, ты хоть на make собирай. Сложные билд-системы убивают разве что желание разрабов жить в трезвом мире...
👉 Мы разрушаем программы абсурдной цепочкой зависимостей, делая всё раздутым и хрупким.
✏️ Я так понял, это пункт про проблему транзитивных зависимостей. Ну тут охота процитировать того деда из сериала: "Ну почему мы не можем жить без сторонних зависимостей? Да потому что блять не можем..."
👉 Мы разрушаем программы, говоря новым разработчикам: «Не изобретай велосипед!».
✏️ Потому что, изобретая велосипед, в большинстве случаев они внесут кучу уязвимостей, которые уже давно поправлены, или уничтожат перформанс. Нет ничего плохого в велосипедах, но только когда ты уже не начинающий.
👉 Мы разрушаем программы, не заботясь о обратной совместимости API.
✏️ Согласен, это отсутствие инженерной культуры.
👉 Мы разрушаем программы, заставляя переписывать то, что уже работает.
✏️ Кто их заставляет? Покажите мне хоть одного менеджера, который в восторге, когда к нему приносят задачу на рефакторинг?
👉 Мы разрушаем программы, бросаясь на каждый новый язык, парадигму и фреймворк.
✏️ По мне так нет ничего плохого в изучении новых языков и парадигм. Плохо, если ты на работе каждую новую систему или фичу делаешь на новом языке или с новым подходом.
👉 Мы разрушаем программы, постоянно недооценивая, насколько сложно работать с существующими сложными библиотеками по сравнению с созданием собственных решений.
✏️ В подавляющем большинстве случаев в разы проще использовать готовые библиотеки. Ты заранее не знаешь всего пиздеца, который уже пофиксили в готовом решении.
👉 Мы разрушаем программы, всегда считая, что стандарт де-факто для XYZ лучше, чем то, что мы можем создать, специально адаптированное под наш случай.
✏️ По моему опыту, это имеет смысл только когда проект уже приносит деньги и есть прям конкретная причина, почему нам нужно свое решение. Часто бывает, что свое решение делают просто из-за того, что есть толпа разрабов, которым нечего делать.
👉 Мы разрушаем программы, утверждая, что комментарии в коде бесполезны.
✏️ Большинство да, однако в сложных местах это может сэкономить часы. Лучше всего руководствоваться правилом наименьшего удивления.
👉 Мы разрушаем программы, ошибочно принимая разработку за чисто инженерную дисциплину.
✏️ Странный пункт. Что это тогда, наука, писательство?
👉 Мы убиваем программы, проектируя их таким образом, что вносить простые изменения становится сложно.
✏️ Потому что заранее ты не сможешь никак предсказать, в какую сторону будет двигаться твой проект. Однако есть архитектурные паттерны, которые могут сгладить этот фактор.
👉 Мы разрушаем программы, стремясь писать код как можно быстрее, а не как можно лучше.
✏️ Бизнесу как-то похуй, к сожалению, с этим приходится мириться.
👉 Мы разрушаем программы, и то, что останется, больше не будет приносить нам радость хакерства.
✏️ Подразумевается, что разработчикам нет никакой радости от работы. Ну совсем сомнительный пункт, не понимаю почему и как это прокомментировать…
👉 Мы убиваем программы, когда добавляем новые фичи, не учитывая вносимой ими дополнительной сложности.
✏️ Согласен, любая новая фича увеличивает когнитивную нагрузку. Сейчас каждая корпорация пытается сделать суперапп, которая на эту нагрузку кладет хер. У меня неделя ушла, чтобы в инсте найти, где мои лайки.
👉 Мы убиваем программы сложными билд-системами.
✏️ Вообще не понял, как это связано. Пользователям вообще похую, ты хоть на make собирай. Сложные билд-системы убивают разве что желание разрабов жить в трезвом мире...
👉 Мы разрушаем программы абсурдной цепочкой зависимостей, делая всё раздутым и хрупким.
✏️ Я так понял, это пункт про проблему транзитивных зависимостей. Ну тут охота процитировать того деда из сериала: "Ну почему мы не можем жить без сторонних зависимостей? Да потому что блять не можем..."
👉 Мы разрушаем программы, говоря новым разработчикам: «Не изобретай велосипед!».
✏️ Потому что, изобретая велосипед, в большинстве случаев они внесут кучу уязвимостей, которые уже давно поправлены, или уничтожат перформанс. Нет ничего плохого в велосипедах, но только когда ты уже не начинающий.
👉 Мы разрушаем программы, не заботясь о обратной совместимости API.
✏️ Согласен, это отсутствие инженерной культуры.
👉 Мы разрушаем программы, заставляя переписывать то, что уже работает.
✏️ Кто их заставляет? Покажите мне хоть одного менеджера, который в восторге, когда к нему приносят задачу на рефакторинг?
👉 Мы разрушаем программы, бросаясь на каждый новый язык, парадигму и фреймворк.
✏️ По мне так нет ничего плохого в изучении новых языков и парадигм. Плохо, если ты на работе каждую новую систему или фичу делаешь на новом языке или с новым подходом.
👉 Мы разрушаем программы, постоянно недооценивая, насколько сложно работать с существующими сложными библиотеками по сравнению с созданием собственных решений.
✏️ В подавляющем большинстве случаев в разы проще использовать готовые библиотеки. Ты заранее не знаешь всего пиздеца, который уже пофиксили в готовом решении.
👉 Мы разрушаем программы, всегда считая, что стандарт де-факто для XYZ лучше, чем то, что мы можем создать, специально адаптированное под наш случай.
✏️ По моему опыту, это имеет смысл только когда проект уже приносит деньги и есть прям конкретная причина, почему нам нужно свое решение. Часто бывает, что свое решение делают просто из-за того, что есть толпа разрабов, которым нечего делать.
👉 Мы разрушаем программы, утверждая, что комментарии в коде бесполезны.
✏️ Большинство да, однако в сложных местах это может сэкономить часы. Лучше всего руководствоваться правилом наименьшего удивления.
👉 Мы разрушаем программы, ошибочно принимая разработку за чисто инженерную дисциплину.
✏️ Странный пункт. Что это тогда, наука, писательство?
👉 Мы убиваем программы, проектируя их таким образом, что вносить простые изменения становится сложно.
✏️ Потому что заранее ты не сможешь никак предсказать, в какую сторону будет двигаться твой проект. Однако есть архитектурные паттерны, которые могут сгладить этот фактор.
👉 Мы разрушаем программы, стремясь писать код как можно быстрее, а не как можно лучше.
✏️ Бизнесу как-то похуй, к сожалению, с этим приходится мириться.
👉 Мы разрушаем программы, и то, что останется, больше не будет приносить нам радость хакерства.
✏️ Подразумевается, что разработчикам нет никакой радости от работы. Ну совсем сомнительный пункт, не понимаю почему и как это прокомментировать…
{1/3} TypeScript переписали на Go. Последнее время эта новость пронеслась на волне хайпа. Даже если вы рот топтали TypeScript и Go тут есть кое-что интересное.
Для начала, в чем суть новости. В любом языке есть две части — Транслятор и Рантайм. Трансляторы реализуются в виде компиляторов или интерпретаторов. Чем они отличаются, я надеюсь, вы знаете. Транслятор преобразует текст в команды нужного формата, которые затем исполняет Рантайм. Так вот, TypeScript транслятор теперь реализован на Go вместо JS.
Сделали это с одной целью – ускорить работу компилятора. Тестирование показало, что сборка крупных проектов, типа VS Code с кодовой базой около 1,5 млн строк, с новым компилятором на Go занимает 7,5 секунд против 77,8 секунд ранее. С одной стороны, десятикратное ускорение — весьма неплохо, с другой стороны, компиляция в минуту — это не чтобы big deal. В Gradle у нас столько одна только конфигурация занимает. Другое дело, если бы речь была о сокращении времени с нескольких часов до пары минут.
Самое интересное тут то, как они всё переписали. У команды было 4 претендента на переписывание: C#, Go, Rust и F#. Им хотелось попробовать все 4 варианта. Разумеется, взять и перелопатить такую огромную кодовую базу вручную, да еще и 4 раза, нереально.
Поэтому что они сделали? Они написали автопереводчик с JS языка на каждый из вариантов. Как я понял, из всех вариантов проще всего было перевести на Go. С C# и F# не получилось потому как это чисто объектные и функциональные языки. С Rust не пошло, так как модель памяти в нем запрещает цикличные ссылки, поэтому это имело бы смысл только если на нем бы разрабатывали изначально. С Go не было особых проблем, потому как он ближе всего стоял по подходам, поэтому в результате выбрали его.
Разумеется код там теперь вообще не лучшего качества. Это не удивительно ведь код на JS по определению не может быть качественным, а тут еще и переведен автоматикой на другой язык. При этом по метрикам, даже с таким качеством кода все стало быстрее. Потенциально они еще могут ускорить раз в 5 как говорят матерые Go разрабы.
Казалось бы, разрабам стоит только порадоваться — теперь компиляция кода будет "блейзингли фаст". Однако тут не учитывается психология разработчиков и философия языков программирования.
Для начала, в чем суть новости. В любом языке есть две части — Транслятор и Рантайм. Трансляторы реализуются в виде компиляторов или интерпретаторов. Чем они отличаются, я надеюсь, вы знаете. Транслятор преобразует текст в команды нужного формата, которые затем исполняет Рантайм. Так вот, TypeScript транслятор теперь реализован на Go вместо JS.
Сделали это с одной целью – ускорить работу компилятора. Тестирование показало, что сборка крупных проектов, типа VS Code с кодовой базой около 1,5 млн строк, с новым компилятором на Go занимает 7,5 секунд против 77,8 секунд ранее. С одной стороны, десятикратное ускорение — весьма неплохо, с другой стороны, компиляция в минуту — это не чтобы big deal. В Gradle у нас столько одна только конфигурация занимает. Другое дело, если бы речь была о сокращении времени с нескольких часов до пары минут.
Самое интересное тут то, как они всё переписали. У команды было 4 претендента на переписывание: C#, Go, Rust и F#. Им хотелось попробовать все 4 варианта. Разумеется, взять и перелопатить такую огромную кодовую базу вручную, да еще и 4 раза, нереально.
Поэтому что они сделали? Они написали автопереводчик с JS языка на каждый из вариантов. Как я понял, из всех вариантов проще всего было перевести на Go. С C# и F# не получилось потому как это чисто объектные и функциональные языки. С Rust не пошло, так как модель памяти в нем запрещает цикличные ссылки, поэтому это имело бы смысл только если на нем бы разрабатывали изначально. С Go не было особых проблем, потому как он ближе всего стоял по подходам, поэтому в результате выбрали его.
Разумеется код там теперь вообще не лучшего качества. Это не удивительно ведь код на JS по определению не может быть качественным, а тут еще и переведен автоматикой на другой язык. При этом по метрикам, даже с таким качеством кода все стало быстрее. Потенциально они еще могут ускорить раз в 5 как говорят матерые Go разрабы.
Казалось бы, разрабам стоит только порадоваться — теперь компиляция кода будет "блейзингли фаст". Однако тут не учитывается психология разработчиков и философия языков программирования.
{2/3} В мире языков есть такое понятие как самокомпилируемость (self-hosting). Компиляторы «взрослых» языков написаны на них же самих — C компилируется C-компилятором, Java использует компилятор на Java, даже Go изначально был на C, но потом его переписали на Go и т.д.
Почему это так важно?
1️⃣ Это показывает, что язык достаточно мощный и выразительный, чтобы реализовать функционал собственного компилятора. Если же компилятор переписывается на другой язык, многие могут воспринять это как увядание языка.
2️⃣ Компилятор должен быть независим от других технологий. Вот например: компилятор языка X написан на языке Y. Что случится, если язык Y перестанут поддерживать? У компилятора X будут большие проблемы. Ведь язык, на котором написан компилятор, не развивается, а значит никто не улучшает производительность, никто не добавляет новые фичи для ускорения. Так еще попробуй теперь найти разрабов на устаревающем языке.
3️⃣ Корпоративный аспект: TypeScript поддерживает Microsoft, а Go разрабатывает Google. Получается, что Microsoft в некотором смысле становится зависимым от решений Google. По идее было бы в разы меньше рисков если бы они выбрали С#. Что сказать, у команды TypeScript железные яйца.
С точки зрения рациональности, было бы логично, чтобы компилятор каждого языка был написан на каком-то низкоуровневом языке — это объективно сделает его быстрее. Однако психология восприятия технологий и доверие сообщества разработчиков часто становятся важнее чистой рациональности.
Почему это так важно?
1️⃣ Это показывает, что язык достаточно мощный и выразительный, чтобы реализовать функционал собственного компилятора. Если же компилятор переписывается на другой язык, многие могут воспринять это как увядание языка.
2️⃣ Компилятор должен быть независим от других технологий. Вот например: компилятор языка X написан на языке Y. Что случится, если язык Y перестанут поддерживать? У компилятора X будут большие проблемы. Ведь язык, на котором написан компилятор, не развивается, а значит никто не улучшает производительность, никто не добавляет новые фичи для ускорения. Так еще попробуй теперь найти разрабов на устаревающем языке.
3️⃣ Корпоративный аспект: TypeScript поддерживает Microsoft, а Go разрабатывает Google. Получается, что Microsoft в некотором смысле становится зависимым от решений Google. По идее было бы в разы меньше рисков если бы они выбрали С#. Что сказать, у команды TypeScript железные яйца.
С точки зрения рациональности, было бы логично, чтобы компилятор каждого языка был написан на каком-то низкоуровневом языке — это объективно сделает его быстрее. Однако психология восприятия технологий и доверие сообщества разработчиков часто становятся важнее чистой рациональности.
{3/3} Базу прошли, теперь поговорим про kotlin и gradle.
Важно разделять: есть компилятор языка и есть система сборки, в которой компиляция это лишь одна из фичей. Исходя из прошлых постов я думаю очевидно, что компилятор kotlin всегда будет на kotlin, и в целом это хорошо. JetBrains не настолько психи, чтобы создавать зависимость на другую компанию или технологический стек.
Теперь про систему сборки. В обязанности сборщика помимо компиляции кода входит:
👉 Управление зависимостями
👉 Тестирование
👉 Упаковка приложения
👉 Работа с кешами
👉 И еще дохера всего
Нужно ли для этих задач использовать тот же язык, на котором мы пишем код или на котором написан компилятор? Однозначно нельзя ответить на этот вопрос.
Говоря откровенно, когда я садился писать этот пост, мне казалось что я сейчас быстро найду кучу примеров сборщиков, написанных на других языках. Иии оказалось их почти нет, только один Bazel написан на java и может компилировать C++.
Если пройтись по всем 3-м пунктам которые я привел для компилятора вот что получим:
👉 Если мы не можем написать сборщик на том же языке, то нужно ли вообще такой язык использовать?
👉 Если же другой язык перестанут поддерживать, нам придется переписывать систему сборки на другой, а это затраты
👉 Корпоративный аспект в система сборки кстати не работает, почти всегда сборщики делают сторонние компании, тут конечно минус
Давайте на примере. Допустим у нас есть язык Java и система сборки для нее например на C++ . Кого ты хочешь в команду для разработки сборщика? Человека который всю жизнь писал на Java, знает все ее тонкости и проблемы или человека на C++ который всю жизнь решал другие проблемы?
Как бы мне не хотелось, чтобы гребанный Gradle был написан на шустром Rust, в реальном мире это не сработает. Несмотря на то, что объективно все системы сборки должны быть написаны на низкоуровневых языках, все равно работает идеология. Если тулза для языка не написана на нем самом, на язык смотрят с опасением.
Решение команды TypeScript это интересный прецидент: практическая производительность становится важнее вот этих опасений. Если у них все выгорит, то кто знает, возможно мы когда-нибудь получим аналог Gradle на Rust.
Важно разделять: есть компилятор языка и есть система сборки, в которой компиляция это лишь одна из фичей. Исходя из прошлых постов я думаю очевидно, что компилятор kotlin всегда будет на kotlin, и в целом это хорошо. JetBrains не настолько психи, чтобы создавать зависимость на другую компанию или технологический стек.
Теперь про систему сборки. В обязанности сборщика помимо компиляции кода входит:
👉 Управление зависимостями
👉 Тестирование
👉 Упаковка приложения
👉 Работа с кешами
👉 И еще дохера всего
Нужно ли для этих задач использовать тот же язык, на котором мы пишем код или на котором написан компилятор? Однозначно нельзя ответить на этот вопрос.
Говоря откровенно, когда я садился писать этот пост, мне казалось что я сейчас быстро найду кучу примеров сборщиков, написанных на других языках. Иии оказалось их почти нет, только один Bazel написан на java и может компилировать C++.
Если пройтись по всем 3-м пунктам которые я привел для компилятора вот что получим:
👉 Если мы не можем написать сборщик на том же языке, то нужно ли вообще такой язык использовать?
👉 Если же другой язык перестанут поддерживать, нам придется переписывать систему сборки на другой, а это затраты
👉 Корпоративный аспект в система сборки кстати не работает, почти всегда сборщики делают сторонние компании, тут конечно минус
Давайте на примере. Допустим у нас есть язык Java и система сборки для нее например на C++ . Кого ты хочешь в команду для разработки сборщика? Человека который всю жизнь писал на Java, знает все ее тонкости и проблемы или человека на C++ который всю жизнь решал другие проблемы?
Как бы мне не хотелось, чтобы гребанный Gradle был написан на шустром Rust, в реальном мире это не сработает. Несмотря на то, что объективно все системы сборки должны быть написаны на низкоуровневых языках, все равно работает идеология. Если тулза для языка не написана на нем самом, на язык смотрят с опасением.
Решение команды TypeScript это интересный прецидент: практическая производительность становится важнее вот этих опасений. Если у них все выгорит, то кто знает, возможно мы когда-нибудь получим аналог Gradle на Rust.
Итак, снепшот тесты. Тесты когда мы отрисовываем интерфейс, сравниваем его по пикселям с эталоном и падаем, если картинка сильно отличается.
Я на практике много раз убеждался, что такие тесты нужны иссключительно командам, которые занимаются дизайн-системами. В таких командах действительно важно, чтобы при изменениях в кнопке всё не поехало. Если они выпустят новую версию UI Kit с таким багом, кривая кнопка появится во всех приложениях компании. В таких командах снепшот-тесты реально приносят пользу и экономят деньги.
Но я вижу тенденцию, что эти тесты пытаются затащить в само приложение. Ведь они так просто внедряются, всего пара аннотаций на Compose функцию. По мне, так в приложении в снепшотах столько же смысла, как в сюжете порно. Они очень хрупкие, дорогие в поддержке, и непонятно, какая от них реальная польза.
Что такого вы можете сделать в коде, от чего вас спасут эти тесты? Я просто не понимаю, как можно накосячить в верстке так, чтобы всё сломалось. Compose позволяет даже сложные экраны верстать в полмозга, левой пяткой. В верстке нет сложной логики, которую мы можем сломать изменениями. Или вы падинги рассчитываете по сложной формуле?
Я понимаю, можно накосячить со сложными анимациями, но анимации снепшотами не протестируешь – вообще никак не протестируешь.
Команды, которые внедрили себе снепшот тесты, постоянно страдают. Недавно показывают две идентичные картинки, в которых автоматика находит пару отличающихся пикселей. Причина в том, что эталон снимался на устройстве с одним разрешением, а на ферме эмуляторов сейчас другое. И теперь нужно перелопатить все тесты, чтобы это починить. Бессмысленная дрочь….
Может, у вас есть истории успеха, где такие тесты реально спасли релиз?
Я на практике много раз убеждался, что такие тесты нужны иссключительно командам, которые занимаются дизайн-системами. В таких командах действительно важно, чтобы при изменениях в кнопке всё не поехало. Если они выпустят новую версию UI Kit с таким багом, кривая кнопка появится во всех приложениях компании. В таких командах снепшот-тесты реально приносят пользу и экономят деньги.
Но я вижу тенденцию, что эти тесты пытаются затащить в само приложение. Ведь они так просто внедряются, всего пара аннотаций на Compose функцию. По мне, так в приложении в снепшотах столько же смысла, как в сюжете порно. Они очень хрупкие, дорогие в поддержке, и непонятно, какая от них реальная польза.
Что такого вы можете сделать в коде, от чего вас спасут эти тесты? Я просто не понимаю, как можно накосячить в верстке так, чтобы всё сломалось. Compose позволяет даже сложные экраны верстать в полмозга, левой пяткой. В верстке нет сложной логики, которую мы можем сломать изменениями. Или вы падинги рассчитываете по сложной формуле?
Я понимаю, можно накосячить со сложными анимациями, но анимации снепшотами не протестируешь – вообще никак не протестируешь.
Команды, которые внедрили себе снепшот тесты, постоянно страдают. Недавно показывают две идентичные картинки, в которых автоматика находит пару отличающихся пикселей. Причина в том, что эталон снимался на устройстве с одним разрешением, а на ферме эмуляторов сейчас другое. И теперь нужно перелопатить все тесты, чтобы это починить. Бессмысленная дрочь….
Может, у вас есть истории успеха, где такие тесты реально спасли релиз?