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

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

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

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

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

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

Visual Prolog предлагает встроенный предикат trap/3, который перехватывает исключение и позволяет его обработать. Детальное описание предиката trap/3 можно найти в справке по Visual Prolog (Language Reference -> Built-in Domains, Predicates and Constants-> Predicates ->trap).

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

trap
  (
  MyTxtFileContent = file::readString(myFileName, _IsUnicode),
  ErrorCode,
  handleFileReadError(ErrorCode)
  ),
  ...

Самое интересное здесь это - имплементация предиката 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(),
    trap
      (
      file::getFileProperties(myFileName,_Attributes,Size,_Creation,_LastAccess,_LastChange),
      ErrorCode,
      handleFileGetPropertiesError(ErrorCode)
      ),
      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 :-
    trap
      (
       FileContent = file::readString(FileName, _IsUnicode),
       ErrorCode,
       exception::continue(ErrorCode, classInfo, continuedFromDll,
       [namedValue(version_Parameter,string(dllVersion))])
      ).

Предикат 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()),
    trap
      (
      MyTxtFileContent = loadFile(myFileName),
      TraceID,
      handleFileReadError(TraceID)
      ),
      !,
      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(),
    !, %file cannot be loaded
    exception::descriptor
      (_ErrorCodeContinued,
      _ClassInfoContinued,
      _ExceptionContinued,
      _KindContinued,
      ExtraInfoContinued,
      _,
      _GMTTimeContinued,
      ExceptionDescriptionContinued,
      _ThreadIdContinued) = DescriptorContinued,
      Version = namedValue::mapLookUp(ExtraInfoContinued, version_Parameter, string("")),
      stdIO::write("Exception continued : ",ExceptionDescriptionContinued,"\nDllVersion: ", 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
        (
        "Cannot load file due to: ",ExceptionDescription,
        "\nFile Name: ", FileName,
        "\nReason: ", Reason ),
      fail.
  handleFileReadError(TraceId):-
    isDebugMode = true,
    !,
    exceptionDump::dumpToStdOutput(TraceId),
    fail.
  handleFileReadError(_TraceId):-
    fail.

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

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

Ссылки