`
vboy1988
  • 浏览: 8020 次
  • 性别: 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异常的三个原则——具体明确异常、提早抛出异常和延迟捕获异常,对于创建健壮、易于调试的Java应用程序至关重要。通过合理使用异常类型、异常堆栈跟踪以及用户友好的错误处理提示,我们能够构建出...

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

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

    Java异常处理的设计原则.pdf

    以下是基于给定内容总结的Java异常处理的设计原则: 1. **早处理**:异常不应该用于控制正常程序流程,而应该仅用于处理非预期的情况。一旦发生异常,应尽早捕获并处理,避免异常向上冒泡到程序的更高层次。如果不...

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

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

    java 全局异常java 全局异常java 全局异常java 全局异常

    java 全局异常java 全局异常java 全局异常java 全局异常 java 全局异常java 全局异常java 全局异常java 全局异常 java 全局异常java 全局异常java 全局异常java 全局异常 java 全局异常java 全局异常java 全局异常...

    Java程序设计异常处理实例

    Java程序设计中的异常处理是编程过程中至关重要的一环,它确保了程序在遇到错误或异常情况时能够优雅地处理问题,而不是突然崩溃。异常处理的概念基于Java的异常类层次结构,这是Java语言的一个特色,旨在提高代码的...

    论文研究-基于Java异常处理机制的研究 .pdf

    Java异常处理机制研究的知识点涵盖了异常处理的基本概念、分类、原则以及实际应用等方面。 1. 异常处理概念 异常处理是Java语言中用于处理程序运行时遇到的错误和异常情况的一种机制。它通过异常类的层次结构来实现...

    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 异常处理机制的应用研究,包括 Java 异常体系统结构、异常分类与处理机制、异常处理的一般原则和异常处理框架等。 Java 异常体系统结构 Java 异常体系统结构如图 1 所示,Throwable 是所有...

    Java创建用户异常类

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

    java 架构设计示例文档

    Java中,接口设计通常会使用RESTful API设计原则或者GraphQL等技术。 6. 安全策略:在设计阶段就需要考虑系统的安全策略,包括数据的加密、认证授权机制、防止SQL注入等常见的攻击手段。在Java应用中,安全策略会...

    利用java filter 实现业务异常拦截源码

    利用java filter 实现业务异常拦截 跳转到错误信息提示页面 我们在做项目中肯定都会遇到自定义业务异常 ,然后将业务异常信息跳转的统一的信息提示页面的情况,比如我们在struts的时候我们会用到struts的异常处理...

    java开发详细设计文档模板

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

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

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

    Java课程设计(代码+清单)

    MakeJPEG.java 4.5. 课程设计作业 第5章 标准化考试系统 (单机版) 5.1. 设计内容 5.2. 设计要求 5.3. 总体设计 5.4. 具体设计 5.4.1. 运行效果与程序发布 5.4.2. 主类EnglishTest 5.4.3....

    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程序员指导chatgpt生成的Java设计原则和设计模式学习文档

    Java异常处理.pdf

    Java 异常处理是 Java 程序设计中一个非常重要的机制,也是程序设计的一大难点。Java 语言通过引入异常处理机制来解决程序中的意外情况。下面是 Java 异常处理的相关知识点: 1. 异常的概念 异常是指在程序运行...

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

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

Global site tag (gtag.js) - Google Analytics