Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Питер_Гудлиф_Ремесло_программиста_Практика_написания_хорошего_кода.pdf
Скачиваний:
16
Добавлен:
19.04.2024
Размер:
9.23 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

222m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Глава 9. Поиск ошибокClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

Одно из самых мощных средств в арсенале отладчика – недоверие к чужому коду в совокупности со здоровой дозой цинизма. Причи% ной ошибочного поведения может быть все, что угодно, и, присту% пая к диагностике, вы должны проверять даже самых маловероят% ных кандидатов.

Когда вы ищете ошибку, не верьте никому. Проверьте самые невероятные причины, вместо того чтобы сходу отвергнуть их. Не принимайте ничего на веру.

Охота за ошибками

Как искать ошибки? Если бы существовала какая%нибудь простая трехшаговая процедура, мы бы ее выучили и стали выпускать идеаль% ные программы. Нет такой процедуры, и наши программы такие, ка% кие они есть. Попробуем разобраться в опыте поиска ошибок, накоп% ленном человечеством.

Ошибки этапа компиляции

Мы рассмотрим их первыми, потому что с ними относительно легко бороться. Когда компилятор натыкается на что%то неприятное для не% го, он обычно не просто останавливается, чтобы сообщить об этом, но, пользуясь случаем, оглашает все, что думает о жизни в целом, и разра% жается целой серией новых сообщений об ошибках. Так его учили; найдя ошибку, компилятор пытается по возможности продолжить синтаксический анализ. Это ему редко хорошо удается, но чего еще можно ожидать при таком коде, как ваш?

Результатом является то, что последующие сообщения компилятора могут иметь весьма случайный и необоснованный характер. Смотреть нужно только на самое первое сообщение об ошибке и решать пробле% му с ней. Однако не мешает просмотреть и остальные ошибки; там мо% гут быть другие полезные сообщения об ошибках, но не всегда.

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

Даже первое сообщение компилятора об ошибке может оказаться зага% дочным или вводящим в заблуждение; это зависит от качества компи% лятора (если смысл ошибки невозможно понять, попробуйте другой компилятор). Код стандартных шаблонов C++ может провоцировать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Охотаm

за ошибками

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

223Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

некоторые компиляторы на безудержный поток сообщений об ошиб% ках, содержащих таинственные заклинания.

Обычно синтаксическая ошибка находится на строке, указанной компи% лятором, но иногда – на предшествующей ей и делающей бессмыслен% ной следующую; компилятор замечает это и выражает неудовольствие.1

Пример 1: графическая утилита

Программа

Небольшая утилита с графическим интерфейсом.

Проблема

Программа была переработана для придания ей «современно% го вида» – новые значки и расположение элементов. Старый интерфейс предполагалось сохранить как опцию в настрой% ках. Во время модернизации все работало прекрасно почти до самого момента выпуска, когда кто%то попытался воспользо% ваться старым интерфейсом. Программа аварийно заверша% лась, когда начинало рисоваться окно, но прежде чем оно ото% бражалось полностью.

История

К счастью, эта проблема была возобновляемой. Программу запустили в отладчике и нашли место сбоя, которое оказалось где%то глубоко в библиотеке интерфейса пользователя в коде отображения графики.

Изучение вопроса навело на мысль, что используется недопус% тимая графика. Программа пыталась показать значок, нахо% дящийся по нулевому адресу памяти, т. е. аварийное оконча% ние вызывал нулевой указатель. По стеку вызовов мы опреде% лили, какое изображение нужно было показывать. Взглянув затем на каталог с графикой старой версии, мы обнаружили, что именно этого значка в нем и нет.

Очевидно, что сбой происходил в операции загрузки значка в конструкторе окна, возвращавшей нулевой указатель, кото% рый свидетельствовал о том, что загрузки значка не произош% ло. Но проверка возвращаемого значения отсутствовала – автор предположил, что графика всегда будет в положенном месте.

1C++ может иногда разыграть красивый фокус: предыдущая строка оказы% вается в другом файле! Если пропустить ; в конце объявления класса в за% головочном файле, то первая строка в файле реализации окажется бес% смысленной. Ошибка, о которой сообщает компилятор, оказывается весь%

ма загадочной.

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

224m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Глава 9. Поиск ошибокClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Исправление должно быть двояким:

Устроить проверку значений, возвращаемых всеми проце% дурами загрузки значков, чтобы они более элегантно обра% батывали случаи отсутствия графики.

Поместить отсутствующую графику в нужные каталоги.

Время, потраченное на исправление

На изучение проблемы, исправление ошибки и проверку ис% правленного кода потребовалось несколько часов.

