Есть одна задача, которая дважды попадалась мне на собесах. Как это часто бывает, первый раз я эту задачу полностью завалил, а второй раз написал какую-то кривую хрень. И это при том, что всё решение сводилось к знанию всего одной конкретной структуры данных.
Поэтому сегодня поделюсь с вами этой структурой на примере той самой задачи, вдруг пригодится.
Задача такая: вот у нас есть трекер посещений сайта, с двумя методами:
Метод 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 элементов, пробежаться по ним также быстро как ты в свой первый раз.
😁17❤5👍5🗿3🤔1
Forwarded from Стой под стрелой (Nikita Prokopov)
Хотите прикол? Если в канал ничего не писать, число подписчиков потихоньку растет. А если писать, то кто-то постоянно отписывается. Так что писать невыгодно. Думайте
😁55🗿5❤3👍3
История моего отношения к 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 по-прежнему ни одна модель даже близко не может нормально организовать. Помимо этого, если ей дать слишком большую задачу, не разбитую на шаги, она начинает паниковать и генерить бред. Они всё ещё требуют надзора и ревью с вашей стороны — глупо предполагать, что модель за вас сделает всю работу.
Всё я это к чему. У меня в окружении пока все ещё очень скептически относятся к моделькам, особенно сеньоры. Многие утверждают, что у них уникальная ситуация, что в их кейсе они не подходят. Вероятнее всего, вполне себе подходит, просто до конца не разобрались.
Представьте ваше недоумение, когда вы даёте человеку автомобиль, а потом слышите от него комментарий: "Ну я чет на газ нажал, и въебался в стену, с телегой как-то попроще было, у меня все таки уникальный маршрут".
👍24😁17❤5🥰2🤡1
🗓 Итак, топ постов за февраль. Решил попробовать делать такие дайджесты, посмотрим как пойдет:
👉 Структурная типизация
👉 Мой проеб с внедрением AI
👉 Фишки с типами, о которых не знают джависты
👉 Джуны тупеют
👉 Версионирование
👉 Что такое кольцевой буфер?
👉 Структурная типизация
👉 Мой проеб с внедрением AI
👉 Фишки с типами, о которых не знают джависты
👉 Джуны тупеют
👉 Версионирование
👉 Что такое кольцевой буфер?
1🔥16❤4🤔1
Закончим срач, про интерфейсы. Из всех аргументов "за", был только один связанный с практикой. Комент про то, как интерфейсы помогли разработчику перевести свой проект на 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, то вы никуда быстро не передите.
❤25👍5🗿3
Я думаю, что когда стану уже совсем крутым разрабом, я напишу книгу, которую посвящу моей борьбе с лишними интерфейсами и моделями на каждый слой. Так ее и назову: "Моя борьба". Если переведут на немецкий, вообще бестселер будет
😁67🔥9❤6
Все же смотрели сериал "Кремниевая Долина"? Для тех кто не смотрел: молодой инженер разрабатывал индекс звучаний — инструмент, позволяющий быстро и точно определять музыкальные заимствования. В процессе работы для ускорения сервиса он создал алгоритм сжатия, который неожиданно оказался на голову выше всех существующих решений. Весь сериал в итоге строится вокруг именно этого алгоритма, а первоначальный сервис, разумеется, никому нахер не был нужен.
Интересно то, что пока разработчик пытался решить одну из проблем своего проекта, он создал решение, которое превзошло оригинальную идею по ценности.
Примерно то же самое произошло с созданием Git. Линус Торвальдс просто хотел решить проблему влития изменений и создал Git под нужды своего проекта. Позже оказалось, что Git на порядок удобнее всех конкурентов. Работая над Linux, Торвальдс создал инструмент, который по распространённости не уступает оригинальному проекту.
Ещё одна похожая история связана с создателем языка Zig. Эндрю Келли, заебавшись от работы с C/C++, хотел создать такой же низкоуровневый язык, как C, но значительно более удобный. Эндрю стремился сделать язык, совместимый с C, и обеспечить кросс-компиляцию "из коробки". В итоге главной ценностью решения оказался не сам язык, а его система сборки.
Система сборки получилась настолько впечатляющей, что когда Uber решили перевести часть своих серверов на архитектуру arm64, они доработали билд-систему Zig и внедрили кросс-компиляцию без необходимости переписывать существующий код.
Эти истории показывают интересную закономерность в разработке: порой самые ценные изобретения возникают как побочный продукт решения других задач. Никогда не знаешь, какая из твоих "промежуточных" разработок может стать следующим стандартом индустрии.
Интересно то, что пока разработчик пытался решить одну из проблем своего проекта, он создал решение, которое превзошло оригинальную идею по ценности.
Примерно то же самое произошло с созданием Git. Линус Торвальдс просто хотел решить проблему влития изменений и создал Git под нужды своего проекта. Позже оказалось, что Git на порядок удобнее всех конкурентов. Работая над Linux, Торвальдс создал инструмент, который по распространённости не уступает оригинальному проекту.
Ещё одна похожая история связана с создателем языка Zig. Эндрю Келли, заебавшись от работы с C/C++, хотел создать такой же низкоуровневый язык, как C, но значительно более удобный. Эндрю стремился сделать язык, совместимый с C, и обеспечить кросс-компиляцию "из коробки". В итоге главной ценностью решения оказался не сам язык, а его система сборки.
Система сборки получилась настолько впечатляющей, что когда Uber решили перевести часть своих серверов на архитектуру arm64, они доработали билд-систему Zig и внедрили кросс-компиляцию без необходимости переписывать существующий код.
Эти истории показывают интересную закономерность в разработке: порой самые ценные изобретения возникают как побочный продукт решения других задач. Никогда не знаешь, какая из твоих "промежуточных" разработок может стать следующим стандартом индустрии.
👍49❤9🤔2
Наткнулся на интересный манифест, который последнее время много где упоминается, и хотел бы его разобрать. Много кто писал, что бьет прямо в сердце, а мне кажется, его как будто писал подросток-максималист, за все хорошее против всего плохого. Погнали:
👉 Мы убиваем программы, когда добавляем новые фичи, не учитывая вносимой ими дополнительной сложности.
✏️ Согласен, любая новая фича увеличивает когнитивную нагрузку. Сейчас каждая корпорация пытается сделать суперапп, которая на эту нагрузку кладет хер. У меня неделя ушла, чтобы в инсте найти, где мои лайки.
👉 Мы убиваем программы сложными билд-системами.
✏️ Вообще не понял, как это связано. Пользователям вообще похую, ты хоть на make собирай. Сложные билд-системы убивают разве что желание разрабов жить в трезвом мире...
👉 Мы разрушаем программы абсурдной цепочкой зависимостей, делая всё раздутым и хрупким.
✏️ Я так понял, это пункт про проблему транзитивных зависимостей. Ну тут охота процитировать того деда из сериала: "Ну почему мы не можем жить без сторонних зависимостей? Да потому что блять не можем..."
👉 Мы разрушаем программы, говоря новым разработчикам: «Не изобретай велосипед!».
✏️ Потому что, изобретая велосипед, в большинстве случаев они внесут кучу уязвимостей, которые уже давно поправлены, или уничтожат перформанс. Нет ничего плохого в велосипедах, но только когда ты уже не начинающий.
👉 Мы разрушаем программы, не заботясь о обратной совместимости API.
✏️ Согласен, это отсутствие инженерной культуры.
👉 Мы разрушаем программы, заставляя переписывать то, что уже работает.
✏️ Кто их заставляет? Покажите мне хоть одного менеджера, который в восторге, когда к нему приносят задачу на рефакторинг?
👉 Мы разрушаем программы, бросаясь на каждый новый язык, парадигму и фреймворк.
✏️ По мне так нет ничего плохого в изучении новых языков и парадигм. Плохо, если ты на работе каждую новую систему или фичу делаешь на новом языке или с новым подходом.
👉 Мы разрушаем программы, постоянно недооценивая, насколько сложно работать с существующими сложными библиотеками по сравнению с созданием собственных решений.
✏️ В подавляющем большинстве случаев в разы проще использовать готовые библиотеки. Ты заранее не знаешь всего пиздеца, который уже пофиксили в готовом решении.
👉 Мы разрушаем программы, всегда считая, что стандарт де-факто для XYZ лучше, чем то, что мы можем создать, специально адаптированное под наш случай.
✏️ По моему опыту, это имеет смысл только когда проект уже приносит деньги и есть прям конкретная причина, почему нам нужно свое решение. Часто бывает, что свое решение делают просто из-за того, что есть толпа разрабов, которым нечего делать.
👉 Мы разрушаем программы, утверждая, что комментарии в коде бесполезны.
✏️ Большинство да, однако в сложных местах это может сэкономить часы. Лучше всего руководствоваться правилом наименьшего удивления.
👉 Мы разрушаем программы, ошибочно принимая разработку за чисто инженерную дисциплину.
✏️ Странный пункт. Что это тогда, наука, писательство?
👉 Мы убиваем программы, проектируя их таким образом, что вносить простые изменения становится сложно.
✏️ Потому что заранее ты не сможешь никак предсказать, в какую сторону будет двигаться твой проект. Однако есть архитектурные паттерны, которые могут сгладить этот фактор.
👉 Мы разрушаем программы, стремясь писать код как можно быстрее, а не как можно лучше.
✏️ Бизнесу как-то похуй, к сожалению, с этим приходится мириться.
👉 Мы разрушаем программы, и то, что останется, больше не будет приносить нам радость хакерства.
✏️ Подразумевается, что разработчикам нет никакой радости от работы. Ну совсем сомнительный пункт, не понимаю почему и как это прокомментировать…
👉 Мы убиваем программы, когда добавляем новые фичи, не учитывая вносимой ими дополнительной сложности.
✏️ Согласен, любая новая фича увеличивает когнитивную нагрузку. Сейчас каждая корпорация пытается сделать суперапп, которая на эту нагрузку кладет хер. У меня неделя ушла, чтобы в инсте найти, где мои лайки.
👉 Мы убиваем программы сложными билд-системами.
✏️ Вообще не понял, как это связано. Пользователям вообще похую, ты хоть на make собирай. Сложные билд-системы убивают разве что желание разрабов жить в трезвом мире...
👉 Мы разрушаем программы абсурдной цепочкой зависимостей, делая всё раздутым и хрупким.
✏️ Я так понял, это пункт про проблему транзитивных зависимостей. Ну тут охота процитировать того деда из сериала: "Ну почему мы не можем жить без сторонних зависимостей? Да потому что блять не можем..."
👉 Мы разрушаем программы, говоря новым разработчикам: «Не изобретай велосипед!».
✏️ Потому что, изобретая велосипед, в большинстве случаев они внесут кучу уязвимостей, которые уже давно поправлены, или уничтожат перформанс. Нет ничего плохого в велосипедах, но только когда ты уже не начинающий.
👉 Мы разрушаем программы, не заботясь о обратной совместимости API.
✏️ Согласен, это отсутствие инженерной культуры.
👉 Мы разрушаем программы, заставляя переписывать то, что уже работает.
✏️ Кто их заставляет? Покажите мне хоть одного менеджера, который в восторге, когда к нему приносят задачу на рефакторинг?
👉 Мы разрушаем программы, бросаясь на каждый новый язык, парадигму и фреймворк.
✏️ По мне так нет ничего плохого в изучении новых языков и парадигм. Плохо, если ты на работе каждую новую систему или фичу делаешь на новом языке или с новым подходом.
👉 Мы разрушаем программы, постоянно недооценивая, насколько сложно работать с существующими сложными библиотеками по сравнению с созданием собственных решений.
✏️ В подавляющем большинстве случаев в разы проще использовать готовые библиотеки. Ты заранее не знаешь всего пиздеца, который уже пофиксили в готовом решении.
👉 Мы разрушаем программы, всегда считая, что стандарт де-факто для XYZ лучше, чем то, что мы можем создать, специально адаптированное под наш случай.
✏️ По моему опыту, это имеет смысл только когда проект уже приносит деньги и есть прям конкретная причина, почему нам нужно свое решение. Часто бывает, что свое решение делают просто из-за того, что есть толпа разрабов, которым нечего делать.
👉 Мы разрушаем программы, утверждая, что комментарии в коде бесполезны.
✏️ Большинство да, однако в сложных местах это может сэкономить часы. Лучше всего руководствоваться правилом наименьшего удивления.
👉 Мы разрушаем программы, ошибочно принимая разработку за чисто инженерную дисциплину.
✏️ Странный пункт. Что это тогда, наука, писательство?
👉 Мы убиваем программы, проектируя их таким образом, что вносить простые изменения становится сложно.
✏️ Потому что заранее ты не сможешь никак предсказать, в какую сторону будет двигаться твой проект. Однако есть архитектурные паттерны, которые могут сгладить этот фактор.
👉 Мы разрушаем программы, стремясь писать код как можно быстрее, а не как можно лучше.
✏️ Бизнесу как-то похуй, к сожалению, с этим приходится мириться.
👉 Мы разрушаем программы, и то, что останется, больше не будет приносить нам радость хакерства.
✏️ Подразумевается, что разработчикам нет никакой радости от работы. Ну совсем сомнительный пункт, не понимаю почему и как это прокомментировать…
👍21🤔4😁3❤2👎2
{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 разрабы.
Казалось бы, разрабам стоит только порадоваться — теперь компиляция кода будет "блейзингли фаст". Однако тут не учитывается психология разработчиков и философия языков программирования.
👍23🤔5
{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 железные яйца.
С точки зрения рациональности, было бы логично, чтобы компилятор каждого языка был написан на каком-то низкоуровневом языке — это объективно сделает его быстрее. Однако психология восприятия технологий и доверие сообщества разработчиков часто становятся важнее чистой рациональности.
👍26
{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.
👍21
Итак, снепшот тесты. Тесты когда мы отрисовываем интерфейс, сравниваем его по пикселям с эталоном и падаем, если картинка сильно отличается.
Я на практике много раз убеждался, что такие тесты нужны иссключительно командам, которые занимаются дизайн-системами. В таких командах действительно важно, чтобы при изменениях в кнопке всё не поехало. Если они выпустят новую версию UI Kit с таким багом, кривая кнопка появится во всех приложениях компании. В таких командах снепшот-тесты реально приносят пользу и экономят деньги.
Но я вижу тенденцию, что эти тесты пытаются затащить в само приложение. Ведь они так просто внедряются, всего пара аннотаций на Compose функцию. По мне, так в приложении в снепшотах столько же смысла, как в сюжете порно. Они очень хрупкие, дорогие в поддержке, и непонятно, какая от них реальная польза.
Что такого вы можете сделать в коде, от чего вас спасут эти тесты? Я просто не понимаю, как можно накосячить в верстке так, чтобы всё сломалось. Compose позволяет даже сложные экраны верстать в полмозга, левой пяткой. В верстке нет сложной логики, которую мы можем сломать изменениями. Или вы падинги рассчитываете по сложной формуле?
Я понимаю, можно накосячить со сложными анимациями, но анимации снепшотами не протестируешь – вообще никак не протестируешь.
Команды, которые внедрили себе снепшот тесты, постоянно страдают. Недавно показывают две идентичные картинки, в которых автоматика находит пару отличающихся пикселей. Причина в том, что эталон снимался на устройстве с одним разрешением, а на ферме эмуляторов сейчас другое. И теперь нужно перелопатить все тесты, чтобы это починить. Бессмысленная дрочь….
Может, у вас есть истории успеха, где такие тесты реально спасли релиз?
Я на практике много раз убеждался, что такие тесты нужны иссключительно командам, которые занимаются дизайн-системами. В таких командах действительно важно, чтобы при изменениях в кнопке всё не поехало. Если они выпустят новую версию UI Kit с таким багом, кривая кнопка появится во всех приложениях компании. В таких командах снепшот-тесты реально приносят пользу и экономят деньги.
Но я вижу тенденцию, что эти тесты пытаются затащить в само приложение. Ведь они так просто внедряются, всего пара аннотаций на Compose функцию. По мне, так в приложении в снепшотах столько же смысла, как в сюжете порно. Они очень хрупкие, дорогие в поддержке, и непонятно, какая от них реальная польза.
Что такого вы можете сделать в коде, от чего вас спасут эти тесты? Я просто не понимаю, как можно накосячить в верстке так, чтобы всё сломалось. Compose позволяет даже сложные экраны верстать в полмозга, левой пяткой. В верстке нет сложной логики, которую мы можем сломать изменениями. Или вы падинги рассчитываете по сложной формуле?
Я понимаю, можно накосячить со сложными анимациями, но анимации снепшотами не протестируешь – вообще никак не протестируешь.
Команды, которые внедрили себе снепшот тесты, постоянно страдают. Недавно показывают две идентичные картинки, в которых автоматика находит пару отличающихся пикселей. Причина в том, что эталон снимался на устройстве с одним разрешением, а на ферме эмуляторов сейчас другое. И теперь нужно перелопатить все тесты, чтобы это починить. Бессмысленная дрочь….
Может, у вас есть истории успеха, где такие тесты реально спасли релиз?
🔥21👎5
Давайте подытожим, что у нас со снепшотами. Соберем все плюсы и минусы:
✅ Плюсы:
👉 Нет эмуляторов – довольно жирный плюс
👉 Легко написать проверку вместо вызова кучи функций в espresso
👉 Позволяет отследить баги SDK
🚫Минусы:
👉 Нужно думать как хранить и обновлять скриншоты
👉 Могут начать флакать из-за пары пикселей, нужно подбирать параметры
👉 Не проверяют логику – это пункт, который вообще перекрывает все плюсы
Может у меня это старческое, но я все чаще пытаюсь понять профит в бабках от использования технологии. Тесты имеют смысл тогда, когда затраты на их написание и поддержку окупаются за счет багов, которые они ловят. Вот на конкретном примере, есть два бага:
👉 Поехал текст на кнопке, но сама кнопка работает
👉 Запрос отправляет не на тот endpoint, из-за чего у клиента не работает оплата
Знатоки, внимание вопрос, за какой из этих багов форма вашего стула станет похожа на бутылку?
Если у вас поехала верстка, ну похихикают над вами в твитере и забудут через 5 секунд. В прошлом комменте был кейс про тени. Вы прям потеряли бы кучу клиентов из-за теней, да? При этом если вы потеряете хотя бы рубль при переводе, вас просто уничтожат.
Да, снепшот тесты отлично проверяют внешний вид приложения, а толку то от этого? В тестах важно чтобы не было регресса в логике. Вы тут можете возразить, ведь в таких тестах можно ходить в сеть. Если под капотом снепшотов нет robolecric, вы сходите лесом, а не в сеть. Что если замокать network и проверять на фейковых данных? В этом случае вы проверите как ваше приложение работает в мире феечек с единорогами.
Поэтому я и говорю, что снепшоты приносят профит только для команды дизайн системы. В таких командах вся логика направлена именно на внешний вид, у этих ребят снепшоты реально помогают с регрессом.
Что касается того, что есть какие-то легаси экраны, где верстка не консистентная? Вы просто берете пару дизайнеров или пару QA, отдаете им прилу на полный регресс и все, вам нагенерят задачи на исправление. Это в разы дешевле тысячи человекочасов на создание и поддержку тестов, которые тестируют целое нифига.
✅ Плюсы:
👉 Нет эмуляторов – довольно жирный плюс
👉 Легко написать проверку вместо вызова кучи функций в espresso
👉 Позволяет отследить баги SDK
🚫Минусы:
👉 Нужно думать как хранить и обновлять скриншоты
👉 Могут начать флакать из-за пары пикселей, нужно подбирать параметры
👉 Не проверяют логику – это пункт, который вообще перекрывает все плюсы
Может у меня это старческое, но я все чаще пытаюсь понять профит в бабках от использования технологии. Тесты имеют смысл тогда, когда затраты на их написание и поддержку окупаются за счет багов, которые они ловят. Вот на конкретном примере, есть два бага:
👉 Поехал текст на кнопке, но сама кнопка работает
👉 Запрос отправляет не на тот endpoint, из-за чего у клиента не работает оплата
Знатоки, внимание вопрос, за какой из этих багов форма вашего стула станет похожа на бутылку?
Если у вас поехала верстка, ну похихикают над вами в твитере и забудут через 5 секунд. В прошлом комменте был кейс про тени. Вы прям потеряли бы кучу клиентов из-за теней, да? При этом если вы потеряете хотя бы рубль при переводе, вас просто уничтожат.
Да, снепшот тесты отлично проверяют внешний вид приложения, а толку то от этого? В тестах важно чтобы не было регресса в логике. Вы тут можете возразить, ведь в таких тестах можно ходить в сеть. Если под капотом снепшотов нет robolecric, вы сходите лесом, а не в сеть. Что если замокать network и проверять на фейковых данных? В этом случае вы проверите как ваше приложение работает в мире феечек с единорогами.
Поэтому я и говорю, что снепшоты приносят профит только для команды дизайн системы. В таких командах вся логика направлена именно на внешний вид, у этих ребят снепшоты реально помогают с регрессом.
Что касается того, что есть какие-то легаси экраны, где верстка не консистентная? Вы просто берете пару дизайнеров или пару QA, отдаете им прилу на полный регресс и все, вам нагенерят задачи на исправление. Это в разы дешевле тысячи человекочасов на создание и поддержку тестов, которые тестируют целое нифига.
👍31👎7❤2
В канале моего тёзки был недавно пост про Пирамиду Маслова (да, именно Маслова) в интерфейсах. Крутая идея, которая базируется на том, что интерфейс в самую первую очередь должен решать проблему, и быть удобным, и только в самую последнюю очередь красивым.
Это действительно так, есть много невероятно красивых приложений, которые решают выдуманную проблему, и ими никто не пользуется. В противовес есть куча приложений, которые решают проблему, но у них просто вырвиглазный интерфейс.
Разумеется, в идеальном мире у нас должны быть только красивые приложения, которые решают проблему. Правда, вот какое дело: чем выше по пирамиде, тем субъективнее оценка. То, что один считает красивым, другой увидит говно.
И я подумал, что если поразгонять с этой точки зрения, то снепшоты тестируют именно красоту. Ведь снепшотами вы не тестируете логику, а значит, вы не можете быть уверены, что ваше приложение решает проблему. При этом вы можете протестировать, что не сломалась красота, которая субьективная и если сломается не факт что станет хуже)))
Поэтому если у компании прям дофига денег, и она капец как волнуется о том, что не дай бог на наше приложение ни у кого не встанет, то да, снепшоты прям тема. Правда, история где снепшоты выбирают с целью отказаться от UI тестов, крайне странная.
Это действительно так, есть много невероятно красивых приложений, которые решают выдуманную проблему, и ими никто не пользуется. В противовес есть куча приложений, которые решают проблему, но у них просто вырвиглазный интерфейс.
Разумеется, в идеальном мире у нас должны быть только красивые приложения, которые решают проблему. Правда, вот какое дело: чем выше по пирамиде, тем субъективнее оценка. То, что один считает красивым, другой увидит говно.
И я подумал, что если поразгонять с этой точки зрения, то снепшоты тестируют именно красоту. Ведь снепшотами вы не тестируете логику, а значит, вы не можете быть уверены, что ваше приложение решает проблему. При этом вы можете протестировать, что не сломалась красота, которая субьективная и если сломается не факт что станет хуже)))
Поэтому если у компании прям дофига денег, и она капец как волнуется о том, что не дай бог на наше приложение ни у кого не встанет, то да, снепшоты прям тема. Правда, история где снепшоты выбирают с целью отказаться от UI тестов, крайне странная.
👍11😁2
Смотрите, есть два основных подхода в архитектуре.
Первый — когда мы в самом начале пытаемся все продумать, подсунуть абстракции куда нужно, чтобы можно было быстро поменять поведение.
Второй — эволюционный подход, который базируется на идее: "давай сейчас нафигачим быстрее, а после уже поправим где нужно".
Недостаток первого подхода в том, что мы плохо предвидим будущее и можем подсунуть абстракцию не туда, куда нужно, а куда нужно — не подсунуть.
Недостаток второго подхода в том, что эволюционным путём мы тупо можем упереться в локальный минимум и застрять в нем что приведет к повторению плохих решений.
Если соединить эти два подхода, то ведь получается, что мы обречены писать говнокод, подумал я, не может же быть все так плохо. Потом вспоминаешь всё современное ПО, ах да, точно…
Первый — когда мы в самом начале пытаемся все продумать, подсунуть абстракции куда нужно, чтобы можно было быстро поменять поведение.
Второй — эволюционный подход, который базируется на идее: "давай сейчас нафигачим быстрее, а после уже поправим где нужно".
Недостаток первого подхода в том, что мы плохо предвидим будущее и можем подсунуть абстракцию не туда, куда нужно, а куда нужно — не подсунуть.
Недостаток второго подхода в том, что эволюционным путём мы тупо можем упереться в локальный минимум и застрять в нем что приведет к повторению плохих решений.
Если соединить эти два подхода, то ведь получается, что мы обречены писать говнокод, подумал я, не может же быть все так плохо. Потом вспоминаешь всё современное ПО, ах да, точно…
😁34👍6👏3🔥1
Итак, собесы. Собесов я проходил много, и в закромах уже давно лежит идея рассказать про некоторые из них.
Компания AliExpress Russia, собес на позицию Android-разработчика. Было несколько этапов, насколько помню, три: скрининг, Android-секция и техническая. Скрининг и Android-секцию я слабо помню, видимо, вопросы были максимально банальные в стиле: "расскажите про все основные компоненты". Техническая сессия оказалась алгоритмической.
Первым вопросом у меня была задача на обнаружение циклов в связном списке. Это буквально самый нелепый вопрос на собеседовании, потому как я вообще не понимаю, что он проверяет. Ты либо знаешь алгоритм, либо нет, если ты никогда не задротил LeetCode, ты скорее всего завалишь его сразу. Ну, они, разумеется, не на того напали, ведь я эту задачу прикола ради поизучал буквально за пару дней до собеса.
Вторым вопросом был flatten array. Нужно было написать кастомный iterator, который из такого списка [1, 2, 3, [4, [5, 6]]], делает такой [1, 2, 3, 4, 5, 6]. Задача была прикольная, с натяжкой можно представить, где такое можно применить.
Знаете, что было самое забавное в этих собесах? Собес в AliExpress, по ощущениям, был самым сложным по сравнению с другими, а оффер в итоге был самый маленький…
Если вдруг вам интересно узнать, как решаются эти задачи в моей подаче с иллюстрациями, накидайте огоньков, я про это сделаю посты.
Компания AliExpress Russia, собес на позицию Android-разработчика. Было несколько этапов, насколько помню, три: скрининг, Android-секция и техническая. Скрининг и Android-секцию я слабо помню, видимо, вопросы были максимально банальные в стиле: "расскажите про все основные компоненты". Техническая сессия оказалась алгоритмической.
Первым вопросом у меня была задача на обнаружение циклов в связном списке. Это буквально самый нелепый вопрос на собеседовании, потому как я вообще не понимаю, что он проверяет. Ты либо знаешь алгоритм, либо нет, если ты никогда не задротил LeetCode, ты скорее всего завалишь его сразу. Ну, они, разумеется, не на того напали, ведь я эту задачу прикола ради поизучал буквально за пару дней до собеса.
Вторым вопросом был flatten array. Нужно было написать кастомный iterator, который из такого списка [1, 2, 3, [4, [5, 6]]], делает такой [1, 2, 3, 4, 5, 6]. Задача была прикольная, с натяжкой можно представить, где такое можно применить.
Знаете, что было самое забавное в этих собесах? Собес в AliExpress, по ощущениям, был самым сложным по сравнению с другими, а оффер в итоге был самый маленький…
Если вдруг вам интересно узнать, как решаются эти задачи в моей подаче с иллюстрациями, накидайте огоньков, я про это сделаю посты.
🔥198🗿13❤2👍2
Знаете, я тут подумал и кажется осознал:
👉 покрывать все интерфейсами это единственно верное решение, чтобы ваша кодовая база не развалилась.
👉 npm и gradle это лучшие системы сборки, которые существуют на рынке.
👉 снепшоты это отличная замена UI тестам, в целом можно только их использовать!
👉 llm от лукавого и нужно писать весь код самостоятельно
👉 чтобы быть востребованным на рынке важно, чтобы у вас был красивый github
UPD: лучшая тема в IDE – светлая
👉 покрывать все интерфейсами это единственно верное решение, чтобы ваша кодовая база не развалилась.
👉 npm и gradle это лучшие системы сборки, которые существуют на рынке.
👉 снепшоты это отличная замена UI тестам, в целом можно только их использовать!
👉 llm от лукавого и нужно писать весь код самостоятельно
👉 чтобы быть востребованным на рынке важно, чтобы у вас был красивый github
UPD: лучшая тема в IDE – светлая
🤡63😁37👍16🔥5🗿2🥰1
Вы накидали много огоньков (кажется ни один пост столько не набирал), а это значит, что придется делать разбор задачи. Ну что ж, погнали, поиск цикла в связном списке.
Условия задачи: есть связный список, и нужно понять, есть ли в нем цикл. Цикл — это когда у нас последний элемент списка ссылается не на null, а на какой-то из элементов этого самого списка, короче, смотрите картинку.
Начну с того, что я вообще не ебу, где такое можно применять. Я понимаю алгоритм поиска цикла в графе, который применяется повсеместно, но в связном списке... Цикл в связном списке — это баг реализации, но да ладно.
Сложность тут в том, что его не решишь обычными способами вроде "запомнить все элементы в set и потом проверять по нему". В этом списке нет индексов, элементы могут повторяться, и мы заранее не знаем, где его конец.
Поэтому поступаем изящнее – два указателя. Один нормальный, который идет по элементам, второй в жопу ужаленный, который скачет через элемент. Если запустить два этих указателя, то у нас может быть два кейса:
👉 быстрый указатель упрется в null – цикла нет
👉 они встретятся на каком-то из элементов – цикл есть
Это собственно все... Код решения на python:
В некоторых случаях могут попросить вернуть ссылку на начало цикла, в этом случае вам нужно будет подвигать ссылку head до нужного места.
Условия задачи: есть связный список, и нужно понять, есть ли в нем цикл. Цикл — это когда у нас последний элемент списка ссылается не на null, а на какой-то из элементов этого самого списка, короче, смотрите картинку.
Начну с того, что я вообще не ебу, где такое можно применять. Я понимаю алгоритм поиска цикла в графе, который применяется повсеместно, но в связном списке... Цикл в связном списке — это баг реализации, но да ладно.
Сложность тут в том, что его не решишь обычными способами вроде "запомнить все элементы в set и потом проверять по нему". В этом списке нет индексов, элементы могут повторяться, и мы заранее не знаем, где его конец.
Поэтому поступаем изящнее – два указателя. Один нормальный, который идет по элементам, второй в жопу ужаленный, который скачет через элемент. Если запустить два этих указателя, то у нас может быть два кейса:
👉 быстрый указатель упрется в null – цикла нет
👉 они встретятся на каком-то из элементов – цикл есть
Это собственно все... Код решения на python:
class ListNode:
def __init__(self, x, next):
self.val = x
self.next = next
def detect_cycle(head: ListNode) -> bool:
fast = slow = head
circle_detected = False
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
circle_detected = True
break
return circle_detected
В некоторых случаях могут попросить вернуть ссылку на начало цикла, в этом случае вам нужно будет подвигать ссылку head до нужного места.
🗿20👍12🔥1
Удивительно, но даже на решении базовой задачи умудрились придраться. Смотрите, было несколько комментов касательно решения с использованием set.
Это один из вариантов наивного решения, которое заключается в том, что мы всё кладем в set, и если обнаружили копию, значит есть цикл. Решение рабочее, более простое, но менее эффективное, и на интервью вероятнее всего не примут. Вот почему:
Во-первых, вам нужно сравнивать именно ссылки, по значению не получится, так как в списке могут быть дубликаты — список это не set.
Во-вторых, оба решения имеют временную сложность O(n), однако при использовании set вы получаете расход по памяти O(n), тогда как в решении с двумя указателями расходов на дополнительную память нет.
Это один из вариантов наивного решения, которое заключается в том, что мы всё кладем в set, и если обнаружили копию, значит есть цикл. Решение рабочее, более простое, но менее эффективное, и на интервью вероятнее всего не примут. Вот почему:
Во-первых, вам нужно сравнивать именно ссылки, по значению не получится, так как в списке могут быть дубликаты — список это не set.
Во-вторых, оба решения имеют временную сложность O(n), однако при использовании set вы получаете расход по памяти O(n), тогда как в решении с двумя указателями расходов на дополнительную память нет.
👍17🔥2😁1
Небольшой совет, если вдруг заведете канал, аккуратнее протирайте клавиатуру на компе с включенной телегой. Есть тут один дурачок, который об это обжегся, не буду расскрывать его личность
😁41🗿6🤡5❤3