Hur man använder påståenden i Java

Att skriva program som fungerar korrekt under körning kan vara utmanande. Detta beror på att våra antaganden om hur vår kod kommer att fungera när de körs ofta är felaktiga. Att använda Java: s påståendefunktion är ett sätt att verifiera att din programmeringslogik är sund.

Denna handledning introducerar Java-påståenden. Du lär dig först vad påståenden är och hur du specificerar och använder dem i din kod. Därefter kommer du att upptäcka hur du använder påståenden för att genomdriva förutsättningar och eftervillkor. Slutligen kommer du att jämföra påståenden med undantag och ta reda på varför du behöver båda i din kod.

ladda ner Skaffa koden Ladda ner källkoden för exempel i denna handledning. Skapad av Jeff Friesen för JavaWorld.

Vad är Java-påståenden?

Innan JDK 1.4 använde utvecklare ofta kommentarer för att dokumentera antaganden om programkorrekthet. Kommentarer är dock värdelösa som en mekanism för att testa och felsöka antaganden. Kompilatorn ignorerar kommentarer, så det finns inget sätt att använda dem för att upptäcka fel. Utvecklare uppdaterar ofta inte kommentarer när de byter kod.  

I JDK 1.4 infördes påståenden som en ny mekanism för att testa och felsöka antaganden om vår kod. I huvudsak är påståenden  sammanställbara enheter som körs under körning, förutsatt att du har aktiverat dem för programtestning. Du kan programmera påståenden för att meddela dig om buggar där fel uppstår, vilket avsevärt minskar den tid du annars skulle spendera på att felsöka ett misslyckat program.

Påståenden används för att kodifiera kraven som gör att ett program är korrekt eller inte genom att testa villkor (booleska uttryck) för sanna värden, och meddela utvecklaren när sådana villkor är falska. Att använda påståenden kan kraftigt öka ditt förtroende för riktigheten i din kod.

Hur man skriver ett påstående i Java

Påståenden genomförs via assertuttalandet och java.lang.AssertionErrorklassen. Detta uttalande börjar med nyckelordet assertoch fortsätter med ett booleskt uttryck. Det uttrycks syntaktiskt enligt följande:

hävdar BooleanExpr ;

Om det BooleanExprutvärderas till sant händer ingenting och utförandet fortsätter. Om uttrycket utvärderas till falskt AssertionErrorinstanseras dock och kastas, vilket visas i Listing 1.

Listing 1:AssertDemo.java (version 1)

offentlig klass AssertDemo {public static void main (String [] args) {int x = -1; hävda x> = 0; }}

Påståendet i Listing 1 indikerar utvecklarens tro att variabeln xinnehåller ett värde som är större än eller lika med 0. Detta är dock helt klart inte fallet; det assertuttalande mäklarresulterar i en kastad AssertionError.

Kompilera lista 1 ( javac AssertDemo.java) och kör den med påståenden aktiverade ( java -ea AssertDemo). Följ följande utdata:

Undantag i tråden "main" java.lang.AssertionError at AssertDemo.main (AssertDemo.java:6)

Det här meddelandet är något kryptiskt eftersom det inte identifierar vad som orsakade AssertionErroratt det kastades. Om du vill ha ett mer informativt meddelande, använd assertuttalandet nedan:

hävda BooleanExpr : expr ;

Här exprär något uttryck (inklusive en metodinrop) som kan returnera ett värde - du kan inte åberopa en metod med en voidreturtyp. Ett användbart uttryck är en strängbokstav som beskriver orsaken till misslyckandet, vilket visas i Listing 2.

Listing 2:AssertDemo.java (version 2)

offentlig klass AssertDemo {public static void main (String [] args) {int x = -1; hävda x> = 0: "x <0"; }}

Kompilera Listing 2 ( javac AssertDemo.java) och kör den med påståenden aktiverade ( java -ea AssertDemo). Den här gången bör du följa följande något utökade utdata, som inkluderar orsaken till kastet AssertionError:

Undantag i tråden "main" java.lang.AssertionError: x <0 at AssertDemo.main (AssertDemo.java:6)

