Exceptions 3: Where to put catch blocks and handle exceptions

This is the third article in a series about exceptions:

  1. Basics
  2. Types and filtering
  3. Where to put catch blocks and handle exceptions
  4. Finishing up
A blue map pin stuck into Paris, in a map of Europe
Where your catch blocks are and where exceptions are dealt with matters – probably best to avoid sticking a pin into your code randomly to choose the spot.
Image credit

The location of catch blocks

So far, the splitting of errors via catch blocks hasn’t given us anything that returning an error code couldn’t.  This section is where exceptions reveal their big difference from error codes.

In all the previous examples, exceptions are caught by catch blocks immediately after the call to code that could generate the exception.  This is not required.  You don’t have to put any catch blocks anywhere, and if you put them in code they don’t need to be so closely tied to the source of exceptions.

To show you what I mean, imagine you have three methods A, B and C. A calls B, B calls C and C can throw an exception.  Instead of putting the catch block in B (or even C) as you might expect, it will be in A:

void A()
{
   try
   {
      Console.WriteLine(“In A, before the call to B”);
      B();
      Console.WriteLine(“In A, after the call to B”);
   }
   catch(Exception e)
   {
      Console.WriteLine(“In A, in the catch block”);
   }
   Console.WriteLine(“In A, after the catch block”);
}

void B()
{
   Console.WriteLine(“In B, before the call to C”);
   C();
   Console.WriteLine(“In B, after the call to C”);
}

void C()
{
   Console.WriteLine(“In C, before the exception is thrown”);
   throw new Exception(“oh dear”);
   Console.WriteLine(“In C, after the exception is thrown”);
}

If you looked at the output, you would see:

  1. In A, before the call to B
  2. In B, before the call to C
  3. In C, before the exception is thrown
  4. In A, in the catch block
  5. In A, after the catch block

Lines 1-3 are as you’d probably expect.  When C throws the exception, the world goes into a different mode.  Other then finally blocks (see below) no code in this code block (the method C, in this case) or any enclosing code blocks / methods that called it will be executed until a matching catch block is found.  This has the effect of unwinding or destroying the call stack immediately, and this destruction stops only when a matching catch block is found.

So, given that there’s no catch block in C, the line after the throw is never executed, and the stack is unwound by one frame, to get to B.  The system is still ignoring any code apart from matching catch blocks, so the line after the call to C is also skipped.  There’s no catch block here either, so the stack is unwound by one more frame to get to A.  The normal (outside a catch block) line after the call to B is skipped, but a catch block is found.  The code in it is executed, and then the exception is considered dealt with.  Execution drops out of the catch block, and normal service is resumed (with the stack staying in its rather unwound state).  This means that the line after the catch block is executed as normal.

This is the main difference of exceptions.  Once they are thrown, they are treated as special things by the runtime system.  They are like catch-block-seeking-missiles, moving up through the call stack destroying everything in their way until they find a matching catch block.  This happens automatically.  If you compare that to returning an error code – this would need to be added to the method signatures of all the methods involved, and the value of the error code would need to be manually passed out all the way, i.e. with no help from the runtime system.

One thing that you get for free (because it’s in the base Exception class) is a stack trace.  This is a record (usually just a lump of text rather than anything more structured) of how the stack was when the exception was thrown (not when it was caught).  So, in the previous example, when the exception is caught in A the stack trace will contain A, B and C.

Note that filtering and the location of catch blocks combine.  If there had been a catch block inside B but it was for an incompatible type of exception, it wouldn’t have matched, and so the call stack destruction would have still happened.

Where should you catch an exception?

The main place to catch an exception is where you can handle it, i.e. process it completely so that the code can move on from the error.  The general rule is: Unless a given bit of code can handle the exception, it should let the exception continue on its destructive way.  So, for instance, if there is a method that loops through a list of independent bits of work, then it is a suitable place to catch exceptions from the Nth bit of work, so it can attempt bit N+1.

There are some exceptions to this general rule, which I’ll go through now.  If a bit of code can’t handle an error but can help the code that will handle the error, it might be a good idea for it to do so.  For instance, if some data wasn’t passed down to the code that threw the exception, and this data would be helpful when dealing with the exception, then there’s an argument for catching the exception at that point, adding the data, and then re-throwing the exception or throwing a different one.  This isn’t always a good idea (it might be better to pass the data down), but it’s worth considering.

Other exceptions to this rule are to do with exceptions that can’t be dealt with before they escape into the outside world in some way.  For instance, they will turn into incomprehensible errors that the user sees on the screen, or get written to a user-accessible file etc.  There are at least two things wrong with this.

The first problem is user experience (UX).  Even if you can’t deal with the error, turning the incomprehensible error into just a bland error message in user-appropriate language is better.  So, having some kind of back-stop catch block is often a good idea.

The second problem is security.  The error message that’s incomprehensible to end users can be very helpful to attackers.  So there are further reasons to have a back-stop catch block.

Both of these concerns don’t usually apply for code that users never see, e.g. development builds of the code, that run only in internal environments.  There is a risk that this version of the code will end up with end users or attackers (if you have your environments or build pipelines mis-configured, for instance).  However, it can speed up the spotting and fixing of bugs if exceptions and other errors are displayed in an obvious location (that might be unsuitable for production).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s