Как найти плавающую ошибку

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

Рассказываем, что это за ошибки, откуда они берутся и что с ними делать.

Что такое плавающая ошибка

Плавающая ошибка — это такая ошибка, которая:

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

В англоязычной среде такие ошибки называют «гейзенбагами» (heisenbug). Эта игра слов происходит от принципа неопределённости Гейзенберга (или Хайзенберга) из квантовой механики, когда ошибка как бы есть, но её как бы нет.

Откуда берутся плавающие ошибки

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

Из-за влажности воздуха какие-то контакты в штекерах могут то пропускать, то не пропускать сигналы.

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

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

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

Как найти такую ошибку

Для обнаружения плавающих ошибок нужны особые методы тестирования и отладки, например:

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

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

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

Как описать плавающую ошибку

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

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

Чем больше данных — тем проще тестировщикам понять, что происходит, и исправить это.

Могут выяснить, что ошибка возникает после какого-то раза запуска программы; в какие-то дни недели; в какие-то числа месяца; когда программой пользуется определённый человек или на определённом компьютере. Вариантов бесконечно много. 

Ошибка при работе с плавающей точкой — это оно?

Нет. 

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

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

Автор: Джеймс Бах (James Bach)

Оригинал статьи: http://www.satisfice.com/blog/archives/34

Перевод: Ольга Алифанова

Способность уверенно исследовать плавающие баги – одна из характеристик отличного тестировщика. Самые захватывающие истории, которые я слышал от тестировщиков, были про охоту на «белых китов» в океане сложного кода.

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

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

Нас не беспокоит неустойчивое поведение, если оно а) так и задумано б) не несет в себе тайны, даже если оно не вполне предсказуемо. Например, результат подброшенной монеты или шанс выбить 777 в игровом автомате – вполне себе неустойчивое поведение. Даже загадочное неустойчивое поведение не волнует нас, если мы уверены, что оно не вызовет проблем. Когда я тестирую, я не думаю о магнитных полях или мелких скачках напряжения, хотя они влияют на мою работу постоянно.

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

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

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

Принципы работы с плавающими багами

  • Успокойтесь, они, скорее всего, не вызваны злыми духами.
  • Если это случилось один раз – скорее всего, это произойдет снова.
  • Если баг не был исправлен, вряд ли он самоликвидировался навсегда.
  • С осторожностью относитесь к исправлениям плавающих багов. Исправленный баг и неисправленный плавающий баг неотличимы друг от друга по определению в течение какого-то времени и/или при определенных условиях.
  • Любое состояние системы, переход в которое занимает длительное время в обычных обстоятельствах, может быть мгновенно достижимым в непредсказуемых ситуациях.
  • Сложное и запутанное поведение обычно спровоцировано чем-то довольно простым.
  • Сложное и запутанное поведение иногда спровоцировано целым комплексом причин.
  • Плавающие баги зачастую могут рассказать вам много интересного про ваш продукт.
  • Очень легко поддаться уверенности, что ваше предположение об источнике проблемы разумно, остроумно и имеет смысл – оно просто неверное.
  • Ключ к разгадке может находиться в руках другого человека.
  • Возможно, что баг, «плавающий» на тест-стенде, легко воспроизведется на проде.
  • Принцип Pentium 1994: плавающая техническая проблема может привести к систематическим и очень дорогостоящим проблемам бизнеса.
  • Проблема может быть плавающей, но риск, связанный с ней, стабилен.
  • Чем выше тестируемость продукта, тем легче исследовать плавающие баги.
  • Когда вы исключили все невозможное, то то, что осталось, каким бы невероятным оно ни было, уже нанесло существенный урон! Поэтому не ждите момента, когда вы полностью раскусите этот орешек, заводите плавающие баги как можно раньше!
  • Если вы не успеваете ущучить плавающий баг до релиза, сделайте все, что в ваших силах, чтобы все-таки выловить его и исправить. Получайте удовольствие от процесса, так сказать.

