Kako koristiti tvrdnje u Javi

Pisanje programa koji ispravno rade u vrijeme izvođenja može biti izazov. To je zato što su naše pretpostavke o ponašanju našeg koda kada se izvrše često pogrešne. Korištenje Javine značajke tvrdnji jedan je od načina da provjerite je li vaša programska logika zdrava.

Ovaj vodič predstavlja Java tvrdnje. Prvo ćete naučiti što su tvrdnje i kako ih odrediti i koristiti u kodu. Dalje ćete otkriti kako koristiti tvrdnje za provođenje preduvjeta i postuslova. Na kraju ćete usporediti tvrdnje s iznimkama i saznati zašto vam je potrebno oboje u kodu.

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere u ovom vodiču. Stvorio Jeff Friesen za JavaWorld.

Što su Java tvrdnje?

Prije JDK 1.4, programeri su često koristili komentare kako bi dokumentirali pretpostavke o ispravnosti programa. Međutim, komentari su beskorisni kao mehanizam za testiranje i otklanjanje pogrešaka u pretpostavkama. Kompajler ignorira komentare, pa ne postoji način da ih se koristi za otkrivanje grešaka. Programeri također često ne ažuriraju komentare prilikom promjene koda.  

U JDK 1.4, tvrdnje su predstavljene kao novi mehanizam za testiranje i ispravljanje pogrešaka pretpostavki o našem kodu. U osnovi, tvrdnje  su kompilabilni entiteti koji se izvršavaju u vrijeme izvođenja, pod pretpostavkom da ste ih omogućili za testiranje programa. Možete programirati tvrdnje da vas obavijeste o greškama u kojima se bugovi javljaju, uvelike smanjujući vrijeme koje biste inače potrošili na otklanjanje pogrešaka u neuspjelom programu.

Tvrdnje se koriste za kodificiranje zahtjeva koji program čine ispravnim ili ne testiranjem uvjeta (logički izrazi) za istinske vrijednosti i obavještavanjem programera kada su takvi uvjeti lažni. Korištenje tvrdnji može uvelike povećati vaše povjerenje u ispravnost koda.

Kako napisati tvrdnju na Javi

Tvrdnje se provode putem assertizraza i java.lang.AssertionErrorklase. Ova izjava započinje ključnom riječi, asserta nastavlja se logičkim izrazom. Izražava se sintaktički na sljedeći način:

tvrditi BooleanExpr ;

Ako BooleanExprprocijeni da je točno, ništa se neće dogoditi i izvršenje se nastavlja. Ako se izraz ocjenjuje kao false, AssertionErrorinstancira se i baca, kao što je prikazano u Popisu 1.

Popis 1:AssertDemo.java (verzija 1)

javna klasa AssertDemo {javna statička void glavna (String [] args) {int x = -1; tvrditi x> = 0; }}

Tvrdnja iz popisa 1 ukazuje na uvjerenje programera da varijabla xsadrži vrijednost veću ili jednaku 0. Međutim, to očito nije slučaj; izvršenje assertizjave rezultira bačenim AssertionError.

Sastavite popis 1 ( javac AssertDemo.java) i pokrenite ga s omogućenim tvrdnjama ( java -ea AssertDemo). Trebali biste promatrati sljedeći rezultat:

Iznimka u niti "main" java.lang.AssertionError na AssertDemo.main (AssertDemo.java:6)

Ova je poruka pomalo tajnovita jer ne prepoznaje što je uzrokovalo AssertionErrorbacanje. Ako želite informativniju poruku, upotrijebite assertdolje navedenu izjavu:

ustvrditi BooleanExpr : expr ;

Ovdje exprje bilo koji izraz (uključujući pozivanje metode) koji može vratiti vrijednost - ne možete pozvati metodu s voidvrnutom vrstom. Korisni izraz je string literal koji opisuje razlog neuspjeha, kao što je prikazano u Popisu 2.

Popis 2:AssertDemo.java (verzija 2)

javna klasa AssertDemo {javna statička void glavna (String [] args) {int x = -1; tvrditi x> = 0: "x <0"; }}

