Java异常处理

简介: Java异常,本身知识体系很简单,但要设计好异常,却不是易事Java异常如何使用,尤其checked exception,好些语言(c#,python)都没有此类型异常,只有unchecked exception;对于java为什么有checked exception,是不是设计过渡,在java初期被讨论了很多回,以及如何使用异常也被讨论了很多次,最近我在落地DDD时,又思考到此问题,不得不再翻回这个老问题,翻阅《Effective java》、《J2EE设计开发编程指南》这些经典

Java异常,本身知识体系很简单,但要设计好异常,却不是易事

Java异常如何使用,尤其checked exception,好些语言(c#,python)都没有此类型异常,只有unchecked exception;对于java为什么有checked exception,是不是设计过渡,在java初期被讨论了很多回,以及如何使用异常也被讨论了很多次,最近我在落地DDD时,又思考到此问题,不得不再翻回这个老问题,翻阅《Effective java》、《J2EE设计开发编程指南》这些经典按普世标准,处理异常最佳实践有:

【强制】异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多异常应该只用于异常的情况下:它们永远不应该用于正常的控制流,设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常对可恢复情况使用受检异常,对编程错误使用运行时异常抛出与抽象相对应的异常每个方法抛出的异常都要有文档优先使用标准异常


再来看看前人的论述:

在使用UseCase来描述一个场景的时候,有一个主事件流和n个异常流。异常流可能发生在主事件流的过程,而try语句里面实现的是主事件流,而catch里面实现的是异常流,在这里Exception不代表程序出现了异常或者错误,Exception只是面向对象化的业务逻辑控制方法。如果没有明白这一点,那么我认为并没有真正明白应该怎么使用Java来正确的编程。

而我自己写的程序,会自定义大量的Exception类,所有这些Exception类都不意味着程序出现了异常或者错误,只是代表非主事件流的发生的,用来进行那些分支流程的流程控制的。例如你往权限系统中增加一个用户,应该定义1个异常类,UserExistedException,抛出这个异常不代表你插入动作失败,只说明你碰到一个分支流程,留待后面的catch中来处理这个分支流程。传统的程序员会写一个if else来处理,而一个合格的OOP程序员应该有意识的使用try catch 方式来区分主事件流和n个分支流程的处理,通过try catch,而不是if else来从代码上把不同的事件流隔离开来进行分别的代码撰写

很多人喜欢定义方法的返回类型为boolean型的,当方法正确执行,没有出错的时候返回true,而方法出现出现了问题,返回false。这在Java编程当中是大错而特错的!

方法的返回值只意味着当你的方法调用要返回业务逻辑的处理结果的。如果业务逻辑不带处理结果,那么就是void的,不要使用返回值boolean来代表方法是否正确执行。

boolean login(String username, String password);

很多人喜欢用boolean返回,如果是true,就是login了,如果false就是没有登陆上。其实是错误的。还有的人定义返回值为int型的,例如如果正确返回就是0,如果用户找不到就是-1,如果密码不对,就是-2

int login(String username, String password);

然后在主程序里面写一个if else来判断不同的流程

int logon = UserManager.login(xx,xx);;  
 if (logon ==0); {  
...  
} else if (logon == 1); {  
...  
} else if (logon ==2); {  
..}

这是面向过程的编程逻辑,不是面向对象的编程逻辑。

应该这样来写:

User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException;

主程序这样写:

try {    UserManager.login(xx,xx);;  ....   用户登陆以后的主事件流代码  
} catch (UserNotFoundException e); {  
...  
用户名称没有的事件处理,例如产生一个提示用户注册的页面  
} catch (PasswordNotMatchException e); {  
....  
密码不对的事件处理,例如forward到重新登陆的页面  }



看到这个示例,似乎明显违背了最佳实践的第一条:不要用来流程控制

如果这不是流程控制,那这种写法与流程控制有什么区别呢?再进一步,什么时候使用异常呢?

什么时候使用异常

在异常最佳实践中:异常只用于异常情况下!

需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的

什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?FileNotFoundException你说算是系统异常呢?还是程序异常?站在某些程序员的角度,他会觉得是系统异常,不过像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?对我来说,FileNotFoundException就是程序异常。既然JDK可以抛出异常,凭什么我就不能抛出异常?

站在底层程序员的角度来看,根本没有什么系统异常可言,否则的话,还不如不要定义任何异常得了,干脆就是函数调用返回值,你说为什么Sun不定义0,1,2这样的返回值,而是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?

异常不异常的界定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获业务层异常,你就应该定义业务层异常,向上抛出业务层异常。如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没有意义的

因为0,1,2这样的值表达的含义不够丰富,但是作为返回值,又不合理。————函数有它的本身的返回值。

因此,返回一个异常,其实就是一个封装完好的,返回的对象。这个对象Type不是在函数名的前面说明,而是在一个更加特别的地方,函数的后面说明。这就是异常的本质————非正常的返回值。这个返回值,为什么不能用传统的方法处理呢?因为Object x=method();表明它只能接受某一个特定的对象,如果出现Exception的对象,就会报错。因此需要catch来接手处理这样的返回值。

checked与unchecked选择

对于何时抛出异常,上面的论述大致已经清楚,使用Exception的关键是,你站在什么样的角度来看这个问题,这也得看大家对异常写法的习惯,异常并不只是单单的异常,在OO中,异常也是方法返回值的一部分

Java正统观点认为:已检查异常应该是标准用法,运行时异常表明编程错误,这也正如上面的例子,方法申明异常表明了有这些异常情况,那业务调用方需要考虑这些情况,但是检查异常引起了几个问题

1.太多的代码:开发人员将会因为不得不捕捉他们无法合理地处理的已检查异常(属于“某个东西出了可怕错误”种类)并编写忽略(忍受)它们的代码而感到无力。2.难以读懂的代码:捕捉不能被正确地处理的异常并重新抛出它们没有执行一点有用的功能,反而会使查找实际做某件事的代码变得更困难3.异常的无休止封装:一个已检查异常要么必须被捕捉,要么必须在一个遇到它的那个方法的抛出子句中被声明。这时要么重新抛出数量不断增长的异常,或者说捕捉低级异常,要么重新抛出被封装在一个较高级的新异常中的它们4.易毁坏的方法签名5.已检查异常对接口不一定管用

异常受检的本质并没有为程序员提供任何好处,它反而需要付出努力,还使程序更为复杂

被一个方法单独抛出的受检异常,会给程序员带 来非常高的额外负担。如果这个方法还有其他的受检异常,它被调用的时候一定已经出现在一个try块中,所以这个异常只需要别外一个catch块

非检查型异常的最大风险之一就是它并没有按照检查型异常采用的方式那样自我文档化。除非 API 的创建者明确地文档化将要抛出的异常,否则调用者没有办法知道在他们的代码中将要捕获的异常是什么

Rod Johnson采取了一种比eckel 稍正统的观点,因为Johnson认为已检查异常有一定用武之地,在一个异常相当于来自方法的一个可替代返回值得地方,这个异常无疑应该被检查,并且该语言能帮助实施这一点就再好不过了。但是觉得传统的java方法过分强调了已检查异常。


使用Checked Exception还是UnChecked Exception的原则,我的看法是根据需求而定。

如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;如果你不希望强制你的类调用者来处理异常,就用UnChecked。

那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。

还是拿那个用户登陆的例子来说,可能产生的异常有:

IOException (例如读取配置文件找不到) SQLException (例如连接数据库错误) ClassNotFoundException(找不到数据库驱动类)

NoSuchUserException PasswordNotMatchException

以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。

在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。

至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException;或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。


Checked Exception与UnChecked Exception:

1.抛出Checked Exception,给直接客户施加一个约束,必须处理,但也是一种自由,客户可分门别类的处理不同异常;UnChecked Exception则给直接客户以自由,但也是一种欺瞒,因为客户不知道将要发生什么,所有的处理将是系统默认的处理(如打印堆栈到控制台,对开发者、用户都返回一样的内容,不管别人懂与不懂)。二者的选择其实是约束与自由的权衡。2.“对可恢复的情况使用已检查异常,对程序错误使用运行时异常。”而不是一咕脑的全抛出Checker Exception,这服务提供者是友好的3.所以,若不需要客户依据不同异常采取不同后续行为,那么抛出UnChecked Exception是友好的;但若客户需要根据不同异常类采取不同行动,抛出Checked Exception是友好的。

对于checked exception转unchecked exception,大家都有共识,只是偏执于两方哪一方多些,在前期还是后期

Rod Johnson在spring的data access exception中就是个好示例,一是把异常细分化,更明确具体异常;二是把检查异常SQLException都转化为了unchecked exception

ErrorCode

异常对代码和开发、维护及管理一个应用的人都有用是至关重要的

对于开发、维护人

异常消息串具有有限的价值:当这些消息串出现在日志文件中时,他们对解释问题可能是有帮助的,但它们将无法使调用代码正确地做出反应,并且不能依靠它们本身来把它们显示给用户。当不同的问题可能需要不同的动作时,相应的异常应该被建模为一个公用超类的独立子类。有时,该超类应该是抽象的。现在,调用代码将可自由地在相关的细节级别上捕捉异常

已检查异常要比错误返回码(许多老式的语言中使用)好很多。迟早(或许不久),人们将不能检查一个错误返回值;

使用编译程序来实施正确的错误处理时一件好事。同参数和返回值一样,这样的已检查异常对一个对象的api来说是整体的不可分部分

用户

应该通过在异常中包括错误代码来处理

String getErrorCode();
String getMessage();

在spring早期代码中,就有ErrorCoded接口定义这两个方法,errorCode能够把为终端用户而计划的错误与为开发人员而计划的错误消息区分开。getMessage()用户记录日志,并且是面向开发人员

目录
相关文章
|
27天前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
16天前
|
安全 Java 程序员
Java中的异常处理:从新手到专家
在Java编程的世界里,异常处理是每个开发者必须面对的挑战。本文将带你从基础的异常概念出发,逐步深入到高级处理技巧,让你在遇到代码中的“意外”时,能够从容应对,甚至化险为夷。
|
21天前
|
Java 数据库连接 开发者
Java中的异常处理:从基础到高级
【10月更文挑战第42天】在Java的世界中,异常处理是维护程序稳定性和健壮性的关键。本文将带你深入了解Java的异常处理机制,从基本的try-catch语句出发,逐步探索更复杂的异常处理策略。我们将通过实际代码示例来演示如何捕获和处理异常,以及如何自定义异常类型来满足特定需求。无论你是Java新手还是有经验的开发者,这篇文章都将帮助你更好地理解和应用Java的异常处理。
|
27天前
|
Java 开发者
Java中的异常处理:从基础到高级
【10月更文挑战第35天】在Java的世界里,异常处理是维护程序健壮性的关键。本文将深入浅出地探讨Java的异常处理机制,从基本的try-catch语句到自定义异常类的实现,带领读者理解并掌握如何在Java中优雅地处理错误和异常。我们将通过实际代码示例,展示如何捕获、处理以及预防潜在的运行时错误,确保程序即使在面临意外情况时也能保持稳定运行。
37 7
|
27天前
|
Java 数据库连接 开发者
Java中的异常处理机制及其最佳实践####
在本文中,我们将探讨Java编程语言中的异常处理机制。通过深入分析try-catch语句、throws关键字以及自定义异常的创建与使用,我们旨在揭示如何有效地管理和响应程序运行中的错误和异常情况。此外,本文还将讨论一些最佳实践,以帮助开发者编写更加健壮和易于维护的代码。 ####
|
1月前
|
Java
Java 异常处理下篇:11 个异常处理最佳实践
本文深入探讨了 Java 异常处理的最佳实践,包括早抛出晚捕获、只捕获可处理的异常、不要忽略捕获的异常、抛出具体检查性异常、正确包装自定义异常、记录或抛出异常但不同时执行、避免在 `finally` 块中抛出异常、避免使用异常进行流程控制、使用模板方法处理重复的 `try-catch`、尽量只抛出与方法相关的异常以及异常处理后清理资源。通过遵循这些实践,可以提高代码的健壮性和可维护性。
|
1月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
49 2
|
1月前
|
Java 程序员 数据库连接
深入浅出Java异常处理
【10月更文挑战第30天】在Java的世界里,异常处理就像是生活中的急救箱,遇到意外时能及时救治。本文不仅教你如何使用try-catch语句包扎“伤口”,还会深入讲解如何通过自定义异常来应对那些常见的“头疼脑热”。准备好,我们将一起探索Java异常处理的奥秘,让你的程序更加健壮。
|
1月前
|
Java 程序员 数据库连接
Java中的异常处理:理解与实践
【10月更文挑战第29天】在Java编程的世界里,异常像是不请自来的客人,它们可能在任何时候闯入我们的程序宴会。了解如何妥善处理这些意外访客,不仅能够保持我们程序的优雅和稳健,还能确保它不会因为一个小小的失误而全盘崩溃。本文将通过浅显易懂的方式,带领读者深入异常处理的核心概念,并通过实际示例展现如何在Java代码中实现有效的异常管理策略。
|
1月前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
50 2