There isn't one magic rule to throwing exceptions like there is for catching them (again, that rule is don't catch exceptions unless you can actually handle them). Nonetheless throwing exceptions, whether or not they be your own (which we'll cover next), is still pretty simple. First we'll look at the actual mechanics of throwing exceptions, which relies on the throw statement. Then we'll examine when and why you actually want to throw exceptions.
Throwing Mechanics
You can either throw a new exception, or rethrow a caught exception. To throw a new exception, simply create a new exception and throw it.
throw new Exception("something bad happened!");
//or
Exception ex = new Exception("something bad happened");
throw ex;
I added the second example because some developers think exceptions are some special/unique case - but the truth is that they are just like any other object (except they inherit from System.Exception which in turn inherits from System.Object). In fact, just because you create a new exception doesn't mean you have to throw it - although you probably always would.
On occasion you'll need to rethrow an exception because, while you can't handle the exception, you still need to execute some code when an exception occurs. The most common example is having to rollback a transaction on failure:
ITransaction transaction = null;
try
{
transaction = session.BeginTransaction();
// do some work
transaction.Commit();
}
catch
{
if (transaction != null) { transaction.Rollback(); }
throw;
}
finally
{
//cleanup
}
In the above example our vanilla throw statement makes our catch transparent. That is, a handler up the chain of execution won't have any indication that we caught the exception. In most cases, this is what we want - rolling back our transaction really doesn't help anyone else handle the exception. However, there's a way to rethrow an exception which will make it look like the exception occurred within our code:
catch (HibernateException ex)
{
if (transaction != null) { transaction.Rollback(); }
throw ex;
}
By explicitly rethrowing the exception, the stack trace is modified so that the rethrowing line appears to be the source. This is almost always certainly a bad idea, as vital information is lost. So be careful how you rethrow exceptions - the difference is subtle but important.
If you find yourself in a situation where you think you want to rethrow an exception with your handler as the source, a better approach is to use a nested exception:
catch (HibernateException ex)
{
if (transaction != null) { transaction.Rollback(); }
throw new Exception("Email already in use", ex);
}
This way the original stack trace is still accessible via the InnerException property exposed by all exceptions.
When To Throw Exceptions
It's important to know how to throw exceptions. A far more interesting topic though is when and why you should throw them. Having someone else's unruly code bring down your application is one thing. Writing your own code that'll do the same thing just seems plain silly. However, a good developer isn't afraid to judicially use exceptions.
There are actually two levels of thought on how exceptions should be used. The first level, which is universally accepted, is that you shouldn't hesitate to raise an exception whenever a truly exceptional situation occurs. My favorite example is the parsing of configuration files. Many developers generously use default values for any invalid entries. This is ok in some cases, but in others it can put the system in an unreliable or unexpected state. Another example might be a Facebook application that gets an unexpected result from an API call. You could ignore the error, or you could raise an exception, log it (so that you can fix it, since the API might have changed) and present a helpful message to your users.
The other belief is that exceptions shouldn't just be reserved for exceptional situations, but for any situation in which the expected behavior cannot be executed. This approach is related to the design by contract approach - a methodology that I'm adopting more and more every day. Essentially, if the SaveUser method isn't able to save the user, it should throw an exception.
In languages such as C#, VB.NET and Java, which don't support a design by contract mechanism, this approach can have mixed results. A Hashtable returns null when a key isn't found, but a Dictionary throws an exception - the unpredictable behavior sucks (if you're curious why they work differently check out Brad Abrams blog post). There's also a line between what constitutes control flow and what's considered exceptional. Exceptions shouldn't be used to control an if/else-like logic, but the bigger a part they play in a library, the more likely programmers will use them as such (the int.Parse method is a good example of this).
Generally speaking, I find it easy to decide what should and shouldn't throw an exception. I generally ask myself questions like:
-
Is this exceptional,
-
Is this expected,
-
Can I continue doing something meaningful at this point and
-
Is this something I should be made aware of so I can fix it, or at least give it a second look
Perhaps the most important thing to do when throwing exceptions, or dealing with exceptions in general, is to think about the user. The vast majority of users are naive compared to programmers and can easily panic when presented with error messages. Jeff Atwood recently blogged about the importance of crashing responsibly.
-
It is not the user's job to tell you about errors in your software!
-
Don't expose users to the default screen of death.
-
Have a detailed public record of your application's errors.
It's probably safe to say that Windows' Blue Screen of Death is exactly the type of error message users shouldn't be exposed to (and don't think just because the bar has been set so low that it's ok to be as lazy).
Dostları ilə paylaş: |