I båda fallen, körning AssertDemoutan -eaalternativet (aktivera påståenden) ger ingen utdata. När påståenden inte är aktiverade körs de inte, även om de fortfarande finns i klassfilen.

Förutsättningar och efterförhållanden

Påståenden testar ett programs antaganden genom att verifiera att dess olika förutsättningar och efterförhållanden inte bryts, varnar utvecklaren när ett brott inträffar:

  • En förutsättning är ett villkor som måste utvärderas till sant innan någon kodsekvens körs. Förutsättningar säkerställer att uppringare håller sina kontrakt med callees.
  • En postvillkor är ett villkor som måste utvärderas till sant efter utförandet av någon kodsekvens. Efterförhållandena säkerställer att eleverna håller sina kontrakt med uppringarna.

Förutsättningar

Du kan tillämpa förutsättningar för offentliga konstruktörer och metoder genom att göra explicita kontroller och göra undantag när det behövs. För privata hjälparmetoder kan du genomdriva förutsättningar genom att ange påståenden. Överväg att lista 3.

Listing 3:AssertDemo.java (version 3)

importera java.io.FileInputStream; importera java.io.InputStream; importera java.io.IOException; klass PNG {/ ** * Skapa en PNG-instans, läs specificerad PNG-fil och avkoda * den till lämpliga strukturer. * * @param filespec sökväg och namn på PNG-fil att läsa * * @throw NullPointerException när filespecär *null* / PNG (String filespec) kastar IOException {// Tillämpa förutsättningar i icke-privata konstruktörer och // metoder. om (filespec == null) kasta ny NullPointerException ("filespec är null"); prova (FileInputStream fis = ny FileInputStream (filespec)) {readHeader (fis); }} privat ogiltigt readHeader (InputStream är) kastar IOException {// Bekräfta att förutsättningen är uppfylld i privata // hjälparmetoder. assert is! = null: "null överförd till är"; }} offentlig klass AssertDemo {public static void main (String [] args) kastar IOException {PNG png = new PNG ((args.length == 0)? null: args [0]); }}

Den PNGklass i Listing 3 är den minsta början av ett bibliotek för att läsa och avkoda PNG (Portable Network Graphics) bildfiler. Konstruktören jämför uttryckligen filespecmed null, kastar NullPointerExceptionnär den här parametern innehåller null. Poängen är att genomdriva förutsättningen som filespecinte innehåller null.

Det är inte lämpligt att specificera assert filespec != null;eftersom den förutsättning som nämns i konstruktörens Javadoc inte (tekniskt) skulle uppfyllas när påståenden inaktiverades. (I själva verket skulle det vara hedrad för att FileInputStream()skulle kasta NullPointerException, men du borde inte vara beroende av odokumenterat beteende.)

Det assertär dock lämpligt i samband med den privata readHeader()hjälparmetoden, som så småningom kommer att slutföras för att läsa och avkoda en PNG-fils 8-byte-rubrik. Förutsättningen att isalltid passeras ett icke-nollvärde kommer alltid att gälla.

Postvillkor

Postvillkor specificeras vanligtvis via påståenden, oavsett om metoden (eller konstruktören) är offentlig eller inte. Överväg att lista 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.

Du kanske har märkt en subtil skillnad mellan påståendet och feldetekteringslogiken. Påståendet testar x >= 0, medan feldetekteringslogiken testar x < 0. Påståendet är optimistiskt: Vi antar att argumentet är OK. Däremot är feldetekteringslogiken pessimistisk: Vi antar att argumentet inte är OK. Påståenden dokumenterar korrekt logik, medan undantag dokumenterar felaktig körbeteende.

I denna handledning har du lärt dig hur du använder påståenden för att dokumentera korrekt programlogik. Du har också lärt dig varför påståenden inte ersätter undantag, och du har sett ett exempel där användning av ett undantag skulle vara mer effektivt.

Denna berättelse, "Hur man använder påståenden i Java" publicerades ursprungligen av JavaWorld.