Хабрахабр

«Я был очень негативен по отношению к корутинам»: Артём Зиннатуллин об Android-разработке

Среди Android-разработчиков Артём Зиннатуллин пользуется таким уважением, что про него можно сочинять аналог «фактов о Чаке Норрисе» — что-нибудь такое:

  • Артём так суров, что при его виде гитхаб сам зеленеет (кто из нас может похвастаться таким графиком contributions?)
  • Артём так суров, что для него git — это мессенджер.
  • Артём так суров, что в его приложениях context — это подкаст.

Когда на нашей конференции Mobius мы брали у него интервью, оно предназначалось для онлайн-трансляции. Но увидев, как на него ссылаются в Android-чате, мы решили, что на Хабре оно тоже может многих заинтересовать, и сделали для вас текстовую версию (видеозапись также прилагаем).

В чём недостаток корутин Kotlin? Как жить с проектом на миллион строк кода? Чем разработка в Сан-Франциско отличается от российской? А в чём неправ Google? Под катом — обо всём этом.
Чему был посвящён доклад на Mobius?

Евгений Трифонов: На этом Mobius я пропустил ваш доклад «Android builds at Lyft», но после него в дискуссионной зоне видел толпу желающих задать вопрос. И захотелось уточнить: но ведь большинство зрителей работают не в гигантском проекте вроде Lyft, для них всё равно оказался релевантен этот опыт?

Изначальный план доклада в моей голове, и то, как я в итоге его реализовал, сильно отличаются благодаря вашему классному программному комитету. Артём: Это интересная вещь.

Рассказывал два часа Сергею Боиштяну из программного комитета, он послушал и говорит: «Классно, конечно, но это ты кейноут сделал». Изначально я собирался рассказать, с чего у нас в Lyft все начиналось, почему мы пришли к определённым техническим решениям. И в итоге я понял, что такой доклад, конечно, интересно послушать, но он действительно мало для кого релевантен.

У меня не было цели рассказать, какие инструменты используем конкретно мы. И тогда я переделал его, сместив акцент на принципиальные инженерные подходы к выбору системы сборки, других систем. Хотелось донести именно инженерные практики о том, как делать выбор, и что важно по моему (естественно, субъективному) мнению. Я не хочу, чтобы кто-то взял и слепо начал их использовать, а потом писал мне грозные письма, что не всё работает так, как я рассказывал. Поэтому я надеюсь, что в итоге опыт оказался релевантен большему числу людей, а не просто «чувак из Lyft вышел и что-то рассказал».

Олег olegchir Чирухин: А есть какие-то необычные выборы Lyft, которые другим сделать сложно?

У нас в проекте одновременно две билд-системы, абсолютно никому не рекомендую (смеётся). Артём: Да, конечно.

Но это наше текущее состояние, так исторически сложилось, потому что одна билд-система начала затыкаться на части задач, пришлось заводить вторую. Очень больно поддерживать: постоянно гоняешься за двумя зайцами, в обеих что-то не работает до конца. Я рассказывал о том, как этого избежать и правильно смигрировать на одну из них.

Олег: А что за билд-системы?

Артём: Мы используем Gradle и Buck, а я рассказывал про то, как прийти к Bazel от Google.

Олег: Это какое-то движение в сторону зла: от няшного Gradle к Bazel, в котором даже зависимостей по-нормальному нет.

Ну да, конечно, есть трейд-оффы, и, конечно, у Gradle есть свои достоинства. Артём: Сейчас уже более-менее есть. Некоторым Gradle подойдет больше, чем Buck и Bazel, потому что у них есть принципиальные моменты, по которым они в рамках одного модуля не будут собирать инкрементально, а Gradle будет, и многим это очень важно. Всё зависит от типа проекта. И классно, что Gradle так может.

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

Олег: То есть Bazel только потому, что хочется поддерживать большое количество модулей?

В основном, насколько я понимаю, это изоляция, чтобы не получалось спагетти, которое потом сложно поддерживать. Артём: Скажем так, в нашем случае не «хочется», но разделение проекта на модули позволяет нашему бизнесу двигаться быстрее. У нас почти миллион строк кода. Модули дают больше контроля над тем, какие части кода с какими взаимодействуют. Потому что поверх языка — Java, Kotlin — надо будет что-то накручивать, чтобы запрещать вызовы между пакетами, между которыми никто их не ожидал. Если бы это было в одном модуле, пришлось бы спагеттизировать. Он не будет его параллельно, инкрементально собирать внутри модуля. Плюс там возникнет вопрос, что и Gradle не вывезет такое количество кода в одном модуле.

