【问题标题】:Is there an beautiful way to assert pre-conditions in Java methods?有没有一种漂亮的方法来断言 Java 方法中的前置条件?
【发布时间】:2011-10-12 13:52:08
【问题描述】:

我的很多函数在声明下方都有一大堆验证代码:

if ( ! (start < end) ) {
    throw new IllegalStateException( "Start must be before end." );
    }

我想精确指定某些输入的有效范围 - 例如 A > B、C => 1 或 str_d.length() > 0。

鉴于我的一些函数有很多必须验证的参数,我最终可以编写大量样板来验证前置条件。我正在编写一个主要供非技术开发人员使用的库,我们发现验证函数输入是帮助用户正确操作 API 的最佳方式。我们越早提出错误,客户要做的工作就越少。

是否有更优雅的方法来指定我的方法中的前置条件、后置条件(可能还有不变条件)。

一位同事告诉我 Eiffel 编程语言的一个特性,它允许以非常自然的方式描述前/后/不变条件,而无需重复大量样板代码。 Java 语言是否有一个附加组件可以让我使用其中的一些魔法?

【问题讨论】:

    标签: java contract eiffel


    【解决方案1】:

    如果我发现自己在一个类中重复了相同的样板前置条件检查代码,我会重构我的代码以减少重复并通过将重复的代码提取到新的 (static private) 方法中来增加抽象。我使用 Java-7 Objects.requireNonNull 方法进行 null 检查。

    【讨论】:

    • 如果您正在构建其他人将使用的 SDK,这必须是最佳选择。不需要额外的依赖。 :)
    【解决方案2】:

    对于输入验证,您也可以使用Apache Commons Validator

    请注意,应始终启用输入验证。因此,它在概念上与断言检查(例如,在 Eiffel 中)非常不同,断言检查可以选择开/关——请参阅此相关堆栈溢出问题的答案When should I use Apache Commons' Validate.isTrue, and when should I just use the 'assert' keyword?

    【讨论】:

      【解决方案3】:

      查看Cofoja 项目,该项目通过注释为 Java 提供合同。它提供了前置/后置条件和不变量。此外,与其他 Java 实现相比,它正确处理父类/接口中定义的合同。合约评估可以在运行时启用/禁用。

      这是他们tutorial的代码sn-p:

      import com.google.java.contract.Invariant;
      import com.google.java.contract.Requires;
      
      @Invariant("size() >= 0")
      interface Stack<T> {
        public int size();
      
        @Requires("size() >= 1")
        public T pop();
      
        public void push(T obj);
      }
      

      【讨论】:

        【解决方案4】:

        GuavaPreconditions 课程就是为了这个。您通常将它与静态导入一起使用,因此您的示例如下所示:

        checkArgument(start < end, "Start must be before end");
        

        如果检查通过,也可以轻松地向消息中添加更多信息,而无需支付String 连接的成本。

        checkArgument(start < end, "Start (%s) must be before end (%s)", start, end);
        

        assert 语句不同,这些语句不能被禁用。

        【讨论】:

          【解决方案5】:

          Aspect oriented programming 可用于此类问题。可以拦截方法调用以检查不变量。切入点和建议以声明方式配置。 SpringGuice 使 AOP 的使用变得简单。

          这是example in Guice

          【讨论】:

          • 这很有趣——你能给我举个 Guice 的例子吗?
          • 我不认为 Guice/AOP 是一般前置条件检查的好建议。它非常适合大型横切检查(尤其是您可能只想在测试中进行的检查),例如“发出请求的人是否是登录用户”,但更具体的检查,例如“A
          【解决方案6】:

          您可以通过注解和面向方面的编程来做到这一点。

          如果参数组合不合法,我会使用 IllegalArgumentException。我会使用 IllegalStateException is in a state that prevent the method from working.

          您可以为异常创建辅助方法。

          public static void check(boolean test, String message) {
              if(!test) throw new IllegalArgumentException(message);
          }
          
          check(start < end, "Start must be before end.");
          

          【讨论】:

          • 绝对简洁!
          【解决方案7】:

          JUnit 包具有诸如 assert 之类的结构,可帮助进行此类条件检查。

          【讨论】:

          • 您不仅要在单元测试中使用它,还应该能够在运行时使用它。
          • 因为测试库被认为只用于测试代码,不能成为生产代码的传递依赖。否则你的依赖库/项目将受到影响。
          【解决方案8】:

          assert start &lt; end 怎么样。看看documentation

          【讨论】:

          • 是的,我主要是一名 pthon 开发人员。这是我们将在 Python 中使用的语法,但是我注意到这种断言通常在运行时被禁用。如果 end
          • assert start &lt; end throws AssertionError documentation - 如果我们只想检查方法的参数,这是不正确的工作。 checkArgument() throws IllegalArgumentException 从业务角度来看是正确的
          • 断言默认被禁用(因此不会在生产中运行);将-ea 传递给 JVM Args 以在开发期间启用它们
          • 近九年后再看我的答案,我肯定会建议使用@ColinD 在stackoverflow.com/a/6816469/218454 中的答案并使用番石榴。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-03-22
          • 1970-01-01
          • 2011-09-12
          • 1970-01-01
          • 1970-01-01
          • 2020-07-31
          • 2018-01-07
          相关资源
          最近更新 更多