Обработка исключений: различия между версиями
м |
|||
(не показана 21 промежуточная версия 2 участников) | |||
Строка 1: | Строка 1: | ||
Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты | Исключение - это отклонение программы от маршрута "нормального" выполнения. Пакет exception из состава PFC содержит предикаты помогающие обрабатывать и распространять исключения, которые возникают в программах на Прологе. Это руководство объясняет: | ||
*Как обрабатывать исключения; | *Как обрабатывать исключения; | ||
Строка 7: | Строка 7: | ||
==Как перехватывать исключения== | ==Как перехватывать исключения== | ||
Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат '''file::readString''', который позволяет выполнить эту задачу. Однако, некоторые | Рассмотрим программу, которая читает текстовый файл и выводит его содержимое на консоль. PFC предлагает предикат '''file::readString''', который позволяет выполнить эту задачу. Однако, некоторые обстоятельства могут помешать эту задачу; например, заданное имя файла ссылается на несуществующий файл. Когда предикат '''file::readString''' не может выполнить задачу, он генерирует исключение. | ||
Visual Prolog предлагает | Visual Prolog предлагает конструкцию '''try/catch''' (см. [[#trap vs try|Try вместо Trap]]), которая перехватывает исключение и позволяет его обработать. Детальное описание этой конструкции можно найти в справке по Visual Prolog ('''Language Reference -> Exception Handling'''). | ||
Соответственно, код для перехвата исключения будет выглядеть так: | Соответственно, код для перехвата исключения будет выглядеть так: | ||
<vip> | <vip>try | ||
MyTxtFileContent = file::readString(myFileName, _IsUnicode) | |||
catch ErrorCode do | |||
handleFileReadError(ErrorCode) | |||
end try, | |||
...</vip> | |||
Самое интересное здесь это - имплементация предиката handleFileReadError: | |||
<vip>class predicates | <vip>class predicates | ||
handleFileReadError : ( exception::traceID TraceId ) failure. | handleFileReadError : (exception::traceID TraceId) failure. | ||
clauses | clauses | ||
handleFileReadError(TraceId):- | handleFileReadError(TraceId):- | ||
Descriptor = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotcreate), | Descriptor = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotcreate), | ||
!, % | !, % файл не может быт загружен | ||
exception::descriptor(_ErrorCode, % ErrorCode | exception::descriptor(_ErrorCode, % ErrorCode используется, если пользователь сам вызывает errorExit. | ||
_ClassInfo, % | _ClassInfo, % информация о классе, который возбудил исключение. | ||
_Exception, % | _Exception, % на самом деле это fileSystem_api::cannotcreate, | ||
% | % но параметр не может участвовать в сравнении с помощью ' = '. | ||
% | % смотрите exceptionState::equals | ||
_Kind, % | _Kind, % исключение может быть возбуждено или продолжено | ||
ExtraInfo, | ExtraInfo, | ||
_TraceId, % | _TraceId, % здесь идентификатор ошибки, но необходимости в проверке нет | ||
_GMTTime, % | _GMTTime, % время возникновения исключения | ||
ExceptionDescription, | ExceptionDescription, | ||
_ThreadId) = Descriptor, | _ThreadId) = Descriptor, | ||
Строка 38: | Строка 42: | ||
Reason = namedValue::mapLookUp(ExtraInfo, | Reason = namedValue::mapLookUp(ExtraInfo, | ||
common_exception::errorDescription_parameter, string("")), | common_exception::errorDescription_parameter, string("")), | ||
stdIO::write(" | stdIO::write("Невозможно загрузить файл по причине: ",ExceptionDescription, | ||
"\ | "\nИмя файла: ", FileName, | ||
"\ | "\nПричина: ", Reason ), | ||
fail. | fail. | ||
handleFileReadError(ErrorCode):- | handleFileReadError(ErrorCode):- | ||
isDebugMode = true, | isDebugMode = true, % режим отладки | ||
!, | !, | ||
exceptionDump::dumpToStdOutput(ErrorCode), | exceptionDump::dumpToStdOutput(ErrorCode), | ||
% | % выводим на консоль, поскольку режим отладки | ||
fail. | fail. | ||
handleFileReadError(_ErrorCode):- | handleFileReadError(_ErrorCode):- | ||
% | % программа не может обработать исключение и не информирует о нем | ||
fail.</vip> | fail.</vip> | ||
Когда предполагается исключение '''fileSystem_api::cannotcreate''', предикат '''exception::tryGetDescriptor''' вызывается именно с этим параметром для перехвата и обработки такого исключения. В нашем случае используется вывод на консоль: | |||
<vip>stdIO::write(" | <vip>stdIO::write("Невозможно загрузить файл по причине: ", ExceptionDescription, | ||
"\ | "\nИмяФайла: ", FileName, | ||
"\ | "\nПричина: ", Reason ),</vip> | ||
с объяснением причины исключения. | |||
Обратите внимание, что нет необходимости в очистке следов исключения, поскольку вся необходимая информация сохраняется в TraceId. Этот факт значительно ускоряет работу программы. | |||
Полный пример содержится в проекте '''catchException\catchException.prj6'''. | |||
== | ==Как вызвать исключение== | ||
Рассмотрим программу, которая проверяет размер заданного файла. PFC предлагает предикат file::getFileProperties, который возвращает размер заданного файла. Если файл имеет нулевой размер, то мы можем вызвать исключение с помощью предиката exception::raise. Необходимо при этом создать предикат, который будет характеризовать исключение. С этой целью мы создаём предикат myExceptionZeroFileSize. Код, вызывающий исключение, будет выглядеть так: | |||
<vip>clauses | <vip>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().</vip> | |||
Первый параметр предиката exception::raise - предикат classInfo, который генерируется средой IDE для каждого нового класса. Для генерируемого исключения неплохо было бы определить дополнительную информацию, относящуюся к данному исключению. В нашем случае полезно имя файла, поскольку нулевой размер связан с этим заданным файлом. | |||
Предикат myExceptionZeroFileSize создаётся по шаблону: | |||
<vip>clauses | <vip>clauses | ||
myExceptionZeroFileSize(classInfo, predicate_Name(), | myExceptionZeroFileSize | ||
" | ( | ||
classInfo, | |||
predicate_Name(), | |||
"Размер файла не может быть нулевым" | |||
).</vip> | |||
Здесь первый и второй параметры предикатов исключений всегда есть classInfo и predicate_Name() соответственно, а третий параметр содержит текстовое пояснение причины исключения. | |||
Полностью пример представлен в проекте '''raiseException\raiseException.prj6'''. | |||
== | ==Продолжение другого исключения== | ||
Рассмотрим программу, помещённую в DLL, которая читает текстовый файл и его содержимое передаёт в возвращаемом параметре. Если происходит исключение, то DLL продолжает его и добавляет информацию о версии DLL. Для продолжения исключения код выглядит так: | |||
<vip>constants | <vip>constants | ||
dllVersion = "1.20.0.0". | |||
clauses | 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.</vip> | |||
Предикат continuedFromDll объявлен как: | |||
<vip>predicates | <vip>predicates | ||
continuedFromDll : core::exception as "_ContinuedFromDll@12"</vip> | |||
и этот предикат экспортируется из DLL. Имплементация предиката continuedFromDll создаётся по шаблону (аналогично предикату myExceptionZeroFileSizeabove): | |||
<vip>clauses | <vip>clauses | ||
continuedFromDll | |||
( | |||
classInfo, | |||
predicate_Name(), | |||
"Exception continued from continueException.DLL" | |||
).</vip> | |||
При продолжении исключения предикат exception::continue добавляет информацию о версии DLL. | |||
Полностью пример представлен в проекте '''continueException\continueException.prj6'''. | |||
Посмотрим теперь, как перехватывать такого рода исключения. Код в принципе похож на обсуждавшийся выше, поэтому мы сосредоточимся на различиях. | |||
<vip>constants | <vip>constants | ||
myFileName = "my.txt". | |||
clauses | 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 | class predicates | ||
handleFileReadError : ( exception::traceID TraceId )failure . | |||
clauses | 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.</vip> | |||
Мы пытаемся здесь найти исключение '''continuedFromDll''' и мы вызываем продолжающееся предикатом исключение '''tryGetContinueException'''. Это ясно показывает, что, если исключение продолжающееся, то мы можем получить дополнительную информацию касательно самого исключения. | |||
Полностью пример представлен в проекте '''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.