In my day job, I'm currently running a little project to establish some best practices that we can both adopt going forward, and somehow start applying to our existing legacy systems.
I know there a million people out there with their own ideas of "best practice", and it's a shame that these things don't seem to be portable between different development teams, but I guess as long as we can't agree on the whole tabs-vs-spaces thing for indenting code, I'm not going to hold my breath waiting. Instead, here's where we are at the moment - our Exception Handling holy trinity.
Step 1 - Don't throw or catch the wrong exceptions
First off, don't do any of the following things. Ever. (Almost):
- Don't catch "Exception" - If you do this, and you end up catching something really dirty, like an OutOfMemoryException, then your app will muddle on just making your data worse and worse, and shock horror, if crappy data gets in your DB then you're screwed.
- Don't catch "ApplicationException" - This one was originally something you could do, now someone in MS has created exception types that inherit from ApplicationException which are not safe to catch, so this one's a no-no too. (See http://blogs.msdn.com/b/kcwalina/archive/2006/06/23/644822.aspx for more on this, or read this pretty darn useful book).
- Don't throw "Exception" or "ApplicationException" - If it's not safe to catch these errors, then it stands you reason you really shouldn't be throwing them.
- What you should throw - With regards to what you should throw, I'm still undecided if custom exceptions are a good thing, or if you should as much as possible use the standard .Net exception types, e.g. NullReferenceException, ArgumentException, OperationException, etc. I might post more on this as our best-practice resources at work start to flesh out.
Incidentally, there is one exception to rule 1 above - your application root should have always catch Exception, so that things that are fatal, no matter what they are, you can try to log and/or alert them in a standard way, and try to let the user down gently.
Step 2 - Don't catch exceptions unless you can do 1 of the 3 following things
Do not catch exceptions in your code unless you can do one of the following three good things:
- You can added more detail - This would be done by catching a specific exception type, then wrapping it in a more meaningful exception to make your root exception handler log something more meaningful. I cannot emphasis this bit enough, WRAP the exception! If you don't use the InnerException property on your newly thrown exception, you'll lose the more detailed stack trace.
- You can fix the problem - This generally applies if you're dealing with user input, and you can alert the user they've thrown in junk, try again. Or perhaps a service is down and you have a fallback service, which indicates it's really not exceptional that this particular service it's down. If you can fix the problem, then by all means catch the exception and clean things up.
- You're the application root - The application root should always catch every exception that bubbles that far up the stack trace, simply to make sure it's logged correctly.
I would say, regardless of where you catch exceptions, don't be shy about using finally blocks for resource management wherever you might need them, they have no impact on the above.
Step 3 - The magic bit (that sadly is the hardest bit)
If you've ever tried to adhere to rule 1 above, you'll probably have realised one key problem - if you never catch "Exception" or "ApplicationException", how do you know what else to catch? Do you ever find yourself writing code and thinking, "If this blows up, so what - we've still got to carry on and complete this other massively important job"?
Let's take a typical scenario - you're writing some code to validate postcodes in a shopping cart, to save the user keying in their full address. If you get an exception in this logic, and you don't catch it, the whole page will blow up and you've probably lost a customer. But if you just catch "Exception" and your server's getting resource issues, you run the risk of the customer thinking you're going to fulfil their order,when you might have not even received it. To do this job properly, the magic piece, you need to know where your or someone else's code throws which specific exceptions.
This is actually quite a fundamental thing - some languages, for example Java, won't let you throw an exception unless your method signature includes the exception in a special "throws" clause. Therefore, when consuming a method, you can easily see which exceptions it throws. This is an idea called "Checked Exceptions". The C# guys however decided that checked exceptions brought in their own issues (see http://www.artima.com/intv/handcuffs.html for more on this), so we don't currently have this in the C# world.
Here comes the magic...
The solution in C# is to always, ALWAYS document which exceptions your code throws using XML comments aganst the method! In most parts of the.Net library, MS have done this well. It means when you're consuming a method, you can dig out the XML documentation and it will tell you in plain black and white, "I throw these exception if these bad things happen". In some cases I've seen the Visual Studio intellisense pop up this list of exceptions while I'm coding, but I'm damned if I can get it to work at the moment.
But anyhow, relying on intellisense for something like this is not ideal, if you're lucky enough to be using Resharper (which incidentally I can't recommend enough), try digging out the wonderfully awesome "Exceptional" plugin (see http://exceptionalplugin.codeplex.com/). This plugin is like a silver bullet for a lot of this stuff - I'm not going to go into it here, but trust me, it helps.
Have a great weekend folks, I'm off to go and box in my boiler.