提升编码效率的方法

97 阅读4分钟

1.概述

日常开发的时候经常会去别人负责的工程里开发代码,如果各个工程的代码风格各异会影响日常开发效率,故需要进行一些通用设计来提升日常开发效率。

2.通用设计

2.1.异常

2.1.1.基础知识

异常是可以传递的,里层的异常可以往外层传递,比如下图代码(图2.1.1.1),最终会打印三个异常堆栈(图2.1.1.2)

image.png

图2.1.1.1

image.png

图2.1.1.2

看到过很多自定义异常不支持传递异常,当使用这些自定义异常时往往会先打印日志再抛出异常(图2.1.1.3),代码繁琐并且容易漏打日志

image.png

图2.1.1.3

2.1.2.自定义异常

自定义异常应包含的几个关键要素:

  1. 错误码(处理异常时需根据错误码打印不同级别的日志)
  2. 错误信息(给用户看的,比较简单明了)
  3. 描述信息(给技术人员看的,会携带排查问题所需的关键信息,比如用户查不到会携带用户号)
  4. 异常(可以将里层的异常不断地往外层传递,由最外层打印异常堆栈)

样例:

public class SsoException extends RuntimeException {
    private String errorCode;
    private String errorMsg;
    private String message;
    private String param;

    public SsoException(ErrorCode errorCode) {
        this(null, errorCode, null);
    }

    public SsoException(ErrorCode errorCode, String msg, Object... ss) {
        this(null, errorCode, msg, ss);
    }

    public SsoException(Throwable e, ErrorCode errorCode) {
        this(e, errorCode, null);
    }

    public SsoException(Throwable e, ErrorCode errorCode, String msg, Object... ss) {
        super(e);
        if (errorCode == null) {
            errorCode = ErrorCode.UnknownError;
        }
        this.errorCode = errorCode.getRetCode();
        this.errorMsg = errorCode.getRetMsg();
        this.message = StringUtils.isBlank(msg) ? "[" + errorCode.getRetCode() + "][" + errorCode.getRetMsg() + "]" : MessageFormatter.arrayFormat(msg, ss).getMessage();
    }

    public SsoException(ErrorCode errorCode, ParamNameEnum paramName, String msg, Object... ss) {
        if (errorCode == null) {
            errorCode = ErrorCode.UnknownError;
        }
        this.errorCode = errorCode.getRetCode();
        this.errorMsg = errorCode.getRetMsg();
        this.message = StringUtils.isBlank(msg) ? "[" + errorCode.getRetCode() + "][" + errorCode.getRetMsg() + "]" : MessageFormatter.arrayFormat(msg, ss).getMessage();
        this.param = paramName == null ? null : paramName.name();
    }

    @Override
    public String getMessage() {
        return message;
    }

2.1.3.使用自定义异常

当捕获某处异常并且想中断执行,抛自定义异常并携带原始异常(图2.1.3.1)

image.png

图2.1.3.1

2.1.4.处理自定义异常

http接口统一由切面处理,dubbo接口统一由filter处理,日常开发时无需操心异常的处理,统一处理自定义异常时可以根据携带的错误码打印不同级别的日志,描述信息可根据监控系统规则打标(图2.1.4.1)

image.png

图2.1.4.1

2.2.打印请求和响应日志

不管是接口提供方还是接口调用方,看到过很多代码都是针对单个接口打印请求和响应日志(图2.2.1和图2.2.2),代码繁琐并且容易漏打

image.png

图2.2.1

image.png

图2.2.2

http可以使用切面,dubbo可以使用filter来统一打印请求和响应日志,日常开发时无需操心请求和响应日志的打印

http接口统一打印日志类:

@Aspect
@Component
@Slf4j
public class SystemLogAspect {
    @Resource
    private BizApplicationConfig bizApplicationConfig;

    @Pointcut("@annotation(com.lianlianpay.cb.va.sso.aop.SystemLog)")
    public void controllerAspect() {
    }

    @Around("controllerAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String arg = null;
        Object result = null;
        String uri = null;
        String requestIp = null;
        String userId = null;
        String bizSource = null;
        String requestSource = null;
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            arg = JSON.toJSONString(joinPoint.getArgs());
            uri = request.getRequestURI() + " " + request.getMethod();
            requestIp = HttpUtils.getIp(request);
            bizSource = request.getHeader(SsoConstants.BIZ_SOURCE_HEADER_TAG);
            CookieUser cookieUser = (CookieUser) request.getAttribute("cookieUser");
            userId = cookieUser == null ? null : cookieUser.getUserId();
            requestSource = request.getHeader(SsoConstants.HEADER_REQUEST_SOURCE);
            RequestSourceContext.set(requestSource);
            BizSourceContext.set(bizSource);
            ApContext.set(bizApplicationConfig.getAp(bizSource));
            log.info("http请求开始:uri={},requestIp={},request={},userId={},bizSource={},requestSource={}",
                    uri, requestIp, arg, userId, bizSource, requestSource);
            result = joinPoint.proceed();
        } catch (Throwable e) {
            result = ExceptionUtil.handleException(e, "http请求异常:uri=" + uri);
        } finally {
            RequestSourceContext.remove();
            BizSourceContext.remove();
            ApContext.remove();
            log.info("http请求结束:uri={},requestIp={},request={},userId={},bizSource={},requestSource={},response={},cost={}ms",
                    uri, requestIp, arg, userId, bizSource, requestSource, JSON.toJSONString(result), System.currentTimeMillis() - startTime);
        }
        return result;
    }
}

