[SAP ABAP] Exception handling

Classic exception handling

As was mentioned in the introduction, when working with the SAP system, it is rather inevitable to find some old code, and it was most likely created with classic exceptions. Although it is not recommended, and even discouraged, to use them on a regular basis in a new development, there are still several cases where it is the only way to communicate and handle the unexpected behavior of the program.

Handling

Prior to the introduction of class-based, object-oriented programming in SAP systems, one of the methods of code modularization was function modules. The reusable code components with a particular task to do were wrapped into pretty simple function calls, and the modules were bundled into function groups. The function modules—despite the fact that they were mostly superseded by the classes and methods—are still necessary when using Remote Function Call (RFCs).
Even if the code is well thought out and well written, it is still necessary to ensure that the system will not fail if something unexpected happens. What is even more important, it is required to prevent the database from having inconsistent data, even if processing is interrupted abruptly. To solve this problem, developers were given the appropriate mechanism of communicating failures or problems to anyone using their modules—including the EXCEPTIONS section in the function module interface shown in the following code snippet:
CALL FUNCTION 'SAMPLE_FUNCTION'
    EXPORTING
        …
    IMPORTING
        …
    TABLES
        …
    CHANGING
        …
EXCEPTIONS
The EXCEPTIONS section of the call is built along similar lines to the preceding ones—it is a list of possible exceptions returned by the function modules, followed by the assignment of a number in the 0-65535 range. A simple list is presented here:
CALL FUNCTION 'SAMPLE_FUNCTION'
    some_defined_exception   = 1
    some_other_exception     = 2
    yet_another_exception    = 3
As it is possible for the function module to have a long list of possible exceptions—and not all of them can be handled in a meaningful way—we can use a keyword, OTHERS, to assign a single number to all handleable exceptions not mentioned by name. Refer to the following code snippet:
CALL FUNCTION 'SAPMLE_FUNCTION'
    EXCEPTIONS
        some_defined_exception = 1
        OTHERS                 = 2.

Note

It is important to observe that omitting the OTHERS keyword while not assigning a number to an exception will lead to a runtime error when this particular exception occurs.
Once the function module is executed and the program flow goes back to the caller, the sy-subrcsystem variable is set. By default, if the flow was not interrupted by any exception, the sy-subrc value is set to 0. In any other situation, the value is set according to the number specified in the EXCEPTIONS section thus letting the developer act accordingly.
The classic exceptions are the natural extension of the function modules interface, but they can also be incorporated into the class methods (although it is not recommended). For the local classes, the exceptions are also defined in a dedicated section as shown here:
CLASS lcl_class DEFINITION.
    METHODS
        sample_method
            RETURNING value(arg) TYPE I
            EXCEPTIONS some_exception another_exception yet_another_exception
ENDCLASS.
When defining global classes using the Class Builder, the exceptions are defined on the corresponding screen, accessible via the Exceptions button:
On the new subscreen, the classic exceptions are created simply by declaring their name and adding a meaningful description. As the classic exceptions and class-based exceptions (described further in this chapter) are mutually exclusive, the Exception Classes checkbox needs to be left unmarked, as follows:
Similar to function modules, when calling the method containing the EXCEPTIONS section, the developer is responsible for assigning appropriate codes (numbers) to all the exceptions declared, similar to the following code snippet:
DATA lr_ref TYPE REF TO lcl_class.
DATA lv_result TYPE I.
    …
CREATE OBJECT lr_ref.
lr_ref->some_method( 
    RECEIVING 
        arg = lv_result
    EXCEPTIONS 
        some_exception        = 1
        another_exception     = 2
        yet_another_exception = 3 ).
Once again, should the exception happen while not declared in the call statement, the runtime error will occur and the program will be terminated immediately.
After the method call is completed, the sy-subrc variable is set to 0 for success or to any number assigned to the exception that was raised during the execution.

Raising

Classic exceptions should not be considered the exceptions originating from the system core. They are rather a short explanation, or the reason for the premature termination of the method or a function call. Such termination can be the result of preventive steps to avoid system failure, but also—and probably more likely—it is a simple message stipulating that for some reason, the code logic deviates from the designed flow and the valid results cannot be provided.
Due to their logic-specific nature, classic exceptions need to be raised manually in places where they are actually needed. Regardless of whether you're defining a new function module or new class method, there are two ways of raising a classic exception—the first is with the RAISE statement:
RAISE some_exception.
The second one is an addition to the MESSAGE statement:
MESSAGE 'Some error message'  TYPE 'E' RAISING some_exception.
Both statements result in the immediate termination of the current processing block. This termination may have several outcomes, depending on the context. If any of these two statements is executed within the function module or class method, and the caller provided an appropriate EXCEPTIONS assignment, the program flow resumes right after the call. If they are executed within a subroutine, the interpreter searches the call stack for the first method or function module that wraps the current context.
If there is neither the former nor the latter, the RAISE statement causes a runtime error, whereas MESSAGE-RAISING behaves like the standard MESSAGE statement. Otherwise, if either function module or class method is found, its interface is checked for the definition of the exception. Provided the definition is present, the execution flow is resumed after the call. In other cases, where the definition is not available, RAISE results in a runtime error and MESSAGE-RAISING produces a message.
The aforementioned outcomes lead to a recommendation—when the classic exception is required to be raised, it is preferable to use the MESSAGE-RAISING clause as it may carry a bit more information (a message) than the RAISING clause alone.
Classic exceptions cannot be used alongside class-based exceptions within the same processing block.

Class-based exceptions

As the object-oriented programming philosophy was incorporated into ABAP programming, the exceptions concept had to evolve as well. As programs grew and used more classes and objects, the exceptions were eventually migrated to class-based programming. There was also a more practical reason for this move-defining exception, since an object allows for passing more detailed information on what happened to the caller, thereby letting the developer define better-tailored reactions.
The newly created exception objects—either standard or custom ones—are children of the same master abstract class, CX_ROOT, and therefore all have common attributes: TEXTIDPREVIOUS, and IS_RESUMABLE, and the common methods, GET_TEXTGET_LONGTEXT, and GET_SOURCE_POSITION.
The CX_ROOT class is the most general exception class and, as such, contains very little information about the specific causes of the exception. Thus, there is a whole hierarchy of subclasses grouped into three elements that make up the first tier —CX_STATIC_CHECKCX_DYNAMIC_CHECK, and CX_NO_CHECK. This separation of the inheritance tree is done as per the requirements in the declaration and checks are performed.
The requirements mentioned—as well as short recommendations on usage—are as follows:
  • Exceptions based on CX_STATIC_CHECK and its subclasses need to be declared in the method interface. The syntax checks checks whether the caller wrapped the call with the appropriate TRY-CATCH block to catch the exception. This type of exception should be used when there is no way to prevent the exception and it needs to be forced to handle the exception by the caller explicitly.
  • The exceptions originating from CX_DYNAMIC_CHECK and its children need to be declared in the interface as well. However, there is no syntax check when defining the method call. These exceptions should be raised if the cause is somehow prevented by other means in the application flow, and therefore there is no need to explicitly tell the caller that the exception needs to be handled. More detailed subclasses of this group are, for example, CX_SY_ARITHMETIC_ERRORCX_SY_CONVERSION_ERROR, or CX_SY_ASSIGN_ERROR.
  • The usage of CX_NO_CHECK and its subclasses—as the name suggests—results in exceptions that are not supposed to be declared in the interface but are always propagated. It represents the exceptions that can happen any time and cannot be prevented in any other way. This group contains, for example, CX_SY_REMOTE_CALL_ERROR or CX_BADI subgroups, and CX_SY_ILLEGAL_HANDLER or CX_SY_NO_HANDLER classes.
Although the introduction of standard exception classes is a natural outcome of migrating the ABAP language to object-oriented programming, it is the possibility of creating custom exception classes that make it a powerful tool. The custom exception classes can be created either locally or by using the Class Builder.
The main difference between the custom exception classes and ordinary classes is that the former must be defined as the subclasses of any of the three tier one exception classes (CX_STATIC_CHECKCX_DYNAMIC_CHECK, and CX_NO_CHECK). Apart from this requirement, they can be freely extended with the properties and methods needed.

Handling

Unlike classic exceptions, class-based exceptions are not a part of the method call. This is partly due to the separation between CX_STATIC_CHECKCX_DYNAMIC_CHECK, and CX_NO_CHECK—not all of them are required to be handled every time. Similar to other object-oriented languages with the exception mechanism, class-based exceptions are caught with the TRY-CATCH block. In ABAP, however, the aforementioned block is further enhanced with several optional context-control statements, shown in the following code snippet:
TRY.
    ...execute standard program flow here...
CATCH [BEFORE UNWIND] some_exception another_exception [INTO oref].
    ...react to the exception here...
[CLEANUP [INTO oref].]
    ...cleanup here before passing the exception...
ENDTRY.
The simplest possible declaration of the TRY-CATCH block includes only the following statements with the exception name:
TRY.
    ...execute standard program flow here...
CATCH some_exception.
    ...react to the exception here...
ENDTRY.
Although this straightforward example is sufficient to pass the syntax check and avoid runtime errors caused by some_exception, it is most likely not enough for proper handling in terms of business context or application logs. In order to get more information from the exception, the INTO orefaddition can be used:
TRY.
    ...execute standard program flow here...
CATCH some_exception another_exception INTO oref.
    ...react to the exception here...
CATCH yet_another_exception.
    ...react to the exception here...
ENDTRY.
This addition causes the exception object thrown to be stored in the oref variable. Keep in mind the hierarchical structure of the exception classes; the oref variable's type must be a superclass of all the exceptions declared in the CATCH statement. Alternatively, it is possible to use DATA(oref) instead, letting the code interpreter decide the variable's type.

Note

The type of the oref variable declared with DATA(oref) in the INTO clause will be the lowest common superclass of all the exceptions declared in the CATCH statement.
Once the exception object is stored in the variable, it is possible to access its public properties and methods when reacting to the exception. The exact properties and methods are dependent on the type of the exception and can be freely defined when the custom exception classes are used.
If the standard execution block can raise multiple exceptions, and the reaction for any of them must differ, it is possible to use several CATCH statements within one TRY-ENDTRY section, as was also shown in the previous code snippet.
The exceptions mechanism in ABAP is designed this way, so when the exception is raised and the flow control is passed to the CATCH statement, the context in which it was raised is lost by default. Although not so common, there are some situations that require the preservation of the raising context when executing the reaction block. One particular example is when the exception is thrown is resumable and the flow is indeed supposed to be resumed. For these purposes, the BEFORE UNWINDaddition was introduced:
TRY
    ...execute standard flow here...
CATCH BEFORE UNWIND some_exception INTO oref.
    ...react to the exception here...
    RESUME.
ENDTRY.
One more option for flow control when handling exceptions can be added with the CLEANUP statement. This optional keyword can be used when the exception raised is not handled directly by the same TRY-CATCH block, but by some other surrounding block, as in the following code snippet:
TRY.
    TRY.
        ...execute standard flow here…
        ...some_other_exception is raised…
    CATCH some_exception.
        ...react to some_exception...
    CLEANUP.
        ...cleanup section…
    ENDTRY.
CATCH some_other_exception.
    …react to some_other_exception…
ENDTRY.
When some_other_exception is raised, it is not caught by the innermost TRY-CATCH section, but by the outermost, so the structure is syntactically correct. The CLEANUP section is executed immediately after the raising context is deleted and before the outermost CATCH section.
If the catching statement is supplied with the BEFORE UNWIND addition, the CLEANUP section is executed after the CATCH section, or, if there is a RESUME statement in the CATCH clause, it is not executed at all.
Similar to the CATCH statement, the CLEANUP section can be further supplied with the INTO oref addition to get a reference to the exception object. This time, however, oref is always of the CX_ROOT type.

Raising

Raising class-based exceptions is only a little bit more complicated than classic exceptions. In this case, the RAISE EXCEPTION statement should be used, with appropriate additions and options when needed.
The appropriate syntax for raising this kind of exception depends on whether the exception object variable was declared prior to raising. In the first scenario, the following syntax is valid:
CLASS lcx_exception DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.
...
DATA lx_exception TYPE REF TO lcx_exception.
...
CREATE OBJECT lx_exception.
RAISE EXCEPTION lx_exception.
If the object was not declared, the RAISE statement should have the TYPE keyword and should refer to the type instead of the variable, as in the following code snippet:
CLASS lcx_exception DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.
...
RAISE EXCEPTION TYPE lcx_exception.
The actual values of the exception parameters need to be passed to the exception object during its creation (either with CREATE OBJECT or RAISE EXCEPTION TYPE) through the EXPORTING clause:
RAISE EXCEPTION TYPE custom_exception_class
    EXPORTING
        param1 = value 1
        param2 = value 2
        ...
