Обработка исключений: различия между версиями

Материал из wikiru.visual-prolog.com

(Trap->try)
м
 
(не показано 9 промежуточных версий 2 участников)
Строка 1: Строка 1:
Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты для перехвата исключений, которые возникают в программах на Прологе. Это руководство объясняет:
Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты помогающие обрабатывать и распространять исключения, которые возникают в программах на Прологе. Это руководство объясняет:


*Как обрабатывать исключения;
*Как обрабатывать исключения;
Строка 7: Строка 7:
==Как перехватывать исключения==
==Как перехватывать исключения==


Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат '''file::readString''', который позволяет выполнить эту задачу. Однако, некоторые обстоятельтства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат '''file::readString''' не может выполнить задачу, он генерирует исключение.
Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат '''file::readString''', который позволяет выполнить эту задачу. Однако, некоторые обстоятельства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат '''file::readString''' не может выполнить задачу, он генерирует исключение.


Visual Prolog предлагает конструкцию '''try/catch''', которая перехватывает исключение и позволяет его обработать. Детальное описание этой конструкции можно найти в справке по Visual Prolog ('''Language Reference -> Exception Handling''').
Visual Prolog предлагает конструкцию '''try/catch''' (см. [[#trap vs try|Try вместо Trap]]), которая перехватывает исключение и позволяет его обработать. Детальное описание этой конструкции можно найти в справке по Visual Prolog ('''Language Reference -> Exception Handling''').


Соответственно, код для перехвата исключения будет выглядеть так:
Соответственно, код для перехвата исключения будет выглядеть так:
Строка 22: Строка 22:


<vip>class predicates
<vip>class predicates
     handleFileReadError : ( exception::traceID TraceId ) failure.
     handleFileReadError : (exception::traceID TraceId) failure.
clauses
clauses
     handleFileReadError(TraceId):-
     handleFileReadError(TraceId):-
Строка 32: Строка 32:
             % но параметр не может участвовать в сравнении с помощью ' = '.
             % но параметр не может участвовать в сравнении с помощью ' = '.
             % смотрите exceptionState::equals
             % смотрите exceptionState::equals
             _Kind, % исключени может быть возбуждено или продолжено
             _Kind, % исключение может быть возбуждено или продолжено
             ExtraInfo,
             ExtraInfo,
             _TraceId, % здесь идентификатор ошибки, но необходимости в проверке нет
             _TraceId, % здесь идентификатор ошибки, но необходимости в проверке нет
Строка 69: Строка 69:


==Как вызвать исключение==
==Как вызвать исключение==
Рассмотрим программу, которая проверяет размер заданного файла. PFC предлагает предикат file::getFileProperties, который возвращает размер заданного файла. Если файл имеет нулевой размер, то мы можем вызвать исключение с помощью предиката exception::raise. Необходимо при этом создать предикат, который будет характеризовать исключение. С этой целью мы создаем предикат myExceptionZeroFileSize. Код, вызывающий исключение, будет выглядеть так:
Рассмотрим программу, которая проверяет размер заданного файла. PFC предлагает предикат file::getFileProperties, который возвращает размер заданного файла. Если файл имеет нулевой размер, то мы можем вызвать исключение с помощью предиката exception::raise. Необходимо при этом создать предикат, который будет характеризовать исключение. С этой целью мы создаём предикат myExceptionZeroFileSize. Код, вызывающий исключение, будет выглядеть так:


<vip>clauses
<vip>clauses
Строка 92: Строка 92:
Первый параметр предиката exception::raise - предикат classInfo, который генерируется средой  IDE для каждого нового класса. Для генерируемого исключения неплохо было бы определить дополнительную информацию, относящуюся к данному исключению. В нашем случае полезно имя файла, поскольку нулевой  размер связан с этим заданным файлом.
Первый параметр предиката exception::raise - предикат classInfo, который генерируется средой  IDE для каждого нового класса. Для генерируемого исключения неплохо было бы определить дополнительную информацию, относящуюся к данному исключению. В нашем случае полезно имя файла, поскольку нулевой  размер связан с этим заданным файлом.


Предикат myExceptionZeroFileSize создается по шаблону:
Предикат myExceptionZeroFileSize создаётся по шаблону:


<vip>clauses
<vip>clauses
Строка 108: Строка 108:
==Продолжение другого исключения==
==Продолжение другого исключения==


Рассмотрим программу, помещенную в DLL, которая читает текстовый файл и его содержимое передает в возвращаемом параметре. Если происходит исключение, то DLL продолжает его и добавляет информацию о версии DLL. Для продолжения исключения код выглядит так:
Рассмотрим программу, помещённую в DLL, которая читает текстовый файл и его содержимое передаёт в возвращаемом параметре. Если происходит исключение, то DLL продолжает его и добавляет информацию о версии DLL. Для продолжения исключения код выглядит так:


<vip>constants
<vip>constants
Строка 114: Строка 114:
clauses
clauses
   loadFile(FileName) = FileContent :-
   loadFile(FileName) = FileContent :-
     trap
     try
      (
       FileContent = file::readString(FileName, _IsUnicode)
       FileContent = file::readString(FileName, _IsUnicode),
    catch ErrorCode do
      ErrorCode,
       exception::continue(ErrorCode, classInfo, continuedFromDll,
       exception::continue(ErrorCode, classInfo, continuedFromDll,
       [namedValue(version_Parameter,string(dllVersion))])
       [namedValue(version_Parameter,string(dllVersion))])
      ).</vip>
    end try.</vip>


Предикат continuedFromDll объявлен как:
Предикат continuedFromDll объявлен как:
Строка 127: Строка 126:
   continuedFromDll : core::exception as "_ContinuedFromDll@12"</vip>
   continuedFromDll : core::exception as "_ContinuedFromDll@12"</vip>


и этот предикат экспортируется из DLL. Имплементация предиката continuedFromDll создается по шаблону (аналогично предикату myExceptionZeroFileSizeabove):
и этот предикат экспортируется из DLL. Имплементация предиката continuedFromDll создаётся по шаблону (аналогично предикату myExceptionZeroFileSizeabove):


<vip>clauses
<vip>clauses
Строка 149: Строка 148:
     console::init(),
     console::init(),
     initExceptionState(exception::getExceptionState()),
     initExceptionState(exception::getExceptionState()),
     trap
     try
      (
       MyTxtFileContent = loadFile(myFileName)
       MyTxtFileContent = loadFile(myFileName),
    catch TraceID do
      TraceID,
       handleFileReadError(TraceID)
       handleFileReadError(TraceID)
      ),
    end try,
      !,
    !,
      stdIO::write("The content of ",myFileName,"is:\n",MyTxtFileContent).
    stdIO::write("The content of ",myFileName,"is:\n",MyTxtFileContent).
   run().
   run().


Строка 199: Строка 197:
Мы пытаемся здесь найти исключение '''continuedFromDll''' и мы вызываем продолжающееся предикатом исключение '''tryGetContinueException'''. Это ясно показывает, что, если исключение продолжающееся, то мы можем получить дополнительную информацию касательно самого исключения.
Мы пытаемся здесь найти исключение '''continuedFromDll''' и мы вызываем продолжающееся предикатом исключение '''tryGetContinueException'''. Это ясно показывает, что, если исключение продолжающееся, то мы можем получить дополнительную информацию касательно самого исключения.


Полностью пример представлен в проекте '''continueException\testContinuedException\testContinuedException.prj6'''. Необходимо построить сначала приложение '''continueException\continueException.prj6''' перед тем как вызывать на выполнение исполняемое приложение testContunedException.
Полностью пример представлен в проекте '''continueException\testContinuedException\testContinuedException.prj6'''. Необходимо построить сначала приложение '''continueException\continueException.prj6''' перед тем как вызывать на выполнение исполняемое приложение testContinuedException.
 
==trap vs try==
Начиная с версии 7.0 появилась конструкция '''try/catch/finally''', которая заменяет встроенные предикаты '''trap/3''' и '''finally/2'''.


==Ссылки==
==Ссылки==

Текущая версия на 12:31, 10 января 2008

Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты помогающие обрабатывать и распространять исключения, которые возникают в программах на Прологе. Это руководство объясняет:

  • Как обрабатывать исключения;
  • Как возбуждать ваши собственные исключения;
  • Как продолжать другое исключение.

Как перехватывать исключения

Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат file::readString, который позволяет выполнить эту задачу. Однако, некоторые обстоятельства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат file::readString не может выполнить задачу, он генерирует исключение.

Visual Prolog предлагает конструкцию try/catch (см. Try вместо Trap), которая перехватывает исключение и позволяет его обработать. Детальное описание этой конструкции можно найти в справке по Visual Prolog (Language Reference -> Exception Handling).

Соответственно, код для перехвата исключения будет выглядеть так:

try  
    MyTxtFileContent = file::readString(myFileName, _IsUnicode)
catch ErrorCode do
    handleFileReadError(ErrorCode)
end try,
...

Самое интересное здесь это - имплементация предиката handleFileReadError:

class predicates
    handleFileReadError : (exception::traceID TraceId) failure.
clauses
    handleFileReadError(TraceId):-
        Descriptor = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotcreate),
        !, % файл не может быт загружен
        exception::descriptor(_ErrorCode, % ErrorCode используется, если пользователь сам вызывает errorExit.
            _ClassInfo, % информация о классе, который возбудил исключение.
            _Exception, % на самом деле это fileSystem_api::cannotcreate,
            % но параметр не может участвовать в сравнении с помощью ' = '.
            % смотрите exceptionState::equals
            _Kind, % исключение может быть возбуждено или продолжено
            ExtraInfo,
            _TraceId, % здесь идентификатор ошибки, но необходимости в проверке нет
            _GMTTime, % время возникновения исключения
            ExceptionDescription,
            _ThreadId) = Descriptor,
        FileName = namedValue::mapLookUp(ExtraInfo,
        fileSystem_api::fileName_parameter, string("")),
        Reason = namedValue::mapLookUp(ExtraInfo,
        common_exception::errorDescription_parameter, string("")),
        stdIO::write("Невозможно загрузить файл по причине: ",ExceptionDescription,
            "\nИмя файла: ", FileName,
            "\nПричина: ", Reason ),
        fail.
    handleFileReadError(ErrorCode):-
        isDebugMode = true, % режим отладки
        !,
        exceptionDump::dumpToStdOutput(ErrorCode),
        % выводим на консоль, поскольку режим отладки
        fail.
    handleFileReadError(_ErrorCode):-
        % программа не может обработать исключение и не информирует о нем
        fail.

Когда предполагается исключение fileSystem_api::cannotcreate, предикат exception::tryGetDescriptor вызывается именно с этим параметром для перехвата и обработки такого исключения. В нашем случае используется вывод на консоль:

stdIO::write("Невозможно загрузить файл по причине: ", ExceptionDescription,
    "\nИмяФайла: ", FileName,
    "\nПричина: ", Reason ),

с объяснением причины исключения.

Обратите внимание, что нет необходимости в очистке следов исключения, поскольку вся необходимая информация сохраняется в TraceId. Этот факт значительно ускоряет работу программы.

Полный пример содержится в проекте catchException\catchException.prj6.

Как вызвать исключение

Рассмотрим программу, которая проверяет размер заданного файла. PFC предлагает предикат file::getFileProperties, который возвращает размер заданного файла. Если файл имеет нулевой размер, то мы можем вызвать исключение с помощью предиката exception::raise. Необходимо при этом создать предикат, который будет характеризовать исключение. С этой целью мы создаём предикат myExceptionZeroFileSize. Код, вызывающий исключение, будет выглядеть так:

clauses
  run():-
    console::init(),
    try
      file::getFileProperties(myFileName,_Attributes,Size,_Creation,_LastAccess,_LastChange)
    catch ErrorCode do
      handleFileGetPropertiesError(ErrorCode)
    end try,
 
    Size = unsigned64(0, 0), % file size is zero
    !,
    exception::raise
        (
        classInfo,
        myExceptionZeroFileSize,
        [namedValue(fileSystem_api::fileName_parameter, string(myFileName))]
        ).
  run().

Первый параметр предиката exception::raise - предикат classInfo, который генерируется средой IDE для каждого нового класса. Для генерируемого исключения неплохо было бы определить дополнительную информацию, относящуюся к данному исключению. В нашем случае полезно имя файла, поскольку нулевой размер связан с этим заданным файлом.

Предикат myExceptionZeroFileSize создаётся по шаблону:

clauses
  myExceptionZeroFileSize
    (
    classInfo,
    predicate_Name(),
    "Размер файла не может быть нулевым"
    ).

Здесь первый и второй параметры предикатов исключений всегда есть classInfo и predicate_Name() соответственно, а третий параметр содержит текстовое пояснение причины исключения.

Полностью пример представлен в проекте raiseException\raiseException.prj6.

Продолжение другого исключения

Рассмотрим программу, помещённую в DLL, которая читает текстовый файл и его содержимое передаёт в возвращаемом параметре. Если происходит исключение, то DLL продолжает его и добавляет информацию о версии DLL. Для продолжения исключения код выглядит так:

constants
  dllVersion = "1.20.0.0".
clauses
  loadFile(FileName) = FileContent :-
    try
       FileContent = file::readString(FileName, _IsUnicode)
    catch ErrorCode do
       exception::continue(ErrorCode, classInfo, continuedFromDll,
       [namedValue(version_Parameter,string(dllVersion))])
    end try.

Предикат continuedFromDll объявлен как:

predicates
  continuedFromDll : core::exception as "_ContinuedFromDll@12"

и этот предикат экспортируется из DLL. Имплементация предиката continuedFromDll создаётся по шаблону (аналогично предикату myExceptionZeroFileSizeabove):

clauses
  continuedFromDll
    (
    classInfo,
    predicate_Name(),
    "Exception continued from continueException.DLL"
    ).

При продолжении исключения предикат exception::continue добавляет информацию о версии DLL.

Полностью пример представлен в проекте continueException\continueException.prj6.

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

constants
  myFileName = "my.txt".
clauses
  run():-
    console::init(),
    initExceptionState(exception::getExceptionState()),
    try
      MyTxtFileContent = loadFile(myFileName)
    catch TraceID do
      handleFileReadError(TraceID)
    end try,
    !,
    stdIO::write("The content of ",myFileName,"is:\n",MyTxtFileContent).
  run().
 
class predicates
  handleFileReadError : ( exception::traceID TraceId )failure .
clauses
  handleFileReadError(TraceId):-
    DescriptorContinued = exception::tryGetDescriptor(TraceId, continuedFromDll),
    ErrorDescriptorInformation = exception::tryGetErrorDescriptorInformation(TraceId),
    ContinueException = ErrorDescriptorInformation:tryGetContinueException(),
    !, % ясно, что файл не загрузился
    exception::descriptor
      (_ErrorCodeContinued,
      _ClassInfoContinued,
      _ExceptionContinued,
      _KindContinued,
      ExtraInfoContinued,
      _,
      _GMTTimeContinued,
      ExceptionDescriptionContinued,
      _ThreadIdContinued) = DescriptorContinued,
      Version = namedValue::mapLookUp(ExtraInfoContinued, version_Parameter, string("")),
      stdIO::write("Продолженное исключение : ",ExceptionDescriptionContinued,"\nВерсия Dll: ", Version,"\n"),
      ExtraInfo = ContinueException:getExtraInfo(),
      ExceptionDescription = ContinueException:getExceptionDescription(),
      FileName = namedValue::mapLookUp(ExtraInfo,fileSystem_api::fileName_parameter, string("")),
      Reason = namedValue::mapLookUp(ExtraInfo,common_exception::errorDescription_parameter, string("")),
      stdIO::write
        (
        "Файл не загружен по причине: ",ExceptionDescription,
        "\nИмя файла: ", FileName,
        "\nПричина: ", Reason ),
      fail.
  handleFileReadError(TraceId):-
    isDebugMode = true, % режим отладки
    !,
    exceptionDump::dumpToStdOutput(TraceId),
    fail.
  handleFileReadError(_TraceId):-
    fail.

Мы пытаемся здесь найти исключение continuedFromDll и мы вызываем продолжающееся предикатом исключение tryGetContinueException. Это ясно показывает, что, если исключение продолжающееся, то мы можем получить дополнительную информацию касательно самого исключения.

Полностью пример представлен в проекте continueException\testContinuedException\testContinuedException.prj6. Необходимо построить сначала приложение continueException\continueException.prj6 перед тем как вызывать на выполнение исполняемое приложение testContinuedException.

trap vs try

Начиная с версии 7.0 появилась конструкция try/catch/finally, которая заменяет встроенные предикаты trap/3 и finally/2.

Ссылки