Среди 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 (я уже не знаю, как там сейчас): приходишь после работы, а тебе Шипилёв выносит
А там опять такое появляется: например, там тоже есть своя Java User Group, и туда часто приходят чуваки, скажем, из Oracle, которые запилили какой-нибудь новый Reactive JDBC. И вы сидите, спорите, потому что там же сидит какой-нибудь лид Project Reactor или лид Reactive в Spring, идёт прямо горячее обсуждение, и это классно.
Олег: Спрошу о другом: я посмотрел на репозиторий Mainframer, и там используется Rust. Почему всё это написано не на благословенной Джавке, а на каком-то Расте?
Артём: Я в последнее время упоролся в сторону того, что программа должна есть минимальное количество ресурсов. То есть хочется быть очень близко к тому, как железо переваривает байты. А в Java очень много всего происходит вокруг (это я даже не говорю про сборку мусора), то есть JIT и всё вот это. Мне очень нравится, что Java сейчас идёт в сторону того, что там будет ещё и ahead-of-time compilation. Мне кажется, будет очень круто, например, начать запуск микросервиса с того, что ты скачиваешь с кэша его ahead-of-time compilation, который изначально произошёл на каких-то других машинах, и стартуешь его очень быстро, без прогревов. Это классно, но у Java есть цена. Я не могу просто так попросить людей, которые собирают 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-подходах это очень явно: ты понимаешь, это часть синхронного пайплайна или нет.
Пожалуй, это единственная моя претензия к корутинам: я хочу видеть, когда у меня происходит асинхронность, а когда нет. В идеале я хочу, чтобы люди писали более функциональный код, когда есть маленькие переиспользуемые или хотя бы тестируемые кусочки, которые комбинируются с другими. А мы обратно приходим к тому, что инлайним это всё в логику, а компилятор потом это просто перешинковывает.
Олег: Дай я немножко пооппонирую. Легаси-кода куда больше, чем нового. И если мы берём какие-то вещи типа работы с сетью, работы с файлами и так далее, то никто не будет по-быстрому переписывать всё это, например, с использованием RxJava. А если у нас есть автокорутины, то мы можем, например, отследить все syscall'ы, автоматически их обернуть и отправить на блокировку туда, на паркинг.
Артём: Правда, в любом случае придётся вызывать функции из контекста корутин. Но это интересная мысль, да.
Олег: Может, их как-то сочетать? Верхнеуровневый API будет на RxJava, а низкоуровневый — на корутинах.
Артём: Да, есть сейчас такие подвижки. Но тогда возникает вопрос, потому что на данный момент RxJava может делать всё, что делают корутины, а корутины не могут делать всё, что делает RxJava. То есть первая технология может поглотить вторую, а вторая первую — нет. И поэтому, скорее всего, будут подвижки к тому, что на корутинах будет какой-нибудь кроссплатформенный Rx на Kotlin. Но это другое
И ещё кажется, что для Kotlin корутины — это дополнение. Насколько я понимаю, для языков вроде Go это основа, изначально часть системы типов, и все API, которые они предоставляют, так и работают: там стандартная библиотека очень сильно использует корутины. И в твоей, Олег, ситуации получается, что ты пишешь код, который может быть для тебя legacy, но он асинхронный, и это круто. А то, что мы сейчас получим в Java и в Kotlin — это legacy, и ты уже не понимаешь, асинхронный он или не асинхронный. Лучше уж одно, чем другое. Лучше иметь какое-то понимание, что происходит.
Но, как я и сказал в начале, как пользователь программ я рад. Чем больше инструментов, которые больше подходят большему количеству людей, даются им для того, чтобы писать более правильные программы — тем больше я рад. Поэтому здесь у меня абсолютно никаких претензий.
Евгений: И последний вопрос, довольно общий. После критики корутин, которыми многие очень довольны, хочется спросить: а в чём главные проблемы современной Android-разработки? Интересно, окажется ли и это идущим вразрез с чужими мнениями.
Артём: Интересный вопрос… Сложно ответить. Я бы сказал, что в принципе в Android-разработке особых проблем нет. Есть очень большое количество инструментария, который можно использовать и получить великолепное качество программы. Есть проблема в том, что его сложно масштабировать на большую команду: людям надо правильно понимать, как этот инструмент используется. И в этом RxJava очень сильно проигрывает корутинам, потому что её очень легко задействовать абсолютно неправильно: использовать для каких-то маленьких асинхронных вещей и не выражать везде логику в стримах. В этом плане корутины, скорее всего, зайдут лучше.
Мне кажется, здесь интересно сравнить с iOS. Мне стыдно это говорить, но у нас в Lyft iOS-разработчики только в этом году внедряют dependency injection и RxSwift. А сейчас подходит 2019-й. Я точно знаю iOS-команды, в которых это не так, давно используют современные подходы, clean, вот это всё. Но мне кажется, Android в этом плане — далеко не самая плохая платформа.
Пожалуй, единственное, что мне не нравится — что сейчас делает Google. Долгое время их позицией было «мы не opinionated, используйте всё, что хотите: вот вам фреймворк, а как вы его используете, нам не особо важно». Часть сообщества их долгое время за это пинала — и, на мой взгляд, зря.
Это было золотое время, когда ты мог сказать «RxJava — это решение, потому что...» А сейчас приходят люди и говорят: «Нет, мы будем использовать LiveData». Начинаешь расспрашивать, почему — она проигрывает по всем параметрам и RxJava, и корутинам, и чему угодно. Но, тем не менее, в Google посчитали, что сообществу это важно.
Многие из сообщества сейчас скажут: «Классно, что Google продвигает MVVM». И для них уже третий вопрос то, что этот MVVM абсолютно кривой и неправильный и, на мой взгляд, нарушает все принципы того, каким MVVM должен быть. И многие проекты уже перешли на то, что сейчас рекомендует Google.
Мне кажется, у них нет правильного ощущения, где заканчивается scope проектов. Очень часто неправильная архитектура в итоге разлетается по нескольким проектам.
Но при этом есть и очень классные работы: например, Room очень правильно сделан и вообще очень классная библиотека. А какие-нибудь Architecture Components — очень спорный набор вещей, которые уже были реализованы в сообществе пять лет назад. Зачем Google пришёл так поздно, да ещё и с кривым решением? Вот такие моменты меня напрягают.
Евгений: Подозреваю, что к этому месту у многих возникло жгучее желание возразить. Ну, можно тогда сделать это в комментариях. Спасибо за ответы!
Артём: Очень интересные вопросы.
Следующий Mobius состоится в Петербурге 22-23 мая. Сейчас его программа ещё не оглашена, зато это самый выгодный момент для покупки билета: уже 1 февраля цены на билеты вырастут. А также, раз программа пока не завершена, момент подходит ещё и для того, чтобы самому в неё попасть: мы вовсю принимаем заявки на доклады. Вся информация о конференции и билеты — на сайте.
Автор: Евгений Трифонов