JAVA异常设计原则

异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。 
JAVA根据用处的不同,定义了两类异常 
    *  Checked Exception:  Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。 

    * Unchecked Exception: RuntimeException的子类,方法签名不需要声明throws,编译器也不会强制调用者处理该类异常。

异常设计的几个原则: 
1.如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。 
2.避免使用异常来指出可以视为方法的常用功能的情况。 
3.如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。 
4.如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。 
5.如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。 
6.异常类应该给客户提供丰富的信息,异常类跟其它类一样,允许定义自己的属性和方法。 
7.异常类名和方法遵循JAVA类名规范和方法名规范 
8.跟JAVA其它类一样,不要定义多余的方法和变量。(不会使用的变量,就不要定义,spring的BadSqlGrammarException.getSql() 就是多余的)
 

以下是我工作当中碰到的一些我认为不是很好的写法,我之前也犯过此类的错误 
A. 整个业务层只定义了一个异常类 

Java代码   收藏代码
  1. public class ServiceException extends RuntimeException {  
  2. private static final long serialVersionUID = 8670135969660230761L;  
  3.   
  4. public ServiceException(Exception e) {  
  5. super(e);  
  6. }  
  7.   
  8. public ServiceException(String message) {  
  9. super(message);  
  10. }  
  11. }  


理由: 
1.业务异常不应该是Unchecked Exception。 
2.不存在一个具体的异常类名称是“ServiceException”。 

解决方法:定义一个抽象的业务异常“ServiceException” 
Java代码   收藏代码
  1. public abstract class ServiceException extends Exception {  
  2. private static final long serialVersionUID = -8411541817140551506L;  
  3. }  


B. 忽略异常  
Java代码   收藏代码
  1. try {  
  2. new String(source.getBytes("UTF-8"), "GBK");  
  3. catch (UnsupportedEncodingException e) {  
  4. e.printStackTrace();  
  5. }  


理由: 
1.环境不支持UTF-8或者GBK很显然是一个非常严重的bug,不能置之不理 
2.堆栈的方式记录错误信息不合理,若产品环境是不记录标准输出,这个错误信息就会丢失掉。若产品环境是记录标准输出,万一这段程序被while循环的线程调用,有可能引起硬盘容量溢出,最终导致程序的运行不正常,甚至数据的丢失。 

解决方法:捕获UnsupportedEncodingException,封装成Unchecked Exception,往上抛,中断程序的执行。 
Java代码   收藏代码
  1. try {  
  2. new String(source.getBytes("UTF-8"), "GBK");  
  3. catch (UnsupportedEncodingException e) {  
  4. throw new IllegalStateException("the base runtime environment does not support 'UTF-8' or 'GBK'");  
  5. }  


C. 捕获顶层的异常—Exception  
Java代码   收藏代码
  1. public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {  
  2. final User outUser = userDao.getUser(outUid);  
  3. final User inUser = userDao.getUser(inUserUid);  
  4. outUser.decreaseCoin(coin);  
  5. inUser.increaseCoin(coin);  
  6. try {  
  7. // BEGIN TRANSACTION  
  8. userDao.save(outUser);  
  9. userDao.save(inUser);  
  10. // END TRANSACTION  
  11. // log transfer operate  
  12. catch (Exception e) {  
  13. throw new ServiceException(e);  
  14. }  
  15. }  

理由: 
1. Service并不是只能抛出业务异常,Service也可以抛出其他异常 
如IllegalArgumentException、ArrayIndexOutOfBoundsException或者spring框架的DataAccessException 
2. 多数情况下,Dao不会抛Checked Exception给Service,假如所有代码都非常规范,Service类里不应该出现try{}catch代码。 

解决方法:删除try{}catch代码 
Java代码   收藏代码
  1. public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {  
  2. final User outUser = userDao.getUser(outUid);  
  3. final User inUser = userDao.getUser(inUserUid);  
  4. outUser.decreaseCoin(coin);  
  5. inUser.increaseCoin(coin);  
  6. // BEGIN TRANSACTION  
  7. userDao.save(outUser);  
  8. userDao.save(inUser);  
  9. // END TRANSACTION  
  10. // log transfer operate  
  11. }  


D. 创建没有意义的异常  
Java代码   收藏代码
  1. public class DuplicateUsernameException extends Exception {  
  2. }  

理由 
1. 它除了有一个"意义明确"的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。 

解决方案:定义上捕获者需要用到的信息 
Java代码   收藏代码
  1. public class DuplicateUsernameException extends Exception {  
  2. private static final long serialVersionUID = -6113064394525919823L;  
  3. private String username = null;  
  4. private String[] availableNames = new String[0];  
  5.   
  6. public DuplicateUsernameException(String username) {  
  7. this.username = username;  
  8. }  
  9.   
  10. public DuplicateUsernameException(String username, String[] availableNames) {  
  11. this(username);  
  12. this.availableNames = availableNames;  
  13. }  
  14.   
  15. public String requestedUsername() {  
  16. return this.username;  
  17. }  
  18.   
  19. public String[] availableNames() {  
  20. return this.availableNames;  
  21. }  
  22. }  

E. 把展示给用户的信息直接放在异常信息里。  
Java代码   收藏代码
  1. public class CoinNotEnoughException2 extends Exception {  
  2. private static final long serialVersionUID = 4724424650547006411L;  
  3.   
  4. public CoinNotEnoughException2(String message) {  
  5. super(message);  
  6. }  
  7. }  
  8.   
  9. public void decreaseCoin(int forTransferCoin) throws CoinNotEnoughException2 {  
  10. if (this.coin < forTransferCoin)  
  11. throw new CoinNotEnoughException2("金币数量不够");  
  12. this.coin -= forTransferCoin;  
  13. }  


理由:展示给用户错误提示信息属于文案范畴,文案易变动,最好不要跟程序混淆一起。 
解决方法: 
错误提示的文案统一放在一个配置文件里,根据异常类型获取对应的错误提示信息,若需要支持国际化还可以提供多个语言的版本。 

F. 方法签名声明了多余的throws  
理由:代码不够精简,调用者不得不加上try{}catch代码 
解决方案:若方法不可能会抛出该异常,那就删除多余的throws 

G. 给每一个异常类都定义一个不会用到ErrorCode  
理由:多一个功能就多一个维护成本 
解决方法:不要无谓的定义ErrorCode,除非真的需要(如给别人提供接口调用的,这时,最好对异常进行规划和分类。如1xx代表金币相关的异常,2xx代表用户相关的异常… 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值