Encapsulate exceptions with Assert to elegant code (with project source)

Posted by jameslynns on Mon, 07 Mar 2022 18:13:17 +0100

Python WeChat Subscription Applet Course Video

https://edu.csdn.net/course/detail/36074

Python Actual Quantitative Transaction Finance System

https://edu.csdn.net/course/detail/35475
Assert assertions are not new to you. When we are doing unit tests, looking at business transaction composite expectations, we can verify them with assertions, which are commonly used in the following ways:

public class Assert {
    /**
 * Result = Expected is correct
 */
    static public void assertEquals(Object expected, Object actual);
    /**
 * Result!= Expectations are correct
 */
    static public void assertNotEquals(Object unexpected, Object actual);
    /**
 * condition == true Correct
 */
    static public void assertTrue(boolean condition);
    /**
 * condition == false Correct
 */
    static public void assertFalse(boolean condition);
    /**
 * Always a mistake
 */
    static public void fail();
    /**
 * The result is correct if it is not empty
 */
    static public void assertNotNull(Object object);
    /**
 * Empty results are correct
 */
    static public void assertNull(Object object);
    /**
 * Two object references are correct if they are equal (equels above is similar to comparing two objects using'==')
 */
    static public void assertSame(Object expected, Object actual);
    /**
 * Two object references are correct if they are not equal
 */
    static public void assertNotSame(Object unexpected, Object actual);
    /**
 * Two arrays are correct if they are equal
 */
    public static void assertArrayEquals(Object[] expecteds, Object[] actuals);
    /**
 * This is a separate introduction to the specific reference blog: https://blog.csdn.net/qdhxhz/p/13684458.html
 */
    public static  void assertThat(T actual, Matcher <span class="hljs-built\_in"super T> matcher); 
};

Using assertions can make our code look more refreshing, such as:

   @Test
    public void test1() {
        Order order = orderDao.selectById(orderId);
        Assert.notNull(order, "Order does not exist.");
    }

    @Test
    public void test2() {
        // Another way of writing
        Order order = orderDao.selectById(orderId);
        if (order == null) {
            throw new IllegalArgumentException("Order does not exist.");
        }
    }

Comparing the two, is it obvious that the first is more elegant, and the second is a relatively ugly if {...} block of code. So amazing Assert. What exactly is going on behind notNull ()?

Here are some of the sources for Assert:

public abstract class Assert {
    public Assert() {
    }

    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
}

As you can see, Assert actually helps us encapsulate if {...}, is it amazing? While simple, it's undeniable that the coding experience has improved at least one notch.

So can we mimic Assert and write a custom assertion class, but the exception thrown after the assertion fails is not IllegalArgumentException built-in exceptions, but our own defined exceptions.

Let's try it now.

public interface Assert {
    /**
 * Create Exceptions
 * @param args
 * @return
 */
    BaseException newException(Object... args);

    /**
 * Create Exceptions
 * @param t
 * @param args
 * @return
 */
    BaseException newException(Throwable t, Object... args);

    /**
 * Assertion object obj is not empty. Throw an exception if the object obj is empty
 *
 * @param obj Object to be judged
 */
    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }

    /**
 * Assertion object obj is not empty. Throw an exception if the object obj is empty
 * Exception message supports parameter delivery and avoids string splicing prior to judgment
 *
 * @param obj Object to be judged
 * @param args message Placeholder corresponding parameter list
 */
    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }
}

Note:

  1. Only part of the source code for the Assert interface is given here. Refer to the source code for the project for more assertion methods.
  2. BaseException is the base class for all custom exceptions.
  3. Defining the default method in an interface is a new syntax for Java8.

The Assert assertion method above is defined using the default method of the interface, and then whether you find that when the assertion fails, the exception thrown is not a specific exception, but is provided by two newException interface methods.

Because exceptions in business logic are basically specific scenarios, such as getting user information based on user id, and the query result is null, exceptions thrown at this time may be UserNotFoundException, and have special features

Defined exception codes (such as 7001) and exception information "User does not exist". So what exception is thrown is determined by the implementation class of Assert.

Looking at this, you may wonder how many exceptions there are. I'm going to implement the Assert interface, and then write an equal number of assertion and exception classes, which are obviously anti-human.

That's not what I imagined. Take your time and listen to me in detail.

1. Enum of understanding

Custom exception BaseException has two attributes, code and message. If you have two attributes, do you think any classes will define these two attributes in general?

Yes, it's an enumeration class. And see how I combine Enum with Assert, and I'm sure I'll make you see. The following:

public interface IResponseEnum {
    int getCode();
    String getMessage();
}

We customize a business exception class

/**
 * Business Exceptions
 * When a business process occurs, an exception can be thrown
 *
 */
public class BusinessException extends  BaseException {

    private static final long serialVersionUID = 1L;

    public BusinessException(IResponseEnum responseEnum, Object[] args, String message) {
        super(responseEnum, args, message);
    }

