Рейтинг:  5 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда активна
 

Если Вы читали Ввод-Вывод данных в PascalABC.NET, то у Вас, возможно, возник вопрос: "А как же защититься от некорректного ввода данных пользователем, ведь пользователь введет некорректные данные то будет ошибка на этапе выполнения?". Да, будет ошибка, называемая исключением. Исключения - это ошибки, которые нельзя обойти при выполнении программы, и единственное, что может сделать программа это прекратить работу. Что касается моего мнения и это правильно. Давайте представим, что мы разрабатываем некоторое приложение, которому нужен некоторый файл, который прилагался к программе. Возьмем более конкретно: разработали игру, которой нужно несколько файлов, которые хранять материалы, связанные с игрой. Эту игру мы взяли и дали своему другу поиграть, а он взял и залез в файлы, начал их корректировать, да и вообще может быть взял и удалил их. В полне естественно, что Ваша программа в таком случае должна просто выдать ошибку и ни в коем случае не запускаться. Именно ошибки данного рода и называются исключениями.

В Паскале есть два типа ошибок: исключения и диагностические ошибки. Исключения - это ошибки программы, при возникновении которых программа не сможет выполнять дальнейший код программы, и программа будет вынуждена прекратить свою работу. Диагностические ошибки - это ошибки, возникаемые во время тестирования (!) (только во время тестирования программы разработчиком. В готовой версии программы этих ошибок нет) программы, сообщяющие разработчику о том, что в ходе работы программы произошел некий сбой, из-за которого дальнейшее выполнение программы может быть некорректным, и по желанию разработчика (!)( если программист захочет, то он может продолжить выполнение данной программы. Таким образом, диагностические ошибки - это только предупреждение о некорректности работы и оно остановить программу не может) компьютер продолжит выполнение программы.

Начнем изучать ошибки с диагностических ошибок. Такая ошибка вызывается с помощью комманды

 

 
assert(Predicate,Text_Error); 

 

где Predicate - это некий предикат, который нужно проверить

Text_Error - это текст, отображаемый в окне с ошибкой, если Predicate оказался ложным (поле не обязательное).

Вызов этих ошибок схож с условным оператором, только наоборот. По сути, Assert проверяет коректность данных и в случае их не корректности выдает ошибку. Естественно, в качестве предиката можно передавать логические константы и сложные логические выражения.

Assert

Вообще эти диагностические ошибки очень полезны при разработке каких-либо сложных программ, поскольку благодаря им можно в автоматическом режиме проверить программу на наличие ошибок (только в том случае, если мы знаем при каком значении получается тот или иной результат. Например «assert(odd(5)=true);», «assert(odd(4)=false);», «assert((5+5)=10);», «assert(MyData=0);» ...). Это, кстати, один из способов написания программы «Тестированием». Это, когда мы вначале пишем для нашей программы тесты, а затем пишем саму программу так, чтобы все тесты были пройдены без ошибок.

Да и еще диагностические ошибки никак нельзя обработать. Да и вполне понятно почему (поскольку в пользовательской версии нет Assert-ов, то и способов их обработок нет).

Для большей ясности, опять, рассмотрим пример с оценками из предыдущего урока. Только теперь будем обрабатывать введенные данные через Assert.

 

 
begin 

 
  var o:byte; 

 
  read(o); 

 
  assert((o>=2) and (o<=5),'некорректный ввод'); 

 
  case o of 

 
    2:writeln('неудовлетворительно'); 

 
    3:writeln('удовлетворительно'); 

 
    4:writeln('хорошо'); 

 
    5:writeln('отлично'); 

 
  end; 

 
end. 

 

Разберем этот код. Обьявляется переменная беззнаковая целочисленная «о», которую в 3-ей строке считывают с клавиатуры. Далее Assert проверяет данную переменную на условия: значение переменной «о» больше или равно 2 и ее значение меньше или равно 5. В случае, если это условие не выполняется, выдается диагностическое сообщение с текстом «некорректный ввод». Ну а далее Вы уже можете понять, что происходит. Вы, наверное, заметили, что у оператора «case» нет ветки «иначе». Да и она здесь не нужна, поскольку все остальные случаи мы уже проверили Assert-ом.

Исключения

Теперь перейдем к исключениям. Исключения вызываются очень просто:

 

 
raise new Exeption_Name('Text_Error'); 

 

где raise - это прерывание выполнения программы и вызов исключения

