ABAP OO - Exception classes
Today we look at the different exception classes and how you can create and use your own exception classes.
Table of contents
As we already explained to you in the last article, exception classes are used to map error situations and to be able to transport additional information up the call hierarchy. Today we're going to take a closer look at the exception classes and what you can do with them.
General
An exception class basically looks like a normal class, but in most cases it starts with CX_ or ZCX_ to stand out from the standard class. It usually transports a message that specifies the error in more detail and always inherits from one of the basic types. The top exception class is CX_ROOT, this implements the basics of an exception class but is never used to create a new class. This is an abstract class, as are the following three classes, from which we can also inherit. Here is the ABAP type hierarchy from Eclipse:
CX_STATIC_CHECK
The class is most often used when you want to create an exception class. With this class, the interface of the method is checked to see whether the exception is listed if the exception is generated within and not caught with a CATCH. If the exception definition does not exist, the compiler will indicate this.
If the exception is not defined in the method interface, it cannot be catched, even if you use a TRY/CATCH that is intended to catch precisely this message.
TRY.
raise_static_check( ).
CATCH zcx_bs_demo_static_check.
out->write( 'Catched STATIC_CHECK' ).
ENDTRY.
Hint: The exception class must always be announced in the interface definition under RAISING, otherwise the user cannot react to the exception.
CX_NO_CHECK
As the name of the exception class suggests, no check is carried out here; the exception does not have to be defined in the method interface. The exception is known globally, so to speak, and can be caught when it occurs.
TRY.
raise_no_check( ).
CATCH zcx_bs_demo_no_check.
out->write( 'Catched NO_CHECK' ).
ENDTRY.
Hint: The use of this exception class makes it difficult for the caller to react properly to the exception as long as he does not know about it. Such exceptions are well suited for documenting dumps. The class can be defined in the method interface, but does not have to be.
CX_DYNAMIC_CHECK
When using this exception class, the definition in the interface is not checked at development runtime, but if a type of this exception is triggered during processing, it can only be catched if it has been defined in the method interface.
TRY.
raise_dynamic_check( ).
CATCH zcx_bs_demo_dynamic_check.
out->write( 'Catched DYNAMIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
Example
Here is the entire example of an executable class and how the methods are defined at the end so that the exceptions can be caught correctly.
CLASS zcl_bs_demo_class_exception DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_static_check
RAISING
zcx_bs_demo_static_check,
raise_no_check,
raise_dynamic_check
RAISING
zcx_bs_demo_dynamic_check.
ENDCLASS.
CLASS zcl_bs_demo_class_exception IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_static_check( ).
CATCH zcx_bs_demo_static_check.
out->write( 'Catched STATIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
TRY.
raise_no_check( ).
CATCH zcx_bs_demo_no_check.
out->write( 'Catched NO_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
TRY.
raise_dynamic_check( ).
CATCH zcx_bs_demo_dynamic_check.
out->write( 'Catched DYNAMIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
ENDMETHOD.
METHOD raise_static_check.
RAISE EXCEPTION NEW zcx_bs_demo_static_check( ).
ENDMETHOD.
METHOD raise_no_check.
RAISE EXCEPTION NEW zcx_bs_demo_no_check( ).
ENDMETHOD.
METHOD raise_dynamic_check.
RAISE EXCEPTION NEW zcx_bs_demo_dynamic_check( ).
ENDMETHOD.
ENDCLASS.
CX_ROOT
In the last example we had already used the exception class CX_ROOT in order to be able to catch further errors before a dump occurs. So what is behind the abstract root class for all exception classes? First of all, you shouldn't build an exception class on the basis of this class, but use the three shown subclasses in order to inherit the corresponding purposes. But you can use the class in places where you want to catch any processing errors.
In the examples above, CX_ROOT was always caught when the method interface was not configured correctly. For example, if the RAISING addition was not defined in the definition of method RAISE_STATIC_CHECK, the exception that occurs can only be caught with CX_ROOT and no longer with the actual exception class.
Own exception class
When defining your own exception class, you do not necessarily have to rely on global exception classes; you can also define your exception classes locally in reports or global classes. In our example we are building on a global exception class that we derive from STATIC_CHECK.
We define our class as follows:
CLASS zcx_bs_demo_data_error DEFINITION PUBLIC
INHERITING FROM cx_static_check
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg.
INTERFACES if_t100_message.
CONSTANTS:
BEGIN OF cs_data_error,
msgid TYPE symsgid VALUE 'ZBS_DEMO_LOG',
msgno TYPE symsgno VALUE '003',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cs_data_error,
BEGIN OF cs_type_error,
msgid TYPE symsgid VALUE 'ZBS_DEMO_LOG',
msgno TYPE symsgno VALUE '004',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cs_type_error.
DATA:
md_error TYPE sysubrc.
METHODS constructor
IMPORTING
!textid LIKE if_t100_message=>t100key OPTIONAL
!previous LIKE previous OPTIONAL
id_error TYPE sysubrc.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcx_bs_demo_data_error IMPLEMENTATION.
METHOD constructor ##ADT_SUPPRESS_GENERATION.
CALL METHOD super->constructor
EXPORTING
previous = previous.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = cs_data_error.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
md_error = id_error.
ENDMETHOD.
ENDCLASS.
So what did we adapt for our class?
- The class now contains two different error messages, which are mapped in two constant structures. The CS_DATA_ERROR error message is set in the class by default, unless another error message is passed.
- In addition, there is a new attribute with which we can transport further information; this attribute could also be set to READ-ONLY.
- Extension of the constructor by an IMPORTING parameter to supply the attribute.
If we generate two different errors via this class, with the first we only pass the additional parameter, with the second we also change the message that the class outputs by passing the other defined message.
" Error with additional parameter
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 4
).
" Error with different message
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 3
textid = zcx_bs_demo_data_error=>cs_type_error
).
If there is a reaction to the error, you can now access the corresponding attribute, for example to continue using it or to reference it. The type created must of course be of your defined class. The methods GET_TEXT and GET_LONGTEXT are also available so that you can convert the messages into texts (strings) and return them.
TRY.
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
out->write( lo_error->md_error ).
out->write( lo_error->get_text( ) ).
out->write( lo_error->get_longtext( ) ).
ENDTRY.
The complete example now looks like this, again we have added an executable class for easier use:
CLASS zcl_bs_demo_own_class_exc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_default_message
RAISING
zcx_bs_demo_data_error,
raise_changed_message
RAISING
zcx_bs_demo_data_error.
ENDCLASS.
CLASS zcl_bs_demo_own_class_exc IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_default_message( ).
raise_changed_message( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
out->write( lo_error->md_error ).
out->write( lo_error->get_text( ) ).
out->write( lo_error->get_longtext( ) ).
ENDTRY.
ENDMETHOD.
METHOD raise_default_message.
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 4
).
ENDMETHOD.
METHOD raise_changed_message.
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 3
textid = zcx_bs_demo_data_error=>cs_type_error
).
ENDMETHOD.
ENDCLASS.
Previous
You have probably already noticed that the constructor of the exception class also has a previous parameter, this is used to pass exceptions up in the call stack and thus also to get to the original error or the original message. To do this, we use the previously created exception classes and generate an error at the lowest level. We now catch the error on one of the upper levels and pack it in a new exception, so that several different errors can be included in an exception class.
TRY.
raise_data_exception( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
RAISE EXCEPTION NEW zcx_bs_demo_static_check( previous = lo_error ).
ENDTRY.
We fill the PREVIOUS parameter with the currently caught exception and create our own exception to pass the error on. In the end, after catching the exception, we can loop via the attribute until we have the last error message in the stack, our own original exception. So a loop could look like this:
TRY.
CATCH zcx_bs_demo_static_check INTO DATA(lo_error).
DATA(lo_previous) = lo_error->previous.
DO.
IF lo_previous->previous IS NOT BOUND.
EXIT.
ENDIF.
lo_previous = lo_previous->previous.
ENDDO.
out->write( lo_previous->get_text( ) ).
ENDTRY.
The loop is exited when PREVIOUS is no longer filled, then there is no predecessor. Finally, the entire example that we have used:
CLASS zcl_bs_demo_exc_hierarchy DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_static_exception
RAISING
zcx_bs_demo_static_check,
raise_data_exception
RAISING
zcx_bs_demo_data_error.
ENDCLASS.
CLASS zcl_bs_demo_exc_hierarchy IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_static_exception( ).
CATCH zcx_bs_demo_static_check INTO DATA(lo_error).
DATA(lo_previous) = lo_error->previous.
DO.
IF lo_previous->previous IS NOT BOUND.
EXIT.
ENDIF.
lo_previous = lo_previous->previous.
ENDDO.
out->write( lo_previous->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD raise_static_exception.
TRY.
raise_data_exception( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
RAISE EXCEPTION NEW zcx_bs_demo_static_check( previous = lo_error ).
ENDTRY.
ENDMETHOD.
METHOD raise_data_exception.
RAISE EXCEPTION NEW zcx_bs_demo_data_error( id_error = 8 ).
ENDMETHOD.
ENDCLASS.
Conclusion
Using exception classes is quite easy, but mastering them is not that easy. Defining your own exception classes and equipping them with your own attributes and information can be a challenge.