Wrapping Code in Try-Catch Blocks
Oct 18, 2018 • 7 Minute Read
The Problem This Solves
In coding, unexpected things happen – especially when dealing with systems and code that we don’t control; which is pretty much all the time. When something unexpected happens, this is called an Exception – the code we are writing is the rules and the problem that occurs is the exception. We control what happens when exceptions occur with structured error handling – in C#, the primary mechanism for structured error handling is the try-catch block.
Creating solid structured error handling requires imagination. Frank Borman described the cause of the Apollo 1 accident, a terrible fire which killed three astronauts, as a “failure of imagination” – a set of unexpected circumstances – exceptional circumstances – arose, which no one had accounted for or envisioned. When we write code, we necessarily imagine a happy path where all our premises and plans proceed without problems. Because things fall apart, because things don’t go as planned, we must try and imagine how they will fall apart, so that we can make our systems robust and able to recover from these exceptions.
Our Scenario
Consider an application which relies on a price being updated occasionally – maybe we have a cache file which stores the price and we connect periodically to a REST service across the Internet to update it. When everything works correctly, our network update service works correctly. But the server has an unreliable network connection – every few days, there’s a network traffic storm and the connection to the service fails, throwing an exception. Because our code doesn’t have structured error handling in place, when this failed connection exception occurs, our update service crashes.
Our code creates a PriceClient object, connects to the remote server, and calls a function to retrieve the price:
public class ServiceUpdater
{
public static decimal GetCurrentPrice()
{
var priceClient = new PriceClient();
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
}
Everything works great as long as this line works correctly:
priceClient.Connect();
But, as we mentioned, occasionally this will fail and throw an exception. Because there’s no structured error handling to capture the exception, the exception rises up through the call stack until it is exhausted and crashes the application.
We know about this problem now – we don’t have to imagine it – and we can certainly imagine that it’s going to happen again. Let’s wrap this code in a try-catch block:
public static decimal GetCurrentPrice()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
}
catch
{
}
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
The code that we imagine might be problematic, the call to Connect(), is now wrapped in a try block. Any exception that occurs in the code inside the catch block will immediately transfer to the code in the catch block, where we can specify what we want to happen if the call fails. Thus, if priceClient.Connect() throws a ConnectionFailedException when it attempts to connect, the exception will be handled and the execution of the code will continue without crashing. But with the code like this, it won’t continue for very long.
Try-Catch and Variable Scope
The problem with this simple approach is that if the call to Connect() failed, that means that the service didn’t connect – and we can’t get the price, which is the very next thing we try to do:
var updatedPrice = priceClient.GetPrice();
So, it looks like we need to move that line inside the try block as well:
public static decimal GetCurrentPrice ()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
}
catch
{
}
return updatedPrice;
}
So far so good. But when we try to compile this, we receive the following error:
The name ‘updatedPrice’ does not exist in the current context.
Because we’re declaring updatedPrice inside the try block-
var updatedPrice = priceClient.GetPrice();
-it is not available to the scope outside. We would have the same problem if we tried to return the price inside of the catch block. Here’s a key point:
Variables declared inside a try or catch block are local to the scope of the block.
This is the problem we must solve: resolving exactly how we want the scope of the updatedPrice variable to work. It also gives us a nudge to consider exactly what it is that we want to happen when the call fails and how we want to represent that fact to the rest of the system. The default way is what we started with, just throwing an exception up the stack and causing the application to crash – that’s not what we want.
One way we can handle this is by representing a failed attempt to update the price as a null result. Our method is currently returning a type of decimal – decimal is a value type, so it’s not inherently nullable. To make it nullable, we add a question mark:
public static decimal? GetCurrentPrice ()
This essentially means, “this function will either return a decimal, or it may be null.” So, in plain English, or pseudo-code as it is called, our function works like this:
- Create a client object.
- Try to connect to the service.
- If that attempt fails, stop and return null.
- If it succeeds, get the price and return that.
We’ll move our entire happy path, where everything goes as we expected, inside the try block, and move our unhappy path, what happens if something goes wrong, inside the catch block:
public static decimal? GetCurrentPrice ()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch
{
return null;
}
}
Now our GetCurrentPrice call will never crash, as long as the code outside the try block, the call to create the priceClient object, doesn’t throw any exceptions.
Consuming the New Null Value
It’s important to consider that now that the meaning of the return value of the function has changed slightly, from being a pure decimal value to being a nullable decimal, the code that consumes the value must also be changed to know what to do when a null comes back. The old code which consumed GetCurrentPrice may have looked like this:
var updatedPrice = ServiceUpdater.GetCurrentPrice();
SaveUpdatedPriceToCache(updatedPrice);
To make the system aware that we don’t want to update the price if it’s null, we can change it to this:
var updatedPrice = ServiceUpdater.GetCurrentPrice();
if (updatedPrice != null)
{
SaveUpdatedPriceToCache(updatedPrice);
}
Now, the code will do NOTHING if the attempt to get the price failed – which is just what we want.