Що таке schrödinbug?


52

Ця вікі-сторінка повідомляє:

Schrödinbug - це помилка, яка виявляється лише після того, як хтось читає вихідний код або використовує програму незвичним чином, помічає, що він ніколи не повинен був працювати в першу чергу, і в цей момент програма негайно перестає працювати для всіх, поки не буде виправлено. Файл жаргону додає: "Хоча ... це звучить неможливо, це трапляється; деякі програми роками приховують приховані шрединбуги".

Про що говорять дуже розпливчасто ..

Чи може хтось навести приклад того, як виглядає schrödinbug (на кшталт вигаданої / реальної ситуації)?


15
Зауважте, що цитата сказана жартома.

11
Я думаю, вам краще зрозуміти shrodinbug, якби ви знали про кота Шродінгера: en.wikipedia.org/wiki/Shrodingers_cat
Еймантас

1
@Eimantas Я насправді зараз більше розгублений, але це цікава стаття :)

Відповіді:


82

На мій досвід, закономірність така:

  • Система працює, часто роками
  • Повідомляється про помилку
  • Розробник досліджує помилку і знаходить трохи коду, який, здається, є повністю помилковим, і заявляє, що він "ніколи не міг би працювати"
  • Помилка виправляється, і легенда про код, який ніколи не міг працювати (але робився роками), зростає

Давайте тут будемо логічними. Код, який ніколи не міг би працювати ... ніколи не міг би працювати . Якщо це все- таки спрацювало, тоді твердження є помилковим.

Тож я хочу сказати, що помилка точно так, як описано (тобто дотримання недосконалого коду перестає працювати) - явно нісенітниця.

Насправді те, що трапилося, - це одна з двох речей:

1) Розробник не повністю зрозумів код . У цьому випадку код звичайно безлад, і десь у ньому є велика, але не очевидна чутливість до якогось зовнішнього стану (скажімо, конкретна версія ОС або конфігурація, яка регулює, як деяка функція працює якимось незначним, але значущим чином). Ця зовнішня умова змінюється (скажімо, оновлення або зміна сервера, які, як вважають, не пов'язані), і тим самим призводить до порушення коду.

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

У цій ситуації тут слід розуміти, що думка про те, що "це ніколи не могло працювати" є помилково помилковою (тому що це було).

Це не означає, що переписування - це погано - часто це не так, хоча приємно точно знати, що було неправильно, це забирає багато часу та переписує розділ коду, швидше і дозволяє бути впевненим, що ви виправили речі.

2) Насправді це ніколи не працювало, просто ніхто його ніколи не помічав . Це дивно часто, особливо у великих системах. У цьому випадку хтось новий починає і починає дивитись на речі так, як ніхто не робив раніше, або бізнес-процес змінюється, приводячи якийсь раніше незначний крайовий випадок в основний процес, і те, що ніколи насправді не працювало (або працювало дещо, але не все час) знайдено та повідомляється.

Розробник дивиться на це і заявляє, що "це ніколи не могло б працювати", але користувачі кажуть "дурниці, ми використовуємо це роками", і вони начебто правильно, але те, що вони вважають неактуальним (і зазвичай не зазначають до розробник знаходить точне стан в який момент вони йдуть «о так, ми робимо , що в даний час і не робили раніше») змінилися.

Тут розробник має рацію - він ніколи не міг працювати і ніколи не працював.

Але в будь-якому випадку одна з двох речей:

  • Твердження «це ніколи не могло працювати» є правдивим і воно ніколи не працювало - люди просто думали, що це робиться
  • Це спрацювало, і твердження "він ніколи не міг би працювати" є помилковим і зводиться до (зазвичай розумного) нерозуміння коду та його залежностей.

1
У мене трапляється так часто
генеза

2
Чудове розуміння реалістичності цих ситуацій
StuperUser

1
Я б здогадувався, що зазвичай це результат "WTF" моменту. Я мав це колись. Я перечитав якийсь код, який я написав, і зрозумів, що нещодавно помічена помилка повинна призвести до руйнування всього додатка. Насправді, після подальшої перевірки, інший компонент, про який я писав, був настільки гарний, що компенсував помилки.
Thaddee Tyl

