Как я могу установить проверочные ограничения на входные параметры моего метода? - PullRequest
5 голосов
/ 27 ноября 2009

Вот типичный способ достижения этой цели:

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), чтобы инкапсуляция не нарушалась.

Ответы [ 8 ]

5 голосов
/ 27 ноября 2009

Если вы ищете полноценный механизм проектирования по контракту, я бы взглянул на некоторые проекты, перечисленные на странице Википедии для DBC .

Если вы ищете что-то попроще, вы можете взглянуть на класс Preconditions из коллекций Google, который предоставляет метод checkNotNull (). Таким образом, вы можете переписать код, который вы отправили:

public void myContractualMethod(final String x, final Set<String> y) {
    checkNotNull(x);
    checkArgument(!x.isEmpty());
    checkNotNull(y);
}
2 голосов
/ 27 ноября 2009

Я видел технику Эрика Бёрка , которая примерно такая же, как следующая. Это элегантное использование статического импорта. Код читается очень хорошо.

Чтобы понять, вот класс 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("");
    }
}

Примечание: при экспериментировании обратите внимание, что может быть ошибка при выполнении этого в пакете по умолчанию. Я определенно потерял клетки мозга, пытаясь это сделать.

1 голос
/ 29 января 2010

Существует небольшой Проверка аргументов Java , реализованный в виде простой Java. Он поставляется с несколькими стандартными проверками / проверками. А для тех случаев, когда кому-то нужны свои более конкретные проверки, он поставляется с некоторыми вспомогательными методами. Для проверок, которые происходят несколько раз, просто расширьте интерфейс ArgumentValidation своим собственным и создайте реализующий класс, который выходит из класса ArgumentValidationImpl.

0 голосов
/ 11 апреля 2018

Если вы используете Java 8, лямбда-выражения можно использовать для создания очень элегантного и удобочитаемого решения для проверки:

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);

Понятия не имею, если это плохо для производительности или не очень хорошая идея по какой-то другой причине. (Я только начал изучать лямбды, но код, кажется, работает так, как должен ...) Но мне нравится, что 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)

0 голосов
/ 13 апреля 2010

Не полностью рабочее решение, но в JSR-303 есть предложение для расширения проверки уровня метода . Поскольку это всего лишь предложение по расширению, реализации JSR-303 могут его игнорировать. Найти реализацию немного сложнее. Я не думаю, что Hibernate Validator его поддерживает, но я считаю, что agimatec-validation имеет экспериментальную поддержку. Я не использовал ни для этой цели, поэтому я не знаю, насколько хорошо они работают. Мне было бы интересно узнать, если кто-то попробует.

0 голосов
/ 27 ноября 2009

Я бы использовал аннотации параметров, рефлексию и универсальный класс валидатора, чтобы создать средство для всего приложения. например, вы можете кодировать метод класса, например:

.. myMethod (@notNull String x, @notNullorZero String y) {

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

}

Методы класса «размечены», чтобы комментировать их требования к контракту. Используйте рефлексию для автоматического обнаружения параметров, их значений и аннотаций. Отправьте все это в статический класс для проверки и сообщите результат.

0 голосов
/ 27 ноября 2009

Джаред указал вам на различные фреймворки, которые добавляют поддержку DBC для Java.
Лучшее, что я нашел, это: просто документируйте свой контракт в JavaDoc (или любой другой документации, которую вы используете; Doxygen поддерживает теги DBC.)
Обманывание вашего кода множеством бросков и проверок ваших аргументов не очень полезно для вашего читателя. Документация есть.

0 голосов
/ 27 ноября 2009

Это не дает прямого ответа на ваш вопрос, но я думаю, что частью вашей проблемы является то, что вы перестарались с проверкой. Например, вы можете заменить первый тест на:

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

и положитесь на Java для выдачи NullPointerException, если x равно null. Вам просто нужно изменить свой «контракт», чтобы сказать, что NPE выбрасывается для определенных типов ситуаций «вы назвали меня с недопустимыми параметрами».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...