我一直在决定如何在我的应用程序中处理异常.
如果我的异常问题来自1)通过远程服务访问数据或2)反序列化JSON对象.不幸的是,我不能保证这些任务中的任何一个都成功(切断网络连接,不正确的JSON对象,这是我无法控制的).
因此,如果我遇到异常,我只需在函数内捕获它并返回FALSE给调用者.我的逻辑是,所有调用者真正关心的是任务是否成功,而不是为什么它不成功.
这是典型方法的一些示例代码(在JAVA中)
public boolean doSomething(Object p_somthingToDoOn) { boolean result = false; try{ // if dirty object then clean doactualStuffOnObject(p_jsonObject); //assume success (no exception thrown) result = true; } catch(Exception Ex) { //don't care about exceptions Ex.printStackTrace(); } return result; }
我认为这种方法很好,但我真的很想知道管理异常的最佳实践是什么(我应该在调用堆栈中一直冒出异常吗?).
总结关键问题:
是否可以捕获异常但不会冒泡或正式通知系统(通过日志或通知用户)?
对于不会导致所有需要try/catch块的异常的最佳实践有哪些?
跟进/编辑
感谢所有反馈,在网上找到了一些关于异常管理的优秀来源:
异常处理的最佳实践| 奥莱利媒体
.NET中的异常处理最佳实践
最佳实践:异常管理(文章现在指向archive.org副本)
异常处理反模式
似乎异常管理是根据上下文而变化的事情之一.但最重要的是,人们应该如何管理系统中的异常.
另外注意通过过多的尝试/捕获代码腐烂或不给予例外它的尊重(例外是警告系统,还需要警告什么?).
此外,这是m3rLinEz的一个很好的选择评论.
我倾向于同意Anders Hejlsberg和你的看法,大多数来电者只关心操作是否成功.
从这个评论中,它提出了一些在处理异常时要考虑的问题:
抛出此异常有什么意义?
处理它有什么意义?
呼叫者是否真的关心异常,还是只关心呼叫是否成功?
是否强制调用者管理潜在的异常优雅?
你是否尊重这种语言的含义?
你真的需要返回像布尔这样的成功标志吗?返回boolean(或int)更像是一种C心态而不是Java(在Java中你只是处理异常).
遵循与语言相关的错误管理结构:)!
Brian Rasmus.. 61
对我来说,想要捕获异常并将它们转换为错误代码似乎很奇怪.为什么在后者是Java和C#中的默认值时,您认为调用者更喜欢错误代码而不是异常?
至于你的问题:
您应该只捕获实际可以处理的异常.在大多数情况下,捕获异常并不是正确的做法.有一些例外(例如,线程之间的日志记录和编组异常),但即使对于这些情况,通常也应该重新抛出异常.
你的代码中绝对不应该有很多try/catch语句.同样,我们的想法是只捕获您可以处理的异常.您可以包含一个最顶层的异常处理程序,将任何未处理的异常转换为对最终用户有用的东西,但是否则您不应该尝试捕获每个可能位置的每个异常.
JoshBerke.. 25
这取决于应用和情况.如果你构建一个库组件,你应该冒出异常,尽管它们应该被包装成与你的组件一起上下文.例如,如果您构建一个Xml数据库并假设您正在使用文件系统来存储数据,并且您正在使用文件系统权限来保护数据.您不希望冒出一个FileIOAccessDenied异常,因为它会泄漏您的实现.相反,您将包装异常并抛出AccessDenied错误.如果您将组件分发给第三方,则尤其如此.
至于是否可以吞下例外.这取决于你的系统.如果您的应用程序可以处理故障情况,并且通知用户失败的原因没有任何好处,那么请继续,尽管我强烈建议您记录失败.我总是觉得很难打电话来帮助解决问题并发现他们正在吞下异常(或者替换它并抛出一个新的而不设置内部异常).
一般来说,我使用以下规则:
在我的组件和库中,如果我打算处理它或基于它做一些事情,我只会捕获异常.或者,如果我想在异常中提供其他上下文信息.
我在应用程序入口点或尽可能高的级别使用常规try catch.如果异常到达此处,我只需记录它并让它失败.理想情况下,异常永远不会到达此处
我发现以下代码是一种气味:
try { //do something } catch(Exception) { throw; }
像这样的代码没有任何意义,不应该包括在内.
对我来说,想要捕获异常并将它们转换为错误代码似乎很奇怪.为什么在后者是Java和C#中的默认值时,您认为调用者更喜欢错误代码而不是异常?
至于你的问题:
您应该只捕获实际可以处理的异常.在大多数情况下,捕获异常并不是正确的做法.有一些例外(例如,线程之间的日志记录和编组异常),但即使对于这些情况,通常也应该重新抛出异常.
你的代码中绝对不应该有很多try/catch语句.同样,我们的想法是只捕获您可以处理的异常.您可以包含一个最顶层的异常处理程序,将任何未处理的异常转换为对最终用户有用的东西,但是否则您不应该尝试捕获每个可能位置的每个异常.
这取决于应用和情况.如果你构建一个库组件,你应该冒出异常,尽管它们应该被包装成与你的组件一起上下文.例如,如果您构建一个Xml数据库并假设您正在使用文件系统来存储数据,并且您正在使用文件系统权限来保护数据.您不希望冒出一个FileIOAccessDenied异常,因为它会泄漏您的实现.相反,您将包装异常并抛出AccessDenied错误.如果您将组件分发给第三方,则尤其如此.
至于是否可以吞下例外.这取决于你的系统.如果您的应用程序可以处理故障情况,并且通知用户失败的原因没有任何好处,那么请继续,尽管我强烈建议您记录失败.我总是觉得很难打电话来帮助解决问题并发现他们正在吞下异常(或者替换它并抛出一个新的而不设置内部异常).
一般来说,我使用以下规则:
在我的组件和库中,如果我打算处理它或基于它做一些事情,我只会捕获异常.或者,如果我想在异常中提供其他上下文信息.
我在应用程序入口点或尽可能高的级别使用常规try catch.如果异常到达此处,我只需记录它并让它失败.理想情况下,异常永远不会到达此处
我发现以下代码是一种气味:
try { //do something } catch(Exception) { throw; }
像这样的代码没有任何意义,不应该包括在内.
我想就这个主题推荐另一个好的来源.这是对C#和Java,Anders Hejlsberg和James Gosling的发明者的访谈,分别是关于Java的Checked Exception的主题.
失败和例外
页面底部还有很多资源.
我倾向于同意Anders Hejlsberg和你的看法,大多数来电者只关心操作是否成功.
Bill Venners:您提到了关于已检查异常的可扩展性和版本控制问题.你能澄清这两个问题的含义吗?
Anders Hejlsberg:让我们从版本开始,因为问题很容易在那里看到.假设我创建了一个声明它抛出异常A,B和C的方法foo.在foo的第二个版本中,我想添加一些功能,现在foo可能会抛出异常D.这对我来说是一个彻底的改变.将D添加到该方法的throws子句中,因为该方法的现有调用者几乎肯定不会处理该异常.
在新版本中向throws子句添加新异常会破坏客户端代码.这就像在界面中添加方法一样.在发布接口之后,它实际上是不可变的,因为它的任何实现都可能具有您要在下一个版本中添加的方法.所以你必须创建一个新的界面.与异常类似,您可能必须创建一个名为foo2的全新方法,该方法会抛出更多异常,或者您必须在新foo中捕获异常D,并将D转换为A,B或C.
Bill Venners:但是你不是在这种情况下破坏他们的代码,即使是在没有检查异常的语言中吗?如果新版本的foo将抛出一个客户应该考虑处理的新异常,那么他们的代码是不是因为他们在编写代码时没有预料到异常这一事实?
Anders Hejlsberg:不,因为在很多情况下,人们并不关心.他们不会处理任何这些例外情况.它们的消息循环周围有一个底层异常处理程序.那个处理程序只是打开一个对话框,说明出了什么问题并继续.程序员通过在任何地方编写try finally来保护他们的代码,因此如果发生异常他们会正确退出,但他们实际上并不感兴趣处理异常.
throws子句,至少它在Java中的实现方式,并不一定会强制您处理异常,但如果您不处理它们,它会强制您确切地确认哪些异常可能会通过.它要求您捕获声明的异常或将它们放在您自己的throws子句中.为了解决这个问题,人们做了荒谬的事情.例如,他们用"抛出异常"来装饰每个方法.这完全打败了这个功能,你只是让程序员写了更多的gobbledy gunk.这对任何人都没有帮助.
编辑:添加了有关转换的更多详细信息
检查异常通常是一个有争议的问题,特别是在Java中(稍后我将尝试为那些赞成并反对它们的人找到一些例子).
根据经验,异常处理应该围绕这些准则,而不是特定的顺序:
为了可维护性,请始终记录异常,以便在您开始看到错误时,日志将帮助您指向错误可能已启动的位置.永远不要离开printStackTrace()
或喜欢它,你的一个用户最终可能会获得其中一个堆栈跟踪,并且对于如何处理它几乎没有任何知识.
捕获可以处理的异常,只有那些并处理它们,不要只是将它们抛到堆栈中.
总是捕获一个特定的异常类,通常你永远不应该捕获类型Exception
,你很可能吞下其他重要的异常.
从来没有(永远)抓住Error
!! ,意思是:永远不要捕捉Throwable
s,因为Error
s是后者的子类.Error
s是您很可能永远无法处理的问题(例如OutOfMemory
,或其他JVM问题)
关于您的具体情况,请确保调用您的方法的任何客户端都将收到正确的返回值.如果某些内容失败,布尔返回方法可能会返回false,但请确保您调用该方法的位置能够处理该方法.
您应该只捕获可以处理的异常.例如,如果您正在处理通过网络阅读并且连接超时而您获得异常,则可以再试一次.但是,如果您正在通过网络阅读并获得IndexOutOfBounds异常,那么您实际上无法处理它,因为您没有(好吧,在这种情况下您不会)知道是什么导致了它.如果您要返回false或-1或null,请确保它是针对特定异常的.当抛出的异常是堆内存不足时,我不希望我正在使用的库在网络读取时返回false.