Аварийные завершения и искажение данных
Большинство разработчиков и пользователей, думая об ошибке, прежде всего имеют в виду аварийные сбои (завершения, остановы) программ и искажение (разрушение) данных. В условиях типов ошибок, описанных в предыдущих разделах, пользователи могли бы продолжать работать, но аварийный сбой останавливает работу всей системы — вот почему большая часть этой книги концентрируется на решении именно этих экстремальных проблем. Кроме того, аварийные остановы и ошибки искажения данных — наиболее общий тип ошибок. Некоторые из них легко устранить, а другие — почти невозможно. Главное, что нужно помнить об аварийных остановах и ошибках искажения данных — это то, что никогда не следует отправлять изделие заказчику, если известно, что оно имеет хотя бы один из этих типов ошибок.
Что такое ошибки?
Прежде чем начать отладку, нужно определить, что считать ошибкой. Я определяю это так: ошибка есть то, что приносит огорчение пользователю. Моя классификация ошибок такова:
противоречивые интерфейсы пользователя; несоответствие ожиданиям; низкая производительность; аварийные завершения или искажение (разрушение) данных.
Короткие или недопустимые предельные сроки разработки
Часто нереалистичность предельных сроков разработки, установленных руководством, заставляет предположить, что они были определены либо при помощи специалиста по гаданию на картах таро, либо, если это было слишком дорого, просто наобум. Ответственность за наиболее неадекватные графики работ действительно может лежать на менеджерах, но чаще всего они не виноваты. Обычно такие графики составляются на основе оценок, данных разработчиками. Совершенно очевидно, что качество конечного продукта снижается в результате ошибок планирования независимо от того, кто именно их допускает.
Автору очень повезло работать в нескольких командах, которые вовремя передавали заказчикам программное обеспечение. Во всех случаях в распоряжении разработчиков был реалистичный график с конкретными датами передачи продукта заказчику. При вычислении этих дат принимался во внимание набор свойств приложения. Если компания находила предложенную дату передачи недопустимой, разработчики сокращали свойства, чтобы передвинуть дату. Кроме того, прежде чем график представляли руководству, каждый разработчик должен был с ним согласиться. Таким образом, все усилия команды направлялись на то, чтобы закончить изделие вовремя. Интересно, что при этом конечные продукты отличались и высоким качеством.
Набор навыков
И хорошие отладчики, и хорошие разработчики имеют серьезные навыки решения специфических для программного обеспечения задач. Эти навыки можно приобрести и оттачивать. Сильных отладчиков/разработчиков отличает от хороших то, что они не только умеют решать соответствующие проблемы, но и понимают, как части проекта соотносятся с проектом в целом.
Ниже перечислены компоненты разработки, доскональное знание которых определяет мастерство программиста в отладке:
проект; язык программирования; технология; операционная система; особенности CPU.Знание проекта
Знание своего проекта — это первая линия защиты от ошибок интерфейса пользователя, а также ошибок логики и выполнения программы. Зная, как и где реализуются свойства в различных исходных файлах, можно быстро разобраться, кто что делает и для кого.
К сожалению, из-за большого разнообразия проектов единственный способ их изучения — это чтение проектной документации, если она существует, и просмотр кода в отладчике. Если вы работаете с исходным кодом C++, то может оказаться полезным также взглянуть на файлы соответствующего браузера (программы просмотра исходных кодов C++). Некоторые компании, кроме того, производят инструменты, которые преобразуют существующий исходный код в UML-диаграммы. Даже плохо документированный исходный код лучше, чем совсем ничего, если он спасает вас от необходимости интерпретировать листинги дизассемблера.
Знание языка программирования
Знание языка (или языков) программирования, применяемого при разработке, — задача более трудная, чем это может показаться. Я понимаю под этим знание не только того, как на языке нужно программировать, но и того, что он "делает за сценой". Например, разработчики иногда забывают, что локальные переменные, которые являются объектами С++-классов, или перегруженные С++-операторы могут создавать временные элементы в стеке. И оператор назначения, выглядящий вполне невинно, может иметь дело с большим объемом исполняемого кода.
Язык Microsoft Visual Basic также генерирует значительное количество кода. Многие ошибки, особенно связанные с проблемами производительности, являются результатом неправильного употребления языка, так что имеет смысл затратить некоторое время на изучение индивидуальных особенностей используемых вами языков программирования.
Знание технологии
Освоение применяемых при разработке технологий,— это первый большой шаг на пути устранения наиболее трудных ошибок. Например, если вы знаете, что делает СОМ при создании экземпляра СОМ-объекта и возврате его интерфейса, то вам понадобится намного меньше времени, чтобы проследить, почему потерпел неудачу специфический запрос интерфейса. То же самое происходит и при работе с библиотекой классов MFC (Microsoft Foundation Class). Имеет смысл представлять, как протекают сообщения в архитектуре документ/представление (document/view), на случай возникновения проблем с документом, получающим сообщение от операционной системы Windows. Говоря о знании применяемых технологий, я имею в виду, что необходимо иметь по крайней мере общее их понимание и, что еще важнее, точно знать, где, при необходимости, можно найти более детальную информацию.
Знание операционной системы
Знание операционной системы позволит устранить ошибку, а не пытаться просто что-то делать. Необходимо знать ответы на следующие вопросы, касающиеся используемой операционной системы: что такое библиотека динамической компоновки (DLL), как работает загрузчик изображений, как работает системный реестр. Часто самые тяжелые ошибки возникают тогда, когда программа обращается к операционной системе, и связано это с тем, что разработчик не знает разветвлений, создаваемых этим обращением, или передает неверные данные.
Приведу пример: представьте себе, что программа исчерпывает память, и вы не можете найти, какой модуль не вмещается в выделенную область. Знание операционной системы позволяет устранить эту ошибку — все распределения памяти в конечном счете сводятся к вызову функции API virtuaiAiioc.
Затем можно установить контрольную точку на VirtuaiAiioc и проверить по стеку вызовов, для какого из модулей делается вызов.
Мой друг Мэт Пьетрек, многому научивший меня в отладке, утверждает, что гуру отладки от простых смертных отличает знание именно операционной системы и CPU.
Знание CPU
И наконец, стать сильным отладчиком невозможно без знания центрального процессора. Эти знания необходимы для устранения большинства самых неприятных ошибок, с которыми приходится сталкиваться. Было бы здорово, если бы при аварийном завершении всегда был доступен исходный код, но большинство сбоев направляют пользователя прямо в окно дизассемблера. Несколько часов, потраченных на изучение ассемблера, могут сохранить бесчисленные часы при отладке, однако некоторые инженеры не знают языка ассемблера и не проявляют интереса к его изучению. Нет необходимости уметь написать целую программу на языке ассемблера (я и сам, наверное, не мог бы этого сделать), следует уметь прочитать такую программу. Некоторые сведения о языке ассемблера приводятся в главе 6.
Недостаточная подготовленность разработчика
Другая существенная причина ошибок состоит в том, что разработчики не понимают операционную систему, язык или технологию, которые используют их проекты. К сожалению, немногие признают это и стремятся к обучению.
Весьма часто, однако, это незнание является скорее не персональным недостатком того или иного разработчика, а суровым жизненным фактом. В разработку современного программного обеспечения вовлечено так много слоев знаний и взаимозависимостей между ними, что ни от кого нельзя ожидать знания всех тонкостей каждой операционной системы, языка и технологии. Нет ничего страшного в том, что вы чего-то не знаете. Фактически, знание сильных и слабых сторон каждого из ее разработчиков работает на пользу всей команды, позволяя получить максимальную отдачу от средств, затраченных на обучение. Стабилизируя слабости каждого разработчика, команда лучше приспосабливается к непредвиденным обстоятельствам и расширяет суммарный набор навыков.
Команда может также более точно спланировать время разработки, если разработчики отдадут себе отчет в том, что они чего-то не знают. Если член команды нуждается в изучении новой технологии для реализации некоторой части приложения, но ему не предоставлено достаточно времени, график, почти без сомнения, будет сдвинут.
Ниже, в разделе "Предпосылки к отладке" этой главы, подробнее рассказано о том, какие навыки и знания являются критическими для разработчиков.
Недостаточные обязательства по качеству
Последняя причина существования ошибок в проектах, по моему мнению, наиболее серьезна. Любой разработчик скажет, что он выполняет обязательства по качеству. К сожалению, обязательства по качеству не всегда достаточно реальны. Стремясь к получению надежного и работоспособного продукта, необходимо уделить внимание всем компонентам разработки, а не только самым эффектным. Кроме того, из всех возможных алгоритмов следует выбрать наиболее простой и приложить максимум усилий, чтобы как можно лучше его протестировать. В конце концов, заказчик не покупает алгоритмы, а только высококачественные программные продукты. В основе ..спеха тех фирм-производителей программного обеспечения и индивидуальных разработчиков, которые заботятся о качестве, лежат следующие компоненты: тщательное предварительное планирование, персональная ответственность, жесткий контроль качества и превосходные коммуникативные способности. Многие решают крупные задачи разработки программного обеспечения (т. е. задачи планирования, программирования и т. д.), но только те, кто обращают внимание на детали, сдают продукты вовремя и : высоким качеством.
Хорошим примером правильного подхода к решению проблемы качества является практика, принятая в NuMega при составлении сотрудниками ежегодных обзоров. Одна из ключевых частей обзора должна содержать запись о том, сколько ошибок зарегистрировано в продукте. Учет ошибок является жизненной частью поддержания качества программного продукта, никакая другая компания, в которой работал автор, даже не проверяла настолько очевидные вещи. Разработчики знают, где присутствуют ошибки, но для учета последних нужен стимул. В NuMega нашли такой подход. Узнав, что ошибки учитываются как часть полезной работы, разработчики регистрировали их независимо от того, насколько они были тривиальными. Благодаря этому соревнованию по регистрации ошибок лишь самые диковинные из них "проскользнули" в готовый продукт. Что более важно, это дает реалистическую картину того, в какой точке проекта разработчики находятся в любой заданный момент.
Руководя проектом, автор установил правило, согласно которому каждый член команды должен был согласиться, что продукт готов к переходу на следующий этап разработки. Если хотя бы один человек чувствовал, что продукт не готов к этому, то переход не выполнялся. Исправлялись даже незначительные ошибки, а весь следующий день отводился на тестирование. Это не только гарантировало уверенность каждого члена команды в качестве продукта, но также помогало определить вклад каждого из них в конечный результат. Интересно, что никто никогда не пытался остановить выпуск из-за чьей-то ошибки (это всегда делал автор ошибки).
Обязательства компании по качеству устанавливают настрой для полной мобилизации усилий на разработку. Эти обязательства начинаются с найма работников и в конечном счете гарантируют качество окончательной версии программного продукта. Каждая компания заявляет о своем желании нанять лучших людей, но немногие умеют их привлечь. Кроме того, некоторые компании не желают обеспечивать разработчиков инструментами и оборудованием, необходимыми для создания высококачественных продуктов. К сожалению, очень часто вместо того, чтобы потратить $500 на инструмент, который исправит опасную ошибку аварийного останова за минуты, компании "выбрасывают" тысячи долларов на оплату труда разработчиков, неделями устраняющих одну и ту же ошибку.
Повернуть компанию к сознательной организации качественных разработок непросто. Для достижения цели необходимы усилия и менеджеров, и инженеров.
Необдуманное программирование
Мой друг Питер Иерарди (Peter Ierardi) сформулировал принцип "сначала кодируй, потом думай", описывающий обычную ситуацию, в которой разработчик начинает программировать прежде, чем начинает думать. Каждый из нас повинен в развитии такого подхода. Игра с компиляторами, запись кода и отладка — обычное развлечение, благодаря которому мы заинтересовались именно этим делом. Очень немногим из нас нравится садиться и составлять документы, которые описывают то, что мы собираемся делать. "~" Те, кто обходятся без таких документов, рано или поздно сталкиваются с ошибками. Вместо того чтобы остановиться и поразмышлять о том, как в первую очередь избежать ошибок, разработчик начинает тонкую отладку кода, лишь столкнувшись с ошибками. Нетрудно сообразить, что такая тактика породит проблемы, потому что при этом все больше и больше ошибок вносится в уже нестабильную основу кода.
К счастью, решение этой проблемы просто и заключается в обязательном планировании проекта. Сбору требований и планированию проекта посвящено немало очень хороших книг, обсуждаемых в приложении 2, Упреждающее планирование жизненно важно для устранения ошибок, хотя оно и бывает довольно трудоемким.
Неправильное понимание требований
Надлежащее планирование сводит к минимуму также роль одного из самых больших источников ошибок при разработке: расползание свойств. Расползание свойств — добавление первоначально не запланированных свойств — это признак слабого планирования и неадекватного сбора требований. Добавление полученных в последнюю минуту свойств (в ответ ли на давление конкурентов или по прихоти управленцев) вызывает больше ошибок в программном обеспечении, чем что-то еще.
Разработка программного обеспечения — это бизнес, в высшей степени ориентированный на детали. Чем больше деталей обсуждается и принимается перед началом кодирования, тем меньше возможностей приходится на долю случая. Необходимо планировать промежуточные отчеты и реализации проекта. Конечно, это не означает, что сопроводительная документация должна занимать тысячи страниц.
Одним из лучших проектных документов, которые я когда-либо создавал, был просто ряд бумажных рисунков или "бумажных прототипов" интерфейса пользователя. При этом мы полностью проработали каждый сценарий пользователя, сосредоточились на требованиях для продукта и подробно выяснили, как пользователи собирались выполнять свои задачи. В конце работы мы знали точно, что мы собирались поставлять и, что более важно, это знал каждый работник компании. Если возникали сомнения по поводу того, что именно происходит в данном сценарии, мы извлекали бумажные прототипы и прорабатывали сценарий снова.
В любом случае, для того чтобы реализовать должным образом свои продукты, необходимо действительно понять требования к ним. В одной компании, где я работал (к счастью, меньше года), требования к продукту казались очень простыми и прямолинейными. Однако оказалось, что большинство членов команды не достаточно хорошо понимало, что именно заказчики ожидали от готового приложения. Компания сделала классическую ошибку, решительно увеличив количество инженеров, но без достаточного обучения новичков. Как следствие, изделие запоздало на несколько, и рынок отклонил его.
В этом проекте было две больших ошибки. Первая заключалась в том, что компания не пожелала тратить время, чтобы полностью объяснить потребности заказчиков инженерам, которые были плохо знакомы с проблемной областью, даже при том, что некоторые из нас просили обучения. Вторая ошибка состояла в том, что многие из инженеров и не побеспокоились о более глубоком изучении проблемной области. В результате команда постоянно изменяла направление работ каждый раз, когда рынок предъявлял новые требования. База кода была настолько нестабильна, что потребовались месяцы для безаварийной реализации даже самых простых сценариев пользователя.
Вообще, очень немногие фирмы заботятся об обучении своих разработчиков в проблемной области, хотя именно понимание проблемной области помогает устранять ошибки, вызванные неправильным толкованием требований.
Это недостаток не только компаний. Некоторые разработчики любят думать, что они создают инструменты, предоставляющие пользователю возможность получить решение. Пример предоставления возможности для решения — интерфейс пользователя, который, хотя технически работает, но не согласуется со способом работы пользователя. Другой пример — приложение построено таким образом, что решает какую-то частную задачу пользователя, но не ориентировано на приспособление к изменению его бизнеса.
Только решение задач, а не предоставление возможности их решения делает разработчика настолько осведомленными в проблемной области, что позволяет расширить возможности программного продукта. Лучшие разработчики — не те, кто могут манипулировать битами, а те, кто решает проблему пользователя.
Несоответствие ожиданиям
Несоответствие ожиданиям пользователей — одна из самых трудных для устранения ошибок. Ее фундамент обычно закладывается в самом начале проекта, когда компания пренебрегает изучением запросов реального заказчика. Создается ли программное обеспечение для продажи или для внутреннего применения на предприятии — причина этой ошибки сводится к коммуникационным проблемам.
Обычно группы разработчиков не имеют непосредственной связи с заказчиками их продукта и не изучают нужды пользователей. В идеале, члены инженерной команды должны посещать сайты заказчика, чтобы видеть, как используется их продукт — это откроет глаза на нужды тех, для кого они работают. Если есть возможность, стоит поговорить с заказчиком и вникнуть в широкий круг его проблем.
Другой аспект этого вида ошибок — ситуация, в которой уровень ожидания пользователя был искусственно поднят выше, чем может предоставить продукт. Эта инфляция ожиданий пользователя является классическим результатом слишком большой рекламы, и необходимо сопротивляться искажению возможностей вашего продукта любой ценой. Когда пользователи не получают ожидаемого, они имеют тенденцию чувствовать, что ошибок в изделии даже больше, чем на самом деле. Правило, позволяющее избежать этой ситуации, заключается в том, чтобы никогда не обещать того, что вы не можете поставить, и всегда поставлять то, что вы обещаете.
Низкая производительность
Пользователи не любят, когда приложение замедляет работу, сталкиваясь с реальными данными. Неадекватное тестирование — корень всех ошибок низкой производительности (эффективности), однако хуже всего при разработке выглядит приложение, которое команда не сумела протестировать со всеми подходящими реальными значениями. Один проект, с которым я работал в NuMega — BoundsChecker 3.0 — содержал подобную ошибку в своей оригинальной технологии FinalCheck. Эта версия FinalCheck включала дополнительную отладочную и контекстную информацию непосредственно в исходный код, чтобы сообщения об ошибках, генерируемые BoundsChecker, были более содержательными. К сожалению, код программы FinalCheck не был достаточно протестирован на крупных реальных приложениях, прежде чем выпустили версию BoundsChecker 3.0. В результате, значительная часть пользователей не смогла применить это свойство. В последующих выпусках свойство FinalCheck было полностью переписано, но многие пользователи так никогда и не опробовали его из-за проблем производительности в первоначальной версии, хотя это было одно из наиболее мощных и полезных свойств продукта.
Ошибки низкой производительности отлавливаются двумя способами. Во-первых, следует определить требования к производительности приложения с самого начала процесса разработки. Нужно уметь измерить производительность, чтобы иметь адекватное представление о ней. Важно сохранять основные значения производительности на постоянном уровне; если приложение снижает эти значения на 10% и более, нужно определить причины этого снижения и исправить ситуацию. Во-вторых, удостоверьтесь, что тестирование выполняется насколько возможно близко к реальным сценариям, и как можно раньше в цикле разработки.
Ошибки и отладка
Отладка — интересная тема независимо от того, какой язык или платформу вы используете. Найти ошибку в программе — это здорово, особенно до того, как заказчик увидел вашу работу. Обнаружение таких ошибок в предварительном выпуске программного продукта означает, что вы все же делаете свое дело, и качество вашей работы повышается. Если же ошибку нашел заказчик, то это совсем не здорово.
В сравнении с другими техническими областями, разработка программного обеспечения необычна в двух отношениях. Во-первых, это относительно новая дисциплина. Во-вторых, пользователи вынуждены принимать ошибки в наших продуктах, особенно в программном обеспечении персональных компьютеров. Делают они это довольно неохотно и вообще, находя ошибки, сильно огорчаются.
Ошибки влияют на ваш бизнес и в краткосрочной, и в долгосрочной перспективе, поэтому стоит побеспокоиться о том, чтобы их не было. В краткосрочной — заказчики обращаются к вам за помощью и вынуждают тратить время и деньги на поддержку текущей версии продукта, в то время как конкуренты уже работают над следующими. В долгосрочной — врывается невидимая рука экономики, и заказчики начинают покупать альтернативы вашего содержащего ошибки продукта, давление программного обеспечения более высокого качества будет увеличиваться. Очень скоро пользователи смогут выбирать программное обеспечение, просто перемещаясь от одного Web-сайта к другому, и это еще больше будет стимулировать создание высококачественных программных изделий.
Ошибки процесса разработки и их устранение
Передача программного обеспечения без ошибок возможна (если уделять достаточно внимания деталям), но большинство команд разработчиков не достигло такого совершенства. Ошибки — есть факт жизни в этом бизнесе. Однако можно минимизировать число ошибок в приложениях. Именно это и делают команды, которые сдают высококачественные продукты (но есть много других, которым это делать не удается). Причины ошибок, связанных с процессом разработки, в общем случае попадают в следующие категории:
короткие или недопустимые предельные сроки разработки; необдуманное программирование; неправильное понимание требований; недостаточная подготовленность разработчика; недостаточные требования к качеству.
Освоение набора навыков
Любое дело, связанное с технологией, необходимо изучать постоянно, чтобы держаться на высоком уровне, становиться лучше и продвигаться вперед. В приложении 2 перечислены ресурсы, которые помогли мне и, наверное, помогут вам стать более квалифицированным отладчиком.
Помимо чтения книг и журналов по отладке, полезно также писать утилиты (любого вида). Лучший способ обучения — практическая работа. В данном случае это означает необходимость выполнять кодирование и отладку. Это позволит не только усовершенствовать мастерство кодирования и отладки, но, если рассматривать эти утилиты как реальные проекты (т. е. завершая их вовремя и с высоким качеством), научиться лучше планировать проект и оценивать график работ.
Стимулом для разработки собственных утилит может служить то, что законченные утилиты являются превосходным средством демонстрации ваших профессиональных способностей в области разработки программного обеспечения, вполне заменяя так называемые "рабочие интервью" (деловые собеседования при приеме на работу). Немногие разработчики демонстрируют свои программы на подобных интервью, однако компании предпочитают преуспевающих в этом искусстве тем, которые не имеют таких навыков. Представление портфеля работ, сделанных в свободное время, показывает, что вы можете выполнять такую работу по-своему и имеете пристрастие к разработке программного обеспечения — и это сразу поместит вас в верхнюю часть списка соискателей.
Другая практика, чрезвычайно полезная при более глубоком изучении языков, технологий и операционной системы, состоит в просмотре исходных текстов программ других разработчиков. Такие тексты без труда можно найти в Интернете. Выполняя различные программы под отладчиком, вы увидите, как другие специалисты работают над ошибками. Кроме того, если вам не придумать свою утилиту, можно просто добавить свойство к. одной из найденных утилит.
Еще один способ, который я рекомендовал бы для более глубокого изучения технологий, операционной системы и CPU, состоит в выполнении обратной разработки (reverse engineering).
Это поможет активно изучать язык ассемблера и продвинутые особенности отладчика. Полученных из главы 6 знаний о языке ассемблера должно быть достаточно, чтобы начать работу. Я не рекомендовал бы начинать полную обратную разработку с загрузчика операционной системы, — лучше заняться более простыми задачами. Для меня очень полезной оказалась работа с реализацией функции CoCreateinstanceEx.
Чтение книг и журналов, написание утилит, обзор кодов других инженеров и выполнение обратной разработки — все это отличные способы улучшить навыки отладки. Еще один огромный ресурс — друзья и коллеги по работе. Не бойтесь спрашивать их, как они что-то сделали или как что-то работает, и они, если не находятся в цейтноте, будут рады помочь вам. Я уже говорил, что люблю, когда мне задают вопросы, потому что узнаю при этом больше, чем тот, кто эти вопросы задает! Кроме того, я постоянно читаю группы новостей по программированию, это отличное место для выяснения вопросов — можно получить очень хороший ответ, особенно от тех людей, которых в фирме Microsoft обозначили как MVPS (Most Valuable Professionals — наиболее ценные профессионалы).
В модели DCOM одна из функций, предназначенных для создания удаленного объекта. — Ред.
Планирование отладки
Теперь, коротко рассмотрев типы и происхождение ошибок, перейдем к процессу отладки. Многие начинают думать об отладке только тогда, когда на этапе кодирования сталкиваются с аварийным завершением. Думать же об этом следует с самого начала, на этапе разработки требований. Чем лучше спланирован проект, тем меньшее количество времени (и денег) будет затрачено на последующую его отладку.
Как было сказано выше, "расползание" свойств может стать бедствием для проекта. В большинстве случаев незапланированные свойства вводят ошибки и наносят ущерб продукту. Однако это не означает, что в планы не могут быть внесены изменения. Иногда необходимо изменить свойства продукта или добавить новые, чтобы стал более конкурентоспособным и соответствовал запросам пользователя. Главное, перед изменением кода следует точно определить и спланировать все изменения. И имейте в виду, что добавление свойств затрагивает не только код, но воздействует и на тестирование, и на документацию. Существует правило, согласно которому время, необходимое для добавления или удаления свойства, растет экспоненциально по мере приближения к концу производственного цикла.
В превосходной книге Стива Макконнелла "Искусство кодирования" (Steve McConnell. Code Complete. — Microsoft Press, 1993, pp. 25—26) говорится о стоимости исправления ошибки. Сценарий тот же, что и при добавлении или удалении свойств — на этапе разработки требований и планирования стоимость исправления ошибки невелика, однако в процессе дальнейшей работы она растет экспоненциально; то же самое происходит и со стоимостью отладки.
Планирование отладки идет вместе с планированием тестирования. Во время планирования нужно искать способы ускорения и улучшения обоих процессов. Одна из самых лучших предосторожностей, которые вы можете предпринять, состоит в написании специальных программ сброса файловых данных (file data dumpers) и проверки достоверности (validators) для внутренних структур данных, а также для двоичных файлов, если это необходимо. Если приложение читает и пишет данные в двоичный файл, следует написать тестирующую программу, которая будет сбрасывать (dumps) данные в удобочитаемом формате в текстовый файл. Программа сброса (dumper) должна также проверять достоверность данных и все взаимозависимости в двоичном файле. Этот шаг сделает как тестирование, так и отладку более легкими.
Правильное планирование отладки позволяет минимизировать время работы с отладчиком (в этом и состоит ваша цель). Может показаться, что в книге по отладке такой совет выглядит странно, но идея состоит в том, чтобы попробовать совсем избежать ошибок. Встройте такой отладочный код в свои приложения, чтобы именно он (а не отладчик) сообщал вам, где находятся ошибки. Вопросы, касающиеся отладочного кода, рассмотрены в главе 3.
Предпосылки к отладке
Прежде, чем подойти к сущности отладки, еще несколько общих слов. Все опытные отладчики являются также и хорошими разработчиками. Просто невозможно быть хорошим отладчиком, не являясь хорошим разработчиком, и наоборот.
Процесс отладки
Перейдем, наконец, к обсуждению процесса отладки. Разработать процесс, действенный для всех ошибок, даже для "берущихся неизвестно откуда", было непросто. Но опираясь на собственный опыт и опыт коллег, я описал подход к отладке, которому интуитивно следуют все сильные разработчики; менее же опытные (или просто слабые) разработчики зачастую не находят его очевидным.
Как вы увидите, этот процесс отладки не требует наличия семи пядей во лбу. Главное — систематически применять его. Рекомендуемый мной подход к отладке включает девять шагов.
Шаг 1. Дублируйте ошибку Шаг 2. Опишите ошибку Шаг 3. Всегда предполагайте, что ошибку допустили вы Шаг 4. Разделяйте и преодолевайте Шаг 5. Думайте творчески Шаг 6. Используйте инструменты усиления отладки Шаг 7. Начните интенсивную отладку Шаг 8. Убедитесь, что ошибка исправлена Шаг 9. Изучайте и делитесь с коллегамиВ зависимости от ошибки, можно пропускать некоторые шаги целиком, если проблема и ее расположение полностью очевидны. Начинать всегда нужно с шага 1 и проходить через шаг 2. Решение может быть вычислено и ошибка исправлена на любом этапе между шагом 3 и шагом 7. После этого переходите к шагу 8, чтобы верифицировать и тестировать исправление. На рис 1.1 показаны шаги процесса отладки.
Рис. 1.1. Процесс отладки
Противоречивые интерфейсы пользователя
Противоречивые интерфейсы пользователя — это тип ошибок, которые хотя и не являются самыми серьезными, однако сильно раздражают. Одна из причин успеха системы Microsoft Windows заключается в том, что все Windows-приложения в общем случае ведут себя одинаково. Отклоняясь от стандарта Windows, приложение начинает мешать пользователю. В качестве примера этого нестандартного, раздражающего поведения можно привести клавиши управления поиском в программе Microsoft Outlook. Во всех других англоязычных Windows-приложениях нажатие комбинации клавиш <Ctrl>+ +<F> приводит к открытию диалогового окна Find (Найти), служащего для поиска текста в активном окне. В Outlook, однако, нажатие <Ctrl>+<F> приводит к открытию сообщения. Даже после многих лет работы с Outlook я никак не могу запомнить, что найти текст в текущем открытом сообщении можно с помощью клавиши <F4>.
Решить проблемы с противоречивыми интерфейсами пользователя можно, следуя рекомендациям в книге Microsoft Windows User Experience (Microsoft Press, 1999). В сети разработчиков фирмы Microsoft (Microsoft Developer Network — MSDN) появилась также предыдущая ее версия — The Windows Interface Guidelines for Software Design. Если ни одна из этих книг не содержит необходимых сведений, ищите приложение Microsoft, функции которого аналогичны тем, что вы пытаетесь реализовать, и следуйте его модели.
и описания проблем, которые приводят
Эта глава началась с определения ошибок и описания проблем, которые приводят к ошибкам. Затем обсуждается, что необходимо узнать, прежде чем начинать отладку. Наконец, представлен процесс отладки, которому вы должны следовать при отладке кода.
Наилучший способ отладки — избегать ошибок с самого начала. Если вы рационально планируете проекты, берете на себя реальные обязательства по качеству и проверяете соответствие продукта технологиям, операционной системе и CPU, то время, проводимое за отладкой, может быть многократно уменьшено.
Дублируйте ошибку
Наиболее критический шаг в процессе отладки — первый, дублирование (повторение) ошибки. Это бывает трудно или даже невозможно сделать, но если не удается дублировать ошибку, то, вероятно, не удастся ее и устранить. При попытке дублирования могут потребоваться крайние меры. Однажды я столкнулся с ошибкой, которую не мог дублировать, просто запуская программу на выполнение. Я предположил, что эту ошибку могло вызывать состояние входных данных, поэтому запустил программу под отладчиком и вводил данные, нужные для дублирования, непосредственно в память. Это помогло. Если вы имеете дело с проблемой синхронизации, вам могут потребоваться такие шаги, как загрузка тех же задач, чтобы можно было дублировать состояние, в котором произошла ошибка.
Продублировав ошибку с помощью одного набора шагов, оцените, нельзя ли сделать это как-нибудь еще. Некоторые ошибки можно получить только в одной ветви кода, а другие, — прогоняя программу через множество ветвей. Суть в том, чтобы попытаться посмотреть поведение программы во всех возможных ситуациях. Дублируя ошибку во многих ветвях программного алгоритма, можно намного лучше ощутить смысл данных и граничных условий, вызывающих проблемы. Кроме того, как известно, одни ошибки могут маскировать другие. Чем больше способов вы найдете, чтобы дублировать ошибку, тем лучше.
Даже если не удалось дублировать ошибку, следует зарегистрировать ее в системе трассировки ошибок1. Таким образом, если другой разработчик отвечает за соответствующую секцию кода, он, по крайней мере, знает, что что-то неладно. Регистрируя ошибку, которую вы не можете воссоздать, нужно максимально описать ее, и эта информация в дальнейшем может позволить вам или кому-то другому решить проблему.
Bug-tracking system (BTS) — программный продукт, предназначенный для осуществления контроля за всеми этапами жизненного цикла ошибок в ПО. — Ред.
Опишите ошибку
Вы должны быть способны описать ошибку как устно, так и в письменной форме. Столкнувшись с серьезной ошибкой, необходимо остановиться сразу после дублирования, и описать ее. В идеале это нужно делать в вашей системе прослеживания ошибок, даже если отладка ошибки лежит на вашей ответственности; и обсудить проблему тоже полезно. Часто это помогает исправить ошибку. Я не могу даже вспомнить, сколько раз вычислял ошибку, просто описывая ее кому-то.
Этот "кто-то" даже не обязательно должен быть человеком. С этой ролью с успехом справляется мой кот, который помог мне устранить множество трудных ошибок, просто слушая, как я ему о них рассказывал. Если какие- то ошибки я и не смог исправить с его помощью, то по крайней мере попрактиковался в представлении их моим коллегам.
Коммуникативные навыки очень важны, поскольку коллеги смогут помочь вам, только если вы способны достаточно ясно описать свои ошибки.
Всегда предполагайте, что ошибку допустили вы
За те годы, что я занимался разработкой программного обеспечения, лишь небольшой процент встреченных мною ошибок был результатом работы компилятора или операционной системы. Если есть ошибка, шансы, что эта ошибка — ваша, весьма велики, и следует предполагать именно это. И это хорошо, если ошибка находится в вашем коде, — по крайней мере, вы можете ее исправить; если же она находится в компиляторе или операционной системе, то у вас возникают большие проблемы. Нужно устранить любую возможность появления ошибки в вашем коде, прежде чем тратить время на поиск ее в другом месте.
Разделяйте и преодолевайте
Продублировав и описав ошибку, вы выдвинули гипотезу о ее сути и о том, где ее следует искать. На этом шаге вы начинаете подкреплять и проверять свою гипотезу. Для этого можно начать с легкой отладки в отладчике. Легкая отладка включает проверку состояния и значений переменных, и не подразумевает, что вы будете упорно "продираться" через код, нащупывая решение. Если гипотеза не подтверждается через несколько минут, следует остановиться и переоценить ситуацию. Теперь вы знаете об ошибке больше и можете пересмотреть гипотезу и проверить ее снова.
Отладка подобна алгоритму бинарного поиска. Вы пробуете найти местонахождение ошибки и на каждой итерации на основании различных гипотез, будем надеяться, выделяете не содержащие ошибок секции программы. По мере продолжения поиска происходит исключение все большей части программы, пока ошибка не будет локализована в небольшой секции кода. Вы продолжаете развивать гипотезу и все больше изучаете ошибку, поэтому можете модернизировать описание последней и отразить в нем новую информацию.
На этом шаге я обычно испытываю от трех до пяти солидных гипотез, до того как перейти к следующему шагу. В идеале, вы можете проверять гипотезу без запуска отладчика и продолжать доказывать или опровергать ее.
Думайте творчески
Если приходится иметь дело с одной из тех неприятных ошибок, которые случаются только на некоторых машинах или с трудом поддаются дублированию, проанализируйте ее с различных точек зрения. Подумайте о несоответствии версий DLL, различиях операционной системы, о возможных проблемах с двоичным файлом вашей программы или его установкой, и о других внешних факторах.
Иногда полезно отвлечься от проблемы на день или два — "слишком" сильно сосредоточившись на проблеме, можно за деревьями не увидеть леса и начать пропускать очевидные вещи.
Используйте инструменты усиления отладки
Не поддается пониманию тот факт, что некоторые компании позволяют разработчикам тратить недели на поиск ошибки, расходуя тысячи долларов, тогда как инструменты повышения эффективности и покрытия кода (code coverage) помогли бы им найти текущую ошибку (и ошибки, с которыми они столкнутся в будущем) за считанные минуты.
Прежде чем приступить к интенсивной отладке, я всегда запускаю программу под BoundsChecker/SmartCheck от NuMega (инструмент обнаружения ошибки), TrueTime (инструмент контроля эффективности) и TrueCoverage (инструмент покрытия кода). Другие компании, такие как Rational Software и MuTek Solutions, выпускают продукты с сопоставимыми функциональными. Применение дополнительных инструментов позволяет эффективнее расходовать время, чем при обычной работе с отладчиком.
Рассмотрим кратко назначение инструментов перечисленных типов. Инструмент обнаружения ошибок ищет неправильные обращения к памяти, недействительные параметры системных API-функций и СОМ-интерфейсов, утечки памяти и ресурсов и многое другое. Инструмент контроля эффективности помогает проследить, где именно приложение работает медленно, причем, как правило, оказывается, что это совсем не то место, о котором мы думали. Инструмент покрытия кода показывает строки исходного кода, которые не осуществляются при выполнении программы. Эта информация полезна, потому что поиск ошибки целесообразно ограничить теми строками кода, которые выполняются.
Начните интенсивную отладку
Автор отличает интенсивную ("тяжелую") отладку от "легкой", упомянутой в шаге 4, по тому, что именно выполняется в отладчике. В ходе легкой отладки отслеживаются только некоторые состояния и "пара" переменных. Напротив, во время интенсивной отладки, требующей много времени, анализируется работа всей программы. Именно на тяжелой стадии отладки используются продвинутые свойства отладчика (обсуждаемые в главе 5). Цель состоит в том, чтобы выполнить с помощью отладчика как можно больше "тяжелой" работы.
Приступая к интенсивной отладке (точно так же, как и при "легкой"), необходимо выдвинуть некоторую гипотезу о наиболее вероятном источнике ошибки и только после этого запустить отладчик для проверки этой гипотезы. Никогда "не сидите в отладчике" из простого любопытства.
При интенсивной отладке постоянно контролируйте изменения, внесенные в отладчике для исправления ошибки. Эта двойная проверка особенно важна на более поздних стадиях проекта, когда требуется особая осторожность, для того чтобы избежать дестабилизации основы кода.
Если вы правильно установили свой проект и следуете шагам, указанным в этой главе (а также рекомендациям главы 2), то интенсивная отладка не должна будет занять много времени.
Убедитесь, что ошибка исправлена
Если вы думаете, что окончательно исправили ошибку, то на следующем шаге отладки протестируйте исправление повторно, причем лучше несколько раз. Если ошибка находится в строке кода в изолированном модуле, вызываемом только один раз, тестирование исправления выполняется легко. Однако если исправление находится в корневом модуле, особенно в том, который управляет структурами данных, то следует соблюдать особую осторожность, — исправление может коренным образом повлиять на работу других частей проекта.
При тестировании исправлений, особенно в критическом участке кода, нужно удостовериться, что оно работает со всеми состояниями данных — и "хорошими" и "плохими". Ничего нет хуже, чем исправление одной ошибки, которое вызывает две других. Если изменение вносится в критический модуль, то вся команда должна знать об этом. Только тогда станет возможной помощь коллег в обнаружении любых эффектов, наведенных этими изменениями.
История отладочных войн
Сражение
Один из разработчиков, с которым автор работал в NuMega, думал, что он нашел большую ошибку в интегрированной среде разработки программ на Visual C++ (VC IDE1) фирмы NuMega, потому что VC IDE не работала на его машине.
Для тех, кто незнаком с этой средой, приведем небольшую справку. Программные продукты NuMega интегрированы в VC IDE, благодаря чему окна, панели команд и меню NuMega являются частью интерфейса среды VC IDE.
1VC IDE — Visual C++ Integrated Development Environment (Интегрированная Среда Разработки программ на языке Visual C++). Разработчики данной фирмы сокращенно называют эту систему VC IDE-интеграцией (VC IDE integration). — Пер.
Результат
Этот разработчик потратил часа два, исследуя "ошибку" с помощью SoftlCE. Через некоторое время, установив контрольные точки по всей операционной системе, он заметил, что при запуске VC IDE функция API CreateProcess называлась "\\R2D2\VCommon\MSDev98\Bin\MSDEV.EXE", а не "C:\VSCommon \MSDev98\Bin\MSDEV.EXE", как должно было быть.
Другими словами, VC IDE выполнялась на машине \\R2D2\VCommon\MSDev98\Bin\MSDEV.EXE (вместо C:\VSCommon\MSDev98\Bin\MSDEV.EXE). Как это случилось?
Разработчик только что получил новую машину и установил полную VC IDE для продуктов от NuMega. Чтобы установить ее быстрее, он скопировал связи (LNK-файлы) своего рабочего стола, которые были установлены без VC IDE, со своей старой машины на новую, перетаскивая их мышью. При перетаскивании LNK-файлов внутренние связи обновляются, чтобы отразить местоположение первоначальной связи. Поэтому VC IDE всегда запускалась из LNK-файла пиктограммы рабочего стола, который указывал на старую машину. Таким образом, он все время выполнял VC IDE своей старой машины.
Урок
Решая проблему отладки, разработчик пошел по неправильному пути: вместо попытки многократно дублировать проблему, он попытался просто внедриться в ядро отладки. Выше, в разделе "Шаг 1. Дублируйте ошибку" сказано, что следует попытаться дублировать ошибку несколькими способами, чтобы быть уверенными, что вы имеете дело действительно с ошибкой. Соответствующие рекомендации даны в разделе "Шаг 5. Думайте творчески" данной главы.
Изучайте и разделяйте проблемы с коллегами
Каждый раз, исправляя "хорошую" ошибку (т. е. ту, которая стимулировала поиск исправления), необходимо найти время, для того чтобы документировать свои действия, благодаря этому позже вы сможете увидеть, что и как вы делали для обнаружения и решения проблемы. Особенно важно изучить свои неправильные действия — так вы научитесь избегать тупиков при отладке и быстрее устранять ошибки.
Один из наиболее важных шагов, которые можно сделать, исправив ошибку, — это поделиться добытыми знаниями с коллегами, особенно если ошибка специфична для данного проекта. Это поможет им, когда встретятся с подобной ошибкой.
Заключительный секрет процесса отладки
Поделюсь заключительным секретом отладки: отладчик ответит на все ваши вопросы по отладке, если они заданы правильно. Для этого нужно иметь гипотезу (может быть, даже записать ее) — что-то, что вы хотите доказать или опровергнуть.
Помните, что отладчик — только инструмент, подобный отвертке. Он выполняет лишь инструкции пользователя.