文章目录
传统错误处理的工作原理
对于传统的错误处理,Caché提供了该功能,以便应用程序可以有一个错误处理程序。错误处理程序处理应用程序运行时可能发生的任何错误。特殊变量指定发生错误时要执行的ObjectScript命令。这些命令可以直接处理错误,也可以调用例程来处理它。
要设置错误处理程序,基本流程如下:
- 创建一个或多个例程以执行错误处理。编写代码以执行错误处理。这可以是整个应用程序的通用代码,也可以是特定错误条件的特定处理。这允许对应用程序的每个特定部分执行自定义错误处理。
- 在应用程序中建立一个或多个错误处理程序,每个错误处理程序都使用特定的适当错误处理程序。
如果发生错误且尚未建立错误处理程序,则行为取决于Caché 会话的启动方式:
- 如果在终端提示符下登录到Caché,并且没有设置错误陷阱,则Caché会在主设备上显示一条错误消息,并返回终端提示符,程序堆栈完好无损。程序员稍后可以恢复该程序的执行。
- 如果在应用程序模式下调用了Caché,但尚未设置错误陷阱,则Caché会在主设备上显示错误消息,并执行HALT命令。
内部错误捕获行为
要充分利用Caché错误处理和围绕$ZTRAP
特殊变量(以及$ETRAP
)的作用域问题,了解Caché如何将控制从一个例程转移到另一个例程是很有帮助的。
每次发生以下任何一种情况时,Caché都会构建一个称为“上下文帧”的数据结构:
- 一个例程使用
DO
命令调用另一个例程。(这种框架也称为“DO
框架”。) -
XECUTE
命令参数会导致执行ObjectScript代码。(这种框架也称为“XECUTE
框架”。) - 执行用户定义的函数。
框架构建在调用堆栈上,调用堆栈是进程地址空间中的私有数据结构之一。
Caché将以下元素存储在例程的帧中:
-
$ZTRAP
特殊变量的值(如果有) -
$ETRAP
特殊变量的值(如果有) - 从子例程返回的位置
当例程A
使用DO^B
调用例程B时,Caché会在调用堆栈上构建一个DO
帧,以保留A
的上下文。
当例程B
调用例程C时,Caché会向调用堆栈添加一个DO
帧,以保留B
的上下文,依此类推。
调用堆栈上的帧
如果在终端提示符下使用DO
命令调用上图中的例程A
,则在调用堆栈的底部存在未在图中描述的额外DO
帧。
当前上下文级别
可以使用以下命令返回有关当前上下文级别的信息:
-
$STACK
特殊变量包含当前相对堆栈级别。 -
$ESTACK
特殊变量包含当前堆栈级别。可以在任何用户指定的点处将其初始化为0(零级)。 -
$STACK
函数返回有关当前上下文和已保存在调用堆栈上的上下文的信息
$STACK
特殊变量
$STACK
特殊变量包含当前保存在进程的调用堆栈上的帧数。$STACK
值实质上是当前执行的上下文的上下文级别编号(从零开始)。因此,当启Caché 存映像时,但在处理任何命令之前,$STACK
的值为0。
/// d ##class(PHA.TEST.ObjectScript).TestSTRACK()
ClassMethod TestSTRACK()
{
STA
WRITE !,"Context level in routine STA = ",$STACK
DO A
WRITE !,"Context level after routine A = ",$STACK
QUIT
A
WRITE !,"Context level in routine A = ",$STACK
DO B
WRITE !, "Context level after routine B = ",$STACK
QUIT
B
WRITE !,"Context level in routine B = ",$STACK
XECUTE "WRITE !,""Context level in XECUTE = "",$STACK"
WRITE !,"Context level after XECUTE = ",$STACK
QUIT
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestSTRACK()
Context level in routine STA = 1
Context level in routine A = 2
Context level in routine B = 3
Context level in XECUTE = 4
Context level after XECUTE = 3
Context level after routine B = 2
Context level after routine A = 1
$ESTACK
特殊变量
$ESTACK
特殊变量类似于$STACK
特殊变量,但在错误处理中更有用,因为可以使用新命令将其重置为0(并保存其先前的值)。因此,进程可以重置特定上下文中的$ESTACK
,以将其标记为$ESTACK
0级上下文。如果出现错误,错误处理程序可以测试$ESTACK
的值,以将调用堆栈展开回该上下文。
/// d ##class(PHA.TEST.ObjectScript).TestESTACK()
ClassMethod TestESTACK()
{
Main
WRITE !,"Initial: $STACK=",$STACK," $ESTACK=",$ESTACK
DO Sub1
WRITE !,"Return: $STACK=",$STACK," $ESTACK=",$ESTACK
QUIT
Sub1
WRITE !,"Sub1Call: $STACK=",$STACK," $ESTACK=",$ESTACK
NEW $ESTACK
WRITE !,"Sub1NEW: $STACK=",$STACK," $ESTACK=",$ESTACK
DO Sub2
QUIT
Sub2
WRITE !,"Sub2Call: $STACK=",$STACK," $ESTACK=",$ESTACK
QUIT
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestESTACK()
Initial: $STACK=1 $ESTACK=1
Sub1Call: $STACK=2 $ESTACK=2
Sub1NEW: $STACK=2 $ESTACK=0
Sub2Call: $STACK=3 $ESTACK=1
Return: $STACK=1 $ESTACK=1
$STACK
函数
$STACK
函数返回有关当前上下文和已保存在调用堆栈上的上下文的信息。对于每个上下文,$STACK
函数提供以下信息:
- 上下文类型(
DO
、XECUTE
或用户定义函数) - 上下文中处理的最后一个命令的条目引用和命令编号
- 包含上下文中处理的最后一个命令的源例程行或
XECUTE
字符串 - 上下文中发生的任何错误的
$ECODE
值(仅当$ECODE
为非空时在错误处理期间可用)
发生错误时,所有上下文信息都会立即保存在您的进程错误堆栈中。然后,$STACK
函数可以访问上下文信息,直到错误处理程序清除$ECODE
的值。换句话说,当$ECODE
的值非空时,$STACK
函数返回有关保存在错误堆栈上的上下文的信息,而不是返回相同指定上下文级别的活动上下文的信息。
当发生错误并且错误堆栈已经存在时,除非错误堆栈上的上下文级别已经存在有关另一个错误的信息,否则 Caché 存会在发生错误的上下文级别记录有关新错误的信息。在这种情况下,信息被放在错误堆栈的下一级(不管那里可能已经记录了哪些信息)。
因此,根据新错误的上下文级别,错误堆栈可以扩展(添加一个或多个上下文级别),或者可以重写现有错误堆栈上下文级别的信息以容纳关于新错误的信息。
请记住,可以通过清除$ECODE
特殊变量来清除进程错误堆栈。
错误码
发生错误时,Caché会将$ZERROR
和$ECODE
特殊变量设置为描述错误的值。
$ZERROR
值
Caché将$ZERROR
设置为包含以下内容的字符串:
- 用尖括号括起来的Caché错误代码。
- 发生错误的标签、偏移量和例程名称。
- (对于某些错误):其他信息,如导致错误的项的名称。
%Exception.SystemException
类的AsSystemError()
方法以与$ZERROR
相同的格式返回相同的值。
以下示例显示了Caché遇到错误时设置$ZERROR
的消息类型。在下面的示例中,在例程MyTest
的标签PrintResult
的行偏移量2处调用未定义的局部变量abc
。$ZERROR
包含:
<UNDEFINED>PrintResult+2^MyTest *abc
在行偏移量3处调用不存在的类时发生以下错误:
<CLASS DOES NOT EXIST>PrintResult+3^MyTest *%SYSTEM.XXQL
在行偏移量4处调用现有类的不存在的方法时发生以下错误:
<METHOD DOES NOT EXIST>PrintResult+4^MyTest *BadMethod,%SYSTEM.SQL
还可以将特殊变量$ZERROR
显式设置为最多128个字符的任意字符串;例如:
SET $ZERROR="Any String"
$ZERROR
值旨在错误后立即使用。因为$ZERROR
值可能不会在例程调用中保留,所以希望保留
Z
E
R
R
O
R
值
以
供
以
后
使
用
的
用
户
应
该
将
其
复
制
到
变
量
中
。
强
烈
建
议
用
户
在
使
用
后
立
即
将
‘
ZERROR值以供以后使用的用户应该将其复制到变量中。强烈建议用户在使用后立即将`
ZERROR值以供以后使用的用户应该将其复制到变量中。强烈建议用户在使用后立即将‘ZERROR设置为空字符串(
“”`)。
$ECODE
值
发生错误时,Caché会将$ECODE
设置为逗号括起来的字符串的值,该字符串包含与错误对应的ANSI标准错误代码。例如,当引用未定义的全局变量时,Caché会将$ECODE
集设置为以下字符串:
,M7,
如果错误没有对应的ANSI标准错误代码,则Caché会将$ECODE
设置为逗号括起来的字符串的值,该字符串包含前面带有字母Z
的Caché错误代码。如果进程已耗尽其符号表空间,则Caché会将错误代码<store>
放在$ZERROR
特殊变量中,并将$ECODE
设置为以下字符串:
,ZSTORE,
错误发生后,错误处理程序可以通过检查$ZERROR
特殊变量或$ECODE
特殊变量的值来测试特定的错误代码。
注意:错误处理程序应该检查$ZERROR
而不是$ECODE
特殊变量以查找特定错误。
使用$ZTRAP
处理错误。
要使用$ZTRAP
处理错误,可以将$ZTRAP
特殊变量设置为一个位置,指定为带引号的字符串。将$ZTRAP
特殊变量设置为条目引用,该引用指定在发生错误时将控制权转移到的位置。然后在该位置编写$ZTRAP
代码。
将$ZTRAP
设置为非空值时,它优先于任何现有的$ETRAP
错误处理程序。Caché隐式执行NEW $ETRAP
命令,并将$ETRAP
设置为等于“”
。
在程序中设置$ZTRAP
在程序中,只能将$ZTRAP
特殊变量设置为该程序中的行标签(私有标签)。
不能将$ZTRAP
设置为过程块内的任何外部例程。
显示$ZTRAP
值时,Caché不返回私有标签的名称。相反,它返回该私有标签所在的过程顶部的偏移量。
在例程中设置$ZTRAP
在例程中,可以将$ZTRAP
特殊变量设置为当前例程中的标签、外部例程或外部例程中的标签。如果外部例程不是程序块代码,则只能引用该例程。下面的示例将LogErr^ErrRou
建立为错误处理程序。发生错误时,Caché执行^ErrRou
例程中LogErr
标签中的代码:
SET $ZTRAP="LogErr^ErrRou"
显示$ZTRAP
值时,Caché会显示标签名称和(适当时)例程名称。
标签名称的前31个字符必须是唯一的。标签名称和例程名称区分大小写。
在例程中,$ZTRAP
有三种形式:
SET $ZTRAP="location"
-
SET $ZTRAP="*location"
它在调用它的发生错误的上下文中执行。 -
SET $ZTRAP="^%ETN"
它在调用它的发生错误的上下文中执行系统提供的错误例程%ETN
。不能从过程块执行^%ETN
(或任何外部例程)。指定代码为[Not ProcedureBlock]
,或使用如下例程调用%ETN
入口点back^%ETN
:
ClassMethod MyTest() as %Status
{
SET $ZTRAP="Error"
SET ans = 5/0 /* divide-by-zero error */
WRITE "Exiting ##class(User.A).MyTest()",!
QUIT ans
Error
SET err=$ZERROR
SET $ZTRAP=""
DO BACK^%ETN
QUIT $$$ERROR($$$CacheError,err)
}
编写$ZTRAP
代码
$ZTRAP
指向的位置可以执行各种操作来显示、记录和/或更正错误。无论希望执行什么错误处理操作,$ZTRAP
代码都应该从执行两个任务开始:
-
将
$ZTRAP
设置为另一个值,可以是错误处理程序的位置,也可以是空字符串(“”
)。(必须使用SET
,因为不能k $ZTRAP
。) 这样做是因为如果在错误处理期间发生另一个错误,该错误将调用当前的$ZTRAP
错误处理程序。如果当前错误处理程序是您所在的错误处理程序,这将导致无限循环。 -
将变量设置为
$ZERROR
。如果希望稍后在代码中引用$ZERROR
值,请引用此变量,而不是$ZERROR
本身。之所以这样做,是因为$ZERROR
包含最新的错误,并且$ZERRR
值可能不会在例程调用(包括内部例程调用)中保留。如果在错误处理期间出现另一个错误,则$ZERROR
值将被该新错误覆盖。
/// w ##class(PHA.TEST.ObjectScript).TestMyErrorDieFor()
ClassMethod TestMyErrorDieFor()
{
SET $ZTRAP=""
SET $ZTRAP="Error"
w 1,!
SET ans = 5/0 /* divide-by-zero error */
WRITE "Exiting ##class(User.A).MyTest()",!
QUIT ans
Error
//SET $ZTRAP=""
w 2,!
SET ans = 5/0
q 0
}
强烈建议用户在使用后立即将$ZERROR设置为空字符串(“”)。
以下示例显示了这些基本的$ZTRAP
代码语句:
MyErrHandler
SET $ZTRAP=""
SET err=$ZERROR
/* 使用err作为要处理的错误来处理代码时出错 */
使用$ZTRAP
应用程序中的每个例程都可以通过设置$ZTRAP
来建立自己的$ZTRAP
错误处理程序。发生错误陷阱时,Caché会执行以下步骤:
- 将特殊变量
$ZERROR
设置为错误消息。 - 将程序堆栈重置为设置错误陷阱时(执行设置
$ZTRAP=
执行时)的状态。换句话说,系统删除堆栈上的所有条目,直到它到达设置错误陷阱的点。(如果将$ZTRAP
设置为以星号(*
)开头的字符串,则不会重置程序堆栈。) - 在
$ZTRAP
的值指定的位置恢复程序。$ZTRAP
的值保持不变。
注意:可以将变量$ZERROR
显式设置为最多128个字符的任意字符串。通常会将$ZERROR
设置为空字符串,但也可以将$ZERROR
设置为一个值。
使用错误陷阱解除新命令的堆栈。
发生错误陷阱并删除程序堆栈条目时,Caché还会将所有堆叠的新命令移回包含集合$ZTRAP=
的子例程级别。但是,在该子例程级别执行的所有新命令都将保留,无论它们是在设置$ZTRAP
之前还是之后添加到堆栈中的。
Class PHA.TEST.ObjectScriptTwo Extends %RegisteredObject [ Not ProcedureBlock ]
{
/// w ##class(PHA.TEST.ObjectScriptTwo).TestErrorStack()
ClassMethod TestErrorStack()
{
Main
SET A=1,B=2,C=3,D=4,E=5,F=6
NEW A,B
SET $ZTRAP="ErrSub"
NEW C,D
DO Sub1
RETURN
Sub1()
NEW E,F
WRITE 6/0 // Error: division by zero
RETURN
ErrSub()
WRITE !,"Error is: ",$ZERROR
RETURN
}
}
DHC-APP>w ##class(PHA.TEST.ObjectScriptTwo).TestErrorStack()
Error is: <DIVIDE>Sub1+2^PHA.TEST.ObjectScriptTwo.1
E=5
F=6
W ##CLASS(PHA.TEST.ObjectScriptTwo).TestErrorStack()
^
当Sub1
中的错误激活错误陷阱时,堆叠在Sub1
中的E
和F
的先前值被移除,但A
、B
、C
和D
保持堆叠。
$ZTRAP
控制选项流
调用$ZTRAP
错误处理程序来处理错误并执行任何清理或错误日志记录操作后,错误处理程序有三个流控制选项:
- 处理错误并继续应用程序。
- 将控制传递给另一个错误处理程序
- 终止应用程序
继续应用程序
在$ZTRAP
错误处理程序处理完错误之后,可以通过发出GOTO
继续应用程序。不必清除$ZERROR
或$ECODE
特殊变量的值即可继续正常的应用程序处理。但是,应该清除$ZTRAP
(通过将其设置为空字符串),以避免在发生另一个错误时可能出现的无限错误处理循环。
在完成错误处理后,$ZTRAP
错误处理程序可以使用GOTO
命令将控制转移到应用程序中的预定重新启动或继续点,以恢复正常的应用程序处理。
当错误处理程序处理了错误时,$ZERROR
特殊变量被设置为一个值。错误处理程序完成时,不一定清除此值。当调用错误处理程序的下一个错误发生时,$ZERROR
值将被覆盖。因此,$ZERROR
值只能在错误处理程序的上下文中访问。在任何其他上下文中访问$ZERROR
不会产生可靠的结果。
将控制传递给另一个错误处理程序
如果错误条件不能由$ZTRAP
错误处理程序纠正,可以使用特殊形式的ZTRAP
命令将控制转移给另一个错误处理程序。命令ZTRAP $ZERROR
重新发出错误条件的信号,并使Caché使用错误处理程序将调用堆栈展开到下一个调用堆栈级别。在Caché将调用堆栈展开到下一个错误处理程序的级别之后,处理将在该错误处理程序中继续进行。下一个错误处理程序可能已由$ZTRAP
或$ETRAP
设置。
下图显示了$ZTRAP错误处理例程中的控制流。