Некоторые (анти) паттерны использования assert (Java и др.) - PullRequest
14 голосов
/ 15 декабря 2008

Наконец, у меня есть вопрос к переполнению стека! : -)

Основная цель для Java, но я считаю, что это в основном не зависит от языка: если у вас нет нативного утверждения, вы всегда можете смоделировать его.

Я работаю в компании, продающей набор программного обеспечения, написанного на Java. Код устарел, по крайней мере, начиная с Java 1.3, и в некоторых местах он показывает ... Это большая база кода, около 2 миллионов строк, поэтому мы не можем все сразу же реорганизовать.
Недавно мы переключили последние версии с синтаксиса Java 1.4 и JVM на Java 1.6, консервативно используя некоторые новые функции, такие как assert (раньше мы использовали макрос DEBUG.ASSERT - я знаю, что assert был представлен в 1.4 но мы не использовали его раньше), дженерики (только типизированные коллекции), цикл foreach, перечисления и т. д.

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

Для справки, я искал assert java в SO и нашел несколько интересных тем, но, по-видимому, точных дубликатов нет.

Во-первых, основной вопрос, который вызвал мой вопрос сегодня:

SubDocument aSubDoc = documents.GetAt( i );
assert( aSubDoc != null );
if ( aSubDoc.GetType() == GIS_DOC )
{
   continue;
}
assert( aSubDoc.GetDoc() != null );
ContentsInfo ci = (ContentsInfo) aSubDoc.GetDoc();

( Да, мы используем соглашения по стилю / коду MS C / C ++. И мне даже это нравится (исходя из того же фона)! Так что подайте в суд на нас. )
Во-первых, форма assert() происходит от преобразования вызовов DEBUG.ASSERT(). Мне не нравятся дополнительные скобки, так как assert является языковой конструкцией, а не (здесь больше не) вызовом функции. Мне также не нравится return (foo);: -)
Далее, утверждения здесь не проверяются на инварианты, они скорее используются для защиты от плохих значений. Но, насколько я понимаю, они здесь бесполезны: assert выдаст исключение, даже не задокументированное со строкой-компаньоном, и только если утверждения включены. Поэтому, если у нас есть опция -ea, мы просто выбрасываем утверждение вместо обычного исключения NullPointerException. Это не похоже на первостепенное преимущество, так как в любом случае мы ловим непроверенные исключения на самом высоком уровне.
Правильно ли я полагаю, что мы можем избавиться от них и жить с этим (т. Е. Позволить Java вызвать такое непроверенное исключение)? (или, конечно, проверить нулевое значение, если это возможно, что делается в других местах) ).

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

Кто-то сказал, в последнем потоке, на который ссылаются, что публичные методы должны использовать тесты со значениями параметров (использование публичного API), а приватные методы должны полагаться на утверждения. Хороший совет.
Теперь оба вида методов должны проверять другой источник данных: внешний ввод. То есть. данные, поступающие от пользователя, из базы данных, из какого-либо файла или из сети, например.
В нашем коде я вижу утверждения против этих значений. Я всегда изменяю их на настоящий тест, поэтому они действуют даже при отключенных утверждениях: они не являются инвариантами и должны обрабатываться надлежащим образом.
Я себеЕдинственное возможное исключение, когда ввод предполагается постоянным, например, таблица базы данных, заполненная константами, используемыми в отношениях: программа прервется, если эта таблица будет изменена, но соответствующий код не обновлен.
Видите ли вы другие исключения?

Другое сравнительно частое использование, которое я вижу, которое кажется нормальным: при переключении по умолчанию или в конце серии else if тестирование всех возможных значений (эти случаи датируются еще до нашего использования перечислений!), Там часто assert false : "Unexpected value for stuff: " + stuff;
Выглядит законно для меня (эти случаи не должны происходить в производстве), как вы думаете? (кроме советов "не переключайтесь, используйте OO", которые здесь не имеют значения).

И, наконец, есть ли другие полезные варианты использования или надоедливые ошибки Я пропустил здесь? (Возможно!)

Ответы [ 4 ]

9 голосов
/ 16 декабря 2008

Правило номер один - избегать побочных эффектов в утверждениях. Другими словами, код должен вести себя идентично с отключенными утверждениями, как и при включенных утверждениях, а не при сбое (очевидно, что ошибочные утверждения изменят поведение, поскольку вызовут ошибку).

Правило номер два - не использовать утверждения для обязательных проверок. Их можно отключить (или, вернее, не включить). Для проверки параметров не закрытых методов используйте IllegalArgumentException.

Утверждения являются исполняемыми предположениями. Я использую утверждения, чтобы высказать свои убеждения о текущем состоянии программы. Например, такие вещи, как «Я предполагаю, что n здесь положительно» или «Я предполагаю, что список здесь содержит только один элемент» .

3 голосов
/ 15 декабря 2008

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

Итак, я рекомендую пропустить утверждения. не вставляйте дополнительные проверки нулевого указателя, где язык сделает это за вас. однако, если указатель не может быть разыменован некоторое время, хорошей идеей будет предварительная проверка нуля. Кроме того, всегда используют реальные исключения для случаев, которые должны "никогда" не произойти (последний вариант перехода или случай переключения по умолчанию), не используйте "assert false". если вы используете утверждение, есть вероятность, что кто-то может его отключить, а если ситуация действительно произойдет, все будет по-настоящему запутано.

2 голосов
/ 15 декабря 2008

Я использую assert, не только для проверки параметров, но и для проверки потоков. Каждый раз, когда я выполняю качание, я пишу assert почти во всех методах, чтобы отметить «Я должен выполняться только в рабочем потоке / AWTThread». (Я думаю, что Sun должна сделать это для нас.) Из-за модели потоков Swing, она НЕ МОЖЕТ завершиться с ошибкой (и может произойти случайный сбой), если вы обращаетесь к Swi-API из потока, не являющегося пользовательским интерфейсом. Все эти проблемы довольно сложно найти без утверждения.

Еще один пример, который я могу представить, - это проверить вложенный JAR-ресурс. Вы можете иметь исключение на английском языке, а не NPE.


EDIT: Другой пример; проверка блокировки объекта. Если я знаю, что собираюсь использовать вложенный синхронизированный блок, или когда я собираюсь исправить тупик, я использую Thread.holdLock (Object) , чтобы гарантировать, что блокировки не будут получены в обратном порядке.


РЕДАКТИРОВАТЬ (2): Если вы абсолютно уверены, что какой-то блок кода никогда не будет достигнут, вы можете написать

throw new AssertionError("You dead");

а не

assert false:"I am lucky";

Например, если вы переопределите «equals (Object)» для изменяемого объекта, переопределите hashCode () с помощью AssertionError, если вы считаете, что он никогда не будет ключом. Эта практика предлагается в некоторых книгах. Я не повредит производительности (так как она никогда не должна достигать).

2 голосов
/ 15 декабря 2008

Я рекомендую проверить параметры в общедоступных (API) методах и вызвать исключение IllegalArgumentException, если параметры недопустимы. Здесь нет утверждений, поскольку пользователь API должен получить правильную ошибку (сообщение).

Утверждения следует использовать в закрытых методах для проверки постусловий и, возможно, предусловий. Например:

List returnListOfSize(int size) {
    // complex list creation
    assert list.size == size;
}

Зачастую с помощью умной стратегии обработки ошибок можно обойтись.

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