new - с этим словом мы познакомимся чуть позже. Пока запомните так.

Exeption_Name - это имя исключения. В Паскале есть очень большая иерархия исключений.

Exception - исключения

  ApplicationException - Все пользовательские исключения

  SystemException - системные исключения

    AccessViolationException - несанкционированный доступ к памяти

    ArgumentException - неверные аргументы

      ArgumentNullException - аргумент не существует

      ArgumentOutOfRangeException - аргумент вне диапазона

    ArithmeticException - арифметические исключения

       DivideByZeroException - целочисленное деление на 0

    IndexOutOfRangeException - индекс вне массива

    InvalidCastException - явное приведение к неправильному типу

    FormatException - ошибка ввода/ввывода

    NullReferenceException

    OutOfMemoryException - закончилась память ОС

    StackOverflowException - перполнен стек

    IOException - пространство имен System.IO

       FileNotFoundException - файл не найден

       EndOfStreamException - неожиданный конец файла

Это вся иерархия исключений. Для большинства случаев все это знать не обязательно, но, если Вы хотите обработать каждое исключение по отдельности, то Вам потребуется знать всю иерархию.

Raise

Для большинства случаев можно в качестве имени исключения писать Exception или System.Exception (что одно и то же).

Text_Error - текст, выдаваемый на экран (есть не во всех исключениях).

