Jak używać asercji w Javie

Pisanie programów, które działają poprawnie w czasie wykonywania, może być trudne. Dzieje się tak, ponieważ nasze założenia dotyczące zachowania się naszego kodu po wykonaniu są często błędne. Korzystanie z funkcji asercji języka Java jest jednym ze sposobów sprawdzenia, czy logika programowania jest poprawna.

Ten samouczek przedstawia asercje Java. Najpierw dowiesz się, czym są asercje i jak je określać i używać w kodzie. Następnie dowiesz się, jak używać asercji do wymuszania warunków wstępnych i końcowych. Na koniec porównasz potwierdzenia z wyjątkami i dowiesz się, dlaczego potrzebujesz obu w swoim kodzie.

pobierz Pobierz kod Pobierz kod źródłowy przykładów w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Co to są asercje Java?

Przed JDK 1.4 programiści często używali komentarzy do dokumentowania założeń dotyczących poprawności programu. Komentarze są jednak bezużyteczne jako mechanizm testowania i debugowania założeń. Kompilator ignoruje komentarze, więc nie ma możliwości użycia ich do wykrywania błędów. Deweloperzy często nie aktualizują komentarzy podczas zmiany kodu.  

W JDK 1.4 asercje zostały wprowadzone jako nowy mechanizm testowania i debugowania założeń dotyczących naszego kodu. Zasadniczo asercje  to jednostki, które można kompilować i które są wykonywane w czasie wykonywania, zakładając, że zostały włączone do testowania programów. Możesz zaprogramować asercje, aby powiadamiać Cię o błędach, w których występują, znacznie skracając czas, który w innym przypadku spędziłbyś na debugowaniu programu, który nie działa.

Asercje służą do kodowania wymagań, które czynią program poprawnym lub nie, przez testowanie warunków (wyrażeń boolowskich) pod kątem wartości prawdziwych i powiadamianie programisty, gdy takie warunki są fałszywe. Korzystanie z asercji może znacznie zwiększyć zaufanie co do poprawności kodu.

Jak napisać asercję w Javie

Asercje są implementowane za pośrednictwem assertinstrukcji i java.lang.AssertionErrorklasy. Ta instrukcja zaczyna się od słowa kluczowego asserti jest kontynuowana wyrażeniem logicznym. Wyraża się składniowo w następujący sposób:

assert BooleanExpr ;

Jeśli BooleanExprzwraca wartość true, nic się nie dzieje i wykonywanie jest kontynuowane. Jeśli jednak wyrażenie ma wartość false, AssertionErrorjest tworzona instancja i zgłaszana, jak pokazano na liście 1.

Listing 1:AssertDemo.java (wersja 1)

public class AssertDemo {public static void main (String [] args) {int x = -1; assert x> = 0; }}

Stwierdzenie z listingu 1 wskazuje na przekonanie programisty, że zmienna xzawiera wartość większą lub równą 0. Jednak wyraźnie tak nie jest; wykonanie assertinstrukcji skutkuje wyrzuceniem AssertionError.

Skompiluj Listing 1 ( javac AssertDemo.java) i uruchom go z włączonymi asercjami ( java -ea AssertDemo). Należy zwrócić uwagę na następujące dane wyjściowe:

Wyjątek w wątku „main” java.lang.AssertionError w AssertDemo.main (AssertDemo.java:6)

Ta wiadomość jest nieco tajemnicza, ponieważ nie identyfikuje, co spowodowało AssertionErrorwyrzucenie. Jeśli chcesz otrzymać bardziej pouczającą wiadomość, skorzystaj z assertponiższego oświadczenia:

assert BooleanExpr : wyrażenie ;

Tutaj exprznajduje się dowolne wyrażenie (w tym wywołanie metody), które może zwrócić wartość - nie można wywołać metody ze voidzwracanym typem. Przydatnym wyrażeniem jest literał ciągu, który opisuje przyczynę niepowodzenia, jak pokazano na liście 2.

Listing 2:AssertDemo.java (wersja 2)

public class AssertDemo {public static void main (String [] args) {int x = -1; assert x> = 0: "x <0"; }}

Skompiluj Listing 2 ( javac AssertDemo.java) i uruchom go z włączonymi asercjami ( java -ea AssertDemo). Tym razem należy zwrócić uwagę na następujące nieco rozszerzone dane wyjściowe, w tym przyczynę wyrzucenia AssertionError:

