Вы находитесь на странице: 1из 19

The New Class-Based Exception Handling in ABAP - Part 1

Class-Based Exceptions in ABAP - a Sketch of the New Concept


In this series of weblogs I will introduce the class-based exception concept available as of SAP Web AS 6.10. The first weblog sketches how the new concept works, tells you how to plan classbased exception handling, and provides you with a simple code example of how to raise and handle a class-based exception. 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: I will use the term class-based exception throughout these weblogs to differentiate between the new exceptions and the old error handling devices in ABAP, which are also called exceptions. While the old error handling worked with return codes, the new class-based exception handling is a completely new concept. Think of what you would most likely expect from an error-handling concept: When something goes wrong during program execution, at first you would require the control flow to adapt to the new situation. Secondly, you would surly like to have some information on what went wrong and know the origin of the error. Both of these jobs are done by exception objects. When an exception object is generated and an exception is raised or thrown, the exception serves two functions: 1. It changes the control flow. 2. It carries information about the details of the error.

How Is an Exception Realized?


Class-based exceptions are objects of different specific exception classes which can use attributes to supply additional information about the error type they represent. You can also pass additional specific details about a particular error to the object at runtime, when the object is generated. This is the information part of an exception. Once an exception of a particular type or class is raised, the runtime looks for the first suitable handler by going first to the procedure where the exception occurred, and then by going up the call stack. When a suitable handler is found, the control flow proceeds with that handler's code. The handler should contain some code to resolve the error: to repair it, to abort some procedures or to end processing of the whole program-component, in which the error occurred. The appropriate handling of an exception depends on a thorough semantic analysis of the kind of error that occurred and its impact.

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.

Think as Locally as Possible


Regarding the scope of this consideration let me utter a warning: If you want to write reusable software, plan the exception handling exclusively for the program segment that you have written. This means: Try to avoid any specific assumptions about the layers that you call and those called by you which go beyond the contract specified in the respective interfaces. The layers calling your component could potentially change. In such cases your exception handling might not work any longer, if it depends on specific assumption about your caller's program layout. What this means is that you should only handle the exceptions you can meaningfully deal with and delegate those exceptions that you cannot handle to the callers of your program component Compare your component behaviour to that of a manufacturer: A manufacturer only relies on suppliers to supply what is specified in some contracts. The manufacturer does not care about where and how the suppliers get their products, as long as they meet the conditions specified in the contract. The same applies to the consumer of the goods of our manufacturer. Let us call it the rule of minimal global assumptions. Only by sticking to this rule, you can write components that support a working modularization of software. But of course, you should take this rule with a grain of salt. It is a general rule, and in some situations there may be good reasons to deviate from it. For example, sometimes you know for certain that you are writing a program component which is only called by the user interface. But generally you are well advised to stick to the rule of minimal global assumptions.

What Does an Exception Handler Do?


Now after digressing a bit to this above mentioned condition for writing modularized software, let me state another simple, yet important point about exception handling: There should be some coding in the handler to cope with the error that has occurred. Of course, an empty handler is a handler in the syntactic sense. But in the full sense of the word, an empty handler just will not do. For example, if a cache is defunct, the values from this cache that you need should be calculated again in the handler. If a particular semantic unit no longer makes sense, because its preconditions are not fulfilled (no delivery possible without an address), it should be canceled and the program should proceed with the next action not affected by the error. In case there is no handler at all for an exception in the whole call hierarchy, the program ends with a short dump. This is the way exceptions control the program flow. The Mechanism of Exceptions - a Simple Picture Perhaps a simple analogy will help to understand the way exceptions work: Think of an exception of a particular class as an object of a particular shape. Once the exception is thrown,

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.

System- and User-Defined Exceptions


There are two flavors of exceptions: 1. Those raised by the runtime system, if a error is detected. 2. Those generated explicitly in the ABAP code. A typical exception of the first kind is the division by zero, which is arithmetically not defined and so has no result value. If the ABAP runtime environment is confronted with a division by zero, it stops the computation and throws an exception of type cx_sy_zerodivide. You use exceptions of the second kind to signify a program state which makes the normal execution impossible. There are situations when the exceptional situation is due to a semantic mistake: The program should not schedule the details of a delivery if the customer has no address. In these cases you raise a user-defined exception. All exception classes in ABAP, whether system exceptions or the user defined ones, are indirectly derived from the class cx_root.

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.

