我正在阅读有关Java中异常处理的一些内容,以便能够编写更好的代码。好吧,我承认,我很内疚;我已经使用了太多的try-catch {}块,我在catch中使用了ex.printStackTrace()
,甚至没有使用正确的记录器(实际上System.out
和System.err
被重定向到了PrintWriter
,因此生成了日志)。然而,经过几个小时的阅读,我发现自己处在一个陌生的地方:未知。如果异常被设计为传递有关流的异常状态的信息,那么如何知道WHERE是用该信息做某事的适当级别?
例如,当发生数据库错误时,是应该返回空值还是错误代码,还是抛出异常?如果抛出,那么应该处理该异常?据我所知,如果你不能对它做任何事情,那么记录异常是没用的。但是,在GUI应用程序中,这可能会轻易杀死您的GUI(我使用SWT并且我经常看到这种情况),即使对于menuShown()
方法的情况(ArrayIndexOutOfBounds
异常将关闭应用程序,如果没有处理)。这个例子可以永远持续下去,但这里是问题摘要:
我知道这个主题是永恒的,但实际上我期待着根据你的建议审查一个150个班级的中型项目。非常感谢。
答案 0 :(得分:7)
例外的一般经验法则是,如果你能对它做些什么,抓住它并处理它,如果你不能,将它重新扔到下一个方法。进入你的一些具体细节:
我通常喜欢在异常情况下记录异常 - 即使我无法对其进行任何操作,也有助于诊断问题。如果您不熟悉它,还要查看Thread.setDefaultUncaughtExceptionHandler方法。这允许您处理未被任何人捕获的异常并对其执行某些操作。这对于GUI应用程序特别有用,因为否则可能看不到异常。
进入一些例子:
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 :(得分:3)
许多好的答案,我只想补充一些尚未提及的要点。
对于我创建的许多(可能是大多数)异常,程序可以实际执行的唯一事情是显示错误消息并让用户有机会更改其输入并重试。大多数验证错误 - 无效的日期格式,数字字段中的非数字等 - 都属于此类别。对于这些我创建了一个异常类型,我通常称之为“BadInputException”或“ValidationException”,并且我在整个系统中使用相同的异常类。当出现错误时,我会抛出新的BadInputException(“Amount必须只包含数字”)或其他一些错误,然后让调用者显示它并让用户重试。
另一方面,如果调用者在不同情况下可能会做出不同的事情,那么请将它们作为不同的例外。
简单的经验法则:如果您有两个或多个异常,这些异常始终使用相同的重复代码进行处理,请将它们合并为一个异常。如果你的catch块正在进行额外的检查以确定这究竟是什么类型的错误,那么它应该是两个(或更多)异常类。我见过代码执行exception.getMessage,然后在消息中查找关键字以找出问题所在。这很难看。制作多个例外并干净利落地完成。
(a)它避免了“魔术”返回值的问题,就像非空字符串是一个真正的答案,但是null表示存在错误。或者更糟糕的是,“NF”表示找不到文件,“NV”表示格式无效,其他任何内容都是真正的答案。除了异常,异常是异常,返回值是返回值。
(b)异常整齐地跳过主要代码行。通常当出现错误时,您希望跳过一大堆没有有效数据时没有意义的处理,并直接显示错误消息并退出,或重试操作,或任何适当的操作。在糟糕的老死中,我们会写“GOTO panic-abort”。由于所讨论的所有原因,GOTO都是危险的。例外消除了可能是使用GOTO的最后剩余的理由。
(c)或许是(b)的推论,您可以在适当的级别处理问题。有时,当发生错误时,您需要重试完全相同的功能 - 就像I / O错误可能代表瞬态通信故障。在另一个极端,当你得到一个无法以任何方式处理的错误但是从整个程序中轰炸出来并显示“抱歉,已经发生了意外,这里的每个人都死了”的消息时,你可能会在子程序中达到十级。除了例外,它不仅容易选择正确的级别,而且可以在不同的模块中做出不同的选择。
答案 2 :(得分:1)
我们在团队中做的一件事就是为我们的错误设置自定义异常。我们正在使用Hibernate Validator框架,但您可以使用任何框架或库存例外来执行此操作。
例如,我们有一个ValidationException来处理验证错误。我们有一个ApplicationException来处理系统错误。
你想尽量减少你的尝试。在我们的例子中,我们将使验证器收集“InvalidValue”对象中的所有验证,然后抛出一个ValidationException,并将无效的值信息捆绑到其中。然后,您可以向用户报告哪些字段出错,等等。
如果您提到数据库错误 - 您可能不希望将堆栈跟踪发送到UI(记录它是一个好主意)。在这种情况下,您可以捕获数据库异常,然后将自己的ApplicationException抛出到GUI。您的GUI不必知道如何处理无限数量的服务器错误,但可以设置为处理更广泛的ApplicationException - 可能报告服务器存在问题,并指示用户应该联系您的客户支持部门报告问题。
最后,由于您依赖的外部API,有时您会忍不住使用大量的try / catch块。这可以。如前所述,捕获外部异常,并将其格式化为对您的应用程序更有意义的异常。然后抛出自定义异常。
答案 3 :(得分:1)
虽然我没有任何数字,但我不认为try-catch对性能有任何重大影响(不是我见过的)。我认为,如果你没有遇到很多例外,性能影响基本上没什么。但无论如何,最好先关注正确实现代码,然后再实现良好的性能 - 一旦完成第二次,就更容易做到第二次。
我认为异常类应该具体说明异常是什么。我使用Java的SQLExceptions时遇到的问题是它们没有提供任何真正出错的信息。 Spring使用了一组更具描述性的数据库异常(死锁异常,数据完整性异常等)。这样你就可以知道问题到底是什么了。
经过检查的异常可能很烦人,但我不认为它们总是很糟糕。例如,Spring对数据库错误使用未经检查的异常,但我仍然检查它们,并且1)在那里处理它们,如果可能的话,或2)包装一个更一般的例外,表明组件失败。
不幸的是,我想不出任何好的具体例外情况。但是,就像我说的那样,我发现Spring的异常规则是有用的,但并不烦人,所以也许你可以查看一些Spring文档。 Spring数据库类就是一个很好的例子。
答案 4 :(得分:1)
- 使用过多的try-catch()会对性能产生负面影响吗?
这听起来像微优化,如果这确实会对性能产生影响,那么在面对这个问题之前,你将不得不面对很多更大的性能问题。
- 使用特定的异常类型会更好吗?如果我错过了可能发生的X类异常之一怎么办?坦率地说,我听说并且只用了10%,我认为在2 - 3年内会出现Java标准异常。是的,有人说,如果来电者不知道如何处理被抛出的异常,他就没有权利调用投掷方法。是吗?
我不确定我是否明白这个问题,但我会说:“如果您不知道如何处理异常,请重新抛出它。”
- 我读过Anders Hejlsberg的这篇文章,说检查过的异常很糟糕。这是否表明在某些情况下建议进行方便的吞咽异常?
地狱没有。这仅仅意味着在某些情况下应该首选未经检查的异常,特别是当用户不知道如何处理已检查的异常(例如SQL异常)时,或者如果无法恢复时,...
- 一张图片值1000字。我猜一些例子在这里会有很多帮助。
Spring DataAccessException
就是一个很好的例子。查看章节10. DAO support。
答案 5 :(得分:1)
返回值与抛出异常
异常和返回值之间的根本区别在于返回值是传递给您的直接调用方,而异常则传递给调用堆栈中任何位置的catch子句。这允许为许多不同类型的异常重用相同的异常处理程序。当且仅当您需要该功能时,我建议您支持异常而不是返回代码。
绩效影响。
每条指令都会对性能产生负面影响,包括catch-blocks中的指令。但是,任何现代CPU都可以每秒抛出和处理数百万个异常,所以除非你抛出数千个异常,否则你不会注意到任何事情。
特殊例外
对于投掷,具体允许特定处理。 对于处理,您可以是通用的,但是您应该知道可以将任意异常传递给您的处理程序,包括未被查询的未被检查的那些。
检查
争论是否应该使用已检查或未检查的异常。 永远不要吞下一个例外。处理或重新抛出它。如果您不丢弃有关失败的证据,它可以简化维护。
实施例
我最近工作的应用程序通过网络接收命令然后执行。这通常涉及与远程系统的进一步交互,这可能由于许多原因而失败。执行命令的方法不会捕获任何异常,让它们将调用堆栈冒泡到命令侦听器中的中央异常处理程序,执行以下操作:
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;
}
}
}
我们故意没有赶上&amp;在处理逻辑中重新抛出异常,因为我们认为这不会增加任何价值。
答案 6 :(得分:1)
se-radio制作了关于错误处理主题的播客剧集,解释了如何使用例外的一些哲学,可以将其重述为“在何处吸收它们”。
我保留的主要内容是大多数功能应该让它们冒泡,大多数异常细节应该最终出现在日志文件中。然后这些函数只传递全局消息,说明发生了什么事。
从某种意义上说,这会导致一种异常层次结构:每层代码一层。
正如我认为的那样,向用户解释这样的数据库集群因DNS不可用或磁盘已满而失败是没有意义的。在那个级别,发生了一些无法完成交易的事情,这是用户必须知道的。
当然,开发人员/管理员很乐意看到更多细节,这就是为什么在数据库层应该记录特定的例外情况。
答案 7 :(得分:1)
异常就是这样,任务的程序员不必自己处理问题。 (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) {}
如果用户数量很大,替换代码的性能会更好。
当发生数据库错误时,应该返回一个空值,错误代码还是抛出异常? Ans:取决于什么样的错误。就像你找不到用户一样,这不是一个错误。但是如果密码错误或连接断开,这些都是错误,因为试图以正常方式处理它会使程序复杂化。
(1)。使用过多的try-catch()会对性能产生负面影响? Ans:根据“Effective Java”的说法,就我记忆而言,它的效果非常微小(只有不好的循环)(我现在没有这本书)。
(2)。 使用特定的异常类型更好? 答:用户特定的一个最好避免解决错误的问题。
如果我错过了可能发生的X类异常之一怎么办?坦率地说,我听说并且只用了10%,我认为在2 - 3年内会出现Java标准异常。 Ans:就像你毫无例外地处理错误一样,你也可以错过它。你只需在发现它时添加它。
是的,有人说,如果来电者不知道如何处理被抛出的异常,他就没有权利调用投掷方法。是对的吗? 答:不,如果我不知道如何处理某些异常,请重新抛出它。
(3)。我读过Anders Hejlsberg的这篇文章,说检查过的例外情况很糟糕。这是否表明在某些情况下建议进行方便的吞咽异常? Ans:我认为他正在讨论“检查异常”作为编译器的一项功能,以确保应该处理一些异常。有异常的想法。
(4)。一张图片值1000字。我猜一些例子在这里会有很多帮助。 Ans:上面的代码。
我现在跑了......抱歉...... :-p(请等一下,亲爱的!)
答案 8 :(得分:1)
请注意,如果发生非致命错误,请不要返回null。改为返回NullObject。
否则,在每次调用代码后都需要进行空检查,这很麻烦,如果忘记会导致代码崩溃。