Полученные уроки

Проверяйте значения, возвращаемые всеми функциями, даже если вам кажется, что в них не может быть сбоев.

Тестируйте функции программы как можно раньше, осо% бенно редкие условия, которые будут возникать не часто.

Ошибки компоновки в целом гораздо более понятны. Редактор связей сообщит вам, что отсутствует некая функция или библиотека, так что лучше подсуетиться и найти ее (или написать). Иногда компоновщик жалуется на таинственные проблемы, связанные с таблицей виртуаль% ных методов C++; обычно это признак отсутствия деструктора или че% го%либо аналогичного.

Ошибки этапа исполнения

Ошибки этапа исполнения требуют более тонких действий. Если в про% грамме ошибка, это может означать, что содержащееся где%то в коде условие, которое вы считали выполненным, на самом деле таковым не является. Поиск ошибки состоит в последовательной проверке того, что вы считали истинным, до обнаружения места, где это условие не выполняется. Нужно создать модель реальной работы кода и сравнить ее с предполагавшейся вами работой. Единственный разумный путь – методически применять этот способ.

Отладка – это методичная работа, медленно сужающая кольцо вокруг мес& та нахождения ошибки. Не следует относиться к ней как к игре в угадайку.

Научный метод – это процесс, с помощью которого ученые получают точное представление об окружающем мире. Это сходно с тем, что пы% таемся сделать мы. Научный метод заключает в себе четыре этапа:

1.Наблюдение явления.

2.Формирование гипотезы, объясняющей явление.

3.Предсказание результатов других наблюдений на основе предло% женной гипотезы.

4.Проведение экспериментов, подтверждающих предсказания.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Охотаm

за ошибками

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

225Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Несмотря на то, что мы пытаемся избавиться от феномена ошибки, а не построить его модель, нам необходимо понять суть ошибки, чтобы действительно устранить ее. Научный метод представляет собой хоро% шую основу для отладки, в чем вы убедитесь, изучив предлагаемые ниже действия.

Идентифицировать ошибку

Все начинается с обнаружения того, что программа делает не то, что нужно. Может быть, она аварийно завершается или выводит желтый треугольник вместо синего квадрата – вы видите непорядок и должны его выправить. Прежде всего нужно послать отчет об ошибке в базу данных (см. раздел «Система контроля ошибок» на стр. 205). Это осо% бенно важно, если вы находитесь в процессе отладки какой%то другой ошибки или у вас нет времени, чтобы сразу разобраться с ошибкой. Ре% гистрация ошибки гарантирует, что она не будет забыта. Не пытайтесь ограничиться мысленной отметкой о необходимости вернуться к про% блеме позже – вы обязательно забудете это сделать.

Прежде чем бросаться на поиски ошибки, вызвавшей обнаруженный вами сбой, определите сущность аномального поведения, с которым вы столкнулись. Опишите проблему как можно подробнее, ответив на следующие вопросы: зависит ли ошибка от времени? оказывают ли на нее влияние данные ввода, загруженность системы или состояние про% граммы? Если вы не разберетесь в сути ошибки до того, как начнете ее исправлять, вы просто станете модифицировать код, пока не исчезнут симптомы. Причина ошибки окажется замаскированной, и та же не% исправность проявится где%то в другом месте.

Работал ли код раньше? Поищите в своей системе контроля версий по% следнюю версию, работавшую без сбоев, и сравните тот код с нынешним.

Воспроизвести ошибку

Это нужно сделать в дополнение к описанию ошибки. Опишите набор действий, уверенно приводящий к возникновению данного сбоя. Если таких способов несколько, документируйте их все.

Первый шаг на пути установления места ошибки – это определение способа ее уверенного воспроизведения.

Хуже, если ошибку не удается воспроизвести. Необходимо расставить ловушки для сбора всевозможной информации на тот случай, если сбой возникнет снова. Если сбой возникает нерегулярно, тщательно накап% ливайте все сведения, получаемые в момент сбоя.

Определить место ошибки

Это серьезное дело. След взят; теперь нужно воспользоваться всеми по% лученными данными, чтобы найти зверя и точно установить, где он

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

226m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Глава 9. Поиск ошибокClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

залег. Легко сказать! Это процесс, в котором нужно отсеять все, что не имеет отношение к сбою или явно работает правильно – в духе Шерло% ка Холмса. По ходу дела выясняется потребность в дополнительной информации – чем больше ответов, тем больше возникает вопросов. Возможно, придется сочинить какие%то дополнительные тесты. Воз% можно, придется порыться в малоприятных потрохах кода.