Общие советы по исследованию плавающих проблем

  • Перепроверьте свои базовые предположения: используете ли вы именно тот компьютер? Тестируете ли вы именно то, что нужно? Верны ли ваши наблюдения?
  • Свидетельства очевидцев могут оставить за бортом важную информацию, поэтому слушайте, но не нужно излишней уверенности в чужих словах.
  • Пригласите других наблюдателей, подключите к процессу большее количество людей.
  • Мотивируйте людей сообщать о плавающих проблемах.
  • Если вам сказали, что причина проблемы ТОЧНО не в этом, обратите на ЭТО особое внимание.
  • Проверьте сайты технической поддержки каждого из сторонних компонентов, который используется в вашем приложении. Возможно, ваша проблема там указана.
  • Ищите инструменты, которые помогут вам наблюдать за поведением системы и контролировать его.
  • Налаживайте коммуникацию с наблюдателями (особенно с реальными пользователями).
  • Соберите все загадочные баги в одном месте, чтобы легче было отслеживать закономерности.
  • Просмотрите список багов, поищите в нем похожие на плавающий баг проблемы.
  • Точнее зафиксируйте свои наблюдения, пользуйтесь инструментами.
  • Улучшите тестируемость вашего продукта, добавьте логирование и интерфейсы с поддержкой сценариев.
  • Точнее контролируйте данные, которые вы вводите (включая их последовательность, время, типы, размеры, источники, итерации, комбинации).
  • Систематически покрывайте тестами данные ввода и состояния системы.
  • Сохраняйте абсолютно все логи. Возможно, позже вам понадобится сравнить новые логи со старыми.
  • Если проблема чаще возникает в одних ситуациях и реже – в других, проведите статистический анализ разницы между закономерностями в этих ситуациях.
  • Попробуйте контролировать то, что, с вашей точки зрения, не имеет значения.
  • Упрощайте все. Попытайтесь менять одну переменную за раз, попробуйте разбить систему на части (помогает понять и изолировать проблему).
  • Усложняйте. Попытайтесь изменить несколько переменных за раз, устройте в системе бардак (помогает выловить «лотерейные» баги).
  • Добавьте элемент случайности в состояния системы и обрабатываемые данные (возможно, при помощи менее жесткого контроля), чтобы добиться состояний, которые, возможно, не вписываются в ваш обычный шаблон использования.
  • Создайте искусственный стресс (высокая нагрузка, большие объемы данных).
  • Поставьте ловушку, чтобы при возникновении проблемы в следующий раз вам удалось изучить ее получше.
  • Подумайте насчет код-ревью.
  • Поищите конфликты между компонентами, созданными разными компаниями.
  • Собирайте и храните рассказы о локализации плавающих багов.
  • Систематически размышляйте о причинах плавающих багов (см. ниже).
  • Опасайтесь потратить кучу времени на мелкий баг, всегда спрашивайте себя, стоит ли он того.
  • Если ничего не помогает, дайте проблеме отлежаться, поработайте над чем-нибудь другим и последите, не появится ли она снова.

Возможные причины плавающих багов

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

