Hello, everyone, I am Daming brother, a man who focuses on the creation of “Dead Knock Java” series

Personal website: www.cmsblogs.com/. Focus on Java quality series of articles to share, provide one-stop Java learning materials


preface

I bet for a lot of people, they think that in Java an exception is a try… Catch, throw new Exception if you’re a little conscious, is it really that simple? Forgive me for telling you that you don’t know much about Java exceptions. The following is xiaobian understanding of Java exceptions, if there are mistakes, please forgive me, limited qualifications.

Lead to abnormal

First, in Java, do you prefer to use return codes or exceptions to regulate errors?

Let me start by talking about cases where return codes are used, such as logon logic. We have the following cases:

  • 0: The login succeeds
  • 1: The user name is incorrect and there is no user
  • 2: Indicates that the user password is incorrect
  • 3: Reset the password upon the first login
  • 4: If the password is incorrect for five consecutive times, the account will be frozen

The return code is usually treated as follows:

        int code = userService.login(userName,password);
        if (code == 0) {
            return "Login successful";
        }else if (code == 1) {
            return "Incorrect user name, please re-enter";
        }else if (code == 2) {
            return "Incorrect password, please re-enter";
        } else if (code == 3) {
            return "This is your first login, please reset your password.";
        } else if (code == 4) {
            return "Your password has been incorrectly entered for 5 consecutive times, frozen for a day.";
        }
Copy the code

Of course, you might have a more elegant way of doing this, such as returning code directly to the front end and having the front end display different exceptions based on the return code. Such as:

return userService.login(userName,password);
Copy the code

This back-end approach is elegant, but what about the front end? If you add 5 or 6 more return codes on the back end, do you think your colleagues on the front end won’t kill you?

So what’s the solution?

  1. For example, 0000 indicates a successful login. 0001 indicates the first login. -0001 indicates a login failure. The message description then returns the message.
  2. Unified back-end exception handling.

Some people may say, this is the interaction of the back end, how about the pure back end mode? Again, exceptions should be used.

If you use return codes to regulate interfaces such as:

  • Zero: success
  • 1: indicates abnormal 1
  • 2: exception 2
  • 3: exception 3

So when someone calls a written method, it is necessary to distinguish these cases, such as code = 0, what to do with code = 1,…. “And he did that at first, and he did it very well, and your program worked very well, but one day, you add code = 5, and you happen to forget to tell him, and you’ll get a lot of criticism when you go live.

In fact, this kind of single return value is easy to deal with, if it is multiple return value, how do you deal with? What if the result is a List? Do you have to construct a Map or an object? Don’t say you return null or I’ll knock you over the head. So there are some problems with this approach to return codes:

  1. The programmer accidentally forgot to check the return value, causing a BUG
  2. Method interfaces become very impure, with normal and error values mixed up, leading to semantic problems

Daming brother, in the real project inside saw the logic of such treatment, at that time I saw, chrysanthemum instant tight, surprised for heaven and man. The following

public class ServiceA {
    
    public String method1(a) {
        doSomething1();
        
        doSomething2();
        
        if (a) {
            return "false@xxxx";
        } else if (b) {
            return "false@zzzzz";
        } else if (c) {
            return "false@ccccc";
        } else {
            return "true@vvvvv"; }}}Copy the code

Caller:

public class ServiceB {
    
    public void method2(a) {
        String result = method1();
        String[] results = result.split("@");
        if ("false".equals(results[0]) {if (results[1].contains("xxxx")) {
                doSomething1();
            }
            if (results[1].contains("zzzzz")) {
                doSomething2();
            }
            if (results[1].contains("ccccc")) { doSomething3(); }}}}Copy the code

I said are you scared?

In my opinion, it is really a thankless task to judge whether the program is running correctly by the return code. Java provides a try… Doesn’t he smell good?

Some of you might ask, if you write a method that throws an exception and you don’t tell your colleagues, does that cause an exception in your code? I threw. If this Exception requires extra handling by your colleague, wouldn’t you throw a checked Exception?

May have a friend said, throw an exception can reduce application performance, this really, but there are considered a question: no, we are normal operation of the system in most cases, sometimes throw an exception, if you write the program day and night are throwing an exception, you need to think about, what is your problem or process problem? At the same time, isn’t it worth losing that little bit of performance for highly readable, maintainable, elegant code?

Best practices

Since Exception has many advantages, how should we use it?

In my opinion, errors in programs fall into three broad categories:

  • System error. This type of error is the program running environment problem, normally we can’t avoid, for this type of error, some we can processing, such as request network anomaly, this we can try again a few times, and some is what we can’t handle, such as memory exhaustion OOM, stack overflow, and so on, this we have to stop running, even out of the whole program.
  • Program error. This type of error is usually a bug in our program, such as null pointer, file not created, logic calculation error, for this kind of error, we must record, and better trigger the monitoring system alarm.
  • User error. Illegal parameters such as user input, repeated requests, generally belong to the user application layer such mistakes, for this type of error, we only need to prompt the user, there is no need for logging, but we can do some necessary statistics, such as a user input frequently illegal parameters, constant requests for errors, we can wait for these users into the blacklist, This will help us improve our system and detect malicious user requests.

For these three types of error, we need to distinguish, different exception classification has different levels of processing.

