Preparing for different kinds of error situations is a tricky element of programming that can require even more code that the actual solution to the problem at hand.
Reacting to error situations is often also difficult.
Due to different execution paths the code easily becomes messy.
All kinds of clean up can be needed when an error occurs and it is not rare for it to be left undone.
Recovery from an error in a way that the program execution could still continue requires that operations left unfinished can somehow be cancelled which may not even be possible.
In programming, it is a reasonable starting point to prepare for others acting incorrectly.
When an error is caught early it is possible to adapt to it and in the best case scenario even the program execution can be continued so that the error is not visible in the external behaviour of the program.
When an error occurs in a software component, there are several different ways to react to it.
It depends on the situation, which is the most suitable.
Abrupt termination is the most extreme way to react to an error occuring in a program.
Abrupt termination literally terminates the execution immediately when an error occurs. While it typically is the default in runtime environments it should be avoided.
Abort, exit aims to clear the resources allocated by the software and to notify the user of the error.
In addition the error situation is logged somewhere before terminating the execution.
Continuation ignores the error. This is a really rare situation as an error is by nature something that requires handling in the program.
Rollback returns the program state to where it was before the operation that caused the error.
Implementing rollback is challenging and can be impossible.
Recovery is aborting some local part of the program.
Some part of the program is not able to handle the error on its own but it aims to clear its own state i.e. to free the resources it has allocated and then to notify some other part of the program of the error.
It is not possible to recover from or rollback all error situations as the situation may not be fixable even if it is tried.
As an example of this is running out of memory: if there is no memory available any more, it is really difficult to do any recovery operations.
It is also good to define what is meant by an error in the first place.
An error can be either external or internal.
In an external error the program is asked to do someting it cannot or is not able to do.
In an internal error the implementation itself runs into a situation where something goes wrong.
The easiest step is typically noticing the error situation.
Information hiding and encapsulating the implementation behind an interface also cause that the location of the error is not known by the caller of the service.
A way to notify the caller of the service of the error as well as to offer a possibility to react to is suitable is needed.
Exceptions are a suitable mechanism for this purpose.
In object oriented programming encapsulation hides the implementation of the service from the user of the method.
This applies also to possible error situations. The implementation can also be changed without the user noticing.
Furthermore, polymorphism means that the user doesn’t even know what object they are using at each point in the program.
Inheritance also moves the implementation of the services to several levels in the class hierarchy.
The documentation of the interface must hence also depict those error situations and exceptions that the user may need to react to.
In inheritance the subclasses are not allowed to break the promises of the baseclass i.e. they are not allowed to cause error situations or exceptions not documented for the base class.
A baseclass must thus not promise too much on the error or lack of them.
The baseclass is not aware of what kind of subclasses will be inherited from it.
Documenting error situations is easier if common terms can be used for different situations.
In design by contract error situations are a part of the conditions.
Documenting them can be done by using so called exception guarantees that in addition to the exception itself depict the state the object is in once the exception has occured.
Exception guarantees originate from C++ but are general purpose concepts for any programming language.
Their idea is to categorize reacting to errors into simple basic cases.
The guarantees depict that the user can expect from the object in situations where a service from the interface throws an exception out of it.
Minimal guarantee guarantees that the object does not waste resources when an exception is thrown and the the object can be destroyed or reset in the program but is not otherwise usable.
The class invariant does not necessarily hold after the exception.
Basic guarantee guarantees the an exception thrown out of the service of the interface leaves the object into an unpredictable but as such sensible state.
The class invariant still holds and the object is usable even if its state is not predicatable.
Strong guarantee guarantees that the service is either completed without errors or in case of an exception the state of the object remains as it was before the service was called.
Strong guarantee is commit or rollback as an operation.
While it is nice for the user, it is often difficult to implement.
Nothrow guarantee is the so called programmer’s heaven where the service guarantees never to leak an exception out of the interface. The operation always succeeds and if an exception does occur, the method handles it by itself.
In addition but separate to these there is exception neutrality which means that a software component leaks the exceptions thrown by its
internal components as is without changing them.
The exception neutral services can of course temporarily catch the exception, it is just thrown again forward.
For example forEach() in ArrayList is one such service that relays exceptions thrown by the action to the caller.
In Java the finally block helps with implementing basic and strong quarantees as with is local cleanup operations can be made.
Exception Safety (kesto 24:20)
Preparing for error situations in programming
is easy and makes the program structure clearer
may require all kinds of clean up work that is easily left undone
includes an assumption that others can act incorrectly
is difficult and makes the structure of the code more messy
means that the program always recovers from error situations
In exception safety
the documentation of the interface depicts error situations and exceptions that the user sees
error situations do not occur
the importance of errors and exceptions for the caller increases as encapsulation hides the implementation
encapsulation hides the errors and exceptions created in the interface implementation from the caller
subclasses are not allowed to break the promises of the baseclass
In exception guarantees
the class invariant of the object still holds in the basic guarantee
in the strong guarantee the state of the object remains as it was before the method was called
depict what the user can expect from the object in situations where a method of the object throws an exception for the caller to handle
the aim is always strong guarantee
nothrow means no exceptions occur in the implementation of the method