    public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {
        super(responseEnum, args, message, cause);
    }
}

Business exception assertion implementation class, which inherits two interfaces at the same time, one is the Assert interface and the other is the IResponseEnum interface

public interface BusinessExceptionAssert extends IResponseEnum, Assert {

    @Override
    default BaseException newException(Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);

        return new BusinessException(this, args, msg);
    }

    @Override
    default BaseException newException(Throwable t, Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);

        return new BusinessException(this, args, msg, t);
    }

}

Business Exception Enumeration

/**
 * Business Exception Enumeration
 */
@Getter
@AllArgsConstructor
public enum BusinessResponseEnum implements BusinessExceptionAssert {


    USER_NOT_FOUND(6001, "No user information was queried"),

    ORDER_NOT_FOUND(7001, "No order information was queried"),

    ;

    /**
 * Return code
 */
    private int code;
    /**
 * Return message
 */
    private String message;

}

This allows you to define a new enumeration object in BusinessResponseEnum if it is a business exception. For each additional exception in the future, simply add an instance of the enumeration, and never use each exception again

An exception class has been defined. Next, see the following:

   /**
 * Query user information
 */
    public User queryDetail(Integer userId) {
        final User user = this.getById(userId);
        // Check not empty
        BusinessResponseEnum.USER_NOT_FOUND.assertNotNull(user);
        return user;
    }

Without an assertion, the code might be as follows:

  /**
 * Query user information
 */
    public User queryDetail(Integer userId) {
        final User user = this.getById(userId);

        if (user == null) {
            throw new UserNotFoundException();
            // Or so
            throw new BusinessException(6001, "No user information was queried");
        }
        return user;
    }

Using an enumeration class to bind (inherit) Assert, you only need to define different instances of the enumeration based on specific exceptions, such as USER_above NOT_ FOUND, ORDER_NOT_FOUND allows you to throw specific exceptions for different situations

(This means carrying specific exception codes and messages) so that you don't have to define a large number of exception classes, but also have good readability of your assertions.

2. Verify Unified Exception Handling

After throw throws an exception, we can handle it uniformly. If necessary, we can add the exception log to the database to help troubleshoot problems later.

/**
 * Global exception handler
 * 
 * @author xub
 * @date 2022/2/28 10:52 a.m.
 */
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
public class ViewExceptionResolver {
    /**
 * production environment
 */
    private final static String ENV\_PROD = "prod";

    @Autowired
    private UnifiedMessageSource unifiedMessageSource;

    /**
 * Current environment
 */
    @Value("${spring.profiles.active}")
    private String profile;

    /**
 * Get International Messages
 *
 * @param e abnormal
 * @return
 */
    public String getMessage(BaseException e) {
        String code = "response." + e.getResponseEnum().toString();
        String message = unifiedMessageSource.getMessage(code, e.getArgs());

        if (message == null || message.isEmpty()) {
            return e.getMessage();
        }

        return message;
    }

    /**
 * Business Exceptions
 *
 * @param e abnormal
 * @return Exceptional results
 */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public CommandResult handleBusinessException(BaseException e) {
        log.error(e.getMessage(), e);
        return CommandResult.ofFail(e.getResponseEnum().getCode(), getMessage(e));
    }

    /**
 * Custom Exception
 *
 * @param e abnormal
 * @return Exceptional results
 */
    @ExceptionHandler(value = BaseException.class)
    @ResponseBody
    public CommandResult handleBaseException(BaseException e) {
        log.error(e.getMessage(), e);

        return CommandResult.ofFail(e.getResponseEnum().getCode(), getMessage(e));
    }


    /**
 * No exception defined
 *
 * @param e abnormal
 * @return Exceptional results
 */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public CommandResult handleException(Exception e) {
        log.error(e.getMessage(), e);

        if (ENV_PROD.equals(profile)) {
            // When it is a production environment, it is not appropriate to show specific exception information to users, such as database exception information.
            int code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return CommandResult.ofFail(code, message);
        }

        return CommandResult.ofFail(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage());
    }
}

3. Test and Verification

The address is requested here, and the user does not exist in the business id=10 database, so an error will be reported.

localhost:8085/user/getUserInfo?userId=10


Expected results have been achieved.

4. Summary

With the combination of assertions and enumeration classes, combined with uniform exception handling, most of the basic exceptions can be caught. Why are most exceptions because when spring cloud security is introduced, there are

There are authentication/authorization exceptions, service downgrade exceptions for gateways, cross-module call exceptions, remote call of third-party service exceptions, etc. These exceptions are captured in a different way than those described in this article, but they are not detailed here.

There will be separate articles later.

Project Address: Encapsulate exceptions with Assert to elegant code (with project source)

Thank

This article gives you a good idea, and basically follows it

Introduction to Unified Exception Handling and Actual Warfare: https://www.jianshu.com/p/3f3d9e8d1efa

Topics: Python WPF computer