Saturday, February 26, 2011

basic exception handling

This text summarizes the basics of how try-catch-finally clause error handling works. The examples are in Java, but the rules are the same for C#. The only difference between Java and C# exceptions is that C# doesn't have checked exceptions. Checked and unchecked exceptions are explained in more detail in a different text.
Exceptions are used in a program to signal that some error or exceptional situation has occurred, and that it doesn't make sense to continue the program flow until the exception has been handled. A method may throw an exception for many reasons, for instance if the input parameters are invalid (negative when expecting positive etc.).

The Call Stack Explained

This text refers to the concept the "call stack" in several places. By the call stack is meant the sequence of method calls from the current method and back to the Main method of the program. If a method A calls B, and B calls C then the call stack looks like this:
A
    B
    C
When method C returns the call stack only contains A and B. If B then calls the method D, then the call stack looks like this:
A
    B
    D
Understanding the call stack is important when learning the concept of exception propagation. Exception are propagated up the call stack, from the method that initially throws it, until a method in the call stack catches it. More on that later.

Throwing Exceptions

If a method needs to be able to throw an exception, it has to declare the exception(s) thrown in the method signature, and then include a throw-statement in the method. Here is an example:
public void divide(int numberToDivide, int numberToDivideBy)
    throws BadNumberException{
        if(numberToDivideBy == 0){
            throw new BadNumberException("Cannot divide by 0");
        }
        return numberToDivide / numberToDivideBy;
    }
When an exception is thrown the method stops execution right after the "throw" statement. Any statements following the "throw" statement are not executed. In the example above the "return numberToDivide / numberToDivideBy;" statement is not executed if a BadNumberException is thrown. The program resumes execution when the exception is caught somewhere by a "catch" block. Catching exceptions is explained later.
You can throw any type of exception from your code, as long as your method signature declares it. You can also make up your own exceptions. Exceptions are regular Java classes that extends java.lang.Exception, or any of the other built-in exception classes. If a method declares that it throws an exception A, then it is also legal to throw subclasses of A.

Catching Exceptions

If a method calls another method that throws checked exceptions, the calling method is forced to either pass the exception on, or catch it. Catching the exception is done using a try-catch block. Here is an example:
public void callDivide(){
        try {
            int result = divide(2,1);
            System.out.println(result);
        } catch (BadNumberException e) {
            //do something clever with the exception
            System.out.println(e.getMessage());
        }
        System.out.println("Division attempt done");
    }
The BadNumberException parameter e inside the catch-clause points to the exception thrown from the divide method, if an exception is thrown.
If no exeception is thrown by any of the methods called or statements executed inside the try-block, the catch-block is simply ignored. It will not be executed.
If an exception is thrown inside the try-block, for instance from the divide method, the program flow of the calling method, callDivide, is interrupted just like the program flow inside divide. The program flow resumes at a catch-block in the call stack that can catch the thrown exception. In the example above the "System.out.println(result);" statement will not get executed if an exception is thrown fromt the divide method. Instead program execution will resume inside the "catch (BadNumberException e) { }" block.
If an exception is thrown inside the catch-block and that exception is not caught, the catch-block is interrupted just like the try-block would have been.
When the catch block is finished the program continues with any statements following the catch block. In the example above the "System.out.println("Division attempt done");" statement will always get executed.

Propagating Exceptions

You don't have to catch exceptions thrown from other methods. If you cannot do anything about the exception where the method throwing it is called, you can just let the method propagate the exception up the call stack to the method that called this method. If you do so the method calling the method that throws the exception must also declare to throw the exception. Here is how the callDivide() method would look in that case.
public void callDivide() throws BadNumberException{
        int result = divide(2,1);
        System.out.println(result);
    }
Notice how the try-catch block is gone, and the callDivide method now declares that it can throw a BadNumberException. The program execution is still interrupted if an exception is thrown from the divide method. Thus the "System.out.println(result);" method will not get executed if an exception is thrown from the divide method. But now the program execution is not resumed inside the callDivide method. The exception is propagated to the method that calls callDivide. Program execution doesn't resume until a catch-block somewhere in the call stack catches the exception. All methods in the call stack between the method throwing the exception and the method catching it have their execution stopped at the point in the code where the exception is thrown or propagated.

