Posted by Phil in ASP.NET
on Oct 9th, 2009 | 5 comments
You know what I love, Exceptions. Exceptions are awesome.
They’re an awesome tool that simplify post-mortem debugging, help developers code against and understand the internal workings of an API and they help prevent applications getting into a corrupt state.
Even though exceptions are so invaluable, a lot of the time they’re poorly understood, abused, ignored and forgotten. I hope that I can earn some respect for the venerable exception, by setting down some rules on how they should be treated.
Phil’s 10 rules for the treatment of Exceptions:
-
Throw early, throw often[1]
This is key tenet of exceptions. You must throw as early as possible. For example, if your method requires a parameter isn’t null, throw an ArgumentNullException as soon as possible, on one of the first lines in the method. An early ArgumentNullException provides much more information than a NullReferenceException later in the code, and an early exception prevents the class getting into a corrupt state.
-
Prevention is the best medicine
You should always avoid calling methods that you know will throw an exception only to catch the exception and continue executing. It makes for hard to read code and exceptions have a performance cost, so avoid the exception being thrown in the first place if possible. For example, rather than letting a statement throw a DivideByZeroException, use an ‘if’ statement to check the divisor isn’t zero!
-
Log each, and every exception (almost)
Once an application is in Production, or QA, the chances are you’re not going to have a debugger attached so you can see exceptions when they happen, and you’re going to have to rely on post-mortem debugging. This is why you must log everything. All exceptions that aren’t caught must be logged, and 90% of exceptions that are caught should be logged too.
-
Don’t abuse the BCL exceptions
Knowing the type of an exception being thrown provides a lot of valuable information, so if the type is a generic base type, debugging all of a sudden becomes a lot harder. Microsoft’s guidelines recommend never throwing System.Exception, and I couldn’t agree more. That class tells you nothing about what’s gone wrong.
Try to define your own exceptions classes when possible (ideally with the Exception snippet if you’re in C#,) give them descriptive names, meaningful properties and default messages.
-
Descriptive error messages add context
Even if you’re throwing the right type of exception, you still need to add details to the exception to give it context, and the best way to do that is with a descriptive message. In the example of a FileNotFoundException, providing the name of the file that was missing would be a pretty good start.
-
Know that even when you’re not coding an new API, you’re coding an new API
This is one of my core beliefs in programming, imagine that everything you write is going to be exposed as part of an API. It makes you double think what you write if you’re assuming it’s going to be of the CLR in .net 5.0! With this in mind, think about someone calling your methods, and how they’re going to be able make their own code work when yours is throwing meaningless exceptions, or worse, no exceptions at all.
-
Catch and wrap
Catching an exception and making it the inner exception of a new exception allows you to add additional detail, giving even more context. A common networking exception might be WebException: “The underlying connection is closed.” While helpful, probably won’t give you enough info to resolve it. Catching and wrapping it in a new exception “Calling method X on remote server Y” helps tremendously.
-
Don’t use exceptions when a return value is more appropriate
While I’d never recommend having a function return a value to indicate whether a function failed or not, I’d also never recommend throwing an exception just because there’s nothing to return. It’s okay to return null if the situation calls for it. For example, if you’re using a file for caching, and the file doesn’t exist, don’t throw a FileNotFoundException, return null since there’s nothing cached.
-
Know which exception message are and aren’t appropriate for an end user
While it’s easy to just display the message from an exception to an end user, often they’ll have no idea what it means or how to fix it, no matter how technical they are. You should just try and cover all the common scenarios with custom UI messages, and for the edge cases, logging and ‘send crash report’ type functionality is your friend.
-
Don’t panic!
It’s okay, exceptions aren’t a bad thing! A lot of people feel it’s necessary to catch exceptions at every chance they get. A lot of the time it’s better just to leave the exception to bubble up the stack to be handled at the topmost level, especially when the code that catches the exception then carries on as if nothing’s happened, resulting in a corrupt state and unpredictable behaviour.
For those interested in how logging is best done in ASP.NET, you might want to look at my older post.
There’s a lot to think about here, and I’m sure you all have different opinions, is there anything else you’d add to this list?
Phil
[1]A programmer’s introduction to C# 2.0
I totally agree with all of this and think it is a must for new developers to read and understand. The current code base I’m working with is breaking a number of the rules above and it only makes it a real pain to find the real causes of the problem (especially the “catch and consume” blocks which hide any errors happening).
Two things I’d like to add:
1. Re throwing BCL exceptions. Not only do they not tell you what happened, they also make it extremely difficult to handle the exception differently when catching code (since catching is based upon best fitted type).
2. When rethrowing exceptions – always use the “throw;” statement on it’s own OR include the thrown exception into your newly created exception (as the base exception). There have been many cases where I’d see “throw ex;” (where ex is the caught exception). The problem with this is that it recreates the stacktrace hiding the original point where the program failed!
Anyway; keep up the good blog. Some good tips and pointers you’re writing about!
Thanks Paul, good additions. #2 is one that really bugs me too, and reminded me of another thing I keep seeing people doing, catching an exception (e.g. catch (Exception ex)) then never using ex, which generates a compiler warning. I guess lots of people don’t know you don’t need to specify the variable name!
Great article Phil! It answers a ton of questions I had about exceptions and being a hobbyist developer at best these are things that are very handy to learn.
I realised one of my apps was catching System.Exception by running the targets through Microsoft FxCop and started doing some reading into it.
If people are keen to follow your advice but need to perhaps modify existing code FxCop may help. 🙂
Awesome post dude, I see some of the stuff you have tried to teach me over the years, you’re finally putting into writing! Keep this up for a couple of years and you’ll have content for “Phil’s big book of dev do’s and dont’s”. 🙂
PS. Fix number 6’s grammar “an new” 😛
Its like you learn my thoughts! You appear to know so much approximately this, like you wrote the e book in it or something. I feel that you just can do with some percent to force the message house a bit, but instead of that, this is magnificent blog. An excellent read. I’ll certainly be back.