Assertions gebruiken in Java

Het schrijven van programma's die correct werken tijdens runtime, kan een uitdaging zijn. Dit komt omdat onze aannames over hoe onze code zich gedraagt ​​wanneer deze wordt uitgevoerd, vaak onjuist zijn. Het gebruik van de beweringsfunctie van Java is een manier om te verifiëren dat uw programmeerlogica correct is.

Deze tutorial introduceert Java-beweringen. Je leert eerst wat beweringen zijn en hoe je ze specificeert en gebruikt in je code. Vervolgens ontdek je hoe je beweringen kunt gebruiken om rand- en postvoorwaarden af ​​te dwingen. Ten slotte vergelijk je beweringen met uitzonderingen en ontdek je waarom je beide nodig hebt in je code.

download Download de code Download de broncode voor voorbeelden in deze tutorial. Gemaakt door Jeff Friesen voor JavaWorld.

Wat zijn Java-beweringen?

Vóór JDK 1.4 gebruikten ontwikkelaars vaak opmerkingen om aannames over programmacorrectheid te documenteren. Opmerkingen zijn echter nutteloos als een mechanisme voor het testen en debuggen van aannames. De compiler negeert opmerkingen, dus er is geen manier om ze te gebruiken voor bugdetectie. Ontwikkelaars werken ook vaak geen opmerkingen bij bij het wijzigen van code.  

In JDK 1.4 werden beweringen geïntroduceerd als een nieuw mechanisme voor het testen en debuggen van aannames over onze code. In wezen zijn beweringen  compileerbare entiteiten die tijdens runtime worden uitgevoerd, ervan uitgaande dat u ze hebt ingeschakeld voor het testen van programma's. U kunt beweringen programmeren om u op de hoogte te stellen van bugs waarin de bugs voorkomen, waardoor de hoeveelheid tijd die u anders zou besteden aan het opsporen van fouten in een falend programma aanzienlijk wordt verminderd.

Beweringen worden gebruikt om de vereisten te codificeren die een programma al dan niet correct maken door voorwaarden (Booleaanse uitdrukkingen) te testen op echte waarden en de ontwikkelaar op de hoogte te stellen wanneer dergelijke voorwaarden onwaar zijn. Het gebruik van beweringen kan uw vertrouwen in de juistheid van uw code aanzienlijk vergroten.

Hoe je een bewering schrijft in Java

Beweringen worden geïmplementeerd via de assertverklaring en java.lang.AssertionErrorklasse. Deze instructie begint met het trefwoord asserten gaat verder met een Booleaanse uitdrukking. Het wordt als volgt syntactisch uitgedrukt:

beweren BooleanExpr ;

Als BooleanExprde waarde waar wordt geëvalueerd, gebeurt er niets en wordt de uitvoering voortgezet. Als de uitdrukking echter false oplevert, AssertionErrorwordt deze geïnstantieerd en gegenereerd, zoals aangetoond in Listing 1.

Listing 1:AssertDemo.java (versie 1)

openbare klasse AssertDemo {openbare statische leegte hoofd (String [] args) {int x = -1; beweren x> = 0; }}

De bewering in Listing 1 geeft aan dat de ontwikkelaar van mening is dat variabele xeen waarde bevat die groter is dan of gelijk is aan 0. Dit is echter duidelijk niet het geval; de assertuitvoering van de instructie resulteert in een throw AssertionError.

Compileer Listing 1 ( javac AssertDemo.java) en voer het uit met beweringen ingeschakeld ( java -ea AssertDemo). Let op de volgende output:

Uitzondering in thread "main" java.lang.AssertionError op AssertDemo.main (AssertDemo.java:6)

Dit bericht is enigszins cryptisch omdat het niet identificeert wat de oorzaak is van het AssertionErrorgooien. Als u een meer informatief bericht wilt, gebruik dan de assertonderstaande verklaring:

beweren BooleanExpr : expr ;

Hier expris elke uitdrukking (inclusief een methode-aanroep) die een waarde kan retourneren - u kunt geen methode aanroepen met een voidretourneringstype. Een bruikbare uitdrukking is een letterlijke tekenreeks die de reden voor het mislukken beschrijft, zoals aangetoond in listing 2.

Listing 2:AssertDemo.java (versie 2)

openbare klasse AssertDemo {openbare statische leegte hoofd (String [] args) {int x = -1; assert x> = 0: "x <0"; }}

Compileer Listing 2 ( javac AssertDemo.java) en voer het uit met beweringen ingeschakeld ( java -ea AssertDemo). Deze keer zou je de volgende enigszins uitgebreide uitvoer moeten observeren, inclusief de reden voor het gooien AssertionError:

