Систематический подход к отладке

в 13:02, , рубрики: Без рубрики

Систематический подход к отладке - 1


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

[1] Когда я вернулась из своего творческого отпуска, мне отправили пару багов со словами: «мы сохранили их для тебя!»

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

(Да, наш главный инженер занимается и технической поддержкой; для этого я здесь и нужна.)

Итак, как же я это делаю?

Если мне регулярно присылают баги, которые мне нужно устранить в незнакомых мне системах, то какой же процесс я использую?

И можно ли его применять к чему-то ещё, помимо кода?

Общий подход к отладке

Мой подход систематичен и сосредоточен на понимании первого и самого основного. Причин этому много, но в принципе вам нужно понять, что происходит, чтобы устранить проблему и убедиться, что она устранена.

Вот как выглядит последовательность этого процесса.

Рассказав об этапах, я подробнее опишу каждый из них.

  1. Определить симптомы.
  2. Воспроизвести баг.
  3. Понять систему (системы).
  4. Сформировать гипотезу о причинах возникновения бага.
  5. Проверить гипотезу и при необходимости повторить процесс.
  6. Устранить баг! Проверить исправление и при необходимости повторить.

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

▍ 1. Определение симптомов

Первым делом нужно разобраться в симптомах: в чём заключается «плохое» поведение, которое считывается как баг? Какие поведения не должны происходить, что идёт неверно?

Этот этап кажется очевидным, но люди часто его пропускают.

Когда вы получаете баг-репорт, то первым делом нужно определить, что же он конкретно означает. В наилучшей ситуации вы уже получаете качественно изложенное описание проблемы или от сообщившего о баге, или от коллеги, проверившего отчёт; но даже в этом случае уделите время его «перевариванию». Изучите баг-репорт и поймите, какое поведение нужно устранить, а также немного поработайте с ПО, в котором оно возникает.

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

Какие вопросы нужно задать:

  • Когда баг начал проявляться?
  • Сколько людей столкнулось с ним? Сообщило о нём?
  • Кто заметил его первым?
  • В каких окружениях он возникает?

▍ 2. Воспроизведение бага

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

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

[2] Здесь подразумевается, что в локальном окружении ваш доступ менее ограничен, чем в продакшене. У вас ведь нет рута в продакшене?

Каждая проблема с воспроизведением бага говорит вам больше о баге! Если вы пробуете свести воспроизведение бага до чего-то более мелкого, то найдёте элементы, необходимые для его воспроизведения (происходит ли он со всеми типами пользователей или только с конкретным? во всех рабочих пространствах или в одном?), и те элементы, которые побочны. Это начальная точка для понимания происходящего, она даст вам подсказки о том, что может быть причиной.

Иногда воспроизведение бага может быть чрезвычайно сложной задачей. Этот этап необходим: не пропускайте его. Если вы не сможете воспроизвести баг, то не сможете подтвердить, что он был устранён.

Некоторые баги будут воспроизводимы иногда (особенно справедливо это для багов, возникающих из-за условия гонки). В таком случае стремитесь сделать воспроизведение как можно более надёжным и измеряйте степень воспроизводимости. С багом, возникающим в 1/20 случаев, сложнее быть уверенным, что его устранили, а не просто снизили его вероятность. И если он действительно воспроизводим только иногда, автоматизация и измерение степени воспроизводимости может обеспечить хорошие метрики прогресса устранения этого бага. Можно дать автоматике проверить в десять раз больше необходимых случаев для его воспроизведения, чтобы убедиться, что вы действительно устранили его (вероятно).

▍ 3. Понимание системы/систем

Теперь, когда мы понимаем, в чём заключается баг и как его можно воспроизвести, стоит сделать шаг назад и разобраться с системой в целом. На этом этапе мы инстинктивно стремимся начать с места в карьер и заняться «настоящей» отладкой при помощи дебаггера; сопротивляйтесь этому стремлению, оно пойдёт вам во вред. Лучше сделать шаг назад и сначала понять систему.

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

Вот некоторые из вопросов, ответы на которые я хочу знать при отладке веб-приложений (аналоги существуют и в других видах ПО):

  • Какой код выполняется в данный момент?
  • Когда он был развёрнут в последний раз?
  • Какими были последние изменения?
  • Совпал ли момент возникновения бага с развёртыванием или другим изменением?

Также нужно изучить логи и инструменты наблюдаемости, задействовав их показания. Можно начать с логов, относящихся к этой ошибке, но важно найти и «обычные» логи. Если не изучить обычные логи, то вы не будете знать, как они выглядят; возможно, наблюдаемая вами ошибка на самом деле доброкачественная, но вызывает плохое сообщение в логе, а возможно, они связаны! Если не изучить обычные трассировки, то вы не будете знать, как выглядят странные! Пока вы не поймёте паттерны обычного поведения, то не поймёте, что же является выбросами. Поэтому немного почитайте, изучите систему, чтобы мозг сопоставил паттерны и вы были готовы к более глубокому погружению.

▍ 4. Формирование гипотезы о местоположении бага

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

Какой компонент системы вызывает этот баг?

Какой модуль этого компонента делает что-то плохое?

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

Итак, мы формируем гипотезу о том, где находится баг. Вот некоторые вопросы, на основании которых можно сформировать гипотезу:

  • Какой компонент нашей системы содержит баг? Он всего один или их несколько?
  • Баг находится в компоненте или во взаимодействии между компонентами?

Ещё в самом начале следует рассечь систему. Создайте гипотезу, позволяющую устранить как можно больше мест, в идеале приблизительно 50% системы. Это позволит вам выполнить своего рода двоичный поиск бага и быстро сузить пространство поиска.

▍ 5. Проверка гипотезы

Сформировав гипотезу о местоположении бага, можно её проверить. Найдите соответствующий компонент и валидируйте его ввод/вывод. Тут ли баг, или где-то ещё?

Это может быть сложной задачей со множеством нюансов, потому что вы можете и не видеть всего происходящего, что нужно для проверки гипотезы. Не бойтесь модифицировать исполняемый код, чтобы получить больше информации! Многие люди относятся к этому с опаской, но здесь важно помнить: мощь ПО заключается в том, что мы можем менять его, в том числе и добавлять новые отладочные логи. Только убедитесь, что после внесения изменений баг воспроизводим, в противном случае ваши изменения могут сокрыть баг, даже если кажется, что они никак с ним не связаны3.

[3] Обожаю их. У них даже есть собственное название: гейзенбаг.

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

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

▍ 6. Устранение бага

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

Если вы добрались досюда, то можно надеяться, что это самая лёгкая часть. Если баг «простой», то его можно устранить обычным кодингом. Иногда баг таится в изъянах архитектуры системы, тогда его сложнее устранять, но, по крайней мере, у вас на вооружении есть информация для его устранения или снижения его влияния.

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


В этом и заключается мой процесс в целом!

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

Автор:
ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js