Вероятная причина номер 1: Система ведет себя точно так же, как и вела. Кажущийся сбой поведения – артефакт, относящийся к вашему восприятию.

  • Плохие наблюдения. Наблюдатель мог быть невнимательным — например, подвергнуться «Слепоте невнимания» – феномену, когда мозг занят чем-то еще, и человек не видит того, что находится у него прямо перед носом. Если показать ему то же самое еще раз, он увидит нечто новое для себя и предположит, что раньше такого не происходило. К тому же так работает ряд оптических иллюзий, демонстрируя кажущееся иным поведение при том, что ровным счетом ничего не изменилось.
  • Наблюдения, не относящиеся к делу. Наблюдатель обращает внимание на различия, которые не имеют значения. То, что важно, остается стабильным. Это случается, когда наблюдатель чересчур пристально бдит за мелочами.
  • Плохая память. Возможно, наблюдатель плохо запомнил свои впечатления, или записи, фиксирующие проблему, повреждены. Когда вы наблюдаете за чем-то, ваш мозг обрабатывает кучу данных! Он немедленно упаковывает их и пытается связать их с другими данными, при этом важная информация может потеряться. К тому же в процессе разработки и тестирования мы повторяем одно и то же довольно часто, и можем запутаться в деталях каждого отдельного наблюдения.
  • Ложная атрибуция. Наблюдатель приписывает свое наблюдение не тому, чему нужно. К примеру, «Microsoft Word упал» может означать, что упала Windows, и причина падения не имеет к Word ни малейшего отношения. Word вообще ничего не делал. Этот феномен еще называют «ложной корреляцией» и часто встречается, когда одно событие следует сразу же после другого, и кажется, что это причина и следствие. Благодаря ложной корреляции плавающие баги зачастую считаются обычными багами, появившимися из-за крайне сложного и маловероятного комплекса причин.
  • Искажение фактов. Наблюдатель, возможно, неверно передает информацию. Причин может быть множество – например, вполне невинная: он настолько уверен в своих предположениях, что передает свои наблюдения определенным образом. Как-то раз я спросил сына, подключена ли его нерабочая Playstation к сети. «Конечно», огрызнулся он. Попробовав решить проблему так и сяк, я решил, что блок питания умер. После чего посмотрел на него и обнаружил, что штекер не включен в розетку.
  • Ненадежный оракул. Мнение наблюдателя насчет того, что является проблемой, а что нет, постоянно меняется. У нас может создаться впечатление, что проблема плавающая, только потому, что некоторые люди – иногда – не считают это поведение проблемой, даже если оно вполне предсказуемое. Другие люди могут отнестись к вопросу иначе, и даже один и тот же человек может поменять свое мнение.
  • Ненадежная коммуникация. Коммуникация с наблюдателем может быть неполной. У нас создается ощущение, что мы нашли плавающий баг, потому что сообщения о нем не всегда до нас доходят, даже если проблема вполне предсказуема. Фраза «Мне кажется, проблема больше не проявляется» может означать, что люди просто перестали о ней сообщать.

Вероятная причина номер 2: Система ведет себя иначе, потому что это другая система.

  • Deus ex machina. Разработчик мог специально что-то поменять, а потом вернул все назад. Это часто случается, когда над разными частями платформы одновременно работает несколько разработчиков или команд, не координируя свои действия друг с другом. Другой вариант – злонамеренные модификации приложения хакерами.
  • Случайное изменение. Разработчик мог внести правки случайно. Возможно, незапланированные побочные эффекты и приводят к возникновению плавающих проблем. К тому же разработчик может ошибиться и выкатить правки на прод вместо тест-стенда.
  • Другая платформа. Один из компонентов платформы мог быть заменен или переконфигурирован. Администратор или пользователь могли специально или случайно изменить что-то в компоненте, от которого зависит продукт. Частый источник таких проблем – автоматические обновления Windows, изменения в конфигурации памяти и дискового пространства.
  • Проблемы железа. Может, какой-то физический компонент системы сбоит время от времени. Перебои могут быть вызваны естественными вариациями в его работе, магнитными полями, излишне высокой температурой, низким зарядом батареи, плохим обслуживанием или физическим шоком.
  • Чужая система. В работу вашего приложения может вмешаться какое-нибудь еще. Например, в веб-тестировании я могу иногда получать неверные результаты из-за прокси-сервера провайдера, который загружает кэшированную версию страницы в неподходящий момент. Другие примеры такого вмешательства – это сканирование на вирусы, обновления системы, другие программы или та же самая программа, запущенная повторно.
  • Проблемы кода. Может, проблема кроется в том, как написан код. Один из худших багов, созданных моими руками (худших в смысле тяжести поиска причины) возникал из-за кода в игре, который иногда перезаписывал данные в совершенно другой части программы. Из-за природы этих данных игра не падала — зато поврежденная функция передавала управление функции, которая следовала сразу за ней в памяти приложения. Мне понадобилось несколько дней (и эмулятор чипа), чтобы это понять.
  • Раздвоение личности. То, что вы понимаете под системой, может на самом деле состоять из нескольких систем, мимикрирующих под единую. К примеру, я могу получать разные результаты от Google в зависимости от того, на какой сервер Гугла я попадаю. Или разные компьютеры тест-команды имеют разные версии ключевого компонента, или я опечатался в URL и случайно начал тестировать на другом сервере.
  • Человеческий фактор. Возможно, за запуск части системы отвечает человек, и этот человек ведет себя по-разному.

