Почему умение дебажить один из самых важных навыков для разработчика

от автора

Привет! Меня зовут Абакар и я работаю главным тех лидом в Альфа Банке. Меня часто посещает вопрос — «А какой навык всё-таки самый полезный для разработчика?». Понятное дело, что ответ на этот вопрос обязан быть комплексным и скилл сет разраба не должен ограничиваться одним навыком. Но умение дебажить действительно хороший показатель уровня разработчика. Давай разберем на нескольких примерах почему я так считаю.

Пример №1

У нас на проекте используется дизайн-система и фичи строят свои экраны из компонентов дизайн-системы. Допустим, мы столкнулись с тем, что нам нужно занулять клик лисенер для компонента при определенных кейсах. Ну, в общем-то, дело не самое хитрое:

view.setOnСlickListener(null)

Запускаем код и он не работает.

Какие есть мысли почему не работает? Таких мыслей может быть много:

  • возможно кто-то проставляет клик лисенер после того, как мы проставили его в null;

  • может просто сам метод не отрабатывает так, как нужно;

  • а может мы зануляем клик лисенер не у той вьюхи;

  • а может это как-то связано с версией андроида;

  • а возможно наш компонент использует для своих нужд вьюху не из support library, в которой есть баги.

Какой самый топорный вариант проверить все эти гипотезы? Просто пройтись по ним и вычислить методом тыка.

Но это плохой способ:

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

  • Даже если ты найдешь решение, то для себя ничего из него не вынесешь. Просто улучшишь свой навык решать проблемы методом тыка. Это не системный подход и следуя ему ты будешь стоять на одном месте как специалист.

У тебя может возникнуть логичный вопрос — «А как тогда лучше всего решить проблему выше?» Половина решения проблемы — это нахождения её причины. Давай попробуем найти причину.

Нам помогут два самых главных навыка разработчика — умение читать чужой код и разбираться в нем + умение дебажить. Но перед тем как идти дальше давай разберемся какие виды дебага существуют?


Виды дебага (aka виды отладки)

Давай пройдемся по тому, как можно дебажить наше приложение. Можешь в комментах накидать какие виды дебага ты применял или какие знаешь =) У меня их пять.

№1. Просмотр исходного кода

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

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

№2. Дебаг логгированием

В андроиде, например, уже есть много системных логов, но также мы можем добавлять свой вывод, как просто через system.out.println("my_text"), так и методы класса
Log.v(), Log.d(), Log.i(), Log.w(), и Log.e() — про каждый из этих методов можно прочитать отдельно в главе Log документации Android.

Мемный пример того, как используется логирование в Android SDK.

ActivityThread

ActivityThread

Но и оно может принести пользу, если я разработчик SDK и мне понадобилось получить нужные логи при дебаге ActivityThread — просто меняю тут флажочек и смотрю вывод в консоли.

Вот как могут выглядеть логи в консоли:

Логи android

Логи android

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

1) Тест не проходит и выдает вот такую ошибку:

Not Mocked

Not Mocked

Ответ: У мока не хватает описания — что делать, когда вызывается метод putSerializable. Если добавить мок этого метода, то тест начнет проходить. На самом деле именно об этом нам говорят логи, и если внимательно их прочитать, то можно всё понять.

2) Открываем экран и он крашит в рантайме.

Multiple fields

Multiple fields

Ответ: Dto, сущность, которая описана в data слое, объявляет два поля с дублирующими именами. Чтобы избавиться от краша, достаточно исправить проблему с дублированием нейминга полей.

№3. Дебаг по брейкпоинтам

Это вид отладки, когда мы проверяем работу программы по заданными нами брейкпоинтам. При этом конкретно Андроид Студия даёт богатое количество возможностей при данном виде отладки:

  • можем посмотреть какие значения в каких переменных проставлены;

  • проставить значение в переменную и пустить выполнение программы дальше;

  • также можем настраивать так называемые conditional breakpoints — брейкпоинты, на которых остановка происходит только при выполнении определенного условия.

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

Вот как может выглядеть дебаг панель

Вот как может выглядеть дебаг панель

№4. Дебаг профилированием

Вообще профилирование довольно обширное понятие, но мы его будем обсуждать с точки зрения андроида. С помощью профилирования (один из вариантов systrace) мы можем посмотреть, сколько времени занимает выполнение интересующего нас метода, например, если мы занимаемся оптимизациями. Также мы можем снимать дампы памяти если занимаемся поиском и устранением утечек памяти. И многое многое другое.

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

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

Пример дампа памяти

Пример дампа памяти

