Автор этой лекции — Константин Заикин kzaikin, руководитель группы разработки Яндекс.Браузера для Android в Санкт-Петербурге. Он рассказал об инструментах Android-разработчика и всей команды, а также о том, как справляться с legacy-кодом, публиковать большой проект вовремя и улучшать качество кода.


— Друзья, привет. Я очень рад, что вас так много сегодня пришло. Я приехал из Питера, в Яндексе работаю около шести лет. Успел засветиться в Картах, Такси, Метрике и Поиске. Уже два года я работаю над Яндекс.Браузером для Android.

Сегодня я расскажу, как мы в браузере обеспечиваем хороший код. Браузер для Android — довольно громоздкая штука. В принципе, ядро в браузере — ядро Chromium, вокруг которого стоит core сервисов Яндекса, все это написано в основном на С++, и вокруг этих двух ядер, вложенных друг в друга, написаны платформенные приложения. Яндекс.Браузер есть на Windows, Mac, Linux, Android и на iOS. В основном я буду рассказывать про код для Android, а конкретно про Java и Kotlin.


В Яндексе 200 мобильных разработчиков, но в одном Браузере — с десктопом и всем остальным — тоже 200 человек. Около 30 человек из них пишет под Android. Народу много, и мы написали большое количество кода. По количеству строк мы самые большие в Яндексе. И естественным образом, как и в любом живом организме, в нашем проекте код рождается, живет, стареет, за ним нужно следить и обеспечивать его хорошее самочувствие.

Расскажу последовательно о том, какие мы применили процессы, чтобы обеспечить качество кода, и какие мы используем инструменты. У любого бага, который возникает, есть несколько моментов, когда его можно решить. Можно баг просто не сделать. Можно баг решить, когда он в открытом окошке редактора у разработчика. Можно найти его на юнит-тестах. Можно найти его позже в тестировании или в продакшене.


Мы постарались выстроить свои процессы так, чтобы на всех этих этапах максимальное количество багов отлавливалось или не возникало вовсе. В первую очередь, мы в браузере записали наш codestyle. В большом проекте реально очень важно, чтобы был писаный codestyle, который актуален, поддерживается, и мы его разместили как codestyle MD в нашем репозитории. Чтобы его отредактировать, нужно сделать pull request, чтобы как обычный код его поревьювили, полайкали, команда согласилась, и после этого все соглашаются с этими правилами. Либо мы обсуждаем и вносим новые изменения.

Codestyle кроме того, что фиксирует общее для всей команды правило, еще и носит образовательную функцию. У нас большая команда, мы очень много людей новеньких к себе приглашаем. Новички в первую очередь читают Вики, у нас очень много описаний, мы любим Вики. И читают codestyle. Там есть примеры того, как нужно делать и как не нужно. После того, как разработчик в соответствии с codestyle написал новую фичу, он ее должен закоммитить. Мы сделали precommit hooks, которые не дадут закоммитить код с отклонениями от принятых правил. Это может быть и codestyle, и какие-то вещи, которые статический анализ легко вылавливает. То есть просто не дать проникнуть коду в коммит, git отказывается принимать такой код.

После этого у нас в обязательном порядке проходит кодревью. Мы пользуемся Bitbucket, у нас есть правило, что любой код может попасть в репозиторий только после апрува от двух коллег. Два живых человека должны прийти, посмотреть на код, полайкать его. И обязательно этот pull request должен собраться и пройти все 100% наших юнит-тестов. А тестов у нас много. И ревьюверы смотрят, и все precommit hooks. У некоторых из вас может возникнуть вопрос, сколько времени это вообще занимает? Большой проект ведь. Я с гордостью могу сказать, что у нас минимальное время от создания pull request до попадания кода в репозиторий сейчас составляет около 30 минут. Это минимальное время, за которое код попадет в наш мастер. Мы к этому шли долго. И у нас обычный Chromium-based браузер. У всех, кто такие браузеры пишет, есть очень большие проблемы со временем сборки. Миллион строк кода, их нужно скомпилировать, они компилируются долго. И 400 тыс. строк Java кода нужно скомпилировать и выполнить тесты. Это результат работы всей команды. Мы все работаем над тем, чтобы наши процессы позволяли нам больше думать о результате, а не о том, как дождаться билдов, локально или на TeamCity.

