preamble
In our daily work, we often encounter some exceptions, such as: NullPointerException, NumberFormatException, ClassCastException and so on.
So the question is, how do we handle exceptions to make the code more elegant?
1 Don't ignore exceptions
I don't know if you have come across the following code:
Counterexample:
Long id = null;
try {
id = (keyword);
} catch(NumberFormatException e) {
//Ignore exceptions
}
User-entered parameters that are converted to Long types using the method, if an exception occurs, the exception is simply ignored using try/catch.
And no logs were printed either.
It's a little less easy to troubleshoot the problem if there's an issue with the online code behind it.
It is recommended that you do not ignore exceptions, which can cause a lot of trouble in the follow-up.
Positive Example:
Long id = null;
try {
id = (keyword); } catch(NumberFormatException e) {
} catch(NumberFormatException e) {
(("Conversion of keyword: {} to Long type failed, reason: {}",keyword , e))
}
If there is a problem with the data conversion later, we can find out the exact cause at a glance from the logs.
2 Using a Global Exception Handler
Some of you guys, often like to catch exceptions in Service code.
Whether it's a normal exception Exception, or a runtime exception RuntimeException, use try/catch to catch them.
Counterexample:
try {
checkParam(param);
} catch (BusinessException e) {
return (1,"parameter error");
}
Exceptions are caught in every Controller class.
This code above is found in UserController, MenuController, RoleController, JobController and so on.
Obviously this approach creates a lot of duplicate code.
We catch as few exceptions as possible in Controller, Service and other business code.
This kind of business exception handling should be left to the interceptor for unified handling.
In SpringBoot you can use the @RestControllerAdvice annotation to define a global exception handling handler and then use the @ExceptionHandler annotation to handle the exception on the method.
Example:
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* Harmonize exception handling
*
* @param e exception
* @return API request response entity
*/
@ExceptionHandler()
public ApiResult handleException(Exception e) {
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
("A business exception was thrown for the request:", e);
return ((), ());
}
("A system exception was thrown for the request: ", e); return (HttpStatus.
return (HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal server error, please contact the system administrator!") ;
}
}
With this global exception handler, the previous try/catch code we had in the Controller or Service can be removed.
If an exception occurs in the interface, the global exception handler will encapsulate the result for us and return it to the user.
3 Catch specific exceptions where possible
It is possible that you will need to go through multiple different exceptions in your business logic methods.
You may you find it more cumbersome to just catch the Exception instead.
Counterexample:
try {
doSomething(); } catch(Exception e) {
} catch(Exception e) {
("doSomething processing failed due to:",e); }
}
That's too general a way to catch exceptions.
Actually the doSomething method throws FileNotFoundException and IOException.
In this case we'd better catch the specific exception and do the processing separately.
Positive Example:
try {
doSomething(); } catch(FileNotFoundException e) {
} catch(FileNotFoundException e) {
("doSomething processing failed, file not found, reason:", e); } catch(IOException e) {
} catch(IOException e) {
("doSomething processing failed, an IO exception occurred, cause:",e); } catch(IOException e) {("doSomething processing failed, an IO exception occurred, cause:",e); }
}
This makes it very easy for us to know what the cause is if the above exception occurs later.
4 Close the IO stream in finally
When we use IO streams, we generally need to close them in a timely manner when we run out of them, otherwise they will waste system resources.
We need to handle IO streams in try/catch as IO exceptions may occur.
Counterexample:
try {
File file = new File("/tmp/");
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) ()];
(data);
for (byte b : data) {
(b);
}
();
} catch (IOException e) {
("Failed to read file,rationale:",e)
}
The above code closes fis directly in the try block.
If an IO exception occurs when the method is called, the exception may be thrown directly into the catch block, and there is no way for the method to execute at that point, which means that the IO stream cannot be closed correctly in this case.
Positive Example:
FileInputStream fis = null;
try {
File file = new File("/tmp/");
fis = new FileInputStream(file);
byte[] data = new byte[(int) ()];
(data);
for (byte b : data) {
(b);
}
} catch (IOException e) {
("Failed to read file,rationale:",e)
} finally {
if(fis != null) {
try {
();
fis = null;
} catch (IOException e) {
("Close after reading the fileIOabortive,rationale:",e)
}
}
}
Close the IO stream in the finally block.
However, you must first determine that fis not null, otherwise a NullPointerException may occur when executing the () method.
One thing to keep in mind is that when calling the () method, exceptions may also be thrown, and we also need to do try/catch handling.
5 More use of try-catch-resource
It still feels a bit cumbersome to turn off the IO stream in the finally block earlier.
So after JDK7, a new syntactic sugar try-with-resource appeared.
The code above can be remodeled to look like this:
File file = new File("/tmp/");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = new byte[(int) ()];
(data);
for (byte b : data) {
(b);
}
} catch (IOException e) {
();
("Failed to read file,rationale:",e)
}
The FileInputStream inside the try brackets implements aAutoCloseable
interface, so whether this code finishes executing normally, an exception is thrown, or an exception is thrown to the outside, or an internal block of code is intercepted, the IO stream will eventually be closed automatically.
We try to close the IO stream as much as possible with the syntax of try-catch-resource, and can write less code in finally.
And closing the IO streams in the finally block has ordering issues, if there are multiple IOs, closing them in the wrong order may cause some of them to fail.
And try-catch-resource doesn't have this problem.
6 Do not return in finally
We may have return data in a method.
Counterexample:
public int divide(int dividend, int divisor) {
try {
return dividend / divisor;
} catch (ArithmeticException e) {
// Exception handling
} finally {
return -1;
}
}
In this example above, we returned the data -1 in the finally block.
This ends up overwriting the divide / divisor value with -1 when the divide method returns, causing the normal result to be incorrect as well.
We try not to return data in the finally block.
Positive solution:
public int divide(int dividend, int divisor) {
try {
return dividend / divisor;
} catch (ArithmeticException e) {
// Exception handling
return -1;
}
}
If an exception occurs in divend / divisor, return -1 in the catch block.
7 Less use ()
In our native development, we like to use the () method to output the stack trace information of an exception to the standard error stream.
Counterexample:
try {
doSomething();
} catch(IOException e) {
();
}
This approach does make it easy to locate problems locally.
However, if the code is deployed to a production environment, it may pose the following problem:
- May expose sensitive information such as file paths, usernames, passwords, etc.
- It may affect the performance and stability of the program.
Positive solution:
try {
doSomething(); } catch(IOException e) {
} catch(IOException e) {
("doSomething processing failed due to:",e); }
}
We want to record exception information in the log, not keep it for the user.
8 Exception printing in more detail
After we catch an exception, we need to log information about the exception to the log.
Counterexample:
try {
double b = 1/0; } catch(ArithmeticException e) {
} catch(ArithmeticException e) {
("Processing failed, reason:",());
}
This example uses the () method to return exception information.
But the implementation results are:
The doSomething processing failed for a reason:
This situation anomaly information is not printed at all.
We should print both the exception message and the stack.
Positive Example:
try {
double b = 1/0; } catch(ArithmeticException e) {
} catch(ArithmeticException e) {
("Processing failed due to:",e); }
}
Implementation results:
The doSomething processing failed for a reason:
: / by zero
at (:16)
Print out the specific exception, the code that had the problem and the specific line number.
9 Don't Catch Exceptions and Throw Them Right Away
Sometimes, we may catch and then throw exceptions for logging purposes.
Counterexample:
try {
doSomething();
} catch(ArithmeticException e) {
("doSomethingprocessing failure,rationale:",e)
throw e;
}
If an ArithmeticException exception occurs when the doSomething method is called, it is caught using catch, logged to the log, and then the exception is thrown using the throw key.
This tawdry operation is purely for logging purposes.
But eventually the logs were found to be recorded twice.
This is because this ArithmeticException exception may be logged again in subsequent processing.
This will cause the log to be duplicated.
10 Prioritize the use of standard exceptions
Many of the more commonly used standard exceptions have been defined in Java, such as those listed in the following diagram:
Counterexample:
public void checkValue(int value) {
if (value < 0) {
throw new MyIllegalArgumentException("The value cannot be negative");
}
}
Customizes an exception to indicate a parameter error.
In fact, we can just reuse the existing standard exceptions.
Positive Example:
public void checkValue(int value) {
if (value < 0) {
throw new IllegalArgumentException("The value cannot be negative");
}
}
11 Documentation of exceptions
One of the good habits we have in writing code is to add documentation to methods, parameters and return values, to add documentation.
Counterexample:
/*
* Processing of user data
* @param value User input parameters
* @return (be) worth
*/
public int doSomething(String value)
throws BusinessException {
//business logic
return 1;
}
This doSomething method, adds documentation to the method, parameters, and return value, but not the exception.
Positive solution:
/*
* Processing of user data
* @param value User input parameters
* @return (be) worth
* @throws BusinessException business exception
*/
public int doSomething(String value)
throws BusinessException {
//business logic
return 1;
}
Exceptions are thrown and documentation needs to be added as well.
12 Don't use exceptions to control the flow of the program
We sometimes, use exceptions in our programs to control the flow of the program, which is not really the right thing to do.
Counterexample:
Long id = null;
try {
id = (idStr);
} catch(NumberFormatException e) {
id = 1001;
}
If the idStr entered by the user is of type Long, it is converted to Long and assigned to id, otherwise id is given the default value 1001.
Every time you need to try/catch or more impact on the performance of the system.
Positive Example:
Long id = checkValueType(idStr) ? (idStr) : 1001;
We add a checkValueType method that determines the value of idStr and converts it directly to Long if it is of type Long, otherwise it gives the default value of 1001.
13 Customized exceptions
If the standard exception does not meet our business needs, we can customize the exception.
Example:
/**
* business exception
*
* @author Su San
* @date 2024/1/9 afternoon1:12
*/
@AllArgsConstructor
@Data
public class BusinessException extends RuntimeException {
public static final long serialVersionUID = -6735897190745766939L;
/**
* exception code
*/
private int code;
/**
* Specific Exception Information
*/
private String message;
public BusinessException() {
super();
}
public BusinessException(String message) {
= HttpStatus.INTERNAL_SERVER_ERROR.value();
= message;
}
}
For such customized business exceptions, we can add the fields code and message, where code represents the exception code and message represents the specific exception information.
BusinessException inherits the RuntimeException runtime exception, which is handled more flexibly later.
A variety of construction methods are provided.
Defines a serialization ID (serialVersionUID).
One final note (ask for attention, don't patronize me)
If this article is helpful to you, or if you are inspired, help scan the QR code below to pay attention to it, your support is my biggest motivation to keep writing.
Ask for a one-click trifecta: like, retweet, and watch at.
Concerned about the public number: [Su San said technology], in the public number reply: into the big factory, you can get free access to my recent organization of 100,000 words of the interview dictionary, a lot of partners rely on this dictionary to get a number of big factory offers.