Wednesday, September 5, 2007

Exception Handling

When I first made the transition from Java to C# one of the differences I quickly ran into was exception handling. Java takes a very strict approach where methods must declare every type of exception that it could potentially throw. If you don't declare it, then you don't throw it. That means that when you call a method that declares to throw one of seven exceptions, you need to either catch or yourself throw all of them. Of course, in practice there were actually two different options that I saw people using. The first, and preferred method, was to wrap calls with try/catch blocks and then wrap the caught exceptions with your own custom type. This lead to the types of exception traces you might see with JBoss, where one little FileNotFoundException could lead to litterally hundreds of lines of stack trace with 5+ nesting exceptions... each one providing less and less useful information. The second method was to simply declare that your calling method threw java.lang.Exception and call it good. The biggest motivator was that it allowed the programmer to provide additional context as to the state of the application when the exception occurred. If an exception was thrown while saving an object to the database, we would wrap the SQLException with our own custom exception and provide the ID and type of the object being saved. This greatly helped in debugging.

In fact, I remember spending a significant amount of time thinking about and even more debating various methods for exception handling. At one point I was working on an a bit of logic that was (at compile time) inserted into all of our throw statements to create an 'exception session' to tie together a chain of logically disparate exceptions. Looking back, this was a complete waste of time.

When I first started looking at C# code I noticed that none of our methods declared the exceptions they could throw. To my horror, I found that we pretty much were only catching exceptions at the top level of the application (ie: just before you see the 'Error, had to crash' dialog). That's not to say that there wasn't some sparse exception handling thrown in the mix, but by and large we let problems propagate out. The first bit of code that I contributed to the project handled exceptions just like a good Java programmer should. I caught all over the place and threw out custom exceptions, wrapping the original problem with some additional context information. This worked fine, but also resulted in the same kind of JBoss-esq monster exceptions. When the database was missing a column the developer saw a XamlParseException... pretty much exactly what you want!

try
{
...
}
catch (SomeException se)
{
if (!se.Data.Contains("My.Context"))
{
se.Data.Add("My.Context", myContextObject);
}
throw se;
}


Yesterday I was going back through and cleaning up exception handling when I discovered the Data property of System.Exception. Data is a Dictionary that takes string keys and object values. I can't remember the last time I was more excited at finding an API gem. It's pretty sweet when a tiny difference can make such a huge impact on ease of use. Now, when I want to provide some additional context during an exception event I just add some data values. At the top level we have a single method to build a nice looking report from an exception. The developer gets to see the actual problem right off, but can also find out what was happening deep down in the core.

No comments: