Error management is a fundamental part of software development that allows us to detect, handle, and correct unexpected or exceptional situations during the execution of a program.
An error, which we will also call EXCEPTION, is an abnormal or unexpected condition that interrupts the normal flow of execution of a program.
Error management is part of flow control structures because they modify the normal procedure of the program. Whether intentionally or not.
The occurrence of an EXCEPTION can happen for various reasons. For example,
- Syntax error (that is, you’ve messed up a part of the code)
- Accessing invalid memory (like an undeclared variable, or going out of the length of a collection)
- Accessing an unavailable resource (a file, a database, or a web request…)
- Voluntarily thrown
- Many other causes…
Whatever the reason, the important thing is that an error has occurred. Then the program throws a warning that an anomaly has occurred. We generally call this warning EXCEPTION.
When an exception occurs, two things can happen,
- You can catch it, and try to contain the error
- If you don’t catch it, it propagates to the calling function
If the exception keeps “rising” through calls until it reaches the main function, an error will pop up for the user. In this case, the norm is that the program crashes and stops functioning (in fact, the norm is that it closes abruptly, “boom mode”).
Throwing and Catching Exceptions
In most programming languages, exceptions are thrown when an error occurs using the throw
statement (or similar).
throw new Exception("This is a manually thrown exception!");
These exceptions can be caught and handled by other parts of the code using try-catch
or try-catch-finally
blocks.
try
{
// Code that may throw an exception 💣
}
catch (Exception ex)
{
// Handling the exception, only jumps if an error occurs 💥
}
finally
{
// Optional final block, always executes, whether an error occurs 💥 or not 👍
}
Try Block: The try block contains the code that you want to execute and that could throw an exception.
This code runs normally, but if an exception occurs within thetry
block, control of the program immediately transfers to thecatch
block (the remaining code in the try block would not execute).Catch Block: The catch block executes only if an exception occurs within the try block. <br.This is where you specify how to handle the exception (like displaying an error message, logging the exception, or performing recovery actions).
. Finally Block (optional): The finally block always executes, whether an exception occurs or not in the try block. It is used to specify code that must run after the execution of the try block, regardless of whether an exception occurs or not.
This is useful for resource cleanup (like closing files or database connections, which must be done regardless of whether an error occurs).
Example of Exceptions in Different Languages
Let’s see how to use a try-catch
block to catch exceptions and handle them in a controlled manner in different languages, and how to throw an exception manually with throw
(or similar).
For the example, we will simply use dividing by zero, which is a disallowed operation that generates an error. It’s a very simple way to force throwing the exception for the example.
In C#, exceptions are used with the Exception
object. They are caught with a try-catch-finally
block.
// Example in C#
try
{
// Code that may throw an exception
int result = 10 / 0; // Division by zero 💥
}
catch (Exception ex)
{
// Handling the exception
Console.WriteLine("Error: " + ex.Message);
}
While throwing an exception manually would be done like this.
throw new Exception("This is a manually thrown exception!");
In C++, creating a try-catch
block is identical. In this case, exceptions are part of the std
namespace.
// Example in C++
try
{
// Code that may throw an exception
int result = 10 / 0; // Division by zero 💥
}
catch (const std::exception& ex)
{
// Handling the exception
std::cout << "Error: " << ex.what() << std::endl;
}
While this is how you would throw an exception manually.
throw std::runtime_error("This is a manually thrown exception!");
JavaScript also uses the same try-catch
syntax for error catching.
// Example in JavaScript
try {
// Code that may throw an exception
let result = 10 / 0; // Division by zero 💥
} catch (error) {
// Handling the exception
console.log("Error: " + error.message);
}
Throwing manual exceptions is also similar. In this case, the exception is called Error
.
throw new Error("This is a manually thrown exception!");
Finally, Python as always has its own syntax, and performs the block with try-except
# Example in Python
try:
# Code that may throw an exception
result = 10 / 0 # Division by zero 💥
except ZeroDivisionError as ex:
# Handling the exception
print("Error:", ex)
While throwing an exception manually is done with raise
.
raise Exception("This is a manually thrown exception!")
As usual, apart from the small syntax differences, most programming languages include exception management, with more similarities than differences.
Best Practices Tips
You should not abuse the use of the try-catch
block. Its use should be limited to truly exceptional or unpredictable situations, and not as a mechanism to control the normal flow of the program.
If it is predictable that an error will occur in a process, we should first check the possible causes of errors. The try-catch
block is left only for things that we cannot check.
For example, let’s imagine that you want to access a file. Reading a file is a process that is always susceptible to generating exceptions, because it is a resource of the operating system that we do not control.
try
{
string content = File.ReadAllText(file);
Console.WriteLine("File content: " + content);
}
catch (Exception ex)
{
Console.WriteLine("Error during file reading: " + ex.Message);
}
One possible cause is that the file does not exist. That is not an exception, it is a clear, evident, and known reason that we cannot read it. Therefore, the clean thing is to make that check beforehand.
if (File.Exists(file))
{
try
{
string content = File.ReadAllText(file);
Console.WriteLine("File content: " + content);
}
catch (Exception ex)
{
Console.WriteLine("Error during file reading: " + ex.Message);
}
}
else
{
Console.WriteLine("The file does not exist.");
}
We only leave the try-catch
for reading the file. Which may be susceptible to errors, even if the file exists. This is already an unforeseen or situation that we cannot foresee, and this is where it makes sense to wrap it in a try-catch
.
This is so, firstly, for cleanliness. But, on the other hand, because the try-catch
has a significant impact on the performance of the program. So we should only use it for what it is, management of unforeseen errors. and not for everything.