Error handling. It’s not something that people pay much attention to when they’re learning programming. But in reality, it’s a very important and necessary process.
How should error handling be done?
What to do in error handling?
Dealing with the unexpected
Programs often encounter unusual events during their operation.
For example, if you have a shopping site program
- When I tried to buy the product, it was sold out …
- When I try to connect to the server to pay for my purchase, I can’t get through …
- I don’t know why, but the program suddenly ended halfway through …
Error handling is the process of dealing with such behavior that differs from normal operation
Without error handling, this would happen:
- Even though the item is sold out, the shopping process continues.
- The amount was settled even though the transaction was interrupted midway.
To prevent this, error handling is essential.
Three important points about error handling
There are three important things to consider when programming error handling:
- If an error occurs, immediately abort
- Ensure resources are released when an error occurs
- Record error information for situation analysis
(1) Stop immediately
When an error occurs, the program must stop normal processing and move on to the abnormal processing.
It is important to immediately stop the program when an error occurs.
Do not proceed with the process if the error persists. It would be strange to proceed to the payment screen when the product is sold out.
There’s a saying that goes “it’s better to crash than to trash,” which means it’s better to stop things right there than to let the error continue and cause chaos.
Don’t ignore errors, address them promptly.
(2) Make sure to release resources
A resource is something that a program uses. For example:
- memory
- file
- network
- database
Resources must be allocated when you need them and released when you’re done with them, otherwise they won’t be available for the next process.
In the event of an error, the process will be halted, so if you are not careful, resources will remain reserved and not released, which can lead to even more serious errors.
It is important to ensure that resources are released in error as well as normal cases .
(3) Record error information
When an error occurs, the cause may not be immediately apparent.
You need to keep a record of what happened so that you can analyze the cause of the error later.
This recording is called logging. In most cases, error information is stored in a text file that is invisible to the user. This file is called a log file.
Programming three ways of thinking
Let’s take a look at these three concepts and how to actually program them.
How to implement error handling in programming
There are two things you need to do to handle errors:
- Notify that an error has occurred
- Do something about the error
In a program that is divided into modules, these two things are often handled in separate modules.
There are two main ways to implement it.
- Return value method: Errors are reported by returning a return value, and the returning module handles the error.
- Exception method: Errors are communicated by throwing an exception, and the module that catches the exception handles the error.
- Languages that use the return value method include C, Go, and Rust.
- Exceptions are used in many languages: C++, Java, Python, Ruby, PHP, JavaScript, Kotlin, Swift…
Historically, error handling in programming languages started with the return value method (C language), then became mainstream with the exception method (C++, Java, etc.) , and then
returned to the return value method (Go, Rust) . Here, we will explain using the mainstream exception method.
This is an example of an exception program in Java.
public class ExceptionHandlingExample {
public static void main(String[] args) {
try {
// Code that may throw an exception
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
// Handling for the case where the exception is an ArithmeticException
System.out.println("Division by zero is not allowed.");
}
}
public static int divide(int num1, int num2) {
// Validate the arguments
if (num2 == 0) {
throw new ArithmeticException("Division by zero is not allowed.");
}
// Perform the division
return num1 / num2;
}
}
Let’s take a look at what happens when handling errors.
(1) Use throw to immediately abort
When an error occurs, an exception is generated by throw (also called “throwing an exception”).
When a throw is executed, processing jumps to the catch instead of continuing to the next step. If there is no catch within that method, processing is traced back to the caller.
(2)-1 Release resources with finally
Many programming languages provide some means to ensure that resources are released.
There are two main ways to do this.
The first is to ensure that resources are released using finally.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FinallyExample {
public static void main(String[] args) {
FileInputStream file = null;
try {
file = new FileInputStream("example.txt");
// Add code to read the file or perform other operations
} catch (FileNotFoundException e) {
System.out.println("The file was not found.");
} finally {
// Perform resource cleanup, such as closing the file
if (file != null) {
try {
file.close();
} catch (IOException e) {
System.out.println("An error occurred while closing the file.");
}
}
}
}
}
The catch block runs only when a throw occurs. This is where you handle the error.
A finally block will always be executed, whether or not a throw occurs.
By including the resource release process here, resources will always be released, whether the process is normal or an error occurs.
(2)-2 Releasing resources with try-with-resource
Another way to release resources is called try-with-resource.
In a try block, if you declare a resource that is allocated within the block, it will be released when the block ends.
The try block ends when the last operation in the block is completed or when the block is exited with a throw.
In either case, the resources are released when the try block is exited.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
try (FileInputStream file = new FileInputStream("example.txt")) {
// Add code to read the file or perform other operations
} catch (FileNotFoundException e) {
System.out.println("The file was not found.");
} catch (IOException e) {
System.out.println("An error occurred while reading the file.");
}
}
}
How to release resources by programming language
The implementation of automatic resource release mechanisms varies considerably depending on the programming language.
The basic programming syntax is fairly similar across languages, but this automatic resource release has a huge variety of possibilities.
Below is an example.
- C++ RAII
- Java try-with-resource
- C# using
- Python with
- Swift defer
- Kotlin usr
- Go defer
- Rust Drop trait
(3) Recording error information using a logging library
Logging is typically accomplished by writing error information to a text file.
Avoid implementing logging processes (output to log files) yourself.
Implementing it yourself has the following problems:
- When an error occurs, writing may not be possible reliably.
- There is a risk of crashing due to conflicting writes when using parallel processing (multi-threading, etc.)
Make sure you use a logging library. For Java, a popular one is log4j.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ExceptionHandlingSample {
private static final Logger logger = LogManager.getLogger(ExceptionHandlingSample.class);
public static void main(String[] args) {
try {
// Code that may throw an exception
divide(10, 0);
} catch (ArithmeticException e) {
// Handling when an exception occurs
logger.error("Divide by zero error", e);
}
}
public static int divide(int dividend, int divisor) {
if (divisor == 0) {
throw new ArithmeticException("Divide by zero");
}
return dividend / divisor;
}
}
The most important thing about logging content is to be able to identify where the error occurred, and secondly,
to be able to trace the process leading up to the error .
How to get a stack trace for each programming language
A stack trace is useful for tracking the process when an error occurs. A stack trace can tell you the following when an error occurs:
- Where the error occurred (file name, line number, function or method name, etc.)
- The call stack of the function or method that caused the error
- Information associated with each function or method invocation, such as argument values
Many programming languages automatically collect stack traces when an error occurs, so you can use those.
- Java printStackTrace and getStackTrace in Exception class
- C# StackTrace property in Exception class
- Python traceback module
- JavaScript stack in the Error object
- Swift Thread.callStackSymbols
- Kotlin printStackTrace and getStackTrace in Exception class
- Go runtime.stack, debug.PrintStack
- Rust std::backtrace::Backtrace
Be sure to include log output processing.
Summary
Error handling is often overlooked because it is not very exciting.
However, they are essential to maintaining the soundness of a program. By mastering the basics of error handling, you can make your programs robust.
Comments