Answering exactly to what you are asking, try-catch
blocks are used to handle exceptions, so you should put them where you will actually be handling the exception.
This can be a bit subtle, so let me put some example: imagine you have some method to do something with a file, and the path to the file is given as an argument. There may happen many bad things on that situation:
If the file can't be found, or it can't be open (ex: permissions issues, blocked by some procoess, etc), then it's the caller's and not the callee's fault, and the exception produced is already a description of the problem: let it propagate.
If the file is not in the expected format (for example, you get some int.Parse()
calls failing because there should be a number but there isn't), it's still the caller's fault (it passed you a path to a bad file, how mean!), but the inner exception is not accurate of the problem; here the catch-wrap-throw
pattern works best: you catch the exception, wrap it into a more descriptive exception type (ie, create a new exception of the good type, and put the old one as it's InnerException
), and throw that one: the caller doesn't care about which task within your code failed, it should care about why it failed.
Now, let's tweak the scenario a bit for the last case: let's assume your library tries to read some optional configuration files, at a pre-defined location. If loading any of these fails, the exception should be handled by the library itself, falling back to using the default options or whatever.
In summary: if the exception is a direct consequence of the caller doing something wrong, let it propagate. If it's an indirect consequence (for example, passing the path to the wrong file), use catch-wrap-rethrow
to tell the caller what it did wrong, rather than how it broke your code's logic. And if it's a signal to an unusual but foreseeable condition that you are able to handle, then you just handle it. In addition to that, you can do some "sanity checking" at the start of some methods and preventively throw a descriptive exception when you know some of the argument values will cause trouble, instead of waiting to the trouble actually arising (so this is an alternative to the catch-wrap-rethrow
when the issue can be detected early).
Back on the client code (the code that uses your API
), exceptions should be handled ASAP. Take that literally: as soon as possible, but not before it is actually possible. For example, a function that acts just as a wrapper to another one (a "home-made" curry) shouldn't try to handle exceptions triggered by the arguments it got passed, unless it can actually fix the issue and retry. Example (extremely generic):
void DoSomethingEasily(object someArgument) {
MyAPI.DoSomething(someArgument, "some-predefined-string-argument");
}
// ... somewhere else on the code ...
DoSomethingEasily(myObject);
In that scenario, handling exceptions from MyAPI.DoSomething
can be a split work: if the exception is caused by the "some-predefined-string-argument"
alone, then it's DoSomethingEasily
's job to handle it. If the exception is caused by myObject
, then DoSomethingEasily
shouldn't mess up and let the outer code deal with their own trouble.
If there is some exception that is not caused by the arguments (for example, caused by some state of the API, or by external factors such as a device failing), the ASAP idea applies again: if DoSomethingEasily
has enough information / context to be able to deal with these cases, then it definitely should. If it can't, however, it'll be better to not get in the way so the code calling it has a chance to deal with the problem.
Bonus suggestion: regardless of the case (reason), any exception thrown by your API
should always be documented. If you are using C#
with the Visual Studio IDE, then it's quite advisable to use the built-in XML comment documentation format, so the exceptions (and their description) will show up on Intellisense's tooltips.
Hope this helps.