У вас когда-нибудь случались сбои, причину которых вы не могли определить и вообще не понимали, в чем дело? И никакое тестирование не позволяло воспроизвести проблему? Если да, то вы попали по адресу!
Впрочем, как вы увидите в этой статье, способность отлаживать сложные сбои не появляется мгновенно. Помните об этом: не существует волшебного инструмента, который вы запустите и который даст вам ожидаемый результат. Когда речь идет о сложных сбоях, вместо этого нам нужно подготовить нашу среду таким образом, чтобы эти проблемы при возникновении были более понятны, что поможет в их решении. Давайте посмотрим, как это сделать!
Что такое сложные сбои?
Одна вещь, которую я считаю полезной, — это рационализация проблемы. Легко посмотреть на странную проблему и просто отмахнуться от нее как от чего-то волшебного, что больше никогда не повторится, но это бессмысленно. Всегда есть вполне логичная причина возникновения проблемы (скорее всего, по вашей вине), и чем больше пользователей пострадали от нее, тем больше вероятность того, что это не случайность. Так как может получиться, что прямо сейчас вы сталкиваетесь с проблемой, которая затрагивает большое количество пользователей, и при этом не имеете ни малейшего представления о том, что происходит и как ее воспроизвести?
По моему опыту, неспособность понять суть сбоя всегда сводится к недостатку информации. Проблема не в том, что проблема «слишком сложная», а в том, что у вас недостаточно данных. Подумайте о самой странном креше, который вам когда-либо приходилось рассматривать: разве он не был бы намного менее сложным, если бы в отчете об сбое было точно указано, в чем была проблема и как ее решить? Неважно, насколько странным является сбой, именно ваша способность понять и воспроизвести проблему определяет вероятность ее решения.
Таким образом, если вы хотите быть в состоянии решить любой сбой, вам необходимо улучшить данные, которые его сопровождают. Давайте рассмотрим несколько способов сделать это!
Метаданные, специфичные для конкретного приложения
Вы, наверное, заметили, что такие платформы, как Firebase, всегда включают некоторые полезные метаданные, связанные с устройствами, например, наиболее распространенную версию iOS, вызвавшую крах, были ли пользователи в foreground или background режиме, был ли у устройства jailbrake, сколько места на диске оставалось у каждого пользователя, когда произошел крах, и так далее. Эти данные чрезвычайно полезны, но их недостаточно. Что вам действительно необходимо, так это включить метаданные вашего приложения, которые помогут вам точно определить, что делал пользователь в момент сбоя. Вот некоторые примеры того, что вы должны добавить:
- Экран, на который смотрел пользователь
- «Тип» пользователя, если применимо (бесплатный? премиум? logged out?)
- Последнее действие пользователя (пытался ли он куда-то перейти?)
- Правильно ли завершился запуск приложения?
- Получил ли пользователь предупреждение о нехватке памяти?
- Завершается ли работа приложения?
- Есть ли у пользователя активное подключение к Интернету?
- Какой язык видит пользователь?
Трудно представить полный список, учитывая, что он будет совершенно разным для разных приложений, но то, что вам нужно сделать здесь, это собрать все о вашем приложении, что может повлиять на его выполнение, и включить это в ваши отчеты о сбоях.
Вы можете добавить эту информацию в Firebase через API SDK с парами ключ/значение, но для того, чтобы увидеть эту информацию в процентах, вам, вероятно, придется абстрагировать Firebase в своем собственном бэкенде с отчетами о сбоях.
Аналитика
Помимо метаданных, еще одним важным компонентом является наличие в вашем приложении надежной инфраструктуры аналитики. В большинстве приложений она уже есть, хотя, возможно, вам потребуется внести некоторые изменения, чтобы сделать ее пригодной для использования в целях составления отчетов о сбоях.
Суть в том, что если у вас есть надежная аналитическая реализация, вы должны иметь возможность использовать ее для воспроизведения шагов пользователя до креша. Таким образом, для этого вам необходимо убедиться, что ваш аналитический SDK получает как можно больше информации о взаимодействии с пользователем, например:
- «Пользователь коснулся кнопки X»
- «Пользователь увидел баннер Y»
- «Пользователь перешел на экран Z».
Для обеспечения воспроизводимости большинство сторонних SDK сегодня включают функцию «временной шкалы», которая показывает все события, отправленные конкретным пользователем в определенное время.
Использование этой информации для решения проблем о сбоями
Наконец, получив как можно больше информации о состоянии приложения в момент сбоя, вы можете следовать созданному мной пошаговому руководству, которое поможет вам отследить и решить подавляющее большинство случаев.
Проверьте сбойный поток
Если вы читаете это руководство, это, вероятно, означает, что вы уже пробовали это сделать и ничего не получилось, но в любом случае стоит упомянуть, что в подавляющем большинстве случаев ответ лежит непосредственно в трассировке сбоя. Проверив путь, пройденный кодом, вы сможете найти и воспроизвести проблему.
Проверьте метаданные о сбое
Если трассировка расплывчата, то просмотр добавленных метаданных может выявить проблему. При просмотре метаданных обратите внимание на значения, близкие к 100% или 0%. Это может показать, что сбой связан с конкретным устройством или условием внутри приложения.
Проверьте фоновые потоки
Если метаданные также расплывчаты, это может означать, что сбойный код — это не сама проблема, а скорее косвенное следствие проблемы, которая произошла асинхронно где-то еще. В этой ситуации вы можете обнаружить проблему, посмотрев, что происходит в других потоках, в которых произошел сбой. Попробуйте исследовать много случаев сбоя и сравните их потоки друг с другом. Есть ли у них что-то общее, чего нет в других задачах? Если да, то это может быть причиной проблемы.
Сопоставить среду пользователей, у которых произошел сбой
Если после глубокого анализа трассировки и метаданных вы все еще не можете понять, в чем дело, возможно, сбой связан с конкретным устройством и/или A/B-тестом. Если это так, то вы должны быть в состоянии воспроизвести сбой, сопоставив его с окружением пользователя. Помимо того, что вы должны использовать именно тот смартфон/версию ОС, на которой произошел сбой, убедитесь, что вы также используете флаги A/B-тестирования пользователя (если они есть у вашего приложения).
Что касается флагов, то очень полезно сравнить флаги пользователей с проблемой и флаги пользователей без проблемы. Если проблема связана с флагом, то составление списка общих флагов в этих группах покажет, какой флаг (или его отсутствие, если проблема была вызвана удалением эксперимента) вызывает проблему.
ДОБАВЛЕНИЕ: Дэйв Вервер также упомянул кое-что важное, что я забыл добавить — убедитесь, что вы также запустили точную сборку приложения, на котором произошел сбой у пользователей! Не исключено, что изменения в вашей ветке повлияют на условия возникновения сбоя, поэтому всегда убедитесь, что вы находитесь именно в том коммите, который использовался в сборке. Вы можете получить эту возможность, заставив свой CI создавать git-метку каждый раз, когда он загружает новую сборку — назвав метку соответствующим номером сборки, вы получите возможность откатиться к любому релизу, который вы когда-либо создавали.
Проследите шаги пользователя
Если все оказалось бесполезным, вы должны быть в состоянии воспроизвести проблему, подражая действиям пользователя до того, как произошел сбой. Иногда это может быть что-то абсурдно специфическое, например, открыть/закрыть приложение несколько раз, перевернуть его вверх ногами, открыть плейлист, а затем бросить устройство об стену. Если это так, то вы сможете найти эти действия, просмотрев таймлайн вашего аналитического SDK для этого пользователя.
Важно отметить, что условия возникновения проблемы могут охватывать несколько сессий, например, проблема связана с контентом, который был загружен несколько дней назад. В таких случаях для понимания проблемы необходимо просмотреть данные не только сессии, в которой произошел сбой, но и сессий, которые предшествовали ей.
Инструмент для поиска проблем в безопасности потоков/памяти
Если вы все еще не можете понять, что происходит, то, возможно, вы имеете дело с недетерминированной проблемой, вызванной либо проблемами безопасности потоков, такими как условия гонки, либо проблемами памяти, такими как повреждение кучи. К сожалению, простого способа выяснить это не существует, и для их обнаружения вам потребуется глубокое понимание кода. В iOS некоторую помощь могут оказать инструмент Zombies и средства санации потоков/памяти.
Лучшее, что вы можете сделать в данном случае, — это предотвратить возможность их возникновения. Если вы работаете с асинхронным кодом, всегда будьте на 100% уверены, что ваша реализация безопасна для потоков для всех сценариев использования, прежде чем мерджить ее. Хотя проблемы, связанные с потоками, очень легко создать, их чрезвычайно трудно отлаживать. Выбирайте всегда более безопасный вариант, чтобы избежать подобных проблем в будущем.
Проверьте, что нового в релизе, в котором произошел сбой
В некоторых случаях, особенно при очень старых проблемах, может быть полезно отследить точную версию, в которой возникла проблема, и зайти на GitHub, чтобы посмотреть, что именно было введено в этом выпуске. Если ничего не помогло, то отмена подозрительных пул реквестов может помочь.
Добавьте больше логов
Если вы совершенно не понимаете, что происходит, то добавление дополнительных логов может принести некоторое облегчение. Firebase позволяет прикреплять общие журналы к отчету о сбое, и один из способов их использования — это регистрация информации о состоянии пользовательского приложения в месте, где произошел сбой. Постарайтесь придумать что-нибудь странное или незапланированное, что может происходить вокруг кода, который дает сбой, и запишите это в Firebase — в следующем выпуске вы сможете увидеть новую информацию рядом со сбоями. Логи также могут быть полезны еще до внедрения новой фичи — если вы думаете, что новая функция может вызвать проблемы, вы можете обезопасить ее с помощью логов еще до выпуска. В случае возникновения проблемы у вас уже будет дополнительная информация, необходимая для ее отладки.
Что еще?
Если вы дошли до этого момента, то вполне возможно, что ваши первоначальные мысли верны: вы имеете дело с какой-то странной аппаратной проблемой, вызванной солнечным излучением в определенное время суток для конкретного пользователя в Латвии.
Чтобы избежать подобных ситуаций, я лично стараюсь не рассматривать проблемы до тех пор, пока они не будут постоянно возникать у достаточно большого количества пользователей. Всегда есть вероятность того, что проблемы могут быть вызваны ситуативными факторами, но если они не постоянны или не имеют большого влияния, лучше игнорировать их, чтобы не тратить время на выяснение того, в чем, как оказалось, нет вашей вины.