This weblog is part of a series on the new class-based exception handling concept in ABAP. To navigate to the other parts of the series use one of the following links:
- The New Class-Based Exception Handling in ABAP – Part 1,
- The New Class-Based Exception Handling in ABAP – Part 2,
- The New Class-Based Exception Handling in ABAP – Part 3,
- The New Class-Based Exception Handling in ABAP – Part 4.
Not All Exceptions Must be Declared - Three Types of Exceptions
Up until now I have told you that you have to declare all exceptions that might leave a procedure in the interface. In fact, this is not true for all exceptions. As you have already learned, the root class of all exceptions in ABAP is cx_root.
But you are not allowed to derive an exception class directly from this root-class. There are three sub-classes of cx_root from which you can derive your exceptions. The behavior of each class differs regarding the question whether the declaration of an exception in the interface is or is not checked at design time or/and at runtime.
The Class cx_static_check
Classes derived from this class provide the strictest check. The compiler checks at design time if all exceptions that might leave a procedure are declared in the interface. At runtime only exception declared in the interface can leave a procedure.
How to Deal With Ubiquitous Errors?
Declaring every exception that might leave a procedure does not necessarily lead to safer and better code,. Think of errors that could happen almost everywhere. They are ubiquitous errors.
If those errors lead to exceptions of the static types, you have three alternative ways to deal with them: (1) You could declare them in almost every interface but that would fill the interfaces with useless information. (2) You might catch them somewhere down in your program, but ending the program is not a good idea, because there might be a handler waiting for them somewhere up in the call hierarchy.
(3) You could also write empty handlers, just to make your programs compliant with the checking rules of the compiler. But this is the worst choice of the three alternatives. Never ever do this: If the error occurs, you cannot track it down.
In order to deal with these ubiquitous exceptions, there is a class called cx_no_check in ABAP.
The Class cx_no_check
Exceptions derived from the class cx_no_check need not and cannot be declared in the interface. Those are the exceptions you can expect almost everywhere. Typical examples of these are resource bottlenecks or configuration errors. Normally, nobody wants to nor is able to handle exceptions of this type locally.
The Class cx_dynamic_check - All the Flexibility you Want
Although a particular error could occur in an operation, sometimes you are quite sure that this error cannot happen in your program. Just think of the cx_sy_range_out_of_bounds exception presented to you previously. If before performing this operation, you were to check that the length you want to take from the string is shorter than the string, then the exception cx_sy_range_out_of_bounds would never occur in your program. You do not want to be forced to handle or declare this exception, because you can entirely prevent this exception from occurring .
Abstracting from this particular case we can say that this illustrates checking a precondition of an operation. Regarding all those exceptions which can be prevented by checking a precondition, it would be a good idea, if there were a choice between checking the precondition on the one hand and handling or declaring the resulting exception. This is the idea behind the third group of exceptions, the ones derived from the class cx_dynamic_check.
If an exception of this type is declared or handled, it is not checked statically, that is to say, by the compiler, But at runtime these exceptions can only leave a procedure if they are declared in the raising-clause of the interface.
Before going into the details of what this means for using exceptions of this class let us sum up the three types of exceptions:
The Three Exception Classes in Comparison
cx_static_check: If the exception is not handled inside the procedure, the compiler checks whether the exception is declared in the interface. If this is not the case, the compiler outputs a warning. You might complain that this is a bit weak. But there is a strong reason for this: It does no good to invalidate an entire program just because of a missing declaration of an exception, before it is raised. For example, the exception may be in the branch of an if-statement that is hardly ever reached.
It is the runtime that ensures that the exception can only leave the procedure if it is declared in the interface. If not, an exception of the type cx_sy_no_handler is raised and the previous exception is attached to the attribute "previous" of this exception. Only one exception can be active at the same time, and if you did not keep the reference in the attribute, the original exception would be lost.
cx_dynamic_check: There is no design-time check, but at runtime an exception cannot leave a procedure if it is not declared.
cx_no_check: The exception can leave a procedure in any case. There is no design time and no runtime check if the exception has been declared. It is not even possible to declare this exception at all.
When to Choose Which Exception Class - a Decision Worth Some Consideration
To understand how the three different exception classes work was the easy part. The really difficult job is to decide which exception to use on which occasion. So take your time to reconsider what you have learned so far and then go on reading the explanation, as to which exception is the right one for a given job. Let us start with cx_no_check.
When to Choose cx_no_check
This is the right superclass for exceptions which can arise almost everywhere. Declaring them in almost every interface would make your code clumsy. If you choose cx_dynamic_check or cx_static_check instead, callers of your procedure will probably write empty handlers or map the exception on one of the cx_no_check type.
When to Choose cx_static_check
Choose cx_static_check if the exception can be handled in a meaningful way by a local handler. If you choose the dynamically checked type instead there is no longer any guarantee that the exception is always dealt with. Choosing no_check instead has the bad consequence that this exception can appear everywhere in your program. If it really is raised it is hard to find out who is responsible for it.
When to Choose cx_dynamic_check
This is one of the reasons why there is the class cx_dynamic_check: clear responsibility. You choose this class, if somebody can preclude the exception by checking a precondition. This is the one who is responsible for the exception, if it is raised. The exception cx_sy_zero_division is typical of this type. The responsibility for this exception being raised rests with whatever caller provides the divisor. If you perform a division and the divisor is passed from your caller, you should declare the exception in the interface while the program that provides the divisor should check the precondition or handle the exception.
If the exception is really thrown, you easily find the one responsible for it. It is the one whose job it was to test the precondition or who has forgotten to declare it in the interface. If you choose the static type instead people are forced to declare this exception though they can preclude it or they might feel tempted to write empty handlers.
Choosing no_check instead makes it harder to track down who is responsible for the exception. If the cx_sy_zero_division were of the no_check type, you could only see where the zero division occurred, but not who was responsible for it.
If programmers stick to the rules, exceptions derived from the class cx_dynamic_check make it easy to find out who was responsible for an exception and not only where it was raised.
To make a long story short: Always choose the dynamic type if the exception can be precluded by checking a precondition. Always check this condition, if it can be done without too much effort. Examples of where it might not be feasible to choose the dynamic type would be if the exception is the result of a select-clause non-empty or if it is just too expensive to handle the exception. On the other hand, if the precondition depends on a value given by your caller or if you are not responsible for some other reason, do propagate the exception.
I must confess that the dynamic type is a bit tricky to understand and it took me some time to get the knack of it, but I do think it is well worth pondering over: The benefits of this type make up for the efforts needed to understand it.