Что важно, 30 минут — это время на TeamCity, а у локального разработчика билд занимает 30 секунд. Он не прогоняет все юнит-тесты, он не собирает все сборки под все платформы, все комбинации. А на TeamCity мы это собираем. Плюс на TeamCity прогоняются тесты. У нас сейчас от этих 30 минут юнит-тесты занимают около 20. Кроме того, чтобы обеспечить качественный код, мы ввели техническую квоту. 30% времени команды мы тратим на отдачу от того технического долга, который успели накопить, и который пытаемся делать снова и снова. Каждый спринт 30% команды занимаются рефакторингами, улучшением существующего кода, написанием юнит-тестов, которых не было, либо улучшением инструментов.

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

Для всего, что вы назвали, у нас есть инструменты. Мы умеем измерять, что у нас в проекте сейчас, как было год назад и что происходит сейчас. Мы в первую очередь меряем самочувствие приложения в продакшене, мы используем AppMetrica, это общедоступный сервис, который позволяет следить за событиями, за крешами, за самочувствием приложения. Мы с помощью AppMetrica собираем много всего, технические показатели.

Затем при помощи ClickHouse и наших собственных самописных инструментов делаем отчеты из этих данных. У нас их чуть больше, чем у обычного пользователя AppMetrica, только за счет того, что мы вложились в доступ к этой базе и делаем специальные запросы. AppMetrica позволяет собрать факт события. Произошло событие — столько-то штук. Но AppMetrica не позволяет посмотреть на численные данные. Например, нам нужно измерить энергопотребление в каких-то попугаях. И нужно понять, как в текущей версии у разных пользователей сколько этих попугаев получилось. Как выглядит распределение пользователей по энергопотреблению. Как выглядит распределение пользователей по времени старта. Как выглядит распределение пользователей по скорости скроллла в FPS или во времени между кадрами. И для сбора таких численных данных, чтобы их анализировать, мы использовали гистограммы.

Про них очень хорошо рассказал мой коллега, находятся в Яндексе по слову «гистограммы», первое же видео — рассказ коллеги из моей команды про то, как мы собираем численные показатели и их анализируем. Тот же Илья Богин сможет подробно рассказать, как эти численные показатели собирать. И затем у нас есть очевидный показатель — открытые баги. Есть продукт, есть тестирование, есть пользователи. Они репортят баги. Чем больше багов, тем хуже продукт. Чем больше блокер-багов, тем хуже продукт. Блокер-баги сравнить с минорами… У нас отдельная метрика показывает качество продукта в багах, zero bug policy, условно.


Это скриншот из реального нашего инструмента, в котором собирается количество крешей на миллион сессий. Не очень хорошо видно, но нижний график — это количество крешей в Java, около 200 на миллион сессий. А верхние два… Сначала креши в нативе, их довольно много, потому что у нас Chromium-ядро и там очень много «плюсов». Самый верхний — сумма. В целом, мы контролируем этот показатель и работаем над его улучшением. Чем меньше график, тем лучше. Это за последний год, мы работаем и улучшаем.

То же самое, такие же графики у нас есть для энергопотребления, для скорости скроллла и всего остального.


Кроме того, мы внедрили и очень активно используем такую штуку, SonarQube, ее советую всем. Ее вы можете поставить себе, будь вы инди-разработчиком или в составе команды. Это платформа, на которую можно наворачивать любые статические анализаторы кода. Он опенсорсный, бесплатный и умеет анализировать почти все языки программирования, которые бывают. Мы его внедрили, и сейчас постоянно с его помощью следим за какими-то показателями здоровья нашего проекта. Коротко пройдусь по тому, что на этом слайде есть. Это очень важно, об этом в документации как бы написано, но не очень очевидно.

Это реальный дашборд, скриншот. У нас такое количество багов, больше тысячи. Есть какое-то количество уязвимостей с точки зрения безопасности. Вот такое тестовое покрытие — 52,7%. И такое количество юнит-тестов. Это круто, кажется. У нас сейчас самое большое покрытие из мобильных приложений Яндекса. Еще мы меряем долю дублированного кода. Что интересно, мы настроили эту штуку для всех мобильных приложений Яндекса и можем смотреть дубли между проектами, тоже очень круто, помогает нам выделять те части кода, которые стоит вынести в библиотечку, чтобы больше не дублировать.

