使用异常类

学习目标

  • 使用异常类。

异常处理

在本课程之前,我们使用 TRY、CATCH 和 ENDTRY 语句查看错误处理。TRY 语句后出现可能导致错误的语句。如果发生错误,系统将跳转到 CATCH 语句并处理其内容。无论发生什么情况,该方法都将在 ENDTRY 语句之后进行处理。

在 TRY…ENDTRY 块中,可以包括多个 CATCH 语句。这允许您以不同方式处理不同的异常。

您可以在单个 CATCH 语句中列出多个异常。在此示例中,如果系统触发 cx_sy_arithmetic_error 或 cx_sy_zerodivide,则将处理相同的 CATCH 块。

在 CATCH 语句中列出类时,如果触发该类或其子类之一的异常,系统将处理相应的块。在此示例中,cx_sy_arithmetic_error 是 cx_sy_arithmetic_overflow 和 cx_sy_zerodivide 的超类。因此,如果引发任一子类类型的异常,则将处理块。

所有异常类都是继承层次结构的一部分,其最高超级类是 cx_root 类。正如我们已经看到的,如果在 CATCH 语句中使用超类,则在触发其任何子类类型的异常时,系统将跳转到该类。由于所有异常类都是 cx_root 的子类,因此语句 CATCH cx_root 足以捕获任何异常。

CATCH 语句顺序

编写 CATCH 语句时,必须将最具体的类(或类)放在最前面,最通用的类放在最后。如果将超类置于其子类之一之上,则会导致语法错误。这是因为系统无法处理特定 CATCH 块,因为它被更通用的块覆盖。

INTO 附加

每个异常都由一个对象表示。您可以将此对象放在参考变量中并使用它 - 例如,使用此处示例中所示的 get_text() 方法从异常类中检索错误文本。

可以在 CATCH 语句中使用内联声明,以确保使用正确的类型声明参考变量。

对异常对象最常见的是调用其 get_text() 方法。但是,异常对象可以拥有其它属性和方法来帮助您处理错误。

异常类可以具有更精确地描述错误的属性。这些属性通常用于填充消息的占位符。在异常类的公共部分,使用相应的名称和类型定义属性。

为了在引发异常时填充属性,您需要异常类的构造函数的相应导入参数。您可以使用快速修复生成此导入参数。将光标置于方法名称 constructor 并按 Ctrl + 1。选择快速修复重新生成构造函数

传播异常

方法可以在调用链中传播异常。该方法在其定义中包含 RAISING 子句,而不是使用 CATCH 语句来处理异常。这向方法的调用者指示异常可以传递给它们。

如果未在调用链中的任何位置捕获异常,则会发生运行时错误。

属性 previous

有时,在调用链内,方法将捕获异常,在响应中,它将为其自己的调用者引发不同的异常以进行捕获。例如,如果框架引发了过于技术导向的异常,导致应用程序无法处理,则可能会发生这种情况。触发其自身异常的方法可能会传递第一个异常。可以使用异常类的 previous 属性执行此操作。

引发异常时,可以将异常类的实例传递到导入参数 previous。这会将异常对象附加到新异常。捕获第二个异常的方法可以使用公共只读属性 previous 访问第一个异常对象。

由于 previous 在超类 cx_root 中实施,因此它可用于所有异常类。

试用:异常类

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码片段复制到方法 if_oo_adt_classrun~main() 的实施部分。

