views:

350

answers:

4

Seriously, how can you handle all those exceptions without going nuts? Have I read one too many articles on exception handling or what? I tried refactoring this a couple of times and each time I seem to end up with something even worse. Maybe I should admit exceptions do happen and simply enjoy coding just the happy path? ;) So what's wrong with this code (besides the fact that I was lazy enough just to throw Exception instead of something more specific)? And by all means, don't go easy on me.

public void Export(Database dstDb)
{
    try
    {
        using (DbConnection connection = dstDb.CreateConnection())
        {
            connection.Open();
            DbTransaction transaction = connection.BeginTransaction();
            try
            {
                // Export all data here (insert into dstDb)
                transaction.Commit();
            }
            catch (SqlException sqlex)
            {
                ExceptionHelper.LogException(sqlex);
                try
                {
                    transaction.Rollback();
                }
                catch (Exception rollbackEx)
                {
                    logger.Error("An exception of type " + rollbackEx.GetType() +
                                      " was encountered while attempting to roll back the transaction.");
                }
                throw new Exception("Error exporting message " + Type + " #" + Id + ": [" + sqlex.GetType() + "] " + sqlex.Message, sqlex);
            }
            catch (Exception ex)
            {
                try
                {
                    transaction.Rollback();
                }
                catch (Exception rollbackEx)
                {
                    logger.Error("An exception of type " + rollbackEx.GetType() +
                                      " was encountered while attempting to roll back the transaction.");
                }
                throw new Exception("Error exporting message " + Type + " #" + Id + ": [" + ex.GetType() + "] " + ex.Message, ex);
            }
        }

        try
        {
            Status = MessageStatus.FINISHED;
            srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
                CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
        }
        catch (Exception statusEx)
        {
            logger.ErrorException("Failed to change message status to FINISHED: " +
                                    Type + " #" + Id + ": " + statusEx.Message, statusEx);
        }
    }
    catch (Exception importEx)
    {
        try
        {
            Status = MessageStatus.ERROR;
            srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
                    CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
        }
        catch (Exception statusEx)
        {
            logger.ErrorException("Failed to change message status to ERROR: " +
                                    Type + " #" + Id + ": " + statusEx.Message, statusEx);
        }
        AddErrorDescription(importEx.Message);
        throw new Exception("Couldn't export message " + Type + " #" + Id + ", exception: " + importEx.Message, importEx);
    }
}

Btw. So many times I tried really hard to be as specific as possible when forming questions - the result was no visits, no answers and no idea how to solve the problem. This time I thought about all the times when someone else's question caught my attention, guess this was the right thing to do :)

Update:

I've tried putting some of the tips into practice and here's what I came up with so far. I decided to change the behavior slightly: when it's not possible to set message status to FINISHED after successful export I consider it as job not fully done and I rollback and throw an exception. If you guys still have some patience left, please let me know if it's any better. Or throw some more criticism at me. Btw. Thanks for all the answers, I analyze every single one of them.

Throwing an instance of System.Exception didn't feel right, so I got rid of that, as suggested, and instead decided to introduce a custom exception. This, by the way, also doesn't seem right - overkill? This appears to be fine with public methods but a bit over-engineered for a private member, yet still I want to know there was a problem with changing message status instead of a problem with database connection or something.

I can see couple of ways of extracting methods here, but all of them seem to mix responsibilities which jgauffin mentioned in his comment: managing database connection, handling database operations, business logic (export data). Say the ChangeStatus method - it's some level of abstraction - you change the status of a message and you're not interested in how this thing happens, how the message is persisted, etc. Maybe I should use a Data Mapper pattern to further separate responsibilities, but in this still quite simple scenario I thought I'd get away with Active Record. Maybe the whole design is so convoluted right now, that I don't see where to make the cuts?

public void Export(Database dstDb)
{
    try
    {
        using (DbConnection connection = dstDb.CreateConnection())
        {
            connection.Open();
            using (DbTransaction transaction = connection.BeginTransaction())
            {
                // Export all data here (insert into dstDb)
                ChangeStatus(MessageStatus.FINISHED);
                transaction.Commit();
            }
        }
    }
    catch (Exception exportEx)
    {
        try
        {
            ChangeStatus(MessageStatus.ERROR);
            AddErrorDescription(exportEx.Message);
        }
        catch (Exception statusEx)
        {
            throw new MessageException("Couldn't export message and set its status to ERROR: " +
                    exportExt.Message + "; " + statusEx.Message, Type, Id, statusEx);
        }
        throw new MessageException("Couldn't export message, exception: " + exportEx.Message, Type, Id, exportEx);
    }
}