Самая главная метрика, которая это все собирает, это и еще кучу всего, и в какой-то одной понятной цифре говорит, какой у вас проект, хороший или плохой. Это технический долг — у нас он 204 дня, условных рабочих дня условного разработчика в вакууме, который должен поработать, чтобы все эти проблемы были устранены. Как вы думаете, это большой долг или маленький? У нас команда большая, 30 человек. Если мы возьмемся, мы этот долг за ограниченное время отдадим. У нас были эпизоды, когда мы брались и начинали — давайте все блокеры в SonarQube пофиксим. И начинали их фиксить.

Но тут есть одна особенность. Это серебряная пуля. Неважно, сколько у вас всего багов нашел статический анализ в проекте. Важно, чтобы вы новых багов не делали. И фикс бага, который хороший и давно там лежит, точно когда-нибудь случится. Но он не случился, его в тестировании не нашли, его юнит-тесты не нашли. Скорее всего, он не стрельнет. И фикс старого бага может привести к тому, что у вас возникнет новый, настоящий, который приведет к реальному крешу.

Главное — не фиксить старые баги, а следить за тем, чтобы не было новых. Наш дашборде отдельный показатель — у нас нового долга за последние 14 дней накопилось 1 день. Да, мы его делаем, но очень умеренно. У нас показатель technical debt ratio, доля долга к объему всего кода, неуклонно падает. И это лучший показатель того, что мы все делаем правильно.


Вырезки из реальных скриншотов на экране. Этот же самый Sonar позволяет добавлять комментарии в pull requests. У нас приходят два ревьюера и оставляют комментарии, плюс еще приходит робот, третий ревьюер. Он оставляет комментарии по очевидным вопросам, по codestyle, оформлению кода, находит уязвимости с точки зрения безопасности. Люди его любят. Не дает добавить новый долг.

Кроме того, в Sonar мы можем посмотреть существующие баги. Там очень удобный просмотрщик, где мы видим всю кодовую базу, и для каждой строчки можем посмотреть, какой есть там issue, когда он был добавлен и что с ним делали. Можно прийти к автору и сказать: дружок, за последние две недели ты добавил пять блокеров — как так? Разобраться с причинами и устранить. Мы всегда стараемся любую проблему решить в корне, устранить возможность появления такой проблемы. Как правило, мы готовим какие-то инструменты или делаем так, чтобы баги невозможно было допускать.

Мы очень активно пишем тесты. Мы любим тесты. За последнее время очень сильно продвинулись в их написании.


У нас вот такое количество тестов сейчас выполняется при помощи JUnit, это тесты, которые гоняются на хост-машинах, просто Java и Robolectric. И Android Instrumentation Tests. Такое количество у нас сейчас.

Пока мы писали это количество тестов, мы прошли несколько этапов. Тесты нужно писать, все их пишут, мы хотим качественный продукт — давайте тоже писать. Мы реально огребли кучу проблем. Мы пишем новые тесты, а старые все время ломаются, требуют много времени на поддержку и багов никаких не находят. Любое изменение кода вызывает поломку десятков сотен тестов, и мы ходим и фиксим тесты. К счастью, сейчас мы этот кризис миновали, сейчас наши тесты действительно обеспечивают и документирование кода, и обеспечивают разработчику уверенность в том, что код написан правильно. Сейчас вся команда тесты писать научилась и пишет их очень активно. На предыдущем слайде видно, что покрытие на новом коде — 55%. Это больше, чем на старом, мы движемся в правильном направлении. Лучше бы еще выше, но пока так.

Мы активно используем Robolectric, мы им довольны, он позволяет нам мокать, заменять собой настоящий Android, но при этом выполнять тесты гораздо быстрее. 9000 тестов — это много. Из них на эмуляторах выполняется около 600, остальные тесты выполняются на хост-машине. Билд-агент у нас быстрый, 16 ядер, на них все быстро и просто. Эмулятор если поднимаем, на нем начинают долго и мучительно выполняться Instrumentation Tests. Этого мы стараемся избежать. Robolectric очень хорош.

Еще мы активно используем интерфейсы. Мы сторонники чистого кода, активно используем inversion of control, dependency injection и при помощи интерфейсов отделяем платформу от того кода, который мы пишем. Есть люди, которые отрицают Robolectric, говорят, что нужны только интерфейсы, и Robolectric — зло. Мы сторонники разумного подхода. Мы активно используем Mockito и намеренно выпилили Powermock, поскольку он провоцировал плохой стиль тестов. Это зло, он позволяет мокать статики, файналы, вообще ломает все вокруг и провоцирует плохую архитектуру.