dubbo服务端统一打印日志类:

@Slf4j
public class ProviderLogFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        //请求参数脱敏
        List<Object> argList = null;
        if (invocation.getArguments() != null) {
            argList = Arrays.stream(invocation.getArguments()).map(e -> {
                if (e instanceof String) {
                    return MaskUtil.mask((String) e);
                }
                return e;
            }).collect(Collectors.toList());
        }
        //返回结果处理
        if (result.hasException()) {
            Throwable e = result.getException();
            if (e instanceof SsoException) {
                SsoException se = (SsoException) e;
                //重置result
                if (ErrorCode.ParamsError.getRetCode().equals(se.getErrorCode())) {
                    ((RpcResult) result).setException(null);
                    ((RpcResult) result).setValue(ResultRichUtil.format(se.getErrorCode(), se.getMessage()));
                } else {
                    ((RpcResult) result).setException(null);
                    ((RpcResult) result).setValue(ResultRichUtil.format(se.getErrorCode(), se.getErrorMsg()));
                }
                //打印日志
                //特殊错误码不做日志采集
                BizApplicationConfig bizApplicationConfig = SpringContextUtil.getBean(BizApplicationConfig.class);
                List<String> specialCodeList = bizApplicationConfig.getSpecialErrorCode();
                if (specialCodeList.contains(se.getErrorCode())) {
                    log.info("[{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                            invoker.getInterface(), invocation.getMethodName(),
                            JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
                } else if (se.getErrorCode().equals(ErrorCode.UnknownError.getRetCode())) {
                    log.error("[{}][{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                            LogTagConstant.SYSTEM_ERROR, invoker.getInterface(), invocation.getMethodName(),
                            JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
                } else if (se.getErrorCode().equals(ErrorCode.RemoteInvokeFail.getRetCode())) {
                    log.warn("[{}][{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                            LogTagConstant.REMOTE_ERROR, invoker.getInterface(), invocation.getMethodName(),
                            JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
                } else {
                    log.warn("[{}][{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                            LogTagConstant.REQUEST_ERROR, invoker.getInterface(), invocation.getMethodName(),
                            JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
                }
            } else if (e instanceof BaseException) {
                BaseException be = (BaseException) e;
                //重置Result
                ((RpcResult) result).setException(null);
                ((RpcResult) result).setValue(ResultRichUtil.format(be.getErrCode(), be.getErrMsg()));
                //打印日志
                log.error("[{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                        invoker.getInterface(), invocation.getMethodName(), JSON.toJSONString(argList),
                        JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
            } else {
                //重置Result
                ((RpcResult) result).setException(null);
                ((RpcResult) result).setValue(ResultRichUtil.format(ErrorCode.UnknownError.getRetCode(), ErrorCode.UnknownError.getRetMsg()));
                //打印日志
                log.error("[{}][{}][{}]invoke fail:请求={},响应={},耗时={}ms",
                        LogTagConstant.SYSTEM_ERROR, invoker.getInterface(), invocation.getMethodName(),
                        JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start, e);
            }
        } else {
            BizApplicationConfig bizApplicationConfig = SpringContextUtil.getBean(BizApplicationConfig.class);
            if (!bizApplicationConfig.getDubboLogExclude().contains(invoker.getInterface() + "#" + invocation.getMethodName())) {
                //打印日志
                log.info("[{}][{}]invoke success:请求={},响应={},耗时={}ms",
                        invoker.getInterface(), invocation.getMethodName(),
                        JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start);
            }
        }
        return result;
    }
}

dubbo客户端统一打印日志类:

@Slf4j
public class ConsumerLogFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        //请求参数脱敏
        List<Object> argList = null;
        if (invocation.getArguments() != null) {
            argList = Arrays.stream(invocation.getArguments()).map(e -> {
                if (e instanceof String) {
                    return MaskUtil.mask((String) e);
                }
                return e;
            }).collect(Collectors.toList());
        }
        //返回结果处理
        if (result.hasException()) {
            log.warn("[{}][{}]invoke fail:请求={},耗时={}ms",
                    invoker.getInterface(), invocation.getMethodName(),
                    JSON.toJSONString(argList), System.currentTimeMillis() - start);
        } else {
            BizApplicationConfig bizApplicationConfig = SpringContextUtil.getBean(BizApplicationConfig.class);
            if (!bizApplicationConfig.getDubboLogExclude().contains(invoker.getInterface() + "#" + invocation.getMethodName())) {
                log.info("[{}][{}]invoke success:请求={},响应={},耗时={}ms",
                        invoker.getInterface(), invocation.getMethodName(),
                        JSON.toJSONString(argList), JSON.toJSONString(result.getValue()), System.currentTimeMillis() - start);
            }
        }
        return result;
    }
}

2.3.断言

常见的断言工具断言和异常是捆绑在一起的(图2.3.1),扩展性差、适用性差

image.png

图2.3.1

可以将断言和异常分离开,断言只做判断并对result赋值,抛异常方法判断result,若为false则抛异常,抛什么异常可以自定义(图2.3.2)

image.png

图2.3.2

BaseRefer的子类可以自己扩展,抛异常的方法也可以自己扩展,日常开发使用(图2.3.3)

image.png

图2.3.3