  • System errors: expect exceptions whenever possible, handle them where they can, and throw them out if they can’t
  • Program errors: We need to eliminate as much as possible, record every program handling exception, and make necessary alarms.
  • User error: Parameter verification must, carefully, bring the wrong parameter into the system. We can’t avoid it, we can only properly count and detect it.

At the same time, I don’t recommend defining too many custom exceptions in your system. Some people are obsessed with custom exceptions. Such as UserNotFoundException, UserPasswordErrorException and so on. At present, the only exceptions in the system I am responsible for are the following:

  1. BusinessException: Business exception, inherited from RuntimeException
  2. NotFountException: Business exception, inherited from RuntimeException
  3. ParamValidateException: Business exception, inherited from RuntimeException
  4. SystemException: System Exception, inherit Exception

1, 2, 3 do not need to display processing, 4 must be processed. Then with two enumerations:

  1. BusinessErrorCodeEnum
  2. SystemErrorCodeEnum

In BusinessException, the constructor has an isPrintLog parameter, which is used to determine whether the Error log needs to be printed. For exceptions that do not need our attention, we can pass false. The monitoring system will send all the exceptions in our system to everyone in our team by email every day. I will pay attention to the Error log in the system the previous day every day and remove some exceptions that I don’t care about according to the actual situation. In addition, the monitoring system will inform the relevant developers of the system’s errors and exceptions in real time through emails and enterprise wechat, so that we can find the system’s errors in time, respond in time, and narrow the scope of influence.

Here’s a good summary of his exceptional best practices:

  • Uniform classification of error dictionary. Whether you’re using error codes or exception catching, you need to be careful and consistent in classifying errors. It is best to define the related errors in one place. For example, HTTP 4XX indicates that there is a problem with the client and 5XX indicates that there is a problem with the server. In other words, you have to build a dictionary of errors.
  • The definition of a class error is ideally extensible. This is very important, and it can be done very well through object-oriented inheritance or interface polymorphism like the Go language. This makes it easy to reuse existing code.
  • Define the severity of the error. For example, Fatal indicates a critical Error, Error indicates that resources or needs are not met, Warning indicates that it is not necessarily an Error but still needs to be paid attention to, Info indicates that it is not an Error but just a message, and Debug indicates that it is used by internal developers to Debug programs.
  • It is better to output error logs using error codes rather than error messages. When printing error logs, use a uniform format. It’s best not to use an error message, but to use an error code. The error code may not be a number, but it may be a uniquely readable keyword that can be found in an error dictionary. This makes it much easier for log analysis software to automate monitoring rather than do semantic analysis from error messages. For example, HTTP logs contain the HTTP return code, such as 404. But I prefer to use a logo like PageNotFound, which is easy for both people and machines to handle.
  • Ignore errors and it’s better to log. Otherwise it will bring a lot of trouble to the maintenance.
  • For the same place repeatedly reported error, it is best not to call the log. Otherwise, other logs will be overwhelmed and the log file will be too large. Best practice is to type an error and the number of times it occurs. Do not use error handling logic to process business logic. In other words, do not handle business logic in such a way as exception catching. Instead, use conditional judgment. If a logical control can be clearly expressed with an if-else, exceptions are not recommended. Exception catches are used to handle things that are not expected to happen, and error codes are used to handle things that might happen.
  • The same pattern is used for the same type of error handling. For example, all errors on a null object can be either null plus a condition check or NullPointerException thrown. Do not mix them, as this will help the code specification.
  • Whenever possible, handle errors where they occur. Because it makes it easier for the caller. Go up as far as possible to return the original error. If the error must be returned to a higher level, it should be returned to the original error, rather than reinventing an error.
  • Always clean up allocated resources when handling errors. This is key and can be easily done using RAII technology, or try-catch-finally, or Go’s defer.
  • Handling errors in the body of a loop is not recommended. We’re talking about try-catch, most of the time you don’t need to do that.
  • It is better to put the entire loop inside the try block and do the catch outside. Don’t put a lot of code in a try block. Statements within a try block should do a simple single thing.
  • Provide clear documentation of your error definitions and code examples for each error. If you’re doing RESTful apis, using Swagger will make it easy.
  • For the asynchronous approach, the Promise pattern is recommended for handling errors. There are good practices in JavaScript for this.
  • For distributed systems, apM-related software is recommended. In particular, use analysis of service call tracing such as Zipkin to associate errors.

To conclude today’s article with one sentence: Exceptions should be where they should be.