Приступая к отладке своей программы,
вы, как правило, всегда находите несколько действительно грубых
ошибок. Не отчаивайтесь – вы в хорошей компании! Ведь каждый человек
совершает ошибки. Перед вами – перечень ошибок, которые время от
времени делает каждый программист. Когда вы сами попадетесь на крючок
одной из них, подумайте об этом, как о чем-то само собой разумеющемся.
Предположим, вам нужно поставить ни к чему
не прикрепленный забор длиной в 100 метров между бассейном и шоссе,
чтобы проезжающие автомобилисты не теряли управления, любуясь вашим
загаром. А тот элегантный декоративный стиль ограждения, которому вы
отдаете предпочтение, требует, чтобы столбы стояли на расстоянии 10
метров. Так сколько же столбов вам нужно купить?
Если вы сказали «10», то совершили ошибку заборных
столбов, известную в миру как ошибка неучтенной единицы. Вам потребуется
столб на одном конце, а затем очередной столб каждые десять метров,
т.е. всего 11 столбов, а не 10.
Эквивалентным вопросом в Delphi может быть
следующий: сколько кнопок помещается в массиве ARRAY[0..10] of TButton?
И снова ответом будет 11, а не 10. А если вы захотите иметь массив для
10 кнопок, вам необходимо установить другие границы массива: [0..9]
или [1..10].
Другая проблема неучтенной единицы всплывает
в циклах REPEAT – UNTIL;
Этот код выглядит как цикл, который будет
выполнять вызов функции EngraveOnTablet для переменной Commandment,
принимающей значения от 1 до 10. Но не тут-то было! На самом деле, он
работает на девяти значениях: от 2 до 10. Чтобы исправить положение,
можно либо до входа в цикл установить Commandment равной 0, либо вызывать
функцию EngraveOnTablet до прибавления единицы к значению переменной
Commandment.
В большинстве случаев можно предотвратить ошибку
заборных столбов путем сужения проблемы. Например, чтобы поставить 10-метровый
забор с пролетами длиной в 10 метров, вам понадобиться всего два столба
– по одному на каждом конце. Каждая дополнительная 10-метровая секция
потребует еще одного столба, поэтому для 100-метрового забора необходимо
забить 11 столбов. В случае с циклом REPEAT – UNTIL представьте себе,
что условием окончания цикла было UNTIL Commandment = 2. Не трудно понять,
что заданное условие станет истинным на самом первом проходе цикла,
поэтому цикл выполняется только один раз, а не два. Другими словами,
если вы подозреваете ошибку заборных столбов, постарайтесь сузить проблему
до такой степени, чтобы решение стало очевидным.
Представьте себе ситуацию: вы работали всю
ночь, чтобы закончить налоговую декларацию до 15 апреля. На рассвете
вы протерли глаза и аккуратно запечатали конверт, но забыли положить
в него заполненную декларацию! В это нетрудно поверить, особенно, когда
пишешь функцию, а тем более после всенощной, отданной программированию.
Ваша программа легко может вычислить десяток промежуточных значений,
дотошно объединяя их в одну временную переменную, но затем допустит
небрежность и не присвоит эту переменную возвращаемому значению функции.
Такая функция возвратит самый настоящий мусор – ничего, кроме байтов,
прочитанных из системной памяти.
Лучший способ предотвратить эту проблему состоит
в полном отказе от временной переменной. Каждая функция Delphi имеет
заданную в явном виде локальную переменную с именем Result, и вы можете
и должны использовать именно Result вместо временной переменной. Никаких
новых забот вам это не прибавит. Вот простой пример:
FUNCTION DefaultExt(const FileName, Extension : String) : String;
BEGIN
Result := UpperCase(FileName);
IF Pos (‘.’,Result) = 0 THEN
Result := Result + ‘.’ + Extenstion;
END;
Эта функция начинается с перевода имени файла
в верхний регистр букв и присваивания его переменной Result. Затем,
если переменная Result не содержит точки, эта функция добавит ее, а
также добавит переданное расширение к значению Result. И не нужна здесь
никакая временная переменная!
Очень желательно
всегда работать со своей программой при включенном параметре компилятора
Range checking, отвечающем за проверку диапазона. Просто удивительно,
как много ошибок попадает в эту встроенную сетку безопасности' Конечно,
вам разрешается отключать ее при компиляции окончательной версии вашей
программы. Что говорить, отключение проверок на различные ошибки, возникающие
во время выполнения программы, значительно сокращает результирующий ЕХЕ-файл.
Но когда ваши пользователи сообщат вам об ошибках и вам придется снова
погрузиться в свою программу, сожмите зубы и включите эти параметры снова.
Наряд по охране государственных границ диапазона
выйдет на дежурство, если включить параметр Range checking на странице
Compiler диалогового окна Project Options. Кроме того. вы можете включать
или отключать эту пограничную службу в своей программе с помощью директив
компилятора. {$R+} включает проверку последующих строк кода. a {$R-} отключает
ее. Эти директивы отменяют состояние, установленное выключателем параметра
Range checking, Если ваша программа пытается присвоить переменной значение,
не входящее в дозволенный диапазон, то вы имеете дело с простейшей ошибкой
выхода за диапазон. При включенном параметре Range checking вы столкнетесь
с ошибкой времени выполнения программы (run-time error), А при отключенной
проверке результаты могут вар очень удивить. Если вы попытаетесь поместить
четыре байта типа LongInt в двухбайтовый контейнер, то не вместившиеся
два байта могут попросту потеряться. Например, поместите число 987654321
в переменную типа Word. и в результате вы получите 26801. Кто посмел так
обезобразить наше число?!
Но это только цветочки! А вот если вы отключите
пограничную службу и попытаетесь получить доступ к элементам массива за
его границами, то это может стоить жизни ни в чем не повинных переменных!
Открыв новый проект, создайте обработчик событий OnCreate для формы и
замените любезно предоставленную Delphi пару begin-end следующим кодом
Установите точку прерывания и последней строке
кода перед строкой end;. Теперь нажмите Ctrl+F5 и "несите Bystander,x
к окно Watch.
Запустите программу до точки прерывания, а затем
нажмите F7, чтобы выполнить одну строку кода.
Вот и проявились ужасные последствия ошибки
выхода за диапазон - первозданный нуль невинной переменной Bystander
превратился в пыль. И так будет происходить снова и снова, если вы не
воспользуетесь защитой пограничной службы. А ведь вас предупреждали!
Не существует способа представить громадное
большинство чисел с абсолютной точностью, поэтому мы устанавливаем максимально
возможное количество значащих цифр. Поддерживаемый Delphi тип данных
Extended предлагает 19 значащих цифр - достаточно, чтобы измерить расстояние
от нас до Альфа Центавра с ошибкой всего лишь в несколько миллиметров.
Но даже и в этом случае два числа, отличающиеся друг от друга на такую
крошечную величину, не равны.
Разместите на новой форме друг под другом четыре
метки и установите для всех свойство Alignment равным taRighJustify.
Установите их заголовки равными X:, Y:, Y=X? и Y-X:. В параллельную
вторую колонку добавьте еще четыре метки. Для обработчика событий OnCreate
данной формы замените пару begin-end следующими строками:
VAR X, У : Extended;
begin
X := 1995;
Y:= Sqr(Sqrt(X)).
Label5.Caption := FloatToStr(X);
Label6.Caption := FloatToStr(Y);
IF Y=X THEN
Label7.Caption := "ДА!"
ELSE
Label7.Caption := "НЕТ";
Label8.Caption ;= FloatToSTr(Y-X)
end;
Возведение в квадрат квадратного корня из какого-нибудь
числа должно возвращать вам то же самое число, и, кажется, что так оно
и есть. Но выражение Y-=X не является истинным значением, и, действительно.
между числами Х и Y есть кро-о-о-шечная разница. Эта разница, около
0.0000000000000001, не имеет какого-нибудь существенного значения, но
все-таки это разница.
Может быть, получив однажды шок от внезапного
открытия, что 1995 не всегда равно 1995, вы поинтересуетесь, что же
делать? Оказывается, все очень просто - нужно только принять решение,
какая именно погрешность является достаточной. Тогда вместо проверки
на равенство двух чисел проверяйте, достаточно ли близки эти числа.
Возвращаясь к предыдущему примеру, вставьте
следующую строку сразу после строки, которая объявляет переменные Х
и Y:
CONST epsilon = 0,000000001;
Теперь замените IF X=Y на следующую строку:
If Abs(X-Y) < epsilon
Абсолютное значение разности между Х и Y намного
меньше, чем одна миллиардная, поэтому программа сообщит, что переменная
Х действительно равна переменной Y.