Вероятная причина номер 3. Система ведет себя иначе, потому что находится в другом состоянии.

  • Замершее условие. Решение, которое должно основываться на статусе условия, могло перестать проверять это условие, застряв в состоянии «всегда да» или «всегда нет».
  • Неверная инициализация. Одна или несколько переменных не инициализируются. В результате стартовое состояние расчетов становится зависимым от предыдущих расчетов той же или иной функции.
  • Отказ в ресурсах. Критически важный файл, поток или другая переменная недоступны для системы. Это может произойти, потому что они не существуют, повреждены или заблокированы другим процессом.
  • Прогрессивное повреждение данных. Система могла превратиться в бардак постепенно из-за накапливающихся мелких ошибок. Например, это может проявляться как рассинхрон временных циклов, или ошибка округления при сложных или рефлексивных расчетах.
  • Прогрессивная дестабилизация. Классический пример многоэтапного отказа. Первая часть бага создает нестабильность – например, указатель массива ведет в никуда при определенных условиях, однако внешне баг никак не проявляет себя. Из-за этого невидимого сбоя система переходит ко второму этапу – к видимому сбою, который происходит позднее в комбинации с какими-то другими условиями. Увязать эти события между собой очень трудно из-за задержки между первопричиной и последствиями.
  • Переполнение. Какой-либо контейнер может переполниться, провоцируя сбой или обработку исключений. В эпоху больших объемов памяти о возможности переполнения часто забывают. Даже если исключение корректно обрабатывается, сам процесс обработки, взаимодействуя с другими функциями системы, может вызвать плавающую проблему.
  • Редко встречающиеся функции. Некоторые функции системы используются настолько редко, что мы про них забываем. Это обработка исключений, внутренняя сборка мусора, автосохранение, функции обслуживания. Когда они срабатывают, они могут неожиданно вступить в конфликт с другими функциями или условиями. Обращайте особое внимание на скрытые и автоматические функции.
  • Другой режим или настройки. Система может работать при разнообразных режимах, и пользователь установил какой-то другой. Отличия могут быть незаметны с первого взгляда.

Вероятная причина номер 4. Система ведет себя иначе, потому что получила другие вводные данные.

  • Случайный ввод. Пользвоатель мог ввести что-то или изменить ввод таким образом, что это не должно было ни на что повлиять – но все же повлияло. Это можно назвать синдромом Умного Ганса – лошади, которая периодически умудрялась решать математические задачи. В результате Оскар Пфунгст обнаружил, что лошадь реагировала на непроизвольные микродвижения своего хозяина. В своей области я как-то раз столкнулся с плавающим багом, который возникал, когда солнце проникало через мое окно и падало на оптический сенсор моей мыши. Погодные условия никак не должны были повлиять на ввод данных в приложение – однако влияли. Более распространенный пример странного поведения – баги, возникающие, когда вы используете клавиатуру, а не мышь, чтобы отдать какую-либо команду. Случайный ввод может быть невидимым глазу без специальных инструментов и рекордеров. Например, два идентичных текста в RFT-формате, один из которых сохранен через Word, а другой через Wordpad, выглядят очень похоже, но совсем не идентичны.
  • Секретные границы и условия. Программа может вести себя иначе из-за скрытых границ или областей падения, которые никем не задокументированы и которых вы не ожидаете, выстраивая свою ментальную модель продукта. Как-то раз я тестировал поиск, который вел себя совершенно иначе, если количество найденных записей равнялось тысяче или пятидесяти тысячам. Я обнаружил эти скрытые границы совершенно случайно.
  • Другой профиль использования. Использование приложений некоторыми пользователями может чем-то специфически отличаться. Их предположения насчет ввода ведут к другим результатам вывода. Пользователи с определенным опытом – например, программисты – могут систематически замечать (или не замечать) определенные типы поведения приложения.
  • Призрачный ввод. Источником ввода может быть не пользователь, а машина. Такой ввод часто незаметен пользователю, и включает вариации из-за разных файлов, различные сигналы от периферийных устройств, или данные, поступающие по сети.
  • Deus Ex Machina. Кто-то еще взаимодействует с продуктом в то же самое время, что и пользователь – другой тестировщик, другой пользователь, хакер.
  • Дефектный ввод. Данные могли быть повреждены или перехвачены на пути к системе. Особенно это касается клиент-серверных приложений.
  • Время в качестве вводных данных. Плавающие баги могут зависеть и от времени как такового. Время всегда меняется вне зависимости от того, удалось ли вам проконтролировать и зафиксировать все остальное. Когда время и дата, или временные интервалы, используются в качестве данных, баги могут проявляться только в определенное время.
  • Временная лотерея. Вариации ввода, которые обычно не имеют значения, могут внезапно стать критическими в определенное время или при определенной нагрузке. От этой проблемы страдал Mars Rover – окно уязвимости в три миллисекунды, когда операция записи могла писать в защищенную часть памяти.
  • Комбинационная лотерея. Вариации ввода, которые обычно не имеют значения, могут вызвать проблему, если они скомбинированы определенным образом.

