在代码中使用基于类的异常
ABAP 中存在多种历史错误类型: 系统异常、经典异常和基于类的异常。出于向下兼容性的原因,在 ABAP 中定义独立可处理异常有两个选项:
经典异常
这些异常只能在方法或功能模块的接口中使用 EXCEPTIONS
参数中进行声明,并且可以在此类过程中使用语句 RAISE
或 MESSAGE RAISING
引发。过程调用者可以使用语句 meth( ... )
或 CALL FUNCTION
的附加 EXCEPTIONS
将系统字段 sy-subrc
的返回代码分配给调用者想要处理的异常,并在调用后对其进行评估。
基于类的异常
这些异常由异常类定义,当异常发生时(如果处理程序在 CATCH
中使用了 INTO
附加语句),可以从异常类中创建异常对象。基于类的异常可以取消当前上下文或允许恢复。异常使用语句 RAISE EXCEPTION
引发,并在 TRY
控制结构中使用 CATCH
进行处理。基于类的异常可以在任何存储过程中产生,也可以由任何存储过程进一步传播。
两种异常概念共存的规定如下:
程序接口中不能同时声明经典异常和基于类的异常。在一个处理块中,只能引发经典异常或基于类的异常。 出于互操作性的考虑,在一个处理模块内,可以使用经典异常处理基于类的异常,并评估函数模块和方法的返回值。
编码原则
原则: 不允许使用系统异常,经典异常是显式的且过时的。推荐使用基于类的异常。
自定义的经典异常只不过是返回值而已。如果在存储过程中使用语句 RAISE
引发了经典异常,那么在返回给调用程序后,sy-subrc
系统字段将根据所引发的异常进行设置。调用程序本身必须始终通过查询 sy-subrc
,检查是否有异常产生,并在必要时做出反应,例如,通过适当的处理或明确转发给自己的调用程序(通过产生一个单独的等效异常)。这并不会提高程序的清晰度。
然而,类异常的产生会导致程序流程的改变。它们可以直接被处理,也可以沿着调用层次向上传播。这样,并非每个过程(方法)都必须考虑每种可能的异常情况。这支持了应用程序内的关注点分离。由于异常可以由异常类的一个对象来表示,因此该异常对象可以收集有关异常情况的附加信息,并将其传送给处理程序。与传统异常不同的是,它还可以包含特定的异常文本。
默认情况下,即使异常已被处理,引发异常也会停止整个当前上下文。不过,在某些情况下(例如大规模数据处理),单个错误并不能证明取消整个服务是合理的。在这种情况下,基于类的异常可以作为恢复性异常引发和传播。
由于远程功能模块(RFM)目前不支持基于类的异常,因此仍然需要为远程功能调用(RFC)实现和处理经典异常。
不好的使用方式
下面的源代码显示了在方法中声明和引发典型异常的过程,以及在调用方法后通过评估 sy-subrc
来处理异常的过程。该过程违反了上述规则。
ABAP
CLASS application DEFINITION.
PUBLIC SECTION.
METHODS do_something
EXCEPTIONS application_error.
ENDCLASS.
CLASS application IMPLEMENTATION.
METHOD do_something.
...
RAISE application_error.
...
ENDMETHOD.
ENDCLASS.
...
... oref TYPE REF TO application.
...
oref->do_something(
EXCEPTIONS application_error = 4 ).
IF sy-subrc <> 0.
...
ENDIF.
好的使用方式
下面的源代码显示了异常类的定义、声明、在方法中引发异常以及在 TRY 代码块中调用方法后使用 CATCH 处理异常。
erlang
CLASS cx_application_error DEFINITION
INHERITING FROM cx_static_check.
ENDCLASS.
CLASS application DEFINITION.
PUBLIC SECTION.
METHODS do_something
RAISING cx_application_error.
ENDCLASS.
CLASS application IMPLEMENTATION.
METHOD do_something.
...
RAISE EXCEPTION TYPE cx_application_error.
...
ENDMETHOD.
ENDCLASS.
...
... oref TYPE REF TO application.
...
TRY.
oref->do_something( ).
CATCH cx_application_error.
...
ENDTRY.
这个简单的例子也许不能最明显地说明基于类的异常相对于经典异常的巨大优势。然而,在嵌套过程调用和处理在更远调用层中引发的异常时,我们可以清楚地看到这种优势。
尽快处理异常情况
最好是在调用堆栈中更靠近引发子句的位置处理异常。当前堆栈层的上下文有足够的信息进行适当处理时,再进行处理。
每个问题一个异常类,针对不同问题原因的多个文本
不要为每个提升子句创建许多不同的类。相反,为一种问题(即一种类型的问题处理)创建一个类。
创建有意义的消息来描述此类问题的每个原因。错误处理可能相同,但原因、日志记录和用户通知会有所不同。
示例:您正在从 PC 读取文件。可能存在不同的问题:文件已损坏、文件为空、访问被拒绝等。但如果您只是想知道文件是否上传成功,一个异常类 zcl_io_error
就足够了。但要为每个错误原因或错误类型创建适当的消息,让用户知道文件未上传的确切原因。
检查程序的可访问性
确保您的应用程序可供有障碍的人使用。这意味着用户界面上的任何信息都应该以可访问的形式给出:
- 输入和输出字段必须有有意义的标签;
- 图标必须有工具提示;
- 表格列必须有标题;
- 信息不能仅用颜色来表达;
- 屏幕上的输入和输出字段应适当地分组在框架中,每个框架都有一个有意义的标题。
这就确保了色盲或屏幕阅读器用户等有障碍的人能够完全使用应用程序的功能。
这是使用最新界面技术(例如 SAPUI5、Web Dynpro ABAP 或 ALV)的另一个原因。这些自动只允许可访问的界面,而使用旧技术(例如经典屏幕或经典列表)的应用程序开发人员自己负责确保满足可访问性要求。经典 dynpro 的检查工具和选择屏幕中有一些检查报告违反这些规则的情况(如果可以静态识别)。但是,对于经典列表,在显示之前无法进行此类检查。
参考链接:无障碍
将任何共享数据访问包装到数据访问类中
当您使用共享内存、共享对象、缓冲区等共享数据时,不要直接访问它们。相反,将它们封装到静态数据访问类的 setter
和 getter
方法中。
它将帮助您控制对共享数据的访问,并通过使用位置列表轻松找到任何共享数据更改。它还允许您在单元测试中模拟共享数据访问。
避免隐式数据声明
如果可能,尽量不要使用 TABLES
、 NODES
等数据声明。他们创建具有隐式访问的数据对象。
使用 DATA
代替。只在 LDB
中使用 NODES
。两者都能创建全局工作区,并在所有程序中共享和使用。
切勿使用 TABLE ... WITH HEADER LINE
。将结构、字段符号或引用与表行或内联声明和表表达式的类型一起使用。
内表工作区
表工作区是使用语句 TABLES
或 NODES
声明的扁平结构类型、数据库表类型或 ABAP 字典中的视图类型的结构化数据对象。如果使用 NODES
声明,则其他 ABAP 字典类型也是可能的。
从数据类型的角度来看,语句:
ABAP
TABLES table_wa.
NODES table_wa.
与
ABAP
DATA table_wa TYPE table_wa.
这意味着声明的数据对象与 ABAP 字典中相应的数据类型具有相同的名称和类型。表和节点的进一步含义对此进行了补充。有关完整的含义,请参阅 ABAP 关键字文档。基本属性如下:
TABLES
和NODES
声明接口工作区,这些工作区由程序组的多个程序共享TABLES
声明了经典 dynpro 和选择屏幕的接口NODES
声明逻辑数据库的接口
此外,还可以将使用 TABLES
声明的表工作区用作过时的 Open SQL 缩写形式的隐式工作区,甚至用于数据库访问的较旧语句
原则:除了经典的 dynpro 之外,不要定义内表工作区
- 无论如何,类中不允许使用语句
TABLES
,并且语句NODES
只能在与逻辑数据库关联的可执行程序的全局声明部分中按语法创建。后一种选择不再被允许 - 由于不允许需要语句
TABLES
的过时数据库访问以及程序之间的共享数据区域,因此不需要使用语句TABLES
,除了声明经典 dynpro 的接口 - 经典 dynpro 中的屏幕字段是参考 ABAP Dictionary 中的平面结构定义的 ,则必须使用语句 TABLES 声明 ABAP 程序的同名全局数据对象。否则,ABAP 程序的数据对象不会链接到 dynpro 字段,并且无法访问其内容。此外,在处理选择屏幕的功能代码时,还需要使用 TABLES 来声明特定的工作区域。
使用内置布尔类型和常量
当你想使用一些逻辑信息时,请使用内置的布尔类型 abap_bool
而不是 char1
或其他类型。
使用常量 abap_true
和 abap_false
表示布尔 true 和 false 值。不要使用 'X'
、 ' '
这样的文字 - 它是硬编码。
使用 space
、 IS INITIAL
或 IS NOT INITIAL
也是不可取的,因为它们检查 abap_bool
的技术实现状态,但不是意义真正的布尔数据对象。
不好的习惯
erlang
DATA is_found TYPE c LENGTH 1.
...
is_found = 'X'.
...
IF is_found IS NOT INITIAL.
...
ENDIF.
良好的习惯
ABAP
DATA is_found TYPE abap_bool.
...
is_found = abap_true.
...
IF is_found = abap_true.
...
ENDIF.
不要在用戶界面中使用系统字段
系统字段 (sy) 是技术性的。它们不应该显示给用户。