Каковы некоторые примеры реальной жизни, чтобы понять ключевую роль утверждений?
Что делает ключевое слово Java assert, и когда оно должно использоваться?
Ответ 1
Утверждения (с помощью ключевого слова assert) были добавлены в Java 1.4. Они используются для проверки правильности инварианта в коде. Они никогда не должны запускаться в производственном коде и указывают на ошибку или неправильное использование кода. Они могут быть активированы во время выполнения с помощью параметра -ea
в команде java
, но не включены по умолчанию.
Пример:
public Foo acquireFoo(int id) {
Foo result = null;
if (id > 50) {
result = fooService.read(id);
} else {
result = new Foo(id);
}
assert result != null;
return result;
}
Ответ 2
Предположим, что вы должны написать программу для управления атомной электростанцией. Совершенно очевидно, что даже самая незначительная ошибка может иметь катастрофические результаты, поэтому ваш код должен быть без ошибок (при условии, что JVM не содержит ошибок для аргумента).
Java не является проверяемым языком, а это означает: вы не можете вычислить, что результат вашей операции будет совершенным. Основная причина этого - указатели: они могут указывать куда угодно или нигде, поэтому они не могут быть рассчитаны таким точным значением, по крайней мере, не в пределах разумного диапазона кода. Учитывая эту проблему, невозможно доказать, что ваш код является правильным в целом. Но то, что вы можете сделать, это доказать, что вы, по крайней мере, находите каждую ошибку, когда это происходит.
Эта идея основана на парадигме Design-by-Contract (DbC): сначала вы определяете (с математической точностью), что должен делать ваш метод, а затем проверяйте это, проверяя его во время фактического выполнения. Пример:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
Хотя это довольно очевидно, чтобы работать нормально, большинство программистов не увидит скрытую ошибку внутри этого (подсказка: Ariane V разбился из-за подобной ошибки). Теперь DbC определяет, что вы всегда должны проверять ввод и вывод функции, чтобы убедиться, что она работает правильно. Java может сделать это через утверждения:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
final int result = a + b;
assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
return result;
}
Если эта функция теперь провалится, вы заметите это. Вы узнаете, что в вашем коде есть проблема, вы знаете, где он находится, и вы знаете, что вызвало его (аналогично Исключениям). И что еще более важно: вы перестаете выполнять права, когда это происходит, чтобы предотвратить дальнейший код для работы с неправильными значениями и потенциально привести к повреждению того, что он контролирует.
Исключения Java - это аналогичная концепция, но они не могут проверить все. Если вы хотите еще больше проверок (за счет скорости выполнения), вам нужно использовать утверждения. Это приведет к раздуванию вашего кода, но в конечном итоге вы сможете доставить продукт на удивительно короткое время разработки (чем раньше вы исправляете ошибку, тем ниже стоимость). И кроме того: если в вашем коде есть какая-то ошибка, вы ее обнаружите. Невозможно пропустить пропуски ошибок и вызвать проблемы позже.
Это все еще не является гарантией для кода без ошибок, но он намного ближе к этому, чем обычные программы.
Ответ 3
Утверждения - это инструмент фазы развития, позволяющий поймать ошибки в вашем коде. Они предназначены для легкого удаления, поэтому они не будут существовать в производственном коде. Таким образом, утверждения не являются частью "решения", которое вы предоставляете клиенту. Они являются внутренними проверками, чтобы убедиться, что сделанные вами предположения верны. Самый распространенный пример - проверить значение null. Многие методы написаны так:
void doSomething(Widget widget) {
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
Очень часто в таком методе виджет должен просто никогда не быть нулевым. Поэтому, если он недействителен, в вашем коде есть ошибка, которую нужно отслеживать. Но приведенный выше код никогда не скажет вам об этом. Поэтому в благих намерениях написать "безопасный" код, вы также скрываете ошибку. Это гораздо лучше написать код следующим образом:
/**
* @param Widget widget Should never be null
*/
void doSomething(Widget widget) {
assert widget != null;
widget.someMethod(); // ...
... // do more stuff with this widget
}
Таким образом, вы обязательно поймаете эту ошибку раньше. (Также полезно указать в контракте, что этот параметр никогда не должен быть нулевым.) Обязательно включайте утверждения при проверке кода во время разработки. (И убеждать своих коллег делать это тоже часто бывает трудно, что я нахожу очень раздражающим.)
Теперь некоторые из ваших коллег возражают против этого кода, утверждая, что вы все равно должны поместить нулевую проверку, чтобы исключить исключение в процессе производства. В этом случае утверждение по-прежнему полезно. Вы можете написать это следующим образом:
void doSomething(Widget widget) {
assert widget != null;
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
Таким образом, ваши коллеги будут рады, что нулевая проверка существует для производственного кода, но во время разработки вы больше не скрываете ошибку, когда виджет имеет значение null.
Вот пример реального мира: однажды я написал метод, который сравнивал два произвольных значения для равенства, где любое значение может иметь значение null:
/**
* Compare two values using equals(), after checking for null.
* @param thisValue (may be null)
* @param otherValue (may be null)
* @return True if they are both null or if equals() returns true
*/
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = thisValue.equals(otherValue);
}
return result;
}
Этот код делегирует работу метода equals()
в случае, когда thisValue не является нулевым. Но он предполагает, что метод equals()
корректно выполняет контракт equals()
, правильно обрабатывая нулевой параметр.
Сотрудник возразил против моего кода, сказав мне, что многие из наших классов имеют багги equals()
, которые не проверяют значение null, поэтому я должен поместить эту проверку в этот метод. Это спорно, если это имеет смысл, или если мы должны заставить эту ошибку, так что мы можем обнаружить его и исправить ее, но я отложила мой коллега и поставить в нулевом чеке, который я пометил с комментарием:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = otherValue != null && thisValue.equals(otherValue); // questionable null check
}
return result;
}
Дополнительная проверка здесь other != null
необходима только в том случае, если метод equals()
не может проверить значение null в соответствии с требованиями контракта.
Вместо того, чтобы участвовать в бесплодных дискуссиях с моим коллегой о мудрости, позволяющей багги-коду оставаться в нашей базе кода, я просто ставил два утверждения в коде. Эти утверждения дадут мне знать на этапе разработки, если один из наших классов не сможет правильно реализовать equals()
, поэтому я могу исправить его:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
assert otherValue == null || otherValue.equals(null) == false;
} else {
result = otherValue != null && thisValue.equals(otherValue);
assert thisValue.equals(null) == false;
}
return result;
}
Важные моменты, которые следует учитывать, следующие:
-
Утверждения - это только инструменты фазы разработки.
-
Точка утверждения - сообщить вам, есть ли ошибка, а не только в коде, а в вашей базе кода. (Утверждения здесь будут фактически отмечать ошибки в других классах.)
-
Даже если мой коллега был уверен, что наши классы были правильно написаны, утверждения здесь все равно будут полезны. Будут добавлены новые классы, которые могут не тестировать значение null, и этот метод может помечать эти ошибки для нас.
-
В разработке вы всегда должны включать утверждения, даже если написанный вами код не использует утверждения. Моя IDE настроена всегда делать это по умолчанию для любого нового исполняемого файла.
-
Утверждения не меняют поведение кода в процессе производства, поэтому мой коллега счастлив, что существует нулевая проверка, и что этот метод будет выполняться правильно, даже если метод
equals()
является ошибкой. Я счастлив, потому что в процессе разработки поймаю какой-нибудь баггиequals()
.
Кроме того, вы должны проверить свою политику утверждения, введя временное утверждение, которое не будет выполнено, поэтому вы можете быть уверены, что вас уведомляют, либо через файл журнала, либо трассировку стека в выходном потоке.
Ответ 4
Много хороших ответов, объясняющих, что делает ключевое слово assert
, но мало кто отвечает на реальный вопрос: "Когда нужно использовать ключевое слово assert
в реальной жизни?"
Ответ: почти никогда.
Утверждения, как концепция, прекрасны. В хорошем коде есть много if (...) throw...
операторов (и их родственников, таких как Objects.requireNonNull
и Math.addExact
). Однако некоторые дизайнерские решения сильно ограничивают полезность самого ключевого слова assert
.
Главной идеей ключевого слова assert
является преждевременная оптимизация, и основная функция - это возможность легко отключить все проверки. Фактически, проверки assert
отключены по умолчанию.
Однако критически важно, чтобы инвариантные проверки продолжались в производстве. Это связано с тем, что идеальное покрытие теста невозможно, и весь производственный код будет содержать ошибки, которые должны помочь диагностировать и смягчать.
Поэтому использование if (...) throw...
должно быть предпочтительным, так же, как это требуется для проверки значений параметров публичных методов и для выброса IllegalArgumentException
.
Иногда может возникнуть соблазн написать инвариантную проверку, которая требует слишком долгого времени для обработки (и ее часто называют достаточно важным для нее). Однако такие проверки замедляют тестирование, что также нежелательно. Такие трудоемкие проверки обычно записываются как единичные тесты. Тем не менее, иногда это может иметь смысл использовать assert
по этой причине.
Не используйте assert
просто потому, что он чище и красивее, чем if (...) throw...
(и я говорю это с большой болью, потому что мне нравится чистое и красивое). Если вы просто не можете помочь себе и можете контролировать, как запускается ваше приложение, тогда не стесняйтесь использовать assert
но всегда включайте утверждения в производство. По общему признанию, это то, что я обычно делаю. Я нажимаю на аннотацию lombok, которая заставит assert
действовать более как if (...) throw...
Голосуйте за него здесь.
(Rant: разработчики JVM были кучей ужасных, преждевременно оптимизирующих кодеров. Вот почему вы слышите о многих проблемах безопасности в Java-плагине и JVM. Они отказались включать базовые проверки и утверждения в производственный код, и мы продолжаем заплатить цену.)
Ответ 5
Здесь наиболее распространенный вариант использования. Предположим, вы используете значение перечисления:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
}
Пока вы обрабатываете каждый случай, все в порядке. Но когда-нибудь кто-то добавит фиги к вашему перечислению и забудет добавить его в ваш оператор switch. Это вызывает ошибку, которая может оказаться сложной задачей, потому что эффекты не будут ощущаться до тех пор, пока вы не оставите оператор switch. Но если вы напишете свой переключатель следующим образом, вы можете сразу его поймать:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
default:
assert false : "Missing enum value: " + fruit;
}
Ответ 6
Утверждения используются для проверки пост-условий и предварительных условий "никогда не сбой". Правильный код никогда не должен заканчиваться утверждением; когда они срабатывают, они должны указывать на ошибку (надеюсь, в месте, близком к тому, где находится фактический локус проблемы).
Примером утверждения может быть проверка того, что определенная группа методов вызывается в правильном порядке (например, что hasNext()
вызывается до next()
в Iterator
).
Ответ 7
Что делает ключевое слово assert в Java?
Посмотрите на скомпилированный байт-код.
Сделаем вывод, что:
public class Assert {
public static void main(String[] args) {
assert System.currentTimeMillis() == 0L;
}
}
генерирует почти тот же байт-код, что и:
public class Assert {
static final boolean $assertionsDisabled =
!Assert.class.desiredAssertionStatus();
public static void main(String[] args) {
if (!$assertionsDisabled) {
if (System.currentTimeMillis() != 0L) {
throw new AssertionError();
}
}
}
}
где Assert.class.desiredAssertionStatus()
есть true
, когда -ea
передается в командной строке, а false в противном случае.
Мы используем System.currentTimeMillis()
, чтобы убедиться, что он не будет оптимизирован (assert true;
сделал).
Синтетическое поле генерируется так, что Java нужно только вызвать Assert.class.desiredAssertionStatus()
один раз во время загрузки, а затем кэширует результат. См. Также: В чем смысл "статической синтетики" ?
Мы можем проверить, что при:
javac Assert.java
javap -c -constants -private -verbose Assert.class
С Oracle JDK 1.8.0_45 было создано синтетическое статическое поле (см. также: В чем смысл "статической синтетики" ?):
static final boolean $assertionsDisabled;
descriptor: Z
flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
вместе со статическим инициализатором:
0: ldc #6 // class Assert
2: invokevirtual #7 // Method java/lang Class.desiredAssertionStatus:()Z
5: ifne 12
8: iconst_1
9: goto 13
12: iconst_0
13: putstatic #2 // Field $assertionsDisabled:Z
16: return
и основной метод:
0: getstatic #2 // Field $assertionsDisabled:Z
3: ifne 22
6: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
9: lconst_0
10: lcmp
11: ifeq 22
14: new #4 // class java/lang/AssertionError
17: dup
18: invokespecial #5 // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return
Мы заключаем, что:
- для
assert
нет поддержки уровня байткода: это концепция языка Java. -
assert
можно довольно хорошо эмулировать с помощью системных свойств-Pcom.me.assert=true
для замены-ea
в командной строке иthrow new AssertionError()
.
Ответ 8
Пример реального мира, из класса Stack (из Assertion in Java Articles)
public int pop() {
// precondition
assert !isEmpty() : "Stack is empty";
return stack[--num];
}
Ответ 9
Утверждение позволяет обнаруживать дефекты в коде. Вы можете включить утверждения для тестирования и отладки, оставив их при создании вашей программы.
Зачем утверждать что-то, когда вы знаете, что это правда? Это верно только тогда, когда все работает правильно. Если у программы есть дефект, на самом деле это может быть неверно. Обнаружение этого ранее в процессе позволяет узнать, что что-то не так.
Оператор assert
содержит этот оператор вместе с необязательным сообщением String
.
Синтаксис оператора assert имеет две формы:
assert boolean_expression;
assert boolean_expression: error_message;
Вот некоторые основные правила, которые определяют, где должны использоваться утверждения, и где они не должны использоваться. Утверждения должны использоваться для:
-
Проверка входных параметров частного метода. НЕ для общедоступных методов.
public
методы должны бросать регулярные исключения при использовании плохих параметров. -
В любом месте программы, чтобы гарантировать достоверность факта, который почти наверняка верен.
Например, если вы уверены, что это будет только 1 или 2, вы можете использовать такое утверждение:
...
if (i == 1) {
...
}
else if (i == 2) {
...
} else {
assert false : "cannot happen. i is " + i;
}
...
- Проверка условий сообщения в конце любого метода. Это означает, что после выполнения бизнес-логики вы можете использовать утверждения, чтобы внутреннее состояние ваших переменных или результатов соответствовало тому, что вы ожидаете. Например, метод, который открывает сокет или файл, может использовать утверждение в конце, чтобы убедиться, что сокет или файл действительно открыты.
Утверждения не должны использоваться для:
-
Проверка входных параметров открытого метода. Поскольку утверждения не всегда могут выполняться, следует использовать регулярный механизм исключения.
-
Проверка ограничений на то, что вводится пользователем. То же, что и выше.
-
Не следует использовать для побочных эффектов.
Например, это не подходит, потому что здесь утверждение используется для его побочного эффекта вызова метода doSomething()
.
public boolean doSomething() {
...
}
public void someMethod() {
assert doSomething();
}
Единственный случай, когда это может быть оправдано, - это когда вы пытаетесь выяснить, включены ли в ваш код утверждения:
boolean enabled = false;
assert enabled = true;
if (enabled) {
System.out.println("Assertions are enabled");
} else {
System.out.println("Assertions are disabled");
}
Ответ 10
В дополнение ко всем отличным ответам, представленным здесь, официальное руководство по программированию на Java SE 7 содержит довольно краткое руководство по использованию assert
; с несколькими точными примерами того, когда хорошая (и, что важно, плохая) идея использовать утверждения и чем она отличается от создания исключений.
Ответ 11
Утверждение очень полезно при разработке. Вы используете его, когда что-то просто не может произойти, если ваш код работает правильно. Он прост в использовании и может оставаться в коде навсегда, потому что он будет отключен в реальной жизни.
Если есть вероятность, что это условие может произойти в реальной жизни, тогда вы должны его обработать.
Мне это нравится, но я не знаю, как включить его в Eclipse/Android/ADT. Кажется, что он выключен даже при отладке. (На этом есть поток, но он относится к "Java vm", который не отображается в конфигурации запуска ADT).
Ответ 12
Вот утверждение, которое я написал на сервере для проекта Hibernate/SQL. Сущность bean имела два эффективно-булевых свойства, называемых isActive и isDefault. Каждый может иметь значение "Y" или "N" или null, которое рассматривалось как "N". Мы хотим, чтобы клиент браузера был ограничен этими тремя значениями. Итак, в моих установщиках для этих двух свойств я добавил это утверждение:
assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;
Обратите внимание на следующее.
-
Это утверждение относится только к фазе разработки. Если клиент отправит плохое значение, мы поймаем это раньше и исправим его задолго до того, как мы достигнем производства. Утверждения для дефектов, которые вы можете поймать рано.
-
Это утверждение медленное и неэффективное. Все в порядке. Утверждения могут быть медленными. Нам все равно, потому что они инструменты для развития. Это не замедлит производственный код, потому что утверждения будут отключены. (Есть некоторые разногласия по этому вопросу, которые я получу позже). Это приводит к моей следующей точке.
-
Это утверждение не имеет побочных эффектов. Я мог бы проверить свою ценность против неизменяемого статического окончательного набора, но этот набор остался бы в производстве, где он никогда не будет использоваться.
-
Это утверждение существует для проверки правильной работы клиента. Поэтому к тому времени, когда мы достигнем производства, мы будем уверены, что клиент работает правильно, поэтому мы можем спокойно отключить это утверждение.
-
Некоторые люди спрашивают об этом: если утверждение не требуется в производстве, почему бы просто не вытащить их, когда вы закончите? Потому что они вам понадобятся, когда вы начнете работать над следующей версией.
Некоторые люди утверждали, что вы никогда не должны использовать утверждения, потому что вы никогда не можете быть уверены, что все ошибки исчезли, поэтому вам нужно поддерживать их даже в производстве. И поэтому нет смысла использовать утверждение assert, так как единственное преимущество в утверждениях заключается в том, что вы можете отключить их. Следовательно, согласно этому мышлению, вы должны (почти) никогда не использовать утверждения. Я не согласен. Конечно, верно, что если тест принадлежит к производству, вы не должны использовать утверждение. Но этот тест не принадлежит к производству. Этот способ заключается в том, чтобы поймать ошибку, которая вряд ли когда-либо достигнет производства, поэтому ее можно безопасно отключить, когда вы закончите.
Кстати, я мог бы написать это вот так:
assert value == null || value.equals("Y") || value.equals("N") : value;
Это нормально только для трех значений, но если число возможных значений становится больше, версия HashSet становится более удобной. Я выбрал версию HashSet, чтобы указать на эффективность.
Ответ 13
Утверждения по умолчанию отключены. Чтобы включить их, мы должны запустить программу с параметрами -ea
(возможность детализации может быть изменена). Например, java -ea AssertionsDemo
.
Существует два формата использования утверждений:
- Простой: например.
assert 1==2;//This will raise an AssertionError
assert 1==2;//This will raise an AssertionError
. - Лучше:
assert 1==2: "no way.. 1 is not equal to 2";
Это повысит AssertionError с сообщением, отображаемым тоже, и, таким образом, будет лучше. Хотя фактический синтаксис - этоassert expr1:expr2
где expr2 может быть любым выражением, возвращающим значение, я использовал его чаще всего для печати сообщения.
Ответ 14
Утверждение в основном используется для отладки приложения или используется для замены обработки исключений для некоторого приложения для проверки действительности приложения.
Утверждение работает во время выполнения. Простой пример, который может объяснить всю концепцию очень просто, здесь. Что делает ключевое слово assert в Java? (WikiAnswers).
Ответ 15
Повторить (и это относится ко многим языкам, а не только к Java):
"assert" в основном используется в качестве отладочной помощи разработчиков программного обеспечения во время процесса отладки. Сообщения-подтверждения никогда не должны появляться. Многие языки предоставляют параметр времени компиляции, который приведет к игнорированию всех "утверждений" для использования при генерации кода "production".
"Исключения" - это удобный способ обработки всех видов ошибок, независимо от того, представляют ли они логические ошибки, потому что, если вы столкнулись с условием ошибки, так что вы не можете продолжить, вы можете просто "выбросить их в воздух", где бы вы ни были, ожидая, что кто-то еще там будет готов "поймать" их. Контроль передается за один шаг, прямо из кода, который забросил исключение, прямо к уловителю. (И зрелище может видеть полную обратную линию вызовов, которые имели место.)
Кроме того, вызывающим абонентам этой подпрограммы не нужно проверять, удалось ли выполнить подпрограмму: "Если мы сейчас здесь, она должна быть выполнена успешно, потому что в противном случае она бы выбрала исключение, и мы не были бы здесь Теперь!" Эта простая стратегия значительно упрощает разработку кода и отладку.
Исключения позволяют легко разрешать условия фатальной ошибки: "исключения из правила". И для них нужно обработать код-путь, который также является "исключением из правила..." fly ball! "
Ответ 16
В принципе, "assert true" пройдет, и "assert false" потерпит неудачу. Давайте посмотрим, как это будет работать:
public static void main(String[] args)
{
String s1 = "Hello";
assert checkInteger(s1);
}
private static boolean checkInteger(String s)
{
try {
Integer.parseInt(s);
return true;
}
catch(Exception e)
{
return false;
}
}
Ответ 17
Утверждения - это проверки, которые могут быть отключены. Они редко используются. Зачем?
- Их нельзя использовать для проверки аргументов открытых методов, поскольку вы не можете их контролировать.
- Их не следует использовать для простых проверок, таких как
result != null
как такие проверки очень быстрые, и в них практически ничего не сохраняется.
Итак, что осталось? Дорогие чеки на условия действительно ожидается, чтобы быть правдой. Хорошим примером могут служить инварианты структуры данных, такой как RB-дерево. На самом деле, в ConcurrentHashMap
из JDK8 есть несколько таких значимых утверждений для TreeNodes
.
- Вы действительно не хотите включать их в производство, поскольку они могут легко доминировать во время выполнения.
- Вы можете включить или выключить их во время тестов.
- Вы определенно хотите включить их при работе над кодом.
Ответ 18
assert
- это ключевое слово. Он был представлен в JDK 1.4. Это два типа assert
s
- Очень простые операторы
assert
- Простые инструкции
assert
.
По умолчанию все операторы assert
не будут выполняться. Если оператор assert
получает значение false, он автоматически вызывает ошибку утверждения.