Java – 应该在何处以及如何使用exception?

我正在阅读有关Java中exception处理的一些内容,以便能够编写更好的代码。 好吧,我承认,我很内疚; 我使用了太多的try-catch {}块,我在catch中使用了ex.printStackTrace() ,甚至没有使用正确的记录器(实际上System.outSystem.err被重定向到PrintWriter ,所以a生成了日志)。 然而,经过几个小时的阅读,我发现自己处在一个陌生的地方:未知。 如果exception被设计为传递关于流的exception状态的信息,那么如何知道WHERE是用该信息做某事的适当级别?

例如,当发生数据库错误时,是应该返回空值还是错误代码,还是抛出exception? 如果抛出,那么应该处理该exception? 我明白,如果你不能对它做任何事情,甚至没有用来记录exception。 但是,在GUI应用程序中,这可能会轻易杀死您的GUI(我使用SWT并且我经常看到这一点),即使是menuShown()方法的情况(如果不处理, ArrayIndexOutOfBoundsexception将关闭应用程序) 。 这个例子可以永远持续下去,但这里是问题摘要:

  1. 使用try-catch()过度地对性能产生负面影响吗?
  2. 使用特定的exception类型更好吗? 如果我错过了可能发生的X类exception之一怎么办?
    坦率地说,我听说并且仅用了10%就可以在2 – 3年内考虑Java标准exception。 是的,有人说如果来电者不知道如何处理抛出的exception,他就没有权利调用投掷方法。 是对的吗?
  3. 我读过Anders Hejlsberg的这篇文章,说检查过的例外情况很糟糕。 这是否表明在某些情况下建议进行方便的吞咽exception?
  4. 一张图片价值1000字; 我想一些例子在这里会有很多帮助。

我知道这个主题是永恒的,但实际上我期待着根据你的建议审查一个150个class级的中型项目。 非常感谢。

例外是因为任务的程序员不必自己处理问题。 (1):如果问题对于他在任务中处理不合理。 从流中读取字符串的任务不应该处理磁盘错误。 但是,如果数据不包含String,则处理它应该是非常合理的。

(2):他不能自己处理(没有足够的信息)从文件读取字符串的任务和未找到的文件可能会要求用户选择另一个文件但是现在该任务怎么可能是什么文件夹文件可能是什么扩展文件可能是。 在不知情的情况下,该任务如何创建一个GUI来重新询问。

(3):没有逻辑(或可管理)的方式来区分不同的回报。 如果任务无法读取文件并返回null。 如果文件格式错误怎么办,也返回null? 这两者有何不同? 可以使用例外来区分它们。 这就是为什么它被称为例外:-D。

