我们应该在什么时候创建自己的Java异常类?

我们应该在什么时候创建自己的Java异常类?,java,class,exception,api-design,Java,Class,Exception,Api Design,从良好的设计/实践角度来看,我们应该在什么时候创建和使用自定义Java异常类,而不是Java中已经预定义的异常类 在一些应用程序中,我看到几乎创建了自定义异常类,它们努力始终使用本机Java异常。另一方面,有些应用程序为(几乎)所有内容定义自定义异常。来自: 如果客户机代码没有有用的信息,请不要创建新的自定义异常。 以下代码有什么问题 public class DuplicateUsernameException extends Exception {} 它没有向客户机代码提供任何有用的信息,

从良好的设计/实践角度来看,我们应该在什么时候创建和使用自定义Java异常类,而不是Java中已经预定义的异常类

在一些应用程序中,我看到几乎创建了自定义异常类,它们努力始终使用本机Java异常。另一方面,有些应用程序为(几乎)所有内容定义自定义异常。

来自:

如果客户机代码没有有用的信息,请不要创建新的自定义异常。

以下代码有什么问题

public class DuplicateUsernameException extends Exception {}

它没有向客户机代码提供任何有用的信息,只是提供了一个指示性的异常名称。不要忘记Java异常类与其他类类似,您可以添加您认为客户机代码将调用的方法以获取更多信息

我们可以向DuplicateUsernameException添加有用的方法,例如:

public class DuplicateUsernameException
    extends Exception {
    public DuplicateUsernameException 
        (String username){....}
    public String requestedUsername(){...}
    public String[] availableNames(){...}
}
新版本提供了两个有用的方法:
requestedUsername()
,它返回请求的名称;以及
availableNames()
,它返回与请求的用户名类似的可用用户名数组。客户端可以使用这些方法通知请求的用户名不可用,并且其他用户名可用。但是如果您不打算添加额外的信息,那么只需抛出一个标准异常:

throw new IllegalArgumentException("Username already taken");

当然,当您希望能够以编程方式处理异常时,即很容易为不同的异常类型创建单独的catch语句,即:

try{    
  buyWidgets();
}
catch(AuthenticationException ex)
{
  promptForLogin();
}
catch(InsufficientFundsException ex)
{
  promptToRefillAccount();
}
//let other types of exceptions to propagate up the call stack
关于上述是否构成流量控制异常的不当使用

虽然异常比if-else语句的CPU开销更大(主要是由于构造堆栈跟踪的成本),但成本是相对的,应该在特定用例的上下文中进行评估。并不是每一段代码都需要在web上快速扩展,有些人发现读取和测试条件更加麻烦。例如,几乎所有事务管理器都使用异常实现提交回滚重试习惯用法。(尝试在不捕获异常的情况下编写事务重试特性)

另外,我们应该坚持关注点分离原则:不是每一段代码都需要处理每一种可能的情况。购买widget时不登录是否属于例外情况,取决于应用程序以及应用程序代码库中的特定位置。例如,您可以拥有一个服务,其中包含针对登录用户的操作。该服务中的方法处理身份验证是没有意义的——相反,这些方法希望调用链中的早期代码能够确保用户得到身份验证,因此,如果不是这样,只会抛出异常。因此,对于这些方法,未登录是一种例外情况

从良好的设计/实践角度来看,我们应该在什么时候创建和使用自定义java异常类,而不是java中已经预定义的异常类

当现有的异常名称不能满足您的需要时

另一个设计问题是扩展“good”异常类;例如,如果引发与I/O相关的异常,理想情况下应该继承
IOException
;如果异常指示程序员错误,则应继承
RuntimeException
(即,取消选中异常)

引发自定义异常还允许您以更精确的方式处理异常;例如,如果定义了继承
IOException
,则可以对其进行特殊处理:

try { ... }
catch (FooException e) { ... } // Catch it _before_ IOException!
catch (IOException e) { ... }

此外,异常与其他类一样是类,因此您可以添加自定义方法等;例如,Jackson定义了哪些继承了
IOException
。如果您捕捉到它,您可以使用获取解析错误的位置信息。

如果需要传递更多信息而不仅仅是错误消息,我通常会创建自定义异常

例如,特定错误代码或实际值与预期值。它们也可以有自己的getter,因此您可以通过编程检索这些字段,而不必解析字符串消息,如果您更改消息的文本,字符串消息可能会中断。(或将其翻译成另一种语言)


如果您创建自己的异常,我建议扩展常见的JDK内置异常,这样您的API就可以说
抛出IOException
,但它实际上抛出
MycustomIOException
。这样,API的用户就不必知道您自己的自定义版本,除非他们愿意。

在您的应用程序中有自定义异常很好,一个可能是应用程序的顶级自定义异常,另一个可能是模块/包级别的自定义异常。如果您在应用程序中有一些特定的功能/操作,并且您需要让用户知道在操作过程中是否发生任何异常,那么最好为该操作添加自定义异常。调试/调查问题将很容易。

当您使用自己的异常为代码库增加价值时,您可以这样做

就这么简单。价值可以是:

  • 在更高级别上捕获可能更容易,例如,当所有异常都有一个公共基类型时
  • 您的异常包含附加数据(我们在自己的异常中包含NLS密钥,以便更高层知道如何在考虑I18N的情况下向人类用户发送消息)

不要只抛出
异常
!这意味着您必须捕获
异常
,这反过来意味着您还必须捕获所有
运行时异常
,从而捕获所有NPE等等!“它不会给客户端代码提供任何有用的信息,除了指示性的异常名称”:但如果您需要对重复的名称进行特定的错误处理,例如提示输入其他名称,则这可能很有用。使用Java构建REST API服务器时,对于不同类型的错误,有不同的异常难道不合乎逻辑吗?