В нашем случае, мне кажется, это правильное решение, но есть и проблема — в том, что мы на данный момент поддерживаем две системы сборки. У каждого решения есть трейд-оффы.

Олег: А что лучше для сотен модулей: монорепо или много репозиториев?

Наверное, один репозиторий лучше с той точки зрения, что не надо думать про версионирование и нет этого dependency hell, когда ты ходишь и клонируешь десяток репозиториев, чтобы сделать одно изменение, а потом открываешь один pull request и еще десяток после него. Артём: Это очень больной вопрос. Для них возникает атомарность изменений: все закоммичено в один проект, и изменения одного модуля автоматически переносятся в другие без их явного согласия. Из системы убирается «трение», и люди не боятся менять код. При этом все проверки, которые вы автоматически написали на CI, выполнятся и проверят, что код компилируется, тестируется и все вот это.

Олег: А не придёшь ли ты в результате к тому, что у тебя, как в каком-нибудь Chrome, ветки будут меняться по две минуты, пока ты пьёшь чай?

Но тут, наверное, вопрос уже в размере продукта: а нужно ли Chrome держать в себе столько кода? Артём: Да, конечно, есть вероятность. Это, наверное, вопрос к организации проекта. Может быть, стоит выделять какие-то части в отдельные инструменты, которые они будут периодически подтягивать, когда в них будут происходить мажорные изменения? У меня есть похожий: переписка с чуваками из Яндекс.Браузера, где у них тоже большие затыки. Классный пример, кстати.

И зачем тогда графическому интерфейсу знать про движок, каждый раз пересобирать его и думать о том, что исходники должны валяться где-то рядом? Chrome можно разбить на несколько составляющих, и если взять какой-нибудь V8 — я не большой специалист, но насколько я понимаю, он мог бы быть вообще отдельным проектом, правильно? Bazel, кстати, это тоже поддерживает.

Это хитрая ситуация, но, тем не менее, такое работает, это позволяет убрать часть файлов из репозитория, сократить размер. Вообще сейчас все большие системы сборки — что Gradle, что Buck, что Bazel — поддерживают такую вещь, как композитные билды, когда ты ссылаешься, например, на другую Bazel-сборку. IDE, например, с ума сойдёт индексировать все эти файлы, поэтому хочется как-то отделять их от общей составляющей проекта.

Мне кажется, нам можно ещё лет пять спокойно фигачить. Но мы пока далеко от этого. У нас не так много людей. В двухминутный checkout мы пока вряд ли упрёмся.

Евгений: А в Lyft есть ещё своя специфика, помимо двух систем сборки?

Так сложилось, что люди, которые пришли в компанию (из Google, Facebook, отовсюду), ненавидят монорепозитории. Артём: Да, там есть пара нетипичных историй. В итоге у нас в Lyft есть три монорепозитория: Android, iOS и L5 (это наши автономные автомобили).

Так исторически сложилось. А всё остальное — это больше 1500 git-репозиториев: у всех микросервисов, у всех библиотек по отдельному. С другой стороны, при работе с каждым из них у тебя мгновенный git clone, мгновенный git push, всё очень быстро, IDE индексирует за секунду. У этого есть своя огромная цена, которую мы платим: протаскивать сквозь них изменения реально сложно. От чуваков из Сан-Франциско я бы ожидал монорепозитория. Могу сказать, что это действительно интересная часть.

Олег: А когда один из этих отдельных репозиториев обновляется — меняется API, например — как это изменение распространяется на всю остальную компанию?

(смеётся) Ну, я не бэкенд-разработчик в том плане, что я не пишу feature-бэкенды, я пишу инфраструктурные бэкенды — они, как правило, достаточно автономные в этом отношении. Артём: Больно.

Как правило, это просто куча митингов, кросс-взаимодействие и потом планирование.

(смеются) Олег: То есть митинги являются частью системы сборки?

Плюс, к сожалению, исторически сложилось так, что у нас многие из этих микросервисов — это Python, который тоже со своими приколами. Артём: Да, сначала надо собрать митинг, потом собрать репозиторий.

Олег: Проскользнула какая-то нелюбовь к Python.

Python, не Python — без разницы, а вот динамическая типизация — это больная штука. Артём: Скорее нелюбовь к динамической типизации.

Евгений: А ещё проскользнуло «для компании из Сан-Франциско», и любопытно спросить вот что: а чем с точки зрения разработки компании из Сан-Франциско отличаются от компаний из России, есть заметная разница?