№5. Дебаг с помощью Layout Inspector (актуален только для приложений с UI)

Если делаешь приложение с пользовательским UI, то часто будешь сталкиваться с багами, которые связаны именно с отрисовкой. В таких случаях очень помогает наличие инструмента, который помогает отлаживать UI. В случае андроида — это Layout Inspector. На моей практике он очень сильно помогал и в нем также много возможностей. Если тебе еще не приходилось с ним сталкиваться — максимально рекомендую изучить.

Отладка с помощью Layout Inspector

Отладка с помощью Layout Inspector

Вернёмся к примеру №1

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

hmmm

hmmm

Мы видим, что у компонента переопределен метод setOnClickListener. И даже если мы передаем в него null , он всё равно вешает клик лисенер. Вопрос «Зачем это было сделано изначально?» мы с тобой рассматривать сейчас не будем, я и сам был удивлен 🙂

Здорово, что нам удалось сэкономить много времени отладки просто путём просмотра исходного кода. Но это не всегда так работает, иногда придется и подебажить, да ещё и погрузиться на самое дно в недра исходников.

На эту тему от меня уже есть статья «Самый запутанный краш в моей жизни» (кейс, когда я столкнулся с очень странным багом и исследовал его).

Пример №2

Исходные условия всё те же, экранчики нашего приложения строятся из компонентов дизайн-системы как из кирпичиков. Открываем приложение и видим такое:

тестовый пользователь

тестовый пользователь

Иконка как-то сильно прижалась к верху. Здесь используется компонент, который мы называем AlertView. Так вот из интересного — в компонент AlertView никаких изменений не вносилось, а ошибка возникла. Давай разбираться, в чём же дело.

В первую очередь заглянем в модель AlertView и посмотрим как она настраивается.

data class AlertViewModel(     val icon: IconElementModel,     val texts: Texts = Texts.None,     val buttons: Buttons = Buttons.None, ): Serializable

В AlertViewModel ничего странного не видим, но заметим, что она внутри себя использует IconElementModel для отображения иконки, с которой как раз у нас проблема.

Давай заглянем внутрь IconElementModel. Важный дисклеймер в IconElementModel тоже никаких изменений не вносилось:

data class IconElementModel constructor(     override val icon: Image? = null,     @Transient  override val horizontalPaddingNew: HorizontalPadding,     @Transient  override val verticalPadding: VerticalPadding ): Serializable

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

hmmm

hmmm

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

Но, есть один важный нюанс — это ведь как-то работало до этого. Почему оно сломалось?

Не буду томить, в итоге оказалось, что на это поведение повлияло повышение targetSdk. А повлияло потому, что при передаче нашей модельки, мы опирались на внутреннюю реализацию SDK, а именно на кейс, когда передача объекта происходила по ссылке, а не через реальную сериализацию и десериализацию. Подробнее про это в статье «Кюветы Android, Часть 1: SDK», на StackOverflow («Bundle.putParcelable») и в исходном коде («BaseBundle.java»).

Ну а раз мы нашли причину проблемы то можем пофиксить её со спокойной совестью.

Пример №3

Давай представим, что у нас с тобой есть RecyclerView. Мы задали ему адаптер, передаем данные и при этом видим пустоту на экране.

Void

Void

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

Logs

Logs

В логах все штатно, никаких ошибок нет. Давай прибегнем к LayoutManager, посмотрим, что он нам скажет:

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

onLayout

onLayout

Видим, что метод onLayout у RecyclerView вызывается, пока всё идет по плану. Если ещё не сталкивался с методами измерения и отрисовки view и viewgroup — эта статья может быть интересной. Провалимся внутрь dispatchLayout:

dispatchLayout

dispatchLayout

А вот мы и нашли проблему.

Мы просто забыли выставить LayoutManager для RecyclerView. Но при этом приложение не крашится, пользователь просто видит пустой экран.

Но ты мог заметить, что в логи сообщение все же выводится «No layout manager attached; skipping layout». И в скриншоте с логами, который был выше это сообщение также есть =) Это была проверка на внимательность и напоминание о том, что очень важно уметь смотреть и понимать логи, они могут сэкономить тебе кучу времени.
Итак давай проставим LayoutManager и попробуем:

Ура, наш список начал отображать элементы. Конечно, нам тут очень могли помочь логи, но мы были не совсем внимательными. Однако и здесь мы не стали решать проблему методом тыка и не поленились её продебажить, чтобы понять в чем дело.

Итоги

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

Самое важное в решении проблемы или фиксе бага — найти корень.


ссылка на оригинал статьи https://habr.com/ru/articles/819481/