`
vboy1988
  • 浏览: 8117 次
  • 性别: Icon_minigender_1
  • 来自: 成都
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

JAVA异常设计原则【转载】

阅读更多


异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。 
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 并发变成设计原则与模式第二版 PDF版本,下载即看

    Java并发编程:设计原则与模式(第二版)-3

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...

    高效的java异常处理框架高效的java异常处理框架高效的java异常处理框架

    Java 异常的基本概念和语法开始,讲述 Java 异常处理的基本知识,分析 Java 异常体系结构,对比 Spring 的异常处理框架,阐述异常处理的基本原则,并提出了自己处理一个大型应用系统异常的思想,并通过设计一个异常...

    java 基础异常 java 基础异常java 基础异常java 基础异常

    java 基础异常 java 基础异常java 基础异常java 基础异常java 基础异常 java 基础异常java 基础异常java 基础异常java 基础异常 java 基础异常java 基础异常java 基础异常java 基础异常 java 基础异常java 基础异常...

    Java并发编程:设计原则与模式(第二版)

    Java并发编程:设计原则与模式(第二版).pdf

    12.java异常的概念.zip

    12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常的概念.zip12.java异常...

    Java异常处理机制及应用.pdf

    Java 异常处理原则是指在处理异常时需要遵守的一些基本原则,如尽量少地抛出异常、合理地使用 finally 块、避免 catch-all 异常、使用多 catch 块等。 五、Java 异常处理机制的应用 Java 异常处理机制的应用非常...

    java API设计

    Java API设计是一个重要的主题,它关乎到软件开发的可维护性、可扩展性和易用性。API(Application Programming Interface)是一组预定义的函数、类、接口和常量,允许开发者通过调用这些元素来实现特定功能,而无需...

    Java创建用户异常类

    如果java提供的系统异常类型不能满足程序设计的需求,那么可以设计自己的异常类型。 从java异常类的结构层次可以看出,java类型的公共父类为Throwable.在程序运行中可能出现俩种问题:一种是由硬件系统或JVM导致的...

    Java程序设计习题集下载

    习题集内容覆盖面广,包括:Java言的基本常识、基本语法、面向对象的基本概念、数组、字符串、异常处理、文件和数据流、图形用户界面设计、小应用程序、线程、编程规范、网络程序设计、多媒体民图形学程序设计以及...

    Java程序设计 7 异常处理.pptx

    Java 程序设计之异常处理 在 Java 程序设计中,异常处理是非常重要的一部分。异常是指程序在执行过程中出现的错误或异常情况,例如文件不存在、网络连接中断、操作数超出预定范围等。在 Java 中,异常本身作为一个...

    java开发详细设计文档模板

    java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板java开发详细设计文档模板

    Java异常处理机制及应用研究.pdf

    Java 异常处理机制是 Java 程序设计的一大难点,也是使用 Java 进行软件开发时不容忽视的问题之一。是否进行异常处理直接关系到开发出的软件的稳定性和健壮性。对 Java 异常处理机制有一个全面的认识,深刻理解 Java...

    23.java自定义异常.zip

    23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java自定义异常.zip23.java...

    java课程设计 学籍管理系统 内含源代码 打包发布

    9. **设计原则与模式**:系统可能应用了面向对象设计原则(如单一职责原则、开闭原则等)和设计模式(如工厂模式、单例模式等),以提高代码的可读性和可维护性。 10. **文档编写**:一份完整的课程设计项目应包含...

    java程序设计教程课件

    第6章 Java图形设计 6.1 图形坐标 6.2 图形绘制 6.3 文本 6.4 属性设置 第7章 异常处理 7.1 异常基本概念介绍 7.2 异常的抛出和捕获 7.3 创建自己的异常类 第8章 输入输出 8.1 输入输出概述 8.2 各种输入输出流 ...

    Java程序与设计

    Java程序与设计是编程领域中的一个重要主题,涵盖了Java语言的基础概念、语法特性、面向对象编程原理以及软件设计原则等多个方面。在这个主题中,学习者将深入理解如何利用Java进行高效且可靠的程序开发。 首先,...

    java课程设计汽车修理管理系统

    Java课程设计:汽车修理管理系统 本项目是一款基于Java技术实现的汽车修理管理系统的课程设计,旨在让学生掌握Java编程语言在实际应用中的技能,特别是对于客户端/服务器(C/S)架构的理解与运用。在这个系统中,...

    Java有效处理异常的三个原则

    总的来说,Java异常处理的原则是提高程序的健壮性,确保在遇到错误时能够正确响应。通过具体明确地抛出和捕获异常,尽早发现和处理问题,以及延迟捕获以实现更精细的控制,我们可以创建出更稳定、易调试的Java应用...

Global site tag (gtag.js) - Google Analytics