Java或C#中异常管理的最佳实践[关闭]

问题

我一直在决定如何在我的应用程序中处理异常。

如果我的异常问题来自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中你只是处理异常)。遵循与语言相关联的错误管理构造:)!

#1 热门回答(59 赞)

对我来说,想要捕获异常并将它们转换为错误代码似乎很奇怪。为什么你认为调用者更喜欢错误代码而不是异常,而后者是Java和C#中的默认代码?

至于你的问题:

  • 你应该只捕获你可以实际处理的异常。在大多数情况下,仅仅捕获异常并不是正确的做法。有一些例外(例如,线程之间的日志记录和编组异常),但即使对于这些情况,通常也应该重新抛出异常。
  • 你的代码中绝对不应该有很多try / catch语句。同样,我们的想法是只捕获你可以处理的异常。你可以包含一个最顶层的异常处理程序,将任何未处理的异常转换为对最终用户有用的东西,但是你不应该尝试在每个可能的位置捕获每个异常。

#2 热门回答(25 赞)

这取决于应用和情况。如果你构建一个库组件,你应该冒出异常,尽管它们应该被包装成与你的组件一起上下文。例如,如果你构建一个Xml数据库并假设你正在使用文件系统来存储数据,并且你正在使用文件系统权限来保护数据。你不希望冒出一个FileIOAccessDenied异常,因为它会泄漏你的实现。相反,你将包装异常并抛出AccessDenied错误。如果将组件分发给第三方,则尤其如此。

至于是否可以吞下例外。这取决于你的系统。如果你的应用程序可以处理故障情况,并且通知用户失败的原因没有任何好处,那么请继续,尽管我强烈建议你记录失败。我总是觉得很难被叫来帮助解决问题并发现他们正在吞下异常(或者替换它并在不设置内部异常的情况下更换新的异常)。

一般来说,我使用以下规则:

  • 在我的组件和库中,如果我打算处理它或基于它做某事,我只会捕获异常。或者,如果我想在异常中提供其他上下文信息。
  • 我在应用程序入口点或最高级别使用一般的try catch。如果异常到达此处,我只需记录它并让它失败。理想情况下,异常永远不会到达这里

我发现以下代码是一种气味:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

像这样的代码没有任何意义,不应该包括在内。


#3 热门回答(9 赞)

我想就这个主题推荐另一个好的来源。这是对C#和Java,Anders Hejlsberg和James Gosling的发明者的访谈,分别是关于Java的Checked Exception的主题。
Failure and Exceptions
页面底部还有很多资源。

我倾向于同意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。这对任何人都没有帮助。

编辑:添加了有关转换的更多详细信息