Вероятная причина номер 5. Прочие причины связаны с тем, что ваша ментальная модель системы и того, что на нее влияет, неверна или неполна.

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

Обсудить в форуме

Петя вспоминает, что как-то уже встречал эту ошибку, но она ни разу не повторилась. Он, разумеется, сразу же перепроверяет все кейсы, связанные с сообщениями пользователей, но у него всё точно по ТЗ и никаких ошибок.

Так Петя встретил свой первый плавающий баг.

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

ПЛАВАЮЩИЙ БАГ – это ошибка, которая в одних и тех же условиях то воспроизводится, то нет.

Однако важно понимать, что любая ошибка, даже плавающая, имеет причины появления. И поиск причины – самое сложное в охоте на плавающий баг.

Основные ошибки при охоте

Какие ошибки может допустить Петя при охоте на свой первый плавающий баг?

1. Ринуться с головой в выяснение причин.
Так делать не стоит. Важно сразу понять, насколько ошибка критична, и, уже отталкиваясь от этого, действовать или…

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

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

Также важно удостовериться, что баг действительно плавающий. Будет весьма глупо потратить много часов своей (и вдобавок чужой) работы на отлов ошибки, фикс которой вы пропустили.

Также стоит убедиться, что ошибка является так называемым тестировщиками “лотерейным” багом, то есть багом, всплывающим из-за выпадения какого-то рандома в проверяемом ПО.

2. Накосячить с оформлением тикета в баг-трекере.
Для исправления ошибки разработчику важно иметь максимум полезной информации и минимум бесполезной. Если всё же ошибка критическая и мы беремся за её поиск, то необходимо взять кейс, при котором хотя бы единожды воспроизвелся баг, и пройти его несколько раз. Также по возможности исключить некоторые пункты. Особенно критически нужно относиться к словам очевидцев (пожаловавшихся пользователей). Потому что пользователь без труда что-нибудь напутает, а вас в ваших поисках это уведет совершенно не в ту степь.

Как охотиться?

А дальше есть множество способов работы, которые можно применять и каждый из них по-своему хорош:

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

2) Ищите инструменты, которые помогут вам точнее отслеживать проблему. Например, логи проекта (технические сообщения от программы, падающие в консоль или её аналог) позволят вам не ломать голову над тем, как же воспроизвести только что сломанное ПО. А вместо этого сразу отдать разработчику то, что укажет, что именно сломалось под капотом программы.

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

4) Поищите в списке багов похожие проблемы. Иногда информация о похожих багах помогает в исправлении.

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

6) Провести стресс-тесты. Возможно, ошибка случилась из-за чрезмерной нагрузки.