1
@Thaddee - я бачив це і раніше, але також бачу дві помилки в кодових модулях, які викликали один одного, скасовуючи один одного, так що він фактично працював. Подивіться або на одного, і вони були зламані, але разом вони були чудовими.
Джон Хопкінс

7
@Jon Hopkins: Я також отримав випадок, коли 2 помилки скасовували один одного, і це насправді дивно. Я знайшов помилку, озлобив сумнозвісну заяву «вона ніколи не могла б працювати», заглянув глибше, щоб зрозуміти, чому це все-таки працює, і знайшов ще одну помилку, яка виправляла перший, принаймні в більшості випадків. Я був дуже приголомшений відкриттям і тим фактом, що лише з однією з помилок, наслідок був би катастрофічним!
Олексій Дуфреной

54

Оскільки всі згадують код, який ніколи не мав би працювати, я наведу вам приклад, в який я потрапив, близько 8 років тому, про вмираючий проект VB3, який перетворювався на .net. На жаль, проект доводилося оновлювати до завершення версії .net - і я був єдиним, хто навіть віддалено зрозумів VB3.

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

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

Частина, позначена зіркою, мала найважливіший код; це була єдина частина, яка зробила фактичний розрахунок. Зрозуміло, це ніколи не повинно спрацювати, правда?

Знадобилося багато налагодження, але я врешті-решт знайшов причину: IsYearlyInterestModeбув True, і Not IsYearlyInterestModeтеж був правдою. Це тому, що десь уздовж рядка хтось кидає його на ціле число, а потім у функції, яка повинна встановити його на істина, збільшивши його (якщо це 0 для Falseнього, було б встановлено 1, а це VB True, тож я можу бачити логіку там), потім відкиньте його назад до булевого. І я залишився із станом, який ніколи не може відбутися і все-таки трапляється постійно.


7
Епілог: Я ніколи не фіксував цю функцію; Я просто зафіксував невдалий дзвінок на сайт, щоб надіслати 2, як і всі інші.
конфігуратор

значить, це означає, що він використовується, коли люди неправильно трактують код?
Pacerier