private void ChangeStatus(MessageStatus status)
{
    try
    {
        Status = status;
        srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
            CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
    }
    catch (Exception statusEx)
    {
        throw new MessageException("Failed to change message status to " + status + ":" + statusEx.Message, statusEx);
    }
}
+12  A: 
  1. Datasets are the root of all evil ;) Try using a ORM instead.
  2. Read about single responsibility principle. your code does a lot of different things.
  3. Do not catch exceptions only to rethrow them
  4. Use using statement on transaction and connection.
  5. No need to catch all different exceptions when all exception handlers do the same thing. The exception details (Exception type and Message property) will provide info.
jgauffin
@2) I've read about SRP and SOLID many times, but as the famous quote says, the gap between theory and practice... Well, the code behind the *performance operator* really is a call to a virtual function, where the actual processing happens so this method's responsibility is to run the whole thing in transaction and record the result - is that too much?
lukem00
@3) If it's a general tip, then I couldn't agree more. What if I catch them to rethrow but also to a) rollback b) log data specific to particular exception (`ExceptionHelper.LogException(sqlex)`) c) decorate the exception with some more info, possibly on a different level of abstraction than the inner exception?
lukem00
You class have these responsibilites: a) Manage database connection. b) Handle database operations. c) Business logic (export data). Try to refactor into three different methods, each one taking care one of of the mentioned steps.
jgauffin
@4) Should I use `using` with transaction as well? Isn't it enough to dispose the connection? Or, if what you mean is to limit the number of try/catch blocks, how will I then know when to rollback the transaction when I can't catch the exception? Also I'm using the filter idiom when catching exceptions to be able to log exception's specific data, but if there's any other *neat* way (not Pokemon exceptions) of doing this, than I'd be more than glad to get rid of some of those catch blocks.
lukem00
@3) I often catch an exception and create a new noe with more info (and use the catched one as inner exception) + log the exception.
jgauffin
@4): Yes. using is very useful with transaction. The code is much more beatiful and Rollback is automatically called if the block fails (always have Commit on the last line).
jgauffin
@SRP: This is probably some psychological barrier which I, and I believe (or hope it's not just me) many people, find difficult to cross - to *allow* yourself (is that the right way to put it?) to have zillions of methods/classes, each doing a single atomic thing. I'll try to be more aggressive with refactoring, thanks for the tip (and all the other tips, so that I don't repeat myself - this is DRY applied to comments ;))
lukem00
@3) So in case of SqlException, instead of logging it directly and then doing the same thing as with any Exception, I would need to add another level of try/catch, where I would just catch SqlException and use its data to form another exception? That would definitely allow me to DRY (as I like to put it) the code, but would add another level of nesting, right?
lukem00
@4) Oh, I didn't know that, I will definitely give it a try, thanks.
lukem00
@3) Only catch exceptions that you actually handle. In this case only SqlException. It's up to the invoker to catch any other exception. As for the SqlException: If this class is your datalayer, you should throw something more generic than a sqlexception (to create a solid layer without leaking layer specific information to the invoker)
jgauffin
@3) Always try to handle only exceptions that really really need to be handled by a method. Dont be afraid to let exceptions propagate through the call stack. You will get much cleaner code.
jgauffin
Another reason you don't need all those catch blocks: most of the detail you're adding is to log what you were doing at the time of the exception. But the stack trace will pretty well show you that. Make sure you log `ex.ToString()` instead of just `ex.Message`.
John Saunders
[John Saunders](http://stackoverflow.com/users/76337/john-saunders): Will the stack trace give me info on the state of the object or just a trace of method calls? Details I'm adding are quite useful - I can tell straight away which messages failed.
lukem00
Only the method calls. If you find the details to be important, then that's a reason to catch the exceptions, log the extra detail, then rethrow the exception. Also, I see you're creating a custom exception type: it's recommended to not do that unless there is code that will catch that type and do something different from existing exception types.
John Saunders
+3  A: 

Exception handling is a well discussed and is implemented in a well varied number of ways. There are some rules I try to abide by when handling exceptions:

  1. Never throw System.Exception, generally there are enough types of exceptions to fill your requirement, if not, specialise. See: http://www.fidelitydesign.net/?p=45
  2. Only ever throw an exception if the method itself cannot do anything but throw an exception. If a method can recover/handle expected variations of input/behaviour, then don't throw an exception. Throwing exceptions is a resource-intensive process.
  3. Never catch an exception just to rethrow it. Catch and re-throw if you need to perform some additional work, such as reporting the exception, or wrapping the exception in another exception (typically I do this for WCF work, or transactional work).

These are just the things I do personally, a lot of developers prefer doing it ways in which they are comfortable....

Matthew Abbott
@2) I am aware of the performance penalty introduced by exception handling and I do have some twitching feeling whenever I throw, catch, or rethrow an exception, but I've definitely seen more situations where a method can only partially recover from the exception and then it will have to rethrow to inform some upper layer. Maybe this is a sign of a bad design? But it does seem to work that way with rollbacks.
lukem00
Haha, +1 for the comic strip - yeah, I got the message :)
lukem00
+7  A: 

