Академический Документы
Профессиональный Документы
Культура Документы
To do this, first figure out what the effect of an error is, then decide what the best reaction to it is, and only as a third step try to find out how you can implement the error handling.
the particularly shaped object flies up the call stack until it finds a slot that matches its shape. This is the handler that catches it. When it is caught, the information encapsulated in the object can be accessed. If there is no suitable handler at all, the program ends with a short dump.
A Simple Example
Before going into the details of class-based exception handling and highlighting its advantages and benefits let us start with a simple and straightforward example:
<span>FORM truncate_at USING length TYPE i CHANGING w ord TYPE string. TRY. w ord = w ord(length). WRITE w ord. CATCH cx_sy_range_out_of_bounds. WRITE 'You tried to address a length that exceeds the length of the w ord'. ENDTRY.ENDFORM.</span>
This subroutine is intended to truncate a word by selecting (length) letters from the left and cutting off the rest. Obviously you get a mistake if you try to grab more letters than the length of the word. If this access fails the runtime environment throws an exception of the type cx_sy_range_out_of_bounds. We catch it and output some information text about what has gone wrong.
Now let me give a more detailed explanation of the code you need to catch an exception. Exceptions can only be handled if they are raised in the protected part of a TRY-ENDTRY construct. The protected part comprises the statements between the TRY and the first exception handler. A handler consists of a CATCH clause and the following statements up to the next CATCH clause or the ENDTRY (if it is the last handler). The CATCH is always followed by the name of one or more exception classes. A CATCH-clause catches only the exceptions of the classes named in the catch-clause and the exceptions that belong to subclasses of those classes. The statements following the CATCH clause in the handler are executed only if a handler catches an exception. All handlers are positioned between the protected section and the ENDTRY. After a handler is processed, the control flow proceeds to the next statement after the ENDTRY.
Leaving details aside, you should keep in mind the following: Write the code which might throw an exception in a protected section shielded by a TRY-ENDTRY construct. Catch the errors in one or more catch clauses after the protected section. A handler can only handle an exception if the name of its class (or superclass) is part of its CATCH clause. The statements of a handler are only processed under the condition that the respective exception is caught. If no error occurs in the protected section, the statements in the catch clauses are not processed.
In the last weblog on class-based exceptions in ABAP you learned some basics about the concept. In this weblog we refine the example and show how to use the standard-text of an exception object, why and how to delegate the exception and how to declare it in the interface.</p><p>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:
You may wonder how the predefined system exception object in our example knew the correct offset, length, and length of the accessed object. When raising an exception you can pass values to attributes of the exception object. These attributes in turn can be parameterized in the information text of the exception. In our example the actual values are passed to the system exception when it is raised. You will learn more about the details of how this is done a bit later when I show you how to create your own exception classes. But before that, you need to have a better understanding of some other aspects of exceptions.
<span>FORM truncate_at USING length TYPE i CHANGING w ord TYPE stringRAISING cx_sy_range_out_of_bounds.***This indicates to the caller of the subroutine that he must be prepared to handle an ***exception of the named type. The calling program looks like this:Try. PERFORM truncate_at USING length CHANGING w ord. CATCH cx_sy_range_out_of_bounds. WRITE 'w rong offset access'.EndTry.</span>
Note that the subroutine which raises the exception can now do without a TRY-ENDTRY block because the whole subroutine is part of the protected area of the calling program.
1. At design time the compiler checks that all exceptions in procedures (methods, subroutines, and function-modules) are handled inside the procedure or declared in the interface and thus delegated to the caller. 2. The runtime system assures that only exceptions that are declared in the interface can leave a procedure.
The exceptions from the subroutines in line 2 and line 3 are caught by the CATCH clause in line 5. As the example shows, one CATCH clause can cover many exception classes. The exception cx_ex2 is caught by the clause in line 7, because cx_root is the superclass of all exception classes. All exceptions are derived from this class. And as each catch clause also catches all exceptions of its subclasses, this clause catches all possible exceptions. So what about the exceptions caught in line 5? Why are they not caught by the clause in line 7 instead? The answer is simple: I have told you that an exception looks for the first handler that catches it. This means: The order of the catch clauses is important. A bit trickier is this case of nested TRY-ENDTRY-constructs within one procedure or on the call stack. Let us compare these constructs to a set of nested brackets. Each pair of brackets has its own set of handlers uniquely related to the pair. If an exception occurs in line n, first the set of handlers of the innermost TRY-ENDTRY-construct around line n is searched through for a suitable handler.
If none is found, the next outer construct is looked through, and so on. Once a suitable handler is found, the flow of control flow processes the code in this handler and then continues after the corresponding ENDTRY. Obviously, if you wrote the more generic catch clause of type cx_root before the catch clauses that catch more specific exceptions, the more specific catch clauses would never be reached. To forestall this, the compiler checks if the catch clauses are sorted in ascending order. If they aren't you get a syntax error. Let's look at the exception cx_outside which may be raised in the handler in line 6. Do you know that cx_outside is not caught by the CATCH cx_root clause in line 7? Does this surprise you? You probably expected that the statement CATCH cx_root which catches all exceptions caught cx_outside in line 7, but remember: an exception is only caught if it occurs within the protected area. If you recall, the protected section goes from the TRY to the first CATCH clause. In this case the handler is not part of the protected section. Of course you can resolve this by enclosing another TRY-ENDTRY construct in a handler.
A Hint of Caution
By the way, caution should be taken when catching cx_root in your program. Since this is the root class of all exceptions classes, you catch all exceptions whatsoever with it. It is quite obvious that it hardly makes any sense to catch all possible errors in one clause somewhere in your program. What error handling code could you write to guarantee a semanticly meaningful execution of your program for whatever exceptionable situation occurs? Use this catch clause only at a very high level in the call hierarchy to preclude short dumps. Think of a couple of tests in ABAP Unit: The test driver should proceed with the next test, if some test faces an error.
the journey. This can be compared to an exception which affects the program so deeply, that it is best to end the program. So before handling an exception, you have to first get a clear idea of how deeply a particular error impacts the component you develop. As I have told you before, you should avoid assumptions about the global layout of the components that call you. If it is your task to provide the address of a customer and there is none for a particular name, delegate the exception to your caller. It is up to the caller to decide how to go on without an address.
An exception is raised in the protected area. The exception is not dealt with in this TRY-ENDTRY block, but somewhere up in the call hierarchy.
So let us change our example a bit and have a look at how a CLEANUP block works:
1 TRY.2 PERFORM someform.3 CATCH cx_ex1.4 "some other exception handling code.5 ENDTRY.6 "some other code7 FORM someform raising cx_ex1 cx_ex2.8 TRY.9 PERFORM truncate_at USING length CHANGING w ord. "can raise "cx_sy_range_out_of_bounds10 RAISE EXCEPTION TYPE cx_ex1.11 CALL FUNCTION 'MYFUNCTION' "exception of class cx_ex2.12 CATCH cx_sy_range_out_of_bounds.13 "some exception handling code w hich may raise exception cx_outside.14 CLEANUP.15 "some cleaning up16 ENDTRY.17 ENDFORM.
What happens to the exceptions in this example? The cx_sy_range_out_of_bounds exception is caught in line 12. If exception cx_ex1 is raised, the CLEANUP block is processed, because it is caught up in the call hierarchy in line 3. Let us suppose that cx_ex2 is caught nowhere in the whole program. This means: If cx_ex2 is thrown, the CLEANUP block is not processed for this reason.
Going on with building our exception class, let us skip the usual screen for assigning your class to a package. You probably already know how to do this.
These are the attributes inherited from "cx_root", from which all exception classes are indirectly derived:
The attribute "previous" can keep a reference to any exception. This is important if an exception is mapped to another one. Someone might still be interested in the original exception, so a reference to it can be kept in this attribute. The attribute cx_root is a reference to a generic exception text which belongs to the root class of all exceptions. With the attribute "textid" you have a choice between different texts for one exception class. You can even choose the standard text from cx_root. You will learn the details about this later.
Now let's go back to the tab rider "Texts" putting the name of the respective attribute between ampersands in the text. When the text is returned by the method get_text, the actual values of the attributes are inserted:
When inserting the text and then saving it, don't worry about the different OTR pop-up windows that open. Just confirm everything and return to the attributes tab. There is now an automatically created attribute with a reference to this OTR entry.
REPORT savingaccount.PARAMETERS: myamount TYPE i.DATA: mysavings TYPE i VALUE 5000,plannedbalance TYPE i,ex TYPE REF TO cx_out_of_money,mes TYPE string. TRY. plannedbalance = mysavings myamount. IF plannedbalance < 0. RAISE EXCEPTION TYPE cx_out_of_money EXPORTING amount = myamount savings = mysavings. ENDIF. CATCH cx_out_of_money INTO ex. mes = ex->get_text( ). MESSAGE mes TYPE 'I'. ENDTRY.
If we run the program and set myamount to 10000, we get the error message: "Withdrawal of 10000 not possible: Savings only 5000."
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 Choosecx_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 Choosecx_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 Choosecx_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