异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。
JAVA根据用处的不同,定义了两类异常
* Checked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。
理由:
1.业务异常不应该是Unchecked Exception。
2.不存在一个具体的异常类名称是“ServiceException”。
解决方法:定义一个抽象的业务异常“ServiceException”
B. 忽略异常
理由:
1.环境不支持UTF-8或者GBK很显然是一个非常严重的bug,不能置之不理
2.堆栈的方式记录错误信息不合理,若产品环境是不记录标准输出,这个错误信息就会丢失掉。若产品环境是记录标准输出,万一这段程序被while循环的线程调用,有可能引起硬盘容量溢出,最终导致程序的运行不正常,甚至数据的丢失。
解决方法:捕获UnsupportedEncodingException,封装成Unchecked Exception,往上抛,中断程序的执行。
C. 捕获顶层的异常—Exception
理由:
1. Service并不是只能抛出业务异常,Service也可以抛出其他异常
如IllegalArgumentException、ArrayIndexOutOfBoundsException或者spring框架的DataAccessException
2. 多数情况下,Dao不会抛Checked Exception给Service,假如所有代码都非常规范,Service类里不应该出现try{}catch代码。
解决方法:删除try{}catch代码
D. 创建没有意义的异常
理由
1. 它除了有一个"意义明确"的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。
解决方案:定义上捕获者需要用到的信息
E. 把展示给用户的信息直接放在异常信息里。
理由:展示给用户错误提示信息属于文案范畴,文案易变动,最好不要跟程序混淆一起。
解决方法:
错误提示的文案统一放在一个配置文件里,根据异常类型获取对应的错误提示信息,若需要支持国际化还可以提供多个语言的版本。
F. 方法签名声明了多余的throws
理由:代码不够精简,调用者不得不加上try{}catch代码
解决方案:若方法不可能会抛出该异常,那就删除多余的throws
G. 给每一个异常类都定义一个不会用到ErrorCode
理由:多一个功能就多一个维护成本
解决方法:不要无谓的定义ErrorCode,除非真的需要(如给别人提供接口调用的,这时,最好对异常进行规划和分类。如1xx代表金币相关的异常,2xx代表用户相关的异常…
JAVA根据用处的不同,定义了两类异常
* Checked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。
* Unchecked Exception: RuntimeException的子类,方法签名不需要声明throws,编译器也不会强制调用者处理该类异常。
异常设计的几个原则:
1.如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。
2.避免使用异常来指出可以视为方法的常用功能的情况。
3.如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。
4.如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。
5.如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。
6.异常类应该给客户提供丰富的信息,异常类跟其它类一样,允许定义自己的属性和方法。
7.异常类名和方法遵循JAVA类名规范和方法名规范
8.跟JAVA其它类一样,不要定义多余的方法和变量。(不会使用的变量,就不要定义,spring的BadSqlGrammarException.getSql() 就是多余的)
以下是我工作当中碰到的一些我认为不是很好的写法,我之前也犯过此类的错误
A. 整个业务层只定义了一个异常类
- public class ServiceException extends RuntimeException {
- private static final long serialVersionUID = 8670135969660230761L;
- public ServiceException(Exception e) {
- super(e);
- }
- public ServiceException(String message) {
- super(message);
- }
- }
理由:
1.业务异常不应该是Unchecked Exception。
2.不存在一个具体的异常类名称是“ServiceException”。
解决方法:定义一个抽象的业务异常“ServiceException”
- public abstract class ServiceException extends Exception {
- private static final long serialVersionUID = -8411541817140551506L;
- }
B. 忽略异常
- try {
- new String(source.getBytes("UTF-8"), "GBK");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
理由:
1.环境不支持UTF-8或者GBK很显然是一个非常严重的bug,不能置之不理
2.堆栈的方式记录错误信息不合理,若产品环境是不记录标准输出,这个错误信息就会丢失掉。若产品环境是记录标准输出,万一这段程序被while循环的线程调用,有可能引起硬盘容量溢出,最终导致程序的运行不正常,甚至数据的丢失。
解决方法:捕获UnsupportedEncodingException,封装成Unchecked Exception,往上抛,中断程序的执行。
- try {
- new String(source.getBytes("UTF-8"), "GBK");
- } catch (UnsupportedEncodingException e) {
- throw new IllegalStateException("the base runtime environment does not support 'UTF-8' or 'GBK'");
- }
C. 捕获顶层的异常—Exception
- public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {
- final User outUser = userDao.getUser(outUid);
- final User inUser = userDao.getUser(inUserUid);
- outUser.decreaseCoin(coin);
- inUser.increaseCoin(coin);
- try {
- // BEGIN TRANSACTION
- userDao.save(outUser);
- userDao.save(inUser);
- // END TRANSACTION
- // log transfer operate
- } catch (Exception e) {
- throw new ServiceException(e);
- }
- }
理由:
1. Service并不是只能抛出业务异常,Service也可以抛出其他异常
如IllegalArgumentException、ArrayIndexOutOfBoundsException或者spring框架的DataAccessException
2. 多数情况下,Dao不会抛Checked Exception给Service,假如所有代码都非常规范,Service类里不应该出现try{}catch代码。
解决方法:删除try{}catch代码
- public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {
- final User outUser = userDao.getUser(outUid);
- final User inUser = userDao.getUser(inUserUid);
- outUser.decreaseCoin(coin);
- inUser.increaseCoin(coin);
- // BEGIN TRANSACTION
- userDao.save(outUser);
- userDao.save(inUser);
- // END TRANSACTION
- // log transfer operate
- }
D. 创建没有意义的异常
- public class DuplicateUsernameException extends Exception {
- }
理由
1. 它除了有一个"意义明确"的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。
解决方案:定义上捕获者需要用到的信息
- public class DuplicateUsernameException extends Exception {
- private static final long serialVersionUID = -6113064394525919823L;
- private String username = null;
- private String[] availableNames = new String[0];
- public DuplicateUsernameException(String username) {
- this.username = username;
- }
- public DuplicateUsernameException(String username, String[] availableNames) {
- this(username);
- this.availableNames = availableNames;
- }
- public String requestedUsername() {
- return this.username;
- }
- public String[] availableNames() {
- return this.availableNames;
- }
- }
E. 把展示给用户的信息直接放在异常信息里。
- public class CoinNotEnoughException2 extends Exception {
- private static final long serialVersionUID = 4724424650547006411L;
- public CoinNotEnoughException2(String message) {
- super(message);
- }
- }
- public void decreaseCoin(int forTransferCoin) throws CoinNotEnoughException2 {
- if (this.coin < forTransferCoin)
- throw new CoinNotEnoughException2("金币数量不够");
- this.coin -= forTransferCoin;
- }
理由:展示给用户错误提示信息属于文案范畴,文案易变动,最好不要跟程序混淆一起。
解决方法:
错误提示的文案统一放在一个配置文件里,根据异常类型获取对应的错误提示信息,若需要支持国际化还可以提供多个语言的版本。
F. 方法签名声明了多余的throws
理由:代码不够精简,调用者不得不加上try{}catch代码
解决方案:若方法不可能会抛出该异常,那就删除多余的throws
G. 给每一个异常类都定义一个不会用到ErrorCode
理由:多一个功能就多一个维护成本
解决方法:不要无谓的定义ErrorCode,除非真的需要(如给别人提供接口调用的,这时,最好对异常进行规划和分类。如1xx代表金币相关的异常,2xx代表用户相关的异常…