7) Поговорите с разработчиками о том, чтобы поставить “ловушку” (функцию в программе, сообщающую о том, что что-то не так), которая даст необходимую информацию.

8) Если ничего не помогает, то дайте проблеме отлежаться. Может, она решится сама собой, а может, вы найдете необходимую информацию.

_______
Пост написан совместно с одним из наших преподавателей (Андрей Высоцкий).
В следующем посте рассмотрим, как на практике происходит поиск плавающих багов и поговорим про ошибке, которые допускаются при их поиске.

«Плавающие» ошибки в проектах. Как поставить задачу так, чтобы их избежать и не прослыть придирой и формалистом

Время на прочтение
5 мин

Количество просмотров 2.1K

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

Это плавающие ошибки

Это плавающие ошибки

Если ты владелец IT-продукта и болеешь за свое дело, то без качественных технических заданий не обойтись. В больших нетиповых проектах постановка и формирование ТЗ становится нетривиальной. Нужно описать много условий, ветвлений и прочих сложных логических конструкций, которые «с наскоку» не реализовать, ведь на бумаге это выглядит лишь как набор абстракций. А так уж устроен наш мозг, что ему нужна конкретика и образы, а не полотна с текстом!

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

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

Есть всего два возможных базовых варианта. Остальное — их комбинации.

Вариант 1. Подумать и сформулировать набор правил для каждой сущности. Например, берем сущность «Переписка» и формулируем правила по ее доступности. Это может выглядеть следующим образом: 

Если между специалистом и заказчиком была переписка, то она блокируется при следующих условиях:

a.      Проект получает статус «Заблокирован»

b.      Проект получает признак «Снят с публикации»

c.       Проект получает статус «Закрыт» (полное закрытие)

d.      Специалист отказался от выполнения проекта на этапе поиска исполнителя

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

Кстати, как вы думаете, какой сюрприз (явно ненужный нам) можно получить по этому правилу? Напишите в комментариях, если заметите!

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

Лайфхак: это лучше зафиксировать в проектной документации в качестве принципа работы!

Таким образом, ваше ТЗ будет состоять из описаний наборов правил, реализовав которые вы должны получить красиво-работающий продукт.

Но что, если вы не смогли чего-то учесть?

Тут на помощь приходит второй вариант постановки задачи…

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

Вот вам абстрактный пример. У нас есть:

  • 3 состояния проекта S = (S1, S2, S3)

  • 3 роли R = (R1, R2, R3)

  • и три атрибута А = (А1, А2, А3), которые зависят от того, как пересекаются S и R.

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

Например, мы подумали, и у нас получилось так: 

S1 + R1 = А1

S1 + R2 = А2

S1 + R3 = А3

S2 + R1 = А1

S2 + R2 = А2

S2 + R3 = А3

S3 + R1 = А1

S3 + R2 = А2

S3 + R3 = А3 

Таким образом, пересечение двух небольших множеств, состоящих всего из трех объектов дало нам выборку из 9 вариантов! 

А если множество статусов S получит еще статус S4? Сколько будет вариантов? Уже 12!

А если размеры обоих множеств станут равны 4-м? Тогда вариантов будет 16!

Это я еще не привел пример, когда нам нужно пересечь 3 множества или больше!

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

Неполная карта частных случаев из одного фриланс-проекта

Неполная карта частных случаев из одного фриланс-проекта

Применимость на практике и «плавающие» ошибки?

Мы рассмотрели два способа подачи информации для ТЗ в сложных проектах. Я считаю, что у обоих способов есть свои преимущества и недостатки.

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

Кстати, бывают случаи, когда правило видоизменяется по ходу проекта, например, если мы что-то не учли изначально или в проекте появляются новые вводные. Такое бывает по невнимательности или по Agile:)

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

Этот способ позволяет зафиксировать уже работающие варианты решения, а также неработающие (или содержащие ошибки). Это особенно полезно, если возникают так называемые «плавающие» ошибки. Плавающая ошибка очень неприятна тем, что как бы исправив ее в одном частном случае, ошибка вылезает в другом частном случае.

