【问题标题】:When should an IllegalArgumentException be thrown?什么时候应该抛出 IllegalArgumentException?
【发布时间】:2013-03-04 18:34:04
【问题描述】:

我担心这是一个运行时异常,所以应该谨慎使用它。
标准用例:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

但这似乎会迫使以下设计:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

使其恢复为已检查异常。

好的,但让我们开始吧。如果输入错误,则会出现运行时错误。所以首先,这实际上是一个很难统一实施的策略,因为您可能必须进行完全相反的转换:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

更糟糕的是 - 虽然检查 0 &lt;= pct &amp;&amp; pct &lt;= 100 客户端代码可能会静态执行,但对于更高级的数据(例如电子邮件地址,或者更糟糕的是,必须根据数据库检查的内容)并非如此,因此在一般客户端代码无法预验证。

所以基本上我的意思是我没有看到一个有意义的一致政策来使用IllegalArgumentException。似乎不应该使用它,我们应该坚持我们自己的检查异常。抛出这个的好用例是什么?

【问题讨论】:

    标签: java exception illegalargumentexception


    【解决方案1】:

    IllegalArgumentException 的 API 文档:

    抛出表明一个方法被传递了一个非法或不适当的参数。

    通过查看how it is used in the JDK libraries,我会说:

    • 在输入进入工作之前抱怨明显错误的输入似乎是一种防御措施,并导致某些事情在中途失败并显示无意义的错误消息。

    • 它用于抛出受检异常太烦人的情况(尽管它出现在 java.lang.reflect 代码中,在这种情况下,对抛出受检异常的荒谬程度的担忧并不明显)。

    我会使用IllegalArgumentException 对常用实用程序进行最后的防御性参数检查(尝试与 JDK 的使用保持一致)。或者期望错误参数是程序员错误,类似于NullPointerException。我不会用它来实现业务代码中的验证。我当然不会将它用于电子邮件示例。

    【讨论】:

    • 我认为“期望错误的参数是程序员错误”的建议与我所看到的使用方式最一致,因此接受这个答案。
    【解决方案2】:

    在谈论“错误输入”时,您应该考虑输入的来源。

    输入是由用户或您无法控制的其他外部系统输入的,您应该期望输入无效,并始终对其进行验证。在这种情况下抛出一个检查异常是完全可以的。您的应用程序应该通过向用户提供错误消息来从该异常中“恢复”。

    如果输入来自您自己的系统,例如您的数据库或应用程序的某些其他部分,您应该能够依赖它是有效的(它应该在到达那里之前已经过验证)。在这种情况下,抛出一个像 IllegalArgumentException 这样的未经检查的异常是完全可以的,它不应该被捕获(通常你​​不应该捕获未经检查的异常)。无效值首先到达那里是程序员的错误;)您需要修复它。

    【讨论】:

    • 为什么“你永远不应该捕获未经检查的异常”?
    • 因为未经检查的异常意味着由于编程错误而被抛出。不能合理地期望抛出此类异常的方法的调用者从中恢复,因此捕获它们通常没有意义。
    • Because an unchecked exception is meant to be thrown as a result of a programming error 帮我清理了很多脑子里的东西,谢谢:)
    【解决方案3】:

    “谨慎地”抛出运行时异常并不是一个真正的好策略——Effective Java 建议您在可以合理预期调用者恢复时使用检查异常。 (程序员错误是一个具体的例子:如果一个特定的情况表明程序员错误,那么你应该抛出一个未经检查的异常;你希望程序员有一个逻辑问题发生在哪里的堆栈跟踪,而不是试图自己处理。)

    如果没有恢复的希望,请随意使用未经检查的异常;抓住它们没有意义,所以这很好。

    不过,从您的示例中无法 100% 清楚地了解此示例在您的代码中的哪种情况。

    【讨论】:

    • 我认为“合理预期会恢复”是狡猾的。任何操作foo(data) 都可能作为for(Data data : list) foo(data); 的一部分发生,其中调用者可能希望尽可能多的成功,即使某些数据格式不正确。也包括程序错误,如果我的应用程序失败意味着交易无法通过,那可能会更好,如果这意味着核冷却脱机,那就不好了。
    • StackOverflowError 和这种情况,调用者不能合理地期望从中恢复。但听起来应该检查任何数据或应用程序逻辑级别的案例。这意味着你的空指针检查!
    • 在核冷却应用中,我宁愿在测试中失败,也不愿让程序员认为不可能通过的情况被忽视。
    • Boolean.parseBoolean(..) 会引发 IllegalArugmentException,即使“可以合理地预期调用者会恢复”。所以.....由您的代码来处理它或故障返回给调用者。
    【解决方案4】:

    IllegalArgumentException 视为先决条件检查,并考虑设计原则:公共方法应该知道并公开记录自己的先决条件。

    我同意这个例子是正确的:

    void setPercentage(int pct) {
        if( pct < 0 || pct > 100) {
             throw new IllegalArgumentException("bad percent");
         }
    }
    

    如果 EmailUtil 是不透明的,这意味着无法向最终用户描述先决条件是有原因的,那么经过检查的异常是正确的。第二个版本,更正了这个设计:

    import com.someoneelse.EmailUtil;
    
    public void scanEmail(String emailStr, InputStream mime) throws ParseException {
        EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
    }
    

    如果 EmailUtil 是透明的,例如它可能是相关类拥有的私有方法,IllegalArgumentException 当且仅当它的前提条件可以在函数文档中描述时才是正确的。这也是一个正确的版本:

    /** @param String email An email with an address in the form abc@xyz.com
     * with no nested comments, periods or other nonsense.
     */
    public String scanEmail(String email)
      if (!addressIsProperlyFormatted(email)) {
          throw new IllegalArgumentException("invalid address");
      }
      return parseEmail(emailAddr);
    }
    private String parseEmail(String emailS) {
      // Assumes email is valid
      boolean parsesJustFine = true;
      // Parse logic
      if (!parsesJustFine) {
        // As a private method it is an internal error if address is improperly
        // formatted. This is an internal error to the class implementation.
        throw new AssertError("Internal error");
      }
    }
    

    这种设计可以采用任何一种方式。

    • 如果先决条件的描述成本很高,或者如果该类旨在供不知道其电子邮件是否有效的客户使用,则使用ParseException。这里的顶级方法名为scanEmail,暗示最终用户打算通过它发送未经研究的电子邮件,因此这可能是正确的。
    • 如果可以在函数文档中描述先决条件,并且该类不打算进行无效输入并因此指示程序员错误,请使用IllegalArgumentException。尽管没有“检查”,但“检查”移动到记录功能的 Javadoc,客户端应遵守该功能。 IllegalArgumentException 客户无法事先判断他们的论点是非法的,这是错误的。

    关于 IllegalStateException 的说明:这意味着“此对象的内部状态(私有实例变量)无法执行此操作。”在客户端调用无法知道对象的状态不一致的情况下,最终用户无法看到私有状态,因此它优先于IllegalArgumentException。我没有一个很好的解释,什么时候它比检查异常更受欢迎,尽管像初始化两次,或者丢失一个未恢复的数据库连接,都是例子。

    【讨论】:

      【解决方案5】:

      在 oracle 官方教程中指出:

      如果可以合理地期望客户端从异常中恢复, 使其成为检查异常。如果客户无法采取任何措施来恢复 从异常中,使其成为未经检查的异常。

      如果我有一个使用JDBC 与数据库交互的应用程序,并且我有一个将参数作为int itemdouble price 的方法。从数据库表中读取对应项目的price。我只是将购买的item 的总数乘以price 的值并返回结果。虽然我总是确定在我的端(应用程序端),表中的价格字段值永远不会是负数。但是如果价格值出现怎么办?这表明数据库端存在严重问题。可能是运营商输入错误的价格。这是调用该方法的应用程序的另一部分无法预料也无法从中恢复的问题。它是您数据库中的BUG。因此,在这种情况下应该抛出IllegalArguementException(),这将表明the price can't be negative
      我希望我已经清楚地表达了我的观点..

      【讨论】:

      • 我不喜欢这个(oracle 的)建议,因为异常处理是关于如何恢复,而不是是否恢复。例如,一个格式错误的用户请求不值得让整个 Web 服务器崩溃。
      • @djechlin。 . .有时应用程序无法恢复。传递非法参数时通常是这种情况。这是调用者应该解决的问题,而不是被调用者。这就是运行时异常出现的地方。关于您的示例,我同意格式错误的用户请求不应使整个 Web 服务器崩溃。但我不认为格式错误的用户请求首先是无效的论点。 Web 服务器应该简要分析无效请求,并将这些垃圾很好地扔回调用者(可能带有有关如何修复它的提示)。问题解决了。
      【解决方案6】:

      任何 API 都应在执行任何公共方法之前检查其每个参数的有效性:

      void setPercentage(int pct, AnObject object) {
          if( pct < 0 || pct > 100) {
              throw new IllegalArgumentException("pct has an invalid value");
          }
          if (object == null) {
              throw new IllegalArgumentException("object is null");
          }
      }
      

      它们代表了应用程序中 99.9% 的错误,因为它要求执行不可能的操作,因此最终它们是应该使应用程序崩溃的错误(因此这是一个不可恢复的错误)。

      在这种情况下并遵循快速失败的方法,您应该让应用程序完成以避免破坏应用程序状态。

      【讨论】:

      • 相反,如果 API 客户端给我一个错误的输入,我应该崩溃我的整个 API 服务器。
      • 当然,它不应该让你的 API 服务器崩溃,而是向调用者返回一个异常,除了客户端,它不应该让任何东西崩溃。
      • 你在评论中写的不是你在答案中写的。
      • 让我解释一下,如果第三方客户端使用错误的参数(错误)调用 API,那么客户端应该会崩溃。如果是 API 服务器的错误调用带有错误参数的方法,那么 API 服务器应该崩溃。检查:en.wikipedia.org/wiki/Fail-fast
      猜你喜欢
      • 2014-07-25
      • 1970-01-01
      • 2011-03-22
      • 2011-11-10
      • 1970-01-01
      • 2020-03-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-02
      相关资源
      最近更新 更多