The try-catch statement can be a useful tool that prevents errors from becoming bugs.
Programming languages generally provide try-catch blocks to handle exceptions. Sometimes, you cannot avoid using more than one try-catch block and nesting them to handle all the exceptions.
Nested try-catch blocks are not an anti-pattern. They can be used to handle exceptions that occur in the outer catch block. Nevertheless, if you do not correctly define these nested try-catch blocks, they can cause code readability and maintenance problems.
This article discusses nested try-catch blocks with a C# code example, can they become software anti-patterns, how you can avoid them, and best practices for exception handling.
Is using nested try-catch blocks an anti-pattern?
When you include a try-catch block inside another try-catch block, it is called a nested try-catch block.
In a nested try-catch block, execution begins from the outer try-catch block. Suppose no exception occurs, then great. No catch block will be activated. However, when the exception occurs, the program execution will continue in the catch block if the catch block was designed to catch a specific error.
Now, if another exception happens inside the nested try-catch block, the nested catch block will try to catch and handle the exception. If the nested can’t catch the exception, the exception will be propagated further up to the call stack. If no catch block handles the exception, the program throws a system-generated message for an unchecked exception and likely crash.
C# code for nested try-catch
Let’s understand these nested try-catch blocks using a C# code example.
internal static void Main()
{
//Array with 6 elements
int[] arr = { 1, 2, 3, 4, 5, 6 };
int input = 8;
// print array elements and their quotients
for (int i = 0; i < arr.Length; i++)
{
//Outer try-catch block
try
{
Console.WriteLine($"\nValue: {arr[i]} " +
$"Quotient: {arr[i] / (arr[i] - 2)}");
}
catch (DivideByZeroException)
{
Console.WriteLine("DivideByZeroException occurred " +
"in outer Try-Catch Block");
try
{
Console.WriteLine($"Divide by zero happened " +
$"while handling the value {i}. {arr[6]}");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("IndexOutOfRangeException " +
"occurred in inner Try-Catch Block");
}
}
}
}
Output
Value: 1 Quotient: -1
DivideByZeroException occurred in outer Try-Catch Block
IndexOutOfRangeException occurred in inner Try-Catch Block
Value: 3 Quotient: 3
Value: 4 Quotient: 2
Value: 5 Quotient: 1
Value: 6 Quotient: 1
This C# code example shows the inner try-catch block. First, the outer try block throws the “DivideByZeroException”. Then, its catch block catches the exception of attempting to divide the second element by zero. Then the program tries to print an element, arr[6]. However, the array only has six elements. Therefore, it throws an “IndexOutOfRangeException”. The nested catch block catches the exception and prints to the console.
Nested try-catch blocks can be more complex than this containing more than one inner try-catch block. In addition, sometimes, only the catch block can contain the nested try-catch blocks.
try
{
}
catch (Exception e1)
{
try
{
}
catch (Exception e2)
{
try
{
}
catch (Exception e3)
{
}
}
}
What is an anti-pattern?
You might have heard the word “patterns” in software engineering. A pattern is a common solution for a repeated problem; usually, it is a good practice to use them as much as possible throughout your code. An anti-pattern is the exact opposite of it.
Anti-patterns are patterns developers should refrain from using. Developers tend to use them because they seem to be quick solutions to their coding problems. However, anti-patterns make code maintenance hard and can cause unnecessary bugs in the program.
Are nested try-catch blocks an anti-pattern?
You cannot completely say that nested try-catch blocks are an anti-pattern because it depends on how you use them and what your program requires to do with them. There are points for both sides.
If you are using the nested try-catch to handle an exception that happens inside the outer catch block, then you cannot say it is a bad programming practice. However, if you use many try-catch clauses that contain many lines of code, it becomes an anti-pattern.
For instance, suppose the outer block handles an arithmetic exception, and the inner block handles an I/O exception. Then it is reasonable to declare them like that. Also, it is a good practice to have a smaller scope of work within one try-catch block. This means you will have to use nested try-catch blocks.
The other common example of using the nested try-catch block is when you want to perform logging in the catch block. The logging operation should be safe in itself. But if it fails, you want to be sure not to cause an exception propagating further up the call stack.
If you can successfully handle the inner exception and allow the program execution to continue, there is no problem with using them. Therefore, it is completely ok to use them.
Large, nested try-catch statements become verbose, reduce the code readability, and cause unnecessary bugs in the program.
Especially for new developers, it will be difficult for them to navigate through the code and understand which section handles which exception. This ultimately affects code maintenance. Therefore, consider refactoring the code if you see such nested try-catch blocks or want to use them.
How do avoid nested try-catch blocks?
The best way to avoid nested try-catch blocks is to extract the nested try-catch section into a separate method and call them inside the outer try block. For example, you can write the above example C# code in the following manner.
internal static void Main()
{
//Array with 6 elements
int[] arr = { 1, 2, 3, 4, 5, 6 };
int input = 8;
// print array elements and their quotients
for (int i = 0; i < arr.Length; i++)
{
//Outer try-catch block
try
{
Console.WriteLine($"\nValue: {arr[i]} " +
$"Quotient: {arr[i] / (arr[i] - 2)}");
}
catch (DivideByZeroException)
{
PrintElements(arr, i);
}
}
}
private static void PrintElements(int[] arr, int i)
{
Console.WriteLine("DivideByZeroException occurred " +
"in outer Try-Catch Block");
try
{
Console.WriteLine($"Divide by zero happened " +
$"while handling the value {i}. {arr[6]}");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("IndexOutOfRangeException " +
"occurred in inner Try-Catch Block");
}
}
PrintElements
method wraps the inner try-catch block making the code more readable. You can also re-use the PrintElements() method in places you must use it.
What are best practices for exceptions?
Now that you have understood how nested try-catch blocks can become anti-patterns and how you can avoid them, let’s see the best practices for C# exception handling:
- It is a good practice to define try-catch blocks in places that have the potential to generate an exception. Do not forget to clean the resources by defining a final block or use ‘using’ statements if you want the program to clean the resources automatically.
- If your program executes remotely within different domains, make the exception data available to all the domains that need them. Make them available in a sharable common application base or a global cache.
- Define grammatically correct, meaningful, and clear error messages.
- Use localized error messages based on the user culture and language to improve user experience.
- When you define custom exceptions, use additional parameters in addition to the exception message string if they are helpful.
- Make use of ‘throw’ statements wherever possible.
- If your code uses the same exception in different places, use helper methods that build the exception and return it to reduce duplicate codes.
- If there are methods that do not complete the execution because of errors, restore their states.
Also, if you need to unit test that the exception has occurred in the code, follow the instructions from this article.
Conclusion
Exception handling using try-catch blocks is essential for a program’s smooth execution.
On many occasions, developers cannot avoid using nested try-catch blocks. As discussed in this article, using a nested try-catch statement does not become a software anti-pattern if you declare them correctly. However, nested try-catch blocks with many lines of code can be problematic. Thus, to not fail nested try-catch blocks, try to use separate methods that define the inner blocks.
In addition, do not forget to follow the best practices of exception handling we have summarized in this article.