There are no best practices. But there are some pretty bad ones when it comes to exceptions. Hans-Eric Grönlund wrote about some of them and I agree with his list. Here are a few more:
Breaking the contract
Languages like C# do not have checked exceptions. Nonetheless, exceptions are still part of the API contract. If compatibility is important, e.g. when you are rolling out a minor version update, you should not change the exception types thrown by existing methods.
// V1.1 public class NotCoolException : Exception { /* ... */ } public void EnsureCool() { if (!cool) { throw new NotCoolException(); } } // V1.2 public class UncoolException : Exception { /* ... */ } public void EnsureCool() { if (!cool) { // UH-OH... users upgrading from V1.1 won't be expecting this! throw new UncoolException(); } }
Some client-friendly alternatives to augment error functionality without breaking anything:
- Create the new exception type from an existing base class already thrown by the method.
- Create a new class or method with enhanced error reporting functionality (and while you’re at it, mark the old method as [Obsolete]).
Error monoculture
Have you ever seen code like this?
public void DoSomethingComplicated() { try { // complicated stuff ... } catch (TooTiredException e) { throw new BadThingHappenedException("Too tired to continue!", e); } catch (CableUnpluggedException e) { throw new BadThingHappenedException("Cable unplugged!", e); } catch (MachineOnFireException e) { throw new BadThingHappenedException("Machine on fire!", e); } // ... }
If you’re using the façade pattern then all of this is completely acceptable. However, if you expect users to be able to employ distinct error handling techniques for distinct errors, then they’re out of luck. These poor users will be stuck with poor practices like dispatching on error message or inner exception. If you’re faced with a defective API like this, considering making your own “exception beautifying” façade along the lines discussed in my earlier post.
Logging overload
Do not feel compelled to log full exception details on every catch and every throw. While the terminology would suggest otherwise, a lot of exceptions are decidedly not exceptional. Imagine a client that supports transparent failover between primary and backup servers hosting a REST API. Logging the full stack trace on every transient timeout error received is probably going overboard (especially given the expected failover behavior — this is just TMI). Consider instead logging only fatal errors or providing some feedback on abnormally pervasive error conditions. The first retry is not a big deal, but perhaps the 1000th is worrisome.
Exceptions are always a source of controversy. Please do your part and try not to make the exception landscape any more vexing.
Pingback: Log once, throw many | WriteAsync .NET