Monday, May 28, 2007

Thoughts on successful exception handling

In the last article, a (very) brief introduction to the benefits of exceptions, we saw that exception handling may produce more efficient and stable code. The example was kept simple enough to provide an operation breakdown on how exceptions basically work. This article will explain how exceptions may be used for greater benefits, how programs and program parts get more stable using exception handling and how exceptions and parts of the programming efficiently interact with error handling and each other.

Why exceptions?

As stated before, many coding efforts directly flow into error handling in general. In case a simple "or die" expression is not enough when an error occurs ("or die" just stops processing of the program-code at once), exception handling provides useful mechanisms to write code that may handle the erroneous behaviour or helps finding the cause of it.

Following the IPO principle, errors in programs or program parts do occur because of a flaw in programming or incorrect input to the program / program part. Assuming we only take a single method call retrieving parameters, the following conditions which result in wrong output can be classified:
  • The parameters given to the method are not well defined (i.e. are wrong or empty / null where they must not be)
  • The "constellation" of the parameter values are not expected in the given way by the method (because if parameter "a" has a certain value, "b" must not be of another certain value)
  • The caller of the method simply calls it in an improper way (i.e. the environmental conditions are not correct, needed resources are not allocated yet etc.)
  • The method allocates external resources (i.e. open connections, file handles etc.) and does not free them (thus leading to an invalid internal state of the program, the operating system or other components like databases)
  • The method relies on external resources (i.e. file system, database etc.) which are not available at the moment they are needed
  • The method and therefor it's programming trusts on an internal state that is not correct (i.e. corrupt data/instances needed to compute the result)
  • The method contains a programming error
Looking at the points above reveals that only the last two arguments deal with programming faults in our method, the other five describe errors that depend on the usage or environmental states the method runs in. So while exception handling can help to identify purely written code while development, it often is used to keep the code up and running correctly in productive systems.

How exception handling is done (right).

Using exception handling is as simple as that: If you write a method and you are able to handle the behaviour without breaking the flow of your code or stressing external logic through "workarounds", you do not need exceptions at that place. The time you wonder about how you may "get this check of a variable/state into an if-then-else" - construct or you rely on methods or (external) resources which may throw exceptions of their own it's another part of the story.

Before your code gets overly complicated throw an exception. You then surely can determine the place where the exception will be thrown and, because of try-catch - blocks, you always have a defined place where your exception and the execution of your code will lead to.

And you cannot handle every error, better: exceptional behaviour, at the place it (immediately) occurs. If you write a method that, say, reads some file content and returns it as a string, and you do not exactly know from which routines this method will be called in the future, then you should let pass or (re)throw the input/output exception as it occurs in your method. What good is it to return an empty/null string if an error happens? Firstly, you will have to check for a null pointer reference anyway, otherwise the surrounding code will not work properly either. Most times checking for a null reference is as verbose as catching an exception. On the other hand, if you don't throw an exception, does returning an empty (not null-) string mean that an error occured or that the file was "empty"?

Take your time and rethink if the method where the error happens really has all informations to react accordingly or handle that error. Else (or if you are not sure how to handle this kind of exceptional behaviour at that place) throw an exception.

And never forget: If you decide to throw an exception, be as precise about the error as you can be. If your language accepts an error message to pass with the exception, make it as accurate as possible. If the language allows to specify different exceptions or even subclass exceptions, be as precise in selecting the type of the exception as possible.

Somewhere ... by and beyond exeptions?

Do not listen to people who argue that exceptions are (time-)expensive. Firstly they are right, no argument needed. But the benefit of clear, more stable and readable code leaves enough time for you to optimize your algorithms on other places more needed. Remember that exceptions represent "exceptional behaviour", they represent erronous states which only happen in special erronous cases. Ordinary spoken: If you do not throw exceptions to just simply leave a loop or code block you will be fine on the performance side of life.

And there are a couple of open points and additional benefits introducing concepts like exceptions. Some of them, more or less tightly married with exceptions, are thingies like stacktraces where you can follow the whole way of the exception through the complete code (in most languages introducing exceptions), finally-blocks and (in a certain way) assertions and closures. But handling these issues would blast this article.

Alas, one controversially discussed exception technique, the so called checked exceptions of the programming language Java, should at least be mentioned here to round off.

Checked exceptions are exceptions you have to catch or, via definition, throw up to the next ("calling") instance. A method, for instance, that does not want to catch the exception on it's own has to declare that the exception which then has to be catched from the caller of the method by default - if not, the compiler will not compile the code. This is a very controversial issue within the (Java-) community because the need to catch checked exceptions naturally leads to more programming overhead. But again, the compiler tells you that the programmer of the method wanted you to explicitely respect that this kind of exception(s) may occur - and you have to deal with it. Integration might be tough, but at least you know about the possible exceptions without consulting the documentation simply by compiling your code. I, personally, had my difficulties accepting this feature, but at least the advantages had beaten the "programming verbosity" pants off for me and I think you will love this feature as it follows your way in larger projects.

So, go on throwing exceptions, and may the catch be with you!

Labels: , ,

 

Friday, March 30, 2007

A brief introduction to benefits of exceptions

