Exception handling basics

Every now and then, I encounter smart developers that seem to misunderstand how exceptions and exception handling works. Often regardless of language or technology stack. While I don't see these issues daily, I thought it might be interesting to reiterate on some exception handling basics.

Rethrow correctly

In .NET, it's important to rethrow exceptions correctly. This is a classic mistake for beginning developers, no shame in that. But I've seen it in code of more experienced developers too.

Take this piece of code:

Unless you're doing something special, you will want to change that throw e statement to throw; (without the e). If not, you will lose the stacktrace. Check out the difference:

Notice how, in the first code snippet, we didn't see the DoSomething method in the stacktrace. This makes a big difference when something goes wrong and you're trying to find where the exception originated from.

Add the original exception

When wrapping a caught exception in another exception, don't forget to include the original exception. Otherwise, again, you'll lose the complete stacktrace:

See the difference with this:

Let exceptions bubble

While the previous items were quite .NET specific, this one is more general. Another example should help demonstrate my point:

try  
{
    DoSomething();
}
catch (ArgumentException e)  
{
    throw new ApplicationException("An exception occurred in DoSomething", e);
}

While we're not violating the previous points here, the thing that bothers me, is that we're not adding any informating. The previous points were all about information. By removing the stacktrace when (re)throwing exceptions, we're also removing valuable information. This information will come in handy when trying to find the bug that caused the exception.

In this case, we're adding information, but of no value. You will know from the stacktrace that an exception occurred in the DoSomething method. So we're just adding pointless code.

If you do have reason to add information, by all means do so. Very often, you will do this to add information that will come in handy for reproducing bugs later:

public void DoSomething(int userId, int applicationId)  
{
    try
    {
        // complex logic here
    }
    catch (SomeException e)
    {
        throw new ApplicationException($"Could not do something for user {userId} and application {applicationId}.");
    }
}

In the above, made-up, example, we've added the userId and applicationId parameters to our exception message. Those will come in handy later.

Another great example is the exception you get when adding a duplicate key to a Dictionary. In the past, you never knew what the key was, leaving you in the dark. In .NET Core, they have added that information.

Throwing and catching generic exceptions

Some code analysis tools will tell you not to throw or catch generic exceptions. Should you avoid this at all costs? As so often is the case in software development, it depends.

My previous points were all about information, and I stand by those points. But this is one isn't so black or white.

In an ASP.NET application, you can definitely catch System.Exception instances. Most often, you will catch it in the Global.asax, log the exception and leave it at that. Because the request is finished after that, there's nothing more to do.

In client applications (like a WPF desktop app for example), this might not be so straightforward. Certain exceptions might mean internal application state has become corrupted and you might need a restart.

What about throwing System.Exception? Here, I would advise against it. Using a more specific exception will make it easier for the calling code to catch that specific type of exception. It also conveys more information.

You will find yourself throwing the same exceptions most of the time: ArgumentException and ArgumentNullException are two that come to mind.

Conclusion

Exceptions, or rather the throwing and handling of exceptions, are an important part of any application. With just a few guidelines to keep in mind, you can make your and your fellow developer's lives much easier. Exceptions are a way to communicate information, so use them correctly, and you'll be happy when you're trying to find that bug later on.