Проанализируйте, что вам стало известно относительно сбоя. Не делая скороспелых выводов, составьте список подозреваемых участков кода. Попробуйте подметить какие%то закономерности событий, которые подскажут возможную причину. Если есть возможность, ведите учет входных и выходных данных, иллюстрирующих проблему.

Расследование хорошо начать с того места, где проявляет себя ошибка, хотя обычно она реально находится совсем в другом месте. Запомните: если отказ возникает в каком%то модуле, из этого необязательно следу% ет, что именно этот модуль во всем виноват. Определить место ава% рийного завершения легко; отладчик сообщит вам, в какой строке про% изошел сбой, значения всех переменных в тот момент и кто вызывал эту функцию. Если аварийного завершения нет, начните с того места, в котором программа ведет себя неправильно. Двигайтесь оттуда в об% ратном направлении, следуя порядку выполнения кода, и проверяйте, что в каждой точке код делает именно то, чего вы от него ждете.

Начните с известного места, например с точки аварийного завершения про& граммы. Затем двигайтесь в обратном направлении в сторону причины, вы& звавшей сбой.

Есть несколько стандартных стратегий поиска ошибок:

Самое худшее – менять что%то произвольным образом и проверять, не исчезнет ли сбой. Это незрелый подход. (Хотя профессионал мо% жет попытаться придать ему наукообразный вид!)

Гораздо лучше стратегия разделяй и властвуй. Допустим, вы су% зили область поиска до одной функции, в которой 20 шагов. После 10%го шага выведите промежуточный результат или установите контрольную точку и изучите ее в отладчике. Если значение пра% вильное, значит, ошибка находится в тех командах, которые лежат ниже; в противном случае она выше. Займитесь этой частью ко% манд и повторяйте операцию, пока не загоните ошибку в угол.

Еще один способ – формальный прогон (dry run). Не рассчитывая, что интуиция поможет вам найти ошибку, поработайте сами вместо компьютера и выполните контрольную трассировку программы, вычисляя все промежуточные значения и конечный результат. Ес% ли ваш результат не совпадет с тем, который дает программа, ошиб% ка в коде – он не делает то, чего вы от него хотите. Конечно, это тре% бует времени, но зато эффективно, потому что доказывает, что ва%

ши предположения не верны.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Охотаm

за ошибками

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

227Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Уясните проблему

Когда вы найдете место, где таится ошибка, нужно разобраться в сути реальной проблемы. Если это простая синтаксическая ошибка, напри% мер = вместо == (уф!), последствия не слишком страшны. Если это более сложная семантическая проблема, убедитесь, что она вам понятна, как и все способы, которыми она может проявиться, и лишь потом дейст% вуйте дальше – возможно, вы обнаружили лишь часть проблемы.

Часто ошибка оказывается очень тонкой: код делает именно то, что должен и что вы предполагали, когда писали его! Проблема заключа% ется в ложных допущениях (помните, как они опасны?). Тот, кто пи% шет функцию, и тот, кто к ней обращается, вполне могут предполагать различное поведение функции отдельных особых случаях. Выполните обратную трассировку и точно разберитесь, в чем причина проблемы и нет ли других фрагментов кода, содержащих ту же ошибку.

Если вам показалось, что вы нашли причину ошибки, досконально исследуйте ее и убедитесь, что не ошиблись. Не принимайте безрассудно первую же ги& потезу.

Это важнейший принцип борьбы с ошибками. Иначе вы пополните число тех программистов, которые создают ошибок больше, чем устра% няют их во время отладки.

Создайте тест

Напишите контрольный пример, демонстрирующий ошибку. Вам стои% ло сделать это еще на этапе «Воспроизведите ее». Если вы не сделали этого тогда, то теперь точно настало время. Используя накопленные знания, сделайте тест достаточно строгим.

Исправьте ошибку

Теперь самое простое: нужно исправить эту чертову штуку! Здесь дей% ствительно не должно быть никаких трудностей: вы точно знаете, по% чему происходит сбой, и можете сами вызвать его появление. При та% ких данных исправление обычно оказывается плевым делом. Часто программисты считают, что исправлять ошибки трудно; это потому, что они пропускают первые два шага.

В следующем разделе мы более подробно рассмотрим, как исправлять ошибки.

Докажите, что вы ее исправили

Теперь вы знаете, зачем вам нужен контрольный пример. Прогоните его снова и докажите, что мир стал более совершенным. Этот контроль% ный пример можно добавить в комплект регрессивных тестов, чтобы гарантировать отсутствие возникновения той же ошибки в будущем.