【问题标题】:Try Catch Exception Helper尝试捕获异常助手
【发布时间】:2020-10-11 02:20:49
【问题描述】:

我已经使用另一个函数中的 Exception 参数创建了一个返回错误消息的助手。(这是一个示例)

void func(int n){ 
    try { 
        // this will throw ArithmeticException if n is 0 
        int x = 10 / n; 
        int y[] = new int[n]; 
        y[x] = 10; 

        // this will throw ArrayIndexOutOfBoundsException 
        // if the value of x surpasses 
        // the highest index of this array 
        System.out.println("No exception arose"); 
    } 
    catch (Exception e) { 
        System.out.println(getErrorType(e)); 
    } 
} 

String getErrorType(Exception e){
    String errorMessage = "";
    if (e instanceof ArithmeticException) 
        errorMessage = "ArithmeticException, Can't divide by 0";
    if (e instanceof ArrayIndexOutOfBoundsException) 
        errorMessage = "ArrayIndexOutOfBoundsException, This index doesn't exist in this array";
    else
        errorMessage = "error";
    return errorMessage;
}

如您所见,我使用 instanceof 来获取 Exception 类型,并且我可以向 String 函数添加更多信息,例如来自错误的消息。我也可以在这个助手中包含许多错误类型。 我的问题是,

  • 这在 Java 中好用吗?
  • 这样使用有很多不便之处?
  • 还有另一种方法可以实现我正在尝试做的事情吗?
  • 它如何影响性能?

我已经知道 try catch 块中的好用处是使用特定的异常添加带有特定异常的捕获级别。但我想让它成为通用的。