.
The values passed here will be accessible within the CATCH statement of the first surrounding TRY-CATCH block, suitable for catching this type of exception.
If the exception thrown is not meant to stop the execution flow—and it should be possible to resume it when the exception is handled—the optional RESUMABLEclause can be used:
RAISE RESUMABLE EXCEPTION TYPE custom_exception_class.
The appropriate CATCH BEFORE UNWIND block may contain the RESUME statement.
Many programs in the ABAP environment utilize messages as a way to communicate their current state and issues to the user, or to store information in logs. Thus, class-based exceptions are also prepared to use this mechanism. As long as the exception class is declared with either the IF_T100_DYN_MSG or IF_T100_MESSAGE interface, the MESSAGEaddition can be used.
When constructing custom exception classes, these interfaces are included automatically when the following checkbox is checked:
The resulting interfaces tab is as follows:
Using the MESSAGE clause similar to the following code snippet will result in the exception object with the fields populated according to the interface rules, with the whole message accessible through the get_text( ) method:
RAISE EXCEPTION TYPE sample_exception_class
    MESSAGE
        ID sy-msgid
        TYPE sy-msgty
        NUMBER sy-msgno
        WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

Assertions

ABAP programs, as well as pretty much every other program, are more or less based on certain assumptions. For example, the data provided to a method or a function is consistent, that the program behaves as designed, or that the outcome of data processing follows some kind of scheme.
In most cases, these assumptions are so obvious that the developer is not even aware of them when writing a piece of code. Sometimes, however, it may be more important to check whether a certain condition is fulfilled before proceeding further. Using the standard IF-ELSE statement for this purpose is sufficient and will do exactly what is needed. However, there is another shorter and more controlled way of achieving this—with assertions.
The assertion in ABAP code is a simple statement saying assert that something fulfills a condition. There is no need to use any sophisticated syntax or build nested IF structures for several conditions. What's more, the assertion mechanism is remotely controlled—particular groups of assertions can be turned on or off when needed (without altering the source code) and several different actions can be performed if the assertion fails.

Building a checkpoint group

The best way to start working with assertions is to run the Checkpoints that Can Be Activated (SAAB) transaction and create a new checkpoint group:
Assign the new group to the appropriate package and transport request. This object should advance to the productive system to handle failed assertions there as well.
The resulting checkpoint group is ready to be used and the preview shows the screen as follows:
The preceding screenshot shows that the information about the settings is very clear. The user can set them in a simple way. They are also grouped by their characteristics.

Defining assertions

Once the checkpoint group is created, it is time to set up some assertions in the code. To to this, the following syntax is used:
ASSERT [ [ ID groupID [ SUBKEY key] ] [ FIELDS field 1 … ] CONDITION  ] expression
The ID addition is used to assign the assertion to the group defined in the SAAB transaction and control it remotely. Although it is possible to omit this addition (resulting in an assertion that is always active), should the assertion fail, the runtime error will be triggered by the non-handleable ASSERTION_FAILED exception. The ID clause can be further specified with the SUBKEY addition—it can be followed by any character string and is used to easier identify assertions in the program.
The optional FIELDS addition is used to provide a list of fields (variables) that should be passed to the assertion log if the assertion fails.
The CONDITION clause is required if either ID or FIELDS clauses are used. After the CONDITION, one defines the logical expression to be checked at runtime.
Using the checkpoint group defined in SAAB, a simple assertion example may appear, similar to the following code snippet:
ASSERT ID ZSAMPLE_ASSERT SUBKEY 'date assertion' FIELDS sy-datum sy-uzeit CONDITION sy-datum < '19990101'.
As the year 1999 is obviously in the past, this assertion is meant to fail.

Using assertions

Provided assertions included in the code are assigned to the checkpoint group using the ID addition, they are inactive by default. Using the SAAB transaction again, these assertions can be activated temporarily and the required action can be defined. You can choose from four options for foreground processing:
  • Inactive: The assertions are not checked at all.
  • Break: Failed assertions open the debugger window.
  • Log: The values of variables specified in the FIELDS addition are logged if the assertion fails.
  • Abort: The ASSERTION_FAILED exception is thrown when the condition is not met and the program is terminated immediately.
The available options are also shown in the following screenshot:

Note

