异常处理。
Java中相对过去的程序语言 异常是个很大的特点
它有什么好处呢:
而且java强制你进行异常处理。看来是我们java程序身在福中不知福啊。
对于java异常 基础知识早已知道,异常的处理,异常的结构。
天天使用,但是如何使用,使用的情况,真是不惨不忍睹。
今日早上又听到同事的讨论,在压力测试的时候,注册用户出现了主键冲突问题。
解释是很多用户一起注册的时候,当前一个用户在判断用户名是否已经使用的时候,另一用户进来了,并成功注册了同样的用户名,看似这就是一个线程并发的问题。
但这种现象我想在正常运行下发生的可能性太小了,看了很多系统,没有那个在注册的时候还加个同步吧。
而且在注册前是先会判断的,那么同事的讨论就涉及到了userManager.save方法的返回值问题,
同事A:这个方法要返回一个boolean,才能对其判断是否正确执行。
同事B:那应该抛个异常啊。
这两句话不由想起对异常的处理,怎么处理异常,怎么去设计好的异常结构。当然从上面两句,感觉同事A被面向过程语言害得惨啊。
如何设计异常,感觉真的是什么时候能理解异常啊,什么时候就能知道什么叫OO了。异常不仅是强制去写个更健壮的程序来,应该更多的是体现了OO思想
我设计的login总是使用自定义的异常来控制流程,我不同意在调用前检查环境比如if(!userExist())这样只会加重数据库的访问负担,另外,考虑register方法:
如果定义
if(!userExist(...)) {
if(register(...)==true) {
}
}
哈哈问题出来了:且不说访问了两次数据库,如果刚好检测了if,另一个线程了插入了相同的用户名怎么办?难道用synchronize同步?这样的话效率比使用异常还要大大的降低
使用Checked Exception还是UnChecked Exception的原则,我的看法是根据需求而定。
如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;
如果你不希望强制你的类调用者来处理异常,就用UnChecked。
那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。
还是拿那个用户登陆的例子来说,可能产生的异常有:
IOException (例如读取配置文件找不到)
SQLException (例如连接数据库错误)
ClassNotFoundException(找不到数据库驱动类)
NoSuchUserException
PasswordNotMatchException
以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。
在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。
至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为 RuntimeException;或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。
对于过去的一个帖子讨论异常使用http://www.iteye.com/topic/2038?page=1
而我自己写的程序,会自定义大量的Exception类,所有这些Exception类都不意味着程序出现了异常或者错误,只是代表非主事件流的发生的,用来进行那些分支流程的流程控制的。例如你往权限系统中增加一个用户,应该定义1个异常类,UserExistedException, 抛出这个异常不代表你插入动作失败,只说明你碰到一个分支流程,留待后面的catch中来处理这个分支流程 。传统的程序员会写一个if else来处理,而一个合格的OOP程序员应该有意识的使用try catch 方式来区分主事件流和n个分支流程的处理,通过try catch,而不是if else来从代码上把不同的事件流隔离开来进行分别的代码撰写。
方法的返回值只意味着当你的方法调用要返回业务逻辑的处理结果的。如果业务逻辑不带处理结果,那么就是void的,不要使用返回值boolean来代表方法是否正确执行。
例如 用户登陆方法
Java代码
1. boolean login(String username, String password);;
boolean login(String username, String password);;
很多人喜欢用boolean返回,如果是true,就是login了,如果false就是没有登陆上。其实是错误的。还有的人定义返回值为int型的,例如如果正确返回就是0,如果用户找不到就是-1,如果密码不对,就是-2
Java代码
1. int login(String username, String password);;
int login(String username, String password);;
然后在主程序里面写一个if else来判断不同的流程。
Java代码
1. int logon = UserManager.login(xx,xx);;
2. if (logon ==0); {
3. ...
4. } else if (logon == 1); {
5. ...
6. } else if (logon ==2); {
7. ..}
int logon = UserManager.login(xx,xx);;
if (logon ==0); {
...
} else if (logon == 1); {
...
} else if (logon ==2); {
..}
这是面向过程的编程逻辑,不是面向对象的编程逻辑。
应该这样来写:
Java代码
1. User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException;
User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException;
主程序这样来写:
Java代码
1. try {
2. UserManager.login(xx,xx);;
3. ....
4. 用户登陆以后的主事件流代码
5.
6. } catch (UserNotFoundException e); {
7.
8. ...
9.
10. 用户名称没有的事件处理,例如产生一个提示用户注册的页面
11.
12. } catch (PasswordNotMatchException e); {
13.
14. ....
15.
16. 密码不对的事件处理,例如forward到重新登陆的页面
17. }
对于上面的代码,一些人提出了疑问:
try{
...
}catch(UserexistException e){
}catch(PasswordisEmptyException e){
}catch(UserPasswordNotMatchException e){
}...
这样写与
if(){
}else if(){
}else if(){
}
有什么太多不同吗?而且效率较低。
主流之流的界定是另外一个问题,比如用户注册,一般来说,用户想注册的id一般来说都是被别人先注册了的。那么catch UserexistException的机会将会多于try中的主流的流程,这是一个极端的情况,我只是想说,主流和之流的界定是很模糊。
不想从效率来说使用异常就说效率低了,不用异常就很高。
对于系统性能的优化,并不是主观意识的,那得通过压力测试,找到性能热点再进行优化。
而且对于优化,很多年前 专家就提出三条经典优化规则:一不优化二还是不优化三确认你是专家再考虑是否优化
更重要的是:不辞劳苦地去优化并不见得就是好的效果,因为你优化的地方可能对于性能热点是个杯水车薪。
对于上面的疑问可以用这段来解释:
又把前面的帖子看了一遍,感觉是大家对程序控制流程有着不同的看法。在《Effective Java》(中文版)上说“ 异常只应该被用于不正常的条件,它们永远不应该被永远正常的控制流 ”。
我原先以为那个不是一个程序控制流程,所以我用
try {
User login(...);
} catch (e) {}
来替代
if (login(...)) {}
是正确的。
但实际上那是一个控制流程,只不过用boolean 来实现时变成了不正常的控制流程--不能返回更详细的出错信息。 异常不应该被用来控制正常的控制流,但可以用来替换不正常的控制流。
这个关键词攫取得不错。哈。。
对于使用checked exception还是runtimeexception
如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;
如果你不希望强制你的类调用者来处理异常,就用UnChecked。
那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。
还是拿那个用户登陆的例子来说,可能产生的异常有:
IOException (例如读取配置文件找不到)
SQLException (例如连接数据库错误)
ClassNotFoundException(找不到数据库驱动类)
NoSuchUserException
PasswordNotMatchException
以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。
在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。
至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为 RuntimeException;或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。
对于这个帖子的总结:
用设计糟糕的异常类层次来否定异常这个事物,是极度缺乏说服力的,就好像有人用菜刀砍了人,你不能否定这把菜刀一样。
这个帖子这么长了,该讨论的问题都讨论清楚了,总结也总结过n遍了,所以我早就没有兴趣再跟帖了。
实际上,这个讨论中隐含两个不断纠缠的话题:
1、checked ,还是unchecked异常?
2、用自定义的方法调用返回code,还是用异常来表达不期望各种的事件流
经过这么长的讨论,我认为结论已经非常清楚:
1、 应该适用unchecked异常,也就是runtimeexception,这样可以让无法处理异常的应用代码简单忽略它,让更上层次的代码来处理
2、 应该适用异常来表达不期望的各种事件流
事实上,你们去看一下现在Spring,Hibernate3都已经采用这种方式,特别是Spring的DataAccessException异常层次设计,是一个很好的例子。即使用RuntimeException来表达不期望的各种事件流。
对于异常处理,可以放到统一的地方进行处理,比如acegi就是,struts也是,可以看看
现在struts中的action中,只要抛出异常就可以,在配置文件中配置下,struts就来处理这些异常,跳转到相应页面,显示很友好的提示信息。