文章来源:http://www.oecp.cn/hi/yongtree/blog/2099
作者: 谭明智
异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。
JAVA根据用处的不同,定义两类异常
* Checked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。
* Unchecked Exception: RuntimeException的子类,方法签名不需要声明throws,编译器也不会强制调用者处理该类异常。
异常的作用和好处:
1. 分离错误代码和正常代码,代码更简洁。
2. 保护数据的正确性和完整性,程序更严谨。
3. 便于调试和排错,软件更好维护。
……
相信很多JAVA开发人员都看到或听到过“不要使用异常来控制流程”,虽然这句话非常易于记忆,但是它并未给出“流程”的定义,所以很难理解作者的本意,让人迷惑不解。
如果“流程”是包括程序的每一步执行,那异常就是用来控制流程的,它就是用来区分程序的正常流程和错误流程,为了更能明确的表达意思,上面这句话应改成 “不要用异常来控制程序的正常流程”。现在带来一个新的问题:如何区分程序正常流程和异常流程?我实在想不出一个评判标准,就举例来说明,大家思维扩散 下。
为了后面更方便的表达,我把异常分成两类,不妥之处请谅解
* 系统异常: 软件的缺陷,客户端对此类异常是无能为力的,通常都是Unchecked Exception。
* 业务异常: 用户未按正常流程操作导致的异常,都是Checked Exception
一个金币转账的例子:需求规定金币一次的转账范围是1~500,如果超过这个额度,就要提示用户金额超出单笔转账的限制。
现在有以下几种场景:
1. 转账的金额是由用户在页面随意输入的:
因为值是用户随意输入的,所以给的值超出限定的范围肯定是司空见惯。我们当然不能把它(输入的值超出限定的范围)归结于异常流程,它应该属于正常流程。
正确的实现如下:
提供一个判断转账金币数量是否超出限定范围的方法
private static final int MAX_PRE_TRANSFER_COIN = 500; public boolean isCoinExceedTransferLimits(int coin) { return coin > MAX_PRE_TRANSFER_COIN; }
Action里先对值进行判断,若不合法,直接返回并提示用户
2. 转账的额度是页面单选框(100、200、300、400、500)选择的:
转账的额度用户不能轻易的更改,但是不排除有些无聊的人会借助其他工具(如,firebug)来修改。此类事件还是占少数的,我们就可以把它归结为非软件bug的异常事件—业务异常(Checked Exception)。
正确的实现如下
CoinExceedTransferLimitExcetion.java
//金币超出限定范围的异常类 public class CoinExceedTransferLimitExcetion extends Exception { private static final long serialVersionUID = -7867713004171563795L; private int coin; public CoinExceedTransferLimitExcetion() { } public CoinExceedTransferLimitExcetion(int coin) { this.coin = coin; } public int getCoin() { return coin; } @Override public String getMessage() { return coin + " is exceed transfer limit:500"; } }
//转账方法
private static final int MAX_PRE_TRANSFER_COIN = 500; public void transferCoin(int coin) throws CoinExceedTransferLimitExcetion { if (coin > MAX_PRE_TRANSFER_COIN) throw new CoinExceedTransferLimitExcetion(coin); // do transfering coin }
3. 接口transferCoin(int coin)的规范里已经定了契约 ,调用transferCoin之前必须要先调用isCoinExceedTransferLimits判断值是否合法:
虽然规范人人都要遵循,但毕竟只是规范,编译器无法强制约束。此时就需要用系统异常(Unchecked Exception)来保证程序的正确性,没遵守规范的就当做软件bug处理。
正确的实现如下:
public class CoinExceedTransferLimitExcetion extends RuntimeException { private static final long serialVersionUID = -7867713004171563795L; public CoinExceedTransferLimitExcetion() { } public CoinExceedTransferLimitExcetion(int coin) { super(coin + " is exceed transfer limit:500"); } }
//转账方法
public void transferCoin(int coin){ if (coin > MAX_PRE_TRANSFER_COIN) throw new CoinExceedTransferLimitExcetion(coin); // do transfering coin }
public String execute() { try { userService.transferCoin(coin); } catch (CoinExceedTransferLimitExcetion e) { e.getCoin(); } return SUCCESS; }
public String execute() { try { userService.transferCoin(coin); } catch (RuntimeException e) { LOG.error(e.getMessage()); } return SUCCESS; }
public class BadSqlGrammarException extends InvalidDataAccessResourceUsageException { private String sql; public BadSqlGrammarException(String task, String sql, SQLException ex) { super(task + "; bad SQL grammar [" + sql + "]", ex); this.sql = sql; } public SQLException getSQLException() { return (SQLException) getCause(); } public String getSql() { return this.sql; } }
public class HttpInvokeException extends RuntimeException { private static final long serialVersionUID = -6477873547070785173L; public HttpInvokeException(String url, String message) { super("http interface unavailable [" + url + "];" + message); } }
public class ServiceException extends RuntimeException { private static final long serialVersionUID = 8670135969660230761L; public ServiceException(Exception e) { super(e); } public ServiceException(String message) { super(message); } }
public abstract class ServiceException extends Exception { private static final long serialVersionUID = -8411541817140551506L; }
try { new String(source.getBytes("UTF-8"), "GBK"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
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'"); }
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); } }
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 }
public class DuplicateUsernameException extends Exception { }
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; } }
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; }