{2/2} Один из способов решить проблему координации большой команды, разделять ее на мелкие и объединять на фичам. В итоге мы собираем в команду всех участников, Android, iOS, QA, Frontend. Фича команда работает над частью приложения. Только над этой частью и берет задачи связанные только с ней, никуда в другое место не лезет.
И вот допустим у нас опять ситуация когда мы ждем Frontend. Что в это время делать остальным платформам? А ничего! Сходить погулять, попить кофе, заняться рефакторигом вон того класса, а то чет неудобно. Еще хорошо бы тестами вот этот функционал покрыть.
Другими словами, мы занимаемся менее важными вещами, от которых можно быстро вернуться к важной и единственной задаче. Мы не берем новую бизнес задачу, пока Frontend не закончит. Удивительно, но в таком случае скорость поставки возрастает в разы. Именно в этом идея объединения в одну команду фронтов и мобильщиков, даже несмотря на то, что у них фундаментально разные подходы.
Про этот феномен очень круто и понятным языком написано в книге “Цель, процесс непрерывного улучшения”. Везде ее рекомендуют как must have литература, кто в будущем хочет стать тимлидом. На самом деле ее можно прочитать даже если и не планируете. Она написана как история директора завода которому нужно спастисвою жопу завод от разорения.
Очень интересная история. И для меня там было две основных идеи. Первая – 100% загруженность разработчиков не значит максимальная продуктивность, а скорее наоборот двигаться будете в итоге медленнее. Страшная идея для тех, кто на аутсорсе. Вторая – компания двигается со скоростью самого медленного звена. Это значит от того, что вы сегодня не доработали один час изменится примерно нихрена.
Поэтому когда устраиваетесь в продуктовую компанию, спрашивайте как происходит разделение на команды. Чаще всего если менеджеры действительно знают свое дело, они будут объединять людей по фича командам. Для меня это индикатор, что в компании ориентируются на результат, нежели на загруженность разработчиков.
Опять-таки, это если мы говорим про большую компанию. Если в мелких нет такого разделения, то это ок)
И вот допустим у нас опять ситуация когда мы ждем Frontend. Что в это время делать остальным платформам? А ничего! Сходить погулять, попить кофе, заняться рефакторигом вон того класса, а то чет неудобно. Еще хорошо бы тестами вот этот функционал покрыть.
Другими словами, мы занимаемся менее важными вещами, от которых можно быстро вернуться к важной и единственной задаче. Мы не берем новую бизнес задачу, пока Frontend не закончит. Удивительно, но в таком случае скорость поставки возрастает в разы. Именно в этом идея объединения в одну команду фронтов и мобильщиков, даже несмотря на то, что у них фундаментально разные подходы.
Про этот феномен очень круто и понятным языком написано в книге “Цель, процесс непрерывного улучшения”. Везде ее рекомендуют как must have литература, кто в будущем хочет стать тимлидом. На самом деле ее можно прочитать даже если и не планируете. Она написана как история директора завода которому нужно спасти
Очень интересная история. И для меня там было две основных идеи. Первая – 100% загруженность разработчиков не значит максимальная продуктивность, а скорее наоборот двигаться будете в итоге медленнее. Страшная идея для тех, кто на аутсорсе. Вторая – компания двигается со скоростью самого медленного звена. Это значит от того, что вы сегодня не доработали один час изменится примерно нихрена.
Поэтому когда устраиваетесь в продуктовую компанию, спрашивайте как происходит разделение на команды. Чаще всего если менеджеры действительно знают свое дело, они будут объединять людей по фича командам. Для меня это индикатор, что в компании ориентируются на результат, нежели на загруженность разработчиков.
Опять-таки, это если мы говорим про большую компанию. Если в мелких нет такого разделения, то это ок)
👍25
📖 Скорочтение
Однажды один мой друг сказал: "Жалко что я медленно читаю, вот если бы я обладал навыком скорочтения, я бы мог быстро прочитать все доки и гораздо быстрее осваивал новые технологии и быстрее бы делал задачи". Мне тогда на уровне интуиции эта идея показалась странной, но я не мог понять почему. Признаться честно я и сам увлекался этой темой в начале студенчества, но у меня как-то не получалось и я забил на это дело. Как оказалась правильно сделал.
Речь тут даже несколько про само скорочтение, а больше о том, что у нас огромный пласт информации. Каждый день кто-то пишет интересную статью, выходят release notes новых библиотек, выходят новые фреймворки, библиотеки, подходы. Кажется если ты не будешь сделать за новостями неделю, но ты сразу превратишься в деда который только про Cobol может говорить, ощущаешь себя как Мэтт Дэймон в Cпасти Рядового Райена.
И вот вроде бы есть решение в виде скорочтения. Представь, ты можешь читать ну очень быстро, прочитывать всю страницу при одном только взгляде. Можешь освободить кучу времени на другие дела. Вот только задавали ли вы себе вопрос: а действительно ли прочитанные статьи делают разработчика, крутым разработчиком? Если взять начинающего разработчика, который очень быстро сможет прочитать всю доку по Android сможет ли он написать действительно крутой код? Ну скорее всего нет)
У Максима Дорофеева есть очень крутая мысль и в книге и в видосах, что мы в современном мире уделяем время только потреблению информации и очень мало уделяем обдумыванию и практике. В IT это проявляется в виде того, что мы много читаем всяких статей (ну или как я просто добавляем в список на потом) но мало пробуем на практике то, что написано. Можно много читать про Compose и про то какой он классный, но пока не попробуешь руками сделать проект не поймешь, где в статье приукрасили, а где он реально хорош.
Мысль о том, что в IT все очень быстро меняется это миф. Ну все конечно меняется, но не так быстро как может показаться. Если посмотреть на историю Android за последние 10 лет, что кардинально поменялось в разработке? Единственное действительно сильное изменение происходит только сейчас с выходом Compose, да и то это касается только UI слоя. Ну и разве что мы перестали все делать в Activity и придумали логику выносить в отдельное место.
Поэтому если вам кажется что если вы не будете постоянно следить за новинками, что-то пропустите, расслабьтесь на самом деле все меняется довольно медленно. Скорочтение не нужно, реальный опыт получаешь только решая проблемы. На себе я это точно хорошо прочувствовал, что действительно я разбираюсь в теме, только если пытаюсь решить какую-то проблему на проекте.
Поэтому если будет выбор между разработчиком который быстро читает и который уже решил хренову тучу проблем выбор очевидный)
Однажды один мой друг сказал: "Жалко что я медленно читаю, вот если бы я обладал навыком скорочтения, я бы мог быстро прочитать все доки и гораздо быстрее осваивал новые технологии и быстрее бы делал задачи". Мне тогда на уровне интуиции эта идея показалась странной, но я не мог понять почему. Признаться честно я и сам увлекался этой темой в начале студенчества, но у меня как-то не получалось и я забил на это дело. Как оказалась правильно сделал.
Речь тут даже несколько про само скорочтение, а больше о том, что у нас огромный пласт информации. Каждый день кто-то пишет интересную статью, выходят release notes новых библиотек, выходят новые фреймворки, библиотеки, подходы. Кажется если ты не будешь сделать за новостями неделю, но ты сразу превратишься в деда который только про Cobol может говорить, ощущаешь себя как Мэтт Дэймон в Cпасти Рядового Райена.
И вот вроде бы есть решение в виде скорочтения. Представь, ты можешь читать ну очень быстро, прочитывать всю страницу при одном только взгляде. Можешь освободить кучу времени на другие дела. Вот только задавали ли вы себе вопрос: а действительно ли прочитанные статьи делают разработчика, крутым разработчиком? Если взять начинающего разработчика, который очень быстро сможет прочитать всю доку по Android сможет ли он написать действительно крутой код? Ну скорее всего нет)
У Максима Дорофеева есть очень крутая мысль и в книге и в видосах, что мы в современном мире уделяем время только потреблению информации и очень мало уделяем обдумыванию и практике. В IT это проявляется в виде того, что мы много читаем всяких статей (ну или как я просто добавляем в список на потом) но мало пробуем на практике то, что написано. Можно много читать про Compose и про то какой он классный, но пока не попробуешь руками сделать проект не поймешь, где в статье приукрасили, а где он реально хорош.
Мысль о том, что в IT все очень быстро меняется это миф. Ну все конечно меняется, но не так быстро как может показаться. Если посмотреть на историю Android за последние 10 лет, что кардинально поменялось в разработке? Единственное действительно сильное изменение происходит только сейчас с выходом Compose, да и то это касается только UI слоя. Ну и разве что мы перестали все делать в Activity и придумали логику выносить в отдельное место.
Поэтому если вам кажется что если вы не будете постоянно следить за новинками, что-то пропустите, расслабьтесь на самом деле все меняется довольно медленно. Скорочтение не нужно, реальный опыт получаешь только решая проблемы. На себе я это точно хорошо прочувствовал, что действительно я разбираюсь в теме, только если пытаюсь решить какую-то проблему на проекте.
Поэтому если будет выбор между разработчиком который быстро читает и который уже решил хренову тучу проблем выбор очевидный)
👍41🔥2👏2👎1
Мой коллега недавно написал серию статей про фрагменты. И вот меня довольно сильно заинтересовала часть про Result API. В статье показаны базовые кейсы как это использовать. Однако в статье не указаны недостатки Result API для фрагментов, не порядок я считаю. И так как я очень люблю похейтить либы гугла, поговорим о минусах этого подхода.
Значит, если вдруг пропустили, Result API это новый подход к тому, как мы можем получать результат с других Activity или Fragments. Вот раньше допустим был геморрой с обработкой разрешения. Нужно разрешение сперва запросить, потом переопределить метод onActivityResult, для это нужно придумывать специальные Id для этого. Мягко говоря это неудобно.
Сейчас все намного проще, вызываем специальный метод, устанавливаем в него лямбду. Затем запрашиваем разрешение и система уже дернет нашу лямбду с нужными уже распарщеными данными. Уже намного удобнее.
Result API крутая штука и по факту сейчас это единственный путь как обрабатывать разрешения или например получать фото с галереи. Метод onActivityResult сейчас помечен как Deprecated. Другими словами, Result API круто использовать когда нужно получить данные извне, однако часто это используется для передачи данных м/у своими же Activity. А когда речь заходит за использование Result API для передачи данных м/у фрагментами начинается вообще мрак.
Основная идея, которой я уже давно придерживаюсь, и которая не раз меня спасала, сводится к следующему: “Передавайте данные м/у своими экранами через Domain слой”. View очень сильно ограничивает в плане передачи данных по трем простым причинам:
1️⃣ Формат передачи данных всегда Bundle. Если конечно не дергаются методы других фрагментов напрямую, где нужно быть ну очень аккуратным.
2️⃣ View очень неустойчивая штука. Этот слой часто меняется, он может умереть в любой момент.
3️⃣ Сложность с навигацией. Даже у библиотеки от самого гугла нет адекватной интеграции с Result API. Мб она конечно есть, но я так и не нашел как нормально это сделать.
Подход с передачей View через Domain слой куда гибче, тут 100 способов как передавать данные удобным тебе способом, в удобном тебе формате и ты целиком и полностью все контролируешь.
Еще основная проблема, что Result API для фрагментов заставляет тебя создавать ебучие строковые константы. Это вообще проблема у всех библиотек гугла, они видимо прям в восторге от строковых констант. Иначе как объяснить это и это и вот это.
В Result API для Activity сделано круто, потому как там есть специальный класс контракт, благодаря которому, ты сразу задаешь правила как преобразовать данные из Bundle в нужный объект. Для фрагментов такого нет, в итоге тебе передают Bundle, а ты иди сам ищи в коде, что вообще в него упаковали!
Подводя итог, хотите облегчить тебе жизнь, передавайте данные м/у фрагментами через Domain слой, этот подход реально спасает когда навигация неожиданно меняется, а она будет меняться. Не хотите лишних сложностей, не тащите Result API во фрагменты!
Значит, если вдруг пропустили, Result API это новый подход к тому, как мы можем получать результат с других Activity или Fragments. Вот раньше допустим был геморрой с обработкой разрешения. Нужно разрешение сперва запросить, потом переопределить метод onActivityResult, для это нужно придумывать специальные Id для этого. Мягко говоря это неудобно.
Сейчас все намного проще, вызываем специальный метод, устанавливаем в него лямбду. Затем запрашиваем разрешение и система уже дернет нашу лямбду с нужными уже распарщеными данными. Уже намного удобнее.
Result API крутая штука и по факту сейчас это единственный путь как обрабатывать разрешения или например получать фото с галереи. Метод onActivityResult сейчас помечен как Deprecated. Другими словами, Result API круто использовать когда нужно получить данные извне, однако часто это используется для передачи данных м/у своими же Activity. А когда речь заходит за использование Result API для передачи данных м/у фрагментами начинается вообще мрак.
Основная идея, которой я уже давно придерживаюсь, и которая не раз меня спасала, сводится к следующему: “Передавайте данные м/у своими экранами через Domain слой”. View очень сильно ограничивает в плане передачи данных по трем простым причинам:
1️⃣ Формат передачи данных всегда Bundle. Если конечно не дергаются методы других фрагментов напрямую, где нужно быть ну очень аккуратным.
2️⃣ View очень неустойчивая штука. Этот слой часто меняется, он может умереть в любой момент.
3️⃣ Сложность с навигацией. Даже у библиотеки от самого гугла нет адекватной интеграции с Result API. Мб она конечно есть, но я так и не нашел как нормально это сделать.
Подход с передачей View через Domain слой куда гибче, тут 100 способов как передавать данные удобным тебе способом, в удобном тебе формате и ты целиком и полностью все контролируешь.
Еще основная проблема, что Result API для фрагментов заставляет тебя создавать ебучие строковые константы. Это вообще проблема у всех библиотек гугла, они видимо прям в восторге от строковых констант. Иначе как объяснить это и это и вот это.
В Result API для Activity сделано круто, потому как там есть специальный класс контракт, благодаря которому, ты сразу задаешь правила как преобразовать данные из Bundle в нужный объект. Для фрагментов такого нет, в итоге тебе передают Bundle, а ты иди сам ищи в коде, что вообще в него упаковали!
Подводя итог, хотите облегчить тебе жизнь, передавайте данные м/у фрагментами через Domain слой, этот подход реально спасает когда навигация неожиданно меняется, а она будет меняться. Не хотите лишних сложностей, не тащите Result API во фрагменты!
👍24🤔2
{1/2} Хочу рассказать про интересную тему с многопоточностью. И начну с вопроса: что такое синхронизация, если мы говорим про многопоточность?
Синхронизация – способ сделать так, чтобы например метод, который может дёрнуться из нескольких потоков гарантировано вызвался только одним. Остальные потоки в этот момент, должны дождаться пока не закончит первый и т.д по очереди.
Все это можно сделать при помощи такой штуки как Mutex. В Java Mutex реализован на уровне языка и представлен в виде блока synchronized. Все довольно просто, помечаем метод специальным модификатором synchronized и все начинает работать как нужно, потоки будут вызывать метод по очереди. А что если я скажу вам, что есть способ сделать синхронизацию, без блокирования потоков?
Есть такая штука как Atomic переменные. В большинстве случаев про них вспоминают в тот момент, когда нужно быстренько сделать какой-нибудь счетчик, который будет корректно работать в многопоточной системе. Однако это не все кейсы использования Atomic переменных. Диапазон задач, которые можно решить при помощи Atomic гораздо шире, чем просто создание счетчиков.
У всех Atomic переменных есть метод который называется compareAndSet. Что он делает: как очевидно из названия, сравнивает и потом устанавливает значение. Если удалось установить новое значение он возвращает true если не удалось false. С чем он сравнивает? Сравнивает он первый аргумент со значением которое установлено в самом Atomic. Да, на словах звучит стремно, поэтому давайте на примере кода:
Если текущее значение Atomic переменной, совпадает со значением первого аргумента функции
И казалось бы, ну и что такого крутого, можно и на syncronized блоках сделать точно такой же метод. Не совсем так, под капотом compareAndSet не использует привычные нам инструменты синхронизации, у этого метода нативная реализация.
У каждого современного процессора есть встроенная команда
Как это использовать? Вот самый простой и базовый пример. Представьте, что у вас есть метод, который вызывается из нескольких потоков. И вам нужно сделать так, чтобы поток, который вызывал этот метод первым, сделал один блок кода, а все остальные потоки прошли мимо.
Как это будет сделано с использованием synchronized блока:
Все отработает как нужно, однако пока один поток будет выполнять метод, все остальные просто будут ждать, как-то не круто. А теперь используем магию Atomic.
Для данной задачи, даже кода получилось меньше. Что произойдет тут. Метод
Вот и все, пример представленный тут очень простой, однако даже такой пример я один раз использовал в продакшен коде. Если заглянуть в исходники Rx, можно обнаружить, что весь Rx построен на базе неблокирующей синхронизации.
Возникает вопрос, зачем тогда вообще нужен Mutex, ведь гораздо быстрее делать все на неблокирующей реализации. В нашем примере и правда неблокирующая реализация, намного эффективнее. Однако это не всегда.
Синхронизация – способ сделать так, чтобы например метод, который может дёрнуться из нескольких потоков гарантировано вызвался только одним. Остальные потоки в этот момент, должны дождаться пока не закончит первый и т.д по очереди.
Все это можно сделать при помощи такой штуки как Mutex. В Java Mutex реализован на уровне языка и представлен в виде блока synchronized. Все довольно просто, помечаем метод специальным модификатором synchronized и все начинает работать как нужно, потоки будут вызывать метод по очереди. А что если я скажу вам, что есть способ сделать синхронизацию, без блокирования потоков?
Есть такая штука как Atomic переменные. В большинстве случаев про них вспоминают в тот момент, когда нужно быстренько сделать какой-нибудь счетчик, который будет корректно работать в многопоточной системе. Однако это не все кейсы использования Atomic переменных. Диапазон задач, которые можно решить при помощи Atomic гораздо шире, чем просто создание счетчиков.
У всех Atomic переменных есть метод который называется compareAndSet. Что он делает: как очевидно из названия, сравнивает и потом устанавливает значение. Если удалось установить новое значение он возвращает true если не удалось false. С чем он сравнивает? Сравнивает он первый аргумент со значением которое установлено в самом Atomic. Да, на словах звучит стремно, поэтому давайте на примере кода:
val atomic = AtomicBoolean(false)
val newValue = true
val result = atomic.compareAndSet(false, newValue)
Если текущее значение Atomic переменной, совпадает со значением первого аргумента функции
compareAndSet
, только в этом случае происходит замена на новое значение (newValue).И казалось бы, ну и что такого крутого, можно и на syncronized блоках сделать точно такой же метод. Не совсем так, под капотом compareAndSet не использует привычные нам инструменты синхронизации, у этого метода нативная реализация.
У каждого современного процессора есть встроенная команда
compareAndSet
, в литературе часто сокращают и называют CAS. Так вот, команда CAS реализована аж на уровне процессора, означает что этот метод во много раз быстрее чем synchronized блоки.Как это использовать? Вот самый простой и базовый пример. Представьте, что у вас есть метод, который вызывается из нескольких потоков. И вам нужно сделать так, чтобы поток, который вызывал этот метод первым, сделал один блок кода, а все остальные потоки прошли мимо.
Как это будет сделано с использованием synchronized блока:
var flag = true
fun toDo() = synchronized{
if(flag){
// block
flag = false
}
}
Все отработает как нужно, однако пока один поток будет выполнять метод, все остальные просто будут ждать, как-то не круто. А теперь используем магию Atomic.
var flag = AtomicBoolean(false)
fun toDo() {
if(flag.compareAndSet(false, true)){
// block
}
}
Для данной задачи, даже кода получилось меньше. Что произойдет тут. Метод
compareAndSet
как вы уже знаете, гарантированно синхронизирован, причем на уровне процессора. Первый поток, который выполняет этот код, успешно произведет замену переменных и метод compareAndSet
вернет ему true. Последующие потоки, будут получать результат false, потому как текущее значение уже не совпадает с первым аргументом. Ни один поток не будет заблокирован, все просто пройдут мимо блока if. Вот и все, пример представленный тут очень простой, однако даже такой пример я один раз использовал в продакшен коде. Если заглянуть в исходники Rx, можно обнаружить, что весь Rx построен на базе неблокирующей синхронизации.
Возникает вопрос, зачем тогда вообще нужен Mutex, ведь гораздо быстрее делать все на неблокирующей реализации. В нашем примере и правда неблокирующая реализация, намного эффективнее. Однако это не всегда.
👍39
{2/2} Неблокирующая реализация хорошо работает когда у вас небольшое количество потоков, если же потоков становится много, она сильно проигрывает обычной блокирующей синхронизации. Связано это с тем, что происходит с потоками при использовании Mutex.
В системе с использованием Mutex (блокирующая синхронизация), потоки, пока ждут своей очереди, переходят в особое состояние. В этом состоянии они не тратят CPU. В неблокирующей синхронизации же, потоки никогда не спят и постоянно тратят CPU.
На самом деле, обор тонкостей когда использовать блокирующую синхронизацию, а когда обычную тянет на отдельную статью. Однако точно стоит знать, что такая модель есть.
В системе с использованием Mutex (блокирующая синхронизация), потоки, пока ждут своей очереди, переходят в особое состояние. В этом состоянии они не тратят CPU. В неблокирующей синхронизации же, потоки никогда не спят и постоянно тратят CPU.
На самом деле, обор тонкостей когда использовать блокирующую синхронизацию, а когда обычную тянет на отдельную статью. Однако точно стоит знать, что такая модель есть.
👍42
⚙️ Gradle
Сейчас по работе довольно часто приходится сталкиваться с gradle. До этого я лишь иногда делал мелкие фиксы скриптов, в основном по схеме: загуглил -> скопировал код со StackOverflow -> вроде работает. Сейчас же копая тему глубже, я понял почему его так хейтят.
На самом деле Gradle довольно неплохая билд система, однако в ней куча странных и очевидно неоптимальных решений которые со временем вылились в то, что имеем.
Поэтому давайте пройдемся по конкретным недостатком которые так бесят:
👉 Память
👉 Инкрементальность конфигурации
👉 Язык скриптов
👉 Нет ограничений
Сейчас по работе довольно часто приходится сталкиваться с gradle. До этого я лишь иногда делал мелкие фиксы скриптов, в основном по схеме: загуглил -> скопировал код со StackOverflow -> вроде работает. Сейчас же копая тему глубже, я понял почему его так хейтят.
На самом деле Gradle довольно неплохая билд система, однако в ней куча странных и очевидно неоптимальных решений которые со временем вылились в то, что имеем.
Поэтому давайте пройдемся по конкретным недостатком которые так бесят:
👉 Память
👉 Инкрементальность конфигурации
👉 Язык скриптов
👉 Нет ограничений
👍15
Когда я учился в школе, это был примерно 2009 или 2010 год, у меня был совсем слабенький ноут. Что-то вроде старого pentium, 2гб оперативы и совсем слабенькая карта. И вот я скачиваю первый Crysis, которая в свое время произвела просто фурор в индустрии видео игр. Каково было мое удивление, что это чудо запустилось без каких-либо явных проблем с производительностью.
Да настройки были не максимальные, но тем не менее, того железа хватало чтобы для комфортной игры. 3d игра это куча расчетов в секунду, загрузка огромного количества ассетов и все это за те самые 16ms. Затем смотрим на Gradle, которая, я напомню, является всего лишь билд системой.
Суть билд системы это преобразовать один файл в другой. Это вот та ироничная проблема индустрии, когда рокет сайнс игре достаточно меньше 1Гб оперативы, а билд системе, суть которой сводится к правильному вызову компилятора нужно больше 10гб. Это вообще что такое?
Это настолько распространенная фигня, что мы уже даже не замечаем эту проблему. Сейчас проходя в компанию мобильным разрабам выдают ноуты с 32Гб оперативы. По той причине, что если будет меньше, все будет страшно зависать. И при этом проекты то продолжают расти, почти все большие компании приходят к супер аппам. Все выглядит так, что скорого и 32гб будет мало.
Единого ответа почему Gradle столько ест у меня нет. Да скорее всего уже и сама команда Gradle затрудняется на это ответить. Очевидно что у Gradle куча легаси, куча утечек памяти и куча не самых оптимальных решений. Этому также способствует что Gradle работает на базе JVM, которая сама по себе черная дыра для памяти.
Остается надеятся на то, что мы когда-нибудь слезем с иглы Gradle и нам не нужно будет покупать компы с большим запасом памяти.
Да настройки были не максимальные, но тем не менее, того железа хватало чтобы для комфортной игры. 3d игра это куча расчетов в секунду, загрузка огромного количества ассетов и все это за те самые 16ms. Затем смотрим на Gradle, которая, я напомню, является всего лишь билд системой.
Суть билд системы это преобразовать один файл в другой. Это вот та ироничная проблема индустрии, когда рокет сайнс игре достаточно меньше 1Гб оперативы, а билд системе, суть которой сводится к правильному вызову компилятора нужно больше 10гб. Это вообще что такое?
Это настолько распространенная фигня, что мы уже даже не замечаем эту проблему. Сейчас проходя в компанию мобильным разрабам выдают ноуты с 32Гб оперативы. По той причине, что если будет меньше, все будет страшно зависать. И при этом проекты то продолжают расти, почти все большие компании приходят к супер аппам. Все выглядит так, что скорого и 32гб будет мало.
Единого ответа почему Gradle столько ест у меня нет. Да скорее всего уже и сама команда Gradle затрудняется на это ответить. Очевидно что у Gradle куча легаси, куча утечек памяти и куча не самых оптимальных решений. Этому также способствует что Gradle работает на базе JVM, которая сама по себе черная дыра для памяти.
Остается надеятся на то, что мы когда-нибудь слезем с иглы Gradle и нам не нужно будет покупать компы с большим запасом памяти.
👍10🤯10🤔5🔥2👎1
Вторая самая бесячая проблема Gradle, это то, что он не умеет кешировать конфигурации. Напомню, Gradle работает в три этапа:
1️⃣ Инициализация. Когда Gradle бегает по файлам и собирает инфу о том, сколько у нас вообще есть модулей и создает объекты Project.
2️⃣ Конфигурация. Запускаются все скрипты build.gradle, подтягиваются все таски и строится граф этих самых тасок.
3️⃣ Выполнение тасок. Тут все просто, Gradle просто запускает нужные таски в нужном порядке
Ну так и вот, на нашем проекте расчет конфигурации занимает (если верить метрикам) в среднем около 40 сек. Почти минута! Минута пенальти, просто чтобы он вообще начал билдить.
Конечно тут много причин почему это происходит. Одна из них это потому что мы криво написали скрипты, и сейчас решаем эту проблему. Однако от действительно хорошей билд системы ожидаешь, что она будет бить по рукам если ты делаешь что-то не то. Gradle же позволяет направо и налево делать side эффекты в скриптах, из-за чего системе приходится прогонять эту самую конфигурацию каждый раз.
Проблема в том, что даже если не делать ничего лишнего и писать скрипты продумано по всем правилам, Gradle не умеет инкрементально запускать конфигурацию. Что удивительно, ведь он умеет в инкрементальность файлов, но при этом не умеет в инкрементальность скриптов, что вроде как не сильно сложнее. Было бы конечно круто, если бы Gradle умел просчитывать изменения в конфигурации и запускать только измененные части.
Помимо отсутствия кеширования и инкрементальности, этап конфигурации не умеет параллелится. Что довольно грустно, у нас сейчас куча ядер в компе, но во время конфигурации используется только одно.
В итоге все эти проблемы сводятся к одной и той же причине. Gradle слишком многое позволяет делать разработчикам, что приводит к тому что он не может применить оптимизации. При этом не думаю что у разработчиков был такой запрос, чтобы можно было делать что угодно. Возможно Gradle попал в ловушку стартапа, решили выдуманную задачу и плохо в итоге стало всем.
Вообщем ситуация такая, каждый раз запуская билд, я сижу в котле ярости, ведь я ничего не менял, а тем не менее опять должен ждать пока рассчитается конфигурация.
1️⃣ Инициализация. Когда Gradle бегает по файлам и собирает инфу о том, сколько у нас вообще есть модулей и создает объекты Project.
2️⃣ Конфигурация. Запускаются все скрипты build.gradle, подтягиваются все таски и строится граф этих самых тасок.
3️⃣ Выполнение тасок. Тут все просто, Gradle просто запускает нужные таски в нужном порядке
Ну так и вот, на нашем проекте расчет конфигурации занимает (если верить метрикам) в среднем около 40 сек. Почти минута! Минута пенальти, просто чтобы он вообще начал билдить.
Конечно тут много причин почему это происходит. Одна из них это потому что мы криво написали скрипты, и сейчас решаем эту проблему. Однако от действительно хорошей билд системы ожидаешь, что она будет бить по рукам если ты делаешь что-то не то. Gradle же позволяет направо и налево делать side эффекты в скриптах, из-за чего системе приходится прогонять эту самую конфигурацию каждый раз.
Проблема в том, что даже если не делать ничего лишнего и писать скрипты продумано по всем правилам, Gradle не умеет инкрементально запускать конфигурацию. Что удивительно, ведь он умеет в инкрементальность файлов, но при этом не умеет в инкрементальность скриптов, что вроде как не сильно сложнее. Было бы конечно круто, если бы Gradle умел просчитывать изменения в конфигурации и запускать только измененные части.
Помимо отсутствия кеширования и инкрементальности, этап конфигурации не умеет параллелится. Что довольно грустно, у нас сейчас куча ядер в компе, но во время конфигурации используется только одно.
В итоге все эти проблемы сводятся к одной и той же причине. Gradle слишком многое позволяет делать разработчикам, что приводит к тому что он не может применить оптимизации. При этом не думаю что у разработчиков был такой запрос, чтобы можно было делать что угодно. Возможно Gradle попал в ловушку стартапа, решили выдуманную задачу и плохо в итоге стало всем.
Вообщем ситуация такая, каждый раз запуская билд, я сижу в котле ярости, ведь я ничего не менял, а тем не менее опять должен ждать пока рассчитается конфигурация.
👍18👏2🤔1
Итак. Я немного проебался и не уточнил всю инфу, благо в коментах меня поправили. Более того, слона то я и не заменил, оказывается у нас на проекте оно давно используется.
Значится, Gradle за последние несколько релизов сделал серьезный шаг вперед и научился кешировать конфигурацию.
Причем для кеширования конфигурации, даже есть две опции. Обе кстати до сих пор экспериментальные, но вроде как работают нормально:
1️⃣ org.gradle.unsafe.configuration-cache. Самая рекомендуемая опция, все довольно очевидно, если input для конфигурации не меняется, т.е не меняются скрипты или другие входные данные все будет закешированно и повторно запускаться не будет. Работает хорошо, но для настройки есть потешные параметры вроде того, сколько ошибок мы можем выдержать прежде чем кэш упадает.
2️⃣ org.gradle.configureondemand. Не совсем кеширование, а скорее мы говорим Gradle, давайка ты не будешь конфигурацию запускать на всем проекте, а только вот на нужных мне модулях. Это будет работать только в том случае, если у вас разработка ведется в небольших sample проектах, т.е когда вы не собираете большой проект со всеми модулями, а только необходимые вам фичи.
Касательно параллелизации конфигурации, Gradle и это умеет поддерживать. Если верить доке, для этого нужно включить этот самый кэш. Вот только что-то на практике не особо ощущается эта параллельность, по ощущениям все также запускается в одном потоке, наверное нужно выполнить еще какие-то требования для этого. Если есть знатоки то плиз подскажите в коментах.
Однако несмотря на то, что Gradle таки научился сохранять кэш проблема с инкрементацией остается актуальной. В примере с кешированием конфигурации, стоит поменять один скрипт, у вас перезапустится вся конфигурация. Это естестенно приводит к тому, что стоит вам переключится на другую ветку, с большой долей вероятности вы опять будете ждать конфигурацию
Значится, Gradle за последние несколько релизов сделал серьезный шаг вперед и научился кешировать конфигурацию.
Причем для кеширования конфигурации, даже есть две опции. Обе кстати до сих пор экспериментальные, но вроде как работают нормально:
1️⃣ org.gradle.unsafe.configuration-cache. Самая рекомендуемая опция, все довольно очевидно, если input для конфигурации не меняется, т.е не меняются скрипты или другие входные данные все будет закешированно и повторно запускаться не будет. Работает хорошо, но для настройки есть потешные параметры вроде того, сколько ошибок мы можем выдержать прежде чем кэш упадает.
2️⃣ org.gradle.configureondemand. Не совсем кеширование, а скорее мы говорим Gradle, давайка ты не будешь конфигурацию запускать на всем проекте, а только вот на нужных мне модулях. Это будет работать только в том случае, если у вас разработка ведется в небольших sample проектах, т.е когда вы не собираете большой проект со всеми модулями, а только необходимые вам фичи.
Касательно параллелизации конфигурации, Gradle и это умеет поддерживать. Если верить доке, для этого нужно включить этот самый кэш. Вот только что-то на практике не особо ощущается эта параллельность, по ощущениям все также запускается в одном потоке, наверное нужно выполнить еще какие-то требования для этого. Если есть знатоки то плиз подскажите в коментах.
Однако несмотря на то, что Gradle таки научился сохранять кэш проблема с инкрементацией остается актуальной. В примере с кешированием конфигурации, стоит поменять один скрипт, у вас перезапустится вся конфигурация. Это естестенно приводит к тому, что стоит вам переключится на другую ветку, с большой долей вероятности вы опять будете ждать конфигурацию
👍21
Про язык я планировал сделать один пост, но он чет разросся, поэтому я сделаю два поста, один про Groovy и уже второй про Kts.
Бывало у вас такое пытаешься ты написать скрипт для Gradle, и во время его написания, будто магия какая-то происходит. В одном месте так нужно писать, в другом по другому.
Вся это магия происходит из-за неудачного выбора Gradle. Смысл в чем, Gradle работает на базе JVM, естественно разработчики хотели для написания скриптов язык который максимально близок к JVM.
Эта близость нужна из-за Maven. В индустрии тогда было принято писать плагины на Java, естественно нужно было сделать мягкий переход для новых пользователей, чтобы откушать часть рынка у Maven.
Помимо этого, язык должен быть скриптовым, не охото компилировать скрипты перед компиляцией проекта.
В момент разработки Gradle таких языков было всего 2: Groovy и Clojure. В расчет не берем всяких JPython и подобных монстров. Clojure не стали использовать так как, он вышел буквально за год до выхода Gradle и его никто не воспринимал всерьез. Плюс, писать скрипты в стиле Lisp не каждый захочет. Остался только один единственный вариант – Groovy.
Groovy отлично подходил под требования. Одновременно и динамический и типизированный, работает на базе JVM и имеет кучу синтаксического сахара. Вот только этот самый сахар и создает ощущение магии для всех, что пытался погрузиться в тему с Gradle. Вот смотрите, на примере, базовая вещь, как можно указать Gradle где находятся сорцы:
1️⃣
2️⃣
3️⃣
Три способа сделать одно и тоже. Приверженцы дзена Python на этом момент должны были в космос улететь от ярости. И ведь это только один мелкий пример, добавить сюда еще то, что ты каждый раз гадаешь типы входного аргумента и что в Gradle все мутабельное и можно поменять почти все что хочешь в любом месте.
Мы с вами творческие ребята, разумеется иногда это круто, когда у тебя есть несколько способов решить задачу. По себе знаю, что иногда хочется выебнутся и решить типичную задачу новым способом. Это круто, когда это все в меру то почему нет? Однако это относится только к основному проекту. В билд системе напротив, такого точно не должно быть, в ней должен быть только один единственно правильный способ сделать что-то. Это важно по двум причинам:
☝️У разработчиков самой билд системы должна быть возможность проводить оптимизации исходя из того, что есть только один способ что-то сделать. Когда ты точно уверен, что у клиента нет возможности сделать что-то иначе, это развязывает тебе руки в оптимизации.
✌️У разработчиков, которые используют билд систему, не должно быть сложностей в изменении скриптов. С Gradle же ты начинаешь в угадайку играть, перебирая различные варианты скопированные с инета.
Ну что тут можно сказать. Скорее всего в ближайшие годы мы не слезем с иглы Gradle. Поэтому если хотите уменьшить уровень магии и упростить себе жизнь, посвятите часок другой изучению Groovy.
Бывало у вас такое пытаешься ты написать скрипт для Gradle, и во время его написания, будто магия какая-то происходит. В одном месте так нужно писать, в другом по другому.
Вся это магия происходит из-за неудачного выбора Gradle. Смысл в чем, Gradle работает на базе JVM, естественно разработчики хотели для написания скриптов язык который максимально близок к JVM.
Эта близость нужна из-за Maven. В индустрии тогда было принято писать плагины на Java, естественно нужно было сделать мягкий переход для новых пользователей, чтобы откушать часть рынка у Maven.
Помимо этого, язык должен быть скриптовым, не охото компилировать скрипты перед компиляцией проекта.
В момент разработки Gradle таких языков было всего 2: Groovy и Clojure. В расчет не берем всяких JPython и подобных монстров. Clojure не стали использовать так как, он вышел буквально за год до выхода Gradle и его никто не воспринимал всерьез. Плюс, писать скрипты в стиле Lisp не каждый захочет. Остался только один единственный вариант – Groovy.
Groovy отлично подходил под требования. Одновременно и динамический и типизированный, работает на базе JVM и имеет кучу синтаксического сахара. Вот только этот самый сахар и создает ощущение магии для всех, что пытался погрузиться в тему с Gradle. Вот смотрите, на примере, базовая вещь, как можно указать Gradle где находятся сорцы:
1️⃣
sourceSets {
main {
java {
srcDirs('src')
}
}
}
2️⃣
sourceSets.main.java.srcDirs = [‘src’]
3️⃣
sourceSets.main.java.srcDirs('src')
Три способа сделать одно и тоже. Приверженцы дзена Python на этом момент должны были в космос улететь от ярости. И ведь это только один мелкий пример, добавить сюда еще то, что ты каждый раз гадаешь типы входного аргумента и что в Gradle все мутабельное и можно поменять почти все что хочешь в любом месте.
Мы с вами творческие ребята, разумеется иногда это круто, когда у тебя есть несколько способов решить задачу. По себе знаю, что иногда хочется выебнутся и решить типичную задачу новым способом. Это круто, когда это все в меру то почему нет? Однако это относится только к основному проекту. В билд системе напротив, такого точно не должно быть, в ней должен быть только один единственно правильный способ сделать что-то. Это важно по двум причинам:
☝️У разработчиков самой билд системы должна быть возможность проводить оптимизации исходя из того, что есть только один способ что-то сделать. Когда ты точно уверен, что у клиента нет возможности сделать что-то иначе, это развязывает тебе руки в оптимизации.
✌️У разработчиков, которые используют билд систему, не должно быть сложностей в изменении скриптов. С Gradle же ты начинаешь в угадайку играть, перебирая различные варианты скопированные с инета.
Ну что тут можно сказать. Скорее всего в ближайшие годы мы не слезем с иглы Gradle. Поэтому если хотите уменьшить уровень магии и упростить себе жизнь, посвятите часок другой изучению Groovy.
🔥22👍6❤4
🎩Kts
На фоне всех этих претензий к Groovy, ребята из JetBrains решили, что стоит упростить всем жизнь и заменить неудобный и неочевидный Groovy на всеми любимый Kotlin. Kotlin благодаря своему синтаксису практически один в один позволяет писать скрипты в том же стиле, что и на Groovy. При этом у нас есть статическая типизация и более адекватная поддержка со стороны IDE.
С одной стороны это круто, теперь все действительно очевиднее, так как сложно на Kotlin написать что-то неожиданное. Однако, Kotlin ни разу не скриптовый язык и за все ништяки мы платим тем, что теперь скрипты перед выполнением компилируются.
Если у вас мелкий проект, разницы вы не заметите. Однако при росте проекта у вас начнутся проблемы. Смотрим на табличку, тест проводили на довольно свежей версии Gradle. Тут явно прослеживается проблема kts, при первом запуске он медленнее Groovy в 1.6 раз. Если конфигурация проекта занимает допустим 30 секунд, теперь будет занимать под 50. Разумеется билд кеш, спасает, однако при переключении с ветку на ветку…
Это все вытекает из фундаментального ограничения Gradle про которое я упоминал ранее. Gradle не умеет в инкрементальность конфигурации, из-за этого внедрение kts решает одну проблему, но создает другую. Разумеется статическая типизация намного круче, лучше интеграция с IDE, не нужно гадать типы. Однако чтобы это работало, билд система должна уметь рассчитывать изменения и запускать компиляцию только там, где это нужно.
Отсюда выходит один интересный вывод, не нужно использовать kts для описания скриптов в модулях. Он хорош для написания конвенций, т.е для тех вещей, которые редко меняются или для плагинов. В обычных скриптах он не сильно много пользы приносит.
В итоге вот вам рецепт:
1️⃣
2️⃣ Общие конфигурации выносим в конвенции и их уже можно писать на kts. Они будут компилироваться только в одном месте, это уже не так сильно будет бить по времени конфигурации.
3️⃣ Если появляется какая-то сложная логика, это лучше выносить в плагины. Сам плагин можно и нужно писать на kotlin, его можно будет сделать бинарным и тогда он вообще не будет компилироваться.
P.S Сори за долгое отсутствие, просто был небольшой отпуск, не волнуйтесь на канал я не забил)
На фоне всех этих претензий к Groovy, ребята из JetBrains решили, что стоит упростить всем жизнь и заменить неудобный и неочевидный Groovy на всеми любимый Kotlin. Kotlin благодаря своему синтаксису практически один в один позволяет писать скрипты в том же стиле, что и на Groovy. При этом у нас есть статическая типизация и более адекватная поддержка со стороны IDE.
С одной стороны это круто, теперь все действительно очевиднее, так как сложно на Kotlin написать что-то неожиданное. Однако, Kotlin ни разу не скриптовый язык и за все ништяки мы платим тем, что теперь скрипты перед выполнением компилируются.
Если у вас мелкий проект, разницы вы не заметите. Однако при росте проекта у вас начнутся проблемы. Смотрим на табличку, тест проводили на довольно свежей версии Gradle. Тут явно прослеживается проблема kts, при первом запуске он медленнее Groovy в 1.6 раз. Если конфигурация проекта занимает допустим 30 секунд, теперь будет занимать под 50. Разумеется билд кеш, спасает, однако при переключении с ветку на ветку…
Это все вытекает из фундаментального ограничения Gradle про которое я упоминал ранее. Gradle не умеет в инкрементальность конфигурации, из-за этого внедрение kts решает одну проблему, но создает другую. Разумеется статическая типизация намного круче, лучше интеграция с IDE, не нужно гадать типы. Однако чтобы это работало, билд система должна уметь рассчитывать изменения и запускать компиляцию только там, где это нужно.
Отсюда выходит один интересный вывод, не нужно использовать kts для описания скриптов в модулях. Он хорош для написания конвенций, т.е для тех вещей, которые редко меняются или для плагинов. В обычных скриптах он не сильно много пользы приносит.
В итоге вот вам рецепт:
1️⃣
build.gradle
лучше писать на groovy, в них нет и не должно быть никакой логики, а только перечисление плагинов, конвенций и зависимостей для конкретного модуля.2️⃣ Общие конфигурации выносим в конвенции и их уже можно писать на kts. Они будут компилироваться только в одном месте, это уже не так сильно будет бить по времени конфигурации.
3️⃣ Если появляется какая-то сложная логика, это лучше выносить в плагины. Сам плагин можно и нужно писать на kotlin, его можно будет сделать бинарным и тогда он вообще не будет компилироваться.
P.S Сори за долгое отсутствие, просто был небольшой отпуск, не волнуйтесь на канал я не забил)
❤32👍15🤔2🤯1
По какой причине все так не любят C++? Основная причина это недостаток дизайна C++, который слишком многое позволяет разработчику. Одни только макросы могут принести неожиданного поведения (реально можно через define заменить все true на false и потом наслаждаться шоу), а в одном из последних стандартов появилась метаклассы, которые буквально позволяют написать свой язык внутри C++.
С одной стороны такая свобода это максимальная гибкость, с другой стороны это возможность кому угодно одной строчкой просто разнести всю кодовую базу. По этой причине все пытаются использовать другие языки если нам не нужна мега производительность. Новичков можно без каких-либо опасений подпускать к кодовой базе на Java.
Это все к чему, Gradle это C++ мира билд систем. В документации Bazel есть интересная фраза про системы вроде Gradle: too much power to engineers and not enough power to the system.
Это пожалуй главная проблема Gradle из которой вырастают все остальные и про которые я писал в прошлых постах. Он как и C++ позволяют разработчику сделать очень многое, тем самым отстрелив ногу по шею. Легко можно взять и пойти в сеть во время конфигурации или файл или вообще по ssh отправить на удаленный сервер какую-то инфу.
Система никогда не знает что происходит внутри Task и никак не может это ограничить. Если вы не использовали Input и Output, то Gradle вообще не особо понимать нужно ли второй раз перезапускать вашу Task, какие данные вам нужны на вход и т.д. Помимо это все Task в Gradle еще и мутабельные в Runtime. Очень просто сделать плагин, который будет выключать таски компиляции во время конфигурации:
Представьте что у вас большой проект с огромной кучей плагинов, каждый из которых уверен, что он самый главный и каждый начинает вносить изменения в граф зависимостей тасок. Ну кошмар же!
Пока проект мелкий и мало скриптов это разумеется ни разу не проблема, однако с ростом масштаба приходится играть в детектива и скрупулезно изучать что делает каждый из плагинов и скриптов по время конфигурации и запуска.
Это проблема очень красиво решается в системах типа Bazel, где Rule (аналог Task в Gradle) вообще ничего не знают ни про других рулов ни про систему вообще. Вы явно должны указать что хотите получить на вход и что выдает ваша рула на выходе. Вы не сможете сходить в сеть или в файл просто потому, что в языке конфигурации отсутствует такая возможность.
Ядро билд системы само решает в каком порядке нужно вызывать Rule и нужно ли их вообще перезапускать. Уже нет проблемы с side-effect потому как их просто невозможно сделать, система ударит вас по рукам.
Gradle развивается и понемногу даже решает эти проблемы. Например, можно использовать механизм Input и Output, чтобы Gradle сам решал, нужно ли вас перезапускать и в каком порядке. Однако никто не запрещает сделать по старинке. Плюс вам же нужно про это найти инфу в документации Gradle которая оставляет желать лучшего.
Подводя итог, хотите разобраться в Gradle по лучше, посмотрите эти три доклада:
👉 Самые базовы вещи
👉 Доклад Зиннатуллина про то, почему в Lift используют Bazel
👉 Также про Gradle, но уже чуть глубже
С одной стороны такая свобода это максимальная гибкость, с другой стороны это возможность кому угодно одной строчкой просто разнести всю кодовую базу. По этой причине все пытаются использовать другие языки если нам не нужна мега производительность. Новичков можно без каких-либо опасений подпускать к кодовой базе на Java.
Это все к чему, Gradle это C++ мира билд систем. В документации Bazel есть интересная фраза про системы вроде Gradle: too much power to engineers and not enough power to the system.
Это пожалуй главная проблема Gradle из которой вырастают все остальные и про которые я писал в прошлых постах. Он как и C++ позволяют разработчику сделать очень многое, тем самым отстрелив ногу по шею. Легко можно взять и пойти в сеть во время конфигурации или файл или вообще по ssh отправить на удаленный сервер какую-то инфу.
Система никогда не знает что происходит внутри Task и никак не может это ограничить. Если вы не использовали Input и Output, то Gradle вообще не особо понимать нужно ли второй раз перезапускать вашу Task, какие данные вам нужны на вход и т.д. Помимо это все Task в Gradle еще и мутабельные в Runtime. Очень просто сделать плагин, который будет выключать таски компиляции во время конфигурации:
tasks.findByPath("assemble")?.enabled = false
Представьте что у вас большой проект с огромной кучей плагинов, каждый из которых уверен, что он самый главный и каждый начинает вносить изменения в граф зависимостей тасок. Ну кошмар же!
Пока проект мелкий и мало скриптов это разумеется ни разу не проблема, однако с ростом масштаба приходится играть в детектива и скрупулезно изучать что делает каждый из плагинов и скриптов по время конфигурации и запуска.
Это проблема очень красиво решается в системах типа Bazel, где Rule (аналог Task в Gradle) вообще ничего не знают ни про других рулов ни про систему вообще. Вы явно должны указать что хотите получить на вход и что выдает ваша рула на выходе. Вы не сможете сходить в сеть или в файл просто потому, что в языке конфигурации отсутствует такая возможность.
Ядро билд системы само решает в каком порядке нужно вызывать Rule и нужно ли их вообще перезапускать. Уже нет проблемы с side-effect потому как их просто невозможно сделать, система ударит вас по рукам.
Gradle развивается и понемногу даже решает эти проблемы. Например, можно использовать механизм Input и Output, чтобы Gradle сам решал, нужно ли вас перезапускать и в каком порядке. Однако никто не запрещает сделать по старинке. Плюс вам же нужно про это найти инфу в документации Gradle которая оставляет желать лучшего.
Подводя итог, хотите разобраться в Gradle по лучше, посмотрите эти три доклада:
👉 Самые базовы вещи
👉 Доклад Зиннатуллина про то, почему в Lift используют Bazel
👉 Также про Gradle, но уже чуть глубже
👍24🔥5❤1