Java或C#中exceptionpipe理的最佳实践
我坚持决定如何处理我的应用程序中的exception。
很多,如果我的exception问题来自1)通过远程服务访问数据或2)反序列化JSON对象。 不幸的是,我不能保证这些任务(切断networking连接,不受我控制的格式错误的JSON对象)的成功。
因此,如果我确实遇到一个exception,我只需在函数中捕获它并返回给调用者的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; }
我认为这种方法没问题,但是我真的很好奇,知道pipe理exception的最佳实践是什么(我真的应该把一个exception从一个调用堆栈中调出来)。
总结关键问题:
- 可以只捕捉exception,但不要冒泡或正式通知系统(通过日志或通知给用户)?
- 什么最佳实践是有exception,不会导致所有需要一个try / catch块?
跟进/编辑
感谢所有的反馈意见,在线查找exceptionpipe理的一些优秀资源:
- exception处理的最佳实践| O'Reilly媒体
- .NET中的exception处理最佳实践
- 最佳实践:exceptionpipe理 (文章现指向archive.org副本)
- exception处理反模式
似乎exceptionpipe理是根据上下文而变化的事物之一。 但是最重要的是,在一个系统中如何pipe理exception应该是一致的。
另外还要注意通过过度尝试/捕获代码腐败或不尊重它的例外(例外是警告系统,还有什么需要警告?)。
另外,这是来自m3rLinEz的漂亮select。
我倾向于赞同安德斯·海斯尔斯伯格(Anders Hejlsberg)和你的观点,即如果手术成功或者没有成功,那么大多数人只会关心。
从这个评论中,它提出了一些需要思考的问题:
- 抛出这个exception有什么意义?
- 如何处理它是有意义的?
- 来电者是真的关心这个exception,还是只关心这个通话是否成功?
- 强迫呼叫者优雅地pipe理潜在的exception?
- 你是否尊重这个语言的习惯?
- 你真的需要返回一个成功的标志,如布尔? 返回boolean(或int)更像是一个C心态而不是Java(在Java中你只是处理exception)。
- 遵循与语言相关的错误pipe理结构:)!
对于我来说,想要捕捉exception并将其转换为错误代码似乎很奇怪。 为什么你认为调用者更喜欢错误代码而不是exception,而后者在Java和C#中都是默认的呢?
至于你的问题:
- 你应该只捕捉你实际可以处理的exception。 在大多数情况下,捕捉exception并不是正确的做法。 有一些例外(例如线程之间的日志logging和编组exception),但即使是这些情况下,通常应该重新抛出exception。
- 您的代码中绝对不应该有大量的try / catch语句。 再次,这个想法是只捕捉你可以处理的exception。 您可以包含一个最高级的exception处理程序,将任何未处理的exception转换为对最终用户有用的东西,否则,您不应该尝试在每个可能的地方捕获每个exception。
这取决于应用和情况。 如果你构build一个库组件,你应该鼓励exception,虽然它们应该被包装成与你的组件相关的上下文。 例如,如果您构build一个Xml数据库,并假设您正在使用文件系统来存储数据,并且正在使用文件系统权限来保护数据。 你不会想冒泡FileIOAccessDeniedexception,因为这泄漏了你的实现。 相反,你会包装exception并抛出一个AccessDenied错误。 如果您将组件分发给第三方,则尤其如此。
至于吞下例外是否可以。 这取决于你的系统。 如果您的应用程序可以处理失败的情况,并没有通知用户为什么失败的好处,那么继续,虽然我强烈build议您logging失败。 我总是发现它被称为帮助排除故障,并发现他们正在吞咽exception(或replace它,抛出一个新的而不设置内部exception)令人沮丧。
一般来说,我使用以下规则:
- 在我的组件和库中,如果我打算处理它或根据它做某件事,我只会捕获一个exception。 或者如果我想提供额外的上下文信息的例外。
- 我在应用程序入口点或最高级别上使用一般性尝试。 如果一个exception到达这里,我只是logging它,让它失败。 理想情况下,例外不应该到达这里。
我发现下面的代码是一种气味:
try { //do something } catch(Exception) { throw; }
这样的代码没有意义,不应该包括在内。
我想在这个话题上推荐另一个好的来源。 这是对C#和Java的发明者Anders Hejlsberg和James Gosling分别就Java的Checked Exception这个主题的访谈。
失败和例外
页面底部也有很多资源。
我倾向于赞同安德斯·海斯尔斯伯格(Anders Hejlsberg)和你的观点:如果手术成功或者没有成功,那么大多数人只会关心。
Bill Venners :您提到了关于检查exception的可伸缩性和版本控制问题。 你能澄清一下你的意思吗?
Anders Hejlsberg :让我们从版本开始,因为这些问题很容易看到。 假设我创build了一个方法foo,声明它会抛出exceptionA,B和C.在foo的第二版中,我想添加一些function,现在foo可能会抛出exceptionD.这对我来说是一个突破性的改变将D添加到该方法的throws子句中,因为该方法的现有调用方几乎肯定不会处理该exception。
在新版本中向throws子句中添加新的exception会破坏客户端代码。 这就像添加一个方法到一个接口。 在你发布一个接口之后,实际上它是不可变的,因为它的任何实现都可能有你想要在下一个版本中添加的方法。 所以你必须改为创build一个新的界面。 与exception类似,您将不得不创build一个名为foo2的全新方法,这会引发更多的exception,或者您必须在新的foo中捕获exceptionD,然后将D转换为A,B或C.
比尔·威纳斯 :但是,在这种情况下,是不是打破了他们的代码,即使是没有检查exception的语言? 如果foo的新版本将抛出一个客户端应该考虑处理的新exception,那么不是因为他们在编写代码的时候并不期望exception而破坏了他们的代码?
Anders Hejlsberg :不,因为在很多情况下,人们并不在乎。 他们不会去处理这些例外。 他们的消息循环周围有一个底层exception处理程序。 那个处理程序就是要打开一个对话框,说出什么地方出了问题,然后继续。 程序员通过编写try finally来保护他们的代码,所以如果发生exception,他们会正确地退出,但是他们实际上并不想处理这些exception。
抛出子句,至less是在Java中实现的方式,并不一定会强制你处理exception,但是如果你不处理它们,就会强制你确认哪些exception可能通过。 它要求你要么捕获声明的exception,要么把它们放在你自己的throws子句中。 为了解决这个问题,人们做了荒谬的事情。 例如,他们用“抛出exception”来装饰每个方法。 这完全破坏了function,你只是让程序员写更多的gobbledy gunk。 这对任何人都没有帮助。
编辑:增加了关于谈话的更多细节
检查exception是一个有争议的问题,特别是在Java中(稍后我将尝试find一些赞成和反对他们的例子)。
作为经验法则,exception处理应该是围绕这些准则的事情,并不是特定的顺序:
- 为了可维护性,请始终loggingexception,以便在开始查看错误时,日志将帮助您指出错误可能已经开始的位置。 不要离开
printStackTrace()
或类似的东西,你的用户可能最终会得到这些堆栈跟踪中的一个,并且对于如何处理这些堆栈跟踪完全没有任何知识 。 - 捕获exception,你可以处理,只有那些, 并处理它们 ,不只是把他们堆放起来。
- 总是捕获一个特定的exception类,一般你不应该捕获types
Exception
,你很可能会吞下其他重要的exception。 - 永远(永远)赶上
Error
! ,意思是: 永远不要抓Throwable
s,因为Error
s是后者的子类。Error
是您很可能永远无法处理的问题(例如,OutOfMemory
或其他JVM问题)
关于您的具体情况,请确保任何调用您的方法的客户端将收到正确的返回值。 如果失败了,布尔返回方法可能会返回false,但是要确保你调用该方法的地方能够处理这个。
你应该只抓住你可以处理的例外。 例如,如果您正在处理通过networking进行的阅读,并且连接超时,并且出现exception,则可以再次尝试。 然而,如果你正在通过networking阅读并得到一个IndexOutOfBoundsexception,你真的无法处理,因为你不知道是什么原因造成的。 如果您要返回false或-1或null,请确保它是针对特定的例外。 我不希望我使用的库在抛出的exception是堆内存不足时,在networking上读取返回false。
exception是不属于正常程序执行的错误。 根据你的程序和它的用途(即文字处理器与心脏监视器),你会想遇到exception时做不同的事情。 我曾经使用exception作为正常执行的一部分的代码,这绝对是一种代码异味。
防爆。
try { sendMessage(); if(message == success) { doStuff(); } else if(message == failed) { throw; } } catch(Exception) { logAndRecover(); }
这段代码让我成为barf。 国际海事组织除非是关键性的计划,否则不应该从例外中恢 如果你抛出exception,那么坏事情正在发生。
以上所有情况似乎都是合理的,而且你的工作场所往往可能有一个政策。 在我们的地方,我们已经定义了exception的types: SystemException
(unchecked)和ApplicationException
(checked)。
我们已经同意SystemException
不可能被恢复,并且一次只能处理一次。 为了提供进一步的上下文,我们的SystemException
被扩展来指示它们发生的地方,例如RepositoryException
, ServiceEception
等。
ApplicationException
可能具有类似InsufficientFundsException
业务意义,应由客户端代码处理。
Witohut一个具体的例子,很难评论你的实现,但我永远不会使用返回码,他们是一个维护问题。 你可能会吞下一个exception,但是你需要决定为什么,并且总是logging事件和堆栈跟踪。 最后,由于你的方法没有其他的处理,它是相当多余的(除了封装?),所以doactualStuffOnObject(p_jsonObject);
可以返回一个布尔值!
经过一番思考,看看你的代码在我看来,你只是重新抛出exception作为布尔值。 你可以让方法通过这个exception通过(你甚至不必去捕捉它)并在调用者中处理它,因为这是它的重要的地方。 如果这个exception会导致调用者重试这个函数,调用者应该是捕捉这个exception的那个。
有时可能发生的情况是你所遇到的exception对调用者来说是没有意义的(即它是一个networkingexception),在这种情况下,你应该把它包装在一个特定于域的exception中。
另一方面,如果exception在你的程序中指示了一个不可恢复的错误(即,这个exception的最终结果将是程序终止),我个人喜欢通过捕获它并抛出一个运行时exception来明确这个错误。
如果您要在示例中使用代码模式,请将其称为TryDoSomething,并仅捕获特定的exception。
当为了诊断目的而loggingexception时,也要考虑使用exceptionfilter 。 VB有exceptionfilter的语言支持。 链接到Greggm的博客有一个可以从C#中使用的实现。 exceptionfilter具有更好的debugging性能,可以捕获和重新抛出。 具体而言,您可以在filter中logging问题并让exception继续传播。 该方法允许附加JIT(即时)debugging器具有完整的原始堆栈。 在重新抛出的地方,重新抛出堆叠。
TryXXXX有意义的情况是,当你包装一个第三方函数时,抛出不是真正例外的情况,或者不调用函数就很难testing。 一个例子是这样的:
// throws NumberNotHexidecimalException int ParseHexidecimal(string numberToParse); bool TryParseHexidecimal(string numberToParse, out int parsedInt) { try { parsedInt = ParseHexidecimal(numberToParse); return true; } catch(NumberNotHexidecimalException ex) { parsedInt = 0; return false; } catch(Exception ex) { // Implement the error policy for unexpected exceptions: // log a callstack, assert if a debugger is attached etc. LogRetailAssert(ex); // rethrow the exception // The downside is that a JIT debugger will have the next // line as the place that threw the exception, rather than // the original location further down the stack. throw; // A better practice is to use an exception filter here. // see the link to Exception Filter Inject above // http://code.msdn.microsoft.com/ExceptionFilterInjct } }
不pipe你是否使用像TryXXX这样的模式都是一个风格问题。 捕捉所有exception并吞咽它们的问题不是风格问题。 确保允许意外的exception传播!
我build议您从标准库中获取您正在使用的语言的提示。 我不能说C#,但让我们看看Java。
例如java.lang.reflect.Array有一个静态set
方法:
static void set(Object array, int index, Object value);
C的方式是
static int set(Object array, int index, Object value);
…返回值是一个成功的指标。 但是你不再是C世界了。
一旦你接受exception,你应该发现,通过将你的error handling代码从你的核心逻辑中移走,它会使你的代码变得更加简单明了。 目的是在一个try
块中有大量的语句。
正如其他人所指出的那样,你应该尽可能地具体说明你所遇到的那种例外情况。
如果你要捕获一个exception并返回false,它应该是一个非常特殊的例外。 你不这样做,你抓住所有的人,并返回假。 如果我得到一个MyCarIsOnFireException我想马上知道它! 我可能不关心的其他例外情况。 所以你应该有一堆exception处理程序,对于一些exception(重新抛出,或者捕捉并重新抛出一个新的exception来解释发生了什么事情),说“这里有些事情是错误的”,并且只为别人返回false。
如果这是一个你要启动的产品,你应该在某个地方logging这些exception情况,这将有助于你在未来进行调整。
编辑:至于包装在try / catch的一切的问题,我认为答案是肯定的。 代码中的exception应该非常罕见,以至于catch块中的代码很less执行,所以根本不会执行。 一个例外应该是你的状态机坏了,不知道该怎么做的状态。 至less重新抛出一个exception,解释当时正在发生的事情,并在其中捕获exception。 “方法doSomeStuff()中的例外”对于那些必须弄清楚为什么在休假期间(或者在新的工作中)破坏的人来说并不是很有帮助。
我的策略:
如果原始函数返回void,则将其更改为返回bool 。 如果发生exception/错误返回false ,如果一切正常返回true 。
如果函数应该返回什么,那么当exception/错误发生时返回null ,否则返回的项目。
而不是bool ,可以返回一个包含错误描述的string 。
在任何情况下,在返回任何logging错误之前。
这里有一些很好的答案 我想补充一点,如果你最终发布了一些类似于你的东西,至less要打印比栈跟踪更多的东西。 说出你当时正在做的事情,和Ex.getMessage(),给开发者一个战斗的机会。
try / catch块形成embedded在第一个(主)集合中的第二组逻辑,因此这是一个很好的方式来摧毁不可读的,难以debugging的意大利面代码。
尽pipe如此,他们合理地使用可读性奇迹,但你应该遵循两个简单的规则:
-
在底层使用它(节省)来处理图书馆的处理问题,并把它们回stream到主要的逻辑stream程中。 我们想要的大多数error handling应该来自代码本身,作为数据本身的一部分。 为什么要特殊条件,如果返回的数据不是特殊的?
-
在上级使用一个大的处理程序来pipe理代码中出现的任何或所有奇怪的条件,而这些条件不是低层次的。 做一些有用的错误(日志,重新启动,恢复等)。
除了这两种types的error handling之外,中间的所有其他代码应该是免费的,并且清除try / catch代码和错误对象。 那样的话,无论你在哪里使用它,或者你使用它,它都可以简单地按照预期工作。
保罗。
我的答案可能有点迟,但error handling是我们随时可以随时改变和发展的东西。 如果你想读更多关于这个主题的东西,我在我的新博客上写了一篇关于它的文章。 http://taoofdevelopment.wordpress.com
快乐的编码。