preface

During the years of my practice, I have participated in the design of many systems. From the point of view of meeting business needs, the system that can support business development quickly can be called “good”. After all, it’s the business that creates value, and without that business drive, it would be pretty frustrating for engineers to master dragon slaying skills. As an excellent programmer, I believe that everyone wants to develop their own system easy to maintain, more robust. This is idealism, of course. Not to mention the rapid changes in the Internet industry, the tight schedule alone requires many engineers to give up the obsession with code maintenance. In my personal experience, it is not uncommon to get the requirements document in the morning and go online in the evening. However, I’d like to take a personal look at how simple, effortless habits can make application maintenance easier. This article is from abnormal use, talk about the bad taste of improper use.

Detected/undetected abnormality

There are runtimeExceptions and exceptions in Java. Typically nullPointerExceptions are RuntimeExceptions that do not need to be caught by the developer and thrown automatically once triggered at runtime. Runtimeexceptions are also called unchecked exceptions by name and are not checked at compile time. All exceptions except RuntimeExceptions are checked and must be handled, thrown, or caught at compile time. Common exceptions include ClassNotFoundException and InterruptedException.

If you feel something unusual here, declare it

I’ve seen a lot of code like this:

PayOrder selectByOrderId(Long orderId) throw Exception;
Copy the code

Suspicious of the network connection/database, I always feel like I’m writing SQL that will throw an exception and want it handled upstream. It’s called defense-oriented programming, but it actually adds a lot of trouble to program maintenance. First, because the exception is thrown so generically, the caller does not know what the person who threw it was thinking at the time and can only force the exception to be caught in the outer layer. If the upper-level caller doesn’t want to handle the exception either, he will continue to throw it to the upper level, and after doing so a few times, the outermost block will have no idea under what circumstances the exception was thrown. Maybe many friends will say, my programming style is that others have exceptions to deal with their own can deal with, not to deal with the upper level. Well, let me just say, you’re great. However, there isn’t just one developer on a project, and everyone has their own programming habits. Some small partners like to throw exceptions to the outer layer, continue to embarrass the upper call. Of course, this is not to say that throwing exceptions is bad. Reasonable use of exceptions can make the structure of the program clearer, more explicit semantics. It also makes it easier for higher-level callers to handle different exceptions instead of having to catch exceptions.

Capture it with or without it

Then, if someone throws a checked exception all the way from the very bottom. So in a system like this, what would you do? It’s easy. Just catch it. But what if the lower layer throws an unchecked exception, a RuntimeException? Unfortunately, once someone on the team does this, in the absence of global exception handling, an experienced developer will choose to catch the exception in the outer layer, while a less experienced or unfamiliar developer will naturally not catch the exception. What does it mean that this error will be thrown into the container? If you return a JSON file, if you throw it into the container it will return an error message. Other callers cannot parse the return value, which is definitely not the case.

From now on, you’ll probably see all outer programs with ugly try catch blocks, whether or not the calling program throws an exception.

Some practical

Exceptions and enumerations

In the Spring container, declarative transactions are commonly used to manage database transactions. During the use of @Transaction, the corresponding exception type must be specified. I have encountered many projects where the exception that is rolled back is a RuntimeException. Exception does not roll back. This is a configuration error. Now that we’ve examined the disadvantages of using RuntimeException, let’s talk about Exception. This kind of coercion requires the capture of exceptions and custom exceptions to have strong semantics, which is convenient for high management to choose and deal with flexibly. It is widely used in engineering. But if you define a new class for each exception, this is verbose. A common practice is to make business judgments by enumerating values with exceptions. As follows:

public enum ResultCodeEnum {
    / * ** successful* /
    SUCCESS("SUCCESS"."ok"),
 / * ** Operation failed.* /  FAIL("FAIL"."Operation failed"),  / * ** System error* /  ERROR("ERROR"."System busy, please try again later."),  / * ** Failed to check the visa* /  VERIFY_FAILED("VERIFY_FAILED"."Failed in visa inspection"),  / * ** Missing arguments* /  LACK_PARAM("LACK_PARAM"."Missing parameters"),   ;    @Getter  private String code;  @Getter  private String msg;  private ResultCodeEnum(String code, String msg) {  this.code = code;  this.msg = msg;  } Copy the code

With enumeration parameter in exception:

public class BusinessException extends Exception {

    private static final long serialVersionUID = -121219158129626814L;
    @Getter
    private ResultCodeEnum resultCode;
 @Getter  private String msg;  public BusinessException(a) {  }  public BusinessException(ResultCodeEnum rsCode) {  super(rsCode.getCode() + ":" + rsCode.getMsg());  this.resultCode = rsCode;  this.msg = rsCode.getMsg();  }  public BusinessException(ResultCodeEnum rsCode, String message) {  super(rsCode.getCode() + ":" + message);  this.resultCode = rsCode;  this.msg = message;  }  public BusinessException(ResultCodeEnum rsCode, Throwable cause) {  super(rsCode.getCode() + ":" + rsCode.getMsg(), cause);  this.resultCode = rsCode;  this.msg = rsCode.getMsg();  }  public BusinessException(ResultCodeEnum rsCode, String message, Throwable cause) {  super(rsCode.getCode() + ":" + message, cause);  this.resultCode = rsCode;  this.msg = message;  } } Copy the code

Thus, use it where you need to throw an exception.

PayTypeEnum payTypeEnum = PayTypeEnum.toEumByName(payRequestDTO.getPayType());
if (payTypeEnum == null) {
    throw new BusinessException(ResultCodeEnum.INVALID_PAY_TYPE);
}
Copy the code

In this way, the outer layer must catch the exception, which can be processed according to the ResultCodeEnum value.

Uniform return value

Generally speaking, a service provides the same recommendations to the outside world. You can use the payload pattern to wrap the results you return. In case you don’t understand, take a look at this class:

public class ResultMessageVO<T> {

    public static final String SUCCESS = "success";

    public static final String ERROR = "error";
 private String status; / / state   private String message; / / message   private T data; // The data returned  .} Copy the code

This makes it easy to normalize returns if a section or other global exception handling mechanism is used. Take the section of a validation parameter as an example:

@Aspect
@Component
@Slf4j
public class ValidationAspect {
    @Around("execution(* io.github.pleuvoir.gateway.. *. * (..) )")
 public Object around(ProceedingJoinPoint point) throws Throwable {  MethodSignature methodSignature = (MethodSignature) point.getSignature();  Method method = methodSignature.getMethod();  Object[] args = point.getArgs();  Parameter[] parameters = method.getParameters();  for (int i = 0; i < parameters.length; i++) {  if (parameters[i].isAnnotationPresent(Valid.class)) {  Object val = args[i];  ValidationResult validationResult = HibernateValidatorUtils.validateEntity(val);  if (validationResult.isHasErrors()) {  return ResultMessageVO.fail(ResultCodeEnum.PARAM_ERROR, validationResult.getErrorMessageOneway());  }  }  }  return point.proceed();  } } Copy the code

Because we return only ResultMessageVO externally, we can do unified processing in the section. Otherwise, each method needs to do independent parameter verification, which is the benefit of unified return value.

Of course, if there is a uniform exception, also because of the existence of the uniform return value, it is easier to handle:

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
 publicResultMessageVO<? > exception(HttpServletRequest request, Exception e){ if (e instanceof NoHandlerFoundException) {  log.error("Page does not exist: {}", e.getMessage());  return new ResultMessageVO(ResultCodeEnum.ERROR, "Page does not exist");  } else if (e instanceof BindException) {  log.error("Parameter format error: {} URL: {}", e.getMessage(), request.getRequestURI());  return new ResultMessageVO(ResultCodeEnum.INVALID_ARGUMENTS, "Parameter format error");  } else if (e instanceof HttpRequestMethodNotSupportedException){  log.error("Unsupported request: {} URL: {}", e.getMessage(), request.getRequestURI());  return new ResultMessageVO(ResultCodeEnum.ERROR, "Unsupported request mode");  } else if (e instanceof BusinessException) {  BusinessException exception = (BusinessException) e;  log.warn("Service exception: {} URL: {}", exception.getMsg(), request.getRequestURI());  return new ResultMessageVO<>(exception.getResultCodeEnum(), exception.getMsg());  } else {  log.error("System exceptions: {} \ t \ r \ n url: {} \ t \ r \ n the header: {} \ t \ r \ n params: {} \ t \ r \ n body: {}". e.getMessage(), request.getRequestURI(), RequestUtil.getHeaders(request), RequestUtil.getParameterMap(request), getBody(request), e);  return new ResultMessageVO<>(ResultCodeEnum.ERROR);  }  }  private String getBody(HttpServletRequest request) {  String body = StringUtils.EMPTY;  try {  body = RequestUtil.getBody(request);  } catch (IOException e) {  log.error("Failed to read request body while printing system exception log, URL :{}", request.getRequestURI(), e);  }  return body;  } } Copy the code

Through the combination of the above two steps, we successfully use checked exception and enumeration to complete the reasonable use of exceptions, and then cooperate with global exception processing to complete the last bottom. In this way, you only need to catch BusinessException in your program. Successful elimination of nasty code snippets.

After the language

This article is some summary of my experience, unavoidable omissions or even mistakes, if there is unreasonable, inadequate, also hope to correct. In addition, I hope you can talk about how you use exceptions in your projects and learn from each other.