在Java应用程序中引发运行时异常


12

我是承包商,以技术负责人的身份为我的客户设计企业Java应用程序。该应用程序将由最终用户使用,当我们离开时,将有一个支持团队为该应用程序提供支持。

与我一起工作的其他技术负责人给人的印象是异常处理会使代码变脏。系统应仅从服务层引发检查的异常,而其余代码应从所有其他层引发运行时异常,因此无需处理未检查的异常。

在业务应用程序中抛出未经检查的异常有什么需要?

根据我过去有关运行时异常的经验:

1)未经检查的异常使代码不可预测,因为它们甚至在Javadoc中也不会显示。

2)在业务应用程序中抛出未经检查的异常是没有意义的,因为当您将其抛出并直接出现在用户脸上时,您如何向用户解释?我已经看过足够多的Web应用程序,500 -Internal Error. Contact Administrator它对最终用户或管理该应用程序的支持团队没有任何意义。

3)引发运行时异常会强制引发异常的类的用户遍历源代码以进行调试,并查看引发异常的原因。只有在很好地记录了运行时异常的Javadoc的情况下,才可以避免这种情况,而我发现这种情况永远不会发生。


您能解释什么是服务层吗?该层距离用户最远还是最靠近用户?
Manoj R

为什么这个问题不适合SO?
Bjarke Freund-Hansen

@Manoj R:都没有。服务层或层是执行业务规则和逻辑的地方,与数据库技术和客户端表示的细节分开。
Michael Borgwardt 2013年

6
unchecked异常即使在Javadoc中也不会显示。=>如果您使用@throws标记将它们记录下来,则会出现它们,顺便说一句,这是一个好习惯。
assylias 2013年

4
@SureshKoya有效的Java,项目62:记录每种方法引发的所有异常。提取:使用Javadoc @throws标记记录方法可以抛出的每个未经检查的异常,但不要使用throws关键字在方法声明中包括未经检查的异常。
assylias 2013年

Answers:


13

受检查的异常是语言设计中的失败尝试。它们迫使您拥有极其泄漏的抽象和肮脏的代码。应尽可能避免它们。

至于你的观点:

1)经过检查的异常会使代码变得肮脏,并且由于它们随处可见,因此它的不可预测性也同样如此。

2)如何更好地向用户显示已检查的异常?已检查和未检查的异常之间的唯一真正区别是技术性的,仅影响源代码。

3)是否听说过堆栈跟踪?他们会告诉您确切的异常抛出位置,无论该异常是选中还是未选中。实际上,检查异常对于调试而言往往更糟,因为它们经常被包装,这导致更长和更丑陋的堆栈跟踪,或者甚至由于包装做错了而一起丢失。

异常有两种:一种是“正常”发生的,通常在非常接近它们发生的地方进行处理;另一种是真正例外的,可以在很高的层次上进行一般处理(只需中止当前操作并将其记录/显示错误)。

受检查的异常是在定义异常时将这种区别纳入语言语法的尝试。问题是

  • 区别实际上取决于调用者,而不是引发异常的代码
  • 它与异常的语义完全正交,但是将其与类层次结构绑定会迫使您将两者混为一谈
  • 整个异常点在于,您可以决定在什么级别捕获它们,而不必冒着无声丢失错误或不必在中间级别污染代码的风险;或者 检查异常会失去第二个优势。

1.如果发生未经检查的异常,您甚至都不知道呼叫是否可能导致未经检查的异常。2.当您说“它与异常的语义完全正交,但是将其与类层次结构绑定会迫使您将二者混在一起”时,我想您的意思是调用层次结构而不是类层次结构。
randominstanceOfLivingThing 2013年

2
@Suresh Koya:不,我的意思是类层次结构,事实上,声明SQLException extends Exception意味着选择抛出一个,SQLException因为错误与数据库访问自动相关,这意味着已检查了异常。而且您可以在Javadoc注释中记录未经检查的异常。适用于所有其他语言。
Michael Borgwardt 2013年

1
@MichaelBorgwardt“已检查的异常是语言设计中的失败尝试”,如果您不是我个人,我想阅读更多有关它的信息。任何真实的链接都将为您提供很大的帮助。
Vipin 2014年

2
@Vipin:这是我个人的看法,但其他许多人也有这样的看法,例如Br​​uce Eckel:mindview.net/Etc/Discussions/CheckedExceptions-而且自Java引入以来的20年中,没有其他语言采用过检查异常。本身……
Michael Borgwardt 2014年

2

我的观点是,抛出哪种类型的异常取决于您的代码在做什么。

当我期望检查异常可能经常发生时(例如,用户输入狡猾的数据),并且希望通过调用代码来处理这种情况,我会抛出检查异常。

当我遇到极少数情况(我不希望调用代码处理)时,会抛出未检查/运行时异常(不经常检查)。一个示例可能是某种与怪异的内存相关的错误,但我从未想到会发生。如果未选中,则可能会导致应用程序崩溃。

没有某种程度的支持,任何异常都不应出现在用户面前。即使只是“请剪切此错误转储并将其粘贴到电子邮件中”。被告知存在错误对用户而言,没有比这更令人讨厌的了,但是没有提供任何详细信息或操作人员可以采取行动来纠正此错误。