【问题讨论】:

    标签: java spring performance try-catch try-catch-finally


    【解决方案1】:

    通常,您希望在 try/catch 子句中指定您期望发生的异常,这样当其他人检查您的代码时,他们就会知道如果他们需要处理它们会发生什么异常。

    您实际上也可以通过执行以下操作将您的 catch 子句合并为一个:

    try { 
        // this will throw ArithmeticException if n is 0 
        int x = 10 / n; 
        int y[] = new int[n]; 
        y[x] = 10; 
    
        // this will throw ArrayIndexOutOfBoundsException 
        // if the value of x surpasses 
        // the highest index of this array 
        System.out.println("No exception arose"); 
    } 
    catch (ArithmeticException | ArrayIndexOutOfBoundsException | SomeOtherException e) { 
        e.printStackTrace(); 
    } 
    

    【讨论】:

    • 你是对的,这是另一种处理方式,但是 Exception 包含所有这些 ExceptionType。
    【解决方案2】:

    这似乎是很多额外的工作,我看不出将所有异常处理代码放在一个大函数中的好处。 Java 的最佳实践是始终保持代码模块化并尽可能利用该语言。

    如果只是您想要的详细信息,使用自定义消息重新抛出异常会更简洁。这是一个简单的例子:

    int divide (int a, int b) {
        try {
            return a / b;
        } catch (ArithmeticException e) {
            throw new ArithmeticException("Divide by 0", e);
        }
    }
    

    请记住,您希望以不同的方式处理异常,因为您不希望应用程序因可以修复的小问题而终止——这不是好的设计。如果您的应用程序遇到可以恢复的问题,例如用户输入错误,您可以捕获异常并停止崩溃。使用通用异常会阻止你这样做,因为你不知道抛出了什么异常。您可以在 getErrorType 函数中进行处理,但您最终可能会得到以下结果:

    String getErrorType(Exception e){
        String errorMessage = "";
        if (e instanceof ArithmeticException) 
            errorMessage = "ArithmeticException, Can't divide by 0";
            warnUserToChangeValue();
        if (e instanceof IllegalArgumentException) 
            errorMessage = "Bad Argument";
            warnUserToChangeValue();    
        if (e instanceof ArrayIndexOutOfBoundsException) 
            errorMessage = "ArrayIndexOutOfBoundsException, This index doesn't exist in this array";
        else
            errorMessage = "error";
        return errorMessage;
    }
    

    这真的很难读。我建议保持简单,并根据您的需要进行捕获:

    catch (ArithmeticException e |  IllegalArgumentException e) 
    {
        // The user probably entered a bad value, no reason to terminate, just tell them
        warnUserToChangeValue(); 
    }
    

    【讨论】:

    • 使用这个助手的想法是只返回带有异常类型的错误消息,不包括额外的逻辑,正如你所说,代码将很难阅读。另一个想法与在 catch 块中仅使用 Exception 参数有关。
    • 返回异常类型的错误消息会破坏捕获异常的目的。我建议阅读 Java 教程中的这篇文章:docs.oracle.com/javase/tutorial/essential/exceptions/…
    【解决方案3】:

    捕获 Exception 通常是一种不好的做法,因为它还会吞下不相关的错误,例如 NullPointerException、IllegalStateException、IllegalArgumentException 和其他属于 BUGS 并且确实应该继续传播的 RuntimeException。

    所以使用:

    catch (ArithmeticException | SomeOtherException e) {
       ...
    }
    

    捕获 IllegalArgumentException 是非常糟糕的做法,除非在错误处理的最顶层。在这种情况下,您所能做的就是在某处(如日志)报告它。

    这是一个错误。

    这个规则的一个例外是当你有一个像线程这样的东西,因为它非常关键,所以永远不应该被中断。

    在这种情况下,捕获 Throwable 会更有效。

    【讨论】:

      【解决方案4】:

      TL;DR

      扔掉整个 try/catch 块,让最顶层的方法告诉用户你不能执行他命令你做的任务。

      概念

      您似乎对如何处理异常情况有一个(广泛的)误解,即“异常是不好的!我必须尽早发现它们!”那是错误的。例外情况非常适合告诉我的来电者我的失败:“我无法完成我的工作。你决定是否可以不做,以其他方式重试,或者只是向你的来电者发出信号”。

      您的func() 有一些工作要做(无论“合同”是什么)。从方法中抛出异常是为了表明该方法没有(完全)完成它的工作。

      在您的情况下,func() 调用者没有机会发现该方法无法执行任何操作(因此很可能没有履行其合同)。所以你的调用者会愉快地继续,不知道他的算法的一个关键步骤失败了,从而产生后续错误,或者更糟:数据废话。

      所以,不捕获异常肯定更好,异常会波及到你的调用者,因此他会被告知失败并且可以决定即使在func()失败后他是否可以明智地继续。

      在大多数情况下,这个决定是“如果我的算法中的任何东西失败了,整个事情都会失败,我想立即中止它的所有计算”。如果你什么都没发现,这就是你从异常中免费获得的东西。

      你的 catch 子句

      您尝试提供比 Java 运行时与异常关联的自动文本形式更好地描述问题的用户友好消息。

      乍一看,这似乎是合理的,但是......

      如果 func() 是在您的代码深处使用的帮助程序,那么坐在他机器前的最终用户不会发现您的措辞比 Java 文本更有帮助。只有当您正在实现一个用户明确想要将两个数字相除的桌面计算器时,一个关于除以零的错误文本才会对用户有所帮助。在典型情况下,他无法将该除以零与他想要从您的程序中得到的任何东西(例如信用利息计算)联系起来。

      回答您的问题

      • 如上所述,您的一般方法有一些缺点。我怀疑您是否可以仅从异常对象创建一个良好的、用户可感知的错误消息,而不依赖于它发生的代码。在 Java 中使用“instanceof”几乎总是一个坏主意。
      • 是的,不方便。你写了很多行,却没有任何有价值的好处。
      • 如果您确实希望保留与异常类型相关的文本,请为不同的异常类使用多个 catch 子句。
      • 我认为这不会产生相关的性能差异。如果错误处理的性能很重要,那么您的软件必须解决一个非常奇怪的领域。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-13
        • 2011-12-05
        • 2014-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多