Sastavite popis 2 ( javac AssertDemo.java) i pokrenite ga s omogućenim tvrdnjama ( java -ea AssertDemo). Ovaj put, trebali biste primijetiti sljedeći malo prošireni izlaz, koji uključuje i razlog bačenog AssertionError:

Iznimka u niti "main" java.lang.AssertionError: x <0 na AssertDemo.main (AssertDemo.java:6)

Za bilo koji primjer, izvođenje AssertDemobez opcije -ea(omogući tvrdnje) ne rezultira izlazom. Kada tvrdnje nisu omogućene, one se ne izvršavaju, iako su i dalje prisutne u datoteci klase.

Preduvjeti i postuslovi

Tvrdnje testiraju pretpostavke programa provjeravanjem da nisu prekršeni njegovi razni preduvjeti i postuslovi, upozoravajući razvojnog programera kad dođe do kršenja:

  • Preduvjet je uvjet koji se mora procijeniti da vrijedi prije izvršenja nekog koda nizu. Preduvjeti osiguravaju pozivatelje da drže svoje ugovore s pozivima.
  • Postcondition je uvjet koji se mora procijeniti da vrijedi nakon izvršenja nekog koda nizu. Postuslovi osiguravaju pozivima da drže svoje ugovore s pozivima.

Preduvjeti

You can enforce preconditions on public constructors and methods by making explicit checks and throwing exceptions when necessary. For private helper methods, you can enforce preconditions by specifying assertions. Consider Listing 3.

Listing 3:AssertDemo.java (version 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG { /** * Create a PNG instance, read specified PNG file, and decode * it into suitable structures. * * @param filespec path and name of PNG file to read * * @throws NullPointerException when filespec is * null */ PNG(String filespec) throws IOException { // Enforce preconditions in non-private constructors and // methods. if (filespec == null) throw new NullPointerException("filespec is null"); try (FileInputStream fis = new FileInputStream(filespec)) { readHeader(fis); } } private void readHeader(InputStream is) throws IOException { // Confirm that precondition is satisfied in private // helper methods. assert is != null : "null passed to is"; } } public class AssertDemo { public static void main(String[] args) throws IOException { PNG png = new PNG((args.length == 0) ? null : args[0]); } }

The PNG class in Listing 3 is the minimal beginning of a library for reading and decoding PNG (portable network graphics) image files. The constructor explicitly compares filespec with null, throwing NullPointerException when this parameter contains null. The point is to enforce the precondition that filespec not contain null.

It’s not appropriate to specify assert filespec != null; because the precondition mentioned in the constructor’s Javadoc would not (technically) be honored when assertions were disabled. (In fact, it would be honored because FileInputStream() would throw NullPointerException, but you shouldn’t depend on undocumented behavior.)

However, assert is appropriate in the context of the private readHeader() helper method, which will be completed eventually to read and decode a PNG file’s 8-byte header. The precondition that is always be passed a non-null value will always hold.

Postconditions

Postconditions are typically specified via assertions, regardless of whether or not the method (or constructor) is public. Consider Listing 4.

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

Možda ste primijetili suptilnu razliku između tvrdnje i logike otkrivanja pogrešaka. Ispitivanja tvrdnji x >= 0, dok logička ispitivanja otkrivanja pogrešaka x < 0. Tvrdnja je optimistična: Pretpostavljamo da je argument u redu. Suprotno tome, logika otkrivanja pogrešaka je pesimistična: pretpostavljamo da argument nije u redu. Tvrdnje dokumentiraju ispravnu logiku, dok iznimke dokumentiraju neispravno ponašanje tijekom izvođenja.

U ovom vodiču naučili ste kako koristiti tvrdnje za dokumentiranje ispravne programske logike. Također ste naučili zašto tvrdnje nisu zamjena za iznimke i vidjeli ste primjer gdje bi uporaba iznimke bila učinkovitija.

Ovu priču "Kako koristiti tvrdnje na Javi" izvorno je objavio JavaWorld.