Как использовать утверждения в Java

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

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

загрузить Получить код Загрузите исходный код для примеров в этом руководстве. Создано Джеффом Фризеном для JavaWorld.

Что такое утверждения Java?

До JDK 1.4 разработчики часто использовали комментарии для документирования предположений о правильности программы. Однако комментарии бесполезны как механизм для проверки и отладки предположений. Компилятор игнорирует комментарии, поэтому нет возможности использовать их для обнаружения ошибок. Разработчики также часто не обновляют комментарии при изменении кода.  

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

Утверждения используются для кодификации требований, которые делают программу правильной или нет, путем тестирования условий (логических выражений) на истинные значения и уведомления разработчика, когда такие условия ложны. Использование утверждений может значительно повысить вашу уверенность в правильности кода.

Как написать утверждение на Java

Утверждения реализуются с помощью assertоператора и java.lang.AssertionErrorкласса. Этот оператор начинается с ключевого слова assertи продолжается логическим выражением. Синтаксически это выражается следующим образом:

assert BooleanExpr ;

Если BooleanExprзначение истинно, ничего не происходит и выполнение продолжается. Однако, если выражение оценивается как ложное, AssertionErrorсоздается и создается экземпляр, как показано в листинге 1.

Листинг 1:AssertDemo.java (версия 1)

публичный класс AssertDemo {публичный статический недействительный основной (String [] аргументы) {int x = -1; утверждать x> = 0; }}

Утверждение в листинге 1 показывает, что разработчик считает, что переменная xсодержит значение, большее или равное 0. Однако это явно не так; выполнение assertоператора приводит к выбросу AssertionError.

Скомпилируйте листинг 1 ( javac AssertDemo.java) и запустите его с включенными утверждениями ( java -ea AssertDemo). Вы должны увидеть следующий результат:

Исключение в потоке «main» java.lang.AssertionError в AssertDemo.main (AssertDemo.java:6)

Это сообщение несколько загадочно, так как не определяет, что вызвало AssertionErrorвыброс. Если вам нужно более информативное сообщение, используйте assertвыражение, приведенное ниже:

assert BooleanExpr : expr ;

Здесь exprлюбое выражение (включая вызов метода), которое может возвращать значение - вы не можете вызвать метод с voidтипом возвращаемого значения. Полезное выражение - это строковый литерал, описывающий причину сбоя, как показано в листинге 2.

Листинг 2:AssertDemo.java (версия 2)

публичный класс AssertDemo {публичный статический недействительный основной (String [] аргументы) {int x = -1; assert x> = 0: «x <0»; }}

Скомпилируйте листинг 2 ( javac AssertDemo.java) и запустите его с включенными утверждениями ( java -ea AssertDemo). На этот раз вы должны увидеть следующий слегка расширенный вывод, который включает причину выброса AssertionError:

Исключение в потоке "main" java.lang.AssertionError: x <0 в AssertDemo.main (AssertDemo.java:6)

В любом случае, работа AssertDemoбез -eaопции (enable assertions) не приводит к выходным данным . Когда утверждения не включены, они не выполняются, хотя они все еще присутствуют в файле класса.

Предпосылки и постусловия

Утверждения проверяют предположения программы, проверяя, что ее различные предварительные и постусловия не нарушаются, и предупреждают разработчика, когда происходит нарушение:

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

Предварительные условия

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

Листинг 3:AssertDemo.java (версия 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG {/ ** * Создание экземпляра PNG, чтение указанного файла PNG и * его декодирование в подходящие структуры. * * @param filespec путь и имя файла PNG для чтения * * @ выдает исключение NullPointerException, если filespec*null* / PNG (String filespec) выбрасывает IOException {// Обеспечивает выполнение предварительных условий в // не частных конструкторах и методах. if (filespec == null) выбросить новое исключение NullPointerException ("filespec is null"); попробуйте (FileInputStream fis = новый FileInputStream (filespec)) {readHeader (fis); }} private void readHeader (InputStream is) выбрасывает исключение IOException {// Подтверждение выполнения предварительного условия в частных // вспомогательных методах. assert is! = null: "null передано в is"; }} открытый класс AssertDemo {public static void main (String [] args) выдает исключение IOException {PNG png = новый PNG ((args.length == 0)? null: args [0]); }}

PNGКласс в листинге 3 является минимальным началом библиотеки для чтения и декодирования PNG (Portable Network Graphics) файлы изображений. Конструктор явно сравнивает filespecс null, выбрасывая, NullPointerExceptionкогда этот параметр содержит null. Дело в том, чтобы обеспечить выполнение предварительного условия, которое filespecне содержит null.

Это нецелесообразно указывать, assert filespec != null;потому что предварительное условие, упомянутое в документации Javadoc конструктора, не будет (технически) выполняться, когда утверждения отключены. (Фактически, это будет уважением, потому FileInputStream()что бросит NullPointerException, но вы не должны зависеть от недокументированного поведения.)

Однако assertэто уместно в контексте частного readHeader()вспомогательного метода, который в конечном итоге будет завершен для чтения и декодирования 8-байтового заголовка файла PNG. Предварительное условие, что isвсегда будет передаваться ненулевое значение, всегда будет выполняться.

Постусловия

Постусловия обычно указываются через утверждения, независимо от того, является ли метод (или конструктор) общедоступным. Рассмотрим листинг 4.

Листинг 4:AssertDemo.java (версия 4)

public class AssertDemo { public static void main(String[] args) { int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 }; sort(array); for (int element: array) System.out.printf("%d ", element); System.out.println(); } private static boolean isSorted(int[] x) { for (int i = 0; i  x[i + 1]) return false; return true; } private static void sort(int[] x) { int j, a; // For all integer values except the leftmost value ... for (int i = 1; i  0 && x[j - 1] > a) { // Shift left value -- x[j - 1] -- one position to its right -- // x[j]. x[j] = x[j - 1]; // Update insert position to shifted value's original position // (one position to the left). j--; } // Insert a at insert position (which is either the initial insert // position or the final insert position), where a is greater than // or equal to all values to its left. x[j] = a; } assert isSorted(x): "array not sorted"; } }

Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller.

The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array.

Assertions vs. exceptions in Java

Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code.

Developers use Java’s exception mechanism to respond to non-fatal (e.g., running out of memory) runtime errors, which may be caused by environmental factors, such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run.

Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately — AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions.

When to use exceptions

Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment:

public double sqrt(double x) { assert x >= 0 : "x is negative"; // ... }

It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows:

public double sqrt(double x) { if (x < 0) throw new IllegalArgumentException("x is negative"); // ... }

The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool that runs the program. Upon reading the error message, the developer can fix whatever code led to the exception.

Вы могли заметить тонкую разницу между утверждением и логикой обнаружения ошибок. Утверждение проверяет x >= 0, а логика обнаружения ошибок проверяет x < 0. Утверждение оптимистично: мы предполагаем, что аргумент в порядке. Напротив, логика обнаружения ошибок пессимистична: мы предполагаем, что аргумент неверен. Утверждения документируют правильную логику, тогда как исключения документируют неправильное поведение во время выполнения.

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

Этот рассказ «Как использовать утверждения в Java» был первоначально опубликован JavaWorld.