it-roy-ru.com

Стиль конструктора Java: параметры проверки не равны NULL

Каковы лучшие практики, если у вас есть класс, который принимает некоторые параметры, но ни один из них не может быть null?

Следующее очевидно, но исключение немного неопределенно:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null || two == null)
        {
            throw new IllegalArgumentException("Parameters can't be null");
        }
        //...
     }
}

Здесь исключения позволяют узнать, какой параметр имеет значение null, но конструктор теперь довольно уродлив:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
  }

Здесь конструктор аккуратнее, но теперь код конструктора на самом деле отсутствует в конструкторе:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        setOne(one);
        setTwo(two);
     }


     public void setOne(Object one)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        //...
     }

     public void setTwo(Object two)
     {
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
     }
  }

Какой из этих стилей лучше? 

Или есть альтернатива, которая более широко принята?

61
user130076

Второй или третий. 

Потому что он сообщает пользователю вашего API, что именно пошло не так.

Для меньшей детализации используйте Validate.notNull(obj, message) от commons-lang. Таким образом, ваш конструктор будет выглядеть так:

public SomeClass(Object one, Object two) {
    Validate.notNull(one, "one can't be null");
    Validate.notNull(two, "two can't be null");
    ...
}

Размещение чека в установщике также допустимо, с тем же комментарием многословия. Если ваши сеттеры также играют роль сохранения целостности объекта, вы можете выбрать и третий.

84
Bozho

Вы можете использовать одну из множества библиотек, разработанных для облегчения проверки предварительных условий. Много кода в Google Guava использует com.google.common.base.Preconditions

Простые статические методы, вызываемые в начале ваших собственных методов для проверки правильности аргументов и состояния. Это позволяет такие конструкции, как

 if (count <= 0) {
   throw new IllegalArgumentException("must be positive: " + count);
 }

заменить на более компактный

 checkArgument(count > 0, "must be positive: %s", count);

Он имеет checkNotNull то есть широко используется в Гуаве . Вы можете написать:

 import static com.google.common.base.Preconditions.checkNotNull;
 //...

 public SomeClass(Object one, Object two) {
     this.one = checkNotNull(one);
     this.two = checkNotNull(two, "two can't be null!");
     //...
 }

Большинство методов перегружены и не принимают ни сообщения об ошибках, ни фиксированных сообщений об ошибках, ни шаблонных сообщений об ошибках с переменными.


На IllegalArgumentException против NullPointerException

В то время как ваш исходный код генерирует IllegalArgumentException в аргументах null, вместо этого Preconditions.checkNotNull в Guava выдает NullPointerException.

Вот цитата из Effective Java 2nd Edition: Пункт 60: Поддерживайте использование стандартных исключений:

Можно утверждать, что все ошибочные вызовы методов сводятся к недопустимому аргументу или недопустимому состоянию, но другие исключения стандартно используются для определенных видов недопустимых аргументов и состояний. Если вызывающая сторона передает null в некотором параметре, для которого нулевые значения запрещены, соглашение предписывает, что NullPointerException будет выброшен, а не IllegalArgumentException.

NullPointerException не зарезервирован только для доступа к членам ссылки null; довольно стандартно бросать их, когда аргумент null, когда это недопустимое значение.

System.out.println("some string".split(null));
// throws NullPointerException
36
polygenelubricants

Старый вопрос; еще один новый ответ (уже упоминавшийся в другом комментарии; но я думаю, что стоит дать свой ответ).

Java 7 добавил Java.lang.Objects.requireNonNull() к API, который может использовать каждый. Поэтому проверка всех аргументов на ноль сводится к короткому списку, например:

this.arg1 = Objects.requireNonNull(arg1, "arg1 must not be null");
this.arg2 = Objects.requireNonNull(arg2, "arg2 must not be null");

Примечания стороны:

  • убедитесь, что вы не изменили два аргумента - второй - это сообщение, которое будет использоваться для NPE, которое выбрасывается, если первый аргумент равен нулю (если вы измените их, ну, тогда ваша проверка никогда не подведет)
  • другая лучшая практика: если возможно, сделайте все члены вашего класса окончательными (чтобы вы могли быть уверены: когда какой-либо объект был успешно создан, все его члены не равны нулю; они не изменятся со временем)
