6

以下是实现此目标的典型方法:

public void myContractualMethod(final String x, final Set<String> y) {
    if ((x == null) || (x.isEmpty())) {
        throw new IllegalArgumentException("x cannot be null or empty");
    }
    if (y == null) {
        throw new IllegalArgumentException("y cannot be null");
    }
    // Now I can actually start writing purposeful 
    //    code to accomplish the goal of this method

我认为这个解决方案很丑陋。您的方法很快就会被检查有效输入参数协定的样板代码填满,从而掩盖了方法的核心。

这是我想要的:

public void myContractualMethod(@NotNull @NotEmpty final String x, @NotNull final Set<String> y) {
    // Now I have a clean method body that isn't obscured by
    //    contract checking

如果这些注释看起来像 JSR 303/Bean Validation Spec,那是因为我借用了它们。不幸的是,它们似乎不是这样工作的。它们用于注释实例变量,然后通过验证器运行对象。

在众多 Java 契约式设计框架中,哪一个提供了与我的“喜欢拥有”示例最接近的功能?抛出的异常应该是运行时异常(如 IllegalArgumentExceptions),因此封装不会被破坏。

4

8 回答 8

7

如果您正在寻找一个完全成熟的按合同设计机制,我会查看Wikipedia 页面上列出的 DBC的一些项目。

但是,如果您正在寻找更简单的东西,您可以查看 google 集合中的Preconditions类,它提供了一个 checkNotNull() 方法。因此,您可以重写发布到的代码:

public void myContractualMethod(final String x, final Set<String> y) {
    checkNotNull(x);
    checkArgument(!x.isEmpty());
    checkNotNull(y);
}
于 2009-11-27T14:44:56.560 回答
3

我看过Eric Burke的一种技术,大致如下。这是对静态导入的优雅使用。代码读起来非常好。

为了得到这个想法,这里是Contract类。这里很少,但可以根据需要轻松填写。

package net.codetojoy;

public class Contract {
    public static void isNotNull(Object obj) {
        if (obj == null) throw new IllegalArgumentException("illegal null");
    }
    public static void isNotEmpty(String s) {
        if (s.isEmpty()) throw new IllegalArgumentException("illegal empty string");
    }
}

这是一个示例用法。该foo()方法说明了静态导入:

package net.codetojoy;

import static net.codetojoy.Contract.*;

public class Example {
    public void foo(String str) {
        isNotNull(str);
        isNotEmpty(str);
        System.out.println("this is the string: " + str);
    }

    public static void main(String[] args) {
        Example ex = new Example();
        ex.foo("");
    }
}

注意:在试验时,请注意在默认包中执行此操作可能存在错误。我当然已经失去了尝试它的脑细胞。

于 2009-11-27T16:18:00.790 回答
1

有一个小的Java 参数验证包,以普通 Java 实现。它带有几个标准检查/验证。对于那些需要自己更具体的验证的情况,它带有一些辅助方法。对于多次发生的验证,只需扩展接口 ArgumentValidation,用你自己的并创建从类 ArgumentValidationImpl 扩展的实现类。

于 2010-01-28T22:13:49.697 回答
0

这并不能直接回答您的问题,但我认为您的部分问题是您过度验证。例如,您可以将第一个测试替换为:

if (x.isEmpty()) {
    throw new IllegalArgumentException("x cannot be empty");
}

并依靠 Java 抛出一个NullPointerExceptionif xis null。你只需要改变你的“合同”,说 NPE 是针对某些类型的“你用非法参数给我打电话”的情况。

于 2009-11-27T14:47:13.827 回答
0

Jared 向您指出了将 DBC 支持添加到 Java 的各种框架。
我发现最有效的是:只需在 JavaDoc 中记录您的合同(或您使用的任何文档框架;Doxygen 支持 DBC 标记。)
让您的代码被大量的抛出和检查参数混淆并没有真正帮助你的读者。文档是。

于 2009-11-27T15:44:20.780 回答
0

我会使用参数注释、反射和通用验证器类来创建应用程序范围的工具。例如,您可以编写一个类方法,如:

.. myMethod(@notNull 字符串 x,@notNullorZero 字符串 y){

if (Validator.ifNotContractual(getParamDetails()) {
    raiseException..
    or 
    return ..
}

}

类方法被“标记”以注释它们的合同要求。使用反射自动发现参数、它们的值和注释。将其全部发送到静态类以进行验证并让您知道结果。

于 2009-11-27T17:13:16.480 回答
0

不是一个完全有效的解决方案,但 JSR-303 有一个方法级验证扩展的提议。因为刚刚只是一个扩展提案,JSR-303 的实现可以随意忽略它。找到一个实现有点棘手。我不认为 Hibernate Validator 支持它,但我相信agimatec-validation有实验支持。我没有为此目的使用过,所以我不知道它们的工作情况如何。不过,如果有人试一试,我很想知道。

于 2010-04-13T09:00:03.640 回答
0

如果您使用的是 Java 8,则可以使用 lambdas 创建一个非常优雅且可读的验证解决方案:

public class Assert {

    public interface CheckArgument<O> {
        boolean test(O object);
    }

    public static final <O> O that(O argument, CheckArgument<O> check) {
        if (!check.test(argument))
            throw new IllegalArgumentException("Illegal argument: " + argument);
        return argument;
    }
}

你像这样使用它:

public void setValue(int value) {
    this.value = Assert.that(value, arg -> arg >= 0);
}

异常将如下所示:

Exception in thread "main" java.lang.IllegalArgumentException: Illegal argument: -7
    at com.xyz.util.Assert.that(Assert.java:13)
    at com.xyz.Main.main(Main.java:16)

第一个好处是上面的 Assert 类是真正需要的:

public void setValue(String value) {
    this.value = Assert.that(value, arg -> arg != null && !arg.trim().isEmpty());
}

public void setValue(SomeObject value) {
    this.value = Assert.that(value, arg -> arg != null && arg.someTest());
}

当然that()可以通过多种方式实现:使用格式字符串和参数,抛出其他类型的异常等。

然而,它并不需要被实现来执行不同的测试。

并不是说如果您愿意,您就不能预先打包测试:

public static CheckArgument<Object> isNotNull = arg -> arg != null;

Assert.that(x, Assert.isNotNull);

// with a static import:

Assert.that(x, isNotNull);

我不知道这是否对性能不利或出于其他原因不是一个好主意。(我自己刚开始研究 lambda,但代码似乎运行正常......)但我喜欢它Assert可以保持简短(不需要对项目可能不重要的依赖项)并且测试非常可见。

这是一个更好的错误消息的方法:

public static final <O> O that(O argument, CheckArgument<O> check,
    String format, Object... objects) 
{
    if (!check.test(argument))
        throw new IllegalArgumentException(
            String.format(format, objects));
    return argument;
}

你这样称呼它:

public void setValue(String value) {
    this.value = Assert.that(value, 
        arg -> arg != null && arg.trim().isEmpty(), 
        "String value is empty or null: %s", value);
}

出来了:

Exception in thread "main" java.lang.IllegalArgumentException: String value is empty or null: null
    at com.xyz.util.Assert.that(Assert.java:21)
    at com.xyz.Main.main(Main.java:16)

更新:如果您想将x = Assert...构造与预打包测试一起使用,结果将被转换为预打包测试中使用的类型。所以它必须被转换回变量的类型......SomeClass x = (SomeClass) Assert.that(x, isNotNull)

于 2018-04-11T14:25:28.817 回答