[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.
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-subrc
system 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.
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: TEXTID
, PREVIOUS
, and IS_RESUMABLE
, and the common methods, GET_TEXT
, GET_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_CHECK
, CX_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 appropriateTRY-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_ERROR
,CX_SY_CONVERSION_ERROR
, orCX_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
orCX_BADI
subgroups, andCX_SY_ILLEGAL_HANDLER
orCX_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_CHECK
, CX_DYNAMIC_CHECK
, and CX_NO_CHECK
). Apart from this requirement, they can be freely extended with the properties and methods needed.
Unlike classic exceptions, class-based exceptions are not a part of the method call. This is partly due to the separation between
CX_STATIC_CHECK
, CX_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 oref
addition 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 UNWIND
addition 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.
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
RESUMABLE
clause 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 MESSAGE
addition 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.
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:
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.
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 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.
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.
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.
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 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.
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:
Comments
Post a Comment