Exceptions: Promoting Good Design Patterns

by Peter A. Bromberg, Ph.D.

Peter Bromberg
"I never forget a face, but in your case I'll be glad to make an exception." --Groucho Marx

In "Coding Standards, Team Development and You" I covered and ranted about several bothersome issues, one of which was the EVIL practice of using non-standard error reporting patterns. This originally surfaced on the MS C# Language newsgroup by an OP who wanted to use an instance of an Exception - derived class as the return value of a method. I'm going to amplify on that issue here, only this time, I'll rely on the real gurus who wrote the book on all this - namely, Brad Abrams, Kryzsztof Cwalina, and a supporting cast of experts that includes Jeffrey Richter, Steven Clarke, Brent Rector, Chris Brumme, Jason Clark, Rico Mariani and others.



Usually, when I write up a short article like this to focus on a specific area of best-practices coding guidelines, it is not only because I feel it will likely be of value to readers -- it's also because I'm focusing on the area for my own benefit, in order to reinforce a concept and to become "a better coder".

Developers who come from other platforms and languages may be used to "return-value-based" error reporting. In the .NET platform, the Exception framework promotes API consistency, because exceptions are designed to be used for failure reporting and nothing else. Also, Exceptions integrate well with object-oriented languages. In the case of constructors, operator overloads and properties, one has no choice in the return value, and an error - reporting method like the exception, which is out-of-band of the method signature, is the only option.

Exceptions should be used to report all errors for all code constructs



With exception-based error-reporting, quality code can be written so that a number of operations and methods are performed with the error - handling logic grouped after the try block, or even higher up in the call stack. Developers can catch a series of logically-grouped exception types, and provide much more meaningful information about any failures.

In the above-captioned article, I made reference to a situation where a developer took it upon himself to create a class that had an error property which got populated if there was an exception. I was expecting to get back a DataTable from his method call, and instead, I got nothing. No exception was thrown, even though I was fully prepared to handle one. The class was not documented at all. This is an example of HOW NOT TO EVER handle failures in the .NET Framework. DO NOT populate "error fields' in the class or return an "error value". THROW AN EXCEPTION, Period! Of course, it took me a while, but I finally realized this bloke was swallowing the generated exception and using it to populate his "error" property, which I was supposed to remember to check!

Wrong, Wrong, Wrong! Wrong because it wasn't documented or expected, wrong because it's not best-practices coding technique, and wrong because it short-circuits all the good things that come as a benefit of using exceptions to report ALL YOUR ERRORS and FAILURES in your code!

When you use the return-code or error-property error handling model, if the API you are calling fails, this can allow your program to continue running with incorrect results, causing it to crash or corrupt data at some point. With the exception handling model, when an error occurs the thread is suspended and the calling code is provided an opportunity to handle the exception. When no method up the stack handles the exception your application is terminated (at least, in .NET 2.0).

That's the way it's supposed to be, and that is the right way. It is far better to have your application blow up than to let it muddle on, at least until the error can be fixed. Use exceptions to report all failures and error situations. DO NOT use error return values or properties - that is not good .NET Framework design. And above all, DO NOT "SWALLOW" exceptions! If you aren't sure what to do , use catch { throw;} semantics; the calling code can gain access to the full exception stack, and can decide what to do.

Why do you want to use ONLY EXCEPTIONS to handle all your failure conditions? Here are just a few reasons:

  • Exceptions can carry rich information describing the cause of the failure.
  • Exceptions allow for unhandled exception handlers -- which can log and help diagnose issues.
  • Exceptions promote instrumentation (e.g., Performance Monitor stats).
  • Exceptions allow for debuggers to break when an exception is thrown.

That last item alone should be sufficient reason. The main point is that exceptions are an object-oriented way to report failure conditions, and they are out-of-band to the method signature and any return values or objects.While I am on it, some more goodies about using exceptions from our friends the gurus:

  • DO report execution failures by throwing exceptions.
  • CONSIDER terminating the process by calling System.Environment.FailFast (.NET 2.0) instead of throwing an exception, if your code reaches a situation where you consider it unsafe for further execution.
  • DO NOT use exceptions for the normal flow of control, if possible.
  • CONSIDER the performance implications of throwing exceptions.
  • DO document all exceptions thrown by publicly callable members because of a violation of the member contract and treat them as part of your contract.
  • DO NOT have public members that can either throw or not based on some option such as a bool parameter (.e.g., bool throwOnError)
  • DO NOT have public members that return exceptions as the return value or as an out parameter.
  • CONSIDER using exception builder methods.
  • DO NOT throw exceptions from exception filter blocks
  • AVOID explicitly throwing exceptions from finally blocks.
  • DO throw the most specific (the most derived) exception that makes sense.
  • DO NOT swallow errors by catching nonspecifc exceptions ( e.g., catch (Exception e) { } // swallowed whole!)
  • CONSIDER catching a specific exception when you understand why it was thrown in a given context and you can respond to the failure programmatically.
  • DO Clean up any side effects when throwing an exception. For example, if a Hashtable.Insert method throws an exception, the caller can assume that the specified item was not added to the Hashtable.
  • DO NOT derive all new exceptions directly from the base class SystemException. Inherit from SystemException only when creating new exceptions in System namespaces. Inherit from ApplicationException when creating new exceptions in other namespaces.
  • DO use the predefined exceptions types. Define new exception types only for programmatic scenarios.
  • DO NOT overcatch. Exceptions should often simply be allowed to propagate up the call stack.
  • DO prefer using an empty throw when catching and rethrowing an exception. This is the best way to preserve the exception call stack.
  • AVOID creating custom exception classes when there is already an exception type that's "good enough".
  • DO Design classes so that in the normal course of use an exception will never be thrown.
  • DO NOT return Error Codes!

When I first started with .NET about 5 years ago, I was so happy I could use exceptions just to find out what I was doing wrong! But, there is a lot more, and to study that, you'll need to either read up on MSDN, or preferably, invest in the book! And, an investment it will really be: you are making a deposit on the future of your career. Good Zen students know better than to ask the Master, "Make me a Zen Master, too! It's URGENT! Email me the answer right away!" They know they must study hard, practice, and endure many years of pain to become a master. They understand there are no shortcuts. Take the time to study the patterns and practices, and implement quality design patterns in your code.

 

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: