在看一些项目源码中,突然发现了异常的处理也是一门比较大的学问,其实处处都有学问,只是你是否留意而已。。好了,少点啰嗦,关于异常的处理,je上有个很精彩的讨论,我花了快一个钟时间才看完了,确实值得一看,帖子的网址:http://www.iteye.com/topic/2038
当然我这里写的,只是一个纯属的建议,大都是看了网上的帖子后,总结的。都是些前辈的经验。。下面开始吧。
——————————————————————————————————————————————
Java异常有三类:错误,运行时异常,检查型异常。
官方的观点是
第 39 条:最好为异常条件使用异常。也就是说,最好不为控制流使用异常。
第 40 条:为可恢复的条件使用检查型异常,为编程错误使用运行时异常。
第 41 条:避免不必要的使用检查型异常。
第 43 条:抛出与抽象相适应的异常。(使处理异常更直观)
在异常的使用上,专家的观点是很不一样的
C#作者Anders根本就忽略检查型异常。
Bruce Eckel,声称在使用 Java 语言多年后,他已经得出这样的结论,认为检查型异常是一个错误 —— 一个应该被声明为失败的试验。
——————————————————————————————————————————————
缺点:
缺点1,代码中包含了过多的catch,使得代码不清晰
缺点2,有时候捕捉的异常没有什么实际意义
缺点3,不够清晰的错误指示。
缺点4,过深的异常层次。
缺点4,性能。
——————————————————————————————————————————————
Eckel 提倡将所有的异常都作为非检查型的,并且提供将检查型异常转变为非检查型异常的一个方法,同时保留当异常从栈向上扩散时捕获特定类型的异常的能力
Rod Johnson ,他采取一个不太激进的方法。他列举了异常的多个类别,并且为每个类别确定一个策略。一些异常本质上是次要的返回代码(它通常指示违反业务规则),而一些异常则是“发生某种可怕错误”(例如数据库连接失败)的变种。Johnson 提倡对于第一种类别的异常(可选的返回代码)使用检查型异常,而对于后者使用运行时异常。在“发生某种可怕错误”的类别中,其动机是简单地认识到没有调用者能够有效地处理该异常,因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小(并且最小化异常淹没的可能性)。
——————————————————————————————————————————————
解决1:谨慎的抛出检查型异常。或者你认为,你可以处理它。否则,包装为运行时异常。
解决2:如果遵守1,2不是问题
解决3:异常不跨层,否则必须捕捉或者包装。
比如持久层丢出的SalException,你或者丢弃/处理/包装(为运行时异常),或者重新包装为业务层异常。保持JEE层的独立和异常的清晰性。
包装底层异常,保持异常链。
解决4:如果符合1,4也不是问题。再次强调,能捕捉就捕捉。
解决5:减少异常使用,减少层次。
——————————————————————————————————————————————
在je里面讨论,在上面的网址可找到:
robin认为异常是流程控制的一部分——当然,考虑到性能问题,这个流程不应该是大概率流程——也就是异常流程
例如用户登录
User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException; try { UserManager.login(xx,xx); .... 用户登陆以后的主事件流代码 } catch (UserNotFoundException e){ ... 用户名称没有的事件处理,例如产生一个提示用户注册的页面 } catch (PasswordNotMatchException e){ .... 密码不对的事件处理,例如forward到重新登陆的页面 }
Potian则认为,没有用户是正常业务逻辑的一部分
If(!用户业务层.没有这个用户(用户名))错误提示界面;
If(用户业务层.检验密码(用户名,密码))登录成功;
else 登录失败;
Potian认为不应该在一个业务中包含了过多的责任。
muziq认为:
从做应用的角度看,我希望利用异常来管理显示在用户面前的错误提示,这样我就会将异常划分为两类,一类是可以显示给用户看的,或者说用户可以看的懂的,一类是给维护人员看的,就是通常说的系统错误,可以想象,让用户看到“空指针异常”是极不友好的行为。我们的项目中有一个专门显示错误的页面,它要么显示有具体业务含义的错误,要么就显示“系统错误”(然后维护人员可以从log中查找错误)。而前一类错误里面,每种错误总有一个不同的异常类和它对应,或者说,业务逻辑方法向表示逻辑抛出的异常,表示逻辑不需要分别处理(不要显示错误的除外),一律交给错误页面(其实是ErrorPageUIAction,我们用Struts),错误页面看是什么异常就显示什么错误提示,这里错误页面需要一个XML,XML里面定义了异常和错误提示的对应关系,当然XML里面只能有用户异常,不在XML里面的异常错误页面就会显示“系统错误”。业务逻辑事先将用户异常登记到XML中,这样错误信息还可以单独、统一的维护。
上面讲的是将异常展示到错误页面的方法,这其实只涉及到最后一层(Action)捕捉异常和业务逻辑层抛出异常的问题,而如何中间层内的异常如何抛、如何捕捉的问题考虑的思路一定是不一样的,不过,做应用系统的,业务逻辑层内部交叉引用的不多,所以这一块异常设计的矛盾并不突出。而如果你正在开发高度重用的组件甚至框架,异常就一定要好好设计一下了。
至于组件、框架的异常如何设计,我想只要去学JDK、Hibernate的那一套东西就没错了。能想到的就是,异常一定是调用者可以理解的,和方法的语义密切相关的。
——————————————————————————————————————————————
一些网上人的评论:
在servlet中,我喜欢仅仅简单的在action中调用最好一个业务层方法就可以完成此action的任务。这意味着我的servlet非常瘦,可以比较容易的被替换。如果采用了potian的办法,则意味着我要把业务层中的代码前移到servlet中来,这模糊了业务层的责任。解决的办法是回到老路子上来。
我还认为,没有异常的业务方法表达能力太弱,异常给了他们更丰富的表达能力。这使得业务层可以更丰富的表达业务意义。避免将业务责任分散掉。
———————————————————————————————————————————————
我的个人偏向:
我个人比较偏向je上muziq 的看法,项目的最终用户是网民,而大都不是计算机专业者,所以不管异常怎么处理,都要对用户有个友好的提示,尽力少,尽量简单。而对于程序设计者来讲又必须依据异常对程序进行修复处理。所以异常一般分为两类:一类是可以显示给用户看的,或者说用户可以看的懂的;一类是给维护人员看的。这是对于异常在程序表面上的处理。
而对于程序设计的底层(即代码的设计),异常尽量往高层抛出,让高层进行统一的处理,当然这是对于检查型异常而言。
如果一个异常是致命的,不可恢复的。或者调用者去捕获它没有任何益处,使用unChecked异常。
如果一个异常是可以恢复的,可以被调用者正确处理的,使用checked异常。
在使用unChecked异常时,必须在在方法声明中详细的说明该方法可能会抛出的unChekced异常。由调用者自己去决定是否捕获
相关的讨论比较好的帖子:http://www.iteye.com/topic/72170
文笔比较差,加上实际经验比较少,很难描述,还是看看代码比较容易懂,该实例是结合了ssh2:
Dao层:不抛出检查型异常,都由Hibernate包装成了运行期异常了
package dao; import dao.base.BaseDao; import model.User; public interface UserDao extends BaseDao<User,Integer>{ public void addUser(User user); public User getUser(String id); }
Dao 的实现层不写了,继承HibernateDaoSupport,由它实现,把异常都包装成运行期异常了。
service 层:抛出XXXService类对应的XXXException 异常
package service; import java.util.List; import exception.UserException; import bean.User; public interface UserManager { public void addUser(User user) throws UserException; public User getUser(String id) throws UserException; }
Service 实现层:抛出XXXService类对应的XXXException 异常
public class UserServiceImp implements UserService { static Logger log=Logger.getLogger( UserServiceImp.class.getName()); private UserDao userDao; @Override public User addUser(User user) throws UserException { try{ ........ }catch(Exception e){ //用日志记录错误信息 log.debug(e.getMessage()); //重新抛出新的异常,因为用户没必要知道详细的底层异常信息 throw new UserException("验证用户登陆出现错误,请重试"); } 。。。。setXXX()。。。。。 }
action 层:如果action 不处理UserException异常,则继续抛出,由拦截器拦截
public class AddUserAction extends BaseAction { private String name; private String password; public String execute() throws Exception{ //如果action 不处理UserException异常,则继续抛出,由拦截器拦截 } }
Struts2 配置文件:利用配置文件,把相应的异常转向相应的页面。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.i18n.encoding" value="GBK"></constant> <package name="struts" extends="struts-default"> <global-results> <result name="XXXException">/error.jsp</result> </global-results> <global-results> <result name="userException">/error.jsp</result> </global-results> <global-exception-mappings> <exception-mapping exception="exception.XXXException" result="XXXException" /> </global-exception-mappings> <global-exception-mappings> <exception-mapping exception="exception.UserException" result="userException" /> </global-exception-mappings> <action name="addUser" class="action.AddUserAction"> <result name="input">/WEB-INF/jsp/addUser.jsp</result> <result name="success">/WEB-INF/jsp/main.jsp</result> <result name="failure">/WEB-INF/jsp/addUser.jsp</result> </action> </package> </struts>