Я не большой любитель так классифицировать, но мне кажется, что здесь более правильная инженерная школа. Артём: Очень большая разница.

Олег: Здесь — это где?

Люди уделяют больше внимания техническим аспектам работы компонентов их системы. Артём: В России, в странах бывшего СССР. Им, как правило, абсолютно неважно, что она тормозит или что они используют её неправильно. А в Штатах часто бывает так, что какая-то библиотека решает задачу, и люди даже не смотрят, как она реализована.

Там есть что изменить. Я там очень много собеседую людей, потому что это часть работы, и общий уровень знаний, пожалуй, пока что ниже. В то время как кандидаты из США очень часто могут вообще не отвечать на вопросы или отвечать «Не помню, когда я последний раз это использовал». Каждый раз, когда приходит человек из восточной Европы, на собеседованиях становится интереснее, потому что люди не боятся чему-то воспротивиться, где-то поспорить. Круто, конечно, но на сеньора не тянет. На вопросы вроде «Как работает HTTP-запрос?» или «Какой формат данных ты выберешь?» они не могут дать нормальных инженерных ответов, а говорят: «Ну, я вот это использовал последние лет пять».

Люди делают более массовые продукты, и там попросту больше масштаб. С другой стороны, есть проекты, которые ушли на годы по сравнению с тем, что мы здесь делаем. Это просто масштаб проблем. Например, Chrome или Uber — у них там уже больше тысячи модулей. Возникает вопрос: зачем? Скажем, в Uber под триста Android-разработчиков. Я бы сказал, такие вопросы здесь решаются реже. (смеётся) Но, тем не менее, они умудрились заставить эту махину работать, постоянно релизиться.

У меня есть друг в Яндекс.Картах: Android-приложение делают десять человек. Вот Яндекс — хороший пример. И при этом у Яндекс.Карт больше функционала. В Google, скорее всего, сотня сидит. Вот и разница, на мой взгляд.

Это правда? Евгений: Помимо этого, Долина ассоциируется ещё и со стартапами, а у них подход «move fast and break things», и кажется, что это на разработке тоже должно сказываться: жить на bleeding edge, использовать всё самое новое.

То есть это уже сформировавшаяся компания. Артём: Я не работал в стартапах, Lyft сложно так назвать: там уже тысячи три человек, где-то больше тысячи из них инженеры.

Если технология раскручена, тогда да. Именно cutting edge-технологии используют достаточно редко. Пока про неё на всех конференциях не поговорят, очень мало людей будет её использовать. Если технология нишевая, но крутая — очень часто нет.

Очень часто ты пишешь кому-нибудь в чатике: «Давайте пообедаем вместе у нас или у вас в офисе и решим, продвинем какой-нибудь вопрос», а потом раз — и появляется опенсорс-проект или pull request в другой проект, что-то фиксится. Но при этом что я очень люблю (в Сан-Франциско и частично в Долине) — очень многие вопросы решаются за счёт того, что компании физически близко.

Но так двигается вся Долина, в итоге все понимают, куда двигаются остальные, и вся индустрия идёт вместе. Что интересно: люди очень часто обсуждают вещи, которые вообще-то не должны обсуждать по NDA. И, конечно, там есть прямо хардкорные специалисты по каким-то технологиям. Скажем, мобильщики Lyft и Uber постоянно общаются про технические вещи, потому что мы используем опенсорс из Uber. Это тоже классно: с ними можно просто пересечься.

Вот в Питере была очень классная Java User Group (я уже не знаю, как там сейчас): приходишь после работы, а тебе Шипилёв выносит мозг, и чего-то хорошо! Я такое люблю, и мне этого не хватало в некоторых городах, где я жил.

И вы сидите, спорите, потому что там же сидит какой-нибудь лид Project Reactor или лид Reactive в Spring, идёт прямо горячее обсуждение, и это классно. А там опять такое появляется: например, там тоже есть своя Java User Group, и туда часто приходят чуваки, скажем, из Oracle, которые запилили какой-нибудь новый Reactive JDBC.

Почему всё это написано не на благословенной Джавке, а на каком-то Расте? Олег: Спрошу о другом: я посмотрел на репозиторий Mainframer, и там используется Rust.

То есть хочется быть очень близко к тому, как железо переваривает байты. Артём: Я в последнее время упоролся в сторону того, что программа должна есть минимальное количество ресурсов. Мне очень нравится, что Java сейчас идёт в сторону того, что там будет ещё и ahead-of-time compilation. А в Java очень много всего происходит вокруг (это я даже не говорю про сборку мусора), то есть JIT и всё вот это. Это классно, но у Java есть цена. Мне кажется, будет очень круто, например, начать запуск микросервиса с того, что ты скачиваешь с кэша его ahead-of-time compilation, который изначально произошёл на каких-то других машинах, и стартуешь его очень быстро, без прогревов. Я не могу просто так попросить людей, которые собирают iOS-проект, иметь Java в системе.

