Monday, February 17, 2014

What happens in try-catch-finally?

let's talk about basics

Try-catch is the most popular way to handle exceptions and with finally block you can really make your code cleaner and make life of all developers who will use it - easier.
As usual I will cover whole code with tests to proof that it works as expected (this time without TDD, because it wouldn't give us any advantages).

You can find code of Finally class here and tests for it here.

which block is the most important?

Let's take a look at the code of simple method (sorry for no logic, but I believe it's enough to understand how everything works :)
int giveMeSomething(boolean throwException) {
    try {
        if (throwException)
            throw new Exception(); 
        
        return 13;
    } catch (Exception e) {
        return -1;
    } finally {
        return 0;
    }
}

and tests:
public class FinallyTest {

    final private Finally thrower = new Finally();

    @Test
    public void notThrownExceptionWithReturnInFinally() {
        assertSame(0, thrower.giveMeSomething(false));
    }
    
    @Test
    public void thrownExceptionWithReturnInFinally() {
        assertSame(0, thrower.giveMeSomething(true));
    }

Maybe it is not what you expect, but those tests passes. Why?
Quoting from documentation: The finally block always executes when the try block exits.This ensures that the finally block is executed even if an unexpected exception occurs. and that's mean no matter what happen in try block instructions in finally block will be executed.

what if... there will be no catch block?

Let's check it:
void letMeToPassException(boolean throwException) throws Exception {
    try {
        if (throwException)
            throw new Exception(); 
    } finally {
        doWhatNeedsToBeDone();
    }
}

The code will pass following tests:
@Test
public void finallyWasReachedWithNoException() throws Exception {
    thrower.letMeToPassException(false);
    
    assertSame(true, thrower.wasFinallyReached());
}

@Test
public void finallyWasReachedAndExceptionWasPassed() {
    try {
        thrower.letMeToPassException(true);
        fail("Exception should be thrown");
    }
    catch (Exception exception){
        assertSame(true, thrower.wasFinallyReached());
    }
}

As you can see if there is no catch block then exception is thrown by the method anyway, just after execution of instructions in finally block.

You may ask why do we need such a construction anyway? Well, sometimes we don't want to hadle thrown exceptions inside a method and it's important for us and for those who uses our code to know what went wrong without digging deep inside the code. Of course, our purpose isn't also to break something, even if code will fail. That's why it's a good idea to use try-finally from time to time - it can help with solving our problems in nicely way.

return in finally

But we don't want to stop here. Let's modify the code little bit:
void letMeToPassException(boolean throwException) throws Exception {
    try {
        if (throwException)
            throw new Exception(); 
    } finally {
        doWhatNeedsToBeDone();
        return 13;
    }
}

and execute tests once again. What happened? finallyWasReachedAndExceptionWasPassed no longer passes!
Yeah, as you already know finally block is executed as a last and if there will be return value inside, it will just suppress each thrown exception and you will never know that something in try block failed.

That's why it's not so good idea to do so. Finally block is considered to be cleanup block, return is not generally expected in it. It's just like trying to tell programmer that something went wrong and give this information in a return value. And this is not a purpose of return value, such an approach leads to lot of conditions in places of usage the code.
We don't want to do this, we want to write code as clean as it possible and even cleaner than this :)

there is one "but"...

As it's written in documentation If the JVM exits while the try or catch code is being executed, then the finally block may not execute.. Which means that e.g code below never will reach finally block:
void doSomething() {
    try {
     System.exit(0);
    } finally {
        doWhatNeedsToBeDone();
    }
}

Probably after look at the code it's pretty obvious for you, but it's important to remember about things like this when you are writing your code.

No comments:

Post a Comment