1
@Pacerier: Частіше, коли код такий безлад, він працює правильно лише випадково. У моєму прикладі жоден розробник не мав на IsYearlyInterestModeметі оцінювати як істинне, а неправдиве; оригінальний розробник, який додав кілька рядків (включаючи один з ifs, насправді не зрозумів, як це працює - це просто трапилось, щоб воно було досить добре.
конфігуратор

16

Не знаєте приклад у реальному світі, але спростіть його на прикладі ситуації:

  • Помилка не помічається протягом певного часу, оскільки програма не запускає код за умов, які спричиняють його збій.
  • Хтось це помічає, роблячи щось поза межами звичайного використання (або перевіряючи джерело).
  • Тепер, коли помилка помічена, додаток не працює і до нормальних умов, поки помилка не буде виправлена.

Це може статися через те, що помилка пошкодить певний стан програми, який спричинить збої в раніше нормальних умовах.


4
Одне пояснення полягає в тому, що в програмному забезпеченні сталися випадкові збої, що ніхто не зміг подумати зв'язок. Таким чином, ці помилки вважалися природними причинами (такими як випадкові збої обладнання). Після того, як вихідний код буде прочитаний, люди тепер зможуть пов'язати всі попередні випадкові помилки з цією однією причиною і зрозуміють, що це ніколи не повинно діяти в першу чергу.
rwong

4
Друге пояснення полягає в тому, що є частина програмного забезпечення, яке реалізується за схемою ланцюгової відповідальності. Кожен обробник написаний надійно, незважаючи на те, що один обробник має критичну помилку. Тепер перший обробник завжди вийде з ладу, але через те, що другий обробник (який має відповідальність перекриття) намагається виконати те саме завдання, здавалося б, загальна операція вдалася. Якщо в другому модулі є якісь зміни, такі як зміна зони відповідальності, це може спричинити загальний збій, хоча справжня помилка знаходиться в іншому місці.
rwong

13

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

У нас є велика внутрішня бібліотека корисних функцій, де я працюю. Одного разу я шукаю функцію, щоб зробити певну річ, і я Frobnicate()намагаюся її використати. У-о: виявляється, що Frobnicate()завжди повертається код помилки.

Вникаючи у реалізацію, я знаходжу деякі основні логічні помилки Frobnicate(), завдяки чому вона завжди виходить з ладу. У керуванні джерелами я бачу, що функція не була змінена з моменту її написання, що означає, що функція ніколи не працювала за призначенням. Чому ніхто цього не помітив? Я переглядаю іншу частину вихідного ресурсу та виявляю, що всі існуючі абоненти Frobnicate()ігнорують повернене значення (і тому містять тонкі помилки власних). Якщо я зміню ці функції, щоб перевірити повернене значення, як слід, то вони також почнуть виходити з ладу.

Це звичайний випадок №2, про який Джон Хопкінс згадував у своїй відповіді, і це гнітюче часто зустрічається у великих внутрішніх бібліотеках.


... що є вагомою причиною уникати написання внутрішньої бібліотеки там, де зовнішня є корисною. Він буде більш перевіреним і таким чином матиме набагато менше таких неприємних сюрпризів (переважні бібліотеки з відкритим кодом, тому що ви можете виправити їх, якщо вони все-таки будуть).
Ян Худек

Так, але якщо програмісти ігнорують коди повернення, це не вина бібліотеки. (До речі, коли ви востаннє перевіряли рекод printf()?)
JensG

Саме тому було винайдено перевірені винятки.
Кевін Крумвіде

10

Ось справжній Schrödinbug, який я бачив у якомусь системному коді. Кореневий демон повинен зв’язуватися з модулем ядра. Отже, код ядра створює дескриптори файлів:

int pipeFDs[1];

потім встановлює зв'язок по трубі, яка буде прикріплена до названої труби:

int pipeResult = pipe(pipeFDs);

Це не повинно працювати. pipe()записує два дескриптори файлу в масив, але є лише простір для одного. Але в протягом семи років він зробив роботу; у масиві траплялося раніше деякий невикористаний простір у пам'яті, який перетворився на дескриптор файлу.

Тоді одного разу мені довелося перенести код до нової архітектури. Він перестав працювати, і виявлено помилку, яка ніколи не мала працювати.


5

Наслідком Шрединбуга є Heisenbug - описує помилку, яка зникає (або зрідка з’являється) при спробі розслідувати та / або виправити її.

Heisenbugs - міфічні розумні маленькі відбійники, які бігають і ховаються під час завантаження налагоджувача, але виходять із дерев’яних виробів, як тільки ви перестали дивитися.

Насправді вони, як правило, викликані тим чи іншим з наступного:

  • вплив, що оптимізація, де компільований код -DDEBUGоптимізований на інший рівень, ніж збірка випусків
  • тонкі відмінності в часі через шини зв'язку в реальному світі або переривання, які тонко відрізняються від модельованих "ідеальних" манекенів

Обидва підкреслюють важливість тестування коду випуску обладнання для випуску, а також тестування блоку / модуля / системи за допомогою емуляторів.


Чому я не помітив відповіді S.Lote та коментаря delnan, перш ніж я опублікував це?
Андрій

Я мало досвідчив, але знайшов пару цього. Я працював в середовищі Android NDK. Коли налагоджувач знайшов точку розриву, він зупинив лише потоки Java, а не C ++, зробивши деякі виклики можливими, оскільки елементи були ініціалізовані на C ++. Якщо залишити без налагоджувача, код Java піде швидше, ніж C ++ і спробує використовувати значення, які ще не були ініціалізовані.
MLProgrammer-CiM

Я виявив Heisenbug під час використання API бази даних Django кілька місяців тому: Коли DEBUG = True, ім'я аргументу "параметрів" змінюється на необроблений запит SQL. Ми використовували це як ключове слово аргумент для ясності через тривалість запиту, який повністю зламався, коли прийшов час перейти на бета-сайт, деDEBUG = False
Ізката

2

Я бачив декількох Schödinbugs і завжди з тієї ж причини:

Політика компанії вимагала, щоб усі мали використовувати програму.
Ніхто насправді не використовував це (здебільшого тому, що для цього не було ніяких тренувань.)
Але вони не могли сказати цьому управління. Тому всі повинні були сказати: "Я користувався цією програмою 2 роки і ніколи не стикався з цією помилкою до сьогодні".
Програма ніколи не працювала, за винятком меншості користувачів (включаючи розробників, які її написали.)

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


1

У мене є приклад із власної історії, це було якихось 25 років тому. Я в дитинстві займався рудиментарним графічним програмуванням в Turbo Pascal. TP мала бібліотеку під назвою BGI, яка включала деякі функції, які дозволяють скопіювати область екрану в блок пам'яті на основі вказівника, а потім перекрити його в іншому місці. У поєднанні з xor-blitting на чорно-білому екрані це можна було б використовувати для простої анімації.

Я хотів зробити це на крок далі і зробити спрайти. Я написав програму, яка намалювала великі блоки та елементи керування для їх забарвлення, як ви це робили, відтворюючи їх у вигляді пікселів, створюючи просту програму малювання для створення спрайтів, яку вона потім могла скопіювати в пам'ять. Була лише одна проблема, щоб користуватися цими розмитими спрайтами, їх потрібно було б зберегти у файл, щоб інші програми могли їх читати. Але TP не змогла серіалізувати розподіл пам'яті на основі покажчика. У посібниках випливає, що вони не можуть бути записані у файл.

Я придумав фрагмент коду, який успішно написав у файл. І почав писати тестову програму, яка виблискувала спрайт з моєї програми малювання на задньому плані - на шляху до створення гри. І це спрацювало, прекрасно. Однак наступного дня він перестав працювати. Це не показувало нічого, крім шаленого безладу. Це більше ніколи не спрацювало. Я створив новий спрайт, і він спрацював ідеально - до цього не сталося, і знову було пошкоджене безладдя.

Минуло багато часу, але врешті я зрозумів, що відбувається. Як я думав, програма малювання не зберегла скопійовані піксельні дані у файл - це збереження самого вказівника. Коли наступна програма прочитала файл, він закінчився вказівником на той самий блок пам'яті - який все ще містив те, що там написала остання програма (це було в MS-DOS, управління пам'яттю не було). Але це спрацьовувало ... до тих пір, поки ви не перезавантажили або не запустили все, що повторно використало ту саму область пам’яті, а потім ви зіткнулися із занепокоєнням, оскільки ви блокували купу абсолютно не пов’язаних даних із блоком відеопам'яті.