Но хотелось переписать его на системном языке, чтобы получить нормальную многопоточность, возможность писать нормальные юнит-тесты, а не просто интеграционные тесты поверх утилиты… Изначально Mainframer был написан на диалекте Bash.

Олег: И можно было бы взять, например, Python.

Но тогда возник бы вопрос с тем, что, во-первых, это динамическая типизация, а, во-вторых… Артём: Да.

Олег: Так в Bash же тоже динамическая типизация.

А кроме этого, есть проблема с тем, что Python сейчас два: в macOS по умолчанию второй, а почти во всех в Линуксах сейчас третий. Артём: Так вот и хотелось переписать. Если мне понадобится какую-то зависимость связать, что я, буду просить людей запустить pip? Возникают всякие такие приколы. Или мне придется её бандлить?

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

Олег: Можно было взять Golang, там хотя бы есть garbage collector.

И заработало. Артём: Вот как раз по этой причине и хотелось попробовать Rust. Плюс в Golang как-то грустновато с дженериками.

Евгений: Раз начали обсуждать языки… В контексте Android-разработки вопрос «Kotlin или Java» уже надоел, но всё-таки задам его для того, чтобы дальше перейти к следующему вопросу.

Артём: Ну, Kotlin, конечно.

Недавно в Kotlin корутины стали stable, и слышны голоса «ура, давайте уйдём от RxJava». Евгений: Теперь тот вопрос, который по-настоящему интересует. Поэтому, когда вижу перед собой человека, которому RxJava очень близка, сразу хочется спросить его мнение о корутинах.

В принципе, до сих пор по большей части негативен, но это отчасти изменил очень долгий разговор с Ромой Елизаровым, который работает над ними. Артём: Я был очень негативен по отношению к корутинам.

Под этим я имею в виду и параллельность, и то, чтобы они использовали правильные API операционных систем для неблокирующих обращений к сети или файлам — с этим в операционных системах очень много проблем, но, тем не менее, такие API есть. Как пользователь программ я хочу, чтобы они были максимально неблокирующими, максимально правильно использовали ресурсы. Мне как пользователю не важно — лишь бы разработчики решили эту проблему так, чтобы им было комфортно. Чем именно это решается? А в этом и есть видение Ромы Елизарова. С этим у меня больших проблем нет. После этого разговора меня как-то попустило.

До этого мне, как и моему другу Артуру Дрёмову, после нескольких лет использования Java в продакшне это казалось шагом назад: код снова становится императивным, нечистым, в нём теряется понимание пайплайна, он снова становится месивом, которое компилятор за тебя превращает в асинхронное месиво.

Мне, если честно, очень страшно на это смотреть. Я не использую корутины, но все примеры, которые я сейчас наблюдаю, перешли к структурированному подходу, когда ты вообще не видишь, какой кусок кода из этого является корутиной. В коде я этого не вижу, потому что корутины сделаны так, чтобы ты этого не видел. Потому что я открываю pull request на GitHub, там вызываются какие-нибудь методы для загрузки картинки и профиля, один из них сходит в сеть, а другой — в локальный SQLite, и вот локальный SQLite спокойно может оказаться блокирующим. Может, это хорошо, но для меня это пока что минус дизайна, потому что в Rx-подходах это очень явно: ты понимаешь, это часть синхронного пайплайна или нет.

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

Легаси-кода куда больше, чем нового. Олег: Дай я немножко пооппонирую. А если у нас есть автокорутины, то мы можем, например, отследить все syscall'ы, автоматически их обернуть и отправить на блокировку туда, на паркинг. И если мы берём какие-то вещи типа работы с сетью, работы с файлами и так далее, то никто не будет по-быстрому переписывать всё это, например, с использованием RxJava.

Но это интересная мысль, да. Артём: Правда, в любом случае придётся вызывать функции из контекста корутин.

Верхнеуровневый API будет на RxJava, а низкоуровневый — на корутинах. Олег: Может, их как-то сочетать?

