Маленькое предварительное замечание: Подробное объяснение потребовало бы объёмов средней книжки. Тут всё дано схематично, кратко и без подробностей. Текст, конечно, хулиганский, но прежде чем наезжать на автора, стоит учесть, что за ним стоит двадцать лет опыта и много-много литературы как классической, так и специалистам ИТ не ведомой.
Есть слово, приносящее индустрии каждый год огромные убытки. И слово это — bug.
Баги — это некие виртуальные вредоносные жучки, прячущиеся внутри программ. Они обладают собственной волей. Они проникают в самые важные участки. Они портят результаты, прерывают выполнение работы и делают другие гадости.
Конечно, это бред, если смотреть правде в глаза. Но, если вывести ментальную модель из того, что делают и говорят программисты, как раз получаются виртуальные живые существа, которых ищут, ловят, выявляют и уничтожают.
Массовая глобальная нескончаемая игра, которой увлечённо предаются практически все работники отрасли, включая тестеров, менеджмент, организаторов процессов и высоколобых теоретиков.
Почему так происходит? Потому что в индустрии совершенно превратно понимают, что такое исходный код и для чего он нужен.
Если опросить специалистов, мы получим сотню разных мнений. Но в сухом остатке, если отбросить всю шелуху, код выступает плодом творческих усилий и выражением гениальности автора. В стандартной ситуации и при любом фактическом уровне профессионализма программист воспринимает себя почти святым гением, создающим почти идеальный продукт.
Если сделать программиста не идеальным, получается одна интересная штука: код перестаёт быть готовым результатом. Он даже перестаёт быть результатом. И становится отражением текущего понимания программистом условий поставленной задачи и способов её решения.
Код именно отражает, а не описывает. Последнее возможно, но требует перестройки всего процесса, от форматов записи до мозгов.
Писать и говорить то, что думаешь, — это всегда отсутствие такта, презрение к окружающим и хамство. Если кто-то ставит в своём коде комментарий «Stupid idea. Does not work, if N < 0. Correct ASAP.», он рискует прослыть минимум странным. А вот если это попадёт в участок ответственности гениального программиста, тут уже мелкой истерикой не ограничится. Даже, если «stupid» будет подразумеваться только по контексту. Или напишите в комментарии что-нибудь типа «I do not know why this works, but otherwise the function generates an exception.» Потом покажите это начальнику и попросите повышения.
И, конечно, гораздо выгоднее говорить «Мы исправляем баги в коммуникационном модуле», а не «Читая документацию мы прошляпили несколько критических моментов и неделю будем всё с нуля переделывать.»
Ладно, оставим. Большинство такого не выдерживает. Страшно. И ронять чувство собственного достоинства тоже страшно. И лицо потерять… И начальство тоже… Короче, фиг с ним, перейдём к плюшкам.
Качество определяется тем временем, которое в системе живёт ошибка
Собственно, по этой теме всё. Вот только концепция волшебным образом прошла мимо европейского менталитета.
Прежде, чем превозносить lean и kanaban и молиться на пересказы пересказов в книжках, авторы которых объясняют, как лучше исполнять ритуалы карго-культа, следует обратиться к основам. А там вполне однозначно сказано, что нефиг возводить замки на гнилом фундаменте. Если программа работает не правильно, она должна останавливаться. Точка.
Да, если на презентации клиенты увидят, как программа вылетит с NullPointerException, это будет не очень хорошо для коммерции. Есть ещё тысяча и одна причина доказать, почему ошибки нужно прятать. Но, если говорить о коде как об отражении понимания, двигаться вперёд можно только исправляя ошибки.
А стандартом индустрии как раз является тенденция любые сбои системы прятать или закапывать в громадных логах, куда никто никогда по доброй воле не смотрит. После чего увлечённо играть в ловлю багов.
И тут можно упомянуть ещё одно интересное качество.
Любой интерфейс должен возвращать ошибку на адекватном уровне
Первое, что я обычно делаю на любом проекте — пишу собственный модуль диагностики. Потому что всё имеющееся удовлетворяет нужды гениев, но слабо предназначено для идиотов, у который случаются ошибки. В результате в моих руках оказывается очень мощный инструмент, одно из основных свойств которого — адекватность.
Адекватный уровень для ошибки в логике выполнения — останов программы
Потому что, если в логике сбои, их нельзя игнорировать. Нужно разбираться.
Да, потом, когда проблема будет ясна, можно обойти, можно выкинуть часть процесса или данных. Но сказать программисту, что он мыслит не правильно, программа должна одним способом — полной остановкой.
Это ужасно медленно и ужасно дорого. (И ужасно страшно.)
В теории.
На практике это значительно спрямляет пути к цели и вовремя отсекает ошибочные решения. (Только не надо сюда приплетать agile, который на русский переводится словосочетанием «Мы — гении!»)
Адекватный уровень для ошибки пользователя — место, где находятся данные
Если пользователь произвёл фигню, он должен её увидеть в результате работы программы. Не всегда удаётся поймать это во время ввода и подкрасить красным ошибочное поле в диалоге. Если на входе таблица в десять тысяч строк, бесполезно ругаться, бесполезно останавливаться и ошибки выдавать в лог или выпрыгивающими сообщениями тоже бесполезно. Не заметят. Или обойдут.
Приходится создавать сообщение на языке, понятном пользователю, с точным указанием места, причин и возможностей исправления, а потом не просто выводить его в лог (который не адекватен уровню ошибки), а тащить до самых конечных результатов. Чтобы, когда пользователь возмутится «А почему у меня тут значение пустое!», спокойно открыть соседний столбец в таблице и громко и с выражением зачитать, почему именно.
Впрочем, если у нас коробочный софт или проект для клиентов, о пользователях можно забыть. Служба поддержки тоже должна на чём-то деньги зарабатывать. И желательно, чтобы это были не наши ошибки.
Не надо ловить блох — ошибки можно прогнозировать
Индустрия молится на Unit Testing. Это модно, это прогрессивно, это агильно. Вот только цель его ответить на вопрос «Я, правда, гений, да?»
Естественно, мелочное тестирование необходимо, когда по стандарту надо подтвердить каждую строчку кода или пять раз в неделю вылавливать результаты посещения шаловливых ручек коллег. Но это не продвигает нас вперёд. И очень хреново отражает работу в задачах реального времени и реальных данных.
Когда программист не идеален, ему не нужно зеркало, ему важно понять, что же на самом деле внутри происходит. А тут наиболее простой способ — сделать функциональный прототип или в реальной ситуации на реальных данных в нужном месте остановиться и посмотреть, что на данном этапе получилось. Естественно, проще всего это делать, когда код пишется, потому что именно в данный момент программист строит предположения на счёт того, что должно и не должно получиться.
Также, в случае обнаружения ошибки для понимания условий и причин проще всего не плестись пошагово по почти идеальному коду, а проверить ход мыслей и построить теорию, после чего в критичных местах вывести в лог нужные параметры и остановиться там, где произойдёт что-то интересное.
Потому для меня самое частое средство отладки программы строка
die "OK!" ;
Естественно, в большинстве случаев строчкой выше стоит $diag->DBG(...), который выводит всё, что я хотел узнать и что мне было интересно. Иногда это пара значений, иногда структура на полтора мегабайта, которая затем тщательно изучается ручками или программой.
Потом, когда всё становится понятным, можно за собой убрать. Правда, трассировку полезнее не удалять, а прятать в комментарий. Потому что ошибки имеют тенденцию возникать вновь, и не нужно будет выдумывать по новой, что и где стоит смотреть.
Естественно, ни один язык и ни один тул этого не поддерживает. Потому что их создают гениальные программисты, а они ошибок не производят и всегда знают, каким мир должен быть.
Ошибки можно ловить там, где их проще всего понять
Если мы ждём ошибки, мы должны сделать программу надёжной. То есть, поставить забор там, где может случиться что-то нехорошее. Даже, если «оно не может случиться» Тут всё в конечном итоге сводится к трём словам: preconditions, postconditions, invariants. Естественно, большинство языков и тулов это не поддерживают или поддерживают совершенно криво.
Приходится ручками делать что-нибудь вроде такого.
$diag->ASSERT( ($max_depth >= 1), "Depth must be an integer >= 1" );
Всё просто. Если условие precondition выполнено, работаем дальше. Если получили данные, с которыми не можем работать, останавливаемся.
Есть один нюанс, из-за которого модуль диагностики приходится делать заново. И это совсем не потому, что стандартные средства не позволяют гибко перенаправлять ввод в файл или диалоговое окно. Секрет в текстовом сообщении.
Это короткое послание в будущее. От сомневающегося автора из времени написания программы неизвестному лицу, которому придётся разбираться с тем, почему условие не сработало.
Это может быть автор на следующий день, а может быть и брат по разуму в далёком Бангалоре с неизвестными навыками и непрогнозируемыми предположениями о том, что происходит внутри программы. И для этого случая критически важно не просто прокукарекать, а передать знания. Впрочем, для автора через пару недель после написания информация становится тоже свежей.
И вот тут мы подошли к разнице между отражением и описанием. В типичном случае правильного применения посланное текстом человеку будет отличаться от того, что код приказал компьютеру. Ниже пара примеров из текущего кода.
$diag->ASSERT( defined( $network->{ $first_vertex }->{ $second_vertex } ) , "Fatal error with deletion" ); $diag->ASSERT( ( $main_name and $main_name !~ /^NA$/i ) , "Main name is empty" );
Секрет тут в том, что в случае ошибки мы не просто получаем информацию о том, что и где сломалось, но и можем понять информацию на мета уровне. То есть, видим зачем и почему автор сделал так, как сделал. Это исключает одну из неприятных особенностей классического дебаггинга, когда быстрое исправление кода создаёт ripple effect, затыкая сообщение об ошибке в данном конкретном месте, но вызывая другие, более сложные для обнаружения и исправления.
Большинство программистов не проверяют ошибки прикрываясь нехваткой времени или необходимостью сделать код быстрым. Но базируется всё на внутренней уверенности в том, что ошибок нет и не будет. А зазнаваться вредно. Оптимизированная программа, работающая не правильно, просто производит больше мусора за единицу времени.
Даже достаточно редкий забор, но сделанный продуманно и профессионально, на ранних этапах выловит те ошибки, которые при «стандартных для индустрии» качестве и организации процесса будут найдены после утомительного тестирования или, вообще, выявятся у клиентов.
Как правило, большинство внутренних проверок никогда не срабатывает. Но, если случается «невероятное», программа сама сообщает, что не надо было задирать нос, и самые беспочвенные опасения сослужили свою службу.
В принципе, на этом всё. Углубляться можно долго, но это уже текст других объёмов и другого качества.
Автор: vit_r