Three .NET Best Practices to Keep in Mind
Practice #1: Catch Exceptions at the Highest Possible Level (Tier)
In general, exceptions should be bubbled up to the highest possible level to be caught and processed. For example, in our project, a search goes through the following logical tiers:
UI -> Services -> Data/SharePoint/Refinement -> Web Services
There are quite a few instances where we’re catching exceptions in the Data or Services layer, logging them using common logging components or configurations, and then swallowing the exception one way or another.
This is a bad practice for few reasons:
- This behavior is not transparent to developers that may be using the DLL but do not have access to the source code.
- Logging from lower tier components is generally a bad idea, as it either requires a static logging location embedded in the dll or creates a dependency on the UI to provide pre-determined logging mechanism (i.e. EntLib). (This can get especially ugly with EntLib, and EntLib Logging and ExceptionHandling blocks generally use named “policies” for handling and logging exceptions, and referencing them from a middle-tier component requires any UI consuming it to implement the exact same logging configuration.
- Applications consuming the DLL do not know about or are not given the ability to choose how to react to exceptions in the code.
- Swallowing exceptions dilutes the effectiveness of unit tests testing these functions, as tests can’t explicitly test for scenarios that might cause specific exceptions,
- Swallowing exceptions can make debugging issues a nightmare (if not impossible) for developers that might consume this middle-tier component and not have access to the source code.
- Catch blocks have an inherent performance cost. Catching exceptions unnecessarily results in unnecessary performance degredataion.
There are a few legitimate cases where this best practice might not be followed:
1. The Exception contains potentially sensitive information
If the exception or exception message contains potentially sensitive information such as user credentials, server names, or any other sensitive information, it likely makes sense to catch the exception, create an exception that accurately describes the issue without the sensitive information, then throw that exception. Note however, there are very few scenarios where this should not occur at the UI layer.
2. The exception is too vague or needs more metadata to be meaningful to the code consuming it.
If the exception by itself is too vague or requires rebadging or augmented metadata to be useful to the code consuming it, it generally makes sense to wrap the exception by creating an exception that allows the original exception to be set as an “InnerException”, and augments the original exception with more information. This new exception should then be thrown.
3. The exception that was caught should not affect the execution of the code
If an exception occurs that should not affect the execution of the code, it may be swallowed or ignored in limited cases. However, exceptions really should not be being thrown unless there is a severe enough issue to potentially interrupt program execution, so I’d suggest reviewing the code throwing the exception you are swallowing in this case.
4. The Exception Occurs At A Physical Tier Boundary
Your multi-tier applications may not only contain multiple logical tiers, but physical tiers as well. (for example, your web application may rely on a web service layer on a separate physical server). While your web service layer may be “middle-tier” as far as the overall application is concerned, exceptions should be caught, processed, and logged at the boundary to that physical tier. Logging on the machine where the services resides aides in issue resolution, and it gives an opportunity to review exceptions for sensitive information before sending them over the network, where they may be intercepted.
Practice #2: You Should (Almost Never) Catch System.Exception
You should almost never write the following:
This code says that you don’t know what type of exception to expect, so catch them all.
Any exception that the code is not expecting is, by definition, an unexpected or unhandled exception. You probably want your application to crash (or gracefully exit) with an “Unhandled Exception” message if an unexpected exception occurs! How can you be certain of the stability of the currently running instance of your code if an exception occurred somewhere that you didn’t expect?
Also, catching specific exceptions helps greatly in diagnosing issues, and displaying useful and meaningful error messages to your users. I think we all know how frustrating a generic error message such as “An error occurred.” can be.
If you think an exception might occur but you don’t know the type, either determine the type and catch it or don’t implement a handler at all! If you need to throw an exception and there’s not an exception type available that is specific to the error that occurred, create a new one.
Practice #3: Always Null-Check Objects Returned from a Function Call
Review the 5 lines of code below.
What’s wrong with this picture?
If you said Null Reference Exception you’re right. Never, ever access any member of any object that has been returned from a function without checking for null. You should never assume that a function will return an instantiated object.
Even if you wrote the function you’re calling.
We all work on projects with multiple developers, and at any given time another developer (or even you) may alter the code and end up returning a null object without realizing that there is some expectation that an instantiated object be returned by some callers.
Taking the extra time to type 15-20 extra characters can mean the difference between stable software and buggy, error-prone code.
I wrote this as an e-mail to my team after doing some refactoring and realized that it could be beneficial to a wider audience being that violations of these practices are so common. I’ve made these mistakes (as I’m sure many of us have) numerous times in the past, so writing this up was a good reminder for me to focus on these points when writing code.
Please feel free to post additions, corrections, questions, and concerns in the comments section below! The dialogue will benefit all involved!