VIII.3.1. Т р а с с и р о в к а Всякий необходимо причиняет пользу, упот- ребленный на своем месте. Напротив того: упражнения лучшего танцмейстера в химии неуместны; советы опытного астронома в танцах глупы. Козьма Прутков Т р а с с и р о в к а ("tracе" - "след") представляет собой пошаговое выполнение программы в автоматическом режиме. Если Вы создали сложную по логике программу, и она выполняется неправильно, а по информации, выводи- мой на экран нельзя установить, где ошибка, то оператор TRON где TRON ("TRacing ON" - "установить трассировку") - служебное слово, поможет Вам произвести трассировку программы во время ее выполнения. Выполнение оператора TRON приводит к последовательному выводу номеров в с е х выполняемых в данный момент строк программы в виде [n1][n2]...[nk] Цепочка этих номеров составляет с л е д (или т р а с с у) работы про- граммы. Между компонентами [···] располагаются значения, выводимые по опе- ратору PRINT или вводимые с клавиатуры по оператору INPUT. Так как номер к а ж д о й выполненной строки будет выведен на экран, то Вы легко увидите, по каким"ветвям" выполняется Ваша программа, поэтому анализ полученной трассы, как правило, позволяет локализовать ошибку. Отменяется режим трассировки оператором TROFF где TROFF("TRacing OFF" - "отменить трассировку") - служебное слово. Итак, если Вам непонятна работа какого-то участка программы, то в нача- ле участка надо поставить оператор TRON, а в конце - оператор TROFF. Например: 249 TRON 250 'Начало проверяемого участка ··· 300 'Конец проверяемого участка 310 TROFF П р и м е р 1. 10 TRON:GOSUB 100:END ───────────── 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 ────▲───── Ok ─────▲──── │ │ П о д у м а й т е, п о ч е м у ?! П р и м е р 2. 10 TRON:INPUT A,B:PRINT A;B ───────────── 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. ┌───────────────────────────────────────────────────────────────────┐ │ Единственное, что может отменить трассировку,- это выполнение │ │ оператора TROFF или нажатие кнопки "RESET"! │ └───────────────────────────────────────────────────────────────────┘ Операторы TRON и TROFF можно использовать и в режиме прямого выполне- ния команд. П р и м е р 3. Ok ───────────── 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) ─────────── run Division by zero in 10 Ok ??? ◀── Р а з д у м ь е ... print X ◀── А в а р и й н а я печать 0 ◀── Вот теперь все ясно! Ok - Вам известны мои методы. Примените их! Конан Дойль. Собака Баскервилей Эффективным методом локализации ошибки для небольших программ является прослеживание в обратном порядке логики выполнения программы с целью обна- ружения точки, в которой нарушена логика [51]. Другими словами, отладка начинается в точке программы, где был обнаружен (например, напечатан) не- корректный результат. Для этой точки на основании полученного результата следует установить (логически вывести), какими должны быть значения пере- менных. Мысленно выполняя из данной точки программу в обратном порядке и опять рассуждая примерно так: "Если в этой точке состояние программы (т.е.значе- ния переменных) было таким, то в другой точке должно быть следующее состо- яние", можно достаточно быстро и точно локализовать ошибку (т.е. опреде- лить место в программе между точкой, где состояние программы соответство- вало ожидаемому, и первой точкой,в которой состояние программы отличалось от ожидаемого). VIII.3.3. Л о к а л и з а ц и я с т о ч к а м и о с т а н о в а Разделяй и властвуй! Людовик XI Трассировка хороша только для коротких программ; более универсальным способом является п е ч а т ь в у з л а х ("snapshot" - "моментальный снимок") или л о к а л и з а ц и я с т о ч к а м и о с т а н о в а . Т о ч к а о с т а н о в а - это точка в программе, на которой Вы мо- жете временно остановить выполнение программы,просмотреть значения интере- сующих Вас переменных и затем продолжить выполнение. Это осуществляется путем включения в программу в точке останова опера- тора, имеющего простейший синтаксис STOP . При выполнении этого оператора вычисления приостанавливаются и пользо- ватель в режиме непосредственного счета может вывести значения интересую- щих его переменных. Анализ их позволяет делать выводы о правильности вычи- слительного процесса, а следовательно, и принимать те или иные решения. Оператор STOP выводит сообщение "Break in ∗∗∗∗" ("О с т а н о в к а в с т р о к е с н о м е р о м ∗∗∗∗"), где ∗∗∗∗ - номер строки, содержащей оператор STOP. Если Вы будете внимательны, то услышите и предупредительный звонок,как и при выполнении оператора BEEP. После останова по оператору STOP вычисления могут быть возобновлены ко- мандой CONT которая должна быть выполнена в режиме непосредственного счета. ┌────────────────────────────────────────────────────────────┐ │ Однако ни в коем случае не изменяйте, не добавляйте и │ │ не исключайте в этот момент строки Вашей программы! │ └────────────────────────────────────────────────────────────┘ П р и м е р. Программа, осуществляющая вычеркивание R-й буквы из сло- ─────────── ва 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 ┌─▶ cont ┌─▶ cont ┌─▶ cont ┌─▶... Break in 130 │ Break in 130 │ Break in 130 │ Break in 130 │ Ok │ Ok │ Ok │ Ok │ print Q$ │ print Q$ │ print Q$ │ print Q$ │ к │ кр │ кро │ кров │ Ok ──────┘ Ok ───────┘ Ok ──────┘ Ok ──────┘ Кстати, нажатие клавиш "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") . После устранения причин, вызвавших прерывание программы, как прави- ло, приходится запускать ее заново. Однако имеется возможность"обработать" ошибку, не прекращая вычислений. Для этих целей используются операторы: ┌─────────────────────┐ │ ON ERROR GOTO n │ │ ON ERROR GOTO 0 │ , └─────────────────────┘ где: ON("на"), ERROR("ошибка"), GOTO - служебные слова; n - номер программной строки. При выполнении оператора ON ERROR GOTO n происходит назначение переда- чи управления на строку с номером n,но сама передача реализуется л и ш ь в случае возникновения ошибки. ┌───────────────────────────────────────────────────────────┐ │ Оператор ON ERROR GOTO 0 отключает обработку ошибок │ │ пользователем и включает системную обработку ошибок. │ └───────────────────────────────────────────────────────────┘ Фрагмент программы, начинающийся со строки с номером n и заканчивающий- ся одним из операторов: ┌───────────────────────────────┐ │ RESUME 0 или RESUME │ │ RESUME NEXT │ │ RESUME m │ └───────────────────────────────┘ называется подпрограммой о б р а б о т к и о ш и б о к. Здесь: RESUME ("продолжаю"), NEXT - служебные слова; m - номер программной строки. После обработки ошибки в зависимости от значения параметра, расположен- ного за служебным словом RESUME, возврат в основную программу осуществля- ется: 1) либо к оператору, вызвавшему ошибку, в случае операторов RESUME 0 или RESUME ; 2) либо к следующему за ним в случае оператора RESUME NEXT , причем этот оператор может находиться в той же строке, в которой была об- наружена ошибка; 3) либо к программной строке с номером m в случае оператора RESUME m . Разумеется, старайтесь не возвращаться "внутрь" цикла, минуя заголовок, и "внутрь" другой подпрограммы! П р и м е р ы: ───────────── 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"Осуществлен переход на └──────└─── О ш и б к и оператор 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 ∗∗∗∗" ("О т с у т с т в и е о п е р а т о р а RESUME в с т р о к е ∗∗∗∗"). При установке "ловушек" ошибок можно не ограничиваться стандартными сообщениями MSX-BASIC об ошибках. Если в Вашей программе требуется, чтобы вводимое число находилось в ин- тервале между 0 и 1000,нарушение этого требования можно рассматривать как ошибку, которой присваивается собственный код. Конечно, Вы заметили, что в MSX-BASIC задействованы не все номера ко- дов ошибок. Так, коды 26÷49 и 60÷255 находятся в распоряжении программис- та. (В последующих версиях MSX-BASIC эти значения могут изменяться!) Для подпрограммы обработки указанной ошибки ввода возьмем код 255. Со- ответствующие операторы могут иметь такой вид: 10 ON ERROR GOTO 1000 ··· 50 IF X<0 OR X>1000 THEN ERROR 255 ··· 1000 IF ERR=255 THEN PRINT"Число вне диапазона" 1010 RESUME Строка 50 при обнаружении ошибки вызывает автоматический переход к под- программе. Формирование собственных сообщений об ошибках приносит не много пользы. Этот способ несколько неуклюж: он требует наличия двух операторов IF... THEN, тогда как фактически достаточно одного. Главное его преимущество со- стоит в том,что появляется возможность сгруппировать в программе все сооб- щения об ошибках и сделать их единообразными, что облегчает процедуру по- полнения списка обрабатываемых ошибок. MSX-BASIC обеспечивает одновременную обработку т о л ь к о о д н о й ошибки. Если оператор ON ERROR GOTO отсылает какую-либо программу к под- программе обработки ошибок, в которой возникает еще одна ошибка, эта ошиб- ка не будет обработана, но она вызовет появление сообщения об ошибке и прекращение счета. ┌────────────────────────────────────────────────────────────────────────┐ │ Отметим, что в слове ONELIN рабочей области по адресу &HF6B9 хранится │ │ссылка на адрес первой программной строки подпрограммы обработки ошибок.│ └────────────────────────────────────────────────────────────────────────┘ П р и м е р 3. 10 ON ERROR GOTO 90 ───────────── 20 INPUT 30 END 90 A=A+1 100 RESUME Выполним эту программу. А теперь... ? HEX$(PEEK(&HF6BA));HEX$(PEEK(&HF6B9)) 801B ◀── Мы получили адрес PIT, начиная с которого расположе- Ok на первая строка подпрограммы обработки ошибок. А теперь, просмотрeв PIT при помощи "палочки-выручалочки" - оператора PEEK, получим: ┌───────────┬────────────┬─────────────────────────────────┐ │ А д р е с │ Содержимое │ К о м м е н т а р и й │ │───────────┼────────────┼─────────────────────────────────┤ │ &H801B │ &H25 │ Указатель на адрес программной │ │ &H801C │ &H80 │ строки, следующей за строкой 90 │ ├───────────┼────────────┼─────────────────────────────────┤ │ &H801D │ &H5A=90 │ Номер первой программной строки │ │ &H801E │ 0 │ подпрограммы обработки ошибок │ ├───────────┼────────────┼─────────────────────────────────┤ │ &H801F │ &H41 │ Код символа "A" │ └───────────┴────────────┴─────────────────────────────────┘ В подпрограммах обработки ошибок обычно используются функции: α) ERR где ERR ("ERRor") - служебное слово, которое возвращает код последней ошибки, и β) ERL где ERL ("ERror Line"-"ошибочная строка") - служебное слово,которое воз- вращает номер строки, где произошла ошибка. П р и м е р ы: ───────────── 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" ──▶A=0 11 10 Ошибка! Ok 11 20 0 Ok ┌────────────────────────────────────────────────────────────┐ │ Значение функции ERL хранится в слове ERRLIN области │ │ системных переменных по адресу &HF6B3 │ └────────────────────────────────────────────────────────────┘ Например: 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 П р и м е р ы: ───────────── 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 └── "Сомнительное" имя! Если Вы не обнаружили причины появления ошибки и номер строки, в кото- рой она произошла, то это может привести к "зацикливанию" Вашей программы, избавиться от которого Вам поможет включение в подпрограмму обработки оши- бок оператора: 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 ───────────── 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 ───────────── 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", и программа будет прервана.