Подтвердить что ты не робот

Как вы относитесь к прерывистым ошибкам?

Сценарий

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

Проблема

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

Вопрос

Как вы проводите проверку своих изменений?

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Чтобы определить основную причину: если ваша платформа разрешает это, подключите некоторую посмертную отладку в проблему.

Например, в Windows, получите код, чтобы создать файл minidump (core dump в Unix), когда он сталкивается с этой проблемой. Затем вы можете получить клиента (или WinQual, в Windows), чтобы отправить вам этот файл. Это должно дать вам больше информации о том, как ваш код поступил неправильно в производственной системе.

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

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

Ответ 3

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

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

Иногда вы "знаете" (или, скорее, чувствуете), что можете исправить проблему без обширного тестирования или модульного тестирования строительных лесов, потому что вы действительно понимаете проблему. Однако, если вы этого не сделаете, это очень часто сводится к чему-то вроде "мы запустили его 100 раз, и ошибка больше не произошла, поэтому мы рассмотрим ее исправленную до следующего раза, когда она сообщит".

Ответ 4

Я использую то, что я называю "защитным программированием тяжелого стиля" : добавлять утверждения во все модули, которые, по-видимому, связаны с проблемой. Я хочу сказать, что добавить много утверждений, утверждает доказательства, утверждает состояние объектов во всех своих членах, утверждает состояние "environmentnement" и т.д.

Помогает вам идентифицировать код, который НЕ связан с проблемой.

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

Ответ 5

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

Ответ 6

Сначала вам нужно получить трассировку стека от ваших клиентов, таким образом, вы можете на самом деле сделать некоторые судебные экспертизы.

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

Ответ 7

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

Вот некоторый рискованный код в c:

const int NITER = 1000;
int thread_unsafe_count = 0;
int thread_unsafe_tracker = 0;

void* thread_unsafe_plus(void *a){
  int i, local;
  thread_unsafe_tracker++;
  for (i=0; i<NITER; i++){
    local = thread_unsafe_count;
    local++;
    thread_unsafe_count+=local;
  };
}
void* thread_unsafe_minus(void *a){
  int i, local;
  thread_unsafe_tracker--;
  for (i=0; i<NITER; i++){
    local = thread_unsafe_count;
    local--;
    thread_unsafe_count+=local;
  };
}

который я могу проверить (в среде pthreads) с помощью:

pthread_t th1, th2;
pthread_create(&th1,NULL,&thread_unsafe_plus,NULL);
pthread_create(&th2,NULL,&thread_unsafe_minus,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
if (thread_unsafe_count != 0) {
  printf("Ah ha!\n");
}

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

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

Ответ 8

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

Мое эмпирическое правило: я не исправляю его, если не могу воспроизвести его самостоятельно, или мне представлен журнал, который явно показывает что-то не так. В противном случае я не могу подтвердить свое изменение, и я не могу проверить, что мое изменение ничего не сломало. Конечно, это просто эмпирическое правило - я делаю исключения.

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

Ответ 9

В этой ситуации, где больше ничего не работает, я ввожу дополнительную регистрацию.

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

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

Даже если ничего не появляется, вы сужаете вещи. Так или иначе, вы получите полезные теории.

Ответ 10

Некоторые вопросы, которые вы могли бы задать себе:

  • Когда этот фрагмент кода работал без проблем.
  • Что было сделано, так как оно перестало работать.

Если код никогда не работал, подход был бы естественным.

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

Ответ 11

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

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

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

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

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

Ответ 12

Конкретный сценарий

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

Проблема возникает, когда пользователь взаимодействует с пользовательским интерфейсом (точнее, с помощью TabControl) на определенной фазе процесса. Это не всегда происходит, и я считаю, что это потому, что окно времени для выставления проблемы невелико. Мое подозрение в том, что инициализация UserControl (мы в .NET с использованием С#) совпадает с событием изменения состояния из другой области приложения, что приводит к удалению шрифта. Между тем другой элемент управления (метка) пытается нарисовать свою строку с этим шрифтом и, следовательно, сбой.

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

Подход

Мой подход был первым, чтобы посмотреть трассировку стека из наших отчетов о сбоях и изучить код Microsoft с помощью Reflector. К сожалению, это привело к вызову GDI + с небольшой документацией, которая возвращает только число ошибок .NET превращает это в довольно бесполезное сообщение о том, что что-то недействительно. Отлично.

Оттуда я пошел посмотреть, какой вызов в нашем коде приводит к этой проблеме. Стек начинается с цикла сообщений, а не в нашем коде, но я нашел вызов Update() в общей области под подозрением и, используя контрольно-измерительные приборы (следы и т.д.), Мы смогли подтвердить примерно на 75% уверенность в том, что это был источником сообщения краски. Однако он не был источником ошибки - просить ярлык рисовать не было преступлением.

Оттуда я посмотрел на каждый аспект вызова краски, который был сбой (DrawString), чтобы увидеть, что может быть недействительным, и начал управлять каждым из них до тех пор, пока он не упадет на одноразовые элементы. Затем я определил, какие из них мы контролировали, а шрифт был единственным. Итак, я взглянул на то, как мы обрабатывали шрифт и при каких обстоятельствах мы его использовали, чтобы определить любые потенциальные причины. Я смог придумать правдоподобную последовательность событий, которые соответствуют отчетам пользователей, и, следовательно, могут кодировать исправление с низким уровнем риска.

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

Заключение

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

Ответ 13

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

Второй шаг - это, как правило, утверждение допущений и оценок границ. Все, что вам кажется, вы знаете о коде, о котором идет речь, напишите. В частности, проверьте объекты на nullity и (если ваш язык является динамическим).

В-третьих, проверьте охват unit test. Выполняют ли ваши модульные тесты фактическое покрытие каждой вилки в исполнении? Если у вас нет модульных тестов, это, вероятно, хорошее место для начала.

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

Ответ 14

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

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

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

Ответ 15

Эти проблемы всегда были вызваны:

  • Проблемы с памятью
  • Проблемы с потоками

Чтобы решить проблему, вам необходимо:

  • Инструмент вашего кода (Добавить записи журнала)
  • Обзор просмотра кода
  • Кодирование Распределение памяти памяти/разыменование

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

Ответ 16

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

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

Ответ 17

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

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

Инструмент кода (журнал, как сумасшедший). Работайте с вашим QA - они хорошо воссоздают проблему, и вам необходимо организовать полный набор инструментов для разработчиков, доступных вам на своих машинах. Используйте автоматические инструменты для неинициализированной памяти/ресурсов. Просто взгляните на код. Там нет простого решения.

Ответ 18

Просто: спросите пользователя, который сообщил об этом.

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

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

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

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

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

[1] Если вы когда-либо сообщали об ошибке, вы, скорее всего, знаете, что много раз ответ от команды разработчиков/обслуживания как-то отрицателен с точки зрения конечных пользователей или вообще не будет ответа - особенно в ситуации, когда ошибка не может быть воспроизведена командой разработчиков.