Но тогда возникает вопрос, потому что на данный момент RxJava может делать всё, что делают корутины, а корутины не могут делать всё, что делает RxJava. Артём: Да, есть сейчас такие подвижки. И поэтому, скорее всего, будут подвижки к тому, что на корутинах будет какой-нибудь кроссплатформенный Rx на Kotlin. То есть первая технология может поглотить вторую, а вторая первую — нет. Это как можно взять forEach и погнали делать какой-нибудь map, а можно взять стримы и написать на них. Но это другое мышление. А с корутинами мы сейчас идём в обратную сторону. И, кажется, даже энтерпрайзное Java-комьюнити уже одобрило и стало писать стримы, потому что это более выразительно.

Насколько я понимаю, для языков вроде Go это основа, изначально часть системы типов, и все API, которые они предоставляют, так и работают: там стандартная библиотека очень сильно использует корутины. И ещё кажется, что для Kotlin корутины — это дополнение. А то, что мы сейчас получим в Java и в Kotlin — это legacy, и ты уже не понимаешь, асинхронный он или не асинхронный. И в твоей, Олег, ситуации получается, что ты пишешь код, который может быть для тебя legacy, но он асинхронный, и это круто. Лучше иметь какое-то понимание, что происходит. Лучше уж одно, чем другое.

Чем больше инструментов, которые больше подходят большему количеству людей, даются им для того, чтобы писать более правильные программы — тем больше я рад. Но, как я и сказал в начале, как пользователь программ я рад. Поэтому здесь у меня абсолютно никаких претензий.

После критики корутин, которыми многие очень довольны, хочется спросить: а в чём главные проблемы современной Android-разработки? Евгений: И последний вопрос, довольно общий. Интересно, окажется ли и это идущим вразрез с чужими мнениями.

Я бы сказал, что в принципе в Android-разработке особых проблем нет. Артём: Интересный вопрос… Сложно ответить. Есть проблема в том, что его сложно масштабировать на большую команду: людям надо правильно понимать, как этот инструмент используется. Есть очень большое количество инструментария, который можно использовать и получить великолепное качество программы. В этом плане корутины, скорее всего, зайдут лучше. И в этом RxJava очень сильно проигрывает корутинам, потому что её очень легко задействовать абсолютно неправильно: использовать для каких-то маленьких асинхронных вещей и не выражать везде логику в стримах.

Мне стыдно это говорить, но у нас в Lyft iOS-разработчики только в этом году внедряют dependency injection и RxSwift. Мне кажется, здесь интересно сравнить с iOS. Я точно знаю iOS-команды, в которых это не так, давно используют современные подходы, clean, вот это всё. А сейчас подходит 2019-й. Но мне кажется, Android в этом плане — далеко не самая плохая платформа.

Долгое время их позицией было «мы не opinionated, используйте всё, что хотите: вот вам фреймворк, а как вы его используете, нам не особо важно». Пожалуй, единственное, что мне не нравится — что сейчас делает Google. Часть сообщества их долгое время за это пинала — и, на мой взгляд, зря.

Начинаешь расспрашивать, почему — она проигрывает по всем параметрам и RxJava, и корутинам, и чему угодно. Это было золотое время, когда ты мог сказать «RxJava — это решение, потому что...» А сейчас приходят люди и говорят: «Нет, мы будем использовать LiveData». Но, тем не менее, в Google посчитали, что сообществу это важно.

И для них уже третий вопрос то, что этот MVVM абсолютно кривой и неправильный и, на мой взгляд, нарушает все принципы того, каким MVVM должен быть. Многие из сообщества сейчас скажут: «Классно, что Google продвигает MVVM». И многие проекты уже перешли на то, что сейчас рекомендует Google.

Очень часто неправильная архитектура в итоге разлетается по нескольким проектам. Мне кажется, у них нет правильного ощущения, где заканчивается scope проектов.

А какие-нибудь Architecture Components — очень спорный набор вещей, которые уже были реализованы в сообществе пять лет назад. Но при этом есть и очень классные работы: например, Room очень правильно сделан и вообще очень классная библиотека. Вот такие моменты меня напрягают. Зачем Google пришёл так поздно, да ещё и с кривым решением?

Ну, можно тогда сделать это в комментариях. Евгений: Подозреваю, что к этому месту у многих возникло жгучее желание возразить. Спасибо за ответы!

Артём: Очень интересные вопросы.

Сейчас его программа ещё не оглашена, зато это самый выгодный момент для покупки билета: уже 1 февраля цены на билеты вырастут. Следующий Mobius состоится в Петербурге 22-23 мая. Вся информация о конференции и билеты — на сайте. А также, раз программа пока не завершена, момент подходит ещё и для того, чтобы самому в неё попасть: мы вовсю принимаем заявки на доклады.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть