Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по
Скачать 5.88 Mb.
|
ГЛАВА 23 Отладка 539 Для нахождения лишних символов комментария или кавычек в коде C, C++ или Java вставьте в него последовательность: /*”/**/ Этот фрагмент завершит комментарий или строку, что поможет сузить область, в которой скрываются лишние символы. 23.3. Устранение дефекта Сложной частью отладки является поиск дефекта. Устранить его легко. Однако, как часто бывает, из#за этой самой легкости устранение одних дефектов создает бла# гоприятные условия для внесения других. По крайней мере в одном исследова# нии было обнаружено, что первые варианты исправления дефектов в половине случаев оказывались некорректными (Yourdon, 1986b). Ниже я привел несколько советов по снижению вероятности ошибок этого рода. Прежде чем браться за решение проблемы, поймите ее «Руковод# ство Дьявола по отладке» не врет: нет более эффективного способа усложнить себе жизнь и ухудшить качество программы, чем исправле# ние дефектов без их настоящего понимания. Приступайте к устранению пробле# мы, только разобравшись в ней до конца. Триангулируйте источник ошибки с при# менением двух видов тестов: тех, что должны привести к ошибке, и тех, которые должны выполниться безошибочно. Выполняйте тесты, пока не поймете пробле# му достаточно хорошо, чтобы правильно предсказывать появление ошибки в каж# дом случае. Не ограничивайтесь пониманием проблемы — поймите программу Если вы понимаете контекст проблемы, у вас больше шансов решить ее полностью, а не частично. Исследование, проведенное с использованием короткой программы, показало, что программисты, стремящиеся полностью понять поведение програм# мы, чаще изменяли ее правильно, чем программисты, концентрировавшиеся на локальном поведении и изучавшие программу только по мере надобности (Littman et al., 1986). Так как программа в этом исследовании была небольшой (280 строк), я не могу утверждать, что вам следует пытаться полностью понять программу из 50 000 строк перед исправлением дефекта. Но вы должны понять хотя бы код, расположенный по соседству с дефектом — под «соседством» я понимаю не не% сколько, а несколько сотен строк. Подтвердите диагноз проблемы Перед исправлением дефекта убедитесь, что вы диагностировали проблему правильно. Выполните тесты, которые доказыва# ют вашу гипотезу и опровергают конкурирующие гипотезы. Если вы установили только то, что ошибка может быть результатом одной из нескольких причин, к устранению проблемы приступать рано — исключите сначала другие причины. Расслабьтесь Один программист собирался в лыжный поход. Программа была почти готова к выпуску, он опазды# вал, и ему оставалось исправить только один дефект. Он из# менил исходный файл и зарегистрировал его в системе управления версиями. Он не выполнил перекомпиляцию программы и не проверил правильность изменения. Никогда не отлаживайте про- грамму стоя. Джеральд Вайнберг (Gerald Weinberg) 540 ЧАСТЬ V Усовершенствование кода А изменение оказалось неверным, что привело начальника программиста в ярость. Как можно изменять код приложения, готового к выпуску, не проверив его? Что может быть хуже? Разве это не верх некомпетентности? Если такой поступок и не является вершиной некомпетентности, он очень к ней близок, но это нисколько не сказывается на его распространенности. Решение проблемы второпях — один из самых неэффективных в плане времени подходов. Он подталкивает к необоснованным суждениям, неполной диагностике дефектов и внесению неполных исправлений. Принимая желаемое за действительное, вы можете увидеть решение там, где его нет. Давление — часто самовнушенное — скло# няет к принятию случайных и непроверенных решений методом проб и ошибок. А вот другой подход. В самом конце разработки ОС Microsoft Windows 2000 одно# му из программистов нужно было исправить последний дефект, после чего уже мож# но было бы создать коммерческую версию. Он изменил код, проверил исправле# ние и протестировал его на своей локальной сборке. Но сразу регистрировать ис# правление в системе управления версиями он не стал. Вместо этого он пошел иг# рать в баскетбол, сказав: «Я сейчас чувствую слишком большое напряжение и по# этому не могу быть уверен в том, что рассмотрел все, что следовало. Часок отдох# ну, приведу мысли в порядок, а потом вернусь и зарегистрирую код, как только увижу, что мое исправление на самом деле корректно». Отдыхайте, пока правильность решения не станет очевидной. Не поддавайтесь соблазну сэкономить время: обычно это приводит к обратному результату. Сле# дуя этим советам, вы всегда будете вносить правильные исправления, и руково# дителю не придется вызывать вас из лыжного похода. Сохраняйте первоначальный исходный код Перед на# чалом исправления дефекта обязательно заархивируйте имеющийся код, чтобы в случае чего к нему можно было вернуться. Имея дело с несколькими изменениями, вы мо# жете забыть, какое из них важно в текущий момент. Сохра# нение первоначального исходного кода позволит хотя бы сравнить старый и но# вый файлы и определить измененные фрагменты. Устраняйте проблему, а не ее симптомы Конечно, симптомы также нужно устранять, но главной целью должно быть устранение причин проблемы. До кон# ца не разобравшись в проблеме, кода не исправить. Вы устраните симптомы и сделаете код еще хуже. Рассмотрим, например, фрагмент: Пример кода, требующего исправления (Java) for ( claimNumber = 0; claimNumber < numClaims[ client ]; claimNumber++ ) { sum[ client ] = sum[ client ] + claimAmount[ claimNumber ]; } Предположим, величина sum для клиента 45 отличается от правильного значения на 3,45 доллара. Вот неверный способ решения этой проблемы: Перекрестная ссылка Общие вопросы, связанные с измене- нием кода, подробно обсужда- ются в главе 24. ГЛАВА 23 Отладка 541 Пример ухудшения кода в результате его «исправления» (Java) for ( claimNumber = 0; claimNumber < numClaims[ client ]; claimNumber++ ) { sum[ client ] = sum[ client ] + claimAmount[ claimNumber ]; } «Исправление». if ( client == 45 ) { sum[ 45 ] = sum[ 45 ] + 3.45; } Теперь допустим, что при нулевом числе исков (number of claims) со стороны клиента 37 вы не получаете 0. Плохое решение этой проблемы могло бы быть таким: Продолжение примера ухудшения кода в результате его «исправления» (Java) for ( claimNumber = 0; claimNumber < numClaims[ client ]; claimNumber++ ) { sum[ client ] = sum[ client ] + claimAmount[ claimNumber ]; } if ( client == 45 ) { sum[ 45 ] = sum[ 45 ] + 3.45; } Второе «исправление». else if ( ( client == 37 ) && ( numClaims[ client ] == 0 ) ) { sum[ 37 ] = 0.0; } Если уж и это не заставляет вас содрогнуться от ужаса, вы зря читаете мою книгу: ничто в ней вас не тронет. Перечислить все недостатки этого подхода в книге, объемом лишь около 1000 страниц, невозможно, поэтому ниже я указал только три главных. В большинстве случаев эти исправления работать не будут. Проблемы в этом примере очень похожи на дефекты инициализации. Дефекты инициализации по определению непредсказуемы, поэтому тот факт, что сумма для клиента 45 отличается сегодня от верного значения на 3,45 доллара, ничего не говорит о том, что будет завтра. Завтра она может отличаться на 10 000,02 доллара, а может быть верной. Таковы ошибки инициализации. Такой код трудно сопровождать. Если для исправления ошибок создаются ча# стные случаи, они становятся самой заметной особенностью кода. Значение 3,45 доллара не всегда будет таким, и позднее возникнет другая ошибка. В код придется включить новый частный случай, а частный случай для 3,45 доллара удален не будет. К коду будут прилипать все новые частные случаи. В конце концов эти «прилипалы» станут слишком тяжелыми для кода, и он пойдет на дно, где ему самое место. > > 542 ЧАСТЬ V Усовершенствование кода Неразумно использовать компьютер для выполнения чего#то, что лучше сде# лать вручную. Компьютеры хороши для выполнения предсказуемых система# тичных вычислений, а творческая фальсификация данных лучше дается лю# дям. Вместо подделки данных в коде лучше было бы исправить их в распечат# ке результатов работы программы. Изменяйте код только при наличии веских оснований С устранением только симптомов проблемы тесно связана методика случайного изменения кода до тех пор, пока он не покажется верным. Типичный ход мыслей при этом таков: «Похо# же, этот цикл содержит дефект. Наверное, это ошибка занижения или завышения на 1, так что я просто отниму 1 и посмотрю, что получится. Ерунда какая#то. Ну#ка, а если прибавить 1? Вроде все работает. Думаю, проблема решена». Какой бы популярной ни была эта методика, она неэффективна. Внесение слу# чайных изменений в код похоже на накачивание шин автомобиля при неисправ# ности двигателя. Так вы ничего не узнаете — только впустую потратите время. Изменяя программу случайным образом, вы по сути говорите: «Не знаю, в чем тут дело. Будем надеяться, что это изменение сработает». Не делайте так. Это вуду#про# граммирование. Чем сильнее вы измените код, не понимая его, тем более сомни# тельной станет его корректность. Не вносите в программу изменение, если не уверены в его правильности. Невер# ные изменения должны вызывать у вас удивление Они должны вызывать сомне# ния, пересмотр взглядов и самокритику. Они должны быть редкими. Вносите в код по одному изменению за раз Одиночные изменения и так до# вольно коварны. При внесении сразу двух изменений дело только осложняется: они могут привести к тонким ошибкам, похожим на первоначальные. В итоге вы попадаете в затруднительное положение: как узнать, имеете ли вы дело со старой ошибкой, только с новой ошибкой, похожей на старую, или сразу с новой и ста# рой? Не осложняйте себе жизнь и вносите изменения только по одному за раз. Проверяйте исправления Проверьте программу сами, попросите сделать это кого#то другого или проанализируй# те программу вместе. Выполните те же триангуляционные тесты, что и при диагностике проблемы, и проверьте, все ли аспекты проблемы устранены. Если решена только часть проблемы, вы об этом узнаете. Проверьте всю программу на предмет того, не привели ли сделанные изменения к побочным эффектам. Самый легкий и эффективный способ обнаружения по# бочных эффектов — автоматизированное регрессивное тестирование програм# мы в среде JUnit, CppUnit или аналогичной. Добавляйте в набор тестов блочные тесты, приводящие к проявлению имеющихся дефектов Обнаружив ошибку, на которую не смогли указать име# ющиеся тесты, добавьте в набор тестов новый тест, позволяющий предотвратить возвращение этой ошибки. Поищите похожие дефекты Обнаружив один дефект, поищите аналогичные дефекты. Дефекты часто появляются группами, и, обращая внимание на типы своих дефектов, вы сможете исправлять все дефекты конкретного типа. Поиск похожих Перекрестная ссылка Об авто- матизированном регрессивном тестировании см. подраздел «Повторное (регрессивное) те- стирование» раздела 22.6. ГЛАВА 23 Отладка 543 дефектов требует глубокого понимания проблемы. Если вы не можете сообразить, как искать похожие дефекты, значит, вы еще не полностью понимаете проблему. 23.4. Психологические аспекты отладки Отладка предъявляет к интеллекту не меньшие требования, чем любые другие этапы разработки ПО. Самолюбие гово# рит вам, что ваш код не может содержать дефектов, даже если вы уже встречали их в нем. Выдвигая гипотезы, собирая дан# ные, анализируя гипотезы и методично отказываясь от них, вы должны думать строго, стать немыслимым формалистом. Отлаживая собственный код, вы должны быстро переклю# чаться между гибким творческим мышлением, характерным для проектирования, и жестким критичным мышлением, нужным для отладки. Читая код, стремитесь забыть о том, что он вам известен, и увидеть то, что написано на самом деле, а не то, что вы ожидаете увидеть. «Психологическая установка» и слепота при отладке Встречая в программе элемент Num, что вы видите? Неправильно написанное слово «Numb»? Или аббревиатуру слова «Number»? Скорее всего второе. Это одно из проявлений «психологической установки»: вы видите то, что ожидаете увидеть. Что написано тут? Этот казус уже стал классическим: люди часто замечают только один артикль «the». Они видят то, что ожидают увидеть. Ниже описаны другие похожие факты. Студенты, изучающие циклы while, часто считают, что условие цикла оцени# вается непрерывно, т. е. ожидают, что цикл завершится сразу, как только усло# вие станет ложным, а не после проверки условия (Curtis et al., 1986). Они ду# мают, что цикл while должен соответствовать слову «пока» в естественном языке. Программист, который неумышленно использовал и переменную SYSTSTS, и переменную SYSSTSTS, думал, что имеет дело с одной перемен ной. Он не обнаружил проблему, пока программа не была выполнена сотни раз и не была написана книга, содержащая ошибочные результаты (Wein# berg, 1998). Программисты, сталкивающиеся с кодом: if ( x < y ) swap = x; x = y; y = swap; иногда воспринимают его как: Дополнительные сведения Пре- красное обсуждение психологи- ческих аспектов отладки и мно- гих других областей разработ- ки ПО вы найдете в книге «The Psychology of Computer Prog- ramming» (Weinberg, 1998). 544 ЧАСТЬ V Усовершенствование кода if ( x < y ) { swap = x; x = y; y = swap; } Люди ожидают, что новый феномен будет напоминать аналогичные феномены, виденные ими ранее. Они ожидают, что новая управляющая стурктура будет ра# ботать так же, как и старые структуры, что оператор while языка программирова# ния будет соответствовать слову «пока», а имена переменных будут такими же, какими были раньше. Вы видите то, что ожидаете увидеть, и поэтому не замечае# те отличия, такие как неправильное написание слова «структура» в предыдущем предложении. Какое отношение психологическая установка имеет к отладке? Во#первых, она под# черкивает важность грамотных методик программирования. Разумное форматиро# вание и комментирование, удачные имена переменных, методов и другие элемен# ты стиля программирования помогают структурировать фоновые условия програм# мирования так, чтобы вероятные дефекты четко выделялись на общем фоне. Во#вторых, психологическая установка влияет на выбор частей программы, изу# чаемых при обнаружении ошибки. Исследования показали, что программисты, отлаживающие код максимально эффективно, во время отладки мысленно отде# ляют нерелевантные фрагменты программы (Basili, Selby, and Hutchens, 1986). Это позволяет им сужать область поиска и находить дефекты быстрее. Однако при этом можно по ошибке «отложить в сторону» часть программы, содержащую дефект. В результате вы будете искать дефект в правильном фрагменте кода, игнорируя фрагмент, который на самом деле содержит дефект. Вы пойдете по неверному пути и должны будете вернуться к перекрестку. Некоторые советы из раздела 23.2 по# могут преодолеть эту «слепоту при отладке». «Психологическая дистанция» Психологическую дистанцию можно определить как лег# кость различения двух элементов. Если дать кому#нибудь длинный список слов и сказать, что все они имеют отно# шение к уткам, человек может с легкостью перепутать сло# ва «Queck» и «Quack», потому что они кажутся похожими. Психологическая дис# танция между ними мала. Гораздо труднее перепутать «Tuack» и «Quack», хотя они тоже различаются только одной буквой. Слово «Tuack» похоже на «Quack» мень# ше, чем «Queck», потому что первая буква слова сильнее бросается в глаза, чем буква в середине. Вот примеры психологических дистанций между именами переменных (табл. 23#1): Перекрестная ссылка Советы по выбору ясных имен переменных см. в разделе 11.7. ГЛАВА 23 Отладка 545 Табл. 23-1. Примеры психологических дистанций между именами переменных Первая переменная Вторая переменная Психологическая дистанция stoppt stcppt Почти незаметна shiftrn shiftrm Почти отсутствует dcount bcount Небольшая claims1 claims2 Небольшая product sum Большая Приступая к отладке, будьте готовы к проблемам, обусловленным недостаточной психологической дистанцией между похожими именами переменных и методов. Выбирайте во время конструирования имена, ясно отличающиеся от других имен, и вы забудете об этих проблемах. 23.5. Инструменты отладки — очевидные и не очень Отладку можно значительно упростить, используя доступ# ные инструменты. Инструментов, способных забить осино# вый кол в сердце дефекта#вампира, еще не существует, но с каждым годом они становятся все лучше и лучше. Утилиты сравнения исходного кода Утилиты сравнения исходного кода (такие как Diff) полезны при исправлении ошибок. Если после внесения нескольких изменений некоторые из них нужно отменить, но вы плохо помните, что именно вы изменяли, утилита сравнения исходного кода освежит вашу память, указав на различия кода. Если вы обнару# жили в новой версии кода дефект, которого не было в предыдущей версии, про# сто сравните соответствующие файлы. Предупреждающие сообщения компилятора Одним из самых простых и эффективных инструментов отладки явля# ется сам компилятор. Задайте в компиляторе максимально строгий уровень диагностики и устраняйте все ошибки и предупреждения Игнорировать сообщения ком# пилятора об ошибках глупо, но отключать вывод предупреждений еще глупее. Детям иногда кажется, что, если они закроют глаза и перестанут вас видеть, вы пропа# дете. Отключая вывод предупреждений, вы совершаете такую же ошибку: вы лишь перестаете их видеть, но их причины никуда не исчезают. Исходите из того, что разработчики компилятора знают язык гораздо лучше вас. Если они о чем#то вас предупреждают, рассматривайте это как возможность уз# нать о языке что#то новое. Стремитесь понять подлинный смысл каждого предуп# реждения. Рассматривайте предупреждения как ошибки Некоторые компиляторы по# зволяют рассматривать предупреждения как ошибки. Одно из достоинств этой Перекрестная ссылка Граница между инструментами тестиро- вания и отладки размыта. Об инструментах тестирования см. раздел 22.5, а об инструментах разработки ПО — главу 30. 546 ЧАСТЬ V Усовершенствование кода функции в том, что она повышает важность предупреждений. Как перевод часов на пять минут вперед заставляет думать, что сейчас на пять минут больше, чем на самом деле, так и указание компилятору считать предупреждения ошибками зас# тавляет воспринимать их более серьезно. Кроме того, эта функция часто влияет на процесс компиляции программы. При компиляции и компоновке программы предупреждения в отличие от ошибок обычно не предотвращают компоновку. Если вы хотите проверить предупреждения перед компоновкой, прикажите компиля# тору рассматривать предупреждения как ошибки. Стандартизуйте параметры компиляции в масштабе всего проекта Разработайте стандарт, требующий, чтобы все участники проекта компилирова# ли код, используя одинаковые параметры. Иначе при интеграции кода, скомпи# лированного с использованием разных параметров, вы утонете в сообщениях об ошибках и сами узнаете, что такое интеграционный кошмар. Стандарт легко на# вязать, создав один общий make#файл или сценарий сборки программы. Утилиты расширенной проверки синтаксиса и логики Существуют инструменты, проверяющие код тщательнее, чем компилятор. Так, программисты на C используют утилиту lint, которая старательно ищет неиници# ализированные переменные (возникающие, например, при написании = = вмес# то =) и похожие тонкие проблемы. Инструменты профилирования выполнения программы Вам может показаться, что инструменты профилирования не имеют отношения к отладке, но на самом деле несколько минут, потраченных на изучение профиля программы, могут указать на некоторые неожиданные (и скрытые) дефекты. Например, при работе над одной из программ у меня возникло подозрение, что метод управления памятью снижает быстродействие программы. Модуль управ# ления памятью изначально был небольшим компонентом, использующим линей# но упорядоченный массив указателей на области памяти. Я заменил линейно упо# рядоченный массив на хэш#таблицу, ожидая, что время выполнения кода сокра# тится минимум вдвое. Однако при профилировании кода я не обнаружил изме# нения быстродействия. Тщательнее изучив код, я обнаружил дефект в алгоритме выделения памяти, приводивший к огромным тратам времени. Узкое место было обусловлено не линейным поиском, а дефектом. Алгоритм поиска вообще можно было не оптимизировать. Исследуйте результаты работы инструмента профили# рования, чтобы гарантировать, что каждая часть программы выполняется за ра# зумный интервал времени. Среды тестирования и леса Как уже было сказано в разделе 23.2 в отношении поиска дефектов, выделение проблемного фрагмента кода и его тестирование в изоляции от других фрагментов часто ока# зывается самым эффективным способом изгнания бесов из дефектной программы. Перекрестная ссылка О лесах см. подраздел «Создание лесов для тестирования отдельных классов» раздела 22.5. |