文章目录
在发生错误时管理Caché 的行为称为错误处理或错误处理。错误处理执行以下一个或多个功能:
- 更正导致错误的条件
- 执行某些操作,允许在出现错误的情况下继续执行
- 转移执行流程
- 记录有关错误的信息
重要提示:CachéObjectScript错误处理的首选机制是try-catch机制。如果要使用传统的错误处理,则首选 Z T R A P 机 制 。 不 鼓 励 使 用 ZTRAP机制。不鼓励使用 ZTRAP机制。不鼓励使用ETRAP。
/// d ##class(PHA.TEST.ObjectScript).TestCatch()
ClassMethod TestCatch(b)
{
s t1 = $p($zts,",",2)
f i = 1:1:100000 d
.d ..TestCatch1()
s t2= $p($zts,",",2)
w t2-t1,!
s t1 = $p($zts,",",2)
f i = 1:1:100000 d
.d ..TestCatch2()
s t3= $p($zts,",",2)
w t3-t1,!
}
/// d ##class(PHA.TEST.ObjectScript).TestCatch1()
ClassMethod TestCatch1()
{
try{
s a =a/0
}catch{
s a= "异常!"
}
}
/// d ##class(PHA.TEST.ObjectScript).TestCatch2()
ClassMethod TestCatch2()
{
s $zt="ErrTestCatch2"
s a =a/0
ErrTestCatch2
s a= "异常!"
}
经过测试$zt
和try-catch
机制差别不大。
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestCatch()
7.393
7.577
TRY-CATCH
机制
Caché支持用于处理错误的try-catch
机制。使用此机制,可以建立分隔的代码块,每个代码块称为一个try
块;如果在try
块期间发生错误,控制权将传递给try
块的关联catch
块,该块包含用于处理异常的代码。TRY
块还可以包括抛出命令;这些命令中的每个命令都显式地从TRY
块内发出异常,并将执行转移到CATCH
块。
要以最基本的形式使用此机制,请在ObjectScript代码中包括一个try
块。如果此块内发生异常,则执行关联CATCH
块内的代码。try-catch
块的形式为:
TRY {
protected statements
} CATCH [ErrorHandle] {
error statements
}
further statements
- 其中:
try
命令标识用大括号括起来的ObjectScript代码语句块。Try
不接受任何参数。此代码块是用于结构化异常处理的受保护代码。如果try
块内发生异常,则Caché设置异常属性(oref.Name
、oref.Code
、oref.Data
和oref.Location
)、$ZERROR
和$ECODE
,然后将执行转移到由catch
命令标识的异常处理程序。这称为抛出异常。 - 受保护的语句是属于正常执行的ObjectScript语句。(这些可以包括对抛出命令的调用。此方案将在下一节中介绍。)
-
catch
命令定义异常处理程序,该异常处理程序是当try
块中发生异常时要执行的代码块 -
ErrorHandle
变量是异常对象的句柄。这可以是Caché为响应运行时错误而生成的异常对象,也可以是通过调用Throw
命令显式发出的异常对象(将在下一节中介绍)。 - 错误语句是在出现异常时调用的ObjectScript语句。
- 其他语句是ObjectScript语句,如果没有异常,则跟随受保护语句的执行;如果存在异常且控制传递到
CATCH
块之外,则跟随错误语句的执行。
根据受保护语句执行期间的事件,会发生以下事件之一:
- 如果没有发生错误,则继续执行出现在
CATCH
块之外的其他语句。 - 如果确实发生错误,则控制传递到
CATCH
块并执行ERROR
语句。然后,执行取决于CATCH
块的内容:- 如果
CATCH
块包含THROW
或GOTO
命令,则控制直接转到指定位置。 - 如果
CATCH
块不包含THROW
或GOTO
命令,则控制从CATCH
块传出,并继续执行其他语句。
- 如果
使用 THROW
和 TRY-CATCH
当发生运行时错误时,Caché会发出隐式异常。要发出显式异常,可以使用Throw
命令。Throw
命令将执行从TRY
块转移到CATCH
异常处理程序。
Throw
命令的语法为:
THROW expression
其中,expression 是从%Exception.AbstractException
类继承的类的实例,Caché提供该类用于异常处理。
带有抛出的try/catch
块的形式为:
TRY {
protected statements
THROW expression
protected statements
}
CATCH exception {
error statements
}
further statements
其中,Throw
命令显式地发出异常。try-catch
块的其他元素如上一节所述。
抛出的效果取决于抛出发生的位置和抛出的参数:
-
TRY
块内的引发会将控制权传递给CATCH
块。 -
CATCH
块内的抛出将控制向上传递到执行堆栈到下一个错误处理程序。如果异常是%Exception.SystemException
对象,则下一个错误处理程序可以是任何类型(Catch
或传统型);否则必须有一个Catch
来处理异常,否则将抛出<NOCATCH>
错误。
如果由于带有参数的引发而将控制传递到CATCH
块,则ErrorHandle
将包含该参数的值。如果由于系统错误而将控制传递到CATCH
块,则ErrorHandle
是%Exception.SystemException
对象。如果未指定ErrorHandle
,则不会指示控制传递到CATCH
块的原因。
例如,假设有将两个数字相除的代码:
div(num,div) public { //d div^PHA.MOB.TEST(1,2)
TRY {
SET ans=num/div
w ans,!
} CATCH errobj {
b
IF errobj.Name="<DIVIDE>" { SET ans=0 }
ELSE { THROW errobj }
}
QUIT ans
DHC-APP>d div^PHA.MOB.TEST(1,0)
b
^
<BREAK>div+5^PHA.MOB.TEST
DHC-APP 2d1>w errobj
1@%Exception.SystemException
DHC-APP 2d1>zw errobj
errobj=<OBJECT REFERENCE>[1@%Exception.SystemException]
+----------------- general information ---------------
| oref value: 1
| class name: %Exception.SystemException
| reference count: 2
+----------------- attribute values ------------------
| Code = 18 <Set>
| Data = "" <Set>
| InnerException = "" <Set>
| Location = "div+2^PHA.MOB.TEST" <Set>
| Name = "<DIVIDE>" <Set>
| iStack = $lb("^div+2^PHA.MOB.TEST^1","d^^^0")
+-----------------------------------------------------
如果发生被零除的错误,代码将专门设计为返回零作为结果。对于任何其他错误,抛出会将堆栈上的错误向上发送到下一个错误处理程序。
使用$$$ThrowOnError
和$$$ThrowStatus
宏
Caché提供用于异常处理的宏。调用时,这些宏会向CATCH
块抛出异常对象。
下面的示例在%Prepare()
方法返回错误状态时调用$$$ThrowOnError()
宏:
#Include %occStatus
ZNSPACE "SAMPLES"
TRY {
SET myquery = "SELECT TOP 5 Name,Hipness,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET status = tStatement.%Prepare(myquery)
$$$ThrowOnError(status)
WRITE "%Prepare succeeded",!
RETURN
}
CATCH sc {
WRITE "In Catch block",!
WRITE "error code: ",sc.Code,!
WRITE "error location: ",sc.Location,!
WRITE "error data:",$LISTGET(sc.Data,2),!
RETURN
}
error code: 5540
error location: zOnAsStatus+1^%Exception.SQL.1
error data: 相对应的表中没有找到字段 'HIPNESS'^ SELECT TOP ? Name , Hipness ,
下面的示例在测试%Prepare()
方法返回的错误状态值后调用$$$ThrowStatus
:
#Include %occStatus
ZNSPACE "SAMPLES"
TRY {
SET myquery = "SELECT TOP 5 Name,Hipness,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET status = tStatement.%Prepare(myquery)
IF ($System.Status.IsError(status)) {
WRITE "%Prepare failed",!
$$$ThrowStatus(status) }
ELSE {WRITE "%Prepare succeeded",!
RETURN }
}
CATCH sc {
WRITE "In Catch block",!
WRITE "error code: ",sc.Code,!
WRITE "error location: ",sc.Location,!
WRITE "error data:",$LISTGET(sc.Data,2),!
RETURN
}
%Prepare failed
In Catch block
error code: 5540
error location: zOnAsStatus+1^%Exception.SQL.1
error data: 相对应的表中没有找到字段 'HIPNESS'^ SELECT TOP ? Name , Hipness ,
使用%Exception.SystemException
和%Exception.AbstractException
类
Caché提供用于异常处理的%Exception.SystemException
和%Exception.AbstractException
类。%Exception.SystemException
继承自%Exception.AbstractException
类,用于系统错误。对于自定义错误,创建从%Exception.AbstractException
继承的类。%Exception.AbstractException
包含错误名称和发生位置等属性。
当在try
块中捕获到系统错误时,Caché会创建%Exception.SystemException
类的一个新实例,并将错误信息放入该实例中。引发自定义异常时,应用程序程序员负责使用错误信息填充对象。
异常对象具有以下属性:
- Name — 错误名称,例如
<UNDEFINED>
- Code — 错误代码
- Location — 错误的标签+偏移量^例程位置
- Data — 错误报告的任何额外数据,如导致错误的项的名称
try-catch
的其他注意事项
下面描述了使用try-catch
块时可能出现的情况。
在try-catch
块内退出
TRY
或CATCH
块中的QUIT
命令将控制从块传递到TRY-CATCH
之后的下一条语句。
TRY-CATCH
和执行堆栈
try
块不会在执行堆栈中引入新级别。这意味着它不是新命令的作用域边界。错误语句的执行级别与错误的级别相同。如果受保护语句中有DO
命令,并且DO
目标也在受保护语句中,这可能会导致意外结果。在这种情况下,$ESTACK
特殊变量可以提供有关相对执行级别的信息。
在传统错误处理中使用try-catch
TRY-CATCH
错误处理与在执行堆栈的不同级别使用的$ZTRAP
和$ETRAP
错误陷阱兼容。例外情况是$ZTRAP
和$ETRAP
不能在TRY
子句的受保护语句中使用。引发的用户定义错误仅限于try-catch
。使用ZTRAP
命令的用户定义的错误可以与任何类型的错误处理一起使用。
Caché类库中的许多方法通过%status
数据类型返回成功或失败信息。例如,用于保存%Persistent
对象实例的%Save()
方法返回一个%Status
值,指示该对象是否已保存。
- 成功执行方法会返回
%Status
为1
。 - 失败的方法执行返回
%status
作为包含错误状态以及一个或多个错误代码和文本消息的编码字符串。状态文本消息将根据的区域设置语言进行本地化。
可以使用%SYSTEM.Status
类方法检查和操作%Status
值。
Caché提供多个选项,用于以不同格式显示(写入)%STATUS
编码字符串。
在下面的示例中,由于myquery
文本中的错误,%Prepare
失败:“ZOP
”应该是“top
”。此错误由IsError()
方法检测,其他%SYSTEM.Status
方法显示错误代码和文本:
ZNSPACE "SAMPLES"
SET myquery = "SELECT ZOP 5 Name,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET status = tStatement.%Prepare(myquery)
IF ($System.Status.IsError(status)) {
WRITE "%Prepare failed",!
DO StatusError() }
ELSE {WRITE "%Prepare succeeded",!
RETURN }
StatusError()
WRITE "Error #",$System.Status.GetErrorCodes(status),!
WRITE $System.Status.GetOneStatusText(status,1),!
WRITE "end of error display"
QUIT
%Prepare failed
Error #5540
SQLCODE: -29消息: 相对应的表中没有找到字段 'ZOP'^ SELECT ZOP ?
end of error display
以下示例与上一个示例相同,不同之处在于%occStatus
包含文件的$$$ISERR()
宏会检测到状态错误。$$$ISERR()
(反之亦然,$$$ISOK()
)只需检查%status
是否=1:
#Include %occStatus
ZNSPACE "SAMPLES"
SET myquery = "SELECT ZOP 5 Name,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET status = tStatement.%Prepare(myquery)
IF $$$ISERR(status) {
WRITE "%Prepare failed",!
DO StatusError() }
ELSE {WRITE "%Prepare succeeded",!
RETURN}
StatusError()
WRITE "Error #",$System.Status.GetErrorCodes(status),!
WRITE $System.Status.GetOneStatusText(status,1),!
WRITE "end of error display"
QUIT
%Prepare failed
Error #5540
SQLCODE: -29消息: 相对应的表中没有找到字段 'ZOP'^ SELECT ZOP ?
end of error display
某些方法(如%New()
)会生成,但不返回%Status
。%New()
要么在成功时将OREF
返回给类的实例,要么在失败时返回空字符串。可以通过访问%objlasterror
系统变量来检索此类型方法的状态值,如下例所示。
SET session = ##class(%CSP.Session).%New()
IF session="" {
WRITE "session oref not created",!
WRITE "%New error is ",!,$System.Status.GetErrorText(%objlasterror),! }
ELSE {WRITE "session oref is ",session,! }
session oref not created
%New error is
错误 #5906: 缺少会话ID
创建%Status
错误
可以使用error()
方法从自己的方法调用系统定义的%Status
错误。可以指定与要返回的错误消息相对应的错误号。
WRITE "Here my method generates an error",!
SET status = $System.Status.Error(20)
WRITE $System.Status.GetErrorText(status),!
Here my method generates an error
错误 #20: 此文件已经存在
可以在返回的错误消息中包括%1
、%2
和%3
参数,如下例所示:
WRITE "Here my method generates an error",!
SET status = $System.Status.Error(214,"3","^fred","BedrockCode")
WRITE $System.Status.GetErrorText(status),!
Here my method generates an error
错误 #214: 有3个重复指针,第一个为指向BedrockCode的Global ^fred.
以将错误消息本地化为以首选语言显示。
SET status = $System.Status.Error(30)
WRITE "In English:",!
WRITE $System.Status.GetOneStatusText(status,1,"en"),!
WRITE "In French:",!
WRITE $System.Status.GetOneStatusText(status,1,"fr"),!
In English:
the system is not part of the cluster
In French:
le système ne fait pas partie du cluster
可以使用通用错误代码83和5001指定与任何常规错误消息不对应的自定义消息。
可以使用AppendStatus()
方法创建多条错误消息的列表。然后,可以使用GetOneErrorText()
或GetOneStatusText()
按错误消息在此列表中的位置检索各个错误消息:
/// d ##class(PHA.TEST.ObjectScript).TestCatch3()
ClassMethod TestCatch3()
{
SET st1 = $System.Status.Error(83,"my unique error")
SET st2 = $System.Status.Error(5001,"my unique error")
SET allstatus = $System.Status.AppendStatus(st1,st2)
WRITE "All together:",!
WRITE $System.Status.GetErrorText(allstatus),!!
WRITE "One by one",!
WRITE "First error format:",!
WRITE $System.Status.GetOneStatusText(allstatus,1),!
WRITE "Second error format:",!
WRITE $System.Status.GetOneStatusText(allstatus,2),!
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestCatch3()
All together:
错误 #83: 错误代码=my unique error
错误 #5001: my unique error
One by one
First error format:
错误代码=my unique error
Second error format:
my unique error
%SYSTEM.Error
%SYSTEM.Error
类是一般错误对象。它可以从%STATUS
错误、异常对象、$ZERROR
错误或SQLCODE
错误创建。可以使用%SYSTEM.Error
类方法将%Status
转换为异常,或将异常转换为%Status
。