Why exception handling is important
This illustrates the need for system-level as well as component-level exception handling mechanisms. Unfortunately, no well defined techniques exist for building robust exception handling into a system. Most methods are ad hoc and limited to what the design team can anticipate the system will encounter. Luckily, many of the most common problems can easily be avoided as long as code is written to check for them.
Many exception failures in commercial libraries are linked to simple conditions such as checking that a pointer is not null before dereferencing it, or checking that a file is open before attempting to read or write to it. Good software engineering practices such as code reviews, code walkthroughs, and thorough testing can illuminate many of these exceptional conditions, but are limited to the software of the system.
It is also difficult to model the complex interactions of system components at the design phase to determine where other problems lie. It is unrealistic to build a system that is completely bulletproof to exceptional conditions because we cannot anticipate all possible situations.
Therefore it is necessary to build in default exception handlers that will attempt to recover from any of these unanticipated conditions. If the application is somewhat safety critical or has real-time deadlines, some form of graceful degradation must be put in place to reduce the harm or damage done by any system failures.
That is, never try to implement a catch-all handler for the base exception type. We can waste a huge amount of time debugging what the actual problem is, when it could be as simple as a syntax error:.
You might have noticed the error in the previous example; return is mistyped. Although modern editors provide some protection against this specific type of syntax error, this example illustrates how rescue Exception does harm to our code.
At no point is the actual type of the exception in this case a NoMethodError addressed, nor is it ever exposed to the developer, which may cause us to waste a lot of time running in circles. The previous point is a specific case of this rule: We should always be careful not to over-generalize our exception handlers.
This severely affects the extensibility and maintainability of the code. If we do attempt to handle different exception subtypes in the same handler, we introduce fat code blocks that have too many responsibilities. As we will see, often there exists a different part of the application that would be better suited to handle specific exceptions in a more DRY way.
So, define the single responsibility of your class or method, and handle the bare minimum of exceptions that satisfy this responsibility requirement. For example, if a method is responsible for getting stock info from a remote a API, then it should handle exceptions that arise from getting that info only, and leave the handling of the other errors to a different method designed specifically for these responsibilities:.
Here we defined the contract for this method to only get us the info about the stock. It handles endpoint-specific errors , such as an incomplete or malformed JSON response. This is the complement to the last point. An exception can be handled at any point in the call stack, and any point in the class hierarchy, so knowing exactly where to handle it can be mystifying.
To solve this conundrum, many developers opt to handle any exception as soon as it arises, but investing time in thinking this through will usually result in finding a more appropriate place to handle specific exceptions. Note that although this is not technically an exception handler, functionally, it serves the same purpose, since client.
In this case, however, repeating the same error handler in every controller action is the opposite of DRY, and damages maintainability and extensibility. Instead, we can make use of the special nature of exception propagation, and handle them only once, in the parent controller class , ApplicationController :.
This gives us the freedom to fiddle with them if we want to handle specific cases at the lower level, or simply let them propagate gracefully. When developing a gem or a library, many developers will try to encapsulate the functionality and not allow any exception to propagate out of the library.
The library provides developers with two approaches for completeness. The save method handles exceptions without propagating them, simply returning false , while save! This gives developers the option of handling specific error cases differently, or simply handling any failure in a general way. In that case, if there is any uncertainty, it is best to expose the exception , and release it into the wild. Take the earlier example of a stock API consumer fetching stock prices.
We chose to handle the incomplete and malformed response on the spot, and we chose to retry the same request again until we got a valid response. But later, the requirements might change, such that we must fall back to saved historical stock data, instead of retrying the request.
How could they? It was never exposed to them before. We will also have to inform the owners of projects that rely on our library. This might become a nightmare if there are many such projects, since they are likely to have been built on the assumption that this error will be handled in a specific way.
Now, we can see where we are heading with dependencies management. The outlook is not good. So here is the bottom line: if it is unclear how an exception should be handled, let it propagate gracefully. There are many cases where a clear place exists to handle the exception internally, but there are many other cases where exposing the exception is better. So before you opt into handling the exception, just give it a second thought. A good rule of thumb is to only insist on handling exceptions when you are interacting directly with the end-user.
Try to respect the same convention, especially if you are going to open-source your library. Any checked exceptions that can be thrown within a method must be specified in its throws clause.
Because all exceptions thrown within a program are objects, the grouping or categorizing of exceptions is a natural outcome of the class hierarchy.
An example of a group of related exception classes in the Java platform are those defined in java. Its descendants represent more specific errors. For example, FileNotFoundException means that a file could not be located on disk. A method can write specific handlers that can handle a very specific exception. The FileNotFoundException class has no descendants, so the following handler can handle only one type of exception.
A method can catch an exception based on its group or general type by specifying any of the exception's superclasses in the catch statement.
You can find details about what occurred by querying the argument passed to the exception handler. For example, use the following to print the stack trace. You could even set up an exception handler that handles any Exception with the handler here. The Exception class is close to the top of the Throwable class hierarchy. Therefore, this handler will catch many other exceptions in addition to those that the handler is intended to catch. You may want to handle exceptions this way if all you want your program to do, for example, is print out an error message for the user and then exit.
0コメント