异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。
JAVA根据用处的不同,定义两类异常
* Unchecked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。
* Checked 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
}
至此,举例已经结束了,在这里再延伸下—业务异常和系统异常类在实现上的区别,该区别的根源来自于调用者捕获到异常后是如何处理的
Action对业务异常的处理:操作具体的异常类
public String execute() {
try {
userService.transferCoin(coin);
} catch (CoinExceedTransferLimitExcetion e) {
e.getCoin();
}
return SUCCESS;
}
Action对系统异常的处理:无法知道具体的异常类
public String execute() {
try {
userService.transferCoin(coin);
} catch (RuntimeException e) {
LOG.error(e.getMessage());
}
return SUCCESS;
}
调用者捕获业务异常(Checked Excetion)之后,通常不会去调用getMessage()方法的,而是调用异常类里特有的方法,所以业务异常类的实现要注重特有的,跟业务相关的方法,而不是getMessage()方法。
系统异常类恰恰相反,捕获者只会调用getMessage()获取异常信息,然后记录错误日志,所以系统异常类(Uncheck Exception)的实现类对getMessage()方法返回内容比较讲究。
不管是业务异常还是系统异常,都需要提供丰富的信息。如:数据库访问异常,需要提供查询sql语句等;HTTP接口调用异常,需要给出访问的URL和参数列表(如果是post请求,参数列表不提供也可以,取决于维护人员或者开发人员拿到参数列表会如何去使用)。
1. Sql语法错误异常类(取自spring框架的异常类,Spring的异常体系很强大,值得一看):
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;
}
}
2. HTTP接口调用异常类
public class HttpInvokeException extends RuntimeException {
private static final long serialVersionUID = -6477873547070785173L;
public HttpInvokeException(String url, String message) {
super("http interface unavailable [" + url + "];" + message);
}
}
如何选择用Unchecked Exception和Checked Exception
1.是软件bug还是业务异常,软件bug是Unchecked Exception,否则是Checked Exception
2.如果把该异常抛给用户,用户能否做出补救。如果客户端无能为力,则用Unchecked Exception,否则抛Checked Exception
结合这两点,两类异常就不会混淆使用了。
异常设计的几个原则:
如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。
避免使用异常来指出可以视为方法的常用功能的情况。
如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。
如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。
如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。
异常类应该给客户提供丰富的信息,异常类跟其它类一样,允许定义自己的属性和方法。
异常类名和方法遵循JAVA类名规范和方法名规范
跟JAVA其它类一样,不要定义多余的方法和变量,不会使用的变量,就不要定义。我觉得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代表用户相关的异常…
最后推荐几篇关于异常设计原则
1.异常设计
http://www.cnblogs.com/JavaVillage/articles/384483.html(翻译)
http://www.javaworld.com/jw-07-1998/jw-07-techniques.html(原文)
2. 异常处理最佳实践
http://tech.e800.com.cn/articles/2009/79/1247105040929_1.html(翻译)
http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html(原文)
原文地址:http://www.iteye.com/topic/857443
分享到:
相关推荐
Java 异常处理是 Java 语言的一大特色,该文在介绍 Java 语言的异常处理技术的基础上,提出了异常处理的设计原则,从而为 Java 的程序设计实现提供技术上的借鉴。 Java 异常处理机制的主要方法是利用面向对象技术,...
java 并发变成设计原则与模式第二版 PDF版本,下载即看
以下是基于给定内容总结的Java异常处理的设计原则: 1. **早处理**:异常不应该用于控制正常程序流程,而应该仅用于处理非预期的情况。一旦发生异常,应尽早捕获并处理,避免异常向上冒泡到程序的更高层次。如果不...
本文将详细介绍 Java 异常处理机制的应用研究,包括 Java 异常体系统结构、异常分类与处理机制、异常处理的一般原则和异常处理框架等。 Java 异常体系统结构 Java 异常体系统结构如图 1 所示,Throwable 是所有...
Java 异常处理原则是指在处理异常时需要遵守的一些基本原则,如尽量少地抛出异常、合理地使用 finally 块、避免 catch-all 异常、使用多 catch 块等。 五、Java 异常处理机制的应用 Java 异常处理机制的应用非常...
Java API设计是一个重要的主题,它关乎到软件开发的可维护性、可扩展性和易用性。API(Application Programming Interface)是一组预定义的函数、类、接口和常量,允许开发者通过调用这些元素来实现特定功能,而无需...
Java并发编程-设计原则与模式 pdf格式
习题集内容覆盖面广,包括:Java言的基本常识、基本语法、面向对象的基本概念、数组、字符串、异常处理、文件和数据流、图形用户界面设计、小应用程序、线程、编程规范、网络程序设计、多媒体民图形学程序设计以及...
Java 程序设计之异常处理 在 Java 程序设计中,异常处理是非常重要的一部分。异常是指程序在执行过程中出现的错误或异常情况,例如文件不存在、网络连接中断、操作数超出预定范围等。在 Java 中,异常本身作为一个...
java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板
9. **设计原则与模式**:系统可能应用了面向对象设计原则(如单一职责原则、开闭原则等)和设计模式(如工厂模式、单例模式等),以提高代码的可读性和可维护性。 10. **文档编写**:一份完整的课程设计项目应包含...
Java程序设计教程第七版是Java学习者的重要参考资料,其课后习题答案对于巩固理论知识、提高编程技能具有极大帮助。本教程涵盖了Java语言的基础到高级特性,包括但不限于语法、面向对象编程、异常处理、集合框架、多...
第6章 Java图形设计 6.1 图形坐标 6.2 图形绘制 6.3 文本 6.4 属性设置 第7章 异常处理 7.1 异常基本概念介绍 7.2 异常的抛出和捕获 7.3 创建自己的异常类 第8章 输入输出 8.1 输入输出概述 8.2 各种输入输出流 ...
MakeJPEG.java 4.5. 课程设计作业 第5章 标准化考试系统 (单机版) 5.1. 设计内容 5.2. 设计要求 5.3. 总体设计 5.4. 具体设计 5.4.1. 运行效果与程序发布 5.4.2. 主类EnglishTest 5.4.3....
Java课程设计:汽车修理管理系统 本项目是一款基于Java技术实现的汽车修理管理系统的课程设计,旨在让学生掌握Java编程语言在实际应用中的技能,特别是对于客户端/服务器(C/S)架构的理解与运用。在这个系统中,...
总的来说,Java异常处理的原则是提高程序的健壮性,确保在遇到错误时能够正确响应。通过具体明确地抛出和捕获异常,尽早发现和处理问题,以及延迟捕获以实现更精细的控制,我们可以创建出更稳定、易调试的Java应用...
Java语言程序设计实验指导书 理论教材:《Java程序设计标准教程》 第1版 邱加永 人民邮电出版社 实验学时:26 要求 1 Java运行环境 通过在不同环境编译运行一个Java编程环境,认识Java程序结构。 2 Java语言基本...
20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多异常捕获.zip20.java多...
《Java程序设计实用教程第三版》是一本专为初学者和有一定基础的程序员设计的Java学习教材。本书全面覆盖了Java编程的基础知识,包括语法、类与对象、接口与继承、异常处理、多线程、网络编程、数据库操作等多个方面...