Внимание! Нужно учесть, что, в отличие от Assert, исключения ничего на корректность не проверяют. Как только программа дойдет до строки, начинающейся с «raise», то программа будет остановлена и будет выдана ошибка. Поэтому исключения лучше вызывать по одной из веток условного оператора (условный оператор проверяет на корректность какие-то данный и в случае их некорректности (по одной из веток) будет вызвано исключение. Для большей понятности рассмотрим пример с оценками, решение которого преведено выше, но с исключениями.

 

 
begin 

 
  var o:byte; 

 
  read(o); 

 
  if (not((o>=2) and (o<=5))) then 

 
    raise new System.Exception('некорректный ввод'); 

 
  case o of 

 
    2:writeln('неудовлетворительно'); 

 
    3:writeln('удовлетворительно'); 

 
    4:writeln('хорошо'); 

 
    5:writeln('отлично'); 

 
  end; 

 
end. 

 

Рассмотрим этот код. Опять обьявляется целочисленная беззнаковая переменная «о». Считывается ее значение с клавиатуры. А после этого идет проверка на корректность введенных данных и, если введенные данные не корректны (то есть если начение переменной «о» меньше 2 или больше 5 (вообще можно было упростить предикат до «if (o>5) or (o<2) then»)) то вызывается исключение. Которое останавливает ход выполнения программы и на экран выводится сообщение вида «Program1.pas(6) : Ошибка времени выполнения: некорректный ввод». А, если данные корректны, то программа продолжает свою работу (поскольку в данном примере ветки «иначе» нету, то программа просто продолжит выполнять свою работу). Ну а, что твариться далее, Вам должно быть понятно.

Обработка исключений

В Паскале есть две конструкции обработки ошибок. Это

 

 
try 

 
  Action1; 

 
except 

 
  Action2; 

 
end; 

 

и

 

 
try 

 
  Action1; 

 
finally 

 
  Action3; 

 
end; 

 

где Action1; - это набор (!)(в данном случае ни одно действие, а целый набор действий) действий, которые мы пытаемся выполнить

Action2 - набор действий, которые будут выполнены, если произошла ошибка/ возникло исключение при выполнении Action1

Action3 - это набор действий, который будет выполнен в любом случае в не зависимости от того: произошла ошибка в Action1 или же ее не было и код был обработан без ошибок.

Внимание! Эти две конструкции работают только на исключения! На диагностические ошибки они не работают.

Рассмотрим пример с оценками, только с добавлением блока Try-Except.

 

 
begin 

 
  try 

 
    var o:byte; 

 
    read(o); 

 
    if (not((o>=2) and (o<=5))) then 

 
      raise new System.Exception('некорректный ввод'); 

 
    case o of 

 
      2:writeln('неудовлетворительно'); 

 
      3:writeln('удовлетворительно'); 

 
      4:writeln('хорошо'); 

 
      5:writeln('отлично'); 

 
    end; 

 
  except 

 
    writeln('Во время обработки данных произошла ошибка'); 

 
  end; 

 
end. 

 

В данном коде весь код из предыдущего примера я полностью поместил в блок (все, что находится между try и except). Тем самым я сделал так, чтобы все ошибки, которые могут возникнуть при выполнении кода будут обработаны и исключения не возникнет. Если возникнет какая-либо ошибка (о они могут возникнуть при вводе данных (если пользователь введет данные другого типа (например слово «привет» или «-3.14», «с»... Вариантов некорректного ввода очень много)), если введена оценка из некорректного диапазона (например при вводе числа «10» должно быть исключение «некорректный ввод», а его не будет)) во время выполнения кода программа просто перейдет к выполнению кода, который находится в блоке except (все, что находится между except и end;). Так мы обработаем все ошибки и пользователь не узнает, что произошла какая-либо ошибка. Он будет считать, что программа сработала нормально.

Эта обработка ошибок очень часто нужна. Так можно пользователя заставить вводить данные то тех пор, пока он не введет корректные даннные.

 

 
begin 

 
  while true do 

 
  begin 

 
    try 

 
      var o:byte; 

 
      read(o); 

 
      if (not((o>=2) and (o<=5))) then 

 
        raise new System.Exception('некорректный ввод'); 

 
      case o of 

 
        2:writeln('неудовлетворительно'); 

 
        3:writeln('удовлетворительно'); 

 
        4:writeln('хорошо'); 

 
        5:writeln('отлично'); 

 
      end; 

 
      break; 

 
    except 

 
      writeln('Во время обработки данных произошла ошибка'); 

 
    end; 

 
  end; 

 
end. 

 

Пока можете не разбирать этот код. Я его привел просто, чтобы Вы попробовали запустить этот код. Но я обьясню как он работает. Здесь программа заставит пользователя вводить данные до тех пор, пока будут происходить ошибки при работе с введенными данными. Как только будет введены корректные данные, программа прекратит свою работу.

Теперь перейдем ко второму блоку обработки try - finally. Как я уже сказал, этот блок обработки исключений отличается тем, что то, что находится в блоке finally будет выполнено обязательно в не зависимости от того, была ли ошибка во времени выполнения программы или же ее не было. Рассмотрим пример с оценками, только с выводом на экран сообщения о том, была ли ошибка при обработки данных или же ее не было.

 

 
begin 

 
  var my:boolean:=false; 

 
  try 

 
    var o:byte; 

 
    read(o); 

 
    if (not((o>=2) and (o<=5))) then 

 
      raise new System.Exception('некорректный ввод'); 

 
    case o of 

 
      2:writeln('неудовлетворительно'); 

 
      3:writeln('удовлетворительно'); 

 
      4:writeln('хорошо'); 

 
      5:writeln('отлично'); 

 
    end; 

 
    my:=true; 

 
  finally 

 
    if my then 

 
      writeln('программа выполнена успешно') 

 
    else 

 
      writeln('программа выполнена с ошибкой'); 

 
  end; 

 
end. 

 

Разберем этот код. В самом начале я обьявил логическую переменную «my», в которой я храню данные о том, выполнена ли программа без ошибок. Поскольу я не знаю, будет ли выполнена программа без ошибок, присвоил этой переменной значение «лож» (то есть программа будет выполнена с ошибкой). В случае, если программа выполнится без ошибок, то этой переменной будет присвоено значение истины строкой «my:=true;». В блоке «finally» находится условный оператор, который по переменной «my» судит о том, была ли ошибка при выполнении программы. А все остальное Вы уже знаете.

Только в этом подходе к решению задач есть один существенный минус. Этот блок обработки данных не обрабатывает ошибки. Если они возникнут, то программа выполнит блок finally и выдаст ошибку времени выполнения:

f

программа выполнена с ошибкой

Ошибка времени выполнения: System.FormatException: Входная строка имела неверный формат.

Стек:

  в PABCSystem.PABCSystem.Read(Byte& x) в E:\Pascal\PascalABC.NET\LibSource\PABCSystem.pas:строка 3672

  в Program1.Program.$Main() в E:\Pascal\PABCWork.NET\Program1.pas:строка 20

  в Program1.Program.Main()

Таким образом, если вы планируете сделать что-то, что должно быть выведено в не зависимости от того, была ли ошибка, то лучше пользоваться несколькими блоками try.

 

 
begin 

 
  var my:boolean:=false; 

 
  try 

 
    try 

 
      var o:byte; 

 
      read(o); 

 
      if (not((o>=2) and (o<=5))) then 

 
        raise new System.Exception('некорректный ввод'); 

 
      case o of 

 
        2:writeln('неудовлетворительно'); 

 
        3:writeln('удовлетворительно'); 

 
        4:writeln('хорошо'); 

 
        5:writeln('отлично'); 

 
      end; 

 
      my:=true; 

 
    except 

 
      writeln('во время выполнения программы произошла ошибка'); 

 
    end; 

 
  finally 

 
    if my then 

 
      writeln('программа выполнена успешно') 

 
    else 

 
      writeln('программа выполнена с ошибкой'); 

 
  end; 

 
end. 

 

В данном примере, если произойдет ошибка, то на экран будет выведено сообщение вида:

0

во время выполнения программы произошла ошибка

программа выполнена с ошибкой

, а если программа выполнена без ошибок, то будет выведено сообщение типа:

5

отлично

программа выполнена успешно

.

Обработка каждой ошибки по отдельности

Вам не кажется странным, что, какая-либо ошибка не произошла (ошибка ввода, вычислительная ошибка ...), будет выполнен всегда один и тот же код. Это не совсем правильно, ведь мы не сообщаем пользователю, что произошло в программе и в чем была его ошибка. Для этого придумали ставить в блок except, можно сказать, уловный оператор выда

 

 
on e:Exception_Name do 

 

где Exception_Name - имя исключения

Этот «Условный оператор» проверяет: было ли вызвано исключение с именем Exception_Name. И, если оно было вызвано, тогда программа переходит к выполнению кода этого оператора.

Однако у этого «Условного оператора» есть один (для кого-то) существенный минус. Этот оператор нельзя помещать с другими операторами. То есть, если Вы решите каждую из возможных ошибок обрабатывать индивидуальным способом и после обработке (или до обработки) выводить на экран сообщеие вида «произошла ошибка во времени выполения», а затем выводить сообщение об ошибке, то у Вас с этим будут проблемы. В Паскале, если в блоке except Вы решите использовать оператор «on», то кроме этих операторов у Вас ничего не получиться напечатать.

Ну а теперь рассмотрим пример с оценками и индивидуальными обработчиками исключений.

 

 
begin 

 
  var my:boolean:=false; 

 
  try 

 
    try 

 
      var o:byte; 

 
      read(o); 

 
      if (not((o>=2) and (o<=5))) then 

 
        raise new System.DivideByZeroException('некорректный ввод'); 

 
      case o of 

 
        2:writeln('неудовлетворительно'); 

 
        3:writeln('удовлетворительно'); 

 
        4:writeln('хорошо'); 

 
        5:writeln('отлично'); 

 
      end; 

 
      my:=true; 

 
    finally 

 
      if (not my) then 

 
        writeln('во времени выполнения программы произошла ошибка'); 

 
    end; 

 
  except 

 
    on e:system.FormatException do 

 
      writeln('Error 01: Ошибка ввода'); 

 
    on e:system.DivideByZeroException do 

 
      writeln('Error 02: Введены некорректные данные'); 

 
    on e:system.Exception do 

 
      writeln('Error 03: Произошла неопознанная ошибка'); 

 
  end; 

 
end. 

 

Теперь каждая ошибка будет обработана по своему. (внимание! В этом коде я изменил имя исключения при некорректных данных на «DivideByZeroException» (ошибка деления на 0) поскольку, если неожиданно возникнет неопознанное исключение, то его можно будет опознать и устранить. О ошибки деления на 0 в данном коде вообще не может быть).

Теперь разберем этот код. Здесь два блока try. Один из них try-finally (следит за выполнением и, если программа не была выполнена полностью, то это значит, что была ошибка в ходе выполнения и нужно вывести сообщение «во времени выполнения программы произошла ошибка». А более точную информацию об ошибке выведет блок except (Да. На всякий случай скажу, что у одного try не может быть сразу два блока except u finally). А в блоке except несколько «условных операторов», которые проверяют появление исключения со своим именем и, если оно произошло, то выполняют свои действия.

Таким образом, мы научились обрабатывать ошибки ввода и любые другие ошибки, с которыми мы можем встретиться в программировании. Да, в полседнем примере я использовал исключение «DivideByZeroException» для обработки корректности ввода данных вместо своего исключения. В Паскале можно создавать свои собственные исключения, но об этом немного позже. У нас сейчас недостаточно опыта для этого.