416 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.2 is not listed (explicitly or through when others),the routine will pass it to its caller;if there is no caller,meaning that the failed routine is the main program,execution terminates abnormally. In the example there is no need to go to the caller since the exception,just after being raised,is caught by the exception clause of the routine itself,which contains a subclause when Negative =>.. But what then do the corresponding instructions do?Here they are again: put ("Negative argument") return In other words:print out a message-a delicate thought,considering was happens next;and then return to the caller.The caller will not be notified of the event,and will continue its execution as if nothing had happened.Thinking again of typical applications of Ada,we may just wish that artillery computations,which can indeed require square root computations,do not follow this scheme,as it might direct a few missiles to the wrong soldiers(some of whom may,however,have the consolation of seeing the error message shortly before the encounter). This technique is probably worse than the C-Unix signal mechanism,which at least picks up the computation where it left.A when subclause that ends with return does not even continue the current routine(assuming there are more instructions to execute);it gives up and returns to the caller as if everything were fine,although everything is not fine. Managers-and,to continue with the military theme,officers-know this situation well: you have assigned a task to someone,and are told the task has been completed-but it has not.This leads to some of the worst disasters in human affairs,and in software affairs too. This counter-example holds a lesson for Ada programmers:under almost no circumstances should a when subclause terminate its execution with a return.The qualification "almost"is here for completeness,to account for a special case,the false alarm,discussed below;but that case is very rare.Ending exception handling with a return means pretending to the caller that everything is right when it is not.This is dangerous and unacceptable.If you are unable to correct the problem and satisfy the Ada routine's contract,you should make the routine fail.Ada provides a simple mechanism to do this:in an ex ception clause you may execute a raise instruction written as just raise whose effect is to re-raise the original exception to the caller.This is the proper way of terminating an execution that is not able to fulfill its contract. Ada Exception rule The execution of any Ada exception handler should end by either executing a raise instruction or retrying the enclosing program unit
416 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.2 is not listed (explicitly or through when others), the routine will pass it to its caller; if there is no caller, meaning that the failed routine is the main program, execution terminates abnormally. In the example there is no need to go to the caller since the exception, just after being raised, is caught by the exception clause of the routine itself, which contains a subclause when Negative => … But what then do the corresponding instructions do? Here they are again: put ("Negative argument") return In other words: print out a message — a delicate thought, considering was happens next; and then return to the caller. The caller will not be notified of the event, and will continue its execution as if nothing had happened. Thinking again of typical applications of Ada, we may just wish that artillery computations, which can indeed require square root computations, do not follow this scheme, as it might direct a few missiles to the wrong soldiers (some of whom may, however, have the consolation of seeing the error message shortly before the encounter). This technique is probably worse than the C-Unix signal mechanism, which at least picks up the computation where it left. A when subclause that ends with return does not even continue the current routine (assuming there are more instructions to execute); it gives up and returns to the caller as if everything were fine, although everything is not fine. Managers — and, to continue with the military theme, officers — know this situation well: you have assigned a task to someone, and are told the task has been completed — but it has not. This leads to some of the worst disasters in human affairs, and in software affairs too. This counter-example holds a lesson for Ada programmers: under almost no circumstances should a when subclause terminate its execution with a return. The qualification “almost” is here for completeness, to account for a special case, the false alarm, discussed below; but that case is very rare. Ending exception handling with a return means pretending to the caller that everything is right when it is not. This is dangerous and unacceptable. If you are unable to correct the problem and satisfy the Ada routine’s contract, you should make the routine fail. Ada provides a simple mechanism to do this: in an exception clause you may execute a raise instruction written as just raise whose effect is to re-raise the original exception to the caller. This is the proper way of terminating an execution that is not able to fulfill its contract. Ada Exception rule The execution of any Ada exception handler should end by either executing a raise instruction or retrying the enclosing program unit
$12.2 HANDLING EXCEPTIONS 417 Exception handling principles These counter-examples help show the way to a disciplined use of exceptions.The following principle will serve as a basis for the discussion. Disciplined Exception Handling principle There are only two legitimate responses to an exception that occurs during the execution of a routine: RI.Retrying:attempt to change the conditions that led to the exception and to execute the routine again from the start. R2.Failure (also known as organized panic):clean up the environment, terminate the call and report failure to the caller. The classification of In addition,exceptions resulting from some operating system signals(case exception cases, E3 of the classification of exceptions)may in rare cases justify a false alarm including E3,is on response:determine that the exception is hammless and pick up the routine's page 413. execution where it started. Let us do away first with the false alarm case,which corresponds to the basic C-Unix mechanism as we have seen it.Here is an example.Some window systems will cause an exception if the user of an interactive system resizes a window while some process is executing in it.Assume that such a process does not perform any window output;then the exception was harmless.But even in such case there are usually better ways,such as disabling the signals altogether,so that no exception will occur.This is how we will deal with false alarms in the mechanism of the next sections. False alarms are only possible for operating system signals-in fact,only for signals of the more benign kind,since you cannot ignore an arithmetic overflow or an inability to allocate requested memory.Exceptions of all the other categories indicate trouble that cannot be ignored.It would be absurd,for example,to proceed with a routine after finding that its precondition does not hold. So much for false alarms(unfortunately,since they are the easiest case to handle) For the rest of this discussion we concentrate on true exceptions,those which we cannot just turn off like an oversensitive car alarm. Retrying is the most hopeful strategy:we have lost a battle,but we have not lost the war.Even though our initial plan for meeting our contract has been disrupted,we still think that we can satisfy our client by trying another tack.If we succeed,the client will be entirely unaffected by the exception:after one or more new attempts following the initial failed one,we will return normally,having fulfilled the contract.("Mission accomplished, Sir.The usual little troubles along the way,Sir.All fine by now,Sir.") What is the "other tack"to be tried on the second attempt?It might be a different algorithm;or it might be the same algorithm,executed again after some changes have been brought to the state of the execution (attributes,local entities)in the hope of preventing the exception from occurring again.In some cases,it may even be the original routine tried again without any change whatsoever;this is applicable if the exception was due to some
§12.2 HANDLING EXCEPTIONS 417 Exception handling principles These counter-examples help show the way to a disciplined use of exceptions. The following principle will serve as a basis for the discussion. Let us do away first with the false alarm case, which corresponds to the basic C-Unix mechanism as we have seen it. Here is an example. Some window systems will cause an exception if the user of an interactive system resizes a window while some process is executing in it. Assume that such a process does not perform any window output; then the exception was harmless. But even in such case there are usually better ways, such as disabling the signals altogether, so that no exception will occur. This is how we will deal with false alarms in the mechanism of the next sections. False alarms are only possible for operating system signals — in fact, only for signals of the more benign kind, since you cannot ignore an arithmetic overflow or an inability to allocate requested memory. Exceptions of all the other categories indicate trouble that cannot be ignored. It would be absurd, for example, to proceed with a routine after finding that its precondition does not hold. So much for false alarms (unfortunately, since they are the easiest case to handle). For the rest of this discussion we concentrate on true exceptions, those which we cannot just turn off like an oversensitive car alarm. Retrying is the most hopeful strategy: we have lost a battle, but we have not lost the war. Even though our initial plan for meeting our contract has been disrupted, we still think that we can satisfy our client by trying another tack. If we succeed, the client will be entirely unaffected by the exception: after one or more new attempts following the initial failed one, we will return normally, having fulfilled the contract. (“Mission accomplished, Sir. The usual little troubles along the way, Sir. All fine by now, Sir.”) What is the “other tack” to be tried on the second attempt? It might be a different algorithm; or it might be the same algorithm, executed again after some changes have been brought to the state of the execution (attributes, local entities) in the hope of preventing the exception from occurring again. In some cases, it may even be the original routine tried again without any change whatsoever; this is applicable if the exception was due to some Disciplined Exception Handling principle There are only two legitimate responses to an exception that occurs during the execution of a routine: R1 •Retrying: attempt to change the conditions that led to the exception and to execute the routine again from the start. R2 •Failure (also known as organized panic): clean up the environment, terminate the call and report failure to the caller. In addition, exceptions resulting from some operating system signals (case E3 of the classification of exceptions) may in rare cases justify a false alarm response: determine that the exception is harmless and pick up the routine’s execution where it started. The classification of exception cases, including E3, is on page 413
418 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.2 external event-transient hardware malfunction,temporarily busy device or communication line-which we do not control although we expect it will go away. With the other response,failure,we accept that we not only have lost the battle(the current attempt at executing the routine body)but cannot win the war(the attempt to terminate the call so as to satisfy the contract).So we give up,but we must first ensure two conditions,.explaining the use of“organized panic”as a more vivid synonym for“failure”: Making sure(unlike what happened in the sqrt counter-example)that the caller gets an exception.This is the panic aspect:the routine has failed to live up to its contract. Restoring a consistent execution state-the organized aspect. What is a "consistent"state?From our study of class correctness in the previous chapter we know the answer:a state that satisfies the invariant.We saw that in the course of its work a routine execution may temporarily violate the invariant,with the intention of restoring it before termination.But if an exception occurs in an intermediate state the invariant may be violated.The routine must restore it before returning control to its caller. The call chain To discuss the exception handling mechanism it will be useful to have a clear picture of the sequence of calls that may lead to an exception.This is the notion of call chain,already present in the explanation of the Ada mechanism. The call chain Routine call Let ro be the root creation procedure of a certain system(in Ada ro would be the main program).At any time during the execution,there is a current routine,the routine whose execution was started last;it was started by the execution of a certain routine;that routine was itself called by a routine;and so on.If we follow this called-to-caller chain all the way through we will end up at ro.The reverse chain(ro,the last routine r that it called,the last routine r2 that r called,and so on down to the current routine)is the call chain. If a routine produces an exception (as pictured at the bottom-right of the figure),it may be necessary to go up the chain until finding a routine that is equipped to handle the exception-or stop execution if we reachr not having found any applicable exception handler.This was the case in Ada when no routine in the call chain has an exception clause with a when clause that names the exception type or others
418 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.2 external event — transient hardware malfunction, temporarily busy device or communication line — which we do not control although we expect it will go away. With the other response, failure, we accept that we not only have lost the battle (the current attempt at executing the routine body) but cannot win the war (the attempt to terminate the call so as to satisfy the contract). So we give up, but we must first ensure two conditions, explaining the use of “organized panic” as a more vivid synonym for “failure”: • Making sure (unlike what happened in the sqrt counter-example) that the caller gets an exception. This is the panic aspect: the routine has failed to live up to its contract. • Restoring a consistent execution state — the organized aspect. What is a “consistent” state? From our study of class correctness in the previous chapter we know the answer: a state that satisfies the invariant. We saw that in the course of its work a routine execution may temporarily violate the invariant, with the intention of restoring it before termination. But if an exception occurs in an intermediate state the invariant may be violated. The routine must restore it before returning control to its caller. The call chain To discuss the exception handling mechanism it will be useful to have a clear picture of the sequence of calls that may lead to an exception. This is the notion of call chain, already present in the explanation of the Ada mechanism. Let r0 be the root creation procedure of a certain system (in Ada r0 would be the main program). At any time during the execution, there is a current routine, the routine whose execution was started last; it was started by the execution of a certain routine; that routine was itself called by a routine; and so on. If we follow this called-to-caller chain all the way through we will end up at r0. The reverse chain (r0, the last routine r1 that it called, the last routine r2 that r1 called, and so on down to the current routine) is the call chain. If a routine produces an exception (as pictured at the bottom-right of the figure), it may be necessary to go up the chain until finding a routine that is equipped to handle the exception — or stop execution if we reach r0, not having found any applicable exception handler. This was the case in Ada when no routine in the call chain has an exception clause with a when clause that names the exception type or others. The call chain r0 r1 r2 r3 r4 Routine call
$12.3 AN EXCEPTION MECHANISM 419 12.3 AN EXCEPTION MECHANISM From the preceding analysis follows the exception mechanism that fits best with the object-oriented approach and the ideas of Design by Contract. The basic properties will follow from a simple language addition-two keywords to the framework of the preceding chapters.A library class,EXCEPTIONS,will also be available for cases in which you need to fine-tune the mechanism. Rescue and Retry First,it must be possible to specify,in the text of a routine,how to deal with an exception that occurs during one of its calls.We need a new clause for that purpose;the most appropriate keyword is rescue,indicating that the clause describes how to try to recover from an undesirable run-time event.Because the rescue clause describes operations to be executed when the routine's behavior is outside of the standard case described by the precondition (require),body (do)and postcondition (ensure),it will appear,when present,after all these other clauses: routine is require precondition local ..Local entity declarations... do body ensure postcondition rescue rescue clause end The rescue clause is a sequence of instructions.Whenever an exception occurs during the execution of the normal body,this execution will stop and the rescue clause will be executed instead.There is at most one rescue clause in a routine,but it can find out what the exception was(using techniques introduced later),so that you will be able to treat different kinds of exception differently if you wish to. The other new construct is the retry instruction,written just retry.This instruction may only appear in a rescue clause.Its execution consists in re-starting the routine body from the beginning.The initializations are of course not repeated. These constructs are the direct implementation of the Disciplined Exception Handling principle.The retry instruction provides the mechanism for retrying;a rescue clause that does not execute a retry leads to failure
§12.3 AN EXCEPTION MECHANISM 419 12.3 AN EXCEPTION MECHANISM From the preceding analysis follows the exception mechanism that fits best with the object-oriented approach and the ideas of Design by Contract. The basic properties will follow from a simple language addition — two keywords — to the framework of the preceding chapters. A library class, EXCEPTIONS, will also be available for cases in which you need to fine-tune the mechanism. Rescue and Retry First, it must be possible to specify, in the text of a routine, how to deal with an exception that occurs during one of its calls. We need a new clause for that purpose; the most appropriate keyword is rescue, indicating that the clause describes how to try to recover from an undesirable run-time event. Because the rescue clause describes operations to be executed when the routine’s behavior is outside of the standard case described by the precondition (require), body (do) and postcondition (ensure), it will appear, when present, after all these other clauses: routine is require precondition local … Local entity declarations … do body ensure postcondition rescue rescue_clause end The rescue_clause is a sequence of instructions. Whenever an exception occurs during the execution of the normal body, this execution will stop and the rescue_clause will be executed instead. There is at most one rescue clause in a routine, but it can find out what the exception was (using techniques introduced later), so that you will be able to treat different kinds of exception differently if you wish to. The other new construct is the retry instruction, written just retry. This instruction may only appear in a rescue clause. Its execution consists in re-starting the routine body from the beginning. The initializations are of course not repeated. These constructs are the direct implementation of the Disciplined Exception Handling principle. The retry instruction provides the mechanism for retrying; a rescue clause that does not execute a retry leads to failure
420 WHEN THE CONTRACT IS BROKEN:EXCEPTION HANDLING $12.3 How to fail without really trying The last observation is worth emphasizing: Failure principle Execution of a rescue clause to its end,not leading to a retry instruction, causes the current routine call to fail. So if you have wondered how routines can fail in practice-causing case E4 of the See page 413. exception classification-this is it. As a special case,consider a routine which does not have a rescue clause.In practice this will be the case with the vast majority of routines since the approach to exception handling developed here suggests equipping only a select few routines with such a clause. Ignoring possible local entity declarations,arguments,precondition and postcondition,the routine appears as routine is do body end Then if we consider-as a temporary convention-that the absence of a rescue clause is the same thing as an empty rescue clause,that is to say routine is do body rescue --Nothing here (empty instruction list) end the Failure principle has an immediate consequence:if an exception occurs in a routine without rescue clause it will cause the routine to fail,triggering an exception in its caller. Treating an absent rescue clause as if it were present but empty is a good enough For the exact comven- approximation at this stage of the discussion,but we will need to refine this rule slightly tion see“When there when we start looking at the effect of exceptions on the class invariant. is no rescue clause", page 430. An exception history table If a routine fails,either because it has no rescue clause at all or because its rescue clause executes to the end without a retry,it will interrupt the execution of its caller with a "Routine failed"(E4)exception.The caller is then faced with the same two possibilities: either it has a rescue clause that can execute a successful retry and get rid of the exception,or it will fail too,passing the exception one level up the call chain
420 WHEN THE CONTRACT IS BROKEN: EXCEPTION HANDLING §12.3 How to fail without really trying The last observation is worth emphasizing: So if you have wondered how routines can fail in practice — causing case E4 of the exception classification — this is it. As a special case, consider a routine which does not have a rescue clause. In practice this will be the case with the vast majority of routines since the approach to exception handling developed here suggests equipping only a select few routines with such a clause. Ignoring possible local entity declarations, arguments, precondition and postcondition, the routine appears as routine is do body end Then if we consider — as a temporary convention — that the absence of a rescue clause is the same thing as an empty rescue clause, that is to say routine is do body rescue -- Nothing here (empty instruction list) end the Failure principle has an immediate consequence: if an exception occurs in a routine without rescue clause it will cause the routine to fail, triggering an exception in its caller. Treating an absent rescue clause as if it were present but empty is a good enough approximation at this stage of the discussion; but we will need to refine this rule slightly when we start looking at the effect of exceptions on the class invariant. An exception history table If a routine fails, either because it has no rescue clause at all or because its rescue clause executes to the end without a retry, it will interrupt the execution of its caller with a “Routine failed” (E4) exception. The caller is then faced with the same two possibilities: either it has a rescue clause that can execute a successful retry and get rid of the exception, or it will fail too, passing the exception one level up the call chain. Failure principle Execution of a rescue clause to its end, not leading to a retry instruction, causes the current routine call to fail. See page 413. For the exact convention see “When there is no rescue clause”, page 430