Comments 86
Или реальная попытка заставить людей думать и гуглить, прежде чем задавать вопрос?
Скажем, если человек не знает, что полезно делить программу на кусочки в процессе отделки, то ему можно кинуть ссылку на эту статью.
- Баг в компиляторе.
- Баг в процессоре.
Несмотря на то, что они не должны встречаться в программах на 20 строчек, иногда бывает.
Как думаете, сколько времени тупил программист, когда у него
2.0 == 16.0
выдавало true
? Угу, производитель ЭВМ потом тоже сильно удивлен был, ибо умудрились забыть сравнение ординат (экспонент) для вещественных чисел.Кстати, уже чисто интересно с исторической точки зрения — где и когда существовала терминология, что порядок вещественного числа назывался «ординатой»? Я такого не нагугливаю, хотя времена, когда ЭВМ были большие, ещё застал лично.
<sarcasm> Разработчики компилятора OCaml тоже так думали </sarcasm>
А если серьёзно, то наивно, конечно, ожидать, что вот только начал программировать, написал 20 строчек кода и словил глюк процессора. Но не удивлюсь, если новичок, напоровшийся на undefined behavior в C, будет уверен, что наткнулся на баг в компиляторе, который неправильно компилирует.
Если кто не видел эту чудесную историю. Но тут уж и близко не новичок, конечно.
Ну так и я находил баг в GCC (репорт принят и подтверждён), но речь-то (без сарказма) о вероятности каждого конкретного случая…
> Но не удивлюсь, если новичок, напоровшийся на undefined behavior в C, будет уверен, что наткнулся на баг в компиляторе, который неправильно компилирует.
А так и происходит. В трекере GCC при отсылке нового тикета сразу пишут — «если ваша программа начинает работать при -fwrapv, ищите ошибку у себя», а количество закрытых на undefined behavior тикетов идёт на тысячи.
Фишка в том, что все мы идем проторенными путями. И только новичок легко пишет настолько нестандартный код, что вызывает баг.
А объяснять новичкам, что это не баг в компиляторе, а баг в прокладке между экраном и клавиатурой — каждому приходилось. И не один раз. Не об этом речь.
Ну во всяком случае больше тысячи. Как-то не считал всю длину своей карьеры, даже если ограничивать C. :)
> Фишка в том, что все мы идем проторенными путями. И только новичок легко пишет настолько нестандартный код, что вызывает баг.
Да, этот фактор нельзя списывать. Собственно, я тот принятый баг нашёл, экспериментируя с тем, чем ещё ни разу не пользовался.
Но — я при этом имел достаточно общего опыта, чтобы поймать и описать баг. А новичок — таким навыком не владеет. Собственно, исходная статья именно на формирование такого навыка и направлена. Если он после этого всего будет всё равно уверен, что баг не у него — тогда имеет смысл смотреть кому-то существенно опытному.
> А объяснять новичкам, что это не баг в компиляторе, а баг в прокладке между экраном и клавиатурой — каждому приходилось. И не один раз. Не об этом речь.
Именно что исходная статья задаёт именно «это», о котором речь. И я стараюсь держаться в этих рамках.
Новичок новичку рознь. У нас человек попросился на практику (учится в тезникуме). Ну коллега ему и дал стыковку проца с SDRAM (другой проц, чем в примере выше). А в стыковках микросхем очень много такого, что называется «баг документации». То есть из 10 способов сделать в соответствии с докой — будут работать 1-2. А остальные — не будут. И багом это никто не считает. Обычное дело — что-то не рассказано в доке.
Такого очень много в тех же GPS-чипах или в GSM-модемах. И новичку надо понимать, что неописанных в документации особенностей — дофига.
И надо не искать баги (хотя это полезно), а искать способ работы несмотря на баги и лакуны в доках.
А meassge такой: не надо считать, что баг в железке, документации, процессоре, компиляторе — это редчайшая экзотика. Как только вы от области, где пасутся миллионы пользователей переходите туда, где пользователей всего лишь сотни — баг становится весьма вероятен.
В подтверждение — описание эпохального бага в ИКД ГЛОНАСС (от авторов формата RINEX)
К сожалению, в интерфейсном документе ГЛОНАСС, в одной из формул была допущена ошибка в знаке.
Значения должны быть записаны в RINEX файл как -TauN, +GammaN, -TauC.
Первоначальное определение требовало указания -TauN, -GammaN, +TauC. См. параграф 8.2.
Десятки подписей от минобороны, космических войск, разработчиков — и банальный баг в знаке.
Почему вы уверены что все вокруг только делают что ковыряются с железками? Большинство новичков либо клепает сайты либо пишет мобильные приложения (прикладные!). Даже десктопные приложения — уже не в тренде.
И вот тут-то натолкнуться на баг в процессоре уже очень тяжело.
Не видели как мобильники виснут? Это не баг софта, это или банальная трещина в многослойной плате или окислились дорожки из-за попадания влаги.
Чтобы нарваться на баг в браузере — не надо быть программистом, достаточно просто много серфить. Падает и IE и FireFox и Chrome. Не так часто — но падает. Разок было не лень — кинул в багзиллу по FireFox, исправили.
А уж веб-разработка… Мама мия!!! Не, я, конечно, полный чайник в вебе. Делали мы web-интерефейс к одной железке, ну и кроме своего кода пришлось ещё и повозиться с отладкой сайта. Ну как бы проще самому исправить, чем пинать фрилансера.
Уж не знаю, баги это или не баги, но из десятка способов сделать одно и то же, во всех браузерах одинаково работал один. Остальные — или дают артефакт в одном из браузеров или вообще не работают. Самый норовистый — это, разумеется, был IE 8. Повезло — отспорили, что IE 7 не будет. :-)
Так что баг «в процессоре», знаком почти всем, у кого хватило денег на мобильник. Баги в софте — все, кто активно серфит. А вот баги в коде — только для программистов.
Ну тут скорее вопрос о соблюдении тем или иным браузером соглашений по HTML, CSS, JS… если я вас правильно понял. А сделать кроссбраузерную штуку… да еще и на несколько версий… вы, батенька, знаете толк))
Так что только кросброузерно. И ещё желательно угадать, чтобы хотя бы через 10 лет не перестала работать. Радиопередатчики — они такие, и по 20-30 лет живут. Почему наш интерфейс должен сдохнуть первым?
При написании комментариев пользуйтесь галочкой про markdown, пожалуйста.
Из недавнего от коллеги. Не проходит инициализация ОЗУ. 10 раз проверили пайку и код. Спаяли плату с другим ОЗУ по тому же стандарту JEDEC — работает. И пара реплик на форумах от американцев, что не только у нас так. Ну видимо будет в errata лишний раздел. :-)
Электротехника — наука о контактах. Когда вы отлаживаете самодельную плату, шансы на плохой контакт или разведенную не туда дорожку не так уж малы. В отдельных случаях — оно даже вероятней бага в коде.
Кстати, я бы добавил к инструментам отладки осциллограф, логический пробник и микроскоп (пайку проверять). Не так уж и редко приходится использовать. :-)
Баг в компиляторе — это, конечно, редкость. Но — бывает. В любом случае, когда компилятор вылетает во время компиляции — это баг. В компиляторе, в конкретном порте, в библиотеке, в процессоре (!!!!) — надо разбираться. Был один интеллевский процессор с отбитым уголком — очень интересно себя вел. На тестах — все хорошо, а в реальной работе иногда падал.
Скажем, если брать современные андроиды, то большинство их вылетов и зависаний — это трещины на плате, а не баги в коде.
Ордината (вместо порядка) — это термин из школьного курса математики примерно 1980ого года. Связан был скорее всего с использованием лографимической и полулографимической бумаги. А что не нагугливаете — странно. Вот вам раз, два, три, четыре, пять, шесть, семь. Забавно, что для десятичной записи меня тоже тянет использовать слово «порядок», а вот для двоичной «ордината».
Очень малой — это меньше миллионной того, что баг у автора кода.
Ну что вы. Шансы от равных (для собственной платы) до одной сотой (более-менее отлаженное железо). Шансы малы лишь на персоналках и то, пока сильно нестандартные части ядра не используем. Ибо в том же линуксе нарваться на баг ядра или драйвера можно довольно легко.
«This document is only available in a PDF version to registered ARM customers.» Спасибо, но «кортинки не грузяццо» ©.
Впрочем, идею я понял. Как верно заметил yeputons@, Вы таки «страж ночи». И взгляд соответствующий.
Но я таки уверен, что автор исходной статьи подразумевал тех, кто в самом крайнем случае ищет проблемы в том, что он написал под эмулятор MIPS для университетского курса (таких валом валит в SO /assembly), а скорее всего под банальный x86, и скорее всего даже не под C. И там названные мной цифры вероятности более справедливы.
> Шансы малы лишь на персоналках и то, пока сильно нестандартные части ядра не используем. Ибо в том же линуксе нарваться на баг ядра или драйвера можно довольно легко.
Именно. И они за пределами контекста.
> Ордината (вместо порядка) — это термин из школьного курса математики примерно 1980ого года. Связан был скорее всего с использованием лографимической и полулографимической бумаги. А что не нагугливаете — странно. Вот вам раз, два, три, четыре, пять, шесть, семь.
Я гуглил фразой «ордината вещественного числа» и её вариациями. Наверно, если бы ввёл «мантисса и ордината», нашлось бы. Тут, как обычно, надо заранее знать половину ответа, чтобы правильно задать вопрос :)
Что курс района 80-го года — показывает, почему я уже такого не знал — я в 80-м только в 1-й класс пошёл :) а где-то через пару лет была заметная мутация программ и учебников.
Мы вас поздравляем, но речь в треде идёт об ошибках новичков.
Вам уже говорили, что вероятность встретить баг в процессоре или компиляторе у новичка в миллион раз меньше, чем в собственном коде.
P.S. Зависший мобильник — это, как правило, баг железа. Трещина в плате или окислившиеся дорожки из-за попадания влаги.
%)
Ты реально тупой или придуриваешься? Речь идёт о "неправильном" поведении только что написанного новичком кода, который объясняется багом в железе.
Утверждение о зависшем мобильнике видится мне как минимум сомнительным. Все зависания, с которыми я лично сталкивался, лечились перезагрузкой.
Пожалуй, в очередной раз попрошу пруфы на статистику.
Ну да почистили память и проблема решена. Костыль, надо исправлять! (Уровень JVM/Native(Если запущенно из /system/bin
) если Android либо телефон на JEEMP{Точное название не помню}, если виснет телефон с WP/W10M(Уровень ОС) то это уже нехватка памяти либо ОСь шалит, если проявляются глюки на экране или происходит ересь на аппарате то случай Jef239 (Это редко когда происходит), вирусня после прошивки{Да есть такие телефоны} производитель залил значит{Такой телефон на свалку сразу.}, вроде все варианты которые могу перечислить.)
Какие шансы, что написанный новичком сайт будет одинаково работать во всех браузерах?
Да почти нулевые. Обязательно где-то что-то не реализовано, где-то что реализовано не так, где-то что-то задокументировано не так, как реализовано…
Только если писать на C++ в рамках учебных программ на одном компиляторе — шансы налета минимальны. А на том же С++ как сразу 7 операционок и 7 компиляторов — так сразу и опаньки. Тут так отступили от стандарта, тут эдак…
Вот именно, что зависания лечатся перезагрузкой, а не обновлением прошивки. И никакое обновление от них не спасает. Зависания — это прежде всего плохой контакт. На переходах между слоями платы, на стыке к процессора (тот самый реболлинг), на самих дорожках (окисление от воды). А на исправном железе — это долго надо потрудиться, чтобы тот же андроид завесить. я не уверен, что это вообще возможно.
А на исправном железе — это долго надо потрудиться, чтобы тот же андроид завесить.
Трудиться сильно не нужно, нужно только заставить приложение выдавать мусор в большом объеме, тут тебе и зависшее устройство и плата в порядке. (Простой пользователь до этого может додуматься, но не станет этого делать.)
Какие шансы, что написанный новичком сайт будет одинаково работать во всех браузерах?
А если новичок не использует CSS, JS, а только HTML 4.01 Strict?
Он то точно должен отображаться везде одинаково нормально.
Зависания — это прежде всего плохой контакт.
Отнюдь не всегда, одной не документированной инструкции или ошибки в микрокоде будет достаточно чтобы положить весь аппарат.
Вот именно, что зависания лечатся перезагрузкой, а не обновлением прошивки.
А если аппаратная перезагрузка или выключение не предусмотрено либо не желательно?
А если новичок не использует CSS, JS, а только HTML 4.01 Strict?Если strict, а не transitional — значит уже не новичок. А отображаться одинаково — должен, вот только кому он задолжал? Явно не мне. :-)
Он то точно должен отображаться везде одинаково нормально.
По личному чайниковскому опыту, создать сайт, который отображается везде одинаково — это высокое искусство, доступное лишь профессионалам.
А если аппаратная перезагрузка или выключение не предусмотрено либо не желательно?
В таком случае делают разного рода watchdog, перезагрузку одного процессора другим и так далее. Ну в общем можно сделать достаточно неубиваемо, даже несмотря на баги в коде.
Если strict, а не transitional — значит уже не новичок.
По заданию для новичков дают strict чтобы освоились, на transitional сами потом ковыляют переходят.
По личному чайниковскому опыту, создать сайт, который отображается везде одинаково — это высокое искусство, доступное лишь профессионалам.
Да это искусство, но не только доступное лишь для профессионалов, просто нужно время.
В таком случае делают разного рода watchdog, перезагрузку одного процессора другим и так далее.
Могут и выпилить Watchdog, все CPU можно также заставить быть перегруженными. (Подобная ситуация на портативных устройствах не встречается часто.)
Ну в общем можно сделать достаточно не убиваемо, даже несмотря на баги в коде.
Так можно сделать, но нужно время.
Я в своей жизни тоже находил ошибки в компиляторах и репортил их. Но опечатки в своём коде я находил существенно чаще. Люди просто не запоминают свои тупые ошибки, зато легко запоминают чужие.
В любом случае, при стыковке с разными микросхемами/железками проблема не в том, как бы найти баг. Проблема в том, чтобы найти путь, на котором устройство работает без багов.
А своих собственных багов — полно у всех, это не новость. Мне об этом компилятор каждый день намекает.
Очень забавно, что «баг» нашелся в стандарте С++.
В несколько упрощенном виде формулировка из стандарта выглядит так:
«выражение, содержащее в качестве своего самого левого подвыражения явное преобразование типа, которое записано в функциональном стиле, может быть неотличимо от объявления, в котором первый декларатор начинается с левой круглой скобки». Классический пример: что такое T(a); если T — некоторый тип? С одной стороны, это как бы объявление переменной с именем a, тип которой задан как T. С другой — конструкцию можно трактовать как преобразование типа уже объявленной где-то ранее переменной a к типу T.
Ну вы же понимаете, что статья не о тех случаях, о которых вы говорите? :-)
Не знаю уж, как это классифицировать, как баги в браузерах или лакуны в документации к ним.
Ладно, отладился, повезли ставить. А там оказалась чуть более старая версия IE8. И никаких шансов на обновление — ибо береговая радиостанция морской связи. Ну в общем заработало почти всё. Но не всё. :-)
Впрочем, мытарства JavaSript-девелопера, который захочет написать переносимую программу на Си — я вполне себе представляю. Хрен редьки не слаще.
P.S. Более того, начинать учить деток прямо с ардуино — это верный путь. Только про баги железок забывать не надо.
arduino.stackexchange.com/questions/34501/problem-with-pwm-interference
Где то в 2006 году когда у меня был отлажен алгоритм на GPU для которого нужен был работающий X server, американский коллега (тоже программист с большим стажем) из моей же компании спрашивает " а как запустить эту программу X?". Я ему ответил чтоб принципе достаточно набрать «X (uppercase ) а затем „enter“
Нет ни одной причины, по которой корректная программа в 20 строк кода могла бы получить предупреждение компилятора.
Есть: «переменная нигде не используется».
разбить код на методы поменьше
20 строк кода разбить код на методы поменьше? Сколько методов м.б. в программе в 20 строк?!
Сегодня отличный день, чтобы научиться отлаживать код самостоятельно, потому что StackOverflow не будет этим заниматься вместо вас.
Будет! И до StackOverflow (и кроме) этим занимаются много добрых людей (и я в том числе) — 20 строк кода в знакомой мне задаче — полная ерунда. Специально не ищу, но если случайно попалось на глаза и сразу вижу ошибку, то почему не помочь? Тем более, что на некоторых сайтах за такую помощь плюсуют — мне приятно, а человеку польза…
Поэтому: не бойтесь спрашивать по существу!
Есть: «переменная нигде не используется».
По-моему, это отличный повод убрать эту переменную нафиг и уменьшить количество строк в программе. Если она не используется и ни на что не влияет, то в отладке будет мешать.
20 строк кода разбить код на методы поменьше? Сколько методов м.б. в программе в 20 строк?!
В крайнем случае: одна строка — один метод. Ну, скажем, в практически любом домашнем задании решение всегда можно разделить на ввод, обработку данных и вывод. Обработка также обычно состоит из нескольких шагов: например, можно данные переложить из структуры в структуру, подсчитать какой-то кэш (скажем, map из строки в её порядковый номер), подсчитать ответ на задачу.
Специально не ищу, но если случайно попалось на глаза и сразу вижу ошибку, то почему не помочь?
+1
Поэтому: не бойтесь спрашивать по существу!
+1, но отлаживать самостоятельно тоже хорошо бы научиться. Мне кажется, если всё сваливать на добрых людей, то непонятно, как они находят самые коварные баги, хотя там всё более-менее алгоритмизированно (по модулю знаний о специфических подставах языка). Так что тут нужна, прошу прощения, «золотая середина».
По-моему, это отличный повод убрать эту переменную нафиг и уменьшить количество строк в программе. Если она не используется и ни на что не влияет, то в отладке будет мешать.
Верно. Я только придрался к утверждению «Нет ни одной причины...» Т.е., если в программе есть лишние переменные, то они не помешают правильной работе.
В крайнем случае: одна строка — один метод.
Зависит от языка и от принятых форматов («елочка») в этом языке. У меня простейшие методы больше занимают:
procedure TGraph.addEdge (v,u : integer);
begin
addEdgeA (v,u);
public
procedure addEdge (v,u : integer); // добавить ребро (v,u)
end;
+ определение класса:
TGraph = class(TObject)
protected
procedure addEdgeA (v,u : integer); virtual; abstract;
end;
но отлаживать самостоятельно тоже хорошо бы научиться.
Полностью согласен. Обязательно надо научиться.
по модулю знаний о специфических подставах языка). Так что тут нужна, прошу прощения, «золотая середина».
Ok. И не только языка, но и библиотек. Можно проработать 10 лет и хорошо уметь отлаживать самостоятельно, но подключаешь новую библиотеку, которую не использовал, т.к. не было задачи, и оказываешься новичком.
Первый:
procedure TGraph.addEdge (v,u : integer);
begin
addEdgeA (v,u);
end;
второй:
TGraph = class(TObject)
protected
procedure addEdgeA (v,u : integer); virtual; abstract;
public
procedure addEdge (v,u : integer); // добавить ребро (v,u)
end;
(Почему на Хабре так мало времени на редактирование? -Инет бывает ооооочень задумчив!)
По-моему, это отличный повод убрать эту переменную нафиг и уменьшить количество строк в программе. Если она не используется и ни на что не влияет, то в отладке будет мешать.
unique_lock<mutex> lock(_mutex);
Медвежья услуга.
Все вышеописанное, конечно, хорошо и правильно, но для программы из 20 строк многое из вышеперечисленного просто излишне.
И для экономии времени и нервов проще запустить пошаговую отладку и посмотреть, как ведет себя каждая из выполняющихся строк.
for i:=1 to 1000000 do
a[random(1000000)] := a[random(1000000)] ;
;)
Пошаговая отладка — дело творческое.
В данном случае типовые действия такие:
Отрабатываем несколько первых итераций.
Ставим точку останова с условием на переменную цикла в теле цикла (чтобы поймать последнюю итерацию) и точку останова после цикла.
Смотрим на каждой точке останова состояние переменных и изменившуюся память, если исключение не вылезло.
Если что-то не так или исключение, то уже думаем, что в данном конкретном цикле (а не во всей программе) может быть не так.
И всё же исключительно важно умение найти ошибку в программе, не запуская его. В реальной жизни может сэкономить кучу времени и денег. Этому и учит статья.
Ну и как бы бесполезно по шагам отлаживать, если сам толком не понимаешь, для чего нужен каждый шаг. А у начинающих программистов это частая ситуация.
Во-первых, в статье и про проверку запуском поминается, и отладчик не забыт.
Во-вторых, повторюсь, статья правильная. Но в качестве отправной точки постоянно фигурирует программа новичка из 20 строк.
И именно для новичка с такой программой полезно будет сразу запустить отладчик, который покажет наглядно, как ведет себя каждая строка, станет той самой "уточкой", которой можно поведать, что от этой строеи ожидалось в отличие от реальности.
И на этом наглядном примере будет проще научиться понимать, что именно делают определенные конструкции языка, что в дальнейшем позволит анализировать код уже без запуска.
Вообще статья полезная.
PS При чтении вспомнилась старая байка: если вы с первого раза сумели написать программу, в которой компилятор не обнаружил ни одной ошибки, сообщите об этом системному программисту. Он исправит ошибки в компиляторе.
написать программу в 20 строчек с максимальным количеством методов
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition? :)
напишите спецификации, примеры, тесты, предусловия, постусловия и утверждения для каждого метода перед тем, как писать сам метод
Тут надо сказать, что пока учишься, бывает сложно написать тесты и примеры до реализации. Потому что когда начинаешь что-то новое, сам еще не знаешь, как оно получится и что с ним можно будет делать.
Есть задача, но сделать почему-то не получается! Помогите!!!
[0 строк кода]
Есть задача.
[всё, на этом конец]
Ситуация часто скорее иная — студент где-то спер код, подправил его как ему кажется под свою задачу и теперь вообще ничего не понимает и просит помощи.
А особенно, если чужой код из 5 строчек с вызовом внешней библиотеки. Как должна себя вести библиотека на зоопарке разных ОС, с разными аудиодрайверами (PulseAudio, Alsa) и разным набором установленных программ — заранее неизвестно.
>>> a = 2
>>> b = 2
>>> a is b
True
>>> a = 1e10
>>> b = 1e10
>>> a is b
False
Сам так тупил в процессе изучения питона. Я знал, что сравнение объектов требует вызова
__eq__()
, что относительно долго, и что для int
меньше какого-то порога (который я и сейчас не помню) is
всегда True, когда ==
True. Ну ведь абсолютно логично и безопасно допустить, что в условном физбаззе значения рассматриваемого числа не выйдут за этот самый порог. Можно сэкономить пару-тройку тактов.У меня подобного рода прикол был.
Делал сравнение строки из одной и той же переменной, но в ответе получал False, что сильно бесило, но решение нашел в виде побитного сравнения строки в ячейках.
(Да занимался глупостью, но что-то хоть удалось мне изучить.)
Причина немного в другом. Python всегда держит все целые от -5 до 256, поэтому a и b по сути действительно будут ссылаться на один и тот же объект, который был преаллоцирован при запуске и is выдаст True
. Убедиться можно вызвав id(a)
и id(b)
. 1e10 же действительно надо создать.
a = 2
b = 2
print id(a), id(b)
print a is b
a2 = 1e10
b2 = 1e10
print id(a2), id(b2)
print a2 is b2
На выходе получаю
31424480 31424480
True
31485288 31485288
True
В ранних версиях интерпретатора Python 2/3 возможно был такой баг
a = b = 1e10
a == b # False
Когда мы ждем True
Но в последних версиях такого я пока не встречал.
Кстати, подобного поведения (id(a2)==id(b2)
) можно добиться и в интерактивном режиме, достаточно обернуть этот код в функцию. Тогда он просто при подготовке байткода соберет все константы (их можно посмотреть в структуре function_name.__code__.co_consts
) и соптимизирует аллокацию. С запуском скрипта, скорее похожая процедура, но уже на уровне модуля.
Я думаю, что если невозможно найти ошибку за 15 минут, то стоит прибегнуть к помощи профи.
-Wall
включает далеко не всё, но уже больше, чем по умолчанию.Большинство программистов естественным образом полагают, что их код работает, как ожидается
В работе чаще всего сталкиваюсь с обратным:
Первое правило программиста: Твоя программа всегда содержит ошибки и работает не так, как ожидается.
Второе правило программиста: Если твоя программа работает так как ожидается с первого запуска, то см. правило №1. Ошибка там все равно есть, но поймать ее очень сложно и она пройдет сквозь все тесты в продакшен. И обязательно всплывет спустя некоторое время: от нескольких дней до нескольких лет. Иногда имеет смысл даже удалить весь код и реализовать все заново.
Третье правило программиста: Если твоя интуиция кричит, что там есть баг, то он там 100% есть, ибо см. правило №1. И ты этот баг уже нашел, но еще не осознал в чем именно он заключается.
Как отлаживать маленькие программы