Главное резюме этого слайда, что мы все документируем — так же, как и любые вещи. Мы записываем в стиле «не делай так». У нас есть test smells, отдельная страничка, где фиксируются типичные проблемы, которые возникают при написании тестов в режиме «так плохо, а так было бы хорошо». И новые разработчики, когда приходят, обычно говорят, что очень помогает эта вещь, потому что там есть не просто примеры, как не нужно делать, и многие смотрят просто на правую часть, как делать нужно. Там есть многие приемы и инструменты, про которые невозможно догадаться, нужно их понять с опытом или узнать с опытом. А тут мы им на блюдечке с голубой каемочкой это все рассказываем.


Этот график говорит сам за себя. Это данные с июня прошлого года до текущего момента. Мы работали над тестовым покрытием, писали тесты. Но важнее, что мы не стояли на месте, мы росли.

Кроме юнит-тестов мы используем автотесты. Кто не знает и не использует, это условное название для тестов, которые тестируют приложение как черный ящик. Запускают его на девайсе или эмуляторе, и условный робот своим резиновым пальцем скроллит списки, тыкает кнопки, пытается сломать ваше приложение по стандартным сценариям. Автотестами мы заменяем ручных тестировщиков, живых людей, которым лучше заниматься чем-то более осмысленным, чем повторять снова и снова сценарии. Мы ускоряем свою регрессию, проверку того, что новый релиз хорош и может ехать в продакшен. Сейчас мы раз в три недели релизимся и немалая часть этой заслуги в том, что мы перевели ручные тесты на автотесты. У нас больше 60% тестов автоматизированы.


Автотесты у нас написаны на Appium, это довольно стандартный для Яндекса инструмент, он к нам пришел из тестирования веба вместе с людьми, которые тестировали веб. Сейчас мы на Appium написали около 3000 тестов, развернули инфраструктуру, которая позволяет каждый раз запускать 25 эмуляторов, они поднимаются, новые и чистые, на них в параллель запускаются и гоняются тесты. Но все равно все получается очень медленно. У нас запуск регрессии занимает сейчас несколько часов, и мы не можем это делать постоянно, на каждый билд. У нас не хватит ресурсов и железа, чтобы это все выполнять. И как раз эти инструменты мы очень активно контрибьютим в open source, на GitHub в Yandex QA Tools есть много инструментов, есть активное комьюнити, где мы все это выкладываем.

Вот еще одна вещь, которую мы, можно сказать, выстрадали. Изначально автотесты писали отдельные люди, не совсем разработчики. Есть ручники, их нужно автоматизировать. Первая реакция — давайте ручника заменим роботом, пускай он пишет, и отдельно от браузера поднимается репозиторий, куда пишут код. Вроде ребята пишут код на Java, тот же JUnit, вроде тестировщики. Но мы в итоге объединили команды, слились воедино, и сейчас у нас автотесты может писать любой разработчик. Мы специально ротируем людей, чтобы все знали, как автотесты пишутся, чтобы знали, как чинить автотесты, когда мы их внезапно поломали. В автотестах главная боль, что они все время отваливаются и ломаются, что они все время красные.


Покажу один из способов решения этой проблемы. Это телевизор из моего кабинета, один из них. У нас вообще любят телевизоры с графиками. Но это телевизор про автотесты. Зеленые — успешно пройденные, желтые — зафейленные по известной нам причине, красные — по неизвестной. Мы все время за этим смотрим. Выделили отдельного дежурного, который все время меняется, который следит за тем, что с тестами происходит. Это очень важно — следить, чтобы тесты, которые отваливаются, не отгнивали. Если сразу не взяться за тест и не найти причину, то в итоге этот тест умрет и усилия, потраченные на его разработку, вы выкинете. Поэтому мы постоянно следим и знаем, почему зафейлился каждый тест. Указанные тесты у нас реально находят регрессии, проблемы, которые возникают в продакшен-коде. Мы не даем коду докатиться до продакшена, находим все на автотестах, не даем ручникам это посмотреть.


