Java 为未考虑的业务异常返回用户友好的error message
背景
项目早期底层方法捕获了能考虑到的业务异常,但即便如此,生产环境还是会产生各种难以预料的结果,所以在上层调用的时候,这些异常就进入了Exception中,返回到UI上就是“System error”
try
{
}
catch(bizException1 | bizException2)
{
}
catch(Exception)
{
//log
//return "System error"
}
这样不明所以的error message 会让用户很困扰,他们不知道发生了什么,也不知道该如何再操作了。
所以我们需要把有用的信息返回给用户,你可能说了,这还不简单,Exception.getMessage()中就有异常的信息。
但是,这个信息又过于“程序”了,想象一下,用户看到一个索引越界的message同样的还是不友好,和之前的“System error”并没有多大的区别。
我们需要给客户返回业务相关的错误信息,比如,在执行某个操作的时候出现了错误。
具体以当前自动化测试系统来说,如果出现了未考虑的业务异常,我们期望给用户返回“在跳转用户中心的时候出现了错误:在点击跳转按钮的时候出现了错误”。
解决方案
那我们总不能把所有的底层方法一个一个的全用try catch块给包起来吧,毕竟这种意料之外的业务异常还是少数。
那就思路就比较简单了,我们只需要对底层方法进行描述,出现异常的时候,通过反射拿到描述信息,进行拼接,就能得到用户友好的error message了。
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAction {
String name();
}
使用注解
@MyAction(name = "Navigate to xxx page")
public XXX navigateToUserCenterPage() {
}
通过注解获取出异常的方法信息
public static String getErrorMessageFromMethodAnnotations(Exception exception) {
List<Method> methods = getCallStackMethods(exception);
List<String> messageList = new ArrayList<>();
for (Method method : methods) {
if (!Proxy.isProxyClass(method.getDeclaringClass())) {
return extractTestActionMessage(messageList, method);
}
}
}
private static List<Method> getCallStackMethods(Exception exception) {
List<Method> methods = new ArrayList<>();
if (null == exception) {
return methods;
}
StackTraceElement[] stackTraces = exception.getStackTrace();
if (ArrayUtils.isEmpty(stackTraces)) {
return methods;
}
StackTraceElement stackTrace;
for (int i = stackTraces.length - 1; i >= 0; i--) {
stackTrace = stackTraces[i];
Class<?> clazz;
try {
String className = stackTrace.getClassName();
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
continue;
}
Method[] classMethods;
try {
classMethods = clazz.getDeclaredMethods();
} catch (SecurityException e) {
continue;
}
String methodName = stackTrace.getMethodName();
Method method = Arrays.stream(classMethods)
.filter(m -> m.getName().equals(methodName))
.findFirst()
.orElse(null);
if (null == method) {
continue;
}
methods.add(method);
}
return methods;
}
private static void extractMyActionMessage(List<String> messageList, Method method) {
TestAction testAction = MethodUtils.getAnnotation(method, MyAction.class, true, true);
if (testAction != null) {
String nameValue = testAction.name();
if (StringUtils.isNotBlank(nameValue)) {
messageList.add(nameValue.trim() + SPACE + FAILED);
}
}
}