Proactive Java (Exception Handling) and proactive Exception Handling
. Use exceptions only for exceptions:
I don't know if you have met the following code:
Copy codeThe Code is as follows:
Try {
Int I = 0; 3
While (true)
Range [I ++]. climb ();
}
Catch (ArrayIndexOutOfBoundsException e ){
}
The intention of this Code is not obvious. The intention is to traverse every element in the range of the variable array and execute the climb method of the element. When the subscript exceeds the length of the range array, the ArrayIndexOutOfBoundsException exception will be thrown directly, and the catch code block will catch the exception, but will not handle it any more, just treat the error as part of a normal workflow. This is indeed an incredible way of writing. Let's take a look at the modified method:
Copy codeThe Code is as follows:
For (Mountain m: range ){
M. climb ();
}
It is self-evident that the write method is more readable than the previous one. So why does someone use the first method? Obviously, they are misled and they attempt to avoid JVM cross-border checks for each array access in the for-each loop. This is undoubtedly redundant or even counterproductive, because placing the code in a try-catch Block blocks some specific JVM optimizations. As for the array boundary check, now many JVM implementations have optimized them. In the actual test, we will find that the running efficiency is much slower than that of the normal method when exceptions are adopted.
In addition to the efficiency and code readability issues just mentioned, the first method also masks some potential bugs. Assuming that the climb method of the array element also accesses an array, in addition, an array out-of-bounds problem occurs during access. Based on this error, JVM will throw the ArrayIndexOutOfBoundsException exception. Unfortunately, this exception will be caught by catch statements outside the climb function, after no processing is done, it will continue to be executed according to the normal process, so that the Bug will be hidden.
The lesson in this example is simple: "exceptions should be used only in case of exceptions, and they should never be used in normal control flow ". Although sometimes someone will say that this strange writing method can improve the performance, even so, with the continuous improvement of the platform implementation, the performance advantages of this exception mode cannot be maintained. However, the subtle bugs and maintenance pains brought about by this clever model still exist.
According to this principle, we will be inspired when designing APIs. A well-designed API should not ** use exceptions on its client for normal control flow. For example, Iterator and JDK fully consider this point during design. Before executing the next method, the client needs to call the hasNext method to check whether there are readable set elements. See the following code:
Copy codeThe Code is as follows:
For (Iterator I = collection. iterator (); I. hasNext ();){
Foo f = I. next ();
}
If the hasNext method is missing from Iterator, change ** to the following Syntax:
Copy codeThe Code is as follows:
Try {
Iterator I = collection. iterator ();
While (true)
Foo f = I. next ();
}
Catch (NoSuchElementException e ){
}
This should be very similar to the example of traversing the array given at the beginning of this entry. In actual design, there is another way to verify the identifiable error return value. However, this method is not suitable for this example, because for next, return null may be valid. So what are the differences between the two design methods in actual application?
1. if synchronous concurrent access is missing or the status can be changed by the outside world, it is necessary to use the method that can identify the returned value, because in the testing status (hasNext) and the corresponding call (next) there is a time window between them. In this window, the object may change its status. Therefore, in this case, we should select a way to return identifiable error return values.
2. if the state test method (hasNext) and the corresponding call method (next) use the same code, for performance reasons, there is no need to repeat the same work twice, in this case, you should select a way to return identifiable error return values.
3. In other cases, we should try our best to consider the "State test" design method, because it can bring better readability.
Fifty-eight, the use of recoverable exceptions, the use of programming errors runtime exceptions:
Java provides three types of throws: checked exceptions, runtime exceptions, and errors. This entry provides general principles for these three types of applicable scenarios.
1. If the caller is expected to be able to recover properly, check exceptions should be used in this case. If someone wants to shop online and the result balance is insufficient, a custom check exception can be thrown. By throwing a checked exception, the ** caller can handle the exception in the catch clause, or continue to spread it upwards. Therefore, declaring an exception in a method is a potential reminder for API users.
2. a programming error is indicated by a running exception. Most runtime exceptions indicate "prerequisite violation", that is, the API user does not comply with the usage Conventions set up by the API designer. For example, the array access is out of bounds.
3. errors are usually reserved by JVM to indicate insufficient resources, constraints failed, or other conditions that make the program unable to continue execution.
This entry also provides a very useful technique for customizing checked exceptions. When the caller detects this exception, the caller can call the interface method provided by this custom exception, obtain more specific error information, such as the current balance.
Avoid unnecessary exceptions during use:
The exception detected is a good feature provided by Java. Unlike return values, they ** must be used by programmers to handle exceptional conditions, greatly enhancing program reliability. However, excessive use of checked exceptions makes the API very inconvenient to use. After all, we still need to use some additional code to handle these thrown exceptions, if the five APIs it calls throw an exception, writing such function code is frustrating.
If the correct use of the API cannot prevent such exception conditions, and once an exception occurs, API programmers can immediately adopt useful actions. This burden is considered justified. Unless both conditions are true, the unchecked exception is more suitable. See the following test:
Copy codeThe Code is as follows:
Try {
Dosomething ();
} Catch (TheCheckedException e ){
Throw new AssertionError ();
}
Try {
Donsomething ();
} Catch (TheCheckedException e ){
E. printStackTrace ();
System. exit (1 );
}
When we use checked exceptions, if the Exception Handling Method in the catch clause is just like the above two examples, or is not as good as those, we recommend that you consider using unchecked exceptions. The reason is very simple. In the catch clause, they do not take any action to recover exceptions.
60. Standard of priority usage exception:
Using standard exceptions not only makes it easier to reuse existing code, but also makes it easier to learn and use the APIS you have designed, because they are more consistent with the conventional usage that programmers are already familiar. Another advantage is that the Code is more readable and no more unfamiliar code appears when programmers read it. This entry provides some common exceptions that are easy to reuse. See the following table:
Abnormal application scenarios
The value of the non-null IllegalArgumentException parameter is incorrect.
IllegalStateException is not suitable for method calls.
The NullPointerException parameter value is null if null is not allowed.
The value of IndexOutOfBoundsException subscript exceeds the threshold.
ConcurrentModificationException: concurrent modification of the object is detected when concurrent modification is disabled.
The UnsupportedOperationException object does not support user request methods.
Of course, there are many other exceptions in Java, such as ArithmeticException and NumberFormatException. These exceptions have their own application scenarios. However, it should be noted that, in some cases, the boundaries of these exception applications are not very clear. As to which one is the most appropriate choice, more needs to be determined by the context environment.
It should be emphasized that the conditions for throwing an exception must be consistent with those described in this exception document.
. Throwing an exception corresponding to the Abstraction:
If the exception thrown by a method has no obvious relationship with the task it executes, this situation will make people feel overwhelmed. Especially when an exception is thrown from the underlying layer, if no processing is performed on the intermediate layer, the underlying implementation details will directly pollute the high-level API interface. To solve this problem, we usually do the following:
Copy codeThe Code is as follows:
Try {
DoLowerLeverThings ();
} Catch (LowerLevelException e ){
Throw new HigherLevelException (...);
}
This processing method is called exception translation. In fact, Java also provides a more convenient form of translation-exception chain. Imagine the sample code above. In the debugging phase, if the high-level application logic can be informed of the cause of the underlying exception, it will be very helpful to find the root cause of the problem, see the following code:
Copy codeThe Code is as follows:
Try {doLowerLevelThings ();
} Catch (LowerLevelException cause ){
Throw new HigherLevelException (cause );
}
Underlying exceptions are passed as parameters to high-level exceptions. Most standard exceptions support the constructor of exception chains. If not, you can use the Throwable initCause method to set the cause. The exception chain not only allows you to access the cause through the interface function getCause, but also integrates the cause stack track into higher-level exceptions.
Through this exception chain method, the underlying implementation details can be thoroughly separated from the high-level application logic.
Sixty-three, including information that can capture failures:
When the program fails due to an uncaptured exception, the system automatically prints the stack track of the exception. The stack track contains the string representation of the exception, that is, the returned result of the toString method. If we provide detailed error information for this exception at this time, it is extremely meaningful for error locating and tracing. For example, after formatting the input parameters of the function that throws an exception and the domain Field Values of the class where the function is located, package the information and pass it to the exception object to be thrown. Assume that our high-level application captures the IndexOutOfBoundsException exception. If the exception object can carry the lower and upper bounds of the array, as well as the lower values of the current out-of-bounds labels and other information, after seeing this information, we can quickly make correct judgments and revise the Bug.
Especially for checked exceptions, if the thrown exception type still provides some additional interface methods for obtaining the data or information that causes the error, this is very important for calling functions that capture exceptions to recover errors.
14th. Strive to keep failure atomic:
This is a very important suggestion, because in actual development, when you are an interface developer, you will often ignore it. If you think it is not guaranteed, it is estimated that there is no problem. On the contrary, if you are the user of the interface, you will also ignore it and think that this is what the interface Implementer should do.
When an object throws an exception, we usually expect this object to remain in a well-defined available State, even if the failure occurs during the execution of an operation. This is especially important for checked exceptions because the caller wants to recover from such exceptions. In general, failed method calls should keep the object in the State before being called. Methods With this attribute are called with "failed atomicity ".
There are several ways to maintain this atomicity.
1. The simplest method is to design immutable objects. Failed operations will only cause the creation of new objects to fail, without affecting existing objects.
2. For a variable object, the general method is to verify the validity of the parameter before the object is operated. This can cause the object to throw a more meaningful exception before it is modified, for example:
Copy codeThe Code is as follows:
Public Object pop (){
If (size = 0)
Throw new EmptyStackException ();
Object result = elements [-- size];
Elements [size] = null;
Return result;
}
If the size is not verified before the operation, the array of elements will also throw an exception, but because the size value has changed, when you continue to use this object, it will never be able to return to the normal state.
3. write the restoration code in advance and execute the Code with segments when an error occurs. This method brings a lot of maintenance overhead during code writing and code maintenance, in addition, the efficiency is relatively low, so this method is rarely used.
4. Create a temporary copy for the object. If an exception occurs during the operation, use the copy object to reinitialize the status of the current object.
Although it is generally expected to implement the atomicity of failure, it is difficult to do so in some cases. For example, if two threads modify a variable object at the same time, once a ConcurrentModificationException is thrown, it is difficult to restore to the original state.
Do not ignore exceptions:
This is an obvious common sense, but it is often violated. Therefore, this entry is re-proposed, such:
Copy codeThe Code is as follows:
Try {
Dosomething ();
} Catch (SomeException e ){
}
The foreseeable and possible ignore exception is that when FileInputStream is disabled, the data has been read. Even so, if a prompt message is output when the exception is caught, this is also very helpful for some potential problems. Otherwise, some potential problems will remain hidden until a sudden outbreak at a certain time, resulting in irreparable consequences.
The suggestions in this entry also apply to checked and unchecked exceptions.