DATA number1 TYPE i VALUE 2000000000.
DATA number2 TYPE p LENGTH 2 DECIMALS 1 VALUE '0.5'.
DATA result TYPE i.
TRY.
result = number1 / number2.
CATCH cx_sy_arithmetic_overflow.
out->write( 'Arithmetic Overflow' ).
CATCH cx_sy_zerodivide.
out->write( 'Division by zero' ).
ENDTRY.
number2 = 0.
TRY.
result = number1 / number2.
CATCH cx_sy_arithmetic_overflow.
out->write( 'Arithmetic Overflow' ).
CATCH cx_sy_zerodivide.
out->write( 'Division by zero' ).
ENDTRY.
TRY.
result = number1 / number2.
CATCH cx_sy_arithmetic_overflow cx_sy_zerodivide.
out->write( 'Arithmetic overflow or division by zero' ).
ENDTRY.
TRY.
result = number1 / number2.
CATCH cx_sy_arithmetic_error.
out->write( 'Caught both exceptions using their common superclass' ).
ENDTRY.
TRY.
result = number1 / number2.
CATCH cx_root.
out->write( 'Caught any exception using CX_ROOT' ).
ENDTRY.
TRY.
result = number1 / number2.
CATCH cx_root into data(Exception).
out->write( 'Used INTO to intercept the exception object' ).
out->write( 'The get_Text( ) method returns the following error text: ' ).
out->write( Exception->get_text( ) ).
ENDTRY.
  1. 使用 Ctrl + F3 激活类。

  2. 浏览编码以熟悉它。

使用异常对象

通过访问异常对象(即异常类的实例)来改进代码。使用构造函数参数设置异常对象的属性值。

模板:

  • /LRN/CL_S4D401_OOS_FACTORY(全局类)

解决方案:

  • /LRN/CL_S4D401_EXS_HANDLING(全局类)

任务 1: 复制模板(可选)

复制模板类 /LRN/CL_S4D401_OOS_FACTORY。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION

步骤

  1. 将类 /LRN/CL_S4D401_OOS_FACTORY 复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。

  2. 在项目浏览器中,右键单击类 /LRN/CL_S4D401_OOS_FACTORY 以打开上下文菜单。

  3. 从上下文菜单中,选择复制....

  4. 在包字段中输入的名称。在名称字段中,输入 ZCL_##_SOLUTION,其中 ## 代表组编号。

  5. 选择下一步

  6. 确认传输请求并选择完成

  7. 激活复制。

  8. Ctrl + F3 激活类。

任务 2: 访问异常对象

在全局类中,调整方法 IF_OO_ADT_CLASSRUN~MAIN 的实施。调用工厂方法 GET_INSTANCE 时扩展异常处理。在这两个 CATCH 块中,接收对异常对象的引用并提取异常文本。将控制台输出的文字文本替换为异常文本。

步骤

  1. 导航到全局类的方法 IF_OO_ADT_CLASSRUN~MAIN 的实施。

  2. 打开全局类并切换到全局类标签。

  3. 在第一个 CATCH 语句中,在异常对象处添加一个参考变量点,即异常类 CX_ABAP_INVALID_VALUE(建议的变量名称:exc_inv)的实例。

提示

您可以使用内联声明来声明具有正确类型的参考变量。

  1. 如下调整代码:

CATCH cx_abap_invalid_value INTO DATA(exc_inv).
  1. 要显示变量 exc_inv 的类型,请将光标放在 exc_inv 上并按 F2

  2. CX_ABAP_INVALID_VALUE CATCH 块中,注释掉方法 out->write 的调用。从异常对象中检索异常文本,并将其用于方法 out->write 的新调用。

  3. 如下调整代码:

CATCH cx_abap_invalid_value INTO DATA(exc_inv).
*    out->write( |Carrier { c_carrier_id } does not exist| ).
out->write( exc_inv->get_text( ) ).
  1. 对第二个 CATCH 语句和 CATCH 块执行相同操作(参考变量的建议名称:exc_auth)。

  2. 如下调整代码:

CATCH cx_abap_auth_check_exception INTO DATA(exc_auth).
*    out->write( |No authorization to display carrier { c_carrier_id }| ).
out->write( exc_auth->get_text( ) ).
  1. 激活并测试您的代码。调整常量 c_carrier_id 的值以引发不同的异常。

  2. Ctrl + F3 激活代码。

  3. F9 以控制台应用形式执行代码。

  4. 将常量 c_carrier_id 的值设置为 XX 可引发无效值的异常。

  5. 将常量 c_carrier_id 的值设置为 UA,以引发缺少权限的异常。

任务 3: 处理异常组