Additionally to the great answer of jgauffin.

6 . don't catch exceptions just to log them. catch them on the top most level and log all the exceptions there.

Stefan Steinegger
That's definitely the approach I'd like to take. I should probably apply this to my `rollbackEx` (simply rethrow with extra info and let the upper layer log it). However, on a rare occasion (like `statusEx`), I want to swallow the exception (do the wrong thing?) as it's not very serious and leave a note in a log (I should probably use `WarnException` here rather than `ErrorException`).
lukem00
A: 

Create a log class that handles fall backs for its own failures (i.e. tries SQL, if that fails writes to event log, if that fails, writes to a local log file, etc).

Also I don't recommend you catch an error and throw a different one. Every time you do so you are losing valuable trace/debug information about the original exception source.

public void Export(Database dstDb)
{
  try
  {
    using (DbConnection connection = dstDb.CreateConnection())
    {
        connection.Open();
        using (DbTransaction transaction = connection.BeginTransaction())
        {
            // Export all data here (insert into dstDb)
            ChangeStatus(MessageStatus.FINISHED);
            transaction.Commit();
        }
    }
  }
  catch (Exception exportEx)
  {
    LogError(exportEx);// create a log class for cross-cutting concerns 
        ChangeStatus(MessageStatus.ERROR);
        AddErrorDescription(exportEx.Message);

    throw; // throw preserves original call stack for debugging/logging
  }
}

private void ChangeStatus(MessageStatus status)
{
  try
  {
    Status = status;
    srcDb.UpdateDataSet(drHeader.Table.DataSet, HEADERS,
        CreateHeaderInsertCommand(), CreateHeaderUpdateCommand(), null);
  }
  catch (Exception statusEx)
  {
   Log.Error(statusEx);
   throw;
  }
}

Also for any situation where you feel there are additional try/catch blocks needed, make them their own method if they are too ugly. I like Stefan Steinegger's answer, where the top level call in your app is the best place to catch.

Part of the problem here I imagine is that something is mutable that causes you to try to set the status after a failure. If you can factor your object to being in a consistent state whether or not your transaction works you don't have to worry about that part.

Maslow
I was following a piece of advice I found in Code Complete - to throw exceptions at the right level of abstraction. I guess I can extract the stack trace from the inner exception, so I'm not loosing any information. In fact I'm adding some extra details: message type and id. Throwing a custom exception does seem a bit too much trouble for a private method, but that way I can let it bubble up.
lukem00
Each message holds information whether it was successfully extracted or not (plus the reason of failure in the latter case). I'm not sure how would making the message immutable help here?
lukem00
You know, i've read that part about catching at the right level of abstraction,and i'm not sure how I feel about it in this context. Litter your code with try/catch/throw something else vs. catch all near the top that logs the exception and reports a failure. Catch alls vs checked exceptions vs abstraction layers... This is going subjective. I haven't walked the catch-rethrow because I use catch all, so I can't speak to the benefits there.
Maslow
It's true the code is littered, plus all the custom exceptions you have to define... (unless you mostly throw `InvalidOperationException` and the like). Seems like a lot of trouble to go through. And yes, I would very much like to have a top level catch all mechanism, but even though logging low level exceptions along with the stack trace might be more than enough for me, I don't think the user would really want to see such exception on his/her screen.
lukem00
no one said anything about the user seeing them. catching at the top, where you return a high level message to the user of what operation failed.
Maslow