Example: Catching IOException's

If an exception is thrown during a sequence of statements inside a try-catch block, the sequence of statements is interrupted and the flow of control will skip directly to the catch-block. This code can be interrupted by exceptions in several places:
public void openFile(){
        try {
            // constructor may throw FileNotFoundException
            FileReader reader = new FileReader("someFile");
            int i=0;
            while(i != -1){
                //reader.read() may throw IOException
                i = reader.read();
                System.out.println((char) i );
            }
            reader.close();
            System.out.println("--- File End ---");
        } catch (FileNotFoundException e) {
            //do something clever with the exception
        } catch (IOException e) {
            //do something clever with the exception
        }
    }
If the reader.read() method call throws an IOException, the following System.out.println((char) i ); is not executed. Neither is the last reader.close() or the System.out.println("--- File End ---"); statements. Instead the program skips directly to the catch(IOException e){ ... } catch clause. If the new FileReader("someFile"); constructor call throws an exception, none of the code inside the try-block is executed.

Example: Propagating IOException's

This code is a version of the previous method that throws the exceptions instead of catching them:
public void openFile() throws IOException {
        FileReader reader = new FileReader("someFile");
        int i=0;
        while(i != -1){
            i = reader.read();
            System.out.println((char) i );
        }
        reader.close();
        System.out.println("--- File End ---");
    }
If an exception is thrown from the reader.read() method then program execution is halted, and the exception is passed up the call stack to the method that called openFile(). If the calling method has a try-catch block, the exception will be caught there. If the calling method also just throws the method on, the calling method is also interrupted at the openFile() method call, and the exception passed on up the call stack. The exception is propagated up the call stack like this until some method catches the exception, or the Java Virtual Machine does.

Finally

You can attach a finally-clause to a try-catch block. The code inside the finally clause will always be executed, even if an exception is thrown from within the try or catch block. If your code has a return statement inside the try or catch block, the code inside the finally-block will get executed before returning from the method. Here is how a finally clause looks:
public void openFile(){
        FileReader reader = null;
        try {
            reader = new FileReader("someFile");
            int i=0;
            while(i != -1){
                i = reader.read();
                System.out.println((char) i );
            }
        } catch (IOException e) {
            //do something clever with the exception
        } finally {
            if(reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    //do something clever with the exception
                }
            }
            System.out.println("--- File End ---");
        }
    }
No matter whether an exception is thrown or not inside the try or catch block the code inside the finally-block is executed. The example above shows how the file reader is always closed, regardless of the program flow inside the try or catch block.
Note: If an exception is thrown inside a finally block, and it is not caught, then that finally block is interrupted just like the try-block and catch-block is. That is why the previous example had the reader.close() method call in the finally block wrapped in a try-catch block:
} finally {
            if(reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    //do something clever with the exception
                }
            }
            System.out.println("--- File End ---");
        }
That way the System.out.println("--- File End ---"); method call will always be executed. If no unchecked exceptions are thrown that is. More about checked and unchecked in a later chapter.
You don't need both a catch and a finally block. You can have one of them or both of them with a try-block, but not none of them. This code doesn't catch the exception but lets it propagate up the call stack. Due to the finally block the code still closes the filer reader even if an exception is thrown.
public void openFile() throws IOException {
        FileReader reader = null;
        try {
            reader = new FileReader("someFile");
            int i=0;
            while(i != -1){
                i = reader.read();
                System.out.println((char) i );
            }
        } finally {
            if(reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    //do something clever with the exception
                }
            }
            System.out.println("--- File End ---");
        }
    }
Notice how the catch block is gone.

Catch or Propagate Exceptions?

You might be wondering whether you should catch or propate exceptions thrown in your program. It depends on the situation. In many applications you can't really do much about the exception but tell the user that the requested action failed. In these applications you can usually catch all or most exceptions centrally in one of the first methods in the call stack. You may still have to deal with the exception while propagating it though (using finally clauses). For instance, if an error occurs in the database connection in a web application, you may still have to close the database connection in a finally clause, even if you can't do anything else than tell the user that the action failed. How you end up handling exceptions also depends on whether you choose checked or unchecked exceptions for your application. There is more on that in other texts in the error handling trail.

No comments:

Post a Comment