Что такое гейзенбаг: история термина и примеры

Это — справочный материал о гейзенбагах. Говорим о том, как они выглядят и какое отношение имеют к мейнфреймам — прародителям облака.
a9cwbrou8imoxsds7bsaevcw6sy.jpeg
/ фото Lars Zimmermann CC BY

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

История


Автором термина «гейзенбаг» считается сотрудник исследовательского центра IBM Брюс Линдсей (Bruce Lindsay). Он внес вклад в развитие реляционных баз данных и занимался разработкой корпоративной СУБД IBM System R.

В 1985 году во время учебы в университете Беркли Брюс и Джим Грей (James Nicholas Gray), американский ученый в области теории вычислительных систем, трудились над ОС CAL-TSS. Она писалась специально для двухпроцессорного мейнфрейма Control Data 6400 [PDF, стр. 3], на котором военные обрабатывали большие объемы данных.

Само собой, в процессе разработки возникали баги. Но несколько из них были особенными — как только инженеры пытались их исправить, они исчезали. В то время Линдсей как раз изучал физику и принцип Гейзенберга в частности. Внезапно Линдсея осенило — они с Греем стали свидетелями аналогичного явления: ошибки исчезали, потому что наблюдение влияло на свойства объекта. Отсюда и пошло название «гейзенбаг».

Эту истории Линдсей рассказал в интервью с представителями Ассоциации вычислительной техники (ACM) в 2003 году.

Примеры гейзенбагов


Пользователи в сети и на тематических платформах вроде Stack Overflow поделились несколькими примерами гейзенбагов, с которыми они встречались в своих проектах. Один из резидентов SO пытался вычислить площадь фигуры между двумя кривыми с точностью до трех знаков после запятой. Для отладки алгоритма на C++ он добавил строку:

cout << current << endl;


Но как только он её закомментировал, код перестал работать и зациклился. Программа выглядела следующим образом:

#include 
#include 

using namespace std;

double up = 19.0 + (61.0/125.0);
double down = -32.0 - (2.0/3.0);
double rectangle = (up - down) * 8.0;

double f(double x) {
return (pow(x, 4.0)/500.0) - (pow(x, 2.0)/200.0) - 0.012;
}

double g(double x) {
return -(pow(x, 3.0)/30.0) + (x/20.0) + (1.0/6.0);
}

double area_upper(double x, double step) {
return (((up - f(x)) + (up - f(x + step))) * step) / 2.0;
}

double area_lower(double x, double step) {
return (((g(x) - down) + (g(x + step) - down)) * step) / 2.0;
}

double area(double x, double step) {
return area_upper(x, step) + area_lower(x, step);
}

int main() {
double current = 0, last = 0, step = 1.0;

do {
last = current;
step /= 10.0;
current = 0;

for(double x = 2.0; x < 10.0; x += step) current += area(x, step);

current = rectangle - current;
current = round(current * 1000.0) / 1000.0;
//cout << current << endl; //<-- COMMENT BACK IN TO "FIX" BUG
 } while(current != last);

cout << current << endl;
return 0;
}


Суть гейзенбага: когда нет printout, программа выполняет сравнение с высокой точностью в регистрах процессора. При этом точность результата превышает возможности double. Для вывода значения компилятор возвращает результат вычислений в основную память — при этом дробная часть отбрасывается. И последующее сравнение в while приводит к верному результату. Когда строчка закомментирована, неявного усечения дробной части не происходит. По этой причине два значения в while всегда оказываются неравными друг другу. В качестве решения проблемы один из участников обсуждения предложил использовать приближенное сравнение чисел с плавающей запятой.

Еще одной историей про гейзенбаг поделились инженеры, работавшие со средой языка Smalltalk-80 на Unix. Они заметили, что система зависала, если оставить её на какое-то время без дела. Но после перемещения курсора мыши, все вновь работало как обычно.

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

Другие *баги


Есть еще ряд терминов, которые описывают разного рода ошибки: Борбаг, Мандельбаг, Шрёдинбаг.

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

cyloc8lxtugz8gs5v5_3ysquk84.jpeg
/ фото OLCF at ORNL CC BY

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

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

Что в итоге


Примеров гейзенбагов (и других *багов) — множество. Их очень сложно искать, но причины возникновения обычно банальны: неинициализированная переменная, ошибки синхронизации в многопоточной среде или проблемы с алгоритмами удаления «мёртвого» кода. Получается, что для борьбы с подобными ошибками их нужно отсекать еще на этапе проектирования приложения.
Из блога о корпоративном IaaS:

© Habrahabr.ru