\/d Г Л А В А VIII. НЕКОТОРЫЕ ВОПРОСЫ МЕТОДОЛОГИИ ОТЛАДКИ ПРОГРАММ \/d- Я услышал и забыл. Я увидел и запомнил. Я сделал и понял! Г.Клейман О т л а д к а = О б н а р у ж е н и е ошибки + ее И с п р а в л е н и е ZWWWWWWWWWWWWW▌WWWWWWWWWWWWW[ V Т е с т и р о в а н и е + Л о к а л и з а ц и я XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V X Y X Y V V V Процесс V V Процесс V V V Процесс отладки = SобнаруженияT + SисправленияT V V V ошибки V V ошибки V V V Z ▌ [ Z [ V V V V V Тестирование + Локализация V V программы ошибки V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ VIII.1 ОШИБКИ ПРИ ПРОГРАММИРОВАНИИ Какую бы программу вы ни писали, любая ошибка, которая может в нее вкрасться, - вкрадется! Следствие первого закона Чизхолма Будем говорить, что "в программе имеется о ш и б к а, если ее выполне- ние не оправдывает ожиданий пользователя" [50, 51]. Напомним, что при решении задач с использованием компьютера под о т- л а д к о й программ понимается обычно один из этапов решения, во время которого с помощью компьютера происходит обнаружение и исправление оши- бок, имеющихся в программе ; в ходе отладки программист хочет добиться определенной степени уверенности в том, что его программа соответствует своему назначению и не делает того, для чего она не предназначена. Все мы делаем ошибки при программировании. Даже программисты с двадца- тилетним опытом работы допускают их десятками. Разница между хорошим и плохим программистом заключается не в том, что первый не делает ошибок, а скорее в том, что он делает значительно меньше п р о с т ы х ошибок. Начинающий программист, как правило, переоценивает свои возможности и, разрабатывая программу, исходит из того, что в его программе ошибок не бу- дет. А говоря про только что составленную программу, готов уверять, что она на 99% правильна, и ее остается только для большей уверенности один(!) раз выполнить на компьютере с какими-нибудь(!) исходными данными. Естест- венно, что каждый неверный результат, каждая найденная ошибка вызывают у него изумление и считаются, к о н е ч н о, последними. Вследствие такого подхода получение с помощью компьютера надежных результатов по составлен- ной программе откладывается на длительный и неопределенный срок. Только приобретя достаточный опыт, программист понимает справедливость древнего высказывания: "Человеку с в о й с т в е н н о о ш и б а т ь с я!". Оказывается, что практически невозможно для достаточно сложной програм- мы быстро найти и устранить все имеющиеся в ней ошибки. Трудности програм- мирования и отладки подчеркивает следующий популярный в среде программис- тов афоризм: "В к а ж д о й программе есть по крайней мере одна ошибка". Поэтому можно сказать, что наличие ошибок в только что разработанной про- грамме - вполне нормальное и закономерное явление. А совсем ненормальным, из ряда вон выходящим фактом является отсутствие ошибок в программе, кото- рая не была еще подвергнута тщательному тестированию и отладке (конечно, речь здесь идет о достаточно сложных программах!). Поэтому разумно уже при разработке программы на этапах алгоритмизации и программирования г о т о в и т ь с я к о б н а р у ж е н и ю о ш и - б о к на стадии отладки и принимать профилактические меры для их преду- преждения. Ошибки можно объединить в следующие группы [50, 51]: 1) ошибки обращения к данным - использование переменных с неустановлен- ными значениями, ошибки индексации, несоответствие структур данных; 2) ошибки описания данных - отсутствие явного описания или неполное описание данных, отсутствие или неправильное присвоение начальных значе- ний, несогласованность инициализации переменной с ее описанием; 3) ошибки вычислений - наличие в последовательных вычислениях данных недопустимых типов, несогласованность масштабов,приводящая к переполнению или потере точности, возможность деления на нуль; 4) ошибки при сравнениях - использование при операциях сравнения вели- чин несовместимых типов, искажение смысла операций отношения (>, =, <) и логических операций (NOT, AND, OR), сравнение чисел с фиксированной и пла- вающей запятой; 5) ошибки в передачах управления - ошибки организации циклов, приводя- щие к возможности зацикливания или неправильного выполнения цикла, нали- чие неполного числа выходов в операторах-переключателях; 6) ошибки программного и н т е р ф е й с а - несоответствие количест- ва, типов или размерности фактических и формальных параметров подпро- грамм при их вызове, несоответствие описаний переменных требованиям на вы- ходе модуля, несогласованность описаний глобальных переменных и их интер- претации операторами программы (м о д у л ь - это замкнутая программа, ко- торую можно вызвать из любого другого модуля в программе и можно отдельно компилировать). Напомним, что программный и н т е р ф е й с определяет совокупность допустимых процедур или операций и их параметров, список об- щих переменных, областей памяти или других объектов; 7) ошибки ввода-вывода - неполное или неправильное описание атрибутов файлов или оператора обращения к файлу, несогласованность размера записи и выделенной памяти, неполный контроль и регистрация операций с файлами; 8) помехозащита - отсутствие контроля входных данных, отсутствие сохра- нения исходных данных и возможности повторного запуска модуля при сбоях. 9) никогда не считайте, что Вы точно знаете причину ошибочного выполне- ния программы; очень часто в этом виновна ошибка, встретившаяся гораздо раньше (иногда ее называют о т л о ж е н н о й ошибкой). 10) и, наконец, ошибки могут быть также следствием неверной работы обо- рудования - это так называемые а п п а р а т н ы е ошибки.Если в регистр памяти компьютера на одном из этапов работы программы занесено число 12, а при чтении из этого же регистра оно прочиталось как 11, то и дальнейшие результаты, разумеется, будут неверными. Возможен случай, когда из-за та- кой ошибки результат вовсе не будет получен(процесс решения задачи аварий- но прекратится). Разработаны надежные методы борьбы с аппаратными ошибками и их послед- ствиями - п о в т о р н ы е вычисления с последующим сравнением резуль- татов, хранение нескольких экземпляров данных с целью их защиты от искаже- ния и т.д. Поэтому среди встречающихся на практике случаев выдачи компью- терами неверных результатов или невыдачи их вообще доля ошибок, порожден- ных аппаратными средствами, составляет ничтожный процент. Так, согласно одному из определений "ПЭВМ - это вычислительная машина с надежностью военной аппаратуры и ценой изделия бытовой электроники"[58]. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY VТаким образом,в ошибочных ответах компьютера виноваты,как правило,люди!V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Приведенная классификация полезна тем, что для каждой из перечисленных групп ошибок и для каждого типа ошибки в группе можно выделить операторы каждого конкретного языка программирования, потенциально допускающие дан- ный тип ошибок. Некоторые ошибки являются с и с т е м а т и ч е с к и м и; они возни- кают всякий раз при выполнении программы. Если в команде на вычисление по заданной формуле вместо плюса поставить минус, то и ответ, скорее всего, будет ошибочным. Такие ошибки в программах обычно живут недолго: их быст- ро обнаруживают и исправляют. Приведем интересный пример систематической ошибки в программном интерфейсе. 19 июня 1985 года команда американского космоплана многоразового ис- пользования ("Шаттла") должна была развернуть свой корабль так, чтобы зер- кало на его борту могло отражать луч лазера,находившегося на горе высотой 10023 фута. Навигационная система пыталась развернуть "Шаттл" так, чтобы принимать луч с вершины несуществующей горы высотой в 10023 морских миль над уровнем моря. Это произошло из-за того, что один из пары взаимосвязан- ных компонентов программно-аппаратного комплекса передавал высоту в футах, а другой - интерпретировал эту величину в милях. Другие ошибки носят с л у ч а й н ы й характер; при каждом выполне- нии программы они будут приводить к разным результатам, либо программа мо- жет выполняться в большинстве случаев правильно, но время от времени нео- жиданно давать неверный результат. Такие случайные ошибки следует старать- ся выявить на этапе ручной проверки, потому что при машинном выполнении программы они могут "исчезнуть" лишь для того,чтобы снова появиться позже. В некотором смысле достаточно сложная программа напоминает карточный домик: тот факт, что домик стоит, еще не гарантирует, что он не рассыплет- ся в следующее мгновение. Программы опровергают опыт нашей жизни. Обычно, если что-то работает, то оно работает! Если новый стул выдержал Вас, он выдержит Вас и в следующий раз; если сошедший с конвейера автомобиль про- ехал один километр, он сможет проехать еще сотни километров; если здание простояло 5 минут, то, как уверяют строители и архитекторы, оно простоит еще сто лет. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Однако на основании того факта, что часть программы работает, ничего V V нельзя сказать о работоспособности остальной части программы! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Показательны в этом отношении результаты первых попыток запуска "Шат- тла".Программное обеспечение этого космического корабля состояло примерно из полумиллиона программных строк,над которыми трудился большой коллектив разработчиков. Корабль не смог оторваться от Земли из-за нарушения синхро- низации всех его компьютеров. Оказалось, что программная ошибка,явившаяся причиной неудачи, была внесена нечаянно при исправлении другой ошибки, об- наруженной двумя годами раньше, и могла проявляться в среднем только в од- ном из 67 полетов. Одна из наиболее типичных случайных ошибок возникает, если Вы забыли инициализировать переменную, т.е. присвоить ей начальное значение. В этом случае начальное значение переменной зависит от того, какая программа (на- зовем ее Р) выполнялась на компьютере перед тем,как была загружена отлажи- ваемая программа. Если Ваша переменная имеет адрес ═,то программа Р может оставить после своего выполнения в ячейке по адресу ═ "все, что угодно" - код команды, значение переменной, значение адреса переменной (м у с о р, т.е. произвольное, непредсказуемое значение). Еще одной часто встречающейся случайной ошибкой является запись дан- ных в массив, когда значение индекса вышло за допустимые пределы. Если,на- пример, Вы присваиваете начальное значение элементу массива T(J), а значе- ние индекса J должно находиться в границах от 1 до 100, но J случайно ока- залось в какой-то момент больше 100 либо меньше 1,то Вы, разумеется,полу- чите не тот результат, который хотели. Если Вы часто совершаете ошибку та- кого рода, Вам полезно написать несколько операторов, которые будут прове- рять значения каждого индекса перед его использованием и фиксировать его выход из установленного диапазона. Конечно,это увеличивает время выполне- ния программы, но заметно ускоряет процесс отладки. В заключение - п о л е з н ы й совет: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V старайтесь вести список допущенных Вами ошибок V. ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Каждый раз, когда Вы обнаруживаете ошибку, заносите ее в этот список и обязательно проверяйте, чтобы она не повторилась в дальнейшем! XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V "Чтобы избегать о ш и б о к, надо набираться о п ы т а, V V чтобы набираться о п ы т а , надо делать о ш и б к и". V V Принцип Компетентности по Питеру V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ VIII.2. НЕКОТОРЫЕ КЛАССИЧЕСКИЕ ПРИЕМЫ ТЕСТИРОВАНИЯ ПРОГРАММ Самая коварная уловка дьявола состоит в том, чтобы убедить нас, будто его не существует. Ш.Бодлер Известно, что в процессе разработки программы работы по доказательству (д е м о н с т р а ц и и) правильности разрабатываемой программы равно- значны работам по ее изготовлению (алгоритмизации и написанию), что можно выразить следующей формулой: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Разработка программы = Изготовление + Доказательство V . ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Поэтому п р о г р а м м о й следовало бы называть только такую про- грамму, которая выдает правильные результаты, а то, что еще не прошло ста- дию доказательства правильности, является не программой, а ее п о л у- ф а б р и к а т о м. Изготовление такого полуфабриката, конечно, являет- ся делом несравнимо более легким, чем разработка н а с т о я щ е й про- граммы (особенно если программист и не думает о предстоящей отладке!). VIII.2.1. Р у ч н а я п р о в е р к а Если вам кажется, что ситуация улучшается, значит, вы чего-то не заметили! Второе следствие второго закона Чизхолма Отладку любой программы никогда не следует начинать с прогона програм- мы на компьютере, т.к. экспериментально установлено, что "ручными" метода- ми (т.е. без помощи компьютера) удается обнаруживать от 30 до 70% програм- мных и аналитических ошибок из общего числа ошибок,допущенных при програм- мировании! Вначале обязательно проведите р у ч н у ю п р о в е р к у ("desk che- cking"),которая есть не что иное, как тщательная проверка Вашей програм- мы за письменным столом. При ручной проверке программы (или алгоритма) программист по тексту программы мысленно старается восстановить тот вычис- лительный процесс, который определяет программа, после чего сверяет его с требуемым процессом. Самое главное, о чем всегда следует помнить, это то, что ошибки в про- веряемой программе о б я з а т е л ь н о есть и, чем больше их будет об- наружено за с т о л о м, тем легче и быстрее пройдет предстоящий этап отладки программы на компьютере. На время проверки нужно постараться "за- быть" о том, что должен делать проверяемый участок программы, и "узнавать" об этом по ходу его проверки.Только после окончания проверки участка и вы- явления тем самым его действительных функций можно "вспомнить" о том, что он должен делать, и сравнить реальные действия программы с требуемыми. Полезно, найдя какую-либо специфическую ошибку,отойти от последователь- ной проверки и сразу узнать,нет ли таких же ошибок в аналогичных,в особен- ности уже проверенных, местах. Приведем примерный список вопросов, на которые отвечает программист при р у ч н о й п р о в е р к е программы. 1. Есть ли обращения к переменным, которым не присвоены значения? "Присваивайте начальные значения переменным перед тем, как они будут использованы. Убедитесь в том, что переменным в программах и во внутрен- них циклах присваиваются соответствующие значения при каждом входе в них" [56]. 2. Не выходит ли значение индекса элемента массива за границы, опреде- ленные для соответствующего измерения, при всех обращениях к массиву? "Проверьте, чтобы индексы при обращении к элементам массива не выходи- ли за границы"[56]. 3. Принимает ли каждый индекс целые значения при всех обращениях к мас- сиву? Нецелые индексы не являются ошибкой для многих языков программирова- ния, но представляют практическую опасность. 4. Все ли переменные описаны явно? Отсутствие явного описания не обяза- тельно является ошибкой, но обычно служит источником беспокойства. 5. Есть ли переменные со сходными именами (например, VOLT и VOLTS)? На- личие сходных имен не обязательно является ошибкой, но служит признаком того, что имена могут быть перепутаны где-нибудь внутри программы. 6. Возможны ли переполнение или исчезновение порядка во время вычисле- ния значения выражения? Это означает, что конечный результат может казать- ся правильным, но промежуточный результат может быть слишком большим (пе- реполнение) или слишком малым (исчезновение порядка) для машинного пред- ставления данных. 7. Учтено ли, что делитель при делении может обратиться в нуль? 8. Может ли значение переменной выходить за пределы установленного для ее типа диапазона? 9. Сравниваются ли величины различных типов? 10. Корректны ли операции сравнения? Обычно часто путают такие операции отношения, как "не меньше, чем", "не больше, чем". 11. Каждоe ли логическое выражение сформулировано так, как это предпола- галось? Программисты часто делают ошибки при написании логических выраже- ний, содержащих операции NOT, AND, OR. 12. Если в программе содержится оператор-переключатель ON k GOTO m1,m2,...,mn , то может ли значение переменной k превысить n? Например, всегда ли k бу- дет принимать значения 1, 2 или 3 в операторе ON k GOTO 10,20,30 ? 13. Будет ли каждый цикл в конце концов завершен? Придумайте неформаль- ное доказательство или аргументы, подтверждающие их завершение. "Не используйте числа с плавающей запятой в качестве значений счет- чиков. Не надейтесь, что для дробных величин с плавающей запятой справед- ливы известные правила арифметики - это не так"[56]. 14. Будет ли программа или подпрограмма в конечном счете завершена? 15. Возможно ли, что некоторый цикл никогда не сможет выполниться? Если это так, то является ли это оплошностью? "Проверьте, могут ли циклы в программе при определенных обстоятельствах выполняться нулевое число раз"[56]. 16. Совпадают ли количество и тип формальных и фактических параметров используемых подпрограмм? 17. Совпадают ли единицы измерения значений соответствующих фактических и формальных параметров? Например, нет ли случаев, когда значение фактиче- ского параметра выражено в градусах,а в подпрограмме все расчеты проводят- ся с формальным параметром, выраженным в радианах. 18. Не изменяет ли подпрограмма значения переменной, которая использует- ся только как входная величина? 19. Все ли файлы открыты перед их использованием? 20. Существуют ли смысловые или грамматические ошибки в тексте, выводи- мом программой на печать или на экран дисплея? VIII.2.2. Р у ч н а я п р о к р у т к а. М е т о д и ч е с к и е у к а з а н и я п о е е п р о в е д е н и ю Программисту не всегда нужна ЭВМ, иногда по- лезнее удобное кресло и спокойная обстановка. А.Архангельский После окончания ручной проверки проведите несколько раз р у ч н у ю п р о к р у т к у ("walkthrough" - "сквозной контроль") отдельных частей Вашей программы. Иногда ее называют "с у х о й" п р о к р у т к о й ("dry running"-"пробный прогон") в отличие от метода прокрутки, использующего компьютер. Основой п р о к р у т к и является имитация программистом про- цесса выполнения программы (алгоритма) компьютером с целью более конкрет- ного и наглядного представления о процессе, определяемом т е к с т о м проверяемой программы. Прокрутка дает возможность приблизить последовате- льность проверки программы к последовательности ее выполнения, что позво- ляет проверять программу как бы в динамике ее работы, проверять элементы вычислительного процесса, задаваемого проверяемой программой, а не только статичный текст программы. Для выполнения прокрутки обычно приходится задавать какие-то конкрет- ные исходные данные и производить над ними необходимые вычисления, исполь- зуя текст программы. Для программ со сложной логикой, в которых, например, характер работы одного участка программы зависит от результатов работы других ее участков,необходимо осуществлять ручную прокрутку программы для ряда специально подобранных исходных данных и параметров. Прокрутка дает программисту возможность найти более хитрые ошибки в программе, чем при ручной проверке. Трудность применения прокрутки - большой объем ручной работы при попыт- ке точного моделирования работы программы. Поэтому успех применения про- крутки заключается в выборе такой необходимой степени детализации модели- рования, чтобы, с одной стороны, выявить максимальное количество ошибок,а с другой - затратить на это минимальные усилия. Приведем несколько соображений, которые могут помочь уменьшить время, затрачиваемое на прокрутку. Прокрутку следует применять лишь для контроля логически сложных про- грамм или б л о к о в (под б л о к о м будем понимать некоторую группу операторов, объединяемых по какому-либо признаку, например: арифметиче- ский блок - выполняемая последовательно группа операторов, производящих вычисления в программе, логический блок - группа операторов, управляющих последовательностью вычислений в программе). А р и ф м е т и ч е с к и е блоки нужно проверять обычным способом,не задаваясь конкретными исходными данными. Вычислять числовые значения нуж- но лишь для тех величин, от которых зависит последовательность выполнения блоков (операторов) программы, и эта последовательность является очень су- щественной. Поэтому во время прокрутки программы при всякой возможности, когда позволяет характер прокручиваемого блока программы,нужно переходить на обычную ручную проверку и возвращаться на режим прокрутки при начале проверки логически сложных блоков. Исходные данные, влияющие на логику программы, должны выбираться так, чтобы минимизировать прокрутку программы. Но данные должны быть и такими, чтобы в прокрутку вовлеклось большинство ветвей программы и чтобы прокрут- ка отразила типичный характер ее работы. Кроме того, в ходе прокрутки необходимо проверить работу программы и для особых случаев (например, для экстремальных значений параметров). Многократные повторные прокрутки какого-либо участка программы можно не производить, если в логике его выполнения ничего не изменяется по срав- нению с предыдущими проходами. Например, тело цикла можно прокрутить лишь для п е р в ы х двух-трех проходов (проверка входа в цикл) и для п о- с л е д н и х одного-двух (проверка выхода из цикла). Прокрутка бывает необходимой и в том случае, когда программист не в со- стоянии вполне четко представить себе логику проверяемой программы, осо- бенно XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V если программа написана не им и нет хорошего описания V . ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ При первом же пробном запуске вычислительной машины МЭСМ (первая в СССР ЭВМ, 1951 г.) произошел показательный случай. Первую программу для МЭСМ перед запуском прокрутили вручную два квалифицированных математика и получили одинаковые результаты. А машина выдала другой результат. Инжене- ры долго искали неисправность и не смогли ее найти. Тогда академик С.А.Ле- бедев, главный конструктор МЭСМ, сам взялся за ручную прокрутку. Прорабо- тав всю ночь, он обнаружил, что оба математика ошиблись в одном и том же месте, а машина оказалась права! Поговорим теперь о м е т о д и к е проведения ручной прокрутки. Нарисуйте на листе бумаги "начальную обстановку",а затем - "исполняйте" операторы по одному, отмечая все изменения, происходящие при этом в запо- минающем устройстве. Поэтому для ручной прокрутки нужно уметь изображать на бумаге начальную обстановку в Оперативном Запоминающем Устройстве и происходящие в нем изменения. Разумеется, нас интересуют не все б л о к и (участки) памяти, а только те из них, которые используются в программе. Удобнее всего рисовать блоки и их значения мелом на школьной доске: ведь для того, чтобы поместить в блок новое содержимое,нужно "уничтожить" (сте- реть) старое - на доске это сделать очень просто. Каждый блок можно рисо- вать в виде "домика", на "крыше" которого записано имя блока,а внутри раз- мещается содержимое. Форма крыши может говорить о типе переменной. XWWWWWWWWWY V ГОД V Например, на рисунке изображен блок, имя которого VWWWWWWWWWV ГОД, содержимое блока - 1987, тип - строковый, т.к. кры- V 1987 V ша прямоугольная. ZWWWWWWWWW[ Если доски нет, ручную прокрутку можно вести и на бумаге. При этом таб- лицу имен (блоков) приходится изображать немножко по-другому, потому что стирать старые значения на бумаге неудобно, лучше их зачеркивать, а рядом писать новые значения. Ручную прокрутку лучше всего проводить в д в о е м с приятелем. Вы- полняйте строку за строкой программы (алгоритма) и старайтесь независимо друг от друга обнаружить ошибки. Однако избегайте такой методики в том случае, если выяснится, что Вы испытываете искушение переложить на своего приятеля (или он на Вас) заботу о тестировании программы! В заключение необходимо отметить,что использование прокрутки весьма по- лезно еще и потому, что она содействует глубокому осознанию программистом логики составленной им программы и того реального вычислительного процес- са, который ею задается. Ведь быстрота ориентирования в отлаживаемой про- грамме и в выдаваемых отладочных результатах всецело зависит от способно- сти программиста мысленно представить себе во всех деталях алгоритм рас- сматриваемой программы,что невозможно без глубокого и крепкого знания его в течение всего длительного времени проведения отладки.  VIII.2.3. М е т о д к о н т р о л ь н ы х т е с т о в Никогда не берите на корабль два хронометра, берите один или, если есть возможность, три, но не два. Наставление мореплавателям начала ХIХ века ...путем тестирования никогда нельзя установить от- сутствие ошибок в программе. Дело в том, что невоз- можно определенно утверждать, что при проверке най- дена последняя ошибка в программе; следовательно,ни- когда нельзя быть уверенным,что может быть найдена и первая ошибка. Р.Лингер, Х.Миллс, Б.Уитт Как бы ни была тщательно проверена и "прокручена" программа за столом, решающим этапом, устанавливающим ее пригодность для работы, является конт- роль программы по результатам ее выполнения на компьютере. Мы рассмотрим здесь универсальный метод контроля - м е т о д к о н - т р о л ь н ы х т е с т о в ("test" - "испытание", "проверка"). Т е с т и р о в а н и е - это процесс исполнения программы на компью- тере с целью о б н а р у ж е н и я ошибок [50]. Поясним это определение. Т е с т о м будем называть информацию, состоящую из исходных данных, специально подобранных для отлаживаемой программы, и из соответствующих им эталонных результатов (не только окончательных, но и промежуточных), используемых в дальнейшем для контроля правильности работы программы. Если поставить целью демонстрацию о т с у т с т в и я ошибок, то мы подсознательно будем стремиться к этой цели, выбирая тестовые данные, на которых вероятность появления ошибки мала. В то же время, если нашей зада- чей станет обнаружение ошибок, то создаваемый нами тест будет обладать бо- льшей вероятностью обнаружения ошибки. Такой подход заметнее повысит каче- ство программы. Тестирование - процесс деструктивный (т.е.обратный созидательному, кон- структивному). Именно этим и объясняется, почему многие считают его труд- ным. Большинство людей склонно к конструктивному процессу созидания объек- тов и в меньшей степени - к деструктивному процессу разделения на части. Для усиления определения тестирования проанализируем два понятия - "удач- ный" и "неудачный". Большинство назовет тестовый прогон неудачным, если обнаружена ошибка и,наоборот, удачным, если он прошел без ошибок. Из опре- деления тестирования следует противоположное: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V тестовый "прогон" будем называть у д а ч н ы м, если в процессе V V его выполнения обнаружена ошибка, и н е у д а ч н ы м, если по- V V лучен корректный результат V. ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Вопрос о позиции программиста по отношению к продукту его труда связан, как это показано Вейнбергом [68], с принципами безличного программирова- ния и когнитивного диссонанса. К о г н и т и в н ы й д и с с о н а н с - это психологический принцип, который руководит действиями человека, чьи представления о себе оказались под угрозой. "Программист, который искренне считает программу продолжени- ем своего "я", не будет пытаться найти все ошибки в ней. Напротив, он по- старается показать, что программа правильна, даже если это означает не за- мечать ошибок,чудовищных для постороннего взгляда...Человеческий глаз име- ет почти безграничную способность не видеть то, чего он видеть не желает" [68]. Спасти в такой ситуации может безличное программирование. Вместо того, чтобы быть скрытным и защищать свою программу, программист занимает проти- воположную позицию: он открыто приглашает других программистов читать и конструктивно критиковать ее. Когда кто-то находит ошибку в его программе, программист,конечно, не должен радоваться, что ошибся; его позиция пример- но такова: "О! Мы нашли ошибку в н а ш е й программе! Хорошо, что мы нашли ее сейчас, а не позже! Поучимся на этой ошибке, а заодно посмотрим, не найдем ли еще!" Программист, обнаруживший ошибку в чужой программе, не кричит: "Посмотри на свою идиотскую ошибку!", а реагирует примерно так: "К а к любопытно! Интересно, не сделал ли и я такой ошибки в написанном мною модуле?" При использовании метода тестов XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V программа (или отдельный ее блок) считается п р а в и л ь н о й, V V если пропуск программы для выбранной системы тестовых исходных V V данных дает правильные результаты V . ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Таким образом, контроль программы сводится к тому, чтобы подобрать сис- тему тестов, получение правильных результатов для которой гарантировало бы правильную работу программы и для остальных исходных данных из области, указанной в решаемой задаче. Для реализации метода контрольных тестов должны быть изготовлены или заранее известны э т а л о н н ы е результаты, на основании сверки с ко- торыми получаемых тестовых результатов, можно было бы сделать вывод о пра- вильности работы программы на данном тесте. Э т а л о н н ы е результаты для вычислительных задач можно получить, осуществляя вычисления вручную, применяя результаты, полученные ранее на другом компьютере или по другой программе, или, используя известные факты, свойства, физические законы. Разрабатывая систему тестов, нужно стремиться к тому, чтобы успешный пропуск ее на компьютере д о к а з ы в а л наличие ошибок в программе (или отдельном ее блоке), хотя для многих достаточно сложных программ,осо- бенно если над ними работает несколько программистов, можно практически говорить лишь о большей или меньшей вероятности правильности программы. Это объясняется тем, что изготовление и пропуск в с е х тестов, необхо- димых для доказательства, может потребовать такого объема работ, который затянет этап контроля на многие месяцы или годы. Поэтому при разработке системы тестов наряду с задачей всестороннего и глубокого тестирования, стоит задача минимизации количества необходимых тестовых результатов, ма- шинного времени и усилий программиста. В большинстве случаев при использовании метода контрольных тестов во- прос о д о к а з а т е л ь с т в е о т с у т с т в и я ошибок практи- чески можно ставить лишь для небольших блоков (модулей) программы, а для целой программы приходится ограничиваться той или иной вероятностью отсут- ствия ошибок в программе. Неоднократно экспериментально установлено, что в любой сложной програм- ме в процессе эксплуатации обнаруживаются ошибки, даже если проведено са- мое тщательное тестирование. Тем самым утверждается объективная реаль- ность, заключающаяся в невозможности формализовать и обеспечить абсолют- ную полноту всех эталонных значений, а также провести всеобъемлющее исчер- пывающее тестирование и устранить в с е ошибки в сложных программах. Опыт показывает, что до начала тестирования число ошибок в сложных про- граммах - порядка 1-2% от общего числа операторов в программе. Самое тща- тельное тестирование сложных программ позволяет получить программы с веро- ятностью ошибки в каждом операторе 0.0001 ╤ 0.00001,т.е. несколько ошибок может остаться. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V После завершения тестирования программы в течение нескольких V V лет эксплуатации могут быть выявлены еще десятки ошибок! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ VIII.2.3.1. С т р а т е г и я т е с т и р о в а н и я Вы должны радоваться, что мост разрушился,- я планировал построить еще тринадцать по тому же проекту. Замечание, приписываемое Х.Брюнелю, адресованное дирекции Большой запад- ной железной дороги Перечислим основные принципы тестирования [50]. Эти принципы интересны тем, что в основном они интуитивно ясны, но в то же время на них часто не обращают должного внимания. 1. Описание предполагаемых значений выходных данных или результатов должно быть необходимой частью тестового набора. Нарушение этого очевидного принципа представляет одну из наиболее рас- пространенных ошибок. Ошибочные, но правдоподобные результаты могут быть признаны правильными, если результаты теста не были заранее определены. Здесь мы сталкиваемся с явлением психологии: мы видим то,что мы хотим уви- деть. Другими словами,несмотря на то,что тестирование по определению - де- структивный процесс,есть подсознательное желание видеть корректный резуль- тат. Один из способов борьбы с этим состоит в поощрении детального анали- за выходных переменных заранее, еще при разработке теста. 2. Следует избегать тестирования программы ее а в т о р о м . Многие, кому приходилось самому делать дома ремонт, знают, что процесс обрывания старых обоев (деструктивный процесс) не легок, но он просто не- выносим, если не кто-то другой, а Вы сами первоначально их наклеивали.Вот так же и большинство программистов не может эффективно тестировать свои программы, потому что им трудно демонстрировать собственные ошибки. 3. Необходимо досконально изучать результаты применения каждого теста. Представляется достоверным, что значительная часть всех обнаруженных в конечном итоге ошибок могла быть выявлена в результате самых первых тес- товых прогонов, но они были пропущены вследствие недостаточно тщательного анализа результатов первого тестового прогона. 4. Тесты для неправильных и непредусмотренных входных данных следует разрабатывать так же тщательно, как для правильных и предусмотренных. Вполне вероятно,что тесты, представляющие неверные и неправильные вход- ные данные, обладают большей обнаруживающей способностью, чем тесты, соот- ветствующие корректным входным данным. 5. Необходимо проверять не только, делает ли программа то,для чего она предназначена, но и ни делает ли она то, что не должна делать. Обязательно проверяйте программу на нежелательные побочные эффекты. 6. Не следует выбрасывать тесты, даже если программа уже не нужна. Необходимость в использованных тестах наиболее часто возникает в интерак- тивных системах отладки. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Обычно тестирующий сидит за терминалом, на лету придумывает V V тесты и запускает программу на выполнение. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ При такой практике работы после применения тесты пропадают. После вне- сения изменений или исправления ошибок необходимо повторить тестирование, тогда приходится заново изобретать тесты. Как правило, этого стараются из- бегать, поскольку повторное создание тестов требует значительной работы. 7. Нельзя планировать тестирование в предположении, что ошибки не бу- дут обнаружены. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 8. V Вероятность наличия необнаруженных ошибок в части программы V V пропорциональна числу ошибок, уже обнаруженных в этой части. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ На первый взгляд, этот принцип лишен смысла, но тем не менее подтверж- дается многими программами. Например, допустим,что некоторая программа со- стоит из модулей А и В. К определенному сроку в модуле А обнаружено пять ошибок, а в модуле В - только одна, причем модуль А не подвергался более тщательному тестированию. Тогда из рассматриваемого принципа следует, что вероятность необнаруженных ошибок в модуле А больше, чем в модуле В. Спра- ведливость этого принципа подтверждается еще и тем, что для ошибок свойст- венно располагаться в программе в виде неких скоплений, хотя данное явле- ние пока никем еще не объяснено. Таким образом, если в какой-нибудь части программы обнаружено больше ошибок, чем в других, то на ее тестирование должны быть направлены допол- нительные усилия. 9. Тестирование - процесс творческий. Вполне вероятно, что для тестиро- вания большой программы требуется больший творческий потенциал,чем для ее проектирования. Чтобы подчеркнуть некоторые мысли, высказанные в этом разделе,приведем еще раз три наиболее важных принципа тестирования. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Т е с т и р о в а н и е - это процесс выполнения программ на V V компьютере с целью о б н а р у ж е н и я ошибок. V V Х о р о ш и м считается тест, который имеет высокую вероятность V V обнаружения еще не выявленной ошибки. V V У д а ч н ы м является тест, который обнаруживает еще не V V выявленную ошибку. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ "На закуску" рекомендуем выполнить следующий простой тест.Задача состо- ит в том, чтобы проверить программу,которая по трем заданным числам печа- тает сообщение о том, является ли треугольник со сторонами, длины которых равны данным значениям, неравносторонним, равнобедренным или равносторон- ним [51]. Напишите на листе бумаги набор тестов, которые, как Вам кажется, будут адекватно проверять эту программу. Построив свои тесты,проанализируйте их. Приступайте к работе... Следующий шаг состоит в оценке эффективности Вашей проверки. Оказывает- ся, что программу труднее написать, чем это могло показаться вначале.Были изучены различные версии данной программы,и составлен список общих ошибок. Оцените Ваш набор тестов, попытавшись с его помощью ответить на приведен- ные ниже вопросы. За каждый ответ "да" присуждается одно очко. 1. Составили ли Вы тест, который представляет правильный неравносторон- ний треугольник? (Заметим, что ответ "да" на тесты со значениями 1,2,3, и 2,5,10 не обоснован, т.к. не существует треугольников, имеющих такие сто- роны.) 2. Составили ли Вы тест,который представляет правильный равносторонний треугольник? 3. Составили ли Вы тест,который представляет правильный равнобедренный треугольник? (Тесты со значениями 2,2,4 принимать в расчет не следует.) 4. Составили ли Вы,по крайней мере,три теста, которые представляют пра- вильные равнобедренные треугольники, полученные как перестановки двух рав- ных сторон треугольника (например, 3,3,4; 3,4,3, и 4,3,3)? 5. Составили ли Вы тест, в котором длина одной из сторон треугольника принимает нулевое значение? 6. Составили ли Вы тест, в котором длина одной из сторон треугольника принимает отрицательное значение? 7. Составили ли Вы тест, включающий три положительных целых числа, сум- ма двух из которых равна третьему? (Другими словами,если программа выдала сообщение о том, что числа 1,2,3 представляют собой стороны неравносторон- него треугольника, то такая программа содержит ошибку.) 8. Составили ли Вы, по крайней мере, три теста с заданными значениями всех трех перестановок, в которых длина одной стороны равна сумме длин двух других сторон (например, 1,2,3; 1,3,2 и 3,1,2)? 9. Составили ли Вы тест из трех целых положительных чисел, таких, что сумма двух из них меньше третьего числа (т.е. 1,2,4 или 12,15,30)? 10. Составили ли Вы, по крайней мере, три теста из категории 9, в кото- рых Вами испытаны все три перестановки (например: 1,2,4; 1,4,2 и 4,1,2)? 11. Составили ли Вы тест, в котором все стороны треугольника имеют дли- ну, равную нулю (т.е. 0,0,0)? 12. Составили ли Вы, по крайней мере, один тест,содержащий нецелые зна- чения? 13. Составили ли Вы хотя бы один тест, содержащий неправильное число значений (например, два, а не три целых числа)? Конечно, нет гарантий, что с помощью набора тестов, который удовлетво- ряет вышеперечисленным условиям, будут найдены все возможные ошибки.Но по- скольку вопросы 1╤13 представляют ошибки, имевшие место в различных верси- ях данной программы, адекватный тест для нее должен их обнаруживать. Отметим, что опытные профессиональные программисты набирают в среднем только 7╤8 очков из 14 возможных. Выполненное упражнение показывает, что тестирование даже тривиальных программ, подобных приведенной, - не простая задача. VIII.2.3.2. Т а к т и к а т е с т и р о в а н и я Перевести программу из хорошего состояния в отличное неизмеримо труднее, чем из плохого в удовлетворительное. Программистский фольклор Поговорим о методах тестирования. При н е у п о р я д о ч е н н о м тестировании ("smoke test" - "гру- бая проверка работоспособности простым запуском", "дымовой тест") исход- ные данные, имитирующие внешнюю среду, случайным образом генерируются во всем диапазоне возможного изменения параметров. При этом многие значения исходных данных характеризуются малой вероятностью обнаружения ошибок и не оправдывают затраты на выполнение тестирования. Кроме того,возможно по- явление логически противоречивых данных. В то же время данные, наиболее важные с позиции реального использования программ и возможностей обнаруже- ния ошибок, могут оказаться неохваченными в процессе тестирования. Поэтому на практике последовательно применяют следующие методы тестиро- вания: с т а т и ч е с к и й, д е т е р м и н и р о в а н н ы й и с т о- х а с т и ч е с к и й. С т а т и ч е с к о е тестирование ("static check") является наиболее формализованным методом проверки корректности программ. Тестирование про- водится без исполнения программы путем формального анализа текста програм- мы на языке программирования. Операторы и операнды текста программ при этом анализируются в символьном виде, поэтому такой метод называют также с и м в о л и ч е с к и м тестированием. Наиболее трудоемкими и детализирующими являются методы д е т е р - м и н и р о в а н н о г о тестирования. При детерминированном тестирова- нии контролируется каждая комбинация исходных эталонных данных и соответ- ствующая ей комбинация эталонных результатов. Разумеется, в сложных про- граммах невозможно перебрать все комбинации исходных данных и проконтро- лировать результаты функционирования программы на каждой из них. В таких случаях применяется с т о х а с т и ч е с к о е тестирование, при кото- ром исходные тестовые данные задаются множеством случайных величин с со- ответствующими распределениями и для сравнения полученных результатов ис- пользуются также распределения случайных величин. В результате при стохас- тическом тестировании возможно более широкое варьирование исходных данных, хотя отдельные ошибки могут быть не обнаружены, если они мало искажают средние статистические значения или распределения. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Стохастическое тестирование применяется в основном для обнаружения V V ошибок, а для диагностики и локализации ошибок приходится пере- V V ходить к детерминированному тестированию с использованием конкрет- V V ных значений параметров из области изменения использовавшихся слу- V V чайных величин. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Рассмотрим некоторые п р а в и л а тестирования, в которых делается попытка учесть как желательность доказательства правильности контролируе- мой программы, так и ограниченность человеческих возможностей при проведе- нии такого доказательства [49]. П р о х о д у ч а с т к о в. Каждый линейный участок программы должен быть обязательно пройден при выполнении, по крайней мере, одного теста. Очевидно, что в противном случае никакой гарантии в правильности работы всей программы дать будет нельзя. В том случае, когда выполнение некоторого участка программы меняет по- рядок выполнения или характер работы других участков, может потребоваться перебор всех ветвей программы,т.е. проход по всем возможным путям выполне- ния программы (многократная проверка требуется, в частности, для участков, содержащих переменные с индексами). Т о ч н о с т ь п р о в е р к и. Контроль арифметических блоков (как и других блоков) производится путем сверки результатов, полученных при вы- полнении блока, с эталонными результатами. Для арифметических результатов дополнительная сложность заключается в определении точности, с которой не- обходимо сверять (и, тем самым, вычислять) эталонные и тестовые результа- ты, с тем, чтобы можно было действительно удостовериться в правильности работы блока. Дело в том, что величины, входящие в проверяемое арифметическое выраже- ние в зависимости от соотношения их значений и характера производимых над ними операций, вносят различный вклад в результат. Поэтому может ока- заться, что неправильно запрограммированное выражение для некоторых тесто- вых значений величин, входящих в него, будет иметь я к о б ы правильное значение ввиду того, что результат неправильной операции или неверно вы- численный ранее операнд выражения не окажут почти никакого влияния на тес- товое (сравниваемое) значение выражения. Например, для оператора C=A+B из того, что значение C совпало с эталон- ным значением, не следует, что выражение записано в программе верно, по- скольку для случая, когда A>>B, замена плюса на минус не будет обнаружена, если эталонное значение C вычислено с недостаточной точностью. Кроме того, если вычисление А и B не было проверено ранее, то из правильности C нель- зя сделать вывод о правильности вычисления B (для случая A>>B). Таким образом, для того, чтобы быть уверенным в том, что правильный числовой результат, полученный на компьютере, говорит о правильности про- граммы, необходимо следить за промежуточными результатами вычислений, ко- торые не должны выходить за определенный диапазон, устанавливаемый в зави- симости от точности вычислений эталонных результатов. Выполнение такого требования может привести к необходимости многократной проверки выражения для различных диапазонов данных. М и н и м а л ь н о с т ь в ы ч и с л е н и й. Когда продолжительность работы контролируемой программы и, тем самым, количество вычислений и необходимых для контроля тестовых данных зависит от каких-либо параметров, то при контроле их следует выбирать такими, что- бы они минимизировали количество вычислений. К таким параметрам, например, могут относиться : ═) шаг или отрезок интегрирования; ║) порядок матрицы или количество элементов вектора; ╖) длина символьных строк; ╔) точность для итерационных вычислений и т.п. . Конечно, такая инициализация не должна значительно снижать надежность контроля. Следует заметить также, что значения исходных данных нужно вы- бирать такими, чтобы изготовление эталонных результатов вручную было, по возможности, облегчено. Например, данные могут быть сначала взяты целочис- ленными или такими, чтобы при проверке выражений некоторые их слагаемые, уже проверенные ранее, обращались в нуль. Д о с т о в е р н о с т ь э т а л о н о в. Нужно обратить внимание и на достоверность процесса получения эталонных результатов. По возможности они должны вычисляться не самим программистом, а кем-то другим, с тем что- бы одни и те же ошибки в понимании задания не проникли и в программу, и в эталонные результаты. Если тесты готовит сам программист, то эталоны нуж- но вычислять до получения на компьютере соответствующих результатов.В про- тивном случае имеется опасность невольной подгонки вычисляемых значений под желаемые, полученные ранее на компьютере. В качестве эталонных резуль- татов часто используют и данные,полученные при ручной прокрутке программы. П л а н и р о в а н и е. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V При отсутствии планового подхода тестирование обычно сводится V V к тому, что программист берет какие-то, можно сказать, первые V V попавшиеся исходные данные и пропускает программу многократно, V V исправляя ее при обнаружении ошибок и добиваясь того,чтобы по- V V лучаемые результаты походили на желаемые. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Ясно, что при этом контролируется только некоторая часть блоков и опе- раторов, а остальные выполнятся в первый раз уже во время счета, и будут ли при этом найдены ошибки, имеющиеся в них, зависит только от случая. При п л а н о в о м подходе программа проверяется последовательно блок за блоком, причем если программа состоит из центрального блока, кото- рый проводит обращения к периферийным блокам, мало связанным друг с дру- гом, то возможны следующие два основных подхода к контролю такой програм- мы, два основных направления тестирования: от периферии к центру (в о с- х о д я щ е е тестирование) или, наоборот, от центра к периферии (н и с- х о д я щ е е тестирование). При первом, в о с х о д я щ е м способе,применяемом обычно для неболь- ших программ, сначала тестируют отдельные периферийные блоки, а затем пе- реходят к тестированию центральной части, которая, разумеется, взаимодей- ствует только с отлаженными уже блоками. При н и с х о д я щ е м тестировании, используемом для достаточно больших программ, параллельно с контролем периферийных блоков (или даже до начала их контроля) производится и контроль центрального блока, выпол- няемого на компьютере совместно с имитаторами периферийных блоков, называ- емых "з а г л у ш к а м и". В задачу имитаторов входит моделирование рабо- ты соответствующих блоков с целью поддержать функционирование централь- ного блока. Обычно "заглушки" выдают простейший результат, например конс- танту и сообщение о факте своего участия в работе. Вместо постоянной вели- чины на наболее поздней стадии тестирования может выдаваться и случайная величина в требуемом диапазоне. Например, для начального контроля программы, включающей в качестве од- ного из своих блоков вычисление определенного интеграла,"заглушка" такого блока может возвращать константу. В свою очередь, блок интегрирования сам имеет периферийный блок вычисления значений подинтегральной функции, в ка- честве "заглушки" которого поначалу также может быть взята константа или простейшая функциональная зависимость. К сожалению, часто неверно понимают функции, выполняемые "заглушками". Так, порой можно услышать, что "заглушка" должна выполнять лишь запись со- общения, устанавливающего: "Модуль подключился".В большинстве случаев эти утверждения ошибочны. Когда модуль A вызывает модуль B, A предполагает, что B выполняет некую работу, т.е.модуль A получает результаты работы модуля B. Когда же модуль B просто возвращает управление или выдает неко- торое сообщение без передачи в A определенных осмысленных результатов, мо- дуль A работает неверно не вследствие ошибок в самом модуле, а из-за несо- ответствия ему модуля-"заглушки". Более того, результат может оказаться неудовлетворительным, если "ответ" модуля-"заглушки" не меняется в зависи- мости от условий теста. Если "заглушка" всегда возвращает один и тот же фиксированный результат вместо конкретного значения, предполагаемого вызы- вающим модулем именно в этом вызове, то вызывающий модуль сработает как ошибочный (например, зациклится) или выдаст неверное выходное значение. Следовательно, XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V создание модулей-"заглушек" - задача нетривиальная V. ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Практически, оба этих способа редко используются в чистом виде, отдель- но один от другого. Обычно ко времени, когда приступают к контролю цен- трального блока, какие-то простейшие периферийные блоки уже отлажены авто- номно, и нет необходимости моделировать их работу и разрабатывать "за- глушки". Преимуществом ранней отладки центрального блока при нисходящем тести- ровании является то,что программист быстро получает возможность проверить периферийные блоки в условиях, которые в необходимой степени приближены к реальным. Действительно, центральный блок, снабженный хотя бы и простей- шими функциональными возможностями, можно рассматривать как реальную сре- ду, в которую "погружаются" отлаживаемые блоки, добавляемые к центральной части. Добавление отлаживаемых блоков удобно производить по одному для бы- стрейшего поиска ошибок, возникающих при стыковке с центральным блоком. Подключение каждого нового блока к центральной части позволяет постепенно усложнять испытания, которым подвергается тестируемая программа. Строгой, корректной процедуры подключения очередного последовательно тестируемого модуля не существует. Единственное правило, которым следует руководствоваться при выборе очередного модуля, состоит в том, что им дол- жен быть один из модулей, вызываемых модулем, предварительно прошедшим те- стирование. Запомните, что даже если изменения вносятся только в одну подпрограмму, то повторному тестированию подлежит вся система. Этот процесс называется т е с т и р о в а н и е м с в о з в р а т о м . Проверять работу только измененной подпрограммы недостаточно! Недостаточно полное тестирование та- кого рода повышает вероятность неудач. Проведем сравнение нисходящего и восходящего тестирования [50]. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V П р е и м у щ е с т в а V Н е д о с т а т к и V TWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V Н и с х о д я щ е е т е с т и р о в а н и е V TWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V 1. Имеет преимущества, если V 1. Необходимо разрабатывать V V ошибки,главным образом,в верхней V модули-"заглушки", которые часто V V части программы V оказываются сложнее, чем кажется V V 2. Раннее формирование струк-V вначале V V туры программы позволяет провес-V 2. Может оказаться трудным V V ти ее демонстрацию пользователю V или невозможным создать тестовые V V и служит моральным стимулом V условия V V V 3. Сложнее оценка результатов V V OOOOOOOOOOOOOOOOOOOO V тестирования V V OOOOOOOOOOOOOOOOOOOO V 4. Стимулируется незавершение V V V тестирования некоторых модулей V TWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V В о с х о д я щ е е т е с т и р о в а н и е V TWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V 1. Имеет преимущества, если V 1. Программа как единое целое V V ошибки,главным образом,в модуле V не существует до тех пор,пока не V V нижнего уровня V добавлен последний модуль V V 2. Легче создавать тестовые V V V примеры V OOOOOOOOOOOOOOOOOOOO V V 3. Проще оценка результатов V OOOOOOOOOOOOOOOOOOOO V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Значительное повышение корректности и надежности программ достигается применением д в о й н о г о или N - к р а т н о г о программирования ("duplication check"-"двойной просчет", "двойная проверка"). При этом методе при разных алгоритмах и на разных языках программирова- ния создается несколько вариантов программы. Эти варианты реализуют одни и те же функции и при определенных тестовых данных должны выдавать тож- дественные результаты. Различие результатов при тестировании указывает на наличие ошибок по крайней мере в одном из вариантов. Обычно при разработ- ке вариантов программы используется один и тот же алгоритм, но программы создаются на разных языках, разных компьютерах и разными программистами. На практике применяется программирование с NЁ2. Практически очень редки случаи, когда реальная программа создавалась в трех и более вариантах.  В заключение заметим, что если исполнение теста приносит результаты, не соответствующие предполагаемым, то это означает, что ═) либо модуль имеет о ш и б к у; ║) либо н е в е р н ы предполагаемые результаты (ошибки в тесте). Для устранения такого рода недоразумений нужно тщательно проверять на- бор тестов ("тестировать" тесты). VIII.2.3.3. Т и п ы т е с т о в О несчастном случае мой подзащитный впервые узнал лишь тогда,когда этот случай произошел. Из речи адвоката О с н о в н ы м т е с т о м мы будем называть тест, проверяющий ос- новные функциональные возможности программы. Однако существует опасность, что после успешного окончания основного тестирования "на радостях" обыч- но забывают о необходимости дальнейшего, более тщательного контроля про- граммы и отдельных ее участков, да и настроиться на такой контроль стано- вится уже психологически трудно.Поэтому помимо основного теста необходимо применить следуюшие типы тестов. В ы р о ж д е н н ы й тест. Этот тест затрагивает работу отлаживае- мой программы в самой минимальной степени. Обычно тест служит для провер- ки правильности выполнения самых внешних функций программы, например обра- щения к ней и выхода из нее. Т е с т г р а н и ч н ы х з н а ч е н и й, или "с т р е с с о в ы й тест"("high-low bias checking","twin check"). Тест проверяет работу про- граммы для граничных значений параметров, определяющих вычислительный про- цесс. Часто для граничных значений параметра работа программы носит осо- бый характер, который тем самым требует и особого контроля. Если в качестве примера рассмотреть тестирование подпрограммы сортиров- ки, то нужно исследовать следующие ситуации: ═) сортируемый массив пуст; ║) сортируемый массив содержит только один элемент; ╖) все элементы в сортируемом массиве одинаковы; ╚) массив уже отсортирован. Л.Питер приводит следующий поучительный пример. Компьютер одной компа- нии по страхованию автомобилей выслал проживающему в Сент-Луисе клиенту счет на сумму 0.00 долларов. Когда же компьютер направил ему "последнее уведомление" с угрозой расторгнуть договор, этот челевек обратился за по- мощью к своему финансовому агенту. Тот пришел к выводу, что лучший способ уладить дело - отправить компьютеру чек на 0.00 долларов. Это было сде- лано, и в ответ пришло подтверждение с благодарностью и заверением,что до- говор остается в силе! А в а р и й н ы й т е с т. Тест проверяет реакцию программы на возник- новение разного рода аварийных ситуаций в программе, в частности вызван- ных неправильными исходными данными, то есть проверяется диагностика, вы- даваемая программой, а также окончание ее работы или, может быть, попытка исправления неверных исходных данных (разработчики реальных программ зна- ют, что пользователи подобны шаловливому ребенку, играющему в отсутствие старших с телевизором или магнитофоном). Поэтому в реальных программах, спроектированных с достаточной надежнос- тью,совокупности приказов, которые должны работать только в особых аварий- ных ситуациях, занимают порой более 90% общего объема программы. Эти сово- купности приказов называют иногда блоками "з а щ и т ы от д у р а к а" ("fool proof"). Такие системы, обладая достаточной надежностью,устойчиво функционируют даже при самых неподходящих действиях работающих с ними лю- дей. В журнале "Компьютерный мир" рассказывалось об одной довольно дорого обошедшейся ошибке. При вводе данных палец оператора случайно задел не ту клавишу, и автомобиль "Форд", принадлежащий одному из граждан,стал стоить не 950, а 7000950 долларов! А если обладаешь таким дорогим имуществом, нужно платить большой налог. Налог составил 290000 долларов. Когда ошибка обнаружилась, эта сумма была уже включена в бюджет города. Владелец авто- мобиля получил счет на 290 тыс.долларов,но платить не стал. Причиной ошиб- ки следует считать, конечно, не неверно нажатую клавишу, а плохую програм- му, автор которой не позаботился о достаточно мощных процедурах контроля входных данных. Процветающие фирмы, занятые разработкой программного обеспечения, спе- циально нанимают профессионально неподготовленных людей, чтобы они порабо- тали с вновь созданными программами. В их задачу входит за короткое время сделать столько неправильных обращений к программе, сколько пользователь не сделает и за долгий период. Например, когда программа запрашивает цену товара,оператор набирает на клавиатуре слово "Почему?" вместо числа и т.д. Одним из свойств хорошей программы является, как говорят специалисты, ее д р у ж е с т в е н н о с т ь. Это означает, что в случае ошибки поль- зователя программа выдаст на экран сообщение, направленное на оказание по- мощи в выполнении поставленной задачи. Это может быть подсказка, наводя- щий вопрос, разъяснение противоречивости или иной ошибки в требованиях пользователя. В лучших образцах таких программ вместо сообщения "треугольника с таки- ми сторонами не бывает" на экране выдаются тексты вида: "Вероятно, Вы оши- блись. На плоскости невозможно построить треугольник со сторонами, имеющи- ми длины 1, 1, 100. Попытайтесь изменить значения длин сторон." Существуют программы, которые не только обнаруживают, но и исправляют ошибки. Например, при проектировании какого-то прибора инженер за диспле- ем подбирает параметры его деталей и вводит приказ запомнить величину со- противления 150 кОм. Тогда компьютер может ответить: "Вероятно, Вы ошиб- лись. К сожалению, известны только данные о выпускаемых сопротивлениях с номиналами 160 и 180 кОм. Попытайтесь изменить значение номинала сопротив- ления. Если Вам подходит значение 180 кОм, нажмите клавишу "ВВОД"". Про- грамма лишь предложила один из возможных вариантов взамен явно неосущест- вимого. Окончательное решение осталось за пользователем. Однако дружественность программ должна иметь четкие границы, иначе ав- томатическое исправление ошибок превратится в медвежью услугу пользовате- лю. Одно из свойств хороших программ состоит в том, что пользователь не должен при работе с ними удивляться, они не должны делать ничего неожи- данного, т.к. эти неожиданности редко бывают приятными и полезными. В связи с этим интересны рекомендации по проектированию программ веде- ния диалога [60], где автор вообще выступает против какого-либо очелове- чивания вычислительных систем: "Создание ЭВМ, ведущих себя как люди, напоминает попытки строить само- леты, машущие крыльями. ...Создавать вычислительные системы,которые будут вести себя как инструменты...это означает избегать диалоговых систем,начи- нающихся с реплик "Привет, я Бетси 307, назови свое имя". Не давайте человеческих имен или признаков программам и системам. Не приписывайте вычислительным системам свободы воли или поведения, на- поминающего живое существо. Если что-то происходит неправильно,не обвиняйте в этом ЭВМ или програм- му. Помните, что инструменты не делают ошибок, они или отказывают или ло- маются". VIII.3. М е т о д ы л о к а л и з а ц и и о ш и б о к Если Вы думаете, что разработка и кодирова- ние программы - вещь трудная, то Вы еще ни- чего не видели. Популярный афоризм После того как установлено, что в программе или в конкретном ее блоке имеется ошибка, возникает задача ее л о к а л и з а ц и и, то есть уста- новления точного места в программе, где она находится. Можно считать, что п р о ц е с с л о к а л и з а ц и и о ш и б о к состоит из трех основ- ных компонентов [49]: 1) получение вручную или с помощью компьютера тестовых результатов; 2) анализ тестовых результатов и сверка их с эталонными; 3) выявление ошибки или формулировка предположения о характере и месте ошибки в программе. Если ошибка найдена, то производится ее исправление;в противном случае осуществляется переход к пункту 1, т.е. к получению дополнительных тесто- вых результатов. В ходе поиска ошибок программист, анализируя полученные на компьютере результаты, проверяет различные предположения о характере и месте ошибки в программе, которые при этом приходят ему в голову. В случае несоответст- вия этих гипотез выданным результатам, программист выдвигает новые гипоте- зы и проверяет их или в уме, или проводя вычисления за столом, или обраща- ясь за новыми результатами к компьютеру. В таком характере работы программиста можно найти нечто общее с рас- четом вариантов, который осуществляет шахматист (или шашист) во время иг- ры, когда он путем расчетов в уме ищет выигрывающий ход в позиции на шах- матной доске, подвергая проверке один из заслуживающих внимания ходов за другим. Не найдя выигрывающего хода, шахматист делает какой-то,по его мне- нию, хороший ход, приближающий его к цели.Так и программист,не найдя ошиб- ки путем исследования полученных тестовых результатов, делает новое пред- положение о месте или о характере ошибки, вставляет новую отладочную пе- чать или изменяет программу ("ход программиста"), а ЭВМ выдает новые тес- товые результаты ("ход ЭВМ"). Компьютер выступает как своеобразный парт- нер, задача которого заключается в том, чтобы вскрыть ошибки в рассуждени- ях программиста, как бы сформулированных им в тексте отлаживаемой прог- раммы. Продолжая аналогию, можно сказать, что подобно тому, как нельзя ре- ально надеяться выиграть партию в два-три хода, так же нельзя найти все ошибки в реальной программе за одно-два обращения к компьютеру. Программистов, успешно проводящих поиск ошибок в программе,можно услов- но разделить на "аналитиков" и "экспериментаторов" [49]. А н а л и т и к и отлаживают программу, редко используя компьютер и применяя простейшие способы получения тестовых результатов на компьютере путем тщательного изучения этих результатов и на основании глубокого и четкого представления о структуре и особенностях алгоритма отлаживаемой программы. Э к с п е р и м е н т а т о р ы ищут ошибки, изощренно используя все- возможные о т л а д о ч н ы е с р е д с т в а, быстро получая необходи- мые для все большей и большей локализации ошибок промежуточные результа- ты и легко ориентируясь в них. Конечно, идеальным является случай, когда программист сочетает в себе способность к глубокому расчету в уме различных вариантов работы програм- мы и навыки работы с разнообразными отладочными средствами. Если успех аналитического подхода к поиску ошибок зависит, видимо, от способностей и опыта программиста, то изучение и использование средств,по- могающих локализации ошибок - главным образом средств получения необходи- мых промежуточных результатов, - доступно каждому программисту. Имеются такие средства и в языке программирования MSX-BASIC! Под п р о м е ж у т о ч н ы м и результатами выполняемой программы договоримся понимать как арифметические результаты, характеризующие значе- ния используемых величин, так и л о г и ч е с к и е "результаты",т.е. ин- формацию, содержащую сведения о факте или последовательности выполнения операторов программы. Учтите следующие принципы Г.Майерса [51]. 1. Д у м а й т е ! Наиболее эффективный метод отладки заключается в глубоком анализе ин- формации об ошибках. Для ее эффективного проведения XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V специалист должен обладать способностью точно определять V V большинство ошибок без использования компьютера. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ 2. Используйте средства отладки только как вспомогательные.Не применяй- те эти средства вместо того, чтобы обдумывать задачу. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Ясно, что такие средства как трассировка (раздел VIII.3.1) и ава- V V рийная печать (раздел VIII.3.2) отражают случайный подход к отладке. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Эксперименты показали, что программисты, избегающие применения средств отладки, даже при отлаживании незнакомых им программ выполняют ее лучше, чем те, кто пользуется этими средствами. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 3. V Избегайте экспериментирования! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Пользуйтесь им только как последним средством. Наиболее общей ошибкой, которую допускают начинающие программисты,занимающиеся отладкой, является попытка решить задачу посредством внесения в программу экспериментальных изменений ("Я не знаю, что неправильно, но я изменю этот оператор IF и по- смотрю, что получится"). Этот абсолютно неверный подход не может даже рас- сматриваться как отладка; он основан на случайности. Экспериментирование не только уменьшает вероятность успеха, но часто и усложняет задачу, пос- кольку при этом в программу вносятся новые ошибки. 4. Если Вы зашли в тупик, отложите рассмотрение программы. Наше подсо- знание является мощным механизмом решения проблем. То, что мы часто припи- сываем вдохновению, оказывается всего лишь выполненной подсознанием рабо- той по решению задачи, тогда как наша сознательная деятельность в это вре- мя связана с чем-нибудь другим, например с едой, прогулкой или просмотром кинофильма. Если Вы не можете локализовать ошибку в приемлемые сроки(пред- положительно за 30 минут для небольших программ и за несколько часов для больших), прекратите поиски и займитесь каким-нибудь другим делом,так как эффективность Вашей работы значительно снизится. Проблему следует "забыть" до тех пор, пока Вы либо подсознательно не найдете ее решения, либо отдох- нете и будете готовы вновь рассмотреть симптомы ошибки. И наконец, XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V если Вы окончательно зашли в тупик, то изложите задачу кому-нибудь еще.V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Сделав это, Вы, вероятно, обнаружите что-то новое. Часто случается так, что просто пересказав задачу хорошему слушателю,Вы вдруг найдете решение без какой-либо помощи с его стороны. Далее мы рассмотрим некоторые, ставшие уже классическими, способы по- лучения программистом промежуточных результатов, вырабатываемых отлаживае- мой программой.  VIII.3.1. Т р а с с и р о в к а Всякий необходимо причиняет пользу, упот- ребленный на своем месте. Напротив того: упражнения лучшего танцмейстера в химии неуместны; советы опытного астронома в танцах глупы. Козьма Прутков Т р а с с и р о в к а ("tracе" - "след") представляет собой пошаговое выполнение программы в автоматическом режиме. Если Вы создали сложную по логике программу, и она выполняется неправильно, а по информации, выводи- мой на экран, нельзя установить, где ошибка, то оператор TRON , где TRON ("TRacing ON" - "установить трассировку") - служебное слово, поможет Вам произвести трассировку программы во время ее выполнения. Выполнение оператора TRON приводит к последовательному выводу номеров в с е х выполняемых в данный момент строк программы в виде [n1][n2]...[nk] Цепочка этих номеров составляет с л е д (или т р а с с у) работы про- граммы. Между компонентами [GGG] располагаются значения, выводимые по опе- ратору PRINT или вводимые с клавиатуры по оператору INPUT. Так как номер к а ж д о й выполненной строки будет выведен на экран, то Вы легко увидите, по каким"ветвям" выполняется Ваша программа, поэтому анализ полученной трассы, как правило, позволяет локализовать ошибку. Отменяется режим трассировки оператором TROFF , где TROFF("TRacing OFF" - "отменить трассировку") - служебное слово. Итак, если Вам непонятна работа какого-то участка программы, то в нача- ле участка надо поставить оператор TRON, а в конце - оператор TROFF. Например: 249 TRON 250 'Начало проверяемого участка GGG 300 'Конец проверяемого участка 310 TROFF П р и м е р 1. 10 TRON:GOSUB 100:END WWWWWWWWWWWWW 100 IF K<1 THEN K=K+1:PRINT K:GOSUB 100 110 RETURN run run [100] 1 [10][100] 1 [100][110][110] [100][110][110] Ok WWWW▌WWWWW Ok WWWWW▌WWWW V V П о д у м а й т е, п о ч е м у ?! П р и м е р 2. 10 TRON:INPUT A,B:PRINT A;B WWWWWWWWWWWWW 20 IF A*B<99 THEN 100 ELSE TROFF 100 PRINT A+B;A-B Ok run run ? 5,6 [10]? 10,10 5 6 10 10 [20][100] 11 -1 [20] 20 0 Ok Ok Отметим, что команда RUN в ее простейшем виде не отменяет режима TRON, так что использование оператора TRON ведет к выполнению трассировки всех последующих программ до тех пор, пока не встретится оператор TROFF. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Единственное, что может отменить трассировку,- это выполнение V V оператора TROFF или нажатие кнопки "RESET"! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Операторы TRON и TROFF можно использовать и в режиме прямого выполне- ния команд. П р и м е р 3. Ok WWWWWWWWWWWWW TRON Ok 20 GOSUB 100:END 100 IF K<1 THEN K=K+1:PRINT K ELSE GOSUB 100 110 RETURN run [20][100] 1 [110] Ok Этими операторами нужно пользоваться избирательно и крайне осторожно - иначе Вы "утонете" в протоколах трассировки.Поэтому стремитесь ограничить область действия операторов TRON и TROFF в тексте Вашей программы! VIII.3.2. А в а р и й н а я п е ч а т ь Щелкни кобылу в нос - она махнет хвостом. Козьма Прутков Под а в а р и й н о й п е ч а т ь ю ("dump" - "дамп" , "разгрузка памяти","выдача") понимается печать значений переменных в программе в тот момент ее выполнения, когда в ней возникает ошибка,препятствующая дальней- шему нормальному ее выполнению; обычно после осуществления такой печати выполнение программы прекращается. Благодаря аварийной выдаче программист получает доступ к тем значениям переменных, которые они имели в момент возникновения аварийной ситуации. Изучение и сопоставление таких значений обычно позволяет программисту достаточно точно локализовать ошибку в про- грамме, а иногда и не одну. П р и м е р. 10 PRINT 1/SIN(X) WWWWWWWWWWW run Division by zero in 10 Ok ??? ░WW Р а з д у м ь е ... print X ░WW А в а р и й н а я печать 0 ░WW Вот теперь все ясно! Ok - Вам известны мои методы. Примените их! А.Конан Дойль. Собака Баскервилей Эффективным методом локализации ошибки для небольших программ является прослеживание в обратном порядке логики выполнения программы с целью обна- ружения точки, в которой нарушена логика [51]. Другими словами, отладка начинается в точке программы, где был обнаружен (например, напечатан) не- корректный результат. Для этой точки на основании полученного результата следует установить (логически вывести), какими должны быть значения пере- менных. Мысленно выполняя из данной точки программу в обратном порядке и опять рассуждая примерно так: "Если в этой точке состояние программы (т.е.значе- ния переменных) было таким, то в другой точке должно быть следующее состо- яние", можно достаточно быстро и точно локализовать ошибку (т.е. опреде- лить место в программе между точкой, где состояние программы соответство- вало ожидаемому, и первой точкой,в которой состояние программы отличалось от ожидаемого). VIII.3.3. Л о к а л и з а ц и я с т о ч к а м и о с т а н о в а Разделяй и властвуй! Людовик XI Трассировка хороша только для коротких программ; более универсальным способом является п е ч а т ь в у з л а х ("snapshot" - "моментальный снимок") или л о к а л и з а ц и я с т о ч к а м и о с т а н о в а . Т о ч к а о с т а н о в а - это точка в программе, на которой Вы мо- жете временно остановить выполнение программы,просмотреть значения интере- сующих Вас переменных и затем продолжить выполнение. Это осуществляется путем включения в программу в точке останова опера- тора, имеющего простейший синтаксис STOP . При выполнении этого оператора вычисления приостанавливаются и пользо- ватель в режиме непосредственного счета может вывести значения интересую- щих его переменных. Анализ их позволяет делать выводы о правильности вычи- слительного процесса, а следовательно, и принимать те или иные решения. Оператор STOP выводит сообщение "Break in OOOO" ("О с т а н о в к а в с т р о к е с н о м е р о м OOOO"), где OOOO - номер строки, содержащей оператор STOP. Если Вы будете внимательны, то услышите и предупредительный звонок,как и при выполнении оператора BEEP. После останова по оператору STOP вычисления могут быть возобновлены ко- мандой CONT , которая должна быть выполнена в режиме непосредственного счета. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Однако ни в коем случае не изменяйте, не добавляйте и V V не исключайте в этот момент строки Вашей программы! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ П р и м е р. Программа, осуществляющая вычеркивание R-й буквы из сло- WWWWWWWWWWW ва P$ и запись полученного после вычеркивания слова в слово Q$. 100 INPUT P$,R:Q$="":FOR K=1 TO LEN(P$) 130 IF K<>R THEN Q$=Q$+MID$(P$,K,1):STOP:NEXT K:PRINT Q$ run ? корова,2 XW▐ cont XW▐ cont XW▐ cont XW▐... Break in 130 V Break in 130 V Break in 130 V Break in 130 V Ok V Ok V Ok V Ok V print Q$ V print Q$ V print Q$ V print Q$ V к V кр V кро V кров V Ok WWWWWW[ Ok WWWWWWW[ Ok WWWWWW[ Ok WWWWWW[ Кстати, нажатие клавиш "CTRL"+"STOP"(если, конечно, Вы не запретите подобное прерывание!) выполняет те же действия, что и ввод оператора STOP в Вашу программу, только, разумеется, программную строку, в которой прои- зошло прерывание, Вам будет трудно угадать! Заметим, что во время прерывания Вы можете изменять значения перемен- ных. Однако учтите, что если "что-либо" (например, оператор CLEAR) дела- ет продолжение программы, прерванной по оператору STOP, бессмысленным, то любая попытка выполнить команду CONT приводит к сообщению "? Can't CONTINUE" ("Н е л ь з я п р о д о л ж и т ь") . Для продолжения выполнения программы в подобных случаях используйте оператор GOTO. Кстати,аналогичное сообщение возникает и при остроумной попытке продол- жения "листания" текста программы (после нажатия клавиш LIST и CTRL+STOP) путем выполнения команды CONT! Итак, нами изучены средства языка MSX-BASIC, применяемые при локализа- ции с точками останова. Перейдем теперь к рассмотрению а л г о р и т м а этого процесса. Предположим, что Вы знаете как выполняется Ваша программа, т.е. какова последовательность выполняемых операторов в любой момент времени. 1. Выберите оператор, до которого, как Вы полагаете, программа будет выполняться правильно; установите в этом месте точку останова; выполните программу вплоть до этой точки. 2. Просмотрите значения интересующих Вас переменных, чтобы убедиться в том, что программа работает правильно. Если это не так, перейдите к шагу 4; в противном случае удалите только что установленную Вами точку остано- ва и установите новую точку останова, опять выбрав оператор, до которого, как Вам кажется, программа будет выполняться правильно. 3. Запустите программу с только что удаленной Вами точки останова. Ког- да программа дойдет до следующей точки останова, вернитесь к шагу 2. 4. Теперь Вы приблизительно знаете, где находится ошибка. Если Вы не знаете, как выполняется Ваша программа, например, если в ре- зультате ошибок в программе выполняется не тот оператор, либо происходит переход не на ту точку, можно воспользоваться описанной выше процедурой отладки с некоторыми изменениями. На шаге 1 выберите н е с к о л ь к о возможных точек останова; то же сделайте на шаге 2. Кроме того, на шаге 2 удалите в с е точки останова, до которых, по Вашему убеждению, программа не дойдет, когда Вы ее запусти- те на шаге 3. Далее следите не только за содержимым памяти (значениями переменных), но и за тем, на какую точку останова вышла программа, чтобы убедиться, что это есть запланированная в данном случае точка останова. Итак, локализация с точками останова используется для программ,слишком больших для отладки с помощью пошагового выполнения и трассировки.Для еще более длинных программ используется так называемый принцип р а з р ы в а т е л е ф о н н о й л и н и и , к описанию алгоритма которого мы и при- ступаем. Установите первую точку останова где-то в середине программы.Когда про- грамма выполнится до этого места (если это произойдет), проверьте содержи- мое памяти, чтобы убедиться,что программа до этого места работала правиль- но. Если это так, то Вы будете знать, что Ваша первая ошибка находится во второй половине программы. В противном случае она в первой половине. Так или иначе Вы сократили область поиска ошибки до половины программы. Теперь установите точку останова приблизительно в середине этой половины, снова запустите программу и проверьте содержимое памяти. Таким образом Вы локализуете ошибку на участке в четверть программы. Продолжайте процедуру, деля программу каждый раз приблизительно пополам, пока Вы не сократите об- ласть поиска ошибки до таких размеров, при которых можно воспользоваться обычной методикой отладки с точками останова. Если ошибки в программе приводят к неправильному порядку выполнения программных строк, предложенный метод следут, как и ранее, несколько изме- нить. Может, например, получиться, что Вы установили точку останова в сере- дине некоторого участка программы, но при выполнении программа проходит вообще мимо точки останова. Конечно, это все же локализует ошибку: она в первой половине этого участка. Однако Вы всегда можете установить несколь- ко точек останова, как и при обычной отладке с точками останова. VIII.3.4. П р о г р а м м н а я о б р а б о т к а о ш и б о к Don't worry, computer bugs don't byte. Из программистского фольклора Кроме сбоев в работе компьютера из-за неисправностей каких-либо его уз- лов имеется достаточно много причин, по которым происходит прерывание вы- числений по программе и при нормально работающем компьютере.Перечислим не- которые из них: 1) компьютеру "предложено" поделить на нуль; 2) при вычислениях получено число большее, чем допустимо; 3) для элемента массива получено значение индекса, не лежащее в диапа- зоне, указанном оператором DIM; 4) в программе встретилась функция пользователя FN, которая не описана. Все эти и многие подобные им причины, вызывающие прерывания, имеют кон- кретные номера от 1 до 255, называемые к о д а м и ошибок. Если какая-ни- будь "ошибка" происходит, то на экране индицируется соответствующее сооб- щение. Например: "NEXT without FOR in 40" ("NEXT б е з FOR в с т р о к е 40") . После устранения причин, вызвавших прерывание программы, как прави- ло, приходится запускать ее заново. Однако имеется возможность"обработать" ошибку, не прекращая вычислений. Для этих целей используются операторы: XWWWWWWWWWWWWWWWWWWWWWY V ON ERROR GOTO n V V ON ERROR GOTO 0 V , ZWWWWWWWWWWWWWWWWWWWWW[ где: ON("на"), ERROR("ошибка"), GOTO - служебные слова; n - номер программной строки. При выполнении оператора ON ERROR GOTO n происходит назначение переда- чи управления на строку с номером n,но сама передача реализуется л и ш ь в случае возникновения ошибки. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Оператор ON ERROR GOTO 0 отключает обработку ошибок V V пользователем и включает системную обработку ошибок. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Фрагмент программы, начинающийся со строки с номером n и заканчивающий- ся одним из операторов: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V RESUME 0 или RESUME V V RESUME NEXT V V RESUME m V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ называется подпрограммой о б р а б о т к и о ш и б о к. Здесь: RESUME ("продолжаю"), NEXT - служебные слова; m - номер программной строки. После обработки ошибки в зависимости от значения параметра, расположен- ного за служебным словом RESUME, возврат в основную программу осуществля- ется: 1) либо к оператору, вызвавшему ошибку, в случае операторов RESUME 0 или RESUME ; 2) либо к следующему за ним в случае оператора RESUME NEXT , причем этот оператор может находиться в той же строке, в которой была об- наружена ошибка; 3) либо к программной строке с номером m в случае оператора RESUME m . Разумеется, старайтесь не возвращаться "внутрь" цикла, минуя заголовок, и "внутрь" другой подпрограммы! П р и м е р ы: WWWWWWWWWWWWW 1) 10 ON ERROR GOTO 50:INPUT M 2) 10 ON ERROR GOTO 70:INPUT W 30 IF A(M)=0 THEN ?"Ошибки 30 IFA(W)=1: ?"4" THEN:?"5":END нет!":GOSUB40:END ▌ ▌ 40 PRINT"Осуществлен переход на ZWWWWWWZWWW О ш и б к и оператор GOSUB 40!":RETURN 70 RESUME NEXT 50 RESUME NEXT run run ? 30 ? 1 45 Ошибки нет! Ok Осуществлен переход на оператор GOSUB 40! Ok run ? 30 Осуществлен переход на оператор GOSUB 40! Ok Подпрограмма обработки ошибок должна заканчиваться операторами RESUME, END или STOP, в противном случае последует сообщение об ошибке: "No RESUME in OOOO" ("О т с у т с т в и е о п е р а т о р а RESUME в с т р о к е OOOO"). При установке "ловушек" ошибок можно не ограничиваться стандартными сообщениями MSX-BASIC об ошибках. Если в Вашей программе требуется, чтобы вводимое число находилось в ин- тервале между 0 и 1000,нарушение этого требования можно рассматривать как ошибку, которой присваивается собственный код. Конечно, Вы заметили, что в MSX-BASIC задействованы не все номера ко- дов ошибок. Так, коды 26╤49 и 60╤255 находятся в распоряжении программис- та. (В последующих версиях MSX-BASIC эти значения могут изменяться!) Для подпрограммы обработки указанной ошибки ввода возьмем код 255. Со- ответствующие операторы могут иметь такой вид: 10 ON ERROR GOTO 1000 GGG 50 IF X<0 OR X>1000 THEN ERROR 255 GGG 1000 IF ERR=255 THEN PRINT"Число вне диапазона" 1010 RESUME Строка 50 при обнаружении ошибки вызывает автоматический переход к под- программе. Формирование собственных сообщений об ошибках приносит не много пользы. Этот способ несколько неуклюж: он требует наличия двух операторов IF... THEN, тогда как фактически достаточно одного. Главное его преимущество со- стоит в том,что появляется возможность сгруппировать в программе все сооб- щения об ошибках и сделать их единообразными, что облегчает процедуру по- полнения списка обрабатываемых ошибок. MSX-BASIC обеспечивает одновременную обработку т о л ь к о о д н о й ошибки. Если оператор ON ERROR GOTO отсылает какую-либо программу к под- программе обработки ошибок, в которой возникает еще одна ошибка, эта ошиб- ка не будет обработана, но она вызовет появление сообщения об ошибке и прекращение счета. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Отметим, что в слове ONELIN рабочей области по адресу &HF6B9 хранится V Vссылка на адрес первой программной строки подпрограммы обработки ошибок.V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ П р и м е р 3. 10 ON ERROR GOTO 90 WWWWWWWWWWWWW 20 INPUT 30 END 90 A=A+1 100 RESUME Выполним эту программу. А теперь... ? HEX$(PEEK(&HF6BA));HEX$(PEEK(&HF6B9)) 801B ░WW Мы получили адрес PIT, начиная с которого расположе- Ok на первая строка подпрограммы обработки ошибок. А теперь, просмотрeв PIT при помощи "палочки-выручалочки" - оператора PEEK, получим: XWWWWWWWWWWWRWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V А д р е с V Содержимое V К о м м е н т а р и й V VWWWWWWWWWWWUWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V &H801B V &H25 V Указатель на адрес программной V V &H801C V &H80 V строки, следующей за строкой 90 V TWWWWWWWWWWWUWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V &H801D V &H5A=90 V Номер первой программной строки V V &H801E V 0 V подпрограммы обработки ошибок V TWWWWWWWWWWWUWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWS V &H801F V &H41 V Код символа "A" V ZWWWWWWWWWWWQWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ В подпрограммах обработки ошибок обычно используются функции: ═) ERR , где ERR ("ERRor") - служебное слово, которое возвращает код последней ошибки, и ║) ERL , где ERL ("ERror Line"-"ошибочная строка") - служебное слово,которое воз- вращает номер строки, где произошла ошибка. П р и м е р ы: WWWWWWWWWWWWW 4) 10 Z=1/0 5) 10 ON ERROR GOTO 100 run 20 INPUT A:Z=1/A:PRINT Z:END Division by zero in 10 100 ?"Ошибка!":? ERR;ERL:RESUME NEXT Ok run print err;erl ? нажата клавиша "RETURN" WW▐A=0 11 10 Ошибка! Ok 11 20 0 Ok XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Значение функции ERL хранится в слове ERRLIN области V V системных переменных по адресу &HF6B3 V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Например: 1000 PRINT 1/0 run Division by zero in 1000 Ok print peek(&HF6B3)+256*peek(&HF6B4) 1000 Ok Далее, оператором ERROR ═ , где: ERROR - служебное слово; ═ - арифметическое выражение; можно искусственно вызвать (имитировать!) ошибку с кодом, равным INT(═) (разумеется, 0ЁINT(═)Ё255 !). При этом ошибка с заданным кодом возникает в том месте программы, в ко- тором находится оператор ERROR ═. Заметим, что можно легко, зная код ошибки, восстановить "содержание" ошибки с помощью команды(!) ERROR в непосредственном режиме. Например: Ok Ok Ok еrror 73 error 15 error 0 Unprintable error String too long Illegal function call Ok Ok Ok П р и м е р ы: WWWWWWWWWWWWW 6) 10 ON ERROR GOTO 80 7) 10 ON ERROR GOTO 100 20 E=10:Z=0 20 X=0 30 P=E/Z:PRINT "P=";P 30 INPUT"Сколько миль до Луны";FAR 40 Q=LOG(E-11) 40 IF FAR<>238857.0! THEN 50 50 ERROR 250 45 PRINT"Отлично!" 60 PRINT "Завтра":END 48 GOTO 130 70 'Обработка ошибок 50 X=X+1 80 PRINT ERL,ERR 55 IF X>2 THEN ERROR 200 90 IF ERR=11 THEN 130 60 GOTO 30 110 IF ERR=250 THEN 150 100 IF ERR<>200 THEN ON ERROR GOTO 0'Обрабо 120 IF ERR=5 THEN 140 тать ошибку как обычно! 130 Z=.1:RESUME 0 110 PRINT"Уже три попытки. Хватит!" 140 RESUME NEXT 120 RESUME 130 150 RESUME 60 130 END run run 30 11 Сколько миль до Луны? 500000 P= 100 Сколько миль до Луны? 10000 40 5 Сколько миль до Луны? 68999 50 250 Уже три попытки. Хватит! Завтра Ok Ok Отметим, что обработка ошибок пользователем действительна и в непосред- ственном режиме: Вы можете выполнить в непосредственном режиме команду ON ERROR GOTO ,а затем ввести команду,которая вызовет обрабатываемую ошиб- ку, например: 20 PRINT 1:END Ok on error goto 20:max=0 1 ▌ Ok ZWW "Сомнительное" имя! Если Вы не обнаружили причины появления ошибки и номер строки, в кото- рой она произошла, то это может привести к "зацикливанию" Вашей программы, избавиться от которого Вам поможет включение в подпрограмму обработки оши- бок оператора: IF err=X AND erl=Y THEN on error goto 0:resume next ELSE X=err:Y=erl:resume next П р и м е р 8. 10 ON ERROR GOTO 50 WWWWWWWWWWWWW 20 PRINT MAX 30 END 50 PRINT 2:IF ERR=XANDERL=Y THEN ON ERROR GOTO 0:RES UME NEXT ELSE X=ERR:Y=ERL:RESUME NEXT run 2 Ok print x;y 2 20 Ok Список ошибок может быть дополнен ошибками с кодами и названиями, при- думанными пользователем! Для этого зарезервированы ошибки с номерами от 71 до 255 для MSX-DISK BASIC (и с номерами от 60 до 255 для MSX-BASIC). Если Вам потребуется ввести свои коды ошибок, то сделайте это по анало- гии с фрагментом программы: П р и м е р 9. 10 ON ERROR GOTO 150 WWWWWWWWWWWWW 20 INPUT X 30 IF X<0 THEN ERROR 250 40 PRINT SQR(X) 50 END 150 IF ERR=250 THEN PRINT"Аргумент меньше нуля" 160 RESUME NEXT run run ? 10 ? -10 3.1622776601684 Аргумент меньше нуля Ok Ok Если код ошибки, стоящей в операторе ERROR ═, не определен,то будет вы- дано сообщение об ошибке: "Unprintable error", и программа будет прервана.  VIII.3.5. Н е к о т о р ы е п р и ч и н ы, о с л о ж н я ю щ и е п о и с к о ш и б о к [57] Следствие 17: некомпетентность не знает преград ни во времени, ни в пространстве. Л.Питер 1. При отладке достаточно сложных программ, использующих большой набор обрабатываемых данных, иногда трудно вообще установить факт наличия оши- бок в программе, так как программист лишь приблизительно представляет, ка- кой результат должна давать программа. Например, интегрируя систему диффе- ренциальных уравнений, мы, как правило, лишь качественно представляем кар- тину изменения искомых функций и при этом не всегда можем определить коли- чество значащих цифр полученного результата. Такое положение вещей заставляет иногда на стадии отладки прибегать к дублированию получения результата другим методом. В нашем примере это мож- но сделать, проинтегрировав систему уравнений с помощью другой программы интегрирования, и затем сравнить полученные результаты. Правда, при этом возникает вопрос: как быть в случае расхождения результатов? Ведь ошибоч- ным может оказаться не первоначальный, а как раз контрольный результат! 2. В программе, как правило, содержится не одна, а несколько ошибок. Поэтому программист наблюдает эффект не одной ошибки, а результат взаимо- действия нескольких ошибок. 3. Трудно, а иногда и просто невозможно разработать достаточно полную систему тестов, гарантирующих обнаружение всех ошибок в программе. 4. Одни и те же признаки ошибки (формы проявления ошибок) могут быть обусловлены различными причинами. 5. Некоторые ошибки не проявляются сами по себе, а лишь приводят к воз- никновению других ошибок (так называемые н а в е д е н н ы е ошибки), которые и наблюдает программист. Например, обнуляя значения элементов массива и по ошибке выйдя за его границы, можно присвоить нулевое значение некоторой переменной, при испо- льзовании которой в арифметическом выражении будет зафиксировано деление на нуль, хотя вначале этой переменной было присвоено ненулевое значение и она нигде не изменялась. 6. Некоторые ошибки нельзя выявить, разбивая программу на части и отла- живая эти части по отдельности. Ошибка возникает лишь при взаимодействии этих частей. Таким образом, стратегия "разделяй и властвуй" не всегда ока- зывается применимой. 7. Иногда внесение каких-либо изменений в программу с целью лока- лизации ошибки (например, промежуточная печать данных, трассировка и т.п.) приводит к исчезновению проявлявшихся до этого признаков ошибки или к их изменению. Получается своеобразный заколдованный круг, когда всякая попыт- ка выявить ошибку лишь маскирует ее, не давая никакой информации. Эта си- туация схожа с ситуацией, имеющей место в физике микромира: использование какого-либо прибора для наблюдения процесса полностью изменяет этот про- цесс. 8. Иногда, изменяя программу, методом проб и ошибок можно устранить ошибку, т.е. она перестает как-либо проявлять себя. При этом остается аб- солютно непонятным, в чем же она заключалась. 9. Происходит не просто нечто более странное, чем мы предполагали:странность происходящего превы- шает и то, чего мы не смели предположить. Принцип Ожидаемого по Питеру В ходе отладки программист нередко допускает просчет, необоснованно принимая некоторые предположения о возможных источниках ошибок. Например, используя стандартную библиотечную подпрограмму,он полностью уверен в ее безошибочности. Такие неверные установки, как правило, либо заводят программиста в логический тупик, либо программист впустую тратит время, пытаясь обнаружить ошибку там, где ее нет. 10. Не всегда имеет место повторяемость ошибки от запуска программы к запуску, даже если в программу и данные не вносились изменения. Наиболее часто это возникает при наличии в программе переменных, кото- рым не присвоено начальное значение.В этом случае начальное значение этой переменной случайным образом зависит от содержимого соответствующей ей ячейки памяти в момент загрузки программы. В зависимости от этого значе- ния возможно возникновение ошибочных ситуаций. 11. При разработке большого программного комплекса отдельные части про- граммы создаются разными исполнителями, что значительно затрудняет согла- сование между частями. О т л а д к а программы - это прежде всего эксперимент, а не наблюде- ние за поведением программы. Различие между этими двумя понятиями удачно и точно охарактеризовал знаменитый русский физиолог И.П.Павлов: "Наблюдение собирает то,что ему предлагает природа, опыт же берет у природы то, что хочет". И, как всякий эксперимент,отладку нужно уметь про- водить. Очень важно при этом делать правильные выводы на основании данных, полученных из эксперимента. То, насколько при этом можно ошибиться, на- глядно демонстрирует следующая шутливая история [57]. Некий школьник предложил интересную гипотезу: он утверждал, что орга- ны слуха у пауков находятся на ногах, и взялся доказать это. Положив пой- манного паука на стол, он крикнул:"Бегом!". Паук побежал. Мальчик еще раз повторил свой приказ. Паук снова побежал. Затем юный экспериментатор отор- вал пауку ноги и, снова положив его на стол, скомандовал: "Бегом!".Но на сей раз паук остался неподвижен. "Вот видите, - заявил торжествующий маль- чик, - стоило пауку оторвать ноги, как он сразу оглох". А "окончив" отладку, вспомните, что когда известного датского скульп- тора Торвальдсена (1768 или 1770-1844) спросили мнение об одной из его скульптур, он ответил: "Я не вижу в ней недостатков, из чего заключаю,что у меня хромает воображение". VIII.4. ПРИНЦИПЫ ИСПРАВЛЕНИЯ И АНАЛИЗА ДОПУЩЕННЫХ ОШИБОК Программа, свободная от ошибок, есть абстрактное теоретическое понятие. Д.Ван Тассел Ясно, что процесс отладки складывается из двух этапов: определения мес- тонахождения ошибки и последующего ее исправления. Поговорим о принципах исправления ошибок по Майерсу [51]. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 1. V Там, где есть одна ошибка, вероятно, есть и другие. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Другими словами, ошибки имеют тенденцию группироваться. При исправле- нии ошибки проверьте ее непосредственное окружение: нет ли здесь каких- нибудь подозрительных симптомов. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 2. V Находите ошибку, а не ее симптом. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Другим общим недостатком является устранение симптомов ошибки, а не ее самой. Если предполагаемое изменение устраняет не все симптомы ошибки, то она не может быть полностью выявлена. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 3. V Вероятность правильного нахождения ошибки не равна 100%V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ С этим, безусловно, соглашаются, но в процессе исправления ошибки час- то наблюдается иная реакция (например "да, в большинстве случаев это спра- ведливо, но д а н н а я корректировка столь незначительна, что она пра- вильна"). XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Никогда нельзя предполагать, что текст, который включен V V в программу для исправления ошибки, правилен! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Можно утверждать, что корректировки более склонны к ошибкам, чем исход- ный текст программы. Подразумевается, что корректирующая программа должна тестироваться, возможно, даже более тщательно, чем исходная. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 4. V Вероятность правильного нахождения ошибки уменьшается V V с увеличением объема программы. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Это утверждение формулируется по-разному. Эксперименты показали,что от- ношение числа неправильно найденных ошибок к числу первоначально выявлен- ных увеличивается для больших программ. В большой программе, рассчитанной на широкое применение, каждая шестая вновь обнаруженная ошибка может быть допущена при предшествующем внесении изменений в программу. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY 5. V Остерегайтесь внесения новой ошибки при корректировке.V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Необходимо рассматривать не только неверные корректировки, но и те, ко- торые кажутся верными, однако имеют нежелательный побочный эффект и таким образом приводят к новым ошибкам. Другими словами, существует вероятность не только того, что ошибка будет обнаружена неверно, но и того, что ее исправление приведет к новой ошибке. Поэтому после проведения корректиров- ки должно быть выполнено повторное тестирование, позволяющее установить, не внесена ли новая ошибка. Когда кто выходит из дому, пусть поразмыслит о том, что намерен делать, а когда снова войдет в дом, пусть поразмыслит о том, что сделал. Древнегреческий мыслитель Клеобул Укажем один старый прием исправления ошибок, заключающийся в использо- вании так называемых "з а п л а т" ("patch" - "заплата", "вставка в прог- рамму"). Необходимость в "заплате" возникает, когда Вы хотите вставить последо- вательность новых операторов между двумя операторами,которые мы обозначим О1 и О2. Организация заплаты происходит при помощи оператора GOTO (тут мы отступаем от одного из основных принципов структурного программирования!): 30 О1:GOTO 1000 'Переход на операторы "заплаты" 40 О2 GGG 500 END 'Окончание основной программы. GGG 1000 ... 'Операторы "заплаты" 1010 GOTO 40 XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Никогда не оставляйте "заплаты" в о т л а ж е н н о й программе! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Удалить "заплату" - это значит включить операторы заплаты в основной текст программы, расположив их там, где им надлежит находиться. Интересная невыдуманная история с"заплатой" произошла на корабле "Апол- лон", облетающем Луну. Бортовая ЭВМ выдала сигнал тревоги и отказалась выполнять дальнейшие вычисления. Космонавты быстро обнаружили,что неиспра- вен аварийный датчик, дающий неправильный отсчет. Программист на Земле на- писал текст "заплаты" для программы обработки аварийных сигналов, изменяю- щий ее так, чтобы конкретный аварийный сигнал не считался аварийным. Эта "заплата" была написана прямо в кодах, и необходимые изменения текста бор- товой программы на машинном языке были продиктованы космонавтам по радио. Программа с "заплатой" благополучно довела космонавтов до Земли. Однако учтите, что "система, состоящая из "заплаток", возникших при исправлении ошибок, редко оказывается понятнее системы, которая с самого начала не имела ошибок" (Дж.Фокс). Качество работы каждого отдельного программиста существенно повышается, если выполняется детальный анализ обнаруженных ошибок или,по крайней мере, их подмножества. Эта задача трудная и требующая больших временных затрат, поскольку она подразумевает нечто большее, чем просто поверхностную клас- сификацию, такую, как "X% ошибок являются ошибками в логике" или "Y% оши- бок встречается в операторах IF". Тщательный анализ может включать в себя рассмотрение следующих вопросов. 1. К о г д а б ы л а с д е л а н а о ш и б к а? Данный вопрос является наиболее трудным, так как ответ на него требует исследования документации. Однако это и наиболее интересный вопрос. Необ- ходимо точно определить первоначальную причину и время возникновения ошиб- ки. Такой причиной может быть, например, неясная формулировка в постанов- ке задачи или коррекция предшествующей ошибки. 2. К т о с д е л а л о ш и б к у? 3. К а к о в а п р и ч и н а о ш и б к и? Недостаточно определить, когда и кем была сделана ошибка, нужно также выяснить, п о ч е м у она произошла. Была ли она вызвана чьей-то неспосо- бностью писать ясно, непониманием отдельных конструкций языка программиро- вания, ошибкой при печатании на машинке, неверным предположением, отсутст- вием рассмотрения недопустимых входных данных? 4. К а к о ш и б к а м о г л а б ы т ь п р е д о т в р а щ е н а? Ответ на этот вопрос наиболее ценен,так как позволяет осмыслить и коли- чественно обосновать накапливаемый опыт проектирования. 5. П о ч е м у о ш и б к а не б ы л а о б н а р у ж е н а р а - н е е? 6. К а к о ш и б к а м о г л а б ы т ь о п р е д е л е н а р а н е е? Ответ на этот вопрос является другим примером полезной обратной связи. Как могут быть улучшены процессы обзора и тестирования для более раннего нахождения этого типа ошибок в будущих проектах? 7. К а к б ы л а н а й д е н а о ш и б к а? При условии, что мы рассматриваем только ошибки, которые обнаружены с помощью теста, необходимо выяснить, как был написан удачный тест. Почему этот тест был удачным? Можем ли мы что-нибудь почерпнуть из него для напи- сания других удачных тестов с целью проверки данной программы или будущих программ? Такой анализ, конечно, является сложным процессом, но его результаты могут оказаться полезными для дальнейшего улучшения работы программиста. Поэтому вызывает опасения тот факт, что подавляющее большинство программи- стов его не используют! VIII.5. ОСНОВНЫЕ ПОНЯТИЯ СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ Высокое качество программ может достигаться "безошибочным" программиро- ванием ("п а с с и в н ы м и" методами) и выявлением и устранением ошибок ("а к т и в н ы м и" методами). Активные методы мы уже кратко описали. П а с с и в н ы е методы основываются на применениии методологических и организационных правил проектирования программ, а также языков програм- мирования высокого уровня. VIII.5.1. М о д у л ь н о с т ь п р о г р а м м [49] М о д у л ь н о й называют программу, составляемую из таких частей - м о д у л е й, что их можно независимо друг от друга программировать,тран- слировать, отлаживать (проверять, исправлять). Предполагается, что модули имеют небольшие размеры, четко определенные функции и, кроме того, их свя- зи между собой максимально упрощены, в частности, предполагается,что моду- ли имеют лишь одну точку входа (в начале модуля). Разбиение программы на модули при ее написании хотя и является весьма непростым делом,позволяет существенно облегчить в дальнейшем работу над программой на других этапах. После того как в алгоритме выявлены мало зависимые друг от друга час- ти, составление программы упрощается, так как при программировании каждой из этих частей почти не приходится заботиться об их взаимодействии с дру- гими частями, что в свою очередь способствует уменьшению количества вноси- мых ошибок. Кроме того, малая зависимость модулей позволяет при необходи- мости существенно распараллелить составление программы, поручив программи- рование программистам разного класса, причем всегда можно найти подходя- щую работу и для начинающих, и для опытных программистов. На этапе отладки независимость модулей позволяет отлаживать их в любом порядке, в частности и одновременно. Считается, что XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V усилия, затрачиваемые на отладку модуля, обычно пропорциональны V V квадрату его длины [Майерс] V , ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ и поэтому при тестировании небольшие размеры модулей дают возможность по- ставить задачу о проверке всех ветвей таких модулей, что ведет к увеличе- нию достоверности тестирования. Решение такой задачи является обычно недо- стижимым по отношению ко всей программе или крупным ее блокам, когда при- ходится ограничиваться лишь проверкой работы всех линейных участков блока и условий. Разумеется, и наиболее трудная часть отладки - локализация оши- бок, проводимая для модулей, при этом значительно упрощается и ускоряется. В силу минимальности логических связей между модулями облегчается, ко- нечно, и внесение исправлений в алгоритм программы, поскольку меньше при- ходится заботиться о том, чтобы при изменении одной части программы не ис- портить работу другой ее части. Учтите, что чем более мелкими требуется получать модули, тем больше трудностей возникает при проектировании и алгоритмизации программы,но тем легче будет каждый из модулей проверять и тестировать в дальнейшем.Не сле- дует, однако, забывать и о том, что слишком большое количество мелких мо- дулей может значительно увеличить трудоемкость предстоящей комплексной (стыковочной) отладки. З а м е ч а н и е. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Серьезной помощью в разработке программ могут стать б и б л и о т е-V V к и с т а н д а р т н ы х, или т и п о в ы х, модулей, заранее V V составленные автором или другими программистами. Применение при раз-V V работке ранее многократно опробованных модулей, трудность использо-V V вания которых сводится только к заданию правильных аргументов, зна-V V чительно ускоряет составление программы и облегчает ее отладку. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ VIII.5.2. С т р о е н и е п р о г р а м м [49] Не претендуя на полноту классификации, строение программ можно охарак- теризовать одной из следующих схем: 1. М о н о л и т н о е. Программа написана цельным куском, без выделе- ния каких-либо отдельных независимых частей; 2. М о н о л и т н о - м о д у л ь н о е.Имеется достаточно большая мо- нолитная главная часть программы, в которой производятся основные вычисле- ния, и из которой происходят последовательные обращения к модулям; 3. П о с л е д о в а т е л ь н о - м о д у л ь н о е.Центральная часть программы состоит из последовательно выполняемых модулей, которые в свою очередь обращаются к другим модулям; 4. И е р а р х и ч е с к о е. Программа состоит из модулей, связи меж- ду которыми подчиняются строгой иерархии: каждый модуль может обращаться только к модулям, которые ему непосредственно подчинены. Возврат всегда должен происходить в вызывающий модуль, даже в том случае, если в вызыва- емом модуле обнаруживается ошибка, препятствующая дальнейшим вычислениям (правда, не все языки программирования имеют средства для выполнения это- го требования); 5. И е р а р х и ч е с к и - х а о т и ч е с к о е. Иерархическая (или последовательная) подчиненность модулей нарушена дополнительными связями. 6. М о д у л ь н о - х а о т и ч е с к о е. Программа состоит из моду- лей, но связи их между собой не отвечают принципу иерархии (или последова- тельности). Последовательно-модульное и иерархическое (для более сложных программ) строение, как наиболее простые по логическим связям,являются теми образца- ми, к которым необходимо стремиться при разработке программы. Допустимыми вариантами являются иерархически-хаотическое и, может быть, монолитно-мо- дульное. Помимо модульности другим свойством, которое содействует предупрежде- нию появления в программе ошибок, является структурированность. Обычно с т р у к т у р и р о в а н н о й называется программа, логи- ческая структура которой отвечает некоторым жестко установленным требова- ниям. Уже модульную программу можно иногда считать в определенной степени структурированной, поскольку от модульной программы требуется, например, чтобы она состояла только из модулей с одним входом. VIII.5.3. С т р у к т у р н о е п р о г р а м м и р о в а н и е Структура (от лат. "structura" - "строение, распо- ложение, порядок"),совокупность устойчивых связей объекта, обеспечивающих его целостность и тождест- венность самому себе, т.е. сохранение основных свойств при различных внешних и внутренних измене- ниях. Советский Энциклопедический Словарь Впервые основные идеи структурного программирования были высказаны Эдсгером Дейкстрой в 1965 году и позже опубликованы в его работе [55]. Ос- новная задача, которую Э.Дейкстра решал, разрабатывая идеи структурного программирования, была задача доказательства правильности программы. Его внимание было сосредоточено на вопросе, "какими должны быть структуры про- грамм, чтобы без чрезмерных усилий мы могли находить доказательство их правильности". Это особенно важно при разработке больших программных систем. Опыт при- менения методов структурного программирования при разработке ряда сложных операционных систем показывает, что правильность логической структуры системы поддается доказательству, а сама программа допускает достаточно полное тестирование. В результате, в готовой программе встречаются только тривиальные ошибки кодирования, которые легко исправляются. Очевидно, что уменьшение трудностей тестирования приводит к увеличению производительности труда программистов. Это следует из того, что на тести- рование программы тратится от трети до половины времени ее разработки. Производительность труда программиста обычно измеряется числом отлаженных операторов, которые он может написать за день. Приближенные оценки показы- вают, что применение методов структурного программирования позволяет увеличить это число в 5╤6 раз по сравнению с традиционными способами про- граммирования. Заметим между прочим, что при структурном программировании становится излишним вычерчивание б л о к - с х е м. Блок-схема вполне структуриро- ванной программы настолько тривиально проста, что о программе можно ска- зать больше по тексту, чем по блок-схеме. Итак, структурное программирование представляет собой некоторые прин- ципы написания программ в соответствии со строгой дисциплиной и имеет целью облегчить процесс тестирования, повысить производительность труда программистов, улучшить ясность и читабельность программы, а также повы- сить ее эффективность. В настоящее время вряд ли существует достаточно простое и краткое опре- деление структурного программирования. Например, Хоор[54] определяет структурное программирование как "систе- матическое использование абстракции для управления массой деталей и спо- соб документирования, который помогает проектировать программу." Структурное программирование можно толковать как "проектирование, напи- сание и тестирование программы в соответствии с заранее определенной дис- циплиной" [54]. Х.Миллс,П.Лингер и Б.Уитт в книге [69] использовали такое определение: с т р у к т у р и з о в а н н а я программа - это программа,составлен- ная из фиксированного базового множества первичных программ. П е р в и ч н а я программа - это простая программа, не имеющая прос- тых подпрограмм, состоящих более чем из одного узла. П р о с т а я программа - это программа, которая: 1) имеет один вход и один выход, 2) для каждого узла существует путь от входа до выхода, проходящий че- рез этот узел. Суть дела здесь заключается в том, что если программное обеспечение строится только из первичных и простых программ, то логика и сам ход про- цесса ее выполнения значительно проясняются благодаря структуризации. Ис- пользование таких (готовых) структур дисциплинирует разработчика программ, что в результате приводит к появлению более понятных программ, в которых, следовательно, имеется меньшее число ошибок. Перейдем к рассмотрению теоретических оснований и методов структурного программирования. Т е о р е т и ч е с к о й о с н о в о й структурного программирова- ния принято считать принципы, изложенные в классической работе Бома и Джа- копини [40]. Эта работа в оригинале на итальянском языке была опубликова- на в 1965 г., а в английском переводе - в 1966 г. В соответствии с так называемой "структурной" теоремой, сформулирован- ной и доказанной в этой работе, всякая программа может быть построена с использованием только трех основных типов блоков [40]. 1. Ф у н к ц и о н а л ь н ы й б л о к. Ему в языках программирова- ния соответствуют операторы ввода и вывода или любой оператор (группа опе- раторов) присваивания. В виде функционального блока может быть изображена любая последовательность операторов, выполняющихся один за другим, имею- щая один вход и один выход. 2. У с л о в н а я к о н с т р у к ц и я. Этот блок включает провер- ку некоторого логического условия (P),в зависимости от которого выполняет- ся либо оператор S1, либо оператор S2. Приведем аналог условной конструк- ции на языке программирования MSX-BASIC: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V IF P THEN S1 ELSE S2 V . ZWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ 3. Б л о к о б о б щ е н н о г о ц и к л а. Этот блок обеспечивает многократное повторение выполнения оператора(ов) S, пока выполнено логиче- ское условие P. Аналог блока обобщенного цикла на языке MSX-BASIC: XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V n: IF P THEN S ELSE ...:GOTO n V . ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Важной особенностью всех перечисленных блоков является то, V V что каждый из них имеет один вход и один выход. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Кроме того, блоки S, S1, S2,входящие в состав условной конструкции или блока обобщенного цикла, сами могут быть одним из рассмотренных типов бло- ков, поэтому возможны конструкции, содержащие "вложенные" блоки. Однако какова бы ни была степень и глубина "вложенности", важно, что любая конст- рукция в конечном итоге имеет один вход и один выход. Следовательно,любой сложный блок можно рассматривать как "черный ящик" с одним входом и одним выходом. При конструировании программы с использованием рассмотренных типов бло- ков,эти блоки образуют линейную цепочку так, что выход одного блока подсо- единяется к входу следующего. Таким образом, программа имеет линейную структуру, причем порядок следования блоков соответствует порядку, в кото- ром они выполняются. Такая структура значительно облегчает чтение и пони- мание программы, а также упрощает доказательство ее правильности. Так как линейная цепочка блоков может быть сведена к одному блоку, то любая про- грамма может в конечном итоге рассматриваться как единый функциональный блок с одним входом и одним выходом. Перечислим теперь основные п р и н ц и п ы и м е т о д ы структур- ного программирования. Вы говорите, что я повторяюсь. Но я повторю. Т.Эллиот XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY I. V Как можно меньше переходов GOTO ! V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Э. Дейкстра выразил это таким образом: "Уже давно было замечено, что квалификация программистов является убывающей функцией от плотности пред- ложений GOTO в создаваемых ими программах". Причина этого заключается в том, что основные конструкции структурного программирования гораздо более лаконичны и просты, чем их аналоги на неструктурном языке программирова- ния, например MSX-BASIC. Отсюда сразу следует, что программы, написанные на MSX-BASIC, будут насыщены предложениями GOTO (написанными как явно,так и неявно!). Четыре предложения структурного программирования на приведенной ниже схеме в той или иной форме используются во многих языках программирования (приведены примеры конструкций языка программирования TURBO Pascal). XWWWWWWWWWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWRWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Предложения V Неформальное V Соответствующая последо- V V структурного V описание V вательность операторов V V программирования V V на языке MSX-BASIC V TWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWS V V Если условие истин-V V V V но, то выполнить V V VIF C THEN S1 ELSE S2V предложение S1; V IF C THEN S1 ELSE S2 V V V в противном случае V V V V выполнить предложе-V V V V ние S2 V V TWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWS V V Повторить предложе-V V V V ние S, пока условие V GOTO n V V WHILE C DO S V С останется истиннымV m: S V V V (0 или более раз) V n: IF C THEN GOTO m V V V V V TWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWS V V Повторять последова-V V V V тельность S (один V V V REPEAT S UNTIL C V или более раз) до V m: S V V V тех пор, пока усло-V IF NOT C THEN GOTO m V V V вие С не станет ис-V V V V тинным V V TWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWUWWWWWWWWWWWWWWWWWWWWWWWWWWWS V V Выполнить предложе-V V V CASE K OF V ние Si (только если V ON K GOTO N1,N2,...,Nm:V V 1: S1 V значение K=i,причем V GOTO s V V 2: S2 V i равно либо 1, V N1:S1:GOTO s V V GGG V либо 2, V N2:S2:GOTO s V V m: Sm V GGG V GGG V V V либо m V Nm:Sm V V V (выбор по значению) V GGG V V V V s:... V ZWWWWWWWWWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWQWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Обратим Ваше внимание на то, что при программировании конструкций структурного программирования на языке MSX-BASIC невозможно обойтись без оператора GOTO, с помощью которого осуществляется переход на ту или иную ветвь условной конструкции. Однако следует иметь в виду, что оператор GOTO используется только для передачи управления в н у т р и конструк- ции, что не противоречит идеям структурного программирования. Дейкстра продолжает: "Я пришел к убеждению, что предложение GOTO долж- но быть устранено из всех языков программирования "высокого уровня" (т.е. отовсюду, за исключением, возможно, простых машинных кодов)". XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Однако сейчас стало ясным, что программирование без оператора GOTO- V V это еще не структурное программирование. Можно написать программу V V без оператора перехода, логическая структура которой тем не менее V V будет неудачной. И, наоборот, существуют ситуации, в которых пере- V V ход является лаконичным, простым и ясным средством, в то время как V V другие подходы сравнительно неудачны. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Например, правила структурного программирования часто предписывают по- вторять одинаковые фрагменты программы в разных участках модуля, чтобы из- бавиться от употребления оператора GOTO. В этом случае "лекарство хуже болезни", т.к. дублирование резко увеличивает возможность внесения ошибок при изменении модуля в будущем. Д.Кнут в работе [70] показал, что можно говорить о структурном прог- раммировании и при использовании оператора GOTO!Структурное программирова- ние на языках FORTRAN или BASIC возможно, хотя с большими трудностями и некоторыми нежелательными последствиями. Так, например, Чармонмен и Ведже- нер [72] показали,что можно сделать программу на языке FORTRAN похожей на структурную!  II. Другой метод улучшения качества программирования заключается в при- менении н и с х о д я щ е г о п р о е к т и р о в а н и я, ("top-down programming" - "программирование "сверху вниз""). XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Оператор GOSUB является о с н о в н ы м инструментом V V с т р у к т у р н о г о программирования. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ В методе нисходящего проектирования Вы вначале пишете основную програм- му, используя оператор GOSUB для вызова подпрограмм,причем в качестве под- программ вначале Вы вводите "заглушки" вида: PRINT "Вызвали подпрограмму номер ...":RETURN Затем, будучи уверенным в правильности логического построения основной программы, Вы детально "расписываете" каждую подпрограмму, вызывая по ме- ре необходимости подпрограммы более низкого уровня. Этот последовательный процесс продолжается, пока программа не будет завершена и проверена. При другом методе - в о с х о д я щ е м п р о е к т и р о в а н и и (программировании "снизу вверх") - Вы вначале пишете подпрограммы нижнего уровня и тщательно их тестируете и отлаживаете. Далее Вы добавляете под- программы более высокого уровня, которые вызывают подпрограммы нижнего уровня, и так до тех пор, пока Вы не достигнете программы самого верхнего уровня. Метод проектирования "снизу вверх" пригоден при наличии больших библиотек стандартных подпрограмм. Учтите, что иногда лучшим является гибрид двух методов. Однако в обо- их случаях каждая подпрограмма должна быть небольшой, так чтобы можно бы- ло охватить одним взглядом всю ее логику (для персональных компьютеров же- лательно, чтобы и основная программа,и подпрограммы ц е л и к о м поме- щались в пределах 20╤30 строк экрана дисплея!) Всякий велосипедист хорошо знает, что ехать сверху вниз быстрее и удоб- нее, чем снизу вверх. В программировании дело обстоит примерно так же: "сверху вниз" писать программы удобнее потому,что при таком методе мы точ- но знаем, какие подпрограммы описывать. Но есть у этого метода и недостаток: на верхнем уровне не всегда видно, куда спускаться, то есть как разделить решение задачи на такие части, каж- дую из которых было бы легко описать отдельной процедурой. У опытных про- граммистов вырабатывается своеобразное чутье: они сразу видят, какие нуж- ны процедуры, а новичкам иногда приходится туго. Метод "снизу вверх", хотя и требует большого труда, бывает очень поле- зен на первых порах. Пусть даже половину составленных Вами подпрограмм придется потом "выбросить", но зато Вы хорошо почувствуете, какие подпро- граммы для исходной задачи необходимы. Да и отлаживать каждую написанную подпрограмму можно сразу: ведь все, что "под ней", уже описано (а обычно и отлажено). Словом, любишь кататься "сверху вниз" - люби и саночки во- зить (в обратном направлении). Опытные программисты иногда применяют ме- тод "снизу вверх" для того, чтобы заранее заготовить для новой задачи на- бор подпрограмм, которые могут понадобиться в различных случаях. Так что "возить саночки" приходится не только новичкам! III. Структурное программирование до сих пор было у нас представлено как свойство или оценка о к о н ч а т е л ь н о г о текста программы. Необходимо добавить еще один ключевой момент - методологию, или особеннос- ти мыслительного процесса, управляющего процессом получения структурной программы. Этот мыслительный процесс называется п о ш а г о в о й д е - т а л и з а ц и е й и был первоначально предложен Э.Дейкстрой [73], а затем улучшен Н.Виртом [74]. XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY V Пошаговая детализация представляет собой простой процесс, V V предполагающий первоначальное выражение логики программы в V V терминах гипотетического языка "очень высокого уровня" с V V последующей детализацией каждого предложения в терминах язы- V V ка более низкого уровня, до тех пор,пока, наконец, не будет V V достигнут уровень используемого языка программирования. V ZWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[ Причем на протяжении всего процесса логика выражается основными конст- рукциями структурного программирования. В методе пошаговой детализации можно выделить следующие существенные этапы [8]: 1. На уровне 1 создается общее описание программы в целом.Определяются основные логические шаги, требуемые для решения задачи, даже если пока не- известно, как их выполнить. Эти логические шаги могут отражать различные физические шаги решения или могут быть удобными групповыми именами для тех действий, выполнение которых представляется довольно смутно. Последо- вательности шагов, требуемых для решения задачи, записываются на обычном языке или на п с е в д о к о д е (см. ниже). 2. На уровне 2 в общих терминах детализируется описание шагов, введен- ных на этапе 1. В детализированное описание может входить обозначение цик- лических структур,в то время как действия внутри циклов могут по-прежнему оставаться неясными. Таким образом, выполняются только общие эскизы слож- ных действий. 3. На этом и последующих уровнях в виде последовательных итераций про- изводятся те же действия, что описаны на этапе 2). При каждой новой ите- рации уточняются детали, оставшиеся неясными после предыдущих итераций, и создаются более определенные описания. По мере выполнения итераций неопре- деленные детали становятся все проще и проще, так что на каком-то этапе могут быть полностью описаны. 4. Разработка завершена: в модульном виде получено описание требуемой программы. Перевод этого описания в программу на конкретном языке програм- мирования должен быть достаточно простой задачей. П с е в д о к о д включает в себя наборы фраз для написания таких групп операторов: последовательность, выбор, итерация, - дополняемых текс- том на обычном языке. Псевдокод не имеет строгого определения, поэтому Вы всегда можете сконструировать свой с о б с т в е н н ы й п с е в д о - к о д, используя, например, конструкции школьного алгоритмического языка: если , пока , для , выбор , а также комментарии, формулы и словесное опи- WWWW WWWW WWW WWWWW сание действий и процессов. В описание процессов могут входить и такие операторы конкретного языка программирования (например, BASIC), как INPUT, PRINT, READ и присваивания, но не операторы п е р е х о д а или другие средства передачи управления, применение которых должно ограничиваться реализацией трех указанных выше типов структур на заключительном этапе процесса проектирования. Концепцию псевдокода легче всего уяснить на примере. Пусть требуется определить наибольшее значение в некотором наборе дан- ных и вывести эти данные, поделенные на наибольшее значение. Скажем, если данные представляют собой последовательность чисел 4., 2.51, 10.0, -5.0, 7.5 , то вывод должен выглядеть следующим образом: 0.40, 0.251, 1.00, -0.5, 0.75 . Первый уровень разработки ясен: У р о в е н ь 1: WWWWWWWWWWWWWWWW 1) ввести данные; 2) найти максимум введенных данных; 3) вывести результаты. Д е т а л и з а ц и я 1.1. В в о д д а н н ы х можно детализировать WWWWWWWWWWWWWWWWWWWWWWWWWW на псевдокоде следующим образом: 1) определить количество чисел; 2) пока не все элементы введены, прочитать и запомнить значение элемен- та; 3) конец цикла. Это описание можно перевести на язык MSX-BASIC следующим образом: 100 INPUT"Укажите количество элементов";N 110 FOR I=1 TO N:INPUT A(I):NEXT I Д е т а л и з а ц и я 1.2. О т ы с к а н и е м а к с и м у м а мож- WWWWWWWWWWWWWWWWWWWWWWWWWW но детализировать следующим образом: 1) выбрать в качестве максимума первый элемент данных; 2) пробежать все введенные значения, заменяя текущий максимум на оче- редное значение, если оно не превысило его. Д е т а л и з а ц и я 1.3. В ы в о д р е з у л ь т а т о в можно WWWWWWWWWWWWWWWWWWWWWWWWWW детализировать следующим образом: 1) пока не все результаты выведены; 2) ВЫВОД значение очередного элемента, поделенное на максимум; 3) конец цикла. Это описание можно немедленно перевести на MSX-BASIC следующим образом: 300 FOR I=1 TO N:PRINT A(I)/M:NEXT I У р о в е н ь 2. WWWWWWWWWWWWWWWW Он включает в себя три детализованные выше части, из которых только детализация (1.2) требует дополнительного внимания. Ее можно детализиро- вать на псевдокоде следующим образом: 1) положить М, равное первому элементу данных; 2) пока не все элементы просмотрены; 3) если М<текущий элемент, то M = текущий элемент; 4) конец цикла. Это описание можно перевести на MSX-BASIC следующим образом: 200 M=A(1) 210 FOR I=1 TO N 220 IF M