Другими словами это, когда:

  • после проверки было: S1 + R1 = А2 (неправильно), а S1 + R2 = А2 (правильно)

  • после исправления стало: S1 + R1 = А1 (правильно), а S1 + R2 = А1 (неправильно)

Таким образом, наша ошибка уплыла из случая S1 + R1, но приплыла в случай S1 + R2.

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

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

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

При таком подходе снять с себя ответственность уже не получится, а это уже прямое указание на чью-то возможную некомпетентность или лень!

Понимаю, что вам, как руководителю, конечно, очень муторно описывать эти случаи и следить за ними. Частично или полностью эту задачу можно (и нужно!) поручить тестировщикам, но т.к. ответственность за проект несете вы — возможно именно вам придется в этом разобраться.

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

Буду рад, если вы поделитесь в комментариях своим опытом борьбы с плавающими ошибками! Как решали подобные ситуации? Были ли конфликты на этой почве?


Автор и ведущий персонального блога «ПРО-продукт» https://t.me/productmaster

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

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

Что такое плавающий баг?

Нежелательное поведение программы, которое возникает спонтанно и непредсказуемо, называют плавающим багом или гейзенбагом с отсылкой к немецкому физику Вернеру Карлу Гейзенбергу – автору принципа неопределенности из квантовой физики.

Вернер Гейзенберг говорил, что «чем более пристально вы глядите на один предмет, тем меньше внимания вы уделяете чему-то еще».

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

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

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

🐛 Что такое гейзенбаг и как с ним бороться?

Как работать с плавающими багами?

Для избавления от плавающих багов используется традиционный принцип «повторить и разобрать», но с некоторыми оговорками:

  1. Как проверить, что перед вами плавающий баг, а не обычный? Во втором случае вы понимаете, откуда взялась проблема, а в первом – нет. Именно поэтому внимательность и способность ничего не упускать – одно из главных достоинств тестировщика. Самое смешное, неисправленный плавающий и исправленный обычный баги очень похожи – они не воспроизводятся, но плавающий баг может в любой момент вернуться.
  2. Чем выше тестируемость продукта, тем легче найти в нем плавающий баг.
  3. Любая произошедшая по непонятным причинам ошибка может вернуться. «Прошло само» не работает. Код – это четкая структура, поэтому умение локализовать баги важно развивать. Этот навык пригодится, когда ошибка будет воспроизведена, но как – непонятно.
  4. Плавающие баги могут легко воспроизводиться в продуктивной среде и плохо проявляться или вовсе не проявляться в тестовой. Это важно учитывать и соотносить с процессом релиза системы.
  5. В появлении плавающих ошибок может быть виноват целый комплекс причин, поэтому при тестировании используйте таблицы со множеством вариантов. Иногда простой перебор сочетаний различных условий приводит к успеху.
  6. Никогда не пытайтесь скрыть плавающий баг от заказчиков и пользователей. В будущем это может привести к еще большим проблемам.

Яркий пример того, как попытка Intel скрыть ошибку привела к убыткам:

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

Как локализовать ошибку, если первичный анализ ничего не дал?

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

🐛 Что такое гейзенбаг и как с ним бороться?

Почему возникают плавающие баги?

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

