在Java中编写“exception驱动开发”的性能成本?

通过在Java中创建,抛出和捕获exception,是否有任何性能成本?

我计划将“exception驱动的开发”添加到一个更大的项目中。 我想设计自己的exception并将它们包含在我的方法中,迫使开发人员捕获并做适当的工作。

例如,如果您有一种方法可以根据名称从数据库中获取用户。

public User getUser(String name); 

但是,用户可能为null,并且在使用User的公共方法之前忘记检查这一点很常见。

 User user = getUser("adam"); int age = user.getAge(); 

这将导致NullPointerException和崩溃。 但是,如果我在返回user-object之前进行了检查,如果它为null并抛出’UserIsNullException’:

 public User getUser(String name) throws UserIsNullException; 

我强迫实施者思考和行动:

 try { User user = getUser("adam"); int age = user.getAge(); }catch( UserIsNullException e) { } 

它使代码更安全地发生意外崩溃并消除更多错误。 假设该网站每小时有数百名访问者,这种设计模式几乎无处不在。

这样的设计方法将如何影响性能? 这些好处是否会超过成本,还是只是简单的编码?

谢谢你的帮助!

UPDATE! 要清楚,我的注意力不是包装NullPointerException,正如我的例子所暗示的那样。 目标是强制实施者编写try / catch,从而避免真正崩溃的头痛:

user == null

被遗忘了。 问题涉及比较这两种设计模型:

 int age; try { User user = getUser("adam"); age = user.getAge(); }catch( UserIsNullException e) { age = 0; } 

与:

 int age; User user = getUser("adam"); if( user != null ) { age = user.getAge(); } else { age = 0; } 

“这将导致NullPointerException和崩溃。”

NullPointerException是一个exception,可以在catch中捕获。

因此,除非增加代码的清晰度,否则额外的exception是多余的。 在这种情况下,它确实没有。 实际上,您刚刚采用了未经检查的程序员错误exception并将其提升为已检查的exception。

为程序员的错误创建一个已检查的exception实际上意味着你的代码必须明确地处理程序员引入错误的可能性。

抛出exception会有性能损失,但这通常是可以接受的,因为exception处理代码仅在特殊情况下执行。 如果你开始使用流量控制的例外,那么你就会改变惯例并使它们成为预期的行为。 我强烈建议不要这样做。

有性能成本,但这不是主要问题。 你是否真的希望你的代码充斥着像你的例子那样的catch块? 那太糟了。

通常,Web应用程序有一个主要的exception处理程序,其中所有未被捕获的内容都会结束,至少在处理错误时会以干净的方式切断处理。 随着所有这些exception捕获的流程,您的流程将会像落下几层楼梯一样。

一些例外是您期望并可以处理的非常特殊的情况。 但是,一般情况下,当出现意外或无法控制的错误时会弹出exception。 从那时起,您的对象可能处于错误状态,因为后续步骤将期望由于exception而未发生的事情,并且尝试继续将仅触发更多exception。 让意外的exception消失会更好,因此它们可以被中央处理程序捕获。

通常,Java中的exception非常昂贵应该用于流量控制!

exception点是描述一些特殊的东西,例如, NumberIsZeroException并不是那么特别,而PlanesWingsJustDetachedException显然是非常特殊的。 如果你的软件真的很特别,那么用户是null因为数据损坏或id号与任何东西都不匹配,那么就可以使用例外。

什么例外也导致与“快乐道路”的分歧。 虽然这不适用于您的示例代码,但有时使用Null Object而不是返回plain null非常有用。

就个人而言,我认为这是一个糟糕而令人讨厌的想法。

鼓励人们检查空值的常用方法是使用@Nullable类的@Nullable (及其相反的@NotNull ,用于保证返回非空值的函数)。 通过在参数上设置类似的注释(以便设置参数期望),质量IDE和错误检查程序(如FindBugs)可以在代码没有进行足够的检查时生成所有必要的警告。

