代码之家  ›  专栏  ›  技术社区  ›  Robert Campbell

如何在方法输入参数上放置验证约束?

  •  5
  • Robert Campbell  · 技术社区  · 15 年前

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

    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
    

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

    哪一个 many Java design-by-contract frameworks 提供最接近我的“喜欢”示例的功能?引发的异常应该是运行时异常(如IllegalArgumentExceptions),因此封装不会被破坏。

    8 回复  |  直到 7 年前
        1
  •  5
  •   Jared Russell    15 年前

    如果您正在寻找一个完全成熟的契约式设计机制,我将查看 Wikipedia page for DBC .

    如果你想找更简单的东西,你可以看看 Preconditions 来自Google集合的类,该集合提供了checkNotNull()方法。因此,您可以重写发布到的代码:

    public void myContractualMethod(final String x, final Set<String> y) {
        checkNotNull(x);
        checkArgument(!x.isEmpty());
        checkNotNull(y);
    }
    
        2
  •  2
  •   Michael Easter    15 年前

    我见过一种技术 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("");
        }
    }
    

    注意:实验时,注意 there may be a bug 在默认包中执行此操作。我当然已经失去了尝试它的脑细胞。

        3
  •  1
  •   Verhagen    15 年前

    有一个小的 Java Argument Validation 包,实现为纯Java。它附带了几个标准检查/验证。对于那些需要自己更具体验证的情况,它附带了一些辅助方法。对于多次发生的验证,只需使用您自己的扩展接口argumentvalidation,并创建从类argumentvalidationimpl扩展的实现类。

        4
  •  0
  •   Stephen C    15 年前

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

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

    并依靠Java来抛出一个 NullPointerException 如果 x null .你只需要改变你的“合同”就可以说NPE是针对某些类型的“你用非法参数打电话给我”的情况而抛出的。

        5
  •  0
  •   pmr    15 年前

    贾里德指出了各种将DBC支持到Java的框架。
    我发现最有效的方法是:只需在javadoc中记录您的合同(或者您使用的任何documentationframework;doxygen支持dbc标记)。
    让您的代码被大量的抛出和参数检查混淆,对您的读者来说并没有真正的帮助。文件是。

        6
  •  0
  •   srini.venigalla    15 年前

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

    …mymethod(@notnull string x,@notnullor zero string y){

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

    }

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

        7
  •  0
  •   GaryF    15 年前

    不是一个完全有效的解决方案,但是JSR-303有一个关于 method-level validation extension .因为它只是一个扩展建议,所以JSR-303的实现可以忽略它。找到一个实现有点复杂。我认为Hibernate验证器还不支持它,但我相信 agimatec-validation 有实验支持。我也没有用过,所以我不知道它们有多好用。不过,如果有人试一试的话,我会很感兴趣的。

        8
  •  0
  •   Erk    7 年前

    如果使用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)
    

    第一件好事情是上面的断言类是真正需要的:

    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)