我的猜测是,您提到的哲学来自两个来源之一:

  • 懒惰的程序员试图避免做工作。
  • 或者是不得不支持相反代码的开发人员。即过度错误处理。这类包含大量异常处理的代码,其中大部分不起作用,甚至更糟,用于流控制和其他不正确的目的。

如果某些事情经常发生,那也不例外。但同意不应引发错误,用户对此一无所知。
Manoj R

我同意你的哲学。在您正在为用户开发的应用程序上,抛出运行时异常将有什么需要?
randominstanceOfLivingThing 2013年

另外,即使抛出运行时异常,我也希望使用@assylias所指的约定。
randominstanceOfLivingThing 2013年

1

如果您不希望未经检查的运行时异常,那么为什么要使用Java?几乎到处都有运行时异常的可能性-特别是NullPointerException,ArithmeticException,ArrayIndexOutOfBounds异常等。

而且,当您一次读取某些J2EE系统的日志文件(例如SAP NetWeaver安装)时,您会发现此类异常实际上一直在发生。


根据Java语言规范,“运行时异常类(RuntimeException及其子类)免于进行编译时检查,因为根据Java编程语言的设计者的判断,必须声明此类异常不会显着帮助确定正确性。 Java编程语言的许多操作和构造都可能导致运行时异常。”
randominstanceOfLivingThing 2013年

1
您正在谈论的运行时异常是Java产生的异常,因为Java运行时系统不知道您为何尝试执行特定的调用。例如:如果您调用的方法为null,则会出现NullPointerException。在这种情况下,Java运行时系统没有一个适合程序员的恰当词汇,并且会使用NullPointerException来给调用者打耳光。可悲的是,呼叫者已将Netweaver产品出售给您,而作为用户的您现在是不良编程的受害者。测试人员应同样负责,因为他们没有进行所有边界测试。
randominstanceOfLivingThing 2013年

1

您应牢记一些异常处理规则。但是首先,您需要记住,异常是代码公开的接口的一部分记录他们。当然,当接口是公共接口时,这尤其重要,但是在私有接口中也是一个很好的主意。

只能在代码可以对它们做一些明智的事情时处理异常。最糟糕的处理方法是对它们什么也不做,仅当这是正确的选项时才应执行此操作。(当我的代码中遇到这种情况时,我会为此添加一个注释,以使我知道不必担心空的身体。)

第二种最糟糕的选择是抛出一个不相关的异常,而没有附加原始异常。这里的问题是,原始异常中可以诊断问题的信息丢失了;您正在创建任何人都无法做的事情(除了抱怨“它不起作用”,而且我们都知道我们讨厌这些错误报告)。

记录异常要好得多。这样一来,人们就可以找出问题所在并加以解决,但是您只能将异常记录在否则可能会丢失或通过外部连接报告的地方。那不是因为更频繁地记录日志本身就是一个主要问题,而是因为过多的日志记录意味着您只会获得占用更多空间而不包含更多信息的日志。一旦你登录例外,你可以报告简要记录用户/客户端问心无愧(只要你还包括第二代的时间-或其他关联标识-在该报告中,这样的短版可以匹配(如有必要,请提供详细信息)。

当然,最好的选择是完全处理异常,全面处理错误情况。如果可以做到,一定要做到!这甚至可能意味着您可以避免必须记录异常。

处理异常的一种方法是引发另一个异常,该异常提供对问题的更高级描述(例如,“ failed to initialize”而不是“ index out of bounds”)。只要您不丢失有关异常原因的信息,这就是一个很好的模式。使用详细异常来初始化cause更高级别的异常记录详细信息(如上所述)。当您要跨越进程间边界(例如IPC调用)时,日志记录是最合适的,因为无法保证低级异常类将出现在连接的另一端。越过内部边界,最适合作为附带原因。

您看到的另一种模式是捕获并释放:

try {
    // ...
} catch (FooException e) {
    throw e;
}

除非您从其他catch子句中获得类型约束,否则这是一种反模式这意味着您不能只让异常自行通过。这只是Java的一个丑陋功能。

除了必须声明跨越方法边界的已检查异常之外,已检查异常与未检查异常之间没有真正的区别。@throws如果您知道自己的代码故意抛出了未经检查的异常(带有javadoc注释),那么仍然是一个好主意。不要故意抛出java.lang.Error或其子类(除非您正在编写JVM实现)。

意见:意外的错误情况始终表示代码中的错误。受检查的异常是管理这种威胁的一种方法,而开发人员故意使用未检查的异常作为避免处理错误案例的麻烦的方式,您将积累大量的技术债务,需要清理一些时间如果您想要健壮的代码。马虎的错误处理是不专业的(查看错误处理是确定程序员真正的水平的好方法)。


0

我认为您只能在应用程序可以恢复时抛出检查异常。因此,您的库的用户必须捕获这些异常并执行恢复所需的操作。其他所有内容均应取消选中。

举例来说

如果您的应用从文件或数据库加载数据。然后,..

try {
  File data = new File(...);
  // parse file here
} catch (Exception ex) {
  throw new MissingDataFileException("data file not found");
}

调用方总是可以捕获已检查的MissingDataFileException,然后尝试从数据库中加载数据。

try {
  Connection con = DriverManager.getConnection( host, username, password );
  // query data here
} catch (Exception ex) {
  throw new RuntimeException("better call Saul!");
}

1
扫罗3年前死于车祸。失败!;-)
Donal Fellows
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.