简体   繁体   中英

Swallowing exception thrown in catch/finally block

Usually I come across situations where I have to swallow an exception thrown by the clean up code in the catch / finally block to prevent the original exception being swallowed.

For example:

// Closing a file in Java
public void example1() throws IOException {
    boolean exceptionThrown = false;
    FileWriter out = new FileWriter(“test.txt”);
    try {
        out.write(“example”);
    } catch (IOException ex) {
        exceptionThrown = true;
        throw ex;
    } finally {
        try {
            out.close();
        } catch (IOException ex) {
            if (!exceptionThrown) throw ex;
            // Else, swallow the exception thrown by the close() method
            // to prevent the original being swallowed.
        }
    }
}

// Rolling back a transaction in .Net
public void example2() {
    using (SqlConnection connection = new SqlConnection(this.connectionString)) {
        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction = command.BeginTransaction();
        try {
            // Execute some database statements.
            transaction.Commit();
        } catch {
            try {
                transaction.Rollback();
            } catch {
                // Swallow the exception thrown by the Rollback() method
                // to prevent the original being swallowed.
            }
            throw;
        }
    }
}

Let's assumed that logging any of the exceptions is not an option in the scope of method block, but will be done by the code calling the example1() and example2() methods.

Is swallowing the exceptions thrown by close() and Rollback() methods a good idea? If not, what is a better way of handling the above situations so that the exceptions are not swallowed?

I'm not a fan of catching and rethrowing an exception.

If you catch it, do something with it - even if it's just logging the exception.

If you can't do anything with it, don't catch it - add a throws clause to the method signature.

Catching an exception tells me that either you can deal with an exceptional situation and have a recovery plan or "the buck stops here" because an exception cannot propagate in that form any farther (eg, no stack traces back to the user).

You can create a custom Exception type that can hold both exceptions. If you overload the ToString() , you can log both exceptions.

try
{
    transaction.Commit();
}
catch(Exception initialException)
{
    try
    {
        transaction.Rollback();
    }
    catch(Exception rollbackException)
    {
        throw new RollbackException(initialException, rollbackException);
    }

    throw;
}

That's exactly why Commons IO has an IOUtils.closeQuietly method. In most cases what goes wrong during the closing of a file is not that interesting.

Database transactions that have to be rolled back are potentially more interesting, as in that case the function didn't do what it was supposed to do (put stuff in the DB).

There's no reason to rollback the transaction in the C# code. If you close the connection wihtout rolling back the transaction (or committing it) that is equivalent and more efficient...

public void example2() {
  using (SqlConnection connection = new SqlConnection(this.connectionString))
  using (SqlCommand command = connection.CreateCommand())
  using (SqlTransaction transaction = command.BeginTransaction()) {
    // Execute some database statements.
    transaction.Commit();
  }
}

and you are done.

The using statement will ensure (via finally) that the connection is closed regardless of any exceptions and let the raw exception bubble out (with the full/correct stack trace). If an exception occurs before the call to Commit, the transaction will never commit and will be automatically rolled back when the transaction/connection closes.

I believe that an exception should be something you don't expect. If you expect an exception then you should do something with it. So in your first example I think you should probably not bother catching the IOException if you are also stating that your method is going to throw IOException.

I would consider rewriting example1 as follows:

// Closing a file in Java
public void example1() throws IOException {
    boolean success = false;
    FileWriter out = new FileWriter(“test.txt”);
    try {
        out.write(“example”);
        success = true;
        out.close();
    } finally {
        if (!success) {
            try {
                out.close();
            } catch (IOException ex) {
                // log and swallow.
            }
        }
    }
}

Moving the success = true; to after the out.close(); statement would make the meaning of success a bit clearer ... though it may result in out.close() being called twice.

Without knowing all of what your particular circumstance is, you could look into throwing a new exception. In C#, at least, when throwing a new exception you one of the optional constructors accepts an existing exception as a parameter. For example:

throw new Exception("This is my new exception.", ex);

The purpose of this is to retain the original exception.

Another option might be the try .. catch .. finally construct.

try { // normal code that might throw an exception } catch (Exception ex) { // handle that first exception } finally { // handle any cleanup regardless of exception being thrown }

In general if my code can handle an exception in a particular try .. catch then I don't re-throw that exception. If it is important for something further up the call stack to have that exception I will throw a new exception and set the original exception as the inner exception.

usually, risky codes are put all in one try-catch block. nested try-catch blocks are not a very good idea, IMO (or just try to avoid nested try-catch blocks as much as possible, unless you really need them).

because the risky codes are exceptional scenario, so putting exceptional codes for exceptional case for even more exceptional case, that's alot of unnecessary work.

for example in example1() , place all risky codes in one try-catch block:

try{
FileWriter out = new FileWriter(“test.txt”);
out.write(“example”);
out.close();
} catch(Exception e) {
    //handle exception
}

OR, another good idea is to place several catch(s) for the same try:

try{
    FileWriter out = new FileWriter(“test.txt”);
    out.write(“example”);
    out.close();
} catch(FileNotFoundException e) {
        //if IOException, do this..
} catch(IOException e) {
        //if FileNotFound, do this..
} catch(Exception e) {
        //completely general exception, do this..
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM