This article focuses on some misunderstandings in the selection and use of Java exceptions. I hope that readers can master some of the points and principles of exception handling, and pay attention to summary and induction. Only by handling exceptions can we improve the basic qualities of developers, improve the robustness of the system, enhance user experience, and improve the value of the product.
Misunderstanding 1. Abnormal choice
Figure 1. Anomaly classification
Figure 1 describes the structure of the exception. In fact, we all know that abnormalities are divided into detecting abnormalities and non-detecting abnormalities, but in practice, the application of these two abnormalities is confused. Because non-detected exceptions are easy to use, many developers think that detection of exceptions is useless. In fact, the abnormal application scenarios can be summarized as follows:
1. The call code cannot continue to be executed and needs to be terminated immediately. There are too many possibilities for this situation, such as the server is not connected, the parameters are incorrect, etc. Non-detected exceptions are applicable at these times, and there is no need to call explicit capture and processing of code, and the code is concise and clear.
2. The calling code needs further processing and recovery. If SQLException is defined as non-detected exceptions, developers will naturally believe that SQLException does not require calling explicit capture and processing of code, which will lead to serious situations such as not closing the Connection, not rolling back the Transaction, and dirty data in the DB. It is precisely because SQLException is defined as detecting exceptions that developers will be driven to explicitly capture and clean up resources after the code generates an exception. Of course, after cleaning up resources, you can continue to throw non-detected exceptions to prevent the execution of the program. According to observation and understanding, detection of exceptions can be mostly applied to tool classes. Java Learning Group 669823128
Misunderstanding 2: Display exceptions directly on the page or client.
It is common to print exceptions directly on the client side. Taking JSP as an example, once the code runs, the container prints the exception stack information directly on the page by default. In fact, from the customer's perspective, any exception has no practical significance, and most customers cannot understand the exception information at all. Software development should also try to avoid presenting the exception directly to users.
Listing 1
<strong>package</strong> com.ibm.dw.sample.exception;/** * Custom RuntimeException * Add error code attribute*/<strong>public</strong> <strong>class</strong> <strong>RuntimeException</strong> <strong>extends</strong> <strong>java</strong>.<strong>lang</strong>.<strong>RuntimeException</strong> { //Default error code <strong>public</strong> <strong>static</strong> <strong>final</strong> Integer GENERIC = 1000000; //Error code <strong>private</strong> Integer errorCode; <strong>public</strong> <strong>RuntimeException</strong>(Integer errorCode, Throwable cause) { <strong>this</strong>(errorCode, <strong>null</strong>, cause); } <strong>public</strong> <strong>RuntimeException</strong>(String message, Throwable cause) { //Use the general error code <strong>this</strong>(GENERIC, message, cause); } <strong>public</strong> <strong>RuntimeException</strong>(Integer errorCode, String message, Throwable cause) { <strong>super</strong>(message, cause); <strong>this</strong>.errorCode = errorCode; } <strong>public</strong> Integer <strong>getErrorCode</strong>() { <strong>return</strong> errorCode; } }As the example code shows, introduce error codes into exceptions. Once an exception occurs, we just present the error code of the exception to the user, or convert the error code into a more understandable prompt. In fact, the error code here also contains another function, and developers can also accurately know what type of exception occurred based on the error code.
Misconception 3: Pollution of code hierarchy
We often divide the code into different hierarchies such as Service, Business Logic, DAO, etc. The DAO layer will contain methods of throwing exceptions, as shown in Listing 2:
Listing 2
<strong>public</strong> Customer <strong>retrieveCustomerById</strong>(Long id) <strong>throw</strong> SQLException { //Query the database based on ID}At first glance, there is no problem with the above code, but if you think carefully from the perspective of design coupling, the SQLException here pollutes the upper calling code. The calling layer needs to explicitly use try-catch to capture, or further throw it to the higher level. According to the design isolation principle, we can appropriately modify it to:
Listing 3
<strong>public</strong> Customer <strong>retrieveCustomerById</strong>(Long id) { <strong>try</strong>{ //Query the database based on ID}<strong>catch</strong>(SQLException e){ //Use non-detected exception encapsulation to detect exceptions, reduce hierarchical coupling<strong>throw</strong> <strong>new</strong> RuntimeException(SQLErrorCode, e); }<strong>finally</strong>{ //Close the connection and clean up resources}}Misconception 4: Ignore exceptions
The following exception handling is just outputting the exception to the console, and it makes no sense. Moreover, an exception appears here and the program does not interrupt, and the call code continues to be executed, resulting in more exceptions.
Listing 4
<strong>public</strong> <strong>void</strong> <strong>retrieveObjectById</strong>(Long id){ <strong>try</strong>{ //..some code that throws SQLException }<strong>catch</strong>(SQLException ex){ /** *Anyone who knows it knows that the exception printing here is meaningless, just outputting the error stack to the console. * In the Production environment, the error stack needs to be output to the log. * And the program continues to execute after the catch process, which will lead to further problems*/ ex.printStacktrace(); }}Can be reconstructed:
Listing 5
<strong>public</strong> <strong>void</strong> <strong>retrieveObjectById</strong>(Long id){ <strong>try</strong>{ //..some code that throws SQLException } <strong>catch</strong>(SQLException ex){ <strong>throw</strong> <strong>new</strong> RuntimeException("Exception <strong>in</strong> retrieveObjectById", ex); } <strong>finally</strong>{ //clean up resultset, statement, connection etc }}This misunderstanding is relatively basic, and under normal circumstances, you will not make this low-level mistake.
Misconception 5: Include exceptions in loop statement blocks
As shown in the following code, the exception is contained in the for loop statement block.
Listing 6
<strong>for</strong>(<strong>int</strong> i=0; i<100; i++){ <strong>try</strong>{ }<strong>catch</strong>(XXXException e){ //…. }}We all know that exception handling occupies system resources. At first glance, everyone thought that they would not make such a mistake. From another perspective, a loop is executed in class A, and the method of Class B is called in the loop, but the method called in class B contains statement blocks such as try-catch. The hierarchy of the class has faded, and the code is exactly the same as above.
Misconception 6: Use Exception to capture all potential exceptions
Several different types of exceptions are thrown during the execution of a method. For the sake of simplicity of the code, the base class Exception is used to catch all potential exceptions, as shown in the following example:
Listing 7
<strong>public</strong> <strong>void</strong> <strong>retrieveObjectById</strong>(Long id){ <strong>try</strong>{ //…Code call that throws IOException //…Code call that throws SQLException}<strong>catch</strong>(Exception e){ // Here all potential exceptions caught by the base class Exception. If multiple levels catch this, the valid information of the original exception will be lost <strong>throw</strong> <strong>new</strong> RuntimeException("Exception <strong>in</strong> retrieveObjectById", e); }}Can be reconstructed
Listing 8
<strong>public</strong> <strong>void</strong> <strong>retrieveObjectById</strong>(Long id){ <strong>try</strong>{ //..some code that throws RuntimeException, IOException, SQLException }<strong>catch</strong>(IOException e){ // Just catch IOException <strong>throw</strong> <strong>new</strong> RuntimeException(/*Specify the error code corresponding to IOException here*/code, "Exception <strong>in</strong> retrieveObjectById", e); }<strong>catch</strong>(SQLException e){ //Just catch SQLException <strong>throw</strong> <strong>new</strong> RuntimeException(/*Specify the error code corresponding to SQLException here*/code, "Exception <strong>in</strong> retrieveObjectById", e); }}Misconception 7: Multi-level encapsulation throws non-detected exceptions
If we always insist that different types of exceptions must use different capture statements, then most examples can bypass this section. However, if only one piece of code calls will throw more than one exception, it is often not necessary to write a catch statement for each different type of Exception. For development, any exception is enough to explain the specific problem of the program.
Listing 9
<strong>try</strong>{ //The RuntimeException, IOExeption or others may be thrown; //Note the difference between here and Misunderstanding Six, here is a piece of code that throws multiple exceptions. The above are multiple segments of code, each throwing different exceptions}<strong>catch</strong>(<strong>Exception</strong> e){ //As always, convert Exception to RuntimeException, but the e here is actually an instance of RuntimeException, and has been encapsulated in the previous code. RuntimeException(/**/code, /**/, e);}If we convert all Exceptions into RuntimeException as shown in the above example, then when the Exception type is already RuntimeException, we do another encapsulation. The RuntimeException was reencapsulated again, and the valid information carried by the original RuntimeException was lost.
The solution is that we can add relevant checks to the RuntimeException class to confirm that the parameter Throwable is not an instance of RuntimeException. If so, copy the corresponding attribute to the newly created instance. Or use different catch statement blocks to catch RuntimeException and other Exceptions. Personal preference method 1, the benefits are self-evident.
Misconception 8: Multi-level printing exception
Let’s first look at the following example, which defines 2 classes A and B. Class B code is called in class A, and both Class A and Class B capture and print exceptions.
Listing 10
<strong>public</strong> <strong>class</strong> <strong>A</strong> { <strong>private</strong> <strong>static</strong> Logger logger = LoggerFactory.getLogger(A.class); <strong>public</strong> <strong>void</strong> <strong>process</strong>(){ <strong>try</strong>{ //Instantiate class B, you can change to other injection methods such as B b = <strong>new</strong> B(); b.process(); //other code might cause exception } <strong>catch</strong>(XXXException e){ //If the class B process method throws an exception, the exception will be in B The class is printed, and it will also be printed here, so logger.error(e); <strong>throw</strong> <strong>new</strong> RuntimeException(/* Error code*/ errorCode, /*Exception information*/msg, e); } }}<strong>public</strong> <strong>class</strong> <strong>B</strong>{ <strong>private</strong> <strong>static</strong> Logger logger = LoggerFactory.getLogger(B.class); <strong>public</strong> <strong>void</strong> <strong>process</strong>(){ <strong>try</strong>{ //Code that may throw exception} <strong>catch</strong>(XXXException e){ logger.error(e); <strong>throw</strong> <strong>new</strong> RuntimeException(/* Error code*/ errorCode, /*Exception information*/msg, e); } }}The same exception will be printed 2 times. If the level is a little more complicated, it is a headache to not consider the system performance of printing logs, and simply locate specific problems in exception logs.
In fact, printing logs only requires capturing and printing at the outermost layer of the code. Exception printing can also be written as AOP and woven into the outermost layer of the frame.
Misconception 9: The problem that the information contained in exceptions cannot be fully located
Exceptions not only allow developers to know what is wrong, but more often developers also need to know what causes the problem. We know that java .lang.Exception has a constructor of string type parameters, and this string can be customized into easy-to-understand prompt information.
Simple custom information developers can only know where the exception appears, but in many cases, developers need to know more about what parameters are causing such an exception. At this time, we need to append the parameter information of the method call to the custom information. The following example only lists the case of one parameter. In the case of multiple parameters, you can write a tool class to organize such a string.
Listing 11
public <strong>void</strong> retrieveObjectById(Long id){ <strong>try</strong>{ //..some code that throws SQLException }<strong>catch</strong>(SQLException ex){ //Add parameter information to exception information <strong>throw</strong> <strong>new</strong> RuntimeException("Exception <strong>in</strong> retrieveObjectById <strong>with</strong> Object Id :"+ id, ex); }}Misconception 10: Cannot predict potential abnormalities
During the process of writing code, due to lack of deep understanding of the calling code, it is impossible to accurately determine whether the called code will produce exceptions, so processing is ignored. After a Production bug was generated, I remembered that I should add exception retrieval to a certain piece of code, and I could not even accurately point out the cause of the exception. This requires developers not only know what they are doing, but also to know as much as possible what others have done and what possible results may be caused, and to consider the processing process of the entire application from a global perspective. These ideas will affect our writing and processing of code.
Misconception 11: Mixed use of multiple third-party log libraries
Nowadays, there are more and more Java third-party log libraries. Various frameworks will be introduced in a large project, and these frameworks will rely on the implementation of different log libraries. The most troublesome problem is not to introduce all the log libraries needed, the problem is that the log libraries introduced are incompatible themselves. If it may be easy to solve in the early stage of the project, you can reintroduce all the log libraries in your code as needed, or change to a framework. But this kind of cost is not affordable for every project, and the greater the risk as the project progresses.
How can we effectively avoid similar problems? Most frameworks now have taken into account similar problems. They can configure Properties or xml files, parameters, or runtime scanning log implementation classes in the Lib library, and only when the application is running can we determine which specific log library to apply.
In fact, based on the principle of not requiring multiple levels of log printing, we can simplify many classes that originally called log printing code. In many cases, we can use interceptors or filters to print logs to reduce the cost of code maintenance and migration.
Conclusion
The above is purely personal experience and summary. Things are dialectical, and there are no absolute principles. The most effective principle is suitable for you. I hope the above explanation and analysis can be helpful to you.