Він ніколи не повинен був працювати, він ніколи навіть не мав би працювати (і в будь-якій реальній ОС цього не було б), але все одно це було, і коли він зламався - він залишався зламаним.


0

Це відбувається весь час, коли люди використовують налагоджувачі.

Середовище налагодження відрізняється від реального виробничого середовища - без налагодження.

Запуск з налагоджувачем може замаскувати такі речі, як переповнення стека, оскільки кадри стека налагоджувача маскують помилку.


Я не думаю, що це стосується різниці між кодом, який працює у відладчику, і коли компілюється.
Джон Хопкінс

26
Це не schrödinbug, це гайзенбаґ .

@delnan: Це на краю, ІМО. Я вважаю, що це невизначена річ, оскільки існують невідомі ступені свободи. Мені подобається зарезервувати heisenbug для речей, де вимірювання однієї речі насправді порушує інше (тобто умови перегонів, налаштування оптимізатора, обмеження пропускної здатності мережі тощо)
S.Lott

@ S.Lott: Ситуація, яку ви описуєте, передбачає спостереження, що змінюють речі, возившись з рамками стека тощо. (Найгірший такий приклад, який я коли-небудь бачив, - це те, що налагоджувач мирно і «правильно» виконає навантаження недійсних значень реєстру сегмента в режимі однокрокової дії. Результатом стали деякі підпрограми в RTL, які постачаються, незважаючи на завантаження вказівника реального режиму, перебуваючи в захищеному режимі . Оскільки він був лише скопійований і не відписаний, він поводився чудово.)
Лорен Печтел

0

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

Швидше щось змінилося, що виявило помилку, яка ховається століттями. Що б там не змінилося, все одно змінюється, і, таким чином, помилка постійно з’являється, в той же час хтось виявляє помилку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.