Wyjątek w wątku „main” java.lang.AssertionError: x <0 w AssertDemo.main (AssertDemo.java:6)

Na przykład uruchomienie AssertDemobez opcji -ea(włącz asercje) nie spowoduje żadnego wyniku. Gdy asercje nie są włączone, nie są wykonywane, chociaż nadal są obecne w pliku klasy.

Warunki wstępne i końcowe

Asercje testują założenia programu, sprawdzając, czy jego różne warunki wstępne i końcowe nie są naruszone, ostrzegając programistę, gdy wystąpi naruszenie:

  • Warunek jest warunkiem, który musi ocenić true przed wykonaniem jakiejś sekwencji kodu. Warunki wstępne zapewniają, że dzwoniący dotrzymują umów z dzwoniącymi.
  • Postcondition jest warunkiem, który musi ocenić true po wykonaniu pewnej sekwencji kodu. Warunki końcowe zapewniają, że dzwoniący dotrzymują umów z dzwoniącymi.

Warunki wstępne

Możesz wymusić warunki wstępne w publicznych konstruktorach i metodach, dokonując jawnych kontroli i zgłaszając wyjątki, gdy jest to konieczne. W przypadku prywatnych metod pomocniczych można wymusić warunki wstępne, określając potwierdzenia. Rozważ listę 3.

Listing 3:AssertDemo.java (wersja 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG {/ ** * Utwórz instancję PNG, przeczytaj określony plik PNG i zdekoduj * go do odpowiednich struktur. * * @param filespec ścieżka i nazwa pliku PNG do odczytania * * @throws NullPointerException, gdy filespecjest *null* / PNG (String filespec) zgłasza IOException {// Wymuś warunki wstępne w konstruktorach nieprywatnych // i metodach. if (filespec == null) throw new NullPointerException ("filespec is null"); try (FileInputStream fis = new FileInputStream (filespec)) {readHeader (fis); }} private void readHeader (InputStream is) rzuca IOException {// Potwierdź, że warunek wstępny jest spełniony w prywatnych // metodach pomocniczych. assert jest! = null: "null przekazany do jest"; }} public class AssertDemo {public static void main (String [] args) rzuca IOException {PNG png = nowy PNG ((args.length == 0)? null: args [0]); }}

PNGKlasy w listingu 3 jest minimalna początek biblioteki do odczytu i dekodowania PNG (Portable Network Graphics) pliki obrazów. Konstruktor jawnie porównuje filespecz null, zgłaszając, NullPointerExceptiongdy ten parametr zawiera null. Chodzi o to, aby wyegzekwować warunek wstępny, który filespecnie zawiera null.

Nie należy określać, assert filespec != null;ponieważ warunek wstępny wymieniony w Javadoc konstruktora nie byłby (technicznie) honorowany, gdy asercje zostały wyłączone. (W rzeczywistości byłoby to zaszczycone, ponieważ FileInputStream()rzucałoby NullPointerException, ale nie powinieneś polegać na nieudokumentowanym zachowaniu).

Jest to jednak assertodpowiednie w kontekście metody prywatnego readHeader()pomocnika, która zostanie ostatecznie zakończona w celu odczytania i zdekodowania 8-bajtowego nagłówka pliku PNG. Warunek wstępny, który iszawsze jest przekazywany jako wartość różna od null, będzie zawsze obowiązywał.

Warunki końcowe

Warunki końcowe są zwykle określane za pomocą asercji, niezależnie od tego, czy metoda (lub konstruktor) jest publiczna. Rozważ listę 4.

Listing 4:AssertDemo.java (wersja 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.

Być może zauważyłeś subtelną różnicę między stwierdzeniem a logiką wykrywania błędów. Testy asercji x >= 0, a logika wykrywania błędów x < 0. Stwierdzenie jest optymistyczne: zakładamy, że argument jest w porządku. W przeciwieństwie do tego logika wykrywania błędów jest pesymistyczna: zakładamy, że argument nie jest OK. Asercje dokumentują poprawną logikę, podczas gdy wyjątki dokumentują nieprawidłowe zachowanie w czasie wykonywania.

W tym samouczku nauczyłeś się, jak używać asercji do dokumentowania prawidłowej logiki programu. Dowiedziałeś się również, dlaczego asercje nie zastępują wyjątków i widziałeś przykład, w którym użycie wyjątku byłoby bardziej skuteczne.

Ta historia „Jak używać asercji w Javie” została pierwotnie opublikowana przez JavaWorld.