- Assert. Что это?
- Как использовать assertions в Java
- Используйте assertions (утверждения или ассерты) Java для проверки корректности кода и для ускорения тестирования и отладки ваших программ.
- Что такое ассерты в Java?
- Как написать ассерт на Java
- Предварительные условия и постусловия
- Предварительные условия
- Постусловия
- Листинг 4: AssertDemo.java (версия 4)
- Ассерты против исключений в Java
- Когда использовать исключения
Assert. Что это?
Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Эта конструкция может автоматически сигнализировать при обнаружении некорректных данных, что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных. Странная, на первый взгляд, конструкция — может завалить программу в самый неподходящий момент. Какой же в ней смысл? Давайте подумаем, что произойдет, если во время исполнения программы в какой-то момент времени некоторые данные программы стали некорректными и мы не «завалили» сразу же программу, а продолжили ее работу, как ни в чем не бывало. Программа может еще долго работать после этого без каких-либо видимых ошибок. А может в любой момент времени в будущем «завалиться» сама по известной только ей причине. Или накачать вам полный винчестер контента с гей-порносайтов. Это называется неопределенное поведение (undefined behavior) и, вопреки расхожему мнению, оно свойственно не только языкам программирования с произвольным доступом к памяти (aka C, C++). Т.к. assert завершает программу сразу же после обнаружения некорректных данных, он позволяет быстро локализировать и исправить баги в программе, которые привели к некорректным данным. Это его основное назначение. Assert’ы доступны во многих языках программирования, включая java, c#, c и python.
Какие виды assert’ов бывают?
Assert’ы позволяют отлавливать ошибки в программах на этапе компиляции либо во время исполнения. Проверки на этапе компиляции не так важны — в большинстве случаев их можно заменить аналогичными проверками во время исполнения программы. Иными словами, assert’ы на этапе компиляции являются ничем иным, как синтаксическим сахаром. Поэтому в дальнейшем под assert’ами будем подразумевать лишь проверки во время исполнения программы.
Assert’ы можно разделить на следующие классы:
- Проверка входящих аргументов в начале функции.
Если найдено недопустимое значение какого-либо аргумента, значит, где-то рядом с местом вызова этой функции могут быть баги. Пример:
Важно понимать, что входящие аргументы функции могут быть неявными. Например, при вызове метода класса в функцию неявно передается указатель на объект данного класса (aka this и self). Также функция может обращаться к данным, объявленным в глобальной области видимости, либо к данным из области видимости лексического замыкания. Эти аргументы тоже желательно проверять с помощью assert’ов при входе в функцию.
Если некорректные данные обнаружены на этом этапе, то код данной функции может содержать баги. Пример:
Что такое арифметика с произвольной точностью.
Результат функции может быть неявным. Например, функция может модифицировать данные, на которые ссылаются (напрямую или косвенно) аргументы функции. Также функция может модифицировать данные из глобальной области видимости или из области видимости лексического замыкания. Корректность этих данных желательно проверять перед выходом из функции.
- Проверка данных, с которыми работает функция, внутри кода функции.
Если в середине функции обнаруживаются некорректные данные, то баги могут быть где-то в районе этой проверки. Пример:
Когда и где стоит использовать assert’ы?
Ответ прост — используйте assert’ы всегда и везде, где они хоть чуточку могут показаться полезными. Ведь они существенно упрощают локализацию багов в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может запросто закрасться баг. Не бойтесь, что большое количество assert’ов ухудшит ясность кода и замедлит выполнение вашей программы. Assert’ы визуально выделяются из общего кода и несут важную информацию о предположениях, на основе которых работает данный код. Правильно расставленные assert’ы способны заменить большинство комментариев в коде. Большинство языков программирования поддерживают отключение assert’ов либо на этапе компиляции, либо во время выполнения программы, так что они оказывают минимальное влияние на производительность программы. Обычно assert’ы оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ. Если программа написана в лучших традициях ООП, либо с помощью enterprise методологии, то assert’ы вообще можно не отключать — производительность вряд ли изменится.
Когда можно обойтись без assert’ов?
Понятно, что дублирование assert’ов через каждую строчку кода не сильно улучшит эффективность отлова багов. Не существует единого мнения насчет оптимального количества assert’ов, также как и насчет оптимального количество комментариев в программе. Когда я только узнал про существование assert’ов, мои программы стали содержать 100500 assert’ов, многие из которых многократно дублировали друг друга. С течением времени количество assert’ов в моем коде стало уменьшаться. Следующие правила позволили многократно уменьшить количество assert’ов в моих программах без существенного ухудшения в эффективности отлова багов:
Можно избегать дублирующих проверок входящих аргументов путем размещения их лишь в функциях, непосредственно работающих с данным аргументом. Т.е. если функция foo() не работает с аргументом, а лишь передает его в функцию bar(), то можно опустить проверку этого аргумента в функции foo(), т.к. она продублирована проверкой аргумента в функции bar().
Можно опускать assert’ы на недопустимые значения, которые гарантированно приводят к краху программы в непосредственной близости от данных assert’ов, т.е. если по краху программы можно быстро определить местонахождение бага. К таким assert’ам можно отнести проверки указателя на NULL перед его разыменованием и проверки на нулевое значение делителя перед делением. Еще раз повторюсь — такие проверки можно опускать лишь тогда, когда среда исполнения гарантирует крах программы в данных случаях.
Вполне возможно, что существуют и другие способы, позволяющие уменьшить количество assert’ов без ухудшения эффективности отлова багов. Если вы в курсе этих способов, делитесь ими в комментариях к данному посту.
Когда нельзя использовать assert’ы?
Т.к. assert’ы могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления assert’а поведение программы может измениться, то это явный признак неправильного использования assert’а. Таким образом, внутри assert’а нельзя вызывать функции, изменяющие состояние программы либо внешнего окружения программы. Например, следующий код неправильно использует assert’ы:
Очевидно, что данные могут оказаться незащищенными при отключенных assert’ах.
Чтобы исправить эту ошибку, нужно сохранять результат выполнения функции во временной переменной, после чего использовать эту переменную внутри assert’а:
Т.к. основное назначение assert’ов — отлов багов (aka ошибки программирования), то они не могут заменить обработку ожидаемых ошибок, которые не являются ошибками программирования. Например:
Если write() возвращает 0, то это вовсе не означает, что в нашей программе есть баг. Если assert’ы в программе будут отключены, то ошибка записи может остаться незамеченной, что впоследствие может привести к печальным результатам. Поэтому assert() тут не подходит. Тут лучше подходит обычная обработка ошибки. Например:
Я программирую на javascript. В нем нет assert’ов. Что мне делать?
В некоторых языках программирования отсутствует явная поддержка assert’ов. При желании они легко могут быть там реализованы, следуя следующему «паттерну проектирования»:
Вообще, assert’ы обычно реализованы в различных фреймворках и библиотеках, предназначенных для автоматизированного тестирования. Иногда они там называются expect’ами. Между автоматизированным тестированием и применением assert’ов есть много общего — обе техники предназначены для быстрого выявления и исправления багов в программах. Но, несмотря на общие черты, автоматизированное тестирование и assert’ы являются не взаимоисключающими, а, скорее всего, взаимодополняющими друг друга. Грамотно расставленные assert’ы упрощают автоматизированное тестирование кода, т.к. тестирующая программа может опустить проверки, дублирующие assert’ы в коде программы. Такие проверки обычно составляют существенную долю всех проверок в тестирующей программе.
Источник
Как использовать assertions в Java
Используйте assertions (утверждения или ассерты) Java для проверки корректности кода и для ускорения тестирования и отладки ваших программ.
Написание программ, которые правильно работают, может оказаться сложной задачей. Поскольку наши предположения о том, как будет вести себя код при выполнении, часто ошибочны. Использование ассертов в Java — это один из способов проверить правильность логики программирования.
В этом руководстве рассказывается об ассертах в Java. Сначала вы узнаете, что такое ассерты и как их определять и использовать в коде. Далее вы узнаете, как использовать ассерты для обеспечения выполнения предварительных и постусловий. Наконец, вы сравните ассерты с исключениями и выясните, зачем в коде нужно и то и другое.
Загрузите исходный код примеров для этой статьи. Создано Джеффом Фризеном для JavaWorld.
Что такое ассерты в Java?
До JDK 1.4 разработчики часто использовали комментарии для документирования предположений о правильном использовании методов программы. Однако комментарии бесполезны как механизм для проверки и отладки предположений. Компилятор игнорирует комментарии, поэтому нет возможности использовать их для обнаружения ошибок. Разработчики также часто не обновляют комментарии при изменении кода.
В JDK 1.4 ассерты были введены как новый механизм для тестирования и отладки кода. По сути, ассерты — это компилируемые сущности, которые выполняются в runtime, если вы включили их для тестирования программы. Вы можете запрограммировать ассерты так, чтобы они уведомляли вас об ошибках ещё до того как они произошли и программа “упала”, что значительно сокращает время на отладку неисправной программы.
Ассерты используются для определения требований, исполнение которых делают программу правильной. И делается это при помощи проверки условий (логических выражений) на true. Если же выражение выдаёт false, разработчик получает уведомление об этом. Использование утверждений может значительно повысить вашу уверенность в правильности кода.
Как написать ассерт на Java
Ассерты реализуются с помощью оператора assert и класса java.lang.AssertionError. Этот оператор начинается с ключевого слова assert и продолжается логическим выражением. Синтаксически это выражается следующим образом:
Если значение BooleanExpr истинно, ничего не происходит и выполнение продолжается. Однако, если выражение оценивается как ложное, бросается AssertionError, как, например, в этом коде:
Листинг 1: AssertDemo.java (версия 1)
ассерт здесь говорит о том, что по мнению разработчика переменная x должна быть больше или равна 0. Однако это явно не так; выполнение оператора assert приводит к выбросу AssertionError.
Скомпилируйте этот код (javac AssertDemo.java) и запустите его с включенными ассертами (java -ea AssertDemo) (прим.пер.: все популярные среды программирования позволяют включать ассерты через настройку). Вы увидите следующий результат:
Это сообщение несколько загадочно, так как не указывает, что привело к AssertionError. Если вам нужно более информативное сообщение, используйте assert с таким синтаксисом:
Здесь expr любое выражение, включая вызов метода (прим.пер.: в соответствии с лучшими современными практиками рекомендуется отказаться от вызовов методов из ассертов), которое должно возвращать значение — вы не можете вызвать метод void. Удобно использовать просто строку, описывающую причину сбоя, как показано ниже:
Листинг 2: AssertDemo.java (версия 2)
Запустите этот код с включенными ассертами. На этот раз вы увидите чуть больше информации, объясняющей причину выброса AssertionError:
Для любого из этих примеров, выполнение AssertDemo без -ea опции (enable assertions) не приведет к бросанию исключения. Если ассерты отключены, они не выполняются, хотя и присутствуют в файле класса.
Предварительные условия и постусловия
Ассерты проверяют предварительные и постусловия на true, и предупреждают разработчика, когда происходит нарушение:
- Предварительное условие — это условие, которое должно оцениваться как истинное перед выполнением некоторого метода. Выполнение предварительных условий гарантирует, что вызывающие методы соблюдают контракты с вызываемыми методами.
- Постусловие предполагает проверку на true после выполнения некоторой кодовой последовательности. Постусловия гарантируют, что вызываемые методы соблюдают свои контракты с вызывающими.
Предварительные условия
Вы можете обеспечить выполнение предварительных условий для общедоступных конструкторов и методов, выполнив явные проверки и выбрасывая исключения при необходимости. Для private вспомогательных методов вы можете обеспечить выполнение предварительных условий, используя ассерты. Рассмотрим листинг 3.
Листинг 3: AssertDemo.java (версия 3)
Создание экземпляра класса PNG в этом коде является необходимым для чтения и декодирования PNG (Portable Network Graphics) файлов изображений. Конструктор явно сравнивает filespec с null , выбрасывая NullPointerException , если он равен null . Смысл в том, чтобы обеспечить соблюдение предусловия, которое предполагает, что filespec не может быть равным null .
Однако нецелесообразно указывать assert filespec != null; , потому что предварительное условие, упомянутое в документации Javadoc конструктора, не будет (технически) проверяться, когда ассерты отключены (в данном случае, эта проверка будет просто страховкой, потому что FileInputStream() и без нас бросит NullPointerException , но негоже зависеть от недокументированного поведения).
С другой стороны, assert будет хорош во вспомогательном private методе readHeader() , который в конечном итоге завершится чтением и декодированием 8-байтового заголовка PNG файла. Предварительное условие здесь также состоит в том, что параметр is не должен быть равен null .
Постусловия
Постусловия обычно указываются через ассерты, независимо от того, является ли метод (или конструктор) общедоступным. Рассмотрим такой код:
Листинг 4: AssertDemo.java (версия 4)
В этом коде представлен вспомогательный метод sort() , который использует алгоритм сортировки вставкой для массива целочисленных значений. Я имел обыкновение проверять постусловие сортировки x через assert перед возвратом методом sort() результата вызывающему коду.
Этот пример демонстрирует важную характеристику ассертов — их выполнение, обычно, ресурсозатратно. По этой причине в промышленной версии программы ассерты обычно отключены. В методе isSorted() необходимо сканировать весь массив, что может занять много времени в случае большого массива.
Ассерты против исключений в Java
Разработчики используют ассерты для документирования логически запрещённых ситуаций и для обнаружения ошибок в логике программы. Во время её выполнения включенный ассерт предупреждает разработчика о логической ошибке. Разработчик меняет исходный код, чтобы исправить логическую ошибку, а затем перекомпилирует этот код.
Разработчики используют механизм исключений Java для ответа на нефатальные (например, нехватку памяти) ошибки, которые могут быть вызваны факторами окружающей среды, такими как несуществующий файл, или плохо написанным кодом, например попыткой разделить на 0. Обработчик исключений часто пишется так, чтобы после ошибки программа могла продолжить работу.
Ассерты не заменяют исключения. В отличие от исключений, ассерты не поддерживают восстановление после ошибок (ассерты обычно немедленно останавливают выполнение программы — AssertionError не предназначены для перехвата); они часто отключены в промышленном коде; и они обычно не отображают удобные для пользователя сообщения об ошибках (хотя это не проблема assert). Важно знать, когда использовать исключения, а не ассерты.
Когда использовать исключения
Предположим, вы написали sqrt()метод, который вычисляет квадратный корень из своего параметра. В контексте действительных чисел невозможно извлечь квадратный корень из отрицательного числа. Следовательно, вы используете ассерт для отказа от исполнения метода, если аргумент отрицательный. Рассмотрим следующий фрагмент кода:
Неуместно использовать ассерт для проверки аргумента в этом public методе. Ассерт предназначен для обнаружения ошибок в логике программирования, а не для защиты метода от ошибочных значений параметров. Кроме того, если ассерты отключены, невозможно решить проблему отрицательного аргумента. Лучше создать исключение следующим образом:
Разработчик может выбрать, обрабатывать исключение недопустимого аргумента или пробросить его, и тогда сообщение об ошибке отображается инструментом, запускающим программу. Прочитав сообщение об ошибке, разработчик может исправить код, вызвавший исключение.
Вы могли заметить тонкую разницу между ассертом и условием обнаружения ошибок. Ассерт требует x >= 0 , а условие обнаружения ошибок: x . Ассерт оптимистичен: мы предполагаем, что аргумент в порядке. Напротив, условие обнаружения ошибок пессимистично: мы предполагаем, что аргумент неверен. Ассерты документируют правильную логику, тогда как исключения документируют неправильное поведение во время выполнения.
В этом руководстве вы узнали, как использовать ассерты для документирования правильной логики программы. Вы также узнали, почему ассерты не заменяют исключения, и вы видели пример, в котором использование исключения было бы более эффективным.
Этот рассказ «Как использовать ассерты в Java» был первоначально опубликован JavaWorld .
Перевод Академии Progwards
Источник