Exception Handling |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
With this chapter we start the lecture about exception handling. We could as well just use the word "error handling". Before we approach object-oriented exception handling we will in this chapter discuss error handling broadly and from several perspectives. In Chapter 34 we will discuss non-OO, conventional exception handling. In Chapter 35 we encounter object-oriented exception handling. Finally, in Chapter 36 we discuss exception handling in C#. Chapter 36 is the main chapter in the lecture about exception handling.
|
33.1. What is the motivation?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The following items summarize why we should care about error handling:
|
33.2. What is an error?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The word "error" is often used in an undifferentiated way. We will now distinguish between errors in the development process, errors in the source program, and errors in the executing program.
|
Errors in the development process may lead to errors in the source program. Errors in the source program may lead to errors in the running program |
33.3. What is normal? What is exceptional?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
I propose that we distinguish between "normal aspects" and "exceptional aspects" when we write a program. Without this distinction, many real-world programs will become unwieldy. The separation between normal aspects and exceptional aspects adds yet another dimension of structure to our programs.
In many applications and libraries, the programming of the normal aspects leads to nice and well-proportional solution. When exceptional aspects (error handling) are brought in, the normal program aspects are polluted with error handling code. In some situations the normal program aspects are totally dominated by exceptional program aspects. This is exemplified in Section 34.2.
Below we characterize the normal program aspects and the exceptional program aspects.
|
Let us assume that we program the following simple factorial function. Recall that "n factorial" = Factorial(n) = n! = n * (n -1) * ... * 1.
public static long Factorial(int n){ if (n == 0) return 1; else return n * Factorial(n - 1); }
The following problems may appear when we run the Factorial function:
Problem 1 should be dealt with as a normal program aspects. As mentioned, the problem in item 2 is prevented by the analysis of the compiler. Problem 3 is, in a similar way, prevented by the compiler. Problem 4 is classified as an anticipated exceptional aspect. Problem 4 could, alternatively, be dealt with by use of another type than long, such a BigInteger which allows us to work with arbitrary large integers. (BigInteger is not part of the .Net 3.5 libraries, however). Problem 5 could also be foreseen as an anticipated exception. Problem 5, 7, and 8 are beyond the control of the program. In extremely critical applications it may, however, be considered to deal with (handle) problem 6 and 7.
Let us, briefly, address the numeric overflow problem relative to C#. Per default, numeric overflow in integer types does not lead to exceptions in C#. (The result of the evaluation "wraps around", a gives wrong results). It is, however, possible to embed a piece of program in a checked{...} form, in which case numeric overflow leads to exceptions. It is also possible to ask for another default handling of numeric overflow by use of the compiler option /checked+.
With use of normal control structures, a different (although a hypothetical) type BigInteger, and an iterative instead of a recursive algorithm we may rewrite the program to the following version:
public static BigInteger Factorial(int n){ if (n >= 0){ BigInteger res = 1; for(int i = 1; i <= n; i++) res = res * i; return res; } else throw new ArgumentException("n must be non-negative"); }
With this rewrite we have dealt with problem 1, 4, and 5. As an attractive alternative to the if-else, problem 1 could be dealt with by the precondition n >= 0 of the Factorial method, see Section 50.1.
As it appears, we wish to distinguish between normal program aspects and exceptional program aspects via the programming language mechanisms used to deal with them. In C# and similar object-oriented languages, we have special means of expressions to deal with exceptions. The Factorial function shown above throws an exception in case of negative input. See Section 36.2 for details.
Above, we distinguish between different degrees of exceptional aspects. As a programmer, you are probably aware of something that can go wrong in your program. Other errors come as surprises. Some error situations, both the expected and the surprising ones, should be dealt with such that the program execution survives. Others will lead to program termination. Controlled program termination, which allows for smooth program restart, will be an important theme in this lecture.
33.4. When are errors detected?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
It is attractive to find errors as early as possible |
Our next question cares about the point in time where you - the program developer - realize the problem. It should be obvious that we wish to identify troubles as soon as possible.
We identify the following error identification times.
|
If we are clever enough, we will design and program our software such that errors do not occur at all. However, all experience shows that this is not an easy endeavor. Still, it is good wisdom to care about errors and exception handling early in the development process. Problems that can be dealt with effectively at an early point in time will save a lot of time and frustrations in the latter phases of the development process.
Static analysis of the program source files, as done by the front-end of the compiler, is important and effective for relatively early detection of errors. The more errors that can be detected by the compiler before program execution, the better. Handling of errors caught by the compiler requires very little work from the programmers. This is at least the case if we compare it with testing efforts, described next.
Systematic test deals with sample execution of carefully prepared program fragments. The purpose of testing is to identify errors (see also Section 54.1). Testing activities are very time consuming, but all experience indicates that it is necessary. We devote a lecture, covered by Chapter 56 in this material, to testing. We will in particular focus on unit test of object-oriented programs.
Software test is also known as validation in relation to the specification of the software. Alternatively, the program may be formally verified up against a specification. This goes in the direction of a mathematical proof, and an area known as model-checking.
Finally, some errors may creep through to the end-use of the program. Some of these errors could and should perhaps have been dealt with at an earlier point in time. But there will remain some errors in this category. Some can be handled and therefore hidden behind the scene. A fair amount cannot be handled. Most of the discussion in this and the following three chapters are about (handled and unhandled) errors that show up in the program at execution time.
33.5. How are errors handled?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Assuming that we now know about the nature of errors and when they appear in the running program, it is interesting to discuss what to do about them. Here follows some possibilities.
|
The first option - false alarm - is of course naive and unacceptable from a professional point of view. It is naive in the sense that shortly after we have ignored the error another error will most certainly occur. And what should then be done?
The next option is to tell the end-user about the error. This is naive, almost in the same way as false alarm. But the reporting option is a very common reaction from the programmer: "If something goes wrong, just print a message on standard output, and hopefully the problem will vanish." At least, the user will be aware that something inappropriate has happened.
The termination option is often the most viable approach, typically in combination with proper reporting. The philosophy behind this approach is that errors should be corrected when they appear. The sooner the better. The program termination should be controlled and gentle, such that it is possible to continue work when the problem has been solved. Data should be saved, and connections should be closed. It is bad enough that a program fails "today". It is even worse if it is impossible start the program "tomorrow" because of corrupted data.
Repair and recovery at run-time is the ultimate approach. We all wish to use robust and stable software. Unfortunately, there are some problems that are very difficult to deal with by the running program. To mention a few, just think of broken network connections, full harddisks, and power failures. It is only in the most critical applications (medical, atomic energy, etc) that such severe problems are dealt with explicitly in the software that we construct. Needless to say, it is very costly to built software that takes such problems into account.
33.6. Where are errors handled?
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The last fundamental question is about the place in the program where to handle errors. Should we go for local error handling, or for handling at a more remote place in the program.
|
If many errors are handled in the immediate proximity of the source of the error, chances are that a small and understandable program becomes large, unwieldy, and difficult understand. Separation of concerns is worth considering. One concern is the normal program aspects (see Section 33.3). Another concern is exception handling. The two concerns may be dealt with in different corners or the program. Propagation of errors from one place in a C# program to another will be discussed in Section 36.7 of this material.