Exception handling is common to most programming languages, and the mechanism and behaviors are similar in most languages: try\catch\finally. It is well documented. We are not going to discuss that.
However, different approach to exception handling is required in different types of applications like a Library, a UI application, a Framework, etc., and that is the focus of this article. We'll see how exception handling needs to change depending on the nature of the application.
Let's start with some fundamentals.
Exception is an event during the execution of a program that disrupts the normal flow and prevents the program from doing what it is expected to do e.g., a program tries to write to a log file but the file cannot be opened.
In such error conditions, runtime looks for handlers (try-catch) that are registered to handle such exceptions, and when a handler is found, it is invoked with an Exception object having information about the event. The following diagram shows the normal flow of the program (Runtime > main > Method1 > Method2) and the flow when an exception occurs in Method2.
When an exception is not handled by a program, it reaches the runtime where it will be considered an unhandled exception which may result in a crash.
Two Pass vs One Pass: Net Runtime has Two Pass exception handling implementation. Two Pass means the runtime first traverses through entire call stack to find a handler, and after either finding a handler or determining that its an unhandled exception, it runs all the finally blocks in the call stack. JVM on the other hand runs finally block in a method if a handler is not found in that method and then it moves up the call stack. See CLR Exception Handling and JVM Exception Handling for detailed explanation.
Asynchronous Execution
Exception handling in asynchronous execution is little different and it is important to understand. Let's see with an example.
The following is Java code that runs a method on new thread and exits.
public class AsyncRun implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("AsyncRun.run on thread " + Thread.currentThread().getId());
throw new ArithmeticException();
}
}
public static void main( String[] args ) {
try {
Thread t = new Thread(new AsyncRun());
t.start();
System.out.println( "main on thread " + Thread.currentThread().getId());
throw new NullPointerException();
}
catch(Exception ex)
{
System.out.println(ex.getClass().getName() + " handled");
}
}
This would be the output:
main on thread 1
AsyncRun.run on thread 15
java.lang.NullPointerException handled
Exception in thread "Thread-0" java.lang.ArithmeticException
at exceptionhandling.AsyncRun.run(AsyncRun.java:13)
at java.base/java.lang.Thread.run(Thread.java:835)
NullPointerException thrown by the main method is handled by the try-catch in main method, but the exception in AsyncRun.run method is treated as an unhandled exception because JVM could not find a handler in the call stack on the thread running AsyncRun.run. AsyncRun.run is the first method to be called on the thread so the call stack ends there, so the try-catch in the main method won't apply to AsyncRun.run.
However, unlike main thread, unhandled exception on a separate thread did not result in a crash. .Net Framework has same behavior as Java for asynchronous execution.
Alternative to Exceptions
Alternative to Exceptions is error handling, but for that the program has to validate all the scenarios and return different error codes e.g., a function writing to log files would have to first check if file exists and then return an error code if it doesn't, and the calling function needs to check the return value to see if the call was successful or not. This can be very unproductive and error prone.
Exception handling is required primarily for the following reasons:
Where to handle exception is very important and broadly there are two types of places where exceptions should be handled:
1. All Entry Points in to the program \ application to prevent crashes. As we have seen above when an exception reaches the runtime or framework, it may cause a crash. Entry Points can be one or more like:
2. To support different flows in the event of an error, exception handling is required where such diversions in program flow need to be implemented.
Now let's try to see how all this applies for different application types:
A library is a set of functions / classes focusing on some problem domain.
Libraries do not control the flow of program. Functions of libraries are called by the main program with some input and they return some output or provide some behavior e.g, as shown in diagram below, RecommendationEngine executable uses libraries for logging, data access, etc. to perform its job but the flow of program is controlled by the executable only.
Responsibility to prevent a crash or implement an alternate flow lies with the main program.
Therefore, code in a library should not suppress exceptions because that would prevent the calling application from fulfilling alternate flow and logging requirements - it can catch exceptions and rethrow or provide expected output by catching exceptions.
There can be scenarios where a library needs to handle exceptions like explained in the following case, but it should not suppress the exceptions e.g., continuing with the above example, let's say the data can come from MySQL or MongoDB so the DataAccess library itself relies on other libraries. But to make the RecommendataionEngine depend only on the DataAccess library, the DataAccess library can handle exception from MySQL and MongoDB and then throw its own exception. Re-throwing would ensure that the RecommendationEngine gets a chance to decide what to do with the exception.
APIs can be thought of as a library of functions exposed over a network e.g., continuing earlier example, if there is a decision to expose the Data Access APIs as REST APIs, it would look like the following.
As in the case of Library, the caller of API (the RecommendationEgine) should handle the exception, but because of the change in the nature of invocation of Data Access APIs, exceptions need to be handled by API also for the following reasons:
Framework
Framework is an implementation of Inversion of Control pattern. A Framework discovers, loads and calls its plug-ins on events a plug-in is interested in. Both the framework and the plug-ins communicate with each other using known public interfaces e.g, this is typical Rest API application in Java & Spring.
This is how it works:
In the above diagram the calls from Spring to the application - marked with X - are vulnerable points as Spring cannot assume that the application would not throw exceptions, so it would handle exceptions e.g., if UserController.create method throws exception, Spring MVC would handle it and trigger alternate flow to return an appropriate HTTP response indicating error.
So a framework must handle exception in all the places where it is invoking methods on its plug-in.
Framework itself cannot be the reason for a crash so it must also make sure that it internally doesn't allow any exception to go unhandled and reach the runtime because that may result in a crash.
UI Application
Almost all UI applications are written using some framework with the framework calling the application when there is some event. This makes the event handlers an entry point in to the application so exceptions should be caught on event handlers.
Additional exception handling in a UI application will vary based on what the application is doing e.g, if the application is doing work on background threads, the entry points \ starting methods of the thread should handle exceptions.
Any application should not let exceptions go unhandled with the exception of Libraries. At the minimum, all exceptions should be handled at the entry points in to the application and all starting methods of a new thread to prevent crashes. Additionally, handle exceptions where alternate flow of program is required in the event of an exception.
Previously published at https://dev.to/pathiknd/exception-handling-in-different-types-of-applications-38eo