Every programmer of a programming language has to cope with errors/exceptions and error/exception handling on a "it's all in a job" - base nowadays. But astoundingly often, exception handling and it's (more or less) proper or inteded usage is highly discussed all over and over again. This brief introduction wants to show that exception handling is a powerful tool and may be a shortcut for normal error handling. By using exception handling, programs even get more robust and less error-prone.

A tiny example without
exception handling.

A program works using the IPO-principle: Input-Processing-Output. If the input is well defined and the processing is correct, the output is correct too. The following example is a little program that reads three numbers (line by line) from a text file with a given name, adds them up and prints the sum as a result, "written" step by step:
  1. Read the name of the file as parameter of the program into variable "fileName"
  2. Assing a variable "sum" with the value "0" (zero)
  3. Open the file "fileName"
  4. Repeat steps 5 and 6 for 3 times
  5. - Read one line into the variable "line" as text
  6. - Convert the text "line" into a number and add it to "sum"
  7. Close the file "fileName" (the resource handler needs to be freed)
  8. Print "sum" on the screen
The processing of this simple excample is correct, but there are pitfalls that might stop our program from working correctly depending on the input:
  • The program did not get a parameter to store it in "fileName"
  • The file "fileName" does not exist or cannot be opened
  • There is nothing in the file, or, more precisely, there are not at least three lines in the file
  • One of the three lines is/are not numerical and cannot be converted into a number

Programming this example without exception handling means that you have to check each of these error sources step by step (check-list):
  • if no parameter is given, go to "exit" with error "No parameter given" (1.)
  • if the file "fileName" does not exist, go to "exit" with error "File does not exist" (3.)
  • if the file cannot be opened, go to "exit" with error "File cannot be opened" (3.)
  • if the next line cannot be read, go to "exit" with error "Cannot read number" (5.)
  • if the line is not a number, go to "exit" with error "Cannot read number" (6.)
  • write the sum
  • "exit": if the file is open, close it.
This example is written in a defensive kind and shows that the most part of programming often is not the program-logic itself. Moreover it is error handling and reporting, because the user wants to know why he did not get the sum.

Using exception handling.

Using a language that utilizes exception handling will help a lot in this example. At first, the methods provided by the language will provide you with (more or less) reasonable built-in exception handling, i.e. the method "open file" will raise an (input/output) exception if the file cannot be opened or does not exist or even if the parameter for the file name is invalid. This way you will not have to check for the file of your own (though you could) but rather catch an appropriate exception and print an according error message. You define the range where an exception will be "inspected" through the try-catch - block (pseudo-code):

try {
openFile()
do(3-times) {
try {
readNumberAndAddToSum // Assuming that an exception is thrown if no number is applicable
}
catch(NotANumberException) {
closeFile() // because it already is open
printNaNError
exit
}
}
closeFile()
printSum
}
catch(IOException) {
printIOError
}
Wherever an input/output exception occurs (here in the method "openFile()"), the code will stop at this line and jump into the "catch(IOException) { ... }" - block. This is especially true in case the "readNumberAndAddToSum" will throw an IOException (i.e. end of file). The handling of a type of exceptions can be centralized for the whole algorithm this way.

Additionally, a NotANumberException is catched in this example too, the program then terminates. If the IO - routines and the number converting routines work correctly (i.e. throw the right exceptions), this little snippet fulfills all the "if / exit" - assumptions done earlier, without writing more lines of code.

That's all folks?

As shown above, exception handling can shorten and, in a way, clarify the source code. A detailed "which-errors-happen-if" matrix does not need to be as detailed as a matrix that shows all possibilities when only using "if ... then ... else if ..." - constructs (the check-list above). Simply try to write the program with complete error handling using the check-list and you will see the difference.

Let us enhance the original purpose: The little example of reading three numbers matured and needs to be integrated in a larger program. This way the program cannot simply be stopped via "exit" if an error occurs, we have to re-write it as a method returning the sum as a number to fit into the whole program. The question is, case an error occurs, which value should be returned? Normally, errors are indicated using the "-1" - value. But the sum could be "-1" which then will be interpreted as an error.

Throwing an exception or letting an exception through (by not catching it explicitely) is the answer to this problem. By changing the intention of the algorithm ("simply printing the sum" to "returning the sum as a subpart of a larger program"), the scope of the method changed from a single program to a part of a program. So the algorithm cannot come to a conclusion on how to react on errors any more, because it does not know the exact state it was called from (compute - print - terminate is not an option any more). Anyway, the calling instance of the algorithm can do this because it knows why it called the method, it may print an error message or show an error dialog, it may ignore the error or create a file for later processing etc. . So the caller of our newly created method needs to know what exactly went wrong and will get this information from the exception thrown: "Couldn't I read the file? IOException". "The input went wrong because there was no number?
NotANumberException".

So, exception handling is ...

... an additional possibility to handle exceptional behaviour and errors in an efficient, readable and maintainable way, if used correctly. It provides you from getting headaches by declaring detailed error matrixes or by providing the possibility to even catch errors you didn't think of in first place. And remember: A segmentation fault only is an exception never catched.

Labels: ,