Сложный код, плавающие баги и инсайт
Мы три дня ловили баг. Есть такая противная категория ошибок – плавающие баги. Гейзенбаг (англ. Heisenbug) — термин, используемый в программировании для описания программной ошибки, которая исчезает или меняет свои свойства при попытке её обнаружения. Это слово, в отличие от слова «баг», в русском языке практически не используется. Не полностью идентичный, но достаточно близкий по значению русскоязычный термин — «плавающая ошибка». Примером могут являться ошибки, которые проявляются в окончательном варианте программы (релизе), однако не видны в режиме отладки, или ошибки синхронизации в многопоточном приложении.
Обычно при отладке такого бага, очень трудно его локализовать, т.е. повторить, добиться его стабильного возникновения. Он то появляется, то исчезает. Программист проверяет код, и думая, что он уже отлажен делает коммит на продакшен сервер (ну или на тест-сервер). А потом тестировщик (привет Денис:) обнаруживает его, и возвращает программисту. Тикет по этому багу гуляет из “test” в “new” и обратно по многу раз. Это утомляет всех.
Как правило, при анализе ошибок такого типа, приходится полностью перебирать движок, предлагаются самые неимоверные варианты, по дороге находится и исправляется куча других багов, но этот восстает аки Феникс. Тогда для меня становится делом чести прищучить его. Люблю сложные задачки 🙂
Эта ошибка состояла в том, что периодически на одной странице веб-приложения отключался автозагрузчик классов. До этого он работал, везде он работал, а на одной странице брал и отрубался. Через раз. Иногда мог по 6 раз работать, а потом выключиться. Мы начали грешить на кэширование – отключили. Опкод кэшер – тоже выключили. Потом при ручном отключении конкретных подсистем кэша, всё было нормально, начали включать по одной, всё работало. Включили все – работает. А не должно бы. Тикет пошел в “test”, но потом вернулся обратно. “Опять !@#$!@$ глючит” – сказал нам тестировщик. Мы долго думали.
Были сделаны самые невероятные предположения – операционная система, броузер, броузер и операционная система, время суток, погода, дайте нам хоть что-то, за что можно зацепиться! Но нет. Вечер пятницы, а ошибка, так и не исправлена. Посудив, что утро вечера мудренее, мы отправились отдыхать. Благо в субботу у нас намечался корпоратив, во время которого мы славно отдохнули, проветрив мозги.
Иногда бывает так, что ты усиленно думаешь над проблемой, а она не решается. Магии в PHP нет. Есть логика. Как говорил один мой преподаватель – “машина она железная, чего ей скажешь – то она и делает”. Так что надо искать условия, проверять, думать.
У меня есть “правило Ч“, которому научила военка. Например, я всегда жду людей строго определённое время (которое конечно зависит от отстоятельств). Это избавляет от терзаний вида, “ну подожду ещё 5 минуточек, может придёт”. Время вышло, я ущёл. Тоже и с кодингом, нельзя биться головой в стену, есть определённое время на решение проблемы, если оно истекло – значит что-то не так, значит неправильно действуешь.
Когда бьешься долго над такими типами ошибок, пытаешься представить себя машиной, интерпретатором, пропуская через свой разум код программы, вспоминаешь связи между кусками кода, модулями и классами, моделями и видами, контроллерами и действиями. Ты загружаешь программу в своё сознание. Не задумывались об этом? Ошибка не решается, вы идёте гулять/курить/в магазин/гости/бассейн/проветриться и перестаете думать над этой проблемой. Она вытесняется из фокуса сознания. И тут вступает в игру подсознание. Ты вроде бы не думаешь о проблеме, но иногда на тебя словно спускается озарение, инсайт, идея, которую тут же хочется проверить, попробовать, а вдруг – это решение!
Сейчас был именно этот момент. А ведь в тексте ошибки, которую показывал PHP в путях были пути, которые выставляет утилита, а не сам движок. Тут надо сказать, что кроме большого движка веб-приложения, у нас есть несколько утилит, которые делают какую-нибудь полезную работу, они маленькие быстрые (потому что не загружают Zend Framework) и поэтому мы используем их для ряда служебных задач. НО, они совсем ничего не знают о моделях, контроллерах, видах и т.д. Они не умеют грузить модели, у них свой автозагрузчик классов. Стоп! Свой автозагрузчик. А может быть где-то в коде движка инклудятся утилита, которая грузит автозагрузчик, а он затирает пути set_include_path() к классам, которые можно грузить автоматически. Да, это оказалось так. Эврика, инсайт! За это я и люблю свою работу!
После получаса компания в коде я нашел место, там было несколько вложенных условий, которые делали этот баг плавающим, их я тоже выяснил, после чего дал указания программистам, как можно устранить эту ошибку.
Что же после всего хочется сказать?
1. Не делайте ваш код сложным. Поменьше магии, нам в работе итак её хватает. Эй, помееьше магии в прямом смысле! Например, в Doctrine есть магические функции вида $model->findByUserIdOrSessionId($user_id, $session_id). Красиво? Да, но такой функции нет. Что происходит, когда она вызывается. Происходит вызов служебной функции __call(), затем вызов класаа, поиск по массиву (практически парсинг названия функции), составление DQL-запроса, его выполнение и возврат. Сколько лишних операций, зато красиво. Что надо вам? С головой подходите к работе, делайте ваш код понятным, но не перебарщивайте с магией.
2. Просчитывайте варианты. Пытайтесь думать, как машина, смотрите по каким ветвям может пройти программа.
3. Не делайте код сложным. Упрощайте! Делайте функции с говорящими именами, длинные методы разбивайте на маленькие, используйте принцип DRY в своей работе!
4. Отдыхайте! Нашим мозгам тоже нужен отдых, отвлечься от работы очень часто бывает гораздо полезнее, чем сидеть всю ночь над кодом!
Литература
http://trenings.ru/OnLine_treningi/PsihoTrening/PsihoTrening_5-e_zanyatie._Fokus_vnimaniya.html
Интересно было прочитать
как раз сейчас пытаюсь отловить . Не возможно повторить, чтоб хоть как-то локализовать место поиска.
Расчет стоимости доставки глючит примерно в 7%.
Причем не у меня. Я уже сделала заказов 20 в разных броузерах, с разными типами…
не понимаю, как найти.
Тоже zend – controller, но передача данных через ajax