@Nonnull с разными IDE - предупреждают о ненужных проверках нуля
Я работаю в среде, где разработчики используют разные IDE - Eclipse, Netbeans и IntelliJ. Я использую аннотацию @Nonnull (javax.annotation.Nonnull), чтобы указать, что метод никогда не вернет null:
@Nonnull
public List<Bar> getBars() {
return bars; // this.bars is a final, initialized list
}
Я бы хотел, чтобы другие разработчики получили предупреждение, если они сделают одно из следующего:
- Измените метод, чтобы он возвращал ноль, не удаляя аннотацию @Nonnull
- Необязательно проверяйте нулевое значение для методов, которые явно определены с помощью @Nonnull: if (foo.getBars() == null) { ... сделать что-то... }
Первый сценарий поддерживается, например, IntelliJ. Второе нет; клиенты не предупреждены о том, что проверка на нулевое значение не требуется.
В любом случае мы планируем перейти к возврату клонов коллекций вместо самих коллекций, поэтому лучше забыть @Nonnull и сделать это вместо этого:
public List<Bar> getBars() {
return new ArrayList<Bar>(bars);
}
Редактировать:
Чтобы уточнить, я не рассматриваю изменение IDE для этого. Я хотел бы знать, поддерживается ли то, что я описал выше, упомянутыми IDE - или, альтернативно, если есть веская причина, почему это не поддерживается.
Я понимаю, что не слишком полагаюсь на контракты. Однако, если я напишу getBars () со стилем в последнем абзаце (вернет клон списка), то, например, IntelliJ пометит предупреждение для любого кода, который делает
if (foo.getBars() == null) { ... }
Если вы решите следовать этому предупреждению и удалить нулевую проверку, вы, похоже, будете в равной степени полагаться на то, что реализация getBars () не изменится. Однако в этом случае вы, кажется, зависите от деталей реализации, а не от явного контракта (как в случае с @Nonnull).
Редактировать № 2:
Я не беспокоюсь о скорости выполнения, нулевые проверки действительно очень быстрые. Я обеспокоен читаемостью кода. Учтите следующее:
Вариант А:
if ((foo.getBars() == null || foo.getBars().size() < MAXIMUM_BARS) &&
(baz.getFoos() == null || baz.getFoos().size() < MAXIMUM_FOOS)) {
// do something
}
Вариант Б:
if (foo.getBars().size() < MAXIMUM_BARS &&
baz.getFoos().size() < MAXIMUM_FOOS) {
// do something
}
Я думаю, что вариант B более читабелен, чем вариант A. Поскольку код читается чаще, чем пишется, я хотел бы убедиться, что весь код, который я (и другие члены нашей команды), пишу, максимально читаем.
3 ответа
Я бы порекомендовал следующее:
- Придерживаться вашего
@Nonnull
а также@Nullable
аннотации так же, как вы уже делаете это. - Определите и примените значение по умолчанию. На самом деле не имеет значения, какое значение по умолчанию, но оно должно быть одинаковым во всей вашей кодовой базе.
- Используйте FindBugs для проверки ваших случаев 1 и 2. В FindBugs есть плагины для большинства IDE, я упоминал Eclipse, Netbeans и IntelliJ, но есть и другие. (Дело № 2 охвачено правилом RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE. Я проверял его. Просто подумал, что должен упомянуть об этом после прочтения комментария DavidHarkness в другом месте на этой странице.)
Этот подход не зависит от IDE и работает также во внешней среде сборки, такой как Hudson или Jenkins.
Одна вещь, которую нужно добавить к принятому ответу - если вы используете аннотации из jsr305 - используйте @CheckForNull вместо @Nullable. Подробнее об этом здесь: https://today.java.net/pub/a/today/2008/09/11/jsr-305-annotations.html В двух словах - намерение @Nullable - просто сказать, что нуль совершенно нормально здесь и никаких дополнительных проверок не требуется. @CheckForNull говорит, что каждое использование отмеченного поля должно быть проверено на нулевое значение и будет сообщено в findbugs (как и избыточная проверка на нулевое значение и присвоение этого значения одному, помеченному как @Nonnull).
PS Извините, это должен был быть комментарий, а не ответ, но у меня все еще не хватает репутации, чтобы комментировать...
Я думаю, с вашей аннотацией вы хотите сделать две вещи:
- сделать программу быстрее
- заставить программистов работать меньше (потому что им не нужно вводить нулевые проверки)
Для первого: нулевая проверка практически не потребляет ресурсов. Если вы запустите этот образец:
public static void main(String[] args) {
long ms = System.currentTimeMillis();
for (int i=0;i<10000;i++) {
returnsNonNull(i);
doNothing();
}
System.out.println("Without nullcheck took "+(System.currentTimeMillis()-ms)+" ms to complete");
ms = System.currentTimeMillis();
for (int i=10000;i<20000;i++) {
if (returnsNonNull(i)!=null) {
doNothing();
}
}
System.out.println("With nullcheck took "+(System.currentTimeMillis()-ms)+" ms to complete");
}
public static String returnsNonNull(int i) {
return "Foo "+i+" Bar";
}
public static void doNothing() {
}
Вы увидите, что между этими двумя тестами нет никакой разницы. Второй (с нулевой проверкой) иногда даже быстрее первого, что означает, что они оба в значительной степени неразличимы, когда дело доходит до использования ресурсов.
Теперь ко второму пункту: вы фактически заставляете программистов работать больше, а не меньше. За исключением случаев, когда во всем вашем проекте и фреймворке нет абсолютно никаких функций, которые когда-либо возвращали бы ноль, программисту теперь приходится каждый раз искать функцию, чтобы проверить, должна ли она делать нуль-проверку или нет. Видя, что каждая инфраструктура в Java может при некоторых условиях возвращать нуль в некоторых функциях, вы не уменьшаете нагрузку на ваших программистов.
Вы сказали, что хотите, чтобы предупреждение появлялось, когда они выполняют нулевую проверку, когда это не нужно из-за вашей аннотации @Nonnull. Так что они сделают, они напечатают всю нулевую проверку и затем получат предупреждение и должны будут удалить это снова. Видишь, о чем я?
И что делать, если вы допустили ошибку в своем коде и отметили что-то как @Nonnull, которое может вернуть ноль?
То, что я сделал бы, это напишите это в Javadoc под @return. Тогда программист может решить, что он делает, что обычно лучше, чем заставлять других писать код по вашему стилю.