The Code to Catch an Exception

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.

So the abstract structure is:

The New Class-Based Exception Handling in ABAP - Part 2

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:

The Standard-Text of an Exception - the Method get_text( )


Now let us start by changing something about the error text in our example to make it a bit more specific. Every exception class has a method get_text( ) which provides information about the particular exception. The texts accessed by this method can be kept in the Online Text Repository (and be maintained in the class builder, transaction SE24) or in Table T100 (and be maintained in message class maintenance, transaction SE91) respectively, to make them locale independent. Use the error text only for purposes of information in case of a short dump or an error message. Never ever parse the error text in your code in order to obtain information you can use for the error handling. Take this information from attributes of the exception. When you are the one raising an exception, your exception text should be specific enough to provide a comprehensive description of the error. But when you are the one who handles an exception and decide to output some information always consider who will be reading your text. Texts written to a log file for an administrator or a developer should be different from texts written for an end user. The layman will not at all appreciate information like "division by zero in program ZV_AX100 in Include XRZ", while a developer will surely be interested in it. Keeping in mind the rule of minimal global assumptions, you should, of course, only output some text in the user interface if you know that the component you write is properly positioned to give out some information. But let us return to our example. To access the text with get_text(), we must first catch the exception into an appropriately typed variable:
<span>DATA: w ord TYPE string VALUE 'Hallo',ex TYPE REF TO cx_sy_range_out_of_bounds,mes type string.TRY. PERFORM truncate_at USING length CHANGING w ord. CATCH cx_sy_range_out_of_bounds INTO ex. mes = ex->get_text( ). WRITE mes.ENDTRY.</span>

With length = 10 and word = "Hallo" the output mes is:

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.

Why Delegate the Handling of an Exception? - a Short Consideration


To a certain degree exception handling is much like solving real-life problems. Often it is better to delegate the solution of a problem to someone better positioned to solve it or to someone responsible for it. Regarding our example, it would make sense to dispense with the exception handling in our subroutine. If the caller of the subroutine chose the string and the length, the caller is responsible for the error. So why not leave the exception handling to him? With ABAP class-based exceptions this is possible. You can delegate the handling of an exception to higher levels in the call hierarchy. Remember I have already told you to avoid, wherever possible, assumptions about the callers of your component. So just decide whether or not you are responsible for an exception and can handle it in a semantic sense (of course writing an empty handler will not do). If you are not responsible, somebody up in the call hierarchy will again have to make the same decision whether to handle or delegate the exception. But if you delegate the exception it is not your job to rack your brain over, who eventually handles the exception. I will show you how to do this in the next weblog of this series.

How to Delegate the Handling of an Exception