29
GhostCat

У меня был бы полезный метод:

 public static <T> T checkNull(String message, T object) {
     if(object == null) {
       throw new NullPointerException(message);
     }
     return object;
  }

Я хотел бы, чтобы он возвращал объект, чтобы вы могли использовать его в назначениях, как это:

 public Constructor(Object param) {
     this.param = checkNull("Param not allowed to be null", param);
 }

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

4
Yishai

Помимо приведенных выше ответов, которые являются действительными и разумными, я думаю, что хорошо отметить, что, возможно, проверка на нуль не является необходимой «хорошей практикой». (Предполагая, что читатели, кроме ОП, могут принять вопрос как догматический)

Из блога Misko Hevery о тестируемости: Утверждать или не утверждать

3
qnoid

Сравнение способов проверки предварительных условий в Java - Guava против Apache Commons и Spring Framework против простых утверждений Java

public static void fooSpringFrameworkAssert(String name, int start, int end) {
        // Preconditions
        Assert.notNull(name, "Name must not be null");
        Assert.isTrue(start < end, "Start (" + start + ") must be smaller than end (" + end + ")");

        // Do something here ...
    }

    public static void fooApacheCommonsValidate(String name, int start, int end) {
        // Preconditions
        Validate.notNull(name, "Name must not be null");
        Validate.isTrue(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooGuavaPreconditions(String name, int start, int end) {
        // Preconditions
        Preconditions.checkNotNull(name, "Name must not be null");
        Preconditions.checkArgument(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooPlainJavaAsserts(String name, int start, int end) {
        // Preconditions
        assert null != name : "Name must not be null";
        assert start < end : "Start (" + start + ") must be smaller than end (" + end + ")";

        // Do something here ...
    }

это краткое изложение этой статьи: http://www.sw-engineering-candies.com/blog-1/comparison-of-ways-to-check-preconditions-in-Java

2
nekperu15739

Альтернативой выбрасыванию непроверенного исключения будет использование assert. В противном случае я бы выбрал проверенные исключения, чтобы вызывающий объект осознавал тот факт, что конструктор не будет работать с недопустимыми значениями.

Разница между вашими первыми двумя решениями - вам нужно подробное сообщение об ошибке, вам нужно знать, какой параметр не выполнен или достаточно знать, что экземпляр не мог быть создан из-за недопустимых аргументов?

Обратите внимание, что второй и третий пример не могут правильно сообщить, что оба параметра были нулевыми.

Кстати, я голосую за вариант (1):

if (one == null || two == null) {
    throw new IllegalArgumentException(
      String.format("Parameters can't be null: one=%s, two=%s", one, two));
}
1
Andreas_D

Аннотации для статического анализа также полезны, как в дополнение к, так и вместо проверок во время выполнения.

Например, FindBugs предоставляет аннотацию @NonNull. 

public SomeClass (@NonNull Object one, @NonNull Object two) {

1
Andy Thomas

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

0
Marc

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

Мои наставники говорят мне не изобретать велосипед! Их совет - использовать библиотеки. Они (вероятно) хорошо разработаны и проверены. Конечно, вы должны убедиться, что вы взяли библиотеку хорошего качества.

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

Сначала я написал свой собственный закрытый метод для проверки нулевых параметров. Это скучно и излишне. Я знаю, я должен поместить это в класс Утилиты. Но зачем мне писать это в первую очередь, когда кто-то уже это сделал? Я могу сэкономить время, не написав юнит-тест и не спроектировав существующий материал. Если вы не хотите заниматься спортом или учиться, я бы не рекомендовал это делать.

Недавно я начал использовать гуаву от Google, и обнаружил, что - наряду с общими Apache - когда вы начнете их использовать, вы не будете использовать только этот единственный метод. Вы обнаружите и будете использовать его все больше и больше. В конце концов, это сделает ваш код короче, более читабельным, более согласованным и более понятным.

Кстати: в зависимости от ваших целей, я бы пошел с 2 или 3, используя одну из упомянутых библиотек выше ...

0
uthomas