软件开发中的异常管理小结

所谓软件开发中的异常,常见有以下几种情况:

  • 方法的输入参数不合规;
  • 处理逻辑中的数据处理不当;
  • 调用第三方组件时出现错误;
  • 数据库操作、远程调用、IO操作出现问题。

对于这些异常,我们通常的处理方式有两种,继续向上抛,或者使用try...catch捕获并将错误堆栈打印出来。我想大多数人会采用第二种,而且是被迫的,因为这些方法定义了异常,调用时就必须要进行捕获。

有人会问,我使用try...catch把他们包起来不就实现了异常管理了吗?

非也!简单的try...catch只是做到了对异常的“处理”,还达不到“管理”的层次。要达到“管理”的层次,还需从异常的性质说起。异常从性质来讲一般分为以下几类:

  • 由用户操作问题导致,比如说输入的数据格式有问题或是输入了一个不存在的实体ID等等,这种情况需要系统将异常反馈给客户,引导客户对错误进行修正;
  • 可补偿的系统内部异常,但是系统通过捕获这些异常可以进行其他补偿操作逻辑,最终返回给客户成功的结果;
  • 不可补偿的系统内部异常, 比如说数据库连接不上了、某个系统配置有问题或是与外系统的网络中断了等等,这些异常反馈给客户是没有意义的,客户无法利用这些信息,只能通知管理员进行系统的修复。

我们先分析第一种。第一种主要是因为输入不合规导致的,而这种异常通常产生的都是空指针或是客户看不懂的异常,把这种异常直接暴露给客户是没有意义的。如何能让这些异常变得有价值呢?我们需要对这些异常进行提前捕获和并进行封装。举个例子:

public void finishEntry(String entryID)
{
    Entry entry = entryDao.get(entryID);
    entry.setStatus("FINISH");
    entryDao.saveOrUpdate(entry);
}

如果上面的方法中,传入的entryID在数据库中不存在,entry.setStatus("FINISH")必然会报空指针,成熟一点儿的写法(大多数人)一般是这样:

public boolean finishEntry(String entryID)
{
    boolean result = false;
    Entry entry = entryDao.get(entryID);
    if(entry != null)
    {
        entry.setStatus("FINISH");
        entryDao.saveOrUpdate(entry);
        result = true;
    }
    return result;
}

我们把finishEntry方法设置上返回值,并在方法里对根据entryID获取到的entry做非空判断,如果方法执行一切正常,则返回true,相反如果出现没有获取到entry,则返回false。

看似一切完美了?!NO!首先,对于boolean的返回,调用者只得到了执行结果,如果结果为false,调用者根本不知道导致失败的原因,所以使用false来诠释异常是没有意义的。那么我们应该怎么办呢?我们看到这个方法里有两处可能会出现异常:一个是数据库里没有entryID的数据;另一个就是数据库异常时导致对数据库操作会产生异常。对于第一个,由于其本身并不是一个Exception,所以我们需要定义这个异常,来让调用者明白异常时因为entryID错了。

异常类:

public class NoSuchEntryException extends Exception
{
    public NoSuchEntryException(String entryID)
    {
        super("数据库中没有EntryID=" + entryID + "的数据!");
    }
}

改进后的finishEntry方法:

public void finishEntry(String entryID) throws NoSuchEntryException
{
    boolean result = false;
    Entry entry = entryDao.get(entryID);
    if(entry == null)
    {
        log.error("数据库中没有EntryID=" + entryID + "的数据!");
        throw new NoSuchEntryException(entryID);
    }
    entry.setStatus("FINISH");
    entryDao.saveOrUpdate(entry);
}

这样调用者在调用finishEntry是就可以捕获NoSuchEntryException来确定是EntryID传错了导致的异常了。

我们再看第二个。数据库异常会导致对数据库操作的逻辑报错。还好hibernate会统一抛出HibernateException,只要调用者捕获这个异常就可以了。

异常的生成和error日志的打印需要保证一个原则: 必须在异常发生的第一现场使用!以上面的方法为例,在获取不到entry时,马上打印error日志并生成异常抛出。如果方法里有异常抛出,需要判断该方法是否有处理这个异常的能力和必要性,如果没有,则直接继续向上抛,一直抛到有能力和必须处理这个异常的逻辑进行捕获和处理,而且最好要一个一个的捕获,而不是一股脑的catch一个Exception。

上面说到了什么时候用异常,以及异常应当如何使用。但是如果过度的使用异常会导致代码显得累赘,扩展性降低。当一个接口扩展了其逻辑时新加入了一个异常,那就需要修改接口来抛出异常,这就破坏了原有的接口,这是我们不愿离看到的,这就需要我们在设计时根据业务需要设计异常,而不是在写代码时发现和设计异常。

还有另外一种防止接口收到破坏的方法。仔细看看JDK中的异常部分可以发现,有一个RunTimeException,它是集成Exception的,这个RunTimeException是不强制catch的,定义异常时可以集成RunTimeException,这样就可以不破坏接口了。但是这有可能会让调用者遗漏这个异常的捕获,从而导致用户体验下降或其他的后果。

有人就说了,我们可以把所有的异常都定义成RunTimeException不就行了?当然不行。还是上面说的,这个异常可能会被遗忘,从而达不到异常的作用。那什么时候用Exception,什么时候用RunTimeException呢?这里给大家分享一个我的经验:因为外界原因产生的异常,一般使用Exception;相反的,因为内部原因或系统级严重错误,可以使用RunTimeException,如数据库连接错误、远程调用失败、参数配置问题等。

另外,想要使用RunTimeException最好将其转换成强制捕获的Exception,这样可以强制调用者去捕获这个异常进行处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值