在本地测试类 LTCL_FIND_FLIGHTS 中,调整方法 CLASS_SETUP 的实施。调用工厂方法 GET_INSTANCE 时,仅在一个 CATCH 块中处理所有可能的异常。如果出现异常,则为**失败**方法提供异常对象中的异常文本,而不是通用错误消息"无法实例化 lcl_carrier"。

步骤

  1. 导航到本地测试类 LTCL_FIND_FLIGHTS 的方法 CLASS_SETUP 的实施。

  2. 切换到测试类标签。

  3. 滚动到语句 METHOD class_setup

  4. 调用方法 GET_INSTANCE 后,调整异常处理。注释异常类 CX_ABAP_INVALID_VALUE 的捕获块,并将异常类 CX_ABAP_AUTH_CHECK_EXCEPTION 替换为 CX_ROOT

  5. 如下调整代码:

TRY.
the_carrier = lcl_carrier=>get_instance( i_carrier_id = some_flight_data-carrier_id ).
*    CATCH cx_abap_invalid_value.
*      cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ).
CATCH cx_root.
cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ).
ENDTRY.
  1. 将 INTO 子句添加到 CATCH 语句中,并带有内联声明的参考变量(变量的建议名称:exc_root)。

  2. 如下调整代码:

CATCH cx_root INTO DATA(exc_root).
  1. 要显示变量 exc_root 的类型,请将光标放在 exc_root 上,然后按 F2

  2. CX_ROOT CATCH 块中,使用异常文本作为方法 CL_ABAP_UNIT_ASSERT=>failure 的输入。

  3. 如下调整代码:

CATCH cx_root INTO DATA(exc_root).
cl_abap_unit_assert=>fail( exc_root->get_text( ) ).
  1. 激活代码。

  2. Ctrl + F3

任务 4: 设置异常对象的属性

调整工厂方法 GET_INSTANCE 中异常的引发。在引发异常 CX_ABAP_INVALID_VALUE 的地方,使用相应的构造函数参数在异常文本中包含无效的载体标识。在引发异常 CX_ABAP_AUTH_CHECK_EXCEPTION 的地方,使用相应的构造函数参数选择更合适的异常文本。

步骤

  1. 导航到本地类 LCL_CARRIER 的方法 GET_INSTANCE 的实施。

  2. 例如,在 IF_OO_ADT_CLASSRUN~MAIN 方法中,将光标放在 get_instance 上并按 F3

  3. 分析异常类 CX_ABAP_INVALID_VALUECONSTRUCTOR 方法的签名。

  4. 在 RAISE EXCEPTION 语句中,将光标置于 CX_ABAP_INVALID_VALUE 上,然后按 F2 显示异常类的 ABAP 元素信息。

  5. 在对话窗口中,选择构造函数以显示 CONSTRUCTOR 方法的元素信息。

  6. 调整异常类 CX_ABAP_INVALID_VALUE 的 RAISE EXCEPTION 语句。为构造函数**参数值**提供无效的承运商标识。

注意

您必须执行类型转换,因为 value 和 i_carrier_id 的类型不同。

  1. 如下调整代码:

RAISE EXCEPTION TYPE cx_abap_invalid_value
EXPORTING
value = CONV #( i_carrier_id ).
  1. 调整异常类 **CX_ABAP_AUTH_CHECK_**EXCEPTION 的 RAISE EXCEPTION 语句。为构造函数参数 textid 提供该异常类的公共常量 missing_authorization

  2. 如下调整代码:

RAISE EXCEPTION TYPE cx_abap_auth_check_exception
EXPORTING
textid =
cx_abap_auth_check_exception=>missing_authorization.
  1. 激活并测试您的代码。调整常量 c_carrier_id 的值以引发不同的异常。

  2. Ctrl + F3 激活代码。

  3. F9 以控制台应用形式执行代码。

  4. 将常量 c_carrier_id 的值设置为 XX 可引发无效值的异常。

  5. 将常量 c_carrier_id 的值设置为 UA,以引发缺少权限的异常。