Как
правило, отладка — это проверка работы
и исправление ошибок программистом
перед передачей проекта на тестирование.
Инструментарии
отладки позволяет проконтролировать
избранные участки кода приложения для
локализации ошибки, выполняя приложение
по шагам, останавливаясь в точках
останова, дает возможность проверить
значения переменных, свойств объектов
и другую интересующую информацию и
выяснить, таким образом, источник ошибки.
В
набор инструментария отладки Visual Basic 6
входят такие основные инструменты, как:
-
панель инструментов
Debug (Отладка)
с кнопками команд для выполнения отладки
приложения; -
окно
Immediate
(Непосредственное выполнение),
предназначенное для непосредственного
ввода команд, требующих немедленного
выполнения; -
окно
Watches
(Наблюдение), предназначенное для
просмотра значений выражений, включенных
в список просмотра; -
окно
Locals
(Локальные), предназначенное для
просмотра значений переменных; -
редактор кода со
встроенными возможностями просмотра
переменных, констант, свойств, выражений
при отладке приложения в точках останова
и пошаговом выполнении приложения; -
окно
Call Stack (Стек
вызовов) для просмотра вызванных, но
незавершенных процедур.
Панель
инструментов
Debug (рис.
10.4) активизируется при выборе из меню
View
(Вид) команды
Toolbars (Панели
инструментов), а затем значения Debug
(Отладка).
На
панели инструментов
Debug находятся
кнопки, обеспечивающие работу по отладке
приложения. Назначение этих кнопок
описано в табл. 10.3.
Таблица
10.3. Кнопки
панели инструментов
Debug
|
Кнопка |
Название |
Назначение |
|
|
Start/Continue (Продолжить) |
Запускает |
|
|
Break |
Вызывает |
|
|
End(Завершение) |
Завершает |
|
|
Toggle (Установить |
Устанавливает |
|
|
Step |
Осуществляет |
|
|
Step |
Осуществляет |
|
|
Step |
Выполняет |
|
|
Locals |
Открывает |
|
|
Immediate |
Открывает |
|
|
Watch |
Открывает |
|
|
Quick (Быстрый |
Открывает |
|
|
Call (Стек |
Открывает |
С
остальными инструментами вы сможете
познакомиться при рассмотрении процесса
отладки. Откройте любое из ранее
разработанных приложений, например
приложение для изучения записи/чтения
данных из файла. Установите точку
останова в процедуре записи данных. Для
этого щелкните кнопкой мыши, установив
указатель в сером вертикальном поле
редактора кода напротив интересующей
строки, или выполните команду
Toggle Break-point
(Установить точку останова) меню
Debug (Отладка).
При этом в сером вертикальном поле рядом
с выбранной командой устанавливается
жирная точка (рис. 10.5), остановка выполнения
программы произойдет именно в этом
месте кода.
При
запуске программы в точке останова
выполнение программы приостанавливается
и для контроля работы приложения можно
использовать весь отладочный
инструментарий: просматривать значения
переменных и выражений при позиционировании
маркера на выбранной переменной или
выражении (рис. 10.6).
Для
более тщательного контроля работы
приложения можно использовать окна
просмотра:
Immediate
(Немедленное выполнение),
Watches
(Наблюдение),
Locals (Локальные),
Quick Watch
(Быстрый просмотр).
Call Stack (Стек
вызовов).
Кроме
прямого ввода и выполнения команд, окно
Immediate служит
для выяснения значения переменных и
выражении. Для этого необходимо выполнить
команду ? с указанием интересующего
выражения, как это показано на рис. 10.7.
Значение выражения выводится в следующей
строке этого же окна.
При
работе с окном
Immediate удобно
использовать объект Debug и его метод
print. В режиме запуска метод Debug. Print выводит
текстовое сообщение в окне
Immediate.
Синтаксис этого метода очень простой:
Debug.Print
StringMessage где StringMessage — текст выводимого
в окно
Immediate
сообщения.
В
окне Locals
можно просмотреть все переменные и их
значения, используемые в данный момент
приложением (рис. 10.8)
Основным
окном для просмотра значений выражений
является окно Watches
(рис. 10.9). В этом окне можно проконтролировать
любое выражение из списка введенных
для проверки.
Список
проверяемых выражений пополняется в
диалоговом окне
Add Watch (рис.
10.10), открываемом командой
Add Watch (Добавить
наблюдение) контекстного меню редактора
кода или одноименной кнопкой на панели
инструментов
Debug (Отладка).
Для добавления выражения в список
контролируемых необходимо ввести его
в текстовое поле
Expression
(Выражение) этого окна.
Замечание
Если
в тексте кода выделить выражение, то
при вызове окна
Add Watch это
выражение по умолчанию вставляется в
поле Expression.
Область
Context диалогового
окна Add Watch
позволяет установить область видимости
переменных, наблюдаемых в выражении.
Раскрывающийся список
Procedure
(Процедура) задает видимость внутри
процедур, а список Module
(Модуль) — внутри программных модулей.
Переключатель
Watch Type (Тип
просмотра) определяет, как Visual Basic
реагирует на наблюдаемое выражение.
При установке опции
Watch Expression
(Выражение наблюдения) значение выражения
отображается в диалоговом окне
Watches в режиме
прерывания. Опция
Break When Value Is True (Приостановить
при равенстве выражения значению
Истина)
автоматически задает режим прерываний
и обращение к контролю выражений при
условии равенства выражения значению
True или
ненулевому значению для числовых
выражений. Опция
Break When Value Changes
(Приостановить при из менении значения
выражения) аналогична опции
Break When Value Is True, но
приложение входит в режим прерывания
при изменении значения выражения.
В
дополнение к окну
Watches, для
просмотра выражений можно использовать
окно Quick Watch
(Быстрый просмотр) для быстрого доступа
к значению выражения (рис. 10.11). Для этого
окна не требуется вводить список
контролируемых выражений, а достаточно
выделить интересующее выражение в
тексте кода и вызвать окно
Quick Watch
одноименной кнопкой на панели инструментов
Debug,
Для
контроля выполняемых процедур используется
окно Call
Stack (Стек вызовов), в котором показаны
все вызванные и активные в данный момент
процедуры (рис. 10.12).
Из
окна Call Stack
можно перейти к коду программы (рис.
10.13), откуда был выполнен вызов выбранной
в списке процедуры, с помощью кнопки
Show
(Показать). На рис. 10.13 показано место
вызова процедуры, выбранной в окне
Call Stack (рис.
10.12).
Обработка
ошибок
Обработка
ошибок и неправильных действий
пользователя — обязательная составляющая
любого проекта. Для работы с ошибками
в Visual Basic 6 есть специальный оператор on
Error.
Существует
несколько вариантов синтаксиса этого
оператора. Первый
вариант
имеет
вид:
On
Error Go To StringLabel
где
StringLabel — метка оператора. Она должна
быть уникальной в пределах процедуры.
Метка
оператора — это любое текстовое значение,
начинающееся с буквы и завершающееся
двоеточием. В этом варианте синтаксиса
при возникновении ошибки программа
будет переходить к оператору, следующему
непосредственно за меткой StringLabel.
Например, приведенный ниже код выполняет
обработку ошибки:
On
Error Go To ErrorLabel
текст
кода
процедуры
ErrorLabel:
Call
ErrorProcedure ()
End
В
данном случае при возникновении ошибки
будет выполняться оператор call
ErrorProcedure о, вызывающий процедуру обработки
ошибки.
Для
игнорирования ошибки необходимо
использовать следующий вариант оператора
on Error:
On
Error Resume Next
Для
того чтобы отключить обработку ошибок
в какой-либо процедуре, оператор on Error
записывается в виде:
On
Error Go To 0
При
обработке ошибок хорошо бы выдать
сообщение о том, что это за ошибка. Для
этого предназначены перечисленные ниже
встроенные возможности Visual Basic 6:
-
служебная переменная
Err, которая содержит код ошибки; -
служебная переменная
Error, которая содержит текст системного
сообщения об ошибке; -
объект Err, который
содержит одновременно код и сообщение
об ошибке.
Для
выполнения действии программы после
обнаружения ошибки служит оператор
Resume, который имеет различные варианты
использования. Например:
-
Resume Next
При
этом выполняется оператор, следующий
за оператором с ошибкой.
-
On Resume NextLabel
где
NextLabel — метка оператора, который будет
выполняться после обработки ошибки.
Приведенный
ниже код обрабатывает ошибки:
On
Error Go To ErrorLabel
текст
кода
процедуры
ErrorLabel:
Call
ErrorProcedure ()
Resume
NextStatement
текст
кода
процедуры
NextStatement:
текст
кода
При
использовании этого варианта обработки
ошибки выполнение программы не
остановится, как в случае с предыдущим
кодом, а будут выполняться операторы
кода, расположенного после метки
продолжения работы.
Условная
компиляция:
Условную
компиляцию можно использовать для
отбора определенных частей кода для
компиляции, в то время как другие его
части исключаются. Например, при
необходимости написания инструкций
отладки, которые сравнивают скорость
выполнения одной и той же задачи при
различных подходах, либо при необходимости
локализации приложения для нескольких
языков. Инструкции условной компиляции
разработаны для выполнения во время
компиляции, а не во время выполнения.
Объявление
константы условной компиляции в коде
осуществляется при помощи директивы
#Const, а блоки кода для условной компиляции
обозначаются директивой #If…Then…#Else.
Например, для создания версий одного и
того же приложения на французском и
немецком языках из одного исходного
кода, необходимо внедрить зависимые от
платформы сегменты кода в инструкции
#If…Then с помощью определенных ранее
констант FrenchVersion и GermanVersion. Как это
сделать, показано в следующем примере:
Язык
Visual
Basic
#If
FrenchVersion
Then
‘ <code
specific to the French language version>.
#ElseIf
GermanVersion Then
‘ <code
specific to the German language version>.
#Else
‘
<code specific to other versions>.
#End
If
Если
для константы задается FrenchVersion значение
True во время компиляции, компилируется
условный код для французской версии.
Если установить для константы GermanVersion
значение True, компилятор использует
немецкую версию. Если ни одна константа
не имеет значения True, выполняется код
в последнем блоке Else.Примечание.
Автозавершение
не работает при редактировании кода и
использовании директив условной
компиляции, если код не является частью
текущей ветви.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
Обработка исключительных ситуаций. Методы и способы идентификации сбоев и ошибок.
Конструкция try..catch..finally
Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями. Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try…catch…finally.
try { } catch { } finally { }
При использовании блока try…catch..finally вначале пытаются выполниться инструкции в блоке try. Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally. И затем конструкция try..catch..finally завершает свою работу.
Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR (Common Language Runtime) начинает искать блок catch, который может обработать данное исключение. Если нужный блок catch найден, то он выполняется, и после его завершения выполняется блок finally.
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
Рассмотрим следующий пример:
class Program { static void Main(string[] args) { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы"); Console.Read(); } }
В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:
В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException, то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
И в этом случае единственное, что нам остается, это завершить выполнение программы.
Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try…catch…finally. Так, перепишем пример следующим образом:
class Program { static void Main(string[] args) { try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы"); Console.Read(); } }
В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль. И дойдя до строки
выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.
После блока catch будет выполняться блок finally.
Возникло исключение!
Блок finally
Конец программы
Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.
Следует отметить, что в этой конструкции обязателен блок try. При наличии блока catch мы можем опустить блок finally:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); }
И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); }
Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок catch, то исключение не будет обработано, и программа аварийно завершится.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть программа предусматривает ввод числа и вывод его квадрата:
static void Main(string[] args) { Console.WriteLine("Введите число"); int x = Int32.Parse(Console.ReadLine()); x *= x; Console.WriteLine("Квадрат числа: " + x); Console.Read(); }
Если пользователь введет не число, а строку, какие-то другие символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch, чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
static void Main(string[] args) { Console.WriteLine("Введите число"); int x; string input = Console.ReadLine(); if (Int32.TryParse(input, out x)) { x *= x; Console.WriteLine("Квадрат числа: " + x); } else { Console.WriteLine("Некорректный ввод"); } Console.Read(); }
Метод Int32.TryParse() возвращает true, если преобразование можно осуществить, и false — если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try…catch можно обработать возможную исключительную ситуацию.
С точки зрения производительности использование блоков try..catch более накладно, чем применение условных конструкций. Поэтому по возможности вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.
Блок catch и фильтры исключений
Определение блока catch
За обработку исключения отвечает блок catch, который может иметь следующие формы:
-
Обрабатывает любое исключение, которое возникло в блоке try. Выше уже был продемонстрирован пример подобного блока.
catch { // выполняемые инструкции }
-
Обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора catch.
catch (тип_исключения) { // выполняемые инструкции }
Например, обработаем только исключения типа DivideByZeroException:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); }
Однако если в блоке try возникнут исключения каких-то других типов, отличных от DivideByZeroException, то они не будут обработаны.
-
Обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора catch. А вся информация об исключении помещается в переменную данного типа.
catch (тип_исключения имя_переменной) { // выполняемые инструкции }
Например:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException ex) { Console.WriteLine($"Возникло исключение {ex.Message}"); }
Фактически этот случай аналогичен предыдущему за тем исключением, что здесь используется переменная. В данном случае в переменную ex, которая представляет тип DivideByZeroException, помещается информация о возникшем исключени. И с помощью свойства Message мы можем получить сообщение об ошибке.
Если нам не нужна информация об исключении, то переменную можно не использовать как в предыдущем случае.
Фильтры исключений
Фильтры исключений позволяют обрабатывать исключения в зависимости от определенных условий. Для их применения после выражения catch идет выражение when, после которого в скобках указывается условие:
В этом случае обработка исключения в блоке catch производится только в том случае, если условие в выражении when истинно. Например:
int x = 1; int y = 0; try { int result = x / y; } catch(DivideByZeroException) when (y==0 && x == 0) { Console.WriteLine("y не должен быть равен 0"); } catch(DivideByZeroException ex) { Console.WriteLine(ex.Message); }
В данном случае будет выброшено исключение, так как y=0. Здесь два блока catch, и оба они обрабатывают исключения типа DivideByZeroException, то есть по сути все исключения, генерируемые при делении на ноль. Но поскольку для первого блока указано условие y == 0 && x == 0, то оно не будет обрабатывать исключение — условие, указанное после оператора when возвращает false. Поэтому CLR будет дальше искать соответствующие блоки catch далее и для обработки исключения выберет второй блок catch. В итоге если мы уберем второй блок catch, то исключение вобще не будет обрабатываться.
Типы исключений. Класс Exception
Базовым для всех типов исключений является тип Exception. Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении.
-
InnerException: хранит информацию об исключении, которое послужило причиной текущего исключения
-
Message: хранит сообщение об исключении, текст ошибки
-
Source: хранит имя объекта или сборки, которое вызвало исключение
-
StackTrace: возвращает строковое представление стека вызывов, которые привели к возникновению исключения
-
TargetSite: возвращает метод, в котором и было вызвано исключение
Например, обработаем исключения типа Exception:
static void Main(string[] args) { try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch (Exception ex) { Console.WriteLine($"Исключение: {ex.Message}"); Console.WriteLine($"Метод: {ex.TargetSite}"); Console.WriteLine($"Трассировка стека: {ex.StackTrace}"); } Console.Read(); }
Однако так как тип Exception является базовым типом для всех исключений, то выражение catch (Exception ex) будет обрабатывать все исключения, которые могут возникнуть.
Но также есть более специализированные типы исключений, которые предназначены для обработки каких-то определенных видов исключений. Их довольно много, я приведу лишь некоторые:
-
DivideByZeroException: представляет исключение, которое генерируется при делении на ноль
-
ArgumentOutOfRangeException: генерируется, если значение аргумента находится вне диапазона допустимых значений
-
ArgumentException: генерируется, если в метод для параметра передается некорректное значение
-
IndexOutOfRangeException: генерируется, если индекс элемента массива или коллекции находится вне диапазона допустимых значений
-
InvalidCastException: генерируется при попытке произвести недопустимые преобразования типов
-
NullReferenceException: генерируется при попытке обращения к объекту, который равен null (то есть по сути неопределен)
И при необходимости мы можем разграничить обработку различных типов исключений, включив дополнительные блоки catch:
static void Main(string[] args) { try { int[] numbers = new int[4]; numbers[7] = 9; // IndexOutOfRangeException int x = 5; int y = x / 0; // DivideByZeroException Console.WriteLine($"Результат: {y}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException ex) { Console.WriteLine(ex.Message); } Console.Read(); }
В данном случае блоки catch обрабатывают исключения типов IndexOutOfRangeException, DivideByZeroException и Exception. Когда в блоке try возникнет исключение, то CLR будет искать нужный блок catch для обработки исключения. Так, в данном случае на строке
происходит обращение к 7-му элементу массива. Однако поскольку в массиве только 4 элемента, то мы получим исключение типа IndexOutOfRangeException. CLR найдет блок catch, который обрабатывает данное исключение, и передаст ему управление.
Следует отметить, что в данном случае в блоке try есть ситуация для генерации второго исключения — деление на ноль. Однако поскольку после генерации IndexOutOfRangeException управление переходит в соответствующий блок catch, то деление на ноль int y = x / 0 в принципе не будет выполняться, поэтому исключение типа DivideByZeroException никогда не будет сгенерировано.
Однако рассмотрим другую ситуацию:
static void Main(string[] args) { try { object obj = "you"; int num = (int)obj; // InvalidCastException Console.WriteLine($"Результат: {num}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException) { Console.WriteLine("Возникло исключение IndexOutOfRangeException"); } Console.Read(); }
В данном случае в блоке try генерируется исключение типа InvalidCastException, однако соответствующего блока catch для обработки данного исключения нет. Поэтому программа аварийно завершит свое выполнение.
Мы также можем определить для InvalidCastException свой блок catch, однако суть в том, что теоретически в коде могут быть сгенерированы сами различные типы исключений. А определять для всех типов исключений блоки catch, если обработка исключений однотипна, не имеет смысла. И в этом случае мы можем определить блок catch для базового типа Exception:
static void Main(string[] args) { try { object obj = "you"; int num = (int)obj; // InvalidCastException Console.WriteLine($"Результат: {num}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException) { Console.WriteLine("Возникло исключение IndexOutOfRangeException"); } catch (Exception ex) { Console.WriteLine($"Исключение: {ex.Message}"); } Console.Read(); }
И в данном случае блок catch (Exception ex){} будет обрабатывать все исключения кроме DivideByZeroException и IndexOutOfRangeException. При этом блоки catch для более общих, более базовых исключений следует помещать в конце — после блоков catch для более конкретный, специализированных типов. Так как CLR выбирает для обработки исключения первый блок catch, который соответствует типу сгенерированного исключения. Поэтому в данном случае сначала обрабатывается исключение DivideByZeroException и IndexOutOfRangeException, и только потом Exception (так как DivideByZeroException и IndexOutOfRangeException наследуется от класса Exception).
Создание классов исключений
Если нас не устраивают встроенные типы исключений, то мы можем создать свои типы. Базовым классом для всех исключений является класс Exception, соответственно для создания своих типов мы можем унаследовать данный класс.
Допустим, у нас в программе будет ограничение по возрасту:
class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (Exception ex) { Console.WriteLine($"Ошибка: {ex.Message}"); } Console.Read(); } } class Person { private int age; public string Name { get; set; } public int Age { get { return age; } set { if (value < 18) { throw new Exception("Лицам до 18 регистрация запрещена"); } else { age = value; } } } }
В классе Person при установке возраста происходит проверка, и если возраст меньше 18, то выбрасывается исключение. Класс Exception принимает в конструкторе в качестве параметра строку, которое затем передается в его свойство Message.
Но иногда удобнее использовать свои классы исключений. Например, в какой-то ситуации мы хотим обработать определенным образом только те исключения, которые относятся к классу Person. Для этих целей мы можем сделать специальный класс PersonException:
class PersonException : Exception { public PersonException(string message) : base(message) { } }
По сути класс кроме пустого конструктора ничего не имеет, и то в конструкторе мы просто обращаемся к конструктору базового класса Exception, передавая в него строку message. Но теперь мы можем изменить класс Person, чтобы он выбрасывал исключение именно этого типа и соответственно в основной программе обрабатывать это исключение:
class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (PersonException ex) { Console.WriteLine("Ошибка: " + ex.Message); } Console.Read(); } } class Person { private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException("Лицам до 18 регистрация запрещена"); else age = value; } } }
Однако необязательно наследовать свой класс исключений именно от типа Exception, можно взять какой-нибудь другой производный тип. Например, в данном случае мы можем взять тип ArgumentException, который представляет исключение, генерируемое в результате передачи аргументу метода некорректного значения:
class PersonException : ArgumentException { public PersonException(string message) : base(message) { } }
Каждый тип исключений может определять какие-то свои свойства. Например, в данном случае мы можем определить в классе свойство для хранения устанавливаемого значения:
class PersonException : ArgumentException { public int Value { get;} public PersonException(string message, int val) : base(message) { Value = val; } }
В конструкторе класса мы устанавливаем это свойство и при обработке исключения мы его можем получить:
class Person { public string Name { get; set; } private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException("Лицам до 18 регистрация запрещена", value); else age = value; } } } class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 13 }; } catch (PersonException ex) { Console.WriteLine($"Ошибка: {ex.Message}"); Console.WriteLine($"Некорректное значение: {ex.Value}"); } Console.Read(); } }
Поиск блока catch при обработке исключений
Если код, который вызывает исключение, не размещен в блоке try или помещен в конструкцию try..catch, которая не содержит соответствующего блока catch для обработки возникшего исключения, то система производит поиск соответствующего обработчика исключения в стеке вызовов.
Например, рассмотрим следующую программу:
using System; namespace HelloApp { class Program { static void Main(string[] args) { try { TestClass.Method1(); } catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); } Console.WriteLine("Конец метода Main"); Console.Read(); } } class TestClass { public static void Method1() { try { Method2(); } catch (IndexOutOfRangeException ex) { Console.WriteLine($"Catch в Method1 : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Method1"); } Console.WriteLine("Конец метода Method1"); } static void Method2() { try { int x = 8; int y = x / 0; } finally { Console.WriteLine("Блок finally в Method2"); } Console.WriteLine("Конец метода Method2"); } } }
В данном случае стек вызовов выглядит следующим образом: метод Main вызывает метод Method1, который, в свою очередь, вызывает метод Method2. И в методе Method2 генерируется исключение DivideByZeroException. Визуально стек вызовов можно представить следующим образом:
Внизу стека метод Main, с которого началось выполнение, и на самом верху метод Method2.
Что будет происходить в данном случае при генерации исключения?
-
Метод Main вызывает метод Method1, а тот вызывает метод Method2, в котором генерируется исключение DivideByZeroException.
-
Система видит, что код, который вызывал исключение, помещен в конструкцию try..catch
try { int x = 8; int y = x / 0; } finally { Console.WriteLine("Блок finally в Method2"); }
Система ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. Однако такого блока catch нет.
-
Система опускается в стеке вызовов в метод Method1, который вызывал Method2. Здесь вызов Method2 помещен в конструкцию try..catch
try { Method2(); } catch (IndexOutOfRangeException ex) { Console.WriteLine($"Catch в Method1 : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Method1"); }
Система также ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. Однако здесь также подобный блок catch отсутствует.
-
Система далее опускается в стеке вызовов в метод Main, который вызывал Method1. Здесь вызов Method1 помещен в конструкцию try..catch
try { TestClass.Method1(); } catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); }
Система снова ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. И в данном случае ткой блок найден.
-
Система наконец нашла нужный блок catch в методе Main, для обработки исключения, которое возникло в методе Method2 — то есть к начальному методу, где непосредственно возникло исключение. Но пока данный блок catch НЕ выполняется. Система поднимается обратно по стеку вызовов в самый верх в метод Method2 и выполняет в нем блок finally:
finally { Console.WriteLine("Блок finally в Method2"); }
-
Далее система возвращается по стеку вызовов вниз в метод Method1 и выполняет в нем блок finally:
finally { Console.WriteLine("Блок finally в Method1"); }
-
Затем система переходит по стеку вызовов вниз в метод Main и выполняет в нем найденный блок catch и последующий блок finally:
catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); }
-
Далее выполняется код, который идет в методе Main после конструкции try..catch:
Console.WriteLine("Конец метода Main");
Стоит отметить, что код, который идет после конструкции try…catch в методах Method1 и Method2, не выполняется, потому что обработчик исключения найден именно в методе Main.
Консольный вывод программы:
Блок finally в Method2
Блок finally в Method1
Catch в Main: Попытка деления на нуль.
Блок finally в Main
Конец метода Main
Генерация исключения и оператор throw
Обычно система сама генерирует исключения при определенных ситуациях, например, при делении числа на ноль. Но язык C# также позволяет генерировать исключения вручную с помощью оператора throw. То есть с помощью этого оператора мы сами можем создать исключение и вызвать его в процессе выполнения.
Например, в нашей программе происходит ввод строки, и мы хотим, чтобы, если длина строки будет больше 6 символов, возникало исключение:
static void Main(string[] args) { try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 6) { throw new Exception("Длина строки больше 6 символов"); } } catch (Exception e) { Console.WriteLine($"Ошибка: {e.Message}"); } Console.Read(); }
После оператора throw указывается объект исключения, через конструктор которого мы можем передать сообщение об ошибке. Естественно вместо типа Exception мы можем использовать объект любого другого типа исключений.
Затем в блоке catch сгенерированное нами исключение будет обработано.
Подобным образом мы можем генерировать исключения в любом месте программы. Но существует также и другая форма использования оператора throw, когда после данного оператора не указывается объект исключения. В подобном виде оператор throw может использоваться только в блоке catch:
try { try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 6) { throw new Exception("Длина строки больше 6 символов"); } } catch { Console.WriteLine("Возникло исключение"); throw; } } catch (Exception ex) { Console.WriteLine(ex.Message); }
В данном случае при вводе строки с длиной больше 6 символов возникнет исключение, которое будет обработано внутренним блоком catch. Однако поскольку в этом блоке используется оператор throw, то исключение будет передано дальше внешнему блоку catch.
Методы поиска ошибок в программах
Международный стандарт ANSI/IEEE-729-83 разделяет все ошибки в разработке программ на следующие типы.
Ошибка (error) — состояние программы, при котором выдаются неправильные результаты, причиной которых являются изъяны (flaw) в операторах программы или в технологическом процессе ее разработки, что приводит к неправильной интерпретации исходной информации, следовательно, и к неверному решению.
Дефект (fault) в программе — следствие ошибок разработчика на любом из этапов разработки, которая может содержаться в исходных или проектных спецификациях, текстах кодов программ, эксплуатационной документация и т.п. В процессе выполнения программы может быть обнаружен дефект или сбой.
Отказ (failure) — это отклонение программы от функционирования или невозможность программы выполнять функции, определенные требованиями и ограничениями, что рассматривается как событие, способствующее переходу программы в неработоспособное состояние из-за ошибок, скрытых в ней дефектов или сбоев в среде функционирования [7.6, 7.11]. Отказ может быть результатом следующих причин:
- ошибочная спецификация или пропущенное требование, означающее, что спецификация точно не отражает того, что предполагал пользователь;
- спецификация может содержать требование, которое невозможно выполнить на данной аппаратуре и программном обеспечении;
- проект программы может содержать ошибки (например, база данных спроектирована без средств защиты от несанкционированного доступа пользователя, а требуется защита);
- программа может быть неправильной, т.е. она выполняет несвойственный алгоритм или он реализован не полностью.
Таким образом, отказы, как правило, являются результатами одной или более ошибок в программе, а также наличия разного рода дефектов.
Ошибки на этапах процесса тестирования. Приведенные типы ошибок распределяются по этапам ЖЦ и им соответствуют такие источники их возникновения:
- непреднамеренное отклонение разработчиков от рабочих стандартов или планов реализации;
- спецификации функциональных и интерфейсных требований выполнены без соблюдения стандартов разработки, что приводит к нарушению функционирования программ;
- организации процесса разработки — несовершенная или недостаточное управление руководителем проекта ресурсами (человеческими, техническими, программными и т.д.) и вопросами тестирования и интеграции элементов проекта.
Рассмотрим процесс тестирования, исходя из рекомендаций стандарта ISO/IEC 12207, и приведем типы ошибок, которые обнаруживаются на каждом процессе ЖЦ.
Процесс разработки требований. При определении исходной концепции системы и исходных требований к системе возникают ошибки аналитиков при спецификации верхнего уровня системы и построении концептуальной модели предметной области.
Характерными ошибками этого процесса являются:
- неадекватность спецификации требований конечным пользователям;
- некорректность спецификации взаимодействия ПО со средой функционирования или с пользователями;
- несоответствие требований заказчика к отдельным и общим свойствам ПО;
- некорректность описания функциональных характеристик;
- необеспеченность инструментальными средствами всех аспектов реализации требований заказчика и др.
Процесс проектирования. Ошибки при проектировании компонентов могут возникать при описании алгоритмов, логики управления, структур данных, интерфейсов, логики моделирования потоков данных, форматов ввода-вывода и др. В основе этих ошибок лежат дефекты спецификаций аналитиков и недоработки проектировщиков. К ним относятся ошибки, связанные:
- с определением интерфейса пользователя со средой;
- с описанием функций (неадекватность целей и задач компонентов, которые обнаруживаются при проверке комплекса компонентов);
- с определением процесса обработки информации и взаимодействия между процессами (результат некорректного определения взаимосвязей компонентов и процессов);
- с некорректным заданием данных и их структур при описании отдельных компонентов и ПС в целом;
- с некорректным описанием алгоритмов модулей;
- с определением условий возникновения возможных ошибок в программе;
- с нарушением принятых для проекта стандартов и технологий.
Этап кодирования. На данном этапе возникают ошибки, которые являются результатом дефектов проектирования, ошибок программистов и менеджеров в процессе разработки и отладки системы. Причиной ошибок являются:
- бесконтрольность значений входных параметров, индексов массивов, параметров циклов, выходных результатов, деления на 0 и др.;
- неправильная обработка нерегулярных ситуаций при анализе кодов возврата от вызываемых подпрограмм, функций и др.;
- нарушение стандартов кодирования (плохие комментарии, нерациональное выделение модулей и компонент и др.);
- использование одного имени для обозначения разных объектов или разных имен одного объекта, плохая мнемоника имен;
- несогласованное внесение изменений в программу разными разработчиками и др.
Процесс тестирования. На этом процессе ошибки допускаются программистами и тестировщиками при выполнении технологии сборки и тестирования, выбора тестовых наборов и сценариев тестирования и др. Отказы в программном обеспечении, вызванные такого рода ошибками, должны выявляться, устраняться и не отражаться на статистике ошибок компонент и программного обеспечения в целом.
Процесс сопровождения. На процессе сопровождения обнаруживаются ошибки, причиной которых являются недоработки и дефекты эксплуатационной документации, недостаточные показатели модифицируемости и удобочитаемости, а также некомпетентность лиц, ответственных за сопровождение и/или усовершенствование ПО. В зависимости от сущности вносимых изменений на этом этапе могут возникать практически любые ошибки, аналогичные ранее перечисленным ошибкам на предыдущих этапах.
Все ошибки, которые возникают в программах, принято подразделять на следующие классы:
- логические и функциональные ошибки;
- ошибки вычислений и времени выполнения;
- ошибки вводавывода и манипулирования данными;
- ошибки интерфейсов;
- ошибки объема данных и др.
Логические ошибки являются причиной нарушения логики алгоритма, внутренней несогласованности переменных и операторов, а также правил программирования. Функциональные ошибки — следствие неправильно определенных функций, нарушения порядка их применения или отсутствия полноты их реализации и т.д.
Ошибки вычислений возникают по причине неточности исходных данных и реализованных формул, погрешностей методов, неправильного применения операций вычислений или операндов. Ошибки времени выполнения связаны с необеспечением требуемой скорости обработки запросов или времени восстановления программы.
Ошибки ввода-вывода и манипулирования данными являются следствием некачественной подготовки данных для выполнения программы, сбоев при занесении их в базы данных или при выборке из нее.
Ошибки интерфейса относятся к ошибкам взаимосвязи отдельных элементов друг с другом, что проявляется при передаче данных между ними, а также при взаимодействии со средой функционирования.
Ошибки объема относятся к данным и являются следствием того, что реализованные методы доступа и размеры баз данных не удовлетворяют реальным объемам информации системы или интенсивности их обработки.
Приведенные основные классы ошибок свойственны разным типам компонентов ПО и проявляются они в программах по разному. Так, при работе с БД возникают ошибки представления и манипулирования данными, логические ошибки в задании прикладных процедур обработки данных и др. В программах вычислительного характера преобладают ошибки вычислений, а в программах управления и обработки — логические и функциональные ошибки. В ПО, которое состоит из множества разноплановых программ, реализующих разные функции, могут содержаться ошибки разных типов. Ошибки интерфейсов и нарушение объема характерны для любого типа систем.
Анализ типов ошибок в программах является необходимым условием создания планов тестирования и методов тестирования для обеспечения правильности ПО.
На современном этапе развития средств поддержки разработки ПО (CASE-технологии, объектно-ориентированные методы и средства проектирования моделей и программ) проводится такое проектирование, при котором ПО защищается от наиболее типичных ошибок и тем самым предотвращается появление программных дефектов.
Связь ошибки с отказом. Наличие ошибки в программе, как правило, приводит к отказу ПО при его функционировании. Для анализа причинно-следственных связей «ошибкаотказ» выполняются следующие действия:
- идентификация изъянов в технологиях проектирования и программирования;
- взаимосвязь изъянов процесса проектирования и допускаемых человеком ошибок;
- классификация отказов, изъянов и возможных ошибок, а также дефектов на каждом этапе разработки;
- сопоставление ошибок человека, допускаемых на определенном процессе разработки, и дефектов в объекте, как следствий ошибок спецификации проекта, моделей программ;
- проверка и защита от ошибок на всех этапах ЖЦ, а также обнаружение дефектов на каждом этапе разработки;
- сопоставление дефектов и отказов в ПО для разработки системы взаимосвязей и методики локализации, сбора и анализа информации об отказах и дефектах;
- разработка подходов к процессам документирования и испытания ПО.
Конечная цель причинно-следственных связей «ошибка-отказ» заключается в определении методов и средств тестирования и обнаружения ошибок определенных классов, а также критериев завершения тестирования на множестве наборов данных; в определении путей совершенствования организации процесса разработки, тестирования и сопровождения ПО.
Приведем следующую классификацию типов отказов:
- аппаратный, при котором общесистемное ПО не работоспособно;
- информационный, вызванный ошибками во входных данных и передаче данных по каналам связи, а также при сбое устройств ввода (следствие аппаратных отказов);
- эргономический, вызванный ошибками оператора при его взаимодействии с машиной (этот отказ — вторичный отказ, может привести к информационному или функциональному отказам);
- программный, при наличии ошибок в компонентах и др.
Некоторые ошибки могут быть следствием недоработок при определении требований, проекта, генерации выходного кода или документации. С другой стороны, они порождаются в процессе разработки программы или при разработке интерфейсов отдельных элементов программы (нарушение порядка параметров, меньше или больше параметров и т.п.).
Источники ошибок. Ошибки могут быть порождены в процессе разработки проекта, компонентов, кода и документации. Как правило, они обнаруживаются при выполнении или сопровождении программного обеспечения в самых неожиданных и разных ее точках.
Некоторые ошибки в программе могут быть следствием недоработок при определении требований, проекта, генерации кода или документации. С другой стороны, ошибки порождаются в процессе разработки программы или интерфейсов ее элементов (например, при нарушении порядка задания параметров связи — меньше или больше, чем требуется и т.п.).
Причиной появления ошибок — непонимание требований заказчика; неточная спецификация требований в документах проекта и др. Это приводит к тому, что реализуются некоторые функции системы, которые будут работать не так, как предлагает заказчик. В связи с этим проводится совместное обсуждение заказчиком и разработчиком некоторых деталей требований для их уточнения.
Команда разработчиков системы может также изменить синтаксис и семантику описания системы. Однако некоторые ошибки могут быть не обнаружены (например, неправильно заданы индексы или значения переменных этих операторов).
Отладка программы — один их самых сложных этапов разработки программного обеспечения, требующий глубокого знания:
•специфики управления используемыми техническими средствами,
•операционной системы,
•среды и языка программирования,
•реализуемых процессов,
•природы и специфики различных ошибок,
•методик отладки и соответствующих программных средств.
Отладка — это процесс локализации и исправления ошибок, обнаруженных при тестировании программного обеспечения. Локализацией называют процесс определения оператора программы, выполнение которого вызвало нарушение нормального вычислительного процесса. Доя исправления ошибки необходимо определить ее причину, т. е. определить оператор или фрагмент, содержащие ошибку. Причины ошибок могут быть как очевидны, так и очень глубоко скрыты.
Вцелом сложность отладки обусловлена следующими причинами:
•требует от программиста глубоких знаний специфики управления используемыми техническими средствами, операционной системы, среды и языка программирования, реализуемых процессов, природы и специфики различных ошибок, методик отладки и соответствующих программных средств;
•психологически дискомфортна, так как необходимо искать собственные ошибки и, как правило, в условиях ограниченного времени;
•возможно взаимовлияние ошибок в разных частях программы, например, за счет затирания области памяти одного модуля другим из-за ошибок адресации;
•отсутствуют четко сформулированные методики отладки.
Всоответствии с этапом обработки, на котором проявляются ошибки, различают (рис. 10.1):
синтаксические ошибки — ошибки, фиксируемые компилятором (транслятором, интерпретатором) при выполнении синтаксического и частично семантического анализа программы; ошибки компоновки — ошибки, обнаруженные компоновщиком (редактором связей) при объединении модулей программы;
ошибки выполнения — ошибки, обнаруженные операционной системой, аппаратными средствами или пользователем при выполнении программы.
Синтаксические ошибки. Синтаксические ошибки относят к группе самых простых, так как синтаксис языка, как правило, строго формализован, и ошибки сопровождаются развернутым комментарием с указанием ее местоположения. Определение причин таких ошибок, как правило, труда не составляет, и даже при нечетком знании правил языка за несколько прогонов удается удалить все ошибки данного типа.
Следует иметь в виду, что чем лучше формализованы правила синтаксиса языка, тем больше ошибок из общего количества может обнаружить компилятор и, соответственно, меньше ошибок будет обнаруживаться на следующих этапах. В связи с этим говорят о языках программирования с защищенным синтаксисом и с незащищенным синтаксисом. К первым, безусловно, можно отнести Pascal, имеющий очень простой и четко определенный синтаксис, хорошо проверяемый при компиляции программы, ко вторым — Си со всеми его модификациями. Чего стоит хотя бы возможность выполнения присваивания в условном операторе в Си, например:
if (c = n) x = 0; /* в данном случае не проверятся равенство с и n, а выполняется присваивание с значения n, после чего результат операции сравнивается с нулем, если программист хотел выполнить не присваивание, а сравнение, то эта ошибка будет обнаружена только на этапе выполнения при получении результатов, отличающихся от ожидаемых */
Ошибки компоновки. Ошибки компоновки, как следует из названия, связаны с проблемами,
обнаруженными при разрешении внешних ссылок. Например, предусмотрено обращение к подпрограмме другого модуля, а при объединении модулей данная подпрограмма не найдена или не стыкуются списки параметров. В большинстве случаев ошибки такого рода также удается быстро локализовать и устранить.
Ошибки выполнения. К самой непредсказуемой группе относятся ошибки выполнения. Прежде всего они могут иметь разную природу, и соответственно по-разному проявляться. Часть ошибок обнаруживается и документируется операционной системой. Выделяют четыре способа проявления таких ошибок:
• появление сообщения об ошибке, зафиксированной схемами контроля выполнения машинных команд, например, переполнении разрядной сетки, ситуации «деление на ноль», нарушении адресации и т. п.;
•появление сообщения об ошибке, обнаруженной операционной системой, например, нарушении защиты памяти, попытке записи на устройства, защищенные от записи, отсутствии файла с заданным именем и т. п.;
•«зависание» компьютера, как простое, когда удается завершить программу без перезагрузки операционной системы, так и «тяжелое», когда для продолжения работы необходима перезагрузка;
•несовпадение полученных результатов с ожидаемыми.
Примечание. Отметим, что, если ошибки этапа выполнения обнаруживает пользователь, то в двух первых случаях, получив соответствующее сообщение, пользователь в зависимости от своего характера, степени необходимости и опыта работы за компьютером, либо попробует понять, что произошло, ища свою вину, либо обратится за помощью, либо постарается никогда больше не иметь дела с этим продуктом. При «зависании» компьютера пользователь может даже не сразу понять, что происходит что-то не то, хотя его печальный опыт и заставляет волноваться каждый раз, когда компьютер не выдает быстрой реакции на введенную команду, что также целесообразно иметь в виду. Также опасны могут быть ситуации, при которых пользователь получает неправильные результаты и использует их в своей работе.
Причины ошибок выполнения очень разнообразны, а потому и локализация может оказаться крайне сложной. Все возможные причины ошибок можно разделить на следующие группы:
•неверное определение исходных данных,
•логические ошибки,
•накопление погрешностей результатов вычислений (рис. 10.2).
Н е в е р н о е о п р е д е л е н и е и с х о д н ы х д а н н ы х происходит, если возникают любые ошибки при выполнении операций ввода-вывода: ошибки передачи, ошибки преобразования, ошибки перезаписи и ошибки данных. Причем использование специальных технических средств и программирование с защитой от ошибок (см.§ 2.7) позволяет обнаружить и предотвратить только часть этих ошибок, о чем безусловно не следует забывать.
Л о г и ч е с к и е о ш и б к и имеют разную природу. Так они могут следовать из ошибок, допущенных при проектировании, например, при выборе методов, разработке алгоритмов или определении структуры классов, а могут быть непосредственно внесены при кодировании модуля.
Кпоследней группе относят:
ошибки некорректного использования переменных, например, неудачный выбор типов данных, использование переменных до их инициализации, использование индексов, выходящих за границы определения массивов, нарушения соответствия типов данных при использовании явного или неявного переопределения типа данных, расположенных в памяти при использовании нетипизированных переменных, открытых массивов, объединений, динамической памяти, адресной арифметики и т. п.;
ошибки вычислений, например, некорректные вычисления над неарифметическими переменными, некорректное использование целочисленной арифметики, некорректное преобразование типов данных в процессе вычислений, ошибки, связанные с незнанием приоритетов выполнения операций для арифметических и логических выражений, и т. п.;
ошибки межмодульного интерфейса, например, игнорирование системных соглашений, нарушение типов и последовательности при передачи параметров, несоблюдение единства единиц измерения формальных и фактических параметров, нарушение области действия локальных и глобальных переменных;
другие ошибки кодирования, например, неправильная реализация логики программы при кодировании, игнорирование особенностей или ограничений конкретного языка программирования.
На к о п л е н и е п о г р е ш н о с т е й результатов числовых вычислений возникает, например, при некорректном отбрасывании дробных цифр чисел, некорректном использовании приближенных методов вычислений, игнорировании ограничения разрядной сетки представления вещественных чисел в ЭВМ и т. п.
Все указанные выше причины возникновения ошибок следует иметь в виду в процессе отладки. Кроме того, сложность отладки увеличивается также вследствие влияния следующих факторов:
опосредованного проявления ошибок;
возможности взаимовлияния ошибок;
возможности получения внешне одинаковых проявлений разных ошибок;
отсутствия повторяемости проявлений некоторых ошибок от запуска к запуску – так называемые стохастические ошибки;
возможности устранения внешних проявлений ошибок в исследуемой ситуации при внесении некоторых изменений в программу, например, при включении в программу диагностических фрагментов может аннулироваться или измениться внешнее проявление ошибок;
написания отдельных частей программы разными программистами.
Методы отладки программного обеспечения
Отладка программы в любом случае предполагает обдумывание и логическое осмысление всей имеющейся информации об ошибке. Большинство ошибок можно обнаружить по косвенным признакам посредством тщательного анализа текстов программ и результатов тестирования без получения дополнительной информации. При этом используют различные методы:
ручного тестирования;
индукции;
дедукции;
обратного прослеживания.
Метод ручного тестирования. Это — самый простой и естественный способ данной группы. При обнаружении ошибки необходимо выполнить тестируемую программу вручную, используя тестовый набор, при работе с которым была обнаружена ошибка.
Метод очень эффективен, но не применим для больших программ, программ со сложными вычислениями и в тех случаях, когда ошибка связана с неверным представлением программиста о выполнении некоторых операций.
Данный метод часто используют как составную часть других методов отладки.
Метод индукции. Метод основан на тщательном анализе симптомов ошибки, которые могут проявляться как неверные результаты вычислений или как сообщение об ошибке. Если компьютер просто «зависает», то фрагмент проявления ошибки вычисляют, исходя из последних полученных результатов и действий пользователя. Полученную таким образом информацию организуют и тщательно изучают, просматривая соответствующий фрагмент программы. В результате этих действий выдвигают гипотезы об ошибках, каждую из которых проверяют. Если гипотеза верна, то детализируют информацию об ошибке, иначе — выдвигают другую гипотезу. Последовательность выполнения отладки методом индукции показана на рис. 10.3 в виде схемы алгоритма.
Самый ответственный этап — выявление симптомов ошибки. Организуя данные об ошибке, целесообразно записать все, что известно о ее проявлениях, причем фиксируют, как ситуации, в которых фрагмент с ошибкой выполняется нормально, так и ситуации, в которых ошибка проявляется. Если в результате изучения данных никаких гипотез не появляется, то необходима дополнительная информация об ошибке. Дополнительную информацию можно получить, например, в результате выполнения схожих тестов.
В процессе доказательства пытаются выяснить, все ли проявления ошибки объясняет данная гипотеза, если не все, то либо гипотеза не верна, либо ошибок несколько.
Метод дедукции. По методу дедукции вначале формируют множество причин, которые могли бы вызвать данное проявление ошибки. Затем анализируя причины, исключают те, которые противоречат имеющимся данным. Если все причины исключены, то следует выполнить дополнительное тестирование исследуемого фрагмента. В противном случае наиболее вероятную гипотезу пытаются доказать. Если гипотеза объясняет полученные признаки ошибки, то ошибка найдена, иначе — проверяют следующую причину (рис. 10.4).
Метод обратного прослеживания. Для небольших программ эффективно применение метода обратного прослеживания. Начинают с точки вывода неправильного результата. Для этой точки строится гипотеза о значениях основных переменных, которые могли бы привести к получению имеющегося результата. Далее, исходя из этой гипотезы, делают предложения о значениях переменных в предыдущей точке. Процесс продолжают, пока не обнаружат причину ошибки.