(4):有许多类似的任务需要类似的处理和编写,在所有任务中很难维护。 为所有访问编写句柄代码可能会很麻烦,因为您可能需要许多重复。

 interface DBAccess { public Result accessDB(); } class DBOperation { static public void DoOperation(DBAccess pAccess) { try { return DBAccess.accessDB(); } catch(InvalidDBPasswordException IPE) { // Do anything about invalid password } catch(DBConnectionLostException DBCLE) { // Do anything about database connection lost } // Catch all possible DB problem } } ... private User[] ShowUserList_and_ReturnUsers() { // Find the used. // Show user list if (Users.count() == 0) return null; else return Users; // No need to handle DB connection problem here } private User[] GetUserProfile() { // Find the used and return // No need to handle DB connection problem here } ... /** An onClick event to show user list */ { DBOperation.DoOperation(new DBAccess() { public Result accessDB() { return ShowUserList_and_ReturnUsers(); } }); } /** An onClick event to show a user profile */ { DBOperation.DoOperation(new DBAccess() { public Result accessDB() { return GetUserProfile(); } }); } ... Many more DB access 

(5):编写所有错误检查复杂或减慢任务。 上述问题应该说明它如何帮助减少并发症。 这是如何帮助不减速。

 for(int i = 0; i < Users.length; i++) { User aUser = Users[i]; // Do something with user } Replaced with try { for(int i = 0; ; i++) { User aUser = Users[i]; // Do something with user } } catch(ArrayOutOfBoundException AOBE) {} 

如果用户数量很大,替换代码将获得更好的性能。


当发生数据库错误时,应该返回一个空值,错误代码还是抛出exception? Ans:取决于什么样的错误。 就像你找不到用户一样,这不是一个错误。 但是如果密码错误或连接断开,这些都是错误,因为试图以正常方式处理它会使程序复杂化。

(1)。 使用过多的try-catch()对性能有负面影响? Ans:根据“Effective Java”的说法,就我记忆而言,它的效果非常微小(只有不好的循环)(我现在没有这本书)。

(2)。 使用特定的exception类型更好? 答:用户特定的一个最好避免解决错误的问题。

如果我错过了可能发生的X类exception之一怎么办? 坦率地说,我听说并且只用了10%,我认为在2 - 3年内会出现Java标准exception。 Ans:就像你毫无例外地处理错误一样,你也可以错过它。 当你发现它时,你只需添加它。

是的,有人说,如果来电者不知道如何处理被抛出的exception,他就没有权利调用投掷方法。 是对的吗? 答:不,如果我不知道如何处理某些exception,请重新抛出它。

(3)。 我读过Anders Hejlsberg的这篇文章,说检查过的例外情况很糟糕。 这是否表明在某些情况下建议进行方便的吞咽exception? Ans:我认为他正在讨论“检查exception”作为编译器的一项function,以确保应该处理一些exception。 有例外的想法。

(4)。 一张图片值1000字。我猜一些例子在这里会有很多帮助。 Ans:上面的代码。

我现在跑了......抱歉...... :-p(请等一下,亲爱的!)

exception的一般经验法则是,如果你可以对它做些什么,抓住它并处理它,如果你不能,重新将它扔到下一个方法。 要了解您的一些细节:

  1. 不,使用过多的try / catch不会对性能产生影响
  2. 使用最具体的exception类型即可。 例如,如果可以避免,则通常不应抛出exception。 通过抛出特定类型,您可以让用户知道可能出现的问题。 但是,您可以将其重新抛出为更通用的内容,因此不关心特定exception的调用者不需要了解它(例如,GUI不关心它是否是IOException与ArrayIndexOutOFBoundsException)。
  3. 你会发现更喜欢检查exception的人,你会发现更喜欢未经检查的人。 一般情况下,我尝试使用未经检查的exception,因为对于大多数已检查的exception,您通常没有太多可以执行的操作,并且您仍然可以处理未经检查的exception,您不必这样做。 我经常发现自己重新抛出已检查的exception,因为我对它们做不了多少(另一种策略是捕获已检查的exception并将其重新抛出为未经检查的,因此链中较高的类如果不需要则不需要捕获它)。

我通常喜欢在捕获它们的位置记录exception – 即使我不能对它做任何事情,它也有助于诊断问题。 如果您不熟悉它,还要查看Thread.setDefaultUncaughtExceptionHandler方法。 这允许您处理未被任何人捕获的exception并对其执行某些操作。 这对于GUI应用程序特别有用,因为否则可能看不到exception。

举几个例子:

 try { // some database operation } catch (IOException ex) { // retry the database operation. then if an IO exception occurs rethrow it. this shows an example doing something other than just catch, logging and/or rethrowing. } 

如果您愿意,我会很乐意扩展其中的任何部分。

许多好的答案,让我只添加一些尚未提及的要点。

  1. 您的exception类型应该与调用者可能区分它们一样具体。 我的意思是,如果有两个可能的错误,A和B,并且任何调用者都可能在两种情况下做同样的事情,那么创建一个exception类。 如果调用者可能会做两件事,那么就制作两个exception类。

对于我创建的许多(可能是大多数)exception,程序可以实际执行的唯一事情是显示错误消息并让用户有机会更改其输入并重试。 大多数validation错误 – 无效的日期格式,数字字段中的非数字等 – 都属于此类别。 对于这些我创建了一个exception类型,我通常称之为“BadInputException”或“ValidationException”,并且我在整个系统中使用相同的exception类。 当出现错误时,我’抛出新的BadInputException(“Amount必须只包含数字”)或其他一些错误,然后让调用者显示它并让用户重试。

另一方面,如果调用者在不同情况下可能会做出不同的事情,那么请将它们作为不同的例外。

简单的经验法则:如果您有两个或多个exception,这些exception始终使用相同的重复代码进行处理,请将它们组合成一个例外。 如果你的catch块正在进行额外的检查以确定这是什么类型的错误,那么它应该是两个(或更多)exception类。 我见过代码执行exception.getMessage,然后在消息中查找关键字以找出问题所在。 这太丑了。 制作多个例外并干净利落地完成。

  1. 使用exception有三个很好的理由,而不是其他处理错误的方法。

(a)它避免了“魔术”返回值的问题,就像非空字符串是一个真正的答案,但是null表示存在错误。 或者更糟糕的是,“NF”表示找不到文件,“NV”表示格式无效,其他任何内容都是真正的答案。 除了exception,exception是exception,返回值是返回值。

(b)例外整齐地跳过主要代码行。 通常当出现错误时,您希望跳过一大堆没有有效数据时没有意义的处理,并直接显示错误消息并退出,或重试操作,或任何适当的操作。 在糟糕的老死中,我们会写“GOTO panic-abort”。 由于所讨论的所有原因,GOTO都是危险的。 例外消除了可能是使用GOTO的最后剩余正当理由。

(c)也许与(b)相同,您可以在适当的级别处理问题。 有时,当发生错误时,您需要重试完全相同的function – 就像I / O错误可能代表瞬态通信故障。 在另一个极端,当你得到一个无法以任何方式处理的错误但是从整个程序中轰炸出来并显示“抱歉,已经发生了意外,这里的每个人都死了”的消息时,你可以在子程序中达到十级。 除了例外,它不仅容易选择正确的级别,而且可以在不同的模块中做出不同的选择。

我们在团队中做的一件事就是为我们的错误设置自定义exception。 我们正在使用Hibernate Validator框架,但您可以使用任何框架或库存exception来执行此操作。

例如,我们有一个ValidationException来处理validation错误。 我们有一个ApplicationException来处理系统错误。

你想要尽量减少你的尝试。 在我们的例子中,我们将使validation器收集“InvalidValue”对象中的所有validation,然后抛出一个ValidationException,并将无效的值信息捆绑到其中。 然后,您可以向用户报告哪些字段出错,等等。

在您提到数据库错误的情况下 – 您可能不希望将堆栈跟踪发送到UI(记录它是一个好主意)。 在这种情况下,您可以捕获数据库exception,然后将自己的ApplicationException抛出到GUI。 您的GUI不必知道如何处理无限数量的服务器错误,但可以设置为处理更通用的ApplicationException – 可能报告服务器存在问题,并指示用户应联系您的客户支持部门报告问题。

最后,由于您依赖的外部API,有时您会忍不住使用大量的try / catch块。 这可以。 如前所述,捕获外部exception,并将其格式化为对您的应用程序更有意义的exception。 然后抛出自定义exception。

  1. 虽然我没有任何数字,但我不认为try-catch对性能有任何重大影响(不是我见过的)。 我认为,如果你没有遇到很多例外,性能影响基本上没什么。 但无论如何,最好先关注正确实现代码,然后再实现良好的性能 – 一旦完成第二次,就更容易做到第二次。

  2. 我认为exception类应该具体说明exception是什么。 我在Java的SQLExceptions中遇到的问题是它们没有告诉你什么是真正出错的信息。 Spring使用了一组更具描述性的数据库exception(死锁exception,数据完整性exception等)。这样你就可以知道问题到底是什么。

  3. 检查exception可能很烦人,但我不认为它们总是坏的。 例如,Spring对数据库错误使用未经检查的exception,但我仍然检查它们,并且1)在那里处理它们,如果可能的话,或2)包装一个更一般的例外,表明组件失败。

  4. 不幸的是,我想不出任何好的具体例外。 但是,就像我说的那样,我发现Spring的exception规则是有用的,但并不烦人,所以也许你可以查看一些Spring文档。 Spring数据库类就是一个很好的例子。

  • 使用过多的try-catch()对性能有负面影响?

这听起来像微优化,如果这确实对性能产生影响,那么在面对这个问题之前,你将不得不面对很多更大的性能问题。

  • 使用特定的exception类型更好? 如果我错过了可能发生的X类exception之一怎么办? 坦率地说,我听说并且只用了10%,我认为在2 – 3年内会出现Java标准exception。 是的,有人说,如果来电者不知道如何处理被抛出的exception,他就没有权利调用投掷方法。 是对的吗?

我不确定我是否明白这个问题,但我会说:“如果你不知道如何处理exception,请重新抛出它”。

  • 我读过Anders Hejlsberg的这篇文章,说检查过的例外情况很糟糕。 这是否表明在某些情况下建议进行方便的吞咽exception?

一定不行。 这只是意味着在某些情况下应该首选未经检查的exception,特别是当用户不知道如何处理已检查的exception(例如SQLexception),或者如果无法恢复时,…

  • 一张图片值1000字。我猜一些例子在这里会有很多帮助。

Spring的DataAccessException是一个非常好的例子。 检查第10章.DAO支持 。

se-radio制作了一个关于error handling主题的播客集,解释了一些关于如何使用exception的哲学,可以将其重述为“在何处吸收它们”。

我保留的主要内容是大多数函数应该让它们冒泡,大多数exception细节应该最终出现在日志文件中。 然后这些函数只传递全局消息,说明发生了什么。

从某种意义上说,这会导致一种exception层次结构:每层代码一层。

正如我认为他们所说,向用户解释这样的数据库集群因DNS不可用或磁盘已满而失败是没有意义的。 在那个级别,发生的事情无法让交易完成,这是用户必须知道的。

当然,开发人员/管理员很乐意看到更多细节,这就是为什么在数据库层应该记录特定的exception。

返回值与抛出exception

exception和返回值之间的根本区别在于返回值传递给您的直接调用方,而exception则传递给调用堆栈中任何位置的catch子句。 这允许为许多不同类型的exception重用相同的exception处理程序。 当且仅当您需要该function时,我建议您支持exception而不是返回代码。

绩效影响。

每条指令都会对性能产生负面影响,包括catch-blocks中的指令。 但是,任何现代CPU都可以每秒抛出和处理数百万个exception,所以除非你抛出数千个exception,否则你不会注意到任何事情。

具体例外

对于投掷,具体允许特定处理。 对于处理,您可以是通用的,但是您应该知道可以将任意exception传递给您的处理程序,包括未由您的被调用者声明的未经检查的exception。

检查

争论是否应该使用已检查或未检查的exception。 永远不要只是吞下一个例外。 处理或重新抛出它。 如果您不丢弃有关故障的证据,它可以简化维护。

我最近工作的应用程序通过网络接收命令然后执行。 这通常涉及与远程系统的进一步交互,这可能由于许多原因而失败。 执行命令的方法不会捕获任何exception,让它们将调用堆栈冒泡到命令侦听器中的中央exception处理程序,执行以下操作:

 for (int retries = 0;; retries++) { try { commandService.execute(command); return; } catch (Exception e} Log.error(e); if (retries < 3) { continue; } else { saveForAnalysis(command, e); alertOperator(); return; } } } 

我们故意没有在处理逻辑中捕获和重新抛出exception,因为我们认为这不会增加任何价值。

请不要在非致命错误的情况下返回null。 返回一个NullObject。

否则,在每次调用代码之后都需要进行空检查,这很麻烦,如果忘记会导致代码崩溃。