(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
Lamont Harrington recently wrote this:
"For example, when I see code performing some type of data access operation, I expect to see a
try...catch...
statement around the operation that's performing the task. Just because a particular operation performs successfully after 100 tests, does it mean mean that on the 101st test an unexpectedSqlException
can't be thrown?"
I think that might be the wrong question. My response would be something like this: sure, of course a
SqlException
might be thrown, but is this really the right place to catch it?
That's not meant as a rhetorical question, however much it might sound like one. Sometimes the answer will be yes, and sometimes no - it depends on the context.
If the data access code lives inside some service, and is only ever used inside of that service, then often, it will be appropriate to let the exception bubble most of the way up the stack. When writing a service, I usually implement the remote API using the remote facade pattern, because the network interface your service exposes will need to be defined with the kind of coarse-grained approach suitable for a remote API, but your won't want to implement the guts of your service that way. One of the jobs of the facade is to manage errors that occur when processing incoming messages. The details will vary from one case to the next, but it often boils down to something like this:
try { string result = MyBusinessLogic.DoTheRealWork(x, y); SendSuccessResponse(result); } catch (ApplicationException x) { MyLoggingInfrastructure.DOh(x); SendFailureResponse("Internal error processing request"); }
Obviously that's stylised. If you're doing an RPC style of service API, then you would doubtless just return data
rather than explicitly generating responses like this. But the general pattern is the same - if something goes wrong
deep in the guts of our code, our request is doomed, so we just let the exception bubble up to the top of the tier
before dealing with it. (Any necessary tidy up typically goes in a finally
block.)
In particular, we do not, as Harrington suggests, have faith that the operation will succeed. Far from it. We simply deal with the failure higher up the stack.
But this is not always appropriate. There are certain boundaries where you will want to map exceptions onto something else. (Either different exceptions, or something else entirely like sending a message somewhere.) In fact the code above is just such an example - the exception has travelled to a boundary where we can't just let it carry on. We need to catch it and map it into some kind of error indication to the client of our service. (That might look like a SOAP fault in a web service, or some other message indicating a problem. In a .NET remoting scenario it might be appropriate to throw an exception, but you would probably want to map it to some documented exception rather than just allowing exceptions from the depths of your code to be thrown straight out at the client.)
In general, some kind of mapping like this will always be required whenever crossing a tier boundary. It may or may not be appropriate for layer boundaries within tiers too.
So would it ever be appropriate to catch an exception right down at the scope at which it would get thrown in the first place, rather than letting it bubble up? I can think of two good reasons you might want to do that: recovery, and mapping.
Sometimes you will be able to recover from the problem - if you have alternate behaviour you can employ
to work around the failure, a local catch
block is appropriate. However, although those
who are new to exceptions often assume that this is what most of their exception handling will consist of,
it turns out to be relatively rare. In most cases, if an exception occurs, then even if you anticipate it, your
operation will doomed to fail because it depends on the operation that just failed. This means that you'll need
to be throwing some kind of exception anyway. The only question is whether you want to throw a different
exception from the one that drew your attention to the problem in the first place. This brings us onto the
other common reason for catching exceptions at source: mapping.
It is common for certain exception types to make sense only in the context in which they
are thrown. For example, the Int32.Parse
method will throw a FormatException
if the text being parsed is not a valid integer. That exception makes sense at the point at which it is thrown,
but in the broader scheme of things it might be indicative of a bad parameter having been passed, so you
might want to map it to an ArgumentException
. E.g.:
public void Foo(string input) { // Of course as of .NET V2 (aka Whidbey, aka VS.NET 2005) // we'd just use int.TryParse instead. try { int i = int.Parse(input); SendSuccessResponse(result); } catch (FormatException) { throw new ArgumentException("Should be an integer", "input"); } }
(Of course this suggests that Foo
simply has the wrong signature... But sometimes you just
end up being given a string...) It's not uncommon for code not to bother doing this, and for a FormatException
to bubble up the stack to a point where it's plain bewildering. So in cases like these it is useful to throw a different exception type just because
the original one doesn't really reflect the true nature of the problem.
But in general, these cases where you either need to map the exception to something else, or can
handle it in situ are relatively unusual. The vast majority of exception handling code will consist of try
...
finally
blocks (or using
statements) - code that makes sure resources are cleaned
up when exceptions occur, without actually attempting to catch the exception. (So if Harrington had said that
he expected to see try
... finally
blocks or using
statements
around the code in question, then I would have agreed with him.)
If it's news to you that your finally
blocks should be outnumbering your catch
blocks by a significant margin, then you're probably doing something wrong. This is not an original thought.
You'll find it expressed in the middle of this
excellent article (2nd paragraph of the 'Managed Exceptions' section). It also seems to be broadly recognized in the Java world.
The main thing to do when an exception is thrown is to clean up your resources, and ensure that you leave everything in
a consistent state. You don't need to catch the exception to do that. In fact you shouldn't! finally
and
using
are your friends.