At runtime an exception that is not handled locally moves up the call stack. This is what is called propagation. In general, exceptions can only leave an interface at runtime if they are declared in the interface (actually this is not true for all exceptions, as I will show later in another weblog. But for now we won't worry about the unchecked exceptions). So the first thing we have to do is to declare the exception we want to delegate. We extend the interface of the subroutine by adding "raising cx_sy_range_out_of_bounds"

<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.

Why Declare an Exception in the Interface?


You may wonder what exactly is the point of declaring exceptions? Why make the Raising clause part of the interface? Just the way signs such as: "Beware of the Dog" or "Deer Crossing" informs a passer-by which animals to expect, the raising clause tells the caller of a procedure which exceptions need to be taken into account. As the caller of a routine you therefore know which exception you have to handle or delegate. This is the semantic point of declaring exceptions. Now let us have a look at how this liability is technically enforced.

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 New Class-Based Exception Handling in ABAP - Part 3


In part 3 of this weblog series about the new class-based exceptions in ABAP you learn how to handle several exceptions in one CATCH-clause, how to deal with different exceptions in different CATCH-clauses and what a cleanup-clause is good for. In the more conceptual part of this weblog I treat some more basic semantic topics about exceptions: Which different routes can you take in handling an exception: What should happen inside an exception handler? When to raise an exception at all? This weblog is part of a series on the new class-based exception handling concept in ABAP.

Raising and Catching Different Exceptions - an Example


In a normal program you are usually confronted with exceptions of different types. So let me show you how to handle exceptions in these situations:
1 TRY.2 PERFORM truncate_at USING length CHANGING w ord. "can raise "cx_sy_range_out_of_bounds - w ord is the string 'Hallo'3 PERFORM some_form "can raise cx_ex1.4 CALL FUNCTION 'MYFUNCTION' "exception of class cx_ex2.5 CATCH cx_sy_range_out_of_bounds cx_ex1.6 "some exception handling code w hich may raise exception cx_outside.7 CATCH cx_root.8 "some other exception handling code.9 ENDTRY.

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.

What Do Exception Handlers Do?


Up until now we haven't worried about what happens inside an exception handler. To more easily understand this, let us use an analogy of a journey to an error in a program. Suppose you are forced to stop your journey because a bridge over a certain river you need to cross is broken. You cannot proceed along the route you have planned. This is our error situation. How can you cope with this? 1. You can reroute and take a different bridge somewhere downstream or upstream to reach the destination of the day nevertheless. The analogy in a program would be the case in which you have to recalculate some value, because a cache is defunct. 2. You can abandon this trip and go on with what you have planned next in your holiday. - If a certain delivery in an ERP application cannot be processed because there is no address, the control flow can proceed with the next delivery. In this case you should send a message to the user or write some information in a log file. 3. You can decide that the whole journey does not make much sense, because the time schedule cannot be kept if you have to go a long way around. So you choose to finish

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.

Whether or not to Use an Exception


Stepping back a bit from the specific ways in which you deal with an exception once it occurs, you are faced with a basic decision of whether or not to treat a situation as an exception at all. Except for a syntactical error, a situation usually does not come labeled as an exception. But it is up to you to assess it as an exceptional situation or as part of the normal course of events. As a rule of thumb you might say: If some situation is within the realm of expectation, you had better use a return code to indicate it. Reserve exceptions for those situations that exceed the range of the normal.

The Cleanup Section


Once an exception is raised the control flow continues with the next suitable handler which means that the normal flow of control is changed. There may be good reasons to handle some exceptions on a high level in the call hierarchy. This would have the consequence that one or many procedures end prematurely which in turn may leave the application in an inconsistent state or keep resources unreleased. These are the situations the cleanup clause is designed to deal with. The cleanup section is between the last handler and the ENDTRY. It is processed only if some conditions are fulfilled:

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.

Some Peculiarities of the Cleanup Block


Let me say a few words about why the CLEANUP block is only processed if an exception is thrown and then caught somewhere up in the call hierarchy. If an exception is not caught at all, you get a short dump. In such a situation you are interested in all traces that might lead to the source of the mistake. Processing the CLEANUP block would mean blurring the very traces you are looking for in this situation. Never ever leave the CLEANUP clause abnormally with the keywords RETURN, EXIT or CONTINUE. The CLEANUP clause is processed with the promise that the respective exception will be caught and handled in the program. Leaving the CLEANUP clause prematurely could have the consequence of breaking this promise. Take care: During design time you only get a warning, if the CLEANUP clause might be left abnormally. But at runtime there is a short dump, if such a CLEANUP clause is ever tried to be left abnormally.

The New Class-Based Exception Concept in ABAP - Part 4


In the previous weblogs of this series the focus was how to raise, delegate, and handle exceptions. This weblog gives an example how to build a user-defined exception class, how to provide one or several standard-texts for this class, and how to insert parameters in these standard-texts. These parameters are filled by actual values passed to the exception object when it is created. This weblog is part of a series on the new class-based exception handling concept in ABAP.

Building Our Own Exception Class


Now it is time to build our first exception class. Suppose we have a program which allows you to withdraw money from a savings account. When you try to draw more money than exists in the account, an exception should be thrown. We choose to build a global (local would also be possible) exception class cx_out_of_money. We open the classbuilder (transaction SE24) or the object navigator (transaction SE80) and start to create the exception class. Using the prefix cx for the class name signals to the classbuilder that we want to build an exception class, and it then supplies us with the default settings needed for an exception class. We choose the superclass that is proposed by the classbuilder: cx_static_check. This means that the compiler checks if the exception is declared the interface in case it is not handled locally. (I will show in another weblog why there are also two other exception superclasses. For classes derived from them the enforced declaration is less strict or rather does not exist at all.) If we selected the checkbox "With Message Class" (only available since 6.40), we could use table T100-based texts. As a prerequisite, these texts must first be input using transaction SE91, as you cannot do this from the tab "Texts" in the classbuilder. We choose to leave the checkbox unselected which means we will maintain our texts in the Online Text Repository (OTR), which is accessible from the classbuilder. Let me remind you again about how to use these texts: If you raise an exception, be sure to pack an exact description of the error into the text. If you handle the exception and think it necessary to output some information, always consider who is the addressee of this message. The end user will only be able to understand semantic exceptions concerning the business logic. Do not pass technical exception texts to the end user rather write them to a log file. If it is absolutely necessary to inform the end user about the technical problem, catch exceptions with texts he or she will not understand and raise new exceptions with texts on an end-user level. This is what is called mapping an exception.

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.

The Standard Attributes of an Exception


Next you see the attributes tab:

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.

Preparing an Exception Text


For real-life programs do not write exception texts in attributes, but rather in the tab "Texts", where the OTR enables an easy translation of its entries. But of course, this is only a corollary of the rule that you should never write texts for the end user by hard-coding them in strings, but instead provide translatable texts. We write an appropriate text for our exception in the OTR. Write the text for an exception in the line where an exception ID with the name of your class is already prepared by the classbuilder. In our example the exception is thrown, when you try to withdraw more money than is in your savings account. The exception class should also contain some information about the actual balance in the account and the amount you wanted to withdraw. The values of attributes can be part of an exception. First we create two new instance attributes, which hold the values we want insert in our exception text. We switch again to the tab rider "Attributes" and create the attributes

"amount" and "savings".

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.

How to Pass Text to the Standard Exception Text at Runtime


You access the text of an exception with the method get_text( ). The content of this text is determined when the exception object is created. When you raise an exception, the text of the exception object is, by default, the text with the ID of the exception (it is also possible to create an exception object first and then raise an exception with this object, but I will not treat this here). Take a look at how we pass the actual values myamount and mysavings to the exception when it is raised.

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."

Different Texts for One Exception Class


Suppose you want to use the exception class cx_out_of_money with a different message, let us say: "No money in the savings account." To do this we insert an additional line in the text tab; we name the text ID "no_money" (as with variables, choose speaking names for those IDs, rather than using IDs like "additional_text") Next we fill it with the text we want. Looking at the attributes tab you find that there is an attribute additional_text keeping a reference to the new entry in the OTR. When we create the exception we now have to pass the reference to this field to the parameter TEXTID: IF mysavings = 0. &#160RAISE EXCEPTION TYPE cx_out_of_money exporting textid = &#160cx_out_of_money=>no_money. ENDIF. By the way, let me just mention a common mistake which might easily occur when you are in a hurry and not yet accustomed to the new class-based exceptions in ABAP: Though catching and handling the exception, you still get a short dump when running the program. If that happens, check that you have not coded: raise cx_out_of_money. This raises an old exception, which cannot be caught by a CATCH clause for the new object-oriented exceptions. Make sure that you have the correct code to raise an exception: raise exception type cx_out_of_money. This way you make sure that you do not mix old and new exceptions in one procedure.

The New Exception Handling Concept in ABAP - Part 5


After reading the previous weblogs of this series you now know how to create user-defined exception classes, raise and handle exceptions, and know details about some additional special topics. In this weblog you will learn about the three different kinds of exceptions that exist in ABAP and what each kind should be used for. This weblog is part of a series on the new class-based exception handling concept in ABAP.

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 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

Вам также может понравиться