关于java异常使用的一些思考

一直被异常问题困扰。写一下自己的一些看法。很多地方牵扯到一些java开发常用的框架。

spring框架将所有的sql或者Hibernate异常转换成了自己的unchecked异常(DataAccessException),这种异常既可以捕捉也可以不捕捉。这样就有了一个问题,在我们的代码中捕捉这些异常呢,还是不捕捉。我认为应该捕捉,因为如果不捕捉,那么就忽略了程序发生的错误,并且如果我们不捕捉,那么那些unchecked异常最终也会被java虚拟机捕捉,到时候将导致程序不能运行。由此看来,要捕捉,可是要怎么捕捉,在哪里捕捉呢?我想这个问题才是比较关键的问题,并且答案不是唯一的。首先,这些异常是来自jdbc的sql异常,或者来自Hibernate的异常。可以简单的把它们看做是由数据源产生的异常。这类异常是很难处理的。

我们的程序一般都是分层设计,自上而下为:顶层(笼统的代表处理页面的层,该层调用service层)、service层、dao层。dao层负责跟最底层的数据源(jdbc或者Hibernate)交互。那来自于数据源的异常我们应该在哪一层捕捉并且处理(处理可能指直接处理,也可能指转换成其它异常类型)?这是个比较难以权衡的问题。如果我们在比较低的层次(比如dao层)进行捕捉并处理,那么怎么处理,处理的结果又如何反应到应用程序当中?

以一个简单的例子来看,用户注册:

先来看用户注册的流程:
用户填写用户信息->用户提交信息->服务端(假设是一个action)接收用户信息->服务端调用service->service调用dao->dao使用数据源完成用户信息持久化。这是一个理想的流程,但是在这个流程中可能会发生一些分支,比如用户名已经被占用,密码不合规范,用户信息不完整等。为了当发生这些分支时程序仍然能够正常处理,我们可以在用户提交信息和服务端接收信息直接加一个用户信息验证的过程。该过程可以处理密码不合规范、用户习信息不完整等问题。可以将该过程看做一个静态验证过程。
对于用户名被占用的问题,可能有以下两种实现:

1、服务端接收信息后先service检查用户名是否已经存在,比如service上有个返回布尔型的isUsrNameExist()方法。若存在,action做相应处理,最终反映在返回页面上。
2、服务端接收信息后直接调用用service上的方法进行注册,比如service上有一个返回void的reg(User u)方法,该方法将抛出UserNameExistsException异常。action调用service的reg方法时要捕捉并处理该异常,并根据异常反映相应结果到页面上。

我们可把上面的情况看做一个注册用例的逻辑上可能发生的情况,称为可预期异常。但是还有一种因为数据库原因或程序代码原因产生的异常。这类异常不是可预期的,一旦产生,很难处理。spring里的DataAccessException异常就属于这类异常。

难处理并不代表可以不处理,虽然java的机制并没有强制要求我们处理这类异常。我认为应该让这类异常自动向上抛出,直到在某一个层对它们进行统一处理。对于webapp,那么如果我们的代码没有处理这类异常,那么servlet容器(比如Tomcat)最终会处理。那如果我们的代码在运行时抛出了这类异常,我们是不是就不用管它们,完全交给Tomcat来处理呢?理论上可以,但是Tomcat的异常处理页面不够友好。不可取。Tomcat允许我们自定义错误处理界面,这样我们就可以提供一个相对友好的异常界面。但是我们仍然不能直接将异常叫个Tomcat来处理,因为程序中抛出的异常信息不友好,由于跨越了好几个层,原始异常中携带的信息对我们定位异常或者根据异常做相应处理提供的帮助不大。因此我们可以在每一个层对unchecked异常进行包装,将其转换成自定义的异常类型以包含更多信息。

综上,我们自定义一个异常体系:
受查异常基类:AppException extend Exception
不受查异常:AppRunTimeException extend RunTimeException
受查异常参与业务逻辑,反映可预期的业务逻辑分支和用例。主要出现在service层,供service层或者action层捕捉并处理。不受查异常不参与业务逻辑,可在任意层抛出,根据情况捕捉,并在包装后继续向上抛出,最终由Tomcat自定义的错误界面统一处理。

异常还是返回值?
在用例和业务逻辑中,出现可预期的分支是很常见的。如何处理分支呢?

使用返回值:这应该是比较传统的做法。比如我们有login,reg等业务方法,可以给方法一个返回值,然后在action里或者service里根据调用方法得到的返回值做判断。这种做法易于理解,更接近于过程化得思维。

使用受查异常:客户代码调用service提供的方法,service方法执行过程中抛出异常(受查异常,这样做是为了强制客户代码处理流程分支),客户代码对捕捉到的异常进行处理,转而执行相应分支。这种做法比较容易用面向对象的思维来理解,但是必将带来庞大的业务相关的自定义异常体系。

到底两者孰优孰劣?
我认为对于简单的业务逻辑少的系统,使用前者更好。理由是:不用去维护一个异常体系模型。由于系统简单,使用返回值的方法就足以轻松应对多数需求。对于业务逻辑复杂庞大的系统,使用后者更好。理由是:业务逻辑复制的系统,其可能的分支情况将更多,通过业务方法返回值的方式来应对这样一种情况显得比较吃力。而如果使用一个异常体系,由于异常体系本身具有一定的含义,程序能更好得从程序设计文档(比如UML设计)过度而来,更易于理解和维护。具体怎么用,还是要根据系统的具体情况,我觉得甚至都可以混合使用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值