Разработка
Работает — не трогай: как Snapchat переписал свое приложение для Android
Не каждый проект требует переписывания, и мы используем инкрементальный подход к нашим инициативам, когда это возможно. Но нет причин бояться переписывания, если это лучшее решение проблемы.
Год назад команда Snapchat Engineering выпустила новую версию приложения для Android, переписанную с нуля, что сделало ее более производительной и менее подверженной ошибкам. Переписывание Snapchat для Android было сложным и пугающим процессом. Но для нас было важно создать лучшее приложение для нашего глобального сообщества, и мы рады, что сделали это.
Переписывание приложения может иметь положительные результаты, но это сложный процесс. Чтобы разработать нашу стратегию, перед началом работы мы тщательно исследовали производительность нашего приложения. После того, как мы начали, потребовалось огромное количество координации и поддержки, чтобы сохранить проект в нужном русле, и мы с самого начала использовали и тестировали наше новое приложение, чтобы поддерживать высокое качество. В этом посте мы расскажем о некоторых процессах принятия решений и уроках, извлеченных из нашего опыта.
Проблема
Основной мотивацией для нашего переписывания было улучшение производительности приложения. Snapchat — приложение камеры, оптимизированное для захвата мимолетных моментов. Если приложение загружается слишком долго, момент может быть потерян. Как и многие приложения, которые развивались быстрыми темпами, Snapchat превратился в своей сложности в новое проблемное пространство, которым он изначально, конечно, не был задумывался и не создавался.
Функции в нашей кодовой базе были тесно связаны таким образом, что это наносило ущерб гибкости. Особенно проблематичным было отсутствие структурированного способа инициализации и планирования работ. Большая часть нашего кода и данных загружались сразу при старте с помощью функций, которые не были частью запуска, их было трудно понять или отследить. При старте приложения выполнялось так много задач, что приложению требовалось 30-60 секунд на окончательную загрузку и огромный объем памяти для работы в течении всей сессии.
Прежде чем преступить к переписыванию, мы попытались постепенно упростить нашу существующую кодовую базу. Прогресс был медленным, и каждый шаг вызывал непреднамеренные побочные эффекты, такие как тупики или ошибки в специфических случаях. В качестве примера — мы выделили приоритет по скорости загрузки для камеры, отложив всю остальную работу до тех пор, пока камера не будет готова к съемке. Но это привело к тому, что в видео Snap пропускались кадры, так как во время записи тормозились все отложенной задачи. Работа с производительностью старого приложения выглядела как игра в перетягивание каната, где высвобождение ресурсов для повышения производительности одной функции обязательно вредило другой.
Размышляя о будущем, мы хотели приложение, которое бы:
- Будет быстрым. Приложение будет загружаться мгновенно и ощущаться быстрым.
- С быстрыми итерациями. Приложение должно быть простым в разработке.
- Устойчивым. Даже если мы добавим новые функции, приложение останется быстрым в использовании и разработке.
Решение переписать
При рассмотрении вопроса о переписывании приложения в отрасли существует довольно распространенная мудрость — не делайте этого! Мысль о том, что мы можем перестроить 5 лет работы и улучшить ее в короткие сроки, внушала оптимизм. Однако мы неизбежно сделаем новые ошибки, исправление которых может занять много времени. Даже если мы справимся, кто сможет гарантировать, что после этого система не вернется к старому состоянию? Будет много неудовлетворенного спроса на запуск новых функций после переписывания, а быстрый запуск новых функций — это то, что создало наши проблемы в первую очередь.
Мы продвинулись с переписыванием, потому что полагали, что это будет быстрее и менее рискованно, чем инкрементный рефакторинг. Существующее приложение было сложным и взаимосвязанным. Мы полагали, что проблемы, вызванные переписыванием, будет легче найти и исправить, чем те, которые вызваны нынешней сложной системой. Наше руководство полностью поддержало это решение, предоставив нам возможность распланировать успешную переделку.
Чтобы решить некоторые из предстоящих задач, мы приняли следующие стратегии, которые мы обсудим более подробно далее:
- Есть некоторые основные правила. Мы хотели исправить проблему с производительностью. Было важно, чтобы у нас был план, как решить эту проблему заранее, вместо того, чтобы просто надеяться, что переписывание сделает все лучше.
- Фокус. Переписывать код — это весело, но чтобы переписать его в разумные сроки, было важно, чтобы мы сузили сферу его применения и хорошо работали вместе.
- Принять стратегию MVP. Поскольку трудно протестировать переписывание непосредственно в нашем сообществе, мы решили использовать его сами и поддерживать высокое качество с самого начала.
Основные правила
Snapchat состоит из множества небольших связанных приложений, включая камеру, чат, память, редактирование фотографий, потребление контента и карту. Он открывается с камерой, которая требует значительных ресурсов как памяти, так и процессора, и включает в себя AR-линзы и много тяжелого медиа-контента. Объединение этих функций вместе в одном приложении создает привлекательный пользовательский опыт, но представляет собой сложную инженерную задачу.
В операционной системе Android пользователи могут иметь на своем телефоне много разных приложений, и каждое приложение может быстро загружаться и работать без сбоев в изоляции. Мы начали рассматривать наше Android-приложение как мини-ОС, а наши функции — как мини-приложения, работающие внутри этой ОС. Если бы каждое мини-приложение можно было бы быстро загружать отдельно, то можно было бы объединить их, сохраняя высокую производительность без необходимости предварительной загрузки функций при запуске.
В качестве подтверждения концепции мы создали автономные приложения-прототипы для экранов Friends и Discover. Затем мы сделали мгновенную загрузку каждого из этих экранов, настроив архитектуру объектов так, чтобы иметь готовые для рендеринга схему базы данных, инкрементную загрузку данных и иерархию плоского представления. После мы реализовали отдельную страницу камеры и небольшой слой мини-ОС (который мы называем «платформа приложений»), чтобы связать их вместе. Наш прототип доказал, что наше решение будет работать, так как приложение начинало работать быстро без всякой предварительной загрузки.
Наши основные правила обеспечения такой независимости были просты. Не загружать предварительно, рассматривать каждую функцию как отдельное приложение и делать их быстрыми. Мы обнаружили, что это дало командам хорошее понимание того, какова цель переписывания платформы. Команды получили возможность либо повторно использовать существующий код и адаптировать его к новому приложению, либо начать с нуля новое приложение. Основные правила дали им возможность переосмыслить свой код, и большинство команд решили переписать его заново. Повторно используя предложенные компоненты и шаблоны проектирования, они смогли быстро восстановить свой функционал с высокой производительностью.
Фокус
Переписывать код — это весело, ведь старый код часто выглядит уродливым и трудным для понимания. При этом важно не увлекаться восстановлением большего, чем нужно. Это то, что, как мы думали, может подвергнуть риску наш таймлайну и привести к размыванию целей. Мы хотели, чтобы наше новое приложение было намного лучше, но нам не нужно было решать каждую проблему сразу.
После экспериментов с несколькими идеями и мозгового штурма стало ясно, что мы можем сократить масштаб, отложив работу во многих областях:
- Мы не будем добавлять новые функции. За небольшими исключениями, наш набор функций для Android был заморожен на 6 месяцев.
- Мы не будем менять интерфейс приложения. Это превратило наше переписывание в чисто инженерную проблему, а также позволило нам сравнивать яблоки с яблоками.
- Мы не будем вносить никаких изменений в клиент-серверный протокол, если в этом нет явной необходимости.
- Мы не будем переписывать компоненты нашего приложения, которые уже были изолированы и имеют хорошее качество, просто для адаптации новых языков или библиотек.
- Мы не будем менять наши системы сборки, CI, QA или процессы выпуска.
Например, во время переписывания наше приложение в значительной степени полагалось на JSON для выполнения сетевых запросов. Мы знали, что парсинг JSON неэффективен и дорог, и хотели перейти к более современному решению. Однако это заняло бы больше времени, так как нам нужно было бы изменить наши клиент-серверные протоколы и эндпоинты, а кроме того выполнить тщательную миграцию. Вместо этого мы приняли промежуточное решение, в котором мы представили централизованный API сетевого менеджера, который скрыл использование JSON в качестве основы реализации. Эта модель централизации областей будущего улучшения в виде API стала широко использоваться и дальше.
Другим важным аспектом фокусировки была координация. С того момента, как переписывание вышло из стадии создания прототипа, у нас была дорожная карта, объединяющая много функциональных команд, с внутренними метками для определения того, когда нужные функции будут готовы. Мы постоянно обсуждали препятствия и решали, достаточно ли они важны, чтобы сдвинуть наши сроки. Управление проектом было непрерывным процессом, но у нашего переписывания никогда не было неограниченного таймлайна.
Во время разработки наши руководители часто подчеркивали важность и статус проекта для компании. Это позволило различным командам фокусироваться на работе даже тогда, когда мы преодолевали препятствия и выбивались из графика. Это было необходимо для того, чтобы мы придерживались плана, так проект длился месяцы, и спрос на создание новых функций снова вырос. Сохраняя наше внимание, мы смогли минимизировать время, когда нам пришлось поддерживать два приложения в разработке, а также время, когда мы не смогли реализовывать новые функции. Это также сделало нас честными и позволило нам потратить энергию на решение критически важной проблемы производительности, которая с самого начала была причиной переписывания. Наконец, это укрепило уверенность внутри компании в том, что переписывание будет осуществлено, что было важно, учитывая, сколько людей было посвящено этому.
Менталитет MVP
При создании или перестройке функций сначала слишком легко сосредоточиться на их полноте, и только потом беспокоиться о качестве. Учитывая объем функций, которые мы внедряли, изменение этого порядка было жизненно важным для нашего успеха.
С самого начала нашего процесса разработки мы стремились создать приложение, которое было бы стабильным в своей основе: общения с друзьями, просмотра историй и использования камеры. Менее чем через 2 месяца после переписывания многие сотрудники Snap начали переключаться на новое приложение, возвращаясь к старому только для доступа к отсутствующим функциям. Любые ошибки или ухудшения производительности, появившиеся в нашем новом приложении, обрабатывались так, как если бы они были ошибками на продакшене, и немедленно устранялись.
Первая версия приложения, которую мы использовали, позволяла отправлять только фото и видео с простыми текстовыми надписями, а также отправлять/получать простые текстовые сообщения без эффектов, таких как стикеры. Это позволило нам позже отследить многие ухудшения производительности, которые появились, когда мы добавили дополнительную функциональность в коммуникационный поток. Выявление этих регрессий на ранних этапах дало нам время углубиться в коренные причины и инвестировать в лучшие стратегии тестирования и шаблоны проектирования, чтобы снизить вероятность возникновения подобных проблем в будущем. Если бы мы подождали, пока функции не будут полностью реализованы, чтобы начать использовать приложение, возможно, было бы гораздо труднее их найти и решить.
Запуск
Как только у нас был готов достаточный набор функций, мы начали тестировать приложение на новых пользователях. Мы внимательно слушали клиентов и узнали много нового о том, какие функции они ценят больше всего в нашем приложении. Полученные знания позволили нам скорректировать наш план переписывания по ходу работы.
Наше новое приложение намного более производительное. Мы смогли уменьшить медленный холодный старт и возникновение ошибок ANR на 60%, замороженные кадры на 45% и размер APK на 25 Мб. Это также заложило хорошую основу для дальнейшего улучшения производительности. Каждая исправленная ошибка теперь рассматривается как возможность добавить новый тест, новую метрику или новый тест производительности, чтобы уменьшить вероятность регрессии. Начав с нуля и приняв новое мышление, опыт запуска нашего переписанного приложения помог укрепить внутреннюю новую инженерную культуру, которая ценит мастерство, производительность и отличные инженерные практики.
Хотя наша переделка была разовым проектом, мы продолжаем реорганизовывать нашу кодовую базу и поддерживать ее в актуальном состоянии. Имея модульное приложение, мы можем независимо работать с различными компонентами, переписывая те, которые необходимо переписать, и измеряя и улучшая общую систему для повышения производительности. Мы также можем инвестировать в развитие платформы, чтобы уменьшить размер нашего приложения и объем потребляемой памяти, а также поддерживать и интегрировать новые функции Android, такие как Kotlin, динамическая доставка и нативная разработка.
Не каждый проект требует переписывания, и мы используем инкрементальный подход к нашим инициативам, когда это возможно. Но нет причин бояться переписывания, если это лучшее решение проблемы. С хорошим планом игры, вниманием и менталитетом MVP, переписывание может помочь быстро отследить значимые изменения в инженерном продукте и культуре.