Is Your Problem Exceptional?
Error
handling is frequently ignored or postponed until “after the basic
functionality is working”. However,
designing a standard error handling strategy is critical to producing reliable
software, especially if you are writing a software library that will be used by
other developers. Even if you aren’t
building a library for distribution, other developers on your team or project
depend on your code.
When should
you throw an exception? As mentioned in
David Abraham's discussion of exception strategies, the
usual answer is that exceptions are only for unexpected cases. Unfortunately, this answer breaks down. Experienced developers are good at thinking
of strange cases, but that doesn’t mean that every considered case is no longer
unexpected. A better guideline is that
exceptions should be returned if the caller should have known better.
The calling code is using passed parameters to indicate an understanding
of the situation; if that understanding is incorrect, the caller needs to
know! Another useful guideline is that
constructors must throw exceptions if the alternative is to return an object
that is in an invalid state.
The best way
to improve your design skills is to consider complex problems. The following scenarios define a number of
situations that could be resolved through exceptions. Some are clear candidates for exceptions, in
others exceptions are not the best solution. Please send me other interesting exception
handling scenarios, especially ones where the right answer is debatable. I’m planning on expanding this to a story,
probably with a discussion of what approach is correct for each scenario.
1. You are building an online store. The user follows an
“add to cart” link that has the product ID embedded in the query string. The
product ID is invalid; no such product exists in the database.
2. You have a method Foo(). Occasionally it detects an error and
needs to return error information to the caller. Which of the following
two approaches do you prefer?
2.1.
bool Foo(object param,
errorInfo)
// returns
false to indicate an error, error info is set into the errorInfo
param
2.2.
void Foo(object
param)
// throws an exception to indicate problems, error info is in the
exception
3. Revisit the above scenario: what if Foo() is a performance critical method that
will be called millions of times?
4. You write a database helper utility class that offers a static
public string GetConnectionString() method. What happens if the utility class can’t
find the connection string?
5. You are writing a StringCollection class that offers typed access to a
collection of strings. You design a public int
Find(string s) method that returns the index of
string s in the collection. What if string s doesn’t exist in the
collection?
6. You are building a framework to validate custom business
rules (e.g., required fields or min/max validations). Users of your
library will define rules and then pass in data structures to be validated
against those rules. How do you indicate rule violations?
7. You have created a new BigInt datatype
that holds unlimited sized integers. It offers a static public BigInt BigInt.Parse(string) method to create a new BigInt from a string. How
do you indicate that the string is invalid?
8. You are writing a MP3 player to compete with Winamp. You write code to serialize the playlist to playlist.xml when the
application is closed. When the application starts it reads in the
serialized playlist, so you write an instance method
like public void Playlist.ReadPlaylist(string filename). How should that method handle
the case where playlist.xml does not exist?
9. You are designing a persistent
object framework. Every persistent object derives from a base class that
provides concurrency checking and transaction support. The derived
classes override an protected abstract Save()
method that will be called by the base class. You need to decide how the
derived classes should indicate problems to the base class.
Feedback appreciated.