Вот примерный список причин, который поможет в случае, если вдохновение вас покинуло:

  1. Переполнение. Какое-либо поле или контейнер могут переполняться и провоцировать сбой при корректной работе связанных с ним функций. В эпоху больших запасов памяти тестировщики часто упускают это из вида. Например, внутри системы могут считаться огромные суммарные значения, и она окажется не способна переварить такое количество символов.
  2. Проблема кода, которую также называют «Проблемой ТЗ». Зачастую плавающий баг связан с тем, как написан код (мы не рассматриваем сейчас некачественную работу, только непредсказуемую). Например, составителю технического задания на разработку очевидно, что при выборке данных нужно взять некий уникальный объект, но разработчик этого знать не может и прописывает свободный выбор. Бывает, что код написан верно, но какое-то сочетание условий приводит не к поломке, а к подмене функций. В этом месте не будет ошибки, но будет неверная работа программы в целом.
  3. Проблемы ввода. Например, в поле свободного ввода пользователь внес непредусмотренные алгоритмом данные, либо ошибочный символ (запятую вместо точки и т.д.). Сюда же относятся ошибки, связанные с использованием клавиатуры вместо мыши или ошибки призрачного ввода, когда данные вносит машина (особенно где-то в середине расчетов). В таких случаях проблему можно обнаружить, проставляя контрольные «стопы» и проверяя работу кода по частям. Еще один вариант – дефектный ввод, когда данные клиент-серверных приложений повреждены или переданы с ошибкой.
  4. Проблемы оборудования возникают, когда ошибки связаны с физическими факторами. Например, из-за перебоев с электропитанием, высоких температур в серверной, физических повреждений компьютера и т.д.
  5. Случайности. Некая комбинация условий может сработать корректно, но впоследствии привести к ошибке.
  6. Deus Ex Machina. Эти проблемы возникают, когда одновременно с вами кто-то еще использует приложение: разработчик, хакер или другой тестировщик.
  7. Неочевидные моменты. При анализе специалисты часто отбрасывают ненужное, чтобы сосредоточиться на важном, а оказывается, что проблема возникает из-за отброшенных ранее вариантов.
  8. Неизвестное. Невозможно учесть все. Например, в системе может быть функция, о которой тестировщику неизвестно, или некие источники искажений. Внутри приложения могут создаваться незадокументированные переменные и т.д. и т.п.

Еще несколько советов по работе с плавающими багами:

  • Всегда проверяйте базу. Характеристики компьютера, что тестируете, исходные данные в документах и по факту.
  • Всегда проверяйте информацию со стороны. Не задавайте вопросы в чате пользователю или коллеге, лучше сходите и посмотрите сами.
  • Если кажется, что все уже проверено от и до, причем несколько раз, попросите коллег посмотреть. Без вас и ваших рассказов, с минимумом вводных.
  • Поищите похожие кейсы и посмотрите, как решались такие вопросы раньше.
  • Сохраняйте все логи. Возможно вам придется сравнивать не только исходный и последний, а еще и промежуточные.
  • Проверяйте то, что на первый взгляд не имеет значения. Даже если вам кажется, что это вообще не относится к вопросу. Часто именно оно оказывается искомым.
  • Проверяйте конфликты на границах разных модулей и систем, особенно если интеграция происходит между продуктами разных разработчиков.
  • Если используете сторонние компоненты, обратитесь в техподдержку разработчика.
  • Если нет никакой информации, ничего не помогает, а ошибка по прежнему не найдена, дайте проблеме отстояться. Вернитесь к ней завтра, а сегодня переключите внимание на другие задачи.
  • Не отчаивайтесь. Ищите инструменты, которые помогут решить вопрос – однажды вы обязательно их найдете.
  • Соотнесите пользу от устранения бага и потраченные на это усилия. Иногда достаточно знать о наличии проблемы: это не баг, это фича.

***

Невыявленные ошибки могут приводить к опасным последствиям. В 1962 году космический аппарат Mariner должен был направиться к Венере, но сломалась антенна и он перешел на автопилот, полетев не туда. В итоге аппарат пришлось взорвать над Атлантическим океаном, а произошло это из-за пропущенного при программировании системы навигации в одной из формул символа. Большинство багов не настолько фатальны, да и системы поиска ошибок стали надежнее, но стремительный рост информационных технологий приводит и к росту сложности систем. Найти в них даже обычные ошибки становится все труднее, а количество плавающих багов увеличивается. Их устранение – уже не начальный уровень в работе тестировщика, но тем интереснее решать подобные задачи. Удачи вам в обучении!

Понравилась статья? Поделить с друзьями:
  • Как найти статическую ошибку тау
  • Как найти ошибку эксперимента
  • Как научиться не делать орфографические ошибки
  • Как найти ошибку средней величины
  • Как научиться не бояться ошибок