zoukankan      html  css  js  c++  java
  • null?对象?异常?到底应该如何返回错误信息

    这篇文章记录我的一些思考。在工作了一段时间之后。

    问题的核心很简单:到底如何返回错误信息。

    学生时代,见到过当时的老师的代码:

    1 if (foo() == null) {
    2 
    3 }

    当然,这位老师是一位比较擅长c/c++的老程序员,所以他的代码其实使用c写的。但是意思和这段代码类似。当时,我很好奇为什么要对一个方法的返回值是不是null进行判断。现在当然很清楚了:在很多win32的API里面,是通过返回值为null来传递“函数调用失败”这一种信息的。

    那么,这么做好吗?

    我翻看了很多的博客,大致上说这种写法不好的占多数。最重要的理由如下:

    • 无法展示更详细的错误信息
    • 容易让调用者忽略

    这种说法基本还是有道理的。因为返回值为null,确实很明显得存在这两种问题。

    对于第一点,其比较关键的影响就是,既然不知道更多信息,也就无法在日志中体现。从而不能针对可能出现的多种异常进行不同的日志打印和处理。曾经也发生过因为缺失了日志打印,导致花很多时间来定位一个问题的情况。尤其是线上环境不比本地随意debug,一个简单的问题经过几次这种“信息丢失”之后,就有可能成为一个“疑案”。

    而第二点,更是严重影响系统安全性、稳定性。人都有不小心的时候,如果忘了对某个接口的返回值进行校验,就会导致代码逻辑是按照“这件事已经发生并且正确发生”写的,但是实际执行过程中发生了错误。一方面比较难排查,第二方面是可能导致一个问题在另外的地方爆发出来。

    那么是不是绝对不能这么做呢?其实也不尽然。简单来说,如果调用方在系统内部、代码执行逻辑可控、在返回null之前有正确的日志打印、调用方对这个异常确实没有办法处理。满足这几个条件就可以使用返回null。但是还是具有危险性。

    这个问题的改进方案是返回一个对象。比如写一个Result对象。

     1 public class Result {
     2 
     3     private Integer code;
     4 
     5     private String message;
     6 
     7     private Object data;
     8     
     9     // getter and setter
    10 
    11 }

    这样就有一个优化:能够返回错误的信息和一些数据。本次方法成功了吗?如果失败需要打印什么信息?如果成功,是不是需要一些内容?

    但是这种写法也有问题。

    最主要的是其适用性问题。就是说如果很多方法使用同一个Result,能保证都能通过这种方式返回其需要的数据吗?一些特别的方法返回的东西不能够使用一个Object进行转化。(或者说这样做代价不小)

    其次,很多代码业务逻辑极其简单,如果强行使用结果类进行封装,反倒有画蛇添足之嫌。

    最后,如果要严格使用这个类,代码开发的代价很大。

    那么这种做法适合什么场景呢?一句话:对外接口。一方面我们要按照一个统一的规则去返回信息(正确或者错误)。第二方面,很难要求调用方去捕获我们的异常。如果他们忘了捕获,是不是就有可能会造成更严重的情况?所以,这种情况下,我们需要告知调用方失败信息。但是又不能要求对方做出多少改变。

    最后一种解决方案是抛异常。

     Effective Java里面用一句话解释什么时候需要抛异常,什么时候不需要。“当你对异常什么也做不了的时候,就不要抛异常”(大意,有可能记错了)。

     这句话是很有道理的。因为如果你显式地抛出异常,那么调用方就需要去捕获。如果要求对方捕获,可是却对这种异常什么也做不了。那么抛异常是不是就不那么有意义了?

    这个地方的关注焦点在于,调用方如何处理这个异常?假如说,调用方只是打印日志,算不算这个异常有用?在最开始的时候,我曾经认为这种情况可以认定为这个异常对调用方没有帮助。而现在我的想法产生了变化。简单来说和具体业务有关。

    再进一步来说:如果调用方在得到这个错误信息之后,代码逻辑会改变,就适合抛异常。而如果调用方即使得知了错误信息,也不需要改变代码运行逻辑,就可以更简化得处理。比如这个接口是删除数据。那么在失败的情况下,就必须通过抛异常或者别的方式,告知调用方数据删除失败。否则调用方的代码可能会忽视数据删除失败。这里就不适合返回null,因为返回null的检查不是强制性的,调用方如果没有进行判断,就可能导致后面的逻辑完全错误。

    第二种情况就在于对一些缓存的查询。如果失败,或者没查到,抛异常就不再是一个必须的选项了。当然,如果抛异常,能让调用方更清晰地了解为啥没查到缓存,是网络问题,还是参数不正确,或者确实没有这个缓存。这里的重点在于,调用方即使知道了这些信息,恐怕也无法做更多的事,即使知道了是网络问题,也很难通过代码解决。而对于打印日志,这种“没查到缓存”的情况是否有必须在调用端强调其原因,就看具体的业务了。不过我个人觉得,这里的日志打印交给服务提供方比较好,其一是提供方肯定比调用方知道的信息更全。其二是,提供方本来就有责任去维护系统的稳定性。所以对于日志,能做更多。

    除了这些理由之外,还有一个点。就是在项目结构的关键层级,是否需要补货所有异常。比如controller层,如果抛出异常,就会直接给用户造成严重影响。当然如果每个函数都简单地try catch住所有异常,则也是不负责任的,这样不利于排查问题。

    总结起来,是否抛异常,还是通过什么方式返回错误信息。主要取决于:

    • 调用方拿到错误信息能做什么
    • 业务上调用方是否需要获取具体的错误信息
    • 是否有必要捕获所有异常
  • 相关阅读:
    668. Kth Smallest Number in Multiplication Table
    658. Find K Closest Elements
    483. Smallest Good Base
    475. Heaters
    454. 4Sum II
    441. Arranging Coins
    436. Find Right Interval
    410. Split Array Largest Sum
    392. Is Subsequence
    378. Kth Smallest Element in a Sorted Matrix
  • 原文地址:https://www.cnblogs.com/dsj2016/p/7482089.html
Copyright © 2011-2022 走看看