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

时间:2010-02-02 14:57:59

标签: java performance exception exception-handling overhead

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

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

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

public User getUser(String name);

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

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;
}

14 个答案:

答案 0 :(得分:8)

  

“哪会导致   NullPointerException和崩溃。“

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

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

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

答案 1 :(得分:7)

有性能成本,但这不是主要问题。 你真的希望你的代码充满了像你的例子一样的catch块吗?那太可怕了。

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

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

答案 2 :(得分:7)

抛出异常会导致性能下降,但这通常是可以接受的,因为异常处理代码只在异常情况下执行。如果你开始使用流量控制的例外,那么你就会转向通常的情况并使它们成为预期的行为。我强烈建议不要这样做。

答案 3 :(得分:5)

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

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

JSR-305中提供了这些注释,显然还有reference implementation


就性能而言,创建异常是一个昂贵的部分(我读过它是由于堆栈跟踪填充等)。抛出异常很便宜,并且是JRuby中使用的control-transfer technique

答案 4 :(得分:5)

一般情况下,Java中的非常昂贵例外,用于流量控制!

例外的一点是描述例外,例如NumberIsZeroException并不是那么特别,而PlanesWingsJustDetachedException显然是非常特殊的。如果用户是null在您的软件中非常特殊,因为数据损坏或ID号与任何类似的东西都没有匹配,那么就可以使用异常。

除了“幸福的道路”之外,还有什么例外。虽然这不适用于您的示例代码,但有时使用Null Object而非返回普通null非常有用。

答案 5 :(得分:3)

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


即使你没有问,很多人可能会告诉你你想象的方法似乎不太吸引人......: - (

你强行打电话的代码非常丑陋,难以编写和维护。

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

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

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

答案 6 :(得分:1)

有关例外的表现,请参阅this answer

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

答案 7 :(得分:1)

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

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

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);

答案 8 :(得分:1)

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

最终,你可以将其归结为风格,但我认为风格非常糟糕。

答案 9 :(得分:1)

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

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

答案 10 :(得分:0)

  

这样的设计方法将如何?   效果表现?做好处   超出成本或者只是简单的   糟糕的编码?

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

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

根据您的上一次修改。我认为(正如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

答案 13 :(得分:0)

异常花费很多,因为每次抛出异常时,都必须创建并填充堆栈跟踪。

想象一下,由于缺乏资金,1%的案件失败了余额转移操作。 即使故障率相对较低,性能也可能受到严重影响。 有关源代码和基准测试结果,请参阅here