Uitzondering in thread "main" java.lang.AssertionError: x <0 op AssertDemo.main (AssertDemo.java:6)

In beide gevallen resulteert het uitvoeren AssertDemozonder de optie -ea(assertions inschakelen) in geen uitvoer. Als beweringen niet zijn ingeschakeld, worden ze niet uitgevoerd, hoewel ze nog steeds aanwezig zijn in het klassenbestand.

Rand- en postvoorwaarden

Beweringen testen de aannames van een programma door te verifiëren dat de verschillende rand- en postvoorwaarden niet zijn geschonden, waarbij de ontwikkelaar wordt gewaarschuwd wanneer er een overtreding plaatsvindt:

  • Een voorwaarde is een voorwaarde die als waar moet worden geëvalueerd voordat een codereeks wordt uitgevoerd. Randvoorwaarden zorgen ervoor dat bellers hun contract met bellers nakomen.
  • Een postcondition is een voorwaarde die moet worden geëvalueerd als waar na het uitvoeren van een codereeks. Postconditions zorgen ervoor dat oproepers hun contract met bellers nakomen.

Randvoorwaarden

U kunt randvoorwaarden afdwingen aan openbare constructors en methoden door expliciete controles uit te voeren en indien nodig uitzonderingen te gooien. Voor privé-hulpmethoden kunt u voorwaarden afdwingen door beweringen te specificeren. Overweeg lijst 3.

Listing 3:AssertDemo.java (versie 3)

importeer java.io.FileInputStream; importeer java.io.InputStream; importeer java.io.IOException; class PNG {/ ** * Maak een PNG-instantie, lees het gespecificeerde PNG-bestand en decodeer * het in geschikte structuren. * * @param bestandsspecificatiepad en naam van PNG-bestand dat moet worden gelezen * * @throws NullPointerException wanneer filespecis *null* / PNG (String-bestandsspec.) Genereert IOException {// Dwing voorwaarden af ​​in niet-private constructors en // methoden. if (filespec == null) throw new NullPointerException ("filespec is null"); probeer (FileInputStream fis = nieuwe FileInputStream (bestandsspec.)) {readHeader (fis); }} private void readHeader (InputStream is) gooit IOException {// Bevestig dat aan de voorwaarde is voldaan in private // helper-methoden. assert is! = null: "null doorgegeven aan is"; }} openbare klasse AssertDemo {openbare statische leegte hoofd (String [] args) gooit IOException {PNG png = nieuwe PNG ((args.length == 0)? null: args [0]); }}

De PNGklasse in Listing 3 is het minimale begin van een bibliotheek voor het lezen en decoderen van PNG-afbeeldingsbestanden (Portable Network Graphics). De constructor vergelijkt expliciet filespecmet null, gooien NullPointerExceptionwanneer deze parameter bevat null. Het punt is om de voorwaarde af te dwingen die filespecniet bevat null.

Het is niet gepast om te specificeren assert filespec != null;omdat de voorwaarde die wordt genoemd in de Javadoc van de constructeur (technisch) niet zou worden gehonoreerd wanneer beweringen werden uitgeschakeld. (In feite zou het worden gehonoreerd omdat het FileInputStream()zou gooien NullPointerException, maar je moet niet afhankelijk zijn van ongedocumenteerd gedrag.)

Het assertis echter geschikt in de context van de privé- readHeader()helper-methode, die uiteindelijk zal worden voltooid om de 8-byte header van een PNG-bestand te lezen en te decoderen. De voorwaarde waaraan isaltijd een niet-nulwaarde wordt doorgegeven, blijft altijd gelden.

Postcondities

Postconditions worden doorgaans gespecificeerd via beweringen, ongeacht of de methode (of constructor) openbaar is of niet. Overweeg lijst 4.

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

Je hebt misschien een subtiel verschil opgemerkt tussen de bewering en de foutdetectielogica. De bewering test x >= 0, terwijl de foutdetectielogica test x < 0. De bewering is optimistisch: we gaan ervan uit dat het argument in orde is. De logica voor foutdetectie daarentegen is pessimistisch: we nemen aan dat het argument niet OK is. Beweringen documenteren correcte logica, terwijl uitzonderingen onjuist runtime-gedrag documenteren.

In deze tutorial heb je geleerd hoe je beweringen kunt gebruiken om de juiste programmalogica te documenteren. Je hebt ook geleerd waarom beweringen geen vervanging zijn voor uitzonderingen, en je hebt een voorbeeld gezien waarin het gebruik van een uitzondering effectiever zou zijn.

Dit verhaal, "Hoe beweringen in Java te gebruiken" is oorspronkelijk gepubliceerd door JavaWorld.