ABAP programs can be executed as background processes, for which the debugger cannot be opened. Choosing the Break option requires additional decisions as to whether background processing should log the assertion failure or whether to abort processing with ASSERTION_FAILED.
Once the decision is made and the save icon is pressed, the user is prompted to choose an activation period. The assertions from within the checkpoint group will automatically turn inactive after the specified time.
Executing the code with the assertion in Log mode results in a new entry on the Log tab:
Drilling down the tree eventually shows the details of this particular assertion failure and the values of variables defined in the FIELDS clause:
Well-written assertions allow for quick and easy root cause analysis of errors and failures without the need to use debuggers in a productive environment.
The checkpoint group created with SAAB has the option to activate and deactivate BREAK-POINT and LOG-POINT,  both assigned to it. The behavior of the former is similar to the assertion in Break mode, and the latter to the assertion in Log mode, but they don't specify additional conditions.

Runtime errors

A runtime error in an SAP system can have many causes. A runtime error is a problem whose effect is interrupting the program. The most common reasons for this are as follows:
  • Non-handled exceptions.
  • A handleable exception was not handled.
  • A non-handleable exception was raised.
  • An exit message was sent.
  • An assertion failed (assertion in ABORT mode).
The database table SNAPTID lists all existing runtime errors—in total, around 2,000.

ABAP dump analysis tool

ABAP Dump Analysis is used to analyze execution errors in the SAP system. It is a very powerful tool often displayed by developers. The user can run this tool with the ST22 transaction.  The tool provides a lot of information that is necessary to repair the existing situation. The selection screen is shown after starting the ST22 transaction. The selection screen is shown as follows:
Searching for a specific error becomes easier when the developer has as much information as possible; this can narrow down the search area.
The following code will result in a short dump since the division by 0 has not been handled:
REPORT ztestdump.

DATA : lv_count TYPE i VALUE 4,
        lv_div   TYPE i,
        lv_denom TYPE i VALUE 0.

 lv_div = lv_count / lv_denom.
The previous code was presented for demonstration purposes. The error simulated in this program will help the reader understand the ABAP Dump Analysis tool in the SAP system.

Error log

The resulting error log can be analyzed in the transaction described previously. The error log is divided into a tree structure; its main nodes are composed of the following:
  • System Environment
  • User View
  • ABAP Developer View
  • BASIS Developer View
Each of the preceding nodes contains subnodes containing important information related to the problem. 

System environment

There are two subnodes in the System Environment node: User and Transaction and System Environment. The first contains technical information about the system; there is also information about memory usage. In the second section, the user will find information about the User and Transaction. In this subnode, the user can find out which user caused the error and on which mandate they were logged in.  

User View

In this part, the system informs the user what happened in the system at the time of the error. There is also a suggested way to solve the problem, but this information is not usually sufficient for repair. The sample data provided by the SAP system is shown as follows:
The preceding screenshot shows that the information is very general. It is rare that this is sufficient to take appropriate action to repair the system. However, the information provides a general picture of the problem, which is useful.

ABAP developer View

The first set of information contains a brief description of the error. This information (as in this case) is often sufficient to locate the source of the problem. An example message is shown here:
The next set of information is more detailed. It contains detailed information about what has happened and in which program. Often, there is also information about the exception that should be used:
Information on where terminated gives information about where the error occurred and in which line of code the program was aborted. The developer also has access to the data included in the system variables. Sample information about the system variables is shown in the following screenshot:
Some of the most important information provided by this tool is How to correct the error. This tells you what can be done to fix the error. This is to avoid repeating problems when using the program. In the following example, the system determines which classes to use to handle the exception. This information is important for both experienced programmers and beginners alike. Often, the information contained in this section allows you to eliminate the error. The use of the exception class is also described in this chapter. An example screen is shown in the following screenshot:
All parts of the tool shown here are valid, but rarely give sufficient information for repair. The next point, however, is the most useful for everyday work. Users have access to information about the code in which the error occurred. After double-clicking, you can go to the place where it will be modified if the user has permission. The following screenshot shows what it looks like:
The preceding screenshot, along with the code, proves that the tool allows you to easily find the problem in the code.

BASIS developer View

This section describes the problems with BASIS. This is very helpful when the error is related to malfunctions on the kernel side. The user gets information about which program was started during the error. This information is rarely important to the programmer, because in most cases, the problem is programming and the solution consists of modifying the existing code. The sample information is shown as follows:

The preceding screenshot shows that the information in this section is relatively short. However, it offers useful data that the BASIS team can use when the problem and error are on their side.

Comments

Popular posts from this blog

[SAP ABAP] Data declaration

[SAP ABAP] BAPI

[SAP HR] Payroll Schema