Еще мы активно используем Espresso. И сейчас мы считаем, что тесты на Espresso гораздо важнее, чем тесты на Appium, потому что на Espresso писать тесты быстрее. Они быстрее в поддержке и быстрее выполняются, но им тоже нужно много быстрых эмуляторов. Инфраструктура — главная проблема. Тесты на Espresso мы гоняем на эмуляторах. Раньше мы гоняли их на железных девайсах, но это действительно проблема. У нас до сих пор стоят шкафы, где десятки телефонов лежат и моргают экранами. Все время с ними что-то происходит. Поэтому во всех тестах мы ушли от девайсов на эмуляторы и очень этому рады. Если будете поднимать такую инфраструктуру, советую сразу смотреть только на эмуляторы.

В тестах, именно в Espresso, мы начали активно использовать Kotlin, на котором написали очень удобный DSL, описывающий нашу систему, наш браузер, и позволяющий очень быстро реализовывать новые тесты. Потому что все шаги готовы, и ты словно из кубиков все собираешь и делаешь новый тест.

Качество — не разовое мероприятие. Нельзя взять и сделать качественно. Качество — это процесс.


Чтобы добиться качества, мы модифицировали процессы. Добавили роботов, которые проверяют все, что делают люди. Людей мы просим делать все одинаково. Мы контролируем то, что у нас происходит с продуктом, с нашим кодом, пишем много тестов и постоянно их все запускаем. Все юнит-тесты запускаются всегда. И мы пишем много автотестов, которые позволяют разгрузить ручных тестировщиков и раньше находить баги.

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

Комментарии (12)


  1. se_pavel
    27.11.2017 16:34

    А как вы осуществляете переход на новое ядро Chromium при таком объеме кода?


    1. kzaikin
      28.11.2017 08:21

      Регулярно проводим мёрж со стабилизацией после него. Про это нужен отдельный БОЛЬШОЙ рассказ. Война войной, а мёрж по расписанию.


  1. alek0585
    27.11.2017 18:47

    А мониторинг тестов у вас есть?
    Я встречал в своей практике ассерт на true, который разумеется всегда срабатывал. Ревью коллег, которые пишут такие же ассерты, может не помочь.


    1. TITnet
      27.11.2017 20:08

      Для таких тестов можно использовать мутационное тестирование, которое поможет выявить accert на true.


      1. kzaikin
        28.11.2017 08:25

        PIT мы пробовали прикрутить, но пока не взлетело. Тесты ведь проходят кодревью как обычный код. Статические анализаторы часть багов и тупняков в тестах подсвечивают, ревьюверы при некотором опыте их тоже видят.


  1. Hixon10
    27.11.2017 21:22

    Я с гордостью могу сказать, что у нас время от создания pull request до попадания кода в репозиторий сейчас составляет около 30 минут.


    Привет! Не очень понятно, если у вас 30 минут — это время сборки, то как за 30 минут вы успеваете всё это вмёрджить? Я имею ввиду, чтобы 2 разрабочтика провели ревью кода, нужно довольно много часов, наверное (пока программисты найдут у себя время, пока скажут своим замечания, пока код будет поправлен). Или я что-то упускаю?


    1. kzaikin
      27.11.2017 23:15
      +1

      Это минимальное время. Сборка, тесты, статический анализ. Меньше этого времени не получится.
      Пока идёт сборка, тесты и анализ, ревьюверы могут успеть посмотреть ПР, если изменения компактные, а ревьюверы оперативны.


  1. Error1024
    28.11.2017 03:40

    Главное — не фиксить старые баги, а следить за тем, чтобы не было новых

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


    1. kzaikin
      28.11.2017 08:29

      Вы вырываете фразу из контекста. Речь шла про баги в старом коде, найденные статическими анализаторами. Обычный пользователь такие "баги" не увидит никогда. При этом чинить их просто вредно, т.к. можно сделать настоящие баги вместо мнимых.


      1. Zloboglaz
        28.11.2017 10:20

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


        1. kzaikin
          29.11.2017 09:01

          Попробуйте прогнать статический анализ кода на своем проекте. Если раньше не гоняли и не исправлял, вам найдут 100500 проблем, большая часть которых будет False Positive по разным причинам. Их чинить не надо. True Positive же разделятся по важности, от неизбежно приводящих к ошибкам и крешам (blocker) до предложений по улучшению кодстайла. Блокеры мы обязательно все исследуем и чиним. Не блокеры почти всегда чинить нецелесообразно, это будут изменения ради изменений. Люди не любят таким заниматься


  1. ssergs
    28.11.2017 14:39

    Поправьте ссылочку на Yandex QA Tools. Спасибо