Есть ли прекрасный способ утвердить предварительные условия в Java-методах?

Многие мои функции имеют целую нагрузку кода проверки чуть ниже деклараций:

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

Я хотел бы точно указать допустимые диапазоны определенных входных данных - например, A > B, C = >  1 или str_d.length() > 0.

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

Есть ли более элегантный метод для определения предварительных условий, пост-условий (и, возможно, инвариантных условий) в моих методах.

Коллега рассказал мне об особенностях языка программирования Эйфеля, который позволяет описать предварительные/пост-инвариантные условия очень естественным образом, не повторяя много шаблонов. Есть ли дополнение к языку Java, которое позволит мне использовать некоторые из этих магии?

Ответ 1

Guava Preconditions класс просто для этого. Обычно вы используете его со статическим импортом, поэтому ваш пример будет выглядеть так:

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

Это упрощает добавление дополнительной информации в сообщение, не оплачивая стоимость конкатенации String, если проверка проходит.

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

В отличие от операторов assert, они не могут быть отключены.

Ответ 2

Просмотрите проект Cofoja, который предоставляет контракты для Java через аннотации. Он предоставляет Pre-/Postconditions и Invariants. Кроме того, в отличие от других реализаций Java, он правильно обрабатывает контракты, определенные в родительских классах/интерфейсах. Оценка контракта может быть включена/отключена во время выполнения.

Вот фрагмент кода из учебник:

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

Аспектно-ориентированное программирование может быть использовано для такой проблемы. Вызов метода может быть перехвачен проверкой на инвариант. Пояснения и советы настраиваются декларативным способом. Spring и Guice делают использование АОП простым.

Здесь пример в Guice.

Ответ 5

Возможно, вы сможете сделать это с помощью аннотации и ориентированного на аспект программирования.

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

Вы можете создать вспомогательный метод для исключения.

public static void check(boolean test, String message) {
    if(!test) throw new IllegalArgumentException(message);
}

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

Ответ 6

Для проверки ввода вы также можете использовать Apache Commons Validator.

Обратите внимание, что входная проверка всегда должна быть включена. Следовательно, он концептуально отличается от проверки утверждения (как, например, в Eiffel), которая может быть необязательно включена/выключена - см. Ответ на этот связанный вопрос о переполнении стека Когда следует использовать Apache Commons 'Validate.isTrue, и когда следует использовать ключевое слово' assert '?

Ответ 7

Если я обнаружил, что повторяю один и тот же код проверки предварительного кода котельной в классе, я реорганизую свой код, чтобы уменьшить дублирование и увеличить абстракцию, извлекая повторяющийся код в новый (static private) метод. Я использую метод Java-7 Objects.requireNonNull для проверок null.

Ответ 8

Пакет JUnit имеет такие конструкции, как assert, которые помогут выполнить проверку состояния.