总结了一些后端开发规范

 

1.1 异常处理

  异常的处理应当符合 尽早抛出,延迟捕获 的原则。

  在进行 MVC 分层调用时,Service 与 Dao 层触发的异常应统一向上抛出,交给 Controller 层处理。因为只有 Controller 层可以更好的决策发生异常时应当给用户何种反馈。

  但这样会造成我们的 Controller 被庞大的 try-catch 块包裹,异常处理与业务逻辑糅杂在一起极大的降低了代码的可读性。

比如:

 

      我们实际的业务逻辑仅仅占总代码量的一半甚至更少。

      在项目中使用了 Spring3.0 提供的 RestControllerAdvice 机制,定义统一的异常处理方法。

 

      不管在Controller/Service 还是 Dao 中,我们都不需要捕获异常,而只需要在恰当的时候抛出异常。

      最终堆栈会callBack到Spring DispatchServlet 的try部分触发我们的异常处理器。当然这要求我们抛出的异常对应的情况颗粒度更细,以便异常处理器做出合适的反应。我们可以在 inspur-common-exception 包中自定义异常,以“文件长度超限异常”为例:

 

      com.inspur.framework.web.exception.DefaultExceptionHandler 类中定义该异常的处理器:

 

      这样我们的业务代码中不需要有一行异常处理的动作,只需要在恰当的时候将异常抛出。这使我们的业务代码可以仅关注业务逻辑:

 

      同时无论在调用栈的何处发生该异常,Spring都会帮助我们 longjump到异常处理器中的方法,返回给前台一个“文件长度超过限制!”的警示信息。

1.2 多线程任务

  如果遇到需要进行多线程协作的情况,禁止使用 new Thread().start() 等原始的方式创建线程。如果逻辑出现错误(比如过多的循环或层级过深的递归调用)/或者开启新线程的代码部分承载了较大的并发量,我们可能会创建超过系统承受量的线程,威胁系统安全。同时线程的频繁创建销毁需要频繁的进行系统调用,造成处理器在用户态与核心态间的切换,带来不必要的性能开销。

  工程中应对线程池进行封装,并以单例的方式注册到了 IOC 容器中,在项目启动时会对线程进行统一的创建,运行过程中线程创建销毁的频率很低,将该部分性能开销由运行时转嫁到项目启动时。通过 IOC 容器拿到全局唯一的线程池对象,向线程池中提交多线程/异步任务。做到线程资源的复用,同时对系统可创建的总线程数进行控制,保证系统安全。

1.3 防抵赖措施

  工程中应封装统一的调用情况记录工具,用于防抵赖或记录操作日志。使用动态代理配合自定义注解是个不错的选择。

   比如自定义@Log 注解修饰期望记录操作人信息的方法,并标明操作类型与日志名称,在操作完成后织入后置动作将操作信息记录到数据库中。

1.4  Excel导出

   为每一种 pojo 实现一次 excel 导出费时费力,应当考虑抽取统一的方法。可以自定义注解修饰 pojo 中的属性,标示它们在 excel 中对应的列名/列宽等信息。借助反射统一通过该注解生成 excel。

      但值得注意是,这类功能调用非常方便,但是十分消耗性能。因为不仅使用了反射技术,包含反射技术的代码还存在于 for 循环中。 JVM 无法对该部分代码进行性能优化。当数据量过大或对响应时间有特殊需求时建议针对Pojo重新定制导出Excel 的方法。

1.5 文件的上传与下载

  工程中为文件的上传/下载应封装统一的工具类和接口。

  文件的上传与下载都需要在 文件存储地址-临时缓存地址-内存 之间拷贝多次,同时每次拷贝都需要经历 硬盘-内核输入缓冲区-进程空间-内核输出缓冲区-目标地址 的拷贝,十分消耗性能但难以避免。应当在适当的时候采用了NIO 支持的zero-copy 技术,比如:

   将 过程:硬盘-内核输入缓冲区-进程空间-内核输出缓冲区-目标地址 简化为:硬盘-内核输入缓冲区-内核输出缓冲区-目标地址,提升一些性能。性能提升测试:

 

1.6 接口调用

  在调用异构系统接口尤其是请求方式为 POST 方式的接口时,调用参数往往非常复杂,比如:

      需要非常多的代码量来构建请求参数,并且交接人员想要完全掌握这段代码需要按照接口文档进行细致的比对,并进行多次测试。

      我们应当优化参数拼装的代码结构,增强其可读性于扩展性。我们将接口调用参数封装为 DTO,并使用 @ParamAnnotation 注解修饰其属性。

      @ParamAnnotation 目前包含两个属性:name:参数名称,isComplete:是否必传参数。

      这样可以清晰的描述请求参数的含义。在构建一个 DTO 实例后,调用接口前,调用 DTO 的 isComplete 方法(不需显示声明,继承自BaseDto),@ParamAnnotation注解中isComplete属性为true的成员变量是否为null,如果这些调用接口的必传参数存在null值,则会抛出一个自定义的ParamterImperfectException 异常,供上层决策。

      这样调用接口被抽象简化为了三步,如果想要了解接口入参,去查看dto 结构和注解即可。同时如果接口参数发生改变,只需要修改 dto 结构,调用接口代码部分不需要修改,做到了参数构建与接口调用逻辑的解耦,方便在接口频繁变动的情况下对原有代码的扩展。

1.7 调用层级间的通信

  不要向上层返回魔法值让上层决策,如果出现问题就抛出异常。可以帮助我们更清晰的规划代码逻辑,魔法值的传递随着层级的加深只会越来越复杂。

posted @ 2020-03-26 19:46  牛有肉  阅读(1000)  评论(0编辑  收藏  举报