这些注释在JSR-305中可用,显然还有一个参考实现 。


就性能而言,创建exception是一个昂贵的部分(我读过它是由于堆栈跟踪填充等)。 抛出exception是便宜的,并且是JRuby中使用的控制转移技术 。

构建堆栈跟踪大约有一千条基本指令。 它有一定的成本。


即使你不问,很多人可能会告诉你你想象的方法似乎不是很吸引人…… 🙁

你强行打电话的代码真的很难看,难以编写和维护。

为了更具体,并试图理解,我将尝试强调几点。

  • 另一个开发人员负责检查从API收到的用户的无效性(如果文档明确说明的话)。 他的代码会在内部定期进行此类检查,因此他也可以在调用API后执行此操作。 更重要的是,这将使他的代码具有一定的同质性。

  • 当重用现有exception时,它比创建自己的exception要好得多。 例如,如果调用API并请求不存在的用户是错误的,则可以抛出IllegalArgumentException。

有关exception的性能,请参阅此答案 。

基本上在您的想法中,您将RuntimeException NullPointerException包装到已检查的Exception中; imho我会说可以使用ObjectNotFoundException在业务级别管理问题:您没有找到用户,用户为空并且此后会生成错误的事实。

我喜欢这种编码风格,因为从使用您的API的人的角度来看,它非常清楚地发生了什么。 我有时甚至命名API方法getMandatoryUser(String)getUserOrNull(String)来区分永远不会返回null和可以返回null

关于性能,除非您编写一个非常延迟关键的代码片段,否则性能开销将是可以忽略的。 但是,值得一提的是,在try / catch块方面有一些最佳实践。 例如,我相信Effective Java提倡在循环之外创建一个try / catch块,以避免在每次迭代时创建它; 例如

 boolean done = false; // Initialise loop counter here to avoid resetting it to 0 if an Exception is thrown. int i=0; // Outer loop: We only iterate here if an exception is thrown from the inner loop. do { // Set up try-catch block. Only done once unless an exception is thrown. try { // Inner loop: Does the actual work. for (; i<1000000; ++i) { // Exception potentially thrown here. } done = true; } catch(Exception ex) { ... } } while (!done); 

创建exception,抛出exception(使用堆栈跟踪),捕获exception然后垃圾收集该exception(最终)比执行简单的if检查要慢得多。

最终,你可以把它归结为风格,但我认为这是非常糟糕的风格。

空物体可能对您有所帮助。 我前几天正在阅读Martin Fowler的书“Refactoring”,它描述了使用一个特定的对象,该对象在返回null的位置起作用,使您不必一直检查NULL。

我不会在这里解释它,因为它在书中描述得非常好。

这样的设计方法将如何影响性能? 这些好处是否会超过成本,还是只是简单的编码?

我说过这种编程风格几乎没有任何好处。 应将exception理解为(嗯……) exception 。 它应该只在非正常情况下发生。 你怎么定义’正常情况’几乎是有争议的想法……

您可以忘记exception并避免出现这种性能问题。 这使您的API不言而喻。

 int getUserAge(String userName) { int age = 0; UserSearchResult result = getUser(userName); if (result.getFoundUser()) { User user = result.getUser(); age = user.getAge(); } return age; } 

根据你上次的编辑。 我认为(正如Tony所建议的)使用NullObject模式在这里会更有用。

考虑第三种情况:

 class NullUser extends User { public static NullUser nullUser = new NullUser(); private NullUser(){} public int getAge() { return 0; } } //Later... int age; User user = getUser("adam"); // return nullUser if not found. age = user.getAge(); // no catch, no if/null check, pure polymorphism 

exception成本很高,因为每次抛出exception时,都必须创建并填充堆栈跟踪。

想象一下,由于缺乏资金,平衡转移操作在1%的情况下失败。 即使故障率相对较低,性能也可能受到严重影响。 请参阅此处获取源代码和基准测试结果。