© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Assertions

 

 

Design by Contract Assertions1 (dk.: påstande) er et begreb der bla. anvendes i forbindelse med design by contact. Lad det være sagt med det samme, assertions dækker kun visse dele af design by contact, og f.eks. er et sprog som Eiffel mere udbygget på dette punkt (har jeg ladet mig fortælle). Til gengæld må man sige, at det kun er meget få sprog, der i nævneværdig grad understøtter design by contract - så vi skal være taknemlige for lidt!
JDK 1.4 Assertions optræder først i JDK 1.4, men var fra starten planlagt som en del af Java. Det gled ud, da man ikke nåede af få det klar til tiden.

 

1. Assert-sætningen

Som nævnt er en assertion, en påstand. I sproglig forstand er en assertion derfor et boolsk udtryk, der indeholder en påstand om programmets tilstand på et givet tidspunkt af dets udførelse.
Lad os se et eksempel2:
public class Main {
  
  public static void main( String[] argv ) {
  
    for ( int i=0; i<=5; i++ ) {
      // her er i<5
      System.out.println( i );
    }
  }
}
Man kan hurtigt konstatere at påstanden ikke altid holder, men bortset fra det, er kommentaren naturligvis også ganske virkningsløs.
Et alternativ kunne være:
public class Main {
  
  public static void main( String[] argv ) {
  
    for ( int i=0; i<=5; i++ ) {
      if ( !( i<5 ) ) {
        System.out.println( "index 'i' løber for langt: i=" + i );
        Thread.dumpStack();
        System.exit( 1 );
      }
      System.out.println( i );
    }
  }
}
0
1
2
3
4
index 'i' løber for langt: i=5
java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1069)
    at Main.main(Main.java:9)
Problemet med denne løsning er, at den er for voldsom i forhold til opgavens størrelse, og er for besværlig at arbejde med.
Java har i stedet en sproglig understøttelse af assertions, der gør det muligt at erstatte if-sætningen med en enkelt linie. Det gøres med det reserverede ord3: assert:
public class Main {
  
  public static void main( String[] argv ) {
  
    for ( int i=0; i<=5; i++ ) {
      assert i<5 : "index 'i' løber for langt: i=" + i;
      System.out.println( i );
    }
  }
}
0
1
2
3
4
java.lang.AssertionError: index 'i' løber for langt: i=5
 	at Main.main(Main.java:7)
  En assert-sætning kan, som her, have en tekst der beskriver hvorfor påstanden fejlede. Generelt kan en assert-sætning have en af følgende former:
assert <boolsk udtryk>
assert <boolsk udtryk> : <objekt-reference>
Det er valgfrit om man vil inkludere en beskrivende tekst, men det vil normalt være en fordel, da en AssertionError i sig selv ikke siger andet end hvilken assertion der fejlede (linienummeret i stack trace) - ikke noget om hvorfor!
  Den sidste af de to assert-typer tager toString på det objekt som udtrykket efter kolon evalueres til.
 

 

1.1 AssertionError

  Man bemærker, at når en assertion ikke holder, bliver der kastet en AssertionError. Det betyder samtidig, at man kan gribe en AssertionError på samme måde som exceptions og andre errors, der kastes. Det skal dog bemærkes at en sådan praksis ikke vil være gavnlig. Man har netop valgt at gøre brud på en assertion til en error og ikke exception, da der er tale om en fejl, og ikke en undtagelse der bør håndteres af programmet selv.
 

 

1.2 Syntaktisk konvention

  Assert-sætningen optræder på samme måde som andre sætninger i kildeteksten - det er ikke hensigtmæssigt!
  Den adskiller sig nemlig ved, at den ikke er en del af den algoritme som Java-programmet udtrykker. Jeg anvender derfor en speciel syntaks omkring assert-sætninger, så de tydeligt skiller sig ud fra den øvrige kildetekst. Det er samtidig en syntaks der giver dem et skær af kommentar (hvis man kender Pascal):
public class Main {
  
  public static void main( String[] argv ) {
  
    for ( int i=0; i<=5; i++ ) {
      { assert i<5 : "index 'i' løber for langt: i=" + i; }
      System.out.println( i );
    }
  }
}
  Tuborg-paranteserne tjener til at markere assert-sætningen som noget særligt - hvilket den også er. Man bemærker at tuborg-paranteserne er virkningsløse ved denne anvendelse, og kun tjener dekorative formål.
 

 

1.3 Er assertions enabled?

  Hvis man, og det vil jeg bestemt ikke råde nogen til, ønsker at lade et program vide om assertions er enabled, kan det gøres med følgende konstruktion:
public class Main {
  
  public static void main( String[] argv ) {
    boolean assertEnabled=false;
    { assert assertEnabled=true; }
  
    if ( assertEnabled )
      System.out.println( "Assert er enabled" );
  }
}
Assert er enabled
Som sagt, gør det ikke!
 

 

2. Opsætning af compiler og interpreter

 

2.1 Compileren

Reserveret ord !? assert var ikke et reserveret ord før JDK 1.4; hvilket giver problemer. Hvordan skal en 1.4 compiler oversætte et "gammelt" program, hvor der f.eks. kan optræde variable der hedder assert?
Lap Dette kræver naturligvis en lap! Lappen er, at man på 1.4 compilere kan sætte en option, der fortæller hvad den skal gøre når den støder på assert i kildetsksten. Mere præcist fortæller man compileren, hvilken version af JDK kildeteksten skal oversættes som. Man anfører følgende option til javac.exe:
-source 1.4
hvis man ønsker at oversætte kildeteksten med det nye reserverede ord assert. Undlader man dette, og anvender assert, terminerer compileringen, og man får en fejlmeddelelse i stil med følgende (vi har knækket første linie, da den gør siden for bred):
Main.java:7: warning: as of release 1.4, assert is a keyword,
and may not be used as an identifier
       assert i<5 : "index 'i' løber for langt: i=" + i; 
Man kan dårligt kalde det en elegant løsning, men det virker!

 

2.2 Interpreteren

Når man starter den virtuelle maskine, kan man indstille hvorvidt assertions skal håndhæves eller ej. Dette gøres ligeledes med en option, og sætter man den ikke, vil programmet blive afviklet som om der ikke var nogen assertions. Ønsker man at programmet skal afvikles med assertions enabled anfører man følgende option til java.exe:
Enable
-ea
ea (enable assertions) betyder at alle assertions i et program er enabled. Man har dog mulighed for at styre dette på både klasse- og pakke-niveau, hvis man ønsker at begrænse hvilke assertions der skal håndhæves:
-ea:<class>
-ea:...
-ea:<package>...
... Bemærk at de tre punkter skal tages bogstaveligt - de skal skrives! De to nederste gør det muligt at slå assertions til for en package (og dermed automatisk også dens subpackages). Udgaven uden navn vedrører den navnløse package (roden).
Hvis man ikke kan lide forkortelser, kan man i stedet for -ea skrive -enableassertions, men det er nok de færreste der vil foretrække det!
Disable Det er også muligt at disable assertions i klasser og packages. Det gøres med -da (eller -disableassertions), som syntaktisk er analog til -ea. java.exe kan tage vilkårligt mange -ea og -da, og man kan på den måde indstille præcist hvilke klasser og packages man ønsker. Man kan f.eks. inkludere en package og dernæst udelukke nogle af dens klasser.
Når man anfører -ea/-da er det rækkefølgen der afgør resultatet.

 

2.2.1 Nedarvning

Hvis assertions er enabled for nogle klasser, men ikke for andre, i et nedarvningshierarki, skal man holde tungen lige i munden.
Arv bestemmer Hvis man som subklasse har disabled assertions, men nedarver fra en klasser, hvor assertions er enabled, vil alle de dele man nedarver have assertions enabled. Omvendt - hvis man som subklasse har assertions enabled, og man nedarver dele fra en klasse, der har assertions disabled, vil de dele man nedarver have assertions disabled.
Logisk? Man kan mene om det hvad man vil. Nogen vil finde det logisk, da de ser på hvor implementationen er placeret. Andre finder det ulogisk, fordi de i første række vil se på de endelige klasser efter nedarvning, og ikke fokusere på hvor de enkelte metoder er implementeret. Personligt ser jeg positivt på den måde man har valgt at gøre det, da assertions primært spiller en rolle i forbindelse med ... netop implementationen!

 

2.3 Opsætning i Kawa

Kawa problem med JDK 1.4 beta3 Når man anvender JDK 1.4 beta3 vil Kawa beklage sig over, at der er to filer den ikke kan finde. Man kan blot trykke OK i dialog-boxen (der meddeler dette), men Kawa's manglende evne til at lokalisere disse filer, gør det umuligt at anvende debuggeren. Der skulle dog ikke være andre fejl/mangler. Jeg ved ikke om det skyldes at JDK 1.4 kun er i beta, eller det er et permanent problem. Hvis det er et permanent problem, vil det formodentlig være uløseligt, da der ikke længere udvikles på Kawa!
Hvis man anvender Kawa, kan man indstille compileren under: Project -> Compiler Options...:
 
Interpreteren indstilles under: Project -> Interpreter Options...:
 

fodnoter:
 
1
Assertions i Java svarer til det man almindeligvis kalder invarianter. Da betegnelsen "assertion" er fremherskende i Java-kredse, vil vi undlade at anvende betegnelsen invariant (i det mindste på skrift).
2
For at køre disse eksempler, er det en forudsætning af man har læst afsnittet om opsætning af compiler og interpreter. Man kan evt. læse dette afsnit først, hvis man gerne vil køre eksemplerne, samtidig med at man læser.
3
Før JDK 1.4 var assert ikke et reserveret ord; hvilket giver visse komplikationer, som man kan læse om under opsætning af compiler og interpreter.