The Software Interview Question that you should be aware of!
Exception handling is a crucial aspect of software development that allows you to manage and recover from unexpected or exceptional situations that can occur during program execution. These exceptional situations are typically errors or unforeseen conditions that disrupt the normal flow of a program. Exception handling helps improve the robustness and reliability of your code by providing a structured way to deal with such situations.
You know me, I like to get straight to the point, so place your booty in a comfortable position, and Let’s Get Right Into It!
Exception Handling
All of the examples will be in the context of .NET. Keep in mind, however, that exceptions are existing for every single language and it will be very beneficial for any programmer to be acquainted with as many details as possible.
In C#, as we mentioned, there are four main keywords used for exception handling:
- try: This keyword marks a block of code where you expect exceptions to occur. It encloses the code that might cause an exception. If an exception occurs within the try block, the runtime will immediately jump to the appropriate catch block (if available) to handle the exception.
- catch: The catch block follows the try block and specifies how to handle different types of exceptions. It contains code that is executed when an exception of a particular type is thrown within the associated try block. You can have multiple catch blocks to handle different types of exceptions.
- finally: The finally block is used to contain code that needs to be executed regardless of whether an exception was thrown or not. It is often used for cleanup operations such as closing files or releasing resources. The code in the finally block will execute even if there is a return statement in the try or catch block, or if an exception is not caught.
- throw: The throw keyword is used to explicitly raise an exception. You can throw both built-in and custom exceptions. Throwing an exception allows you to signal that an error or exceptional condition has occurred and provides information about the problem.
Exception Mastery
You knowing how to use the blocks, mentioned above will be enough to be able to answer the interview question.
I however love to go in details. Below I will give examples of bad and good error handling. Let’s take a look:
1 — Use specific exception types:
Bad Example:
try
{
// Some code that may throw an exception
}
catch (Exception ex)
{
// Catching the base Exception type
Console.WriteLine("An error occurred: " + ex.Message);
}
Improved Example:
try
{
// Some code that may throw a specific exception
}
catch (FileNotFoundException ex)
{
Console.WriteLine("File not found: " + ex.FileName);
}
catch (IOException ex)
{
Console.WriteLine("Input/output error: " + ex.Message);
}
Why?
You should be wary about which exception you catch. Not only because you have to resolve them in different ways, depending on the exception. But also because it will help you with logging.
2 — Catch only what you can handle:
Bad Example:
try
{
// Some code that may throw an exception
}
catch (Exception ex)
{
// Suppressing the exception without proper handling
// This can lead to hidden bugs and make debugging difficult
}
Improved Example:
try
{
// Some code that may throw an exception
}
catch (SpecificException ex)
{
// Handle the specific exception appropriately
Console.WriteLine("An error occurred: " + ex.Message);
}
Why?
Let’s say that you are working with some web sockets. You can’t forsee all of the problems that can happen. However, you can foresee a few. Those that we know we can handle, should be handled.
3 — Log exceptions:
Bad Example:
try
{
// Some code that may throw an exception
}
catch (Exception ex)
{
// No logging of the exception
Console.WriteLine("An error occurred: " + ex.Message);
}
Improved Example:
try
{
// Some code that may throw an exception
}
catch (Exception ex)
{
// Log the exception for diagnosis
Logger.Log(ex);
Console.WriteLine("An error occurred. Please contact support.");
}
Why?
When working with clients, the problems that can happen are very hard to detect. You should always log when you have the chance. This will help you understand the problem when it comes back. Otherwise, you will be building extended loggings and just waste time.
If you are worried that it will make the file huge, just place a special debug flag that will log only when a specific value is found in your application. This way the user can “enable” the logs when he stumble upon a problem.
4 — Keep catch blocks short:
Bad Example:
try
{
// Some code that may throw an exception
}
catch (Exception ex)
{
// Complex logic within the catch block
if (ex is SomeSpecificException)
{
// Handle specific case
}
else if (ex is AnotherSpecificException)
{
// Handle another case
}
}
Improved Example:
try
{
// Some code that may throw an exception
}
catch (SomeSpecificException ex)
{
// Handle specific case
}
catch (AnotherSpecificException ex)
{
// Handle another case
}
Why?
This one is depending on the problems that are encountered. An engineer might not be able to always keep the blocks short, but it will definitely be better for reading later on!
5 — Use finally for cleanup:
Bad Example:
FileStream fileStream = null;
try
{
fileStream = new FileStream("file.txt", FileMode.Open);
// Some code that may throw an exception
}
catch (Exception ex)
{
Console.WriteLine("An error occurred: " + ex.Message);
}
finally
{
// Cleanup code mixed with exception handling
if (fileStream != null)
{
fileStream.Close();
}
}
Improved Example:
try
{
using (FileStream fileStream = new FileStream("file.txt", FileMode.Open))
{
// Some code that may throw an exception
}
}
catch (Exception ex)
{
Console.WriteLine("An error occurred: " + ex.Message);
}
Why?
While the primary purpose of the finally
block is indeed for code cleanup and ensuring that certain actions are taken regardless of whether an exception occurred or not, there are a few other scenarios where you might consider using the finally
block:
- Resource Management: Apart from direct cleanup, you might use the
finally
block for resource management that goes beyond immediate cleanup. For instance, you might use it to release a lock or unlock a resource that should be held for as short a time as possible.
- Logging or Metrics: You might use the
finally
block to log information or record metrics about the execution of a block of code. This can provide useful diagnostic information even if an exception is thrown.
- State Restoration: In some cases, you might want to restore the application’s state or some variables to a consistent state before exiting a method, regardless of whether an exception occurred. This could be especially relevant in complex algorithms or long-running processes.
- Special Cases Handling: In certain cases, you might want to handle special cases that don’t directly involve cleaning up resources. For example, you might display a message to the user or send a notification when an exceptional situation arises.
- Conditional Logic: While it’s generally best to keep
finally
blocks focused on cleanup, there might be cases where you use them for conditional logic based on the outcome of the try
or catch
blocks. However, this approach can make your code less readable and more difficult to maintain.
Remember, the primary role of the finally
block is to ensure that certain actions are always taken, regardless of whether an exception occurred. If your use of the finally
block extends beyond this purpose, it's important to ensure that your code remains clear, maintainable, and logically organized. If you find that your finally
block is becoming too complex or performing actions that are unrelated to cleanup, you might want to reconsider your design and consider refactoring your code to achieve better separation of concerns.
Outro
I hope you found this article useful. I had tons of fun writing it.
Thank you for being with me and I will see you in the next one! Bye!