Kontraktbaseret Design. Anker Mørk Thomsen

Relaterede dokumenter
Kontraktbaseret Design. Anker Mørk Thomsen

Design by Contract Bertrand Meyer Design and Programming by Contract. Oversigt. Prædikater

Design by Contract. Design and Programming by Contract. Oversigt. Prædikater

Skriftlig eksamen i Datalogi

Ugeseddel 4 1. marts - 8. marts

AAU, Programmering i Java Intern skriftlig prøve 18. maj 2007

Algoritmeskabeloner: Sweep- og søgealgoritmer C#-version

class Time { int hours, min; } } Time t1; // Erklær variabel af type Time class Time1 { public static void main(string[] args) { Time t1; t1.

4 Basal Objekt-orienteret Programmering I.

22 Hobe. Noter. PS1 -- Hobe. Binære hobe. Minimum-hob og maximum-hob. Den abstrakte datatype minimum-hob. Opbygning af hobe. Operationen siv-ned.

Forelæsning Uge 2 Torsdag

Forelæsning Uge 2 Torsdag

Årsagen til fejl. Erkendelse af fejl. Håndtering af fejl.

Abstrakte datatyper C#-version

DM507 Algoritmer og datastrukturer

Forelæsning Uge 2 Torsdag

DM507 Algoritmer og datastrukturer

t a l e n t c a m p d k Matematiske Metoder Anders Friis Anne Ryelund 25. oktober 2014 Slide 1/42

Videregående Programmering for Diplom-E Noter

26 Programbeviser I. Noter. PS1 -- Programbeviser I. Bevis kontra 'check af assertions' i Eiffel. Betingelser og bevisregler.

Klasser og objekter. (Afsnit i manualen)

DANMARKS TEKNISKE UNIVERSITET

Skriftlig eksamen i Datalogi

Sproget Rascal (v. 2)

Objects First with Java A Practical Introduction Using BlueJ

Forelæsning Uge 5 Mandag

Hvad er Objekter - Programmering

Udvidelse og specialisering. Klassehierarkier. Nedarvningsterminologi. Interfaces. Statiske og dynamiske typer. Polymorfi. Abstrakte klasser.

Sproget Six. Til brug i rapportopgaven på kurset Oversættere. Vinter Abstract

DM507 Algoritmer og datastrukturer

Software Construction 1 semester (SWC) Spørgsmål 1

BRP Tal. Om computer-repræsentation og -manipulation. Logaritmer

Kontraktbaseret Programmering Anker Mørk Thomsen 1. udgave ISBN:

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer

Skriftlig Eksamen Algoritmer og Datastrukturer (dads)

DM507 Algoritmer og datastrukturer

Forelæsning Uge 4 Torsdag

Programmering 1999 KVL Side 5-4. Klassen Time: metoder. Metoder i objektet giver mulighed for at ændre tilstanden, eller kigge på tilstanden.

Polymorfi. Arv (inheritance) Abstrakte klasser, substitutionsprincippet, overriding, statisk og dynamisk type. Coercion

Forelæsning Uge 4 Torsdag

Kursus i OOP og Java. Kursus i Objektorienteret programmering i Java

Datalogi OB, Efterår 2002 OH er, forelæsning 10/ Klasser og nedarvning

Martin Olsen. DM507 Projekt Del I. 19. marts 2012 FOTO: Colourbox

Objektorienteret design med arv og polymorfi:

Studiepraktik. Thomas Bøgholm Mikkel Hansen Jacob Elefsen

01017 Diskret Matematik E12 Alle bokse fra logikdelens slides

Forelæsning Uge 4 Mandag

Programmering for begyndere Lektion 2. Opsamling mm

Kursusarbejde 2 Grundlæggende Programmering

3 Algebraisk Specifikation af Abstrakte Datatyper.

Sproget Limba. Til brug i G1 og K1. Dat1E 2003

Forelæsning Uge 2 Torsdag

Eksempel: Skat i år 2000

Målet for disse slides er at diskutere nogle metoder til at gemme og hente data effektivt.

//Udskriver System.out.println("Hej " + ditfornavn + " " + ditefternavn + "."); System.out.println("Du er " + dinalder + " aar gammel!

Elementær Matematik. Mængder og udsagn

1 Opsumering fra tidligere. 2 Dagsorden 3 BIMS. 4 Programtilstande. Statements/kommandoer (Stm) i bims. 3.1 Abstrakt syntaks for bims

Noter til C# Programmering Iteration

Skriftlig eksamen i Datalogi

Greenfoot En kort introduktion til Programmering og Objekt-Orientering

Assignment #5 Toolbox Contract

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 2

Introduktion til funktioner, moduler og scopes i Python

Skriftlig eksamen i Datalogi

Sortering. Eksempel: De n tal i sorteret orden

Jacob Nordfalk. Ingeniørhøjskolen i København. Nykøbing F itvisioncenter 24. februar 2004

4 Oversigt over kapitel 4

CCS Formål Produktblad December 2015

Grundlæggende Programmering ITU, Efterår Skriftlig eksamen i Grundlæggende Programmering

dintprog Manual Revision: 1241 August 24, 2010 I Introduktion 3 1 Notation 3 II Begreber 4 2 Grundbegreber om programmering 4

Software Construction 1. semester (SWC) januar 2014 Spørgsmål 1

Skriftlig Eksamen DM507 Algoritmer og Datastrukturer

16. december. Resume sidste gang

Kursus navn: Indledende programmering Kursus nr

Objektorientering. Programkvalitet

Opgaver hørende til undervisningsmateriale om Herons formel

Rekursion C#-version

Forelæsning Uge 4 Mandag

Andengradsligninger. Frank Nasser. 12. april 2011

Sortering. Eksempel: De n tal i sorteret orden

Udsagnslogik. Anker Mørk Thomsen. 6. december 2013

HTX, RTG. Rumlige Figurer. Matematik og programmering

Sortering af information er en fundamental og central opgave.

b) Udvid din implementation af forme til at understøtte.equals. To objekter af samme form er ens hvis de har samme værdier i felterne.

Dokumentation af programmering i Python 2.75

Forelæsning Uge 4 Torsdag

BOSK F2012, 1. del: Prædikatslogik

Michael Jokil

Tabelbegrebet. Klassediagrammer (III) Oversigt. Anvendelse af Tabeller. Tabeller og qualified associations

METODER ARV KLASSER. Grundlæggende programmering Lektion 5

Sortering af information er en fundamental og central opgave.

Forelæsning Uge 1 Torsdag

Eksempel på den aksiomatisk deduktive metode

Klasser og Objekter i Python. Uge 46 Learning Python: kap 15-16,

ER-modellen. Databaser, efterår Troels Andreasen. Efterår 2002

Python programmering. Per Tøfting. MacFest

Kursusgang 11. Oversigt: Sidste kursusgang Værktøjer til udvikling og implementering af HCI-design Oversigt over Java Swing

Baggrundsnote om logiske operatorer

Forelæsning Uge 6 Mandag

Transkript:

Kontraktbaseret Design Anker Mørk Thomsen 19. februar 2014

-2 Kontraktbaseret Design Anker Mørk Thomsen 1. udgave ISBN: 9788740491500

Forord Bogen er blevet til gennem undervisning i faget Kontraktbaseret Udvikling på bacheloruddannelsen i Softwareudvikling. Emnet har jeg opdelt i to dele. Den første del omfatter konstruktion og udvikling af algoritmer ved anvendelse af formelle metoder. Denne første del indeholder også den diskrete matematik, som danner grundlag for at kunne arbejde med de formelle metoder. Kontraktbaseret Design omhandler design af programmodulerne i et objektorienteret sprog og er inspireret af Design by Contract by example, se Mitchell and McKim [2002]. Januar 2014 Anker Mørk Thomsen -1

Indhold 1 Kontraktbaseret Design 3 1.1 Specifikationer....................................... 4 1.2 Spillet NIM......................................... 5 1.3 Kontrakter......................................... 9 1.4 Introduktion til JML................................... 10 1.4.1 Kontrakt for en procedure............................ 11 1.4.2 Uformelle prædikater i specifikationer..................... 12 1.4.3 Øvelser....................................... 13 1.4.4 Kontrakt for en funktion............................. 13 1.5 Motivation for brug af kontrakter............................ 14 1.6 Interface Clock....................................... 16 1.6.1 Øvelser....................................... 17 1.7 Klassen Medlemsregister................................. 17 1.8 Grundlæggende regler for kontrakter på klasser................... 19 1.9 Kontrakt for interface Personregister.......................... 20 1.10 Øvelser........................................... 24 2 Datatypen Stack 25 2.1 Kontrakt for interface Stack<T>........................... 29 2.1.1 Proceduren Push................................. 29 2.1.2 Den begrebsmæssige model........................... 30 2.1.3 Specifikationen for proceduren push().................... 33 2.1.4 Funktionen top.................................. 33 2.1.5 Proceduren remove................................ 34 2.1.6 Samlet kontrakt for class Stack<T>...................... 34 I

INDHOLD II 2.1.7 Kildekoden til interface Stack<T>...................... 35 2.1.8 Kontrakter for interface-moduler........................ 36 2.2 Den begrebsmæssige model............................... 37 2.3 Øvelser........................................... 38 3 Uforanderlige sekvenser 39 3.1 Sekvenser......................................... 39 3.2 Repræsentation af sekvenser............................... 42 3.3 Basisfunktionerne..................................... 45 3.3.1 Funktionen Head................................. 45 3.3.2 Funtionen Tail................................... 45 3.3.3 Funktionen isempty().............................. 45 3.4 De afledede funktioner.................................. 46 3.4.1 Funktionen getcount()............................. 46 3.4.2 Funktionen precededby()............................ 46 3.4.3 Funktionen elemat()............................... 47 3.4.4 Funktionen concat()............................... 47 3.4.5 Funktionen equals()............................... 47 3.4.6 Funktionen subsequence()........................... 48 3.5 Afsluttende bemærkninger............................... 48 3.6 Øvelser........................................... 50 4 Datatypen Queue<T> 51 4.1 Kontrakt for funktionen insert()........................... 53 4.2 Kontrakt for proceduren Remove()........................... 54 4.3 Hvad skal vi kontrollere?................................. 54 4.3.1 Statisk check.................................... 55 4.3.2 Dynamisk check................................. 55 4.4 Kontrakt for funktionen getfirst().......................... 55 4.5 Kontrakt for funktionen isempty()........................... 56 4.6 Det endelige design af interface Queue........................ 56 4.7 Kildetekst til interface Queue<T>........................... 58 4.8 Øvelser........................................... 59

INDHOLD III 5 Datatypen Mapping<K,V> 61 5.1 interface Mapping<K,V>................................. 62 5.2 Procedurerne insert(k,v) og remove(k)......................... 63 5.3 Kildeteksten til interface Mapping<K,V>....................... 64 5.4 Øvelser........................................... 65 6 Uforanderlige mængder 66 6.1 Datatypen Set....................................... 66 6.2 Kontrakt for funktionen has().............................. 67 6.3 Kontrakt for plus().................................... 68 6.4 Kildetekst til interface Set<T>.............................. 68 6.5 Øvelser........................................... 69 7 Kontrakter for Subtyper 70 7.1 Open-closed principle (OCP).............................. 70 7.2 Liskovs substitutionsprincip (LSP)........................... 72 7.3 Et gammelt eksempel på brud på LSP......................... 74 7.4 Rektangel og Kvadrat................................... 75 7.5 Kildekode til programmet LiskovTest......................... 81 8 Kontrakter for Systemer 84 8.1 Et rammesystem for designmønstret Observer.................... 84 8.2 Kontrakt for observer-systemet............................. 85 8.2.1 Proceduren Attach................................ 86 8.2.2 Proceduren detach(observer obs)...................... 86 8.2.3 Kontrakt for interface Observer....................... 86 8.3 Kildetekst til et observer-system............................ 88 8.4 Øvelser........................................... 91 9 Løsninger til udvalgte øvelser 92

Introduktion Bogen er beregnet til brug som lærebog på kurset Kontraktbaseret Udvikling på erhvervsakademiernes bacheloruddannelse i Software Udvikling. Bogen indeholder pensum til den del af kurset, som omhandler design af programmoduler baseret på specifikationer. Indholdet er inspireret af Michell og McKims Design by Contract by Example Mitchell and McKim [2002]. Design by Contract er et varemærke, som ejes af Bertrand Meyer, som konstruerede programmeringssproget Eiffel. I Design by Contract by Example anvender Mitchell og McKim programmeringssproget Eiffel, hvor man kan skrive specifikationer direkte i koden. Eiffel udmærker sig ved, at specifikationssproget er simpelt og let forståeligt. I Kontraktbaseret Programmering Thomsen [2014] anvendte jeg en programnotation, som blev introduceret af E. W. Dijkstra blandt andet i bogen A Dicipline of Programming Dijkstra [1976] Dijkstra kaldte notationen Q. I denne notation anvendes operatoren := som tilordningsoperator og ikke = som i Java, C #, C og C ++. Anvendelse af = som tilordningsoperator giver det indtryk, at der er tale om lighed og ikke om en aktivitet. Sætningen int k=7 i Java skal ikke læses k er lig med 7 men k sættes til 7. Brugen af = som tilordningsoperator har konsekvensen, at logisk ækvivalens i boolske udtryk kræver en operator, der ikke kan forveksles med =. Sprogene Java, C #, C og C ++ anvender == til sammenligning af simple værdier og referencer. I notationen Q anvendes := som tilordningsoperator og = til sammenligninger. I Java betyder sætningen boolean p = (x==y); at variablen p får tildelt værdien af x = y. I Q skrives dette boolean p:= (x=y) Notationen i Q er bedre end notationen i Java og de andre ovennævnte sprog. Jeg har derfor overvejet at anvende Q, men indtil videre vil jeg bruge Java. Derved er det muligt at afprøve bogens eksempler uden at skulle oversætte dem fra Q til Java. Specifikationerne (kontrakterne) skrives i specifikationssproget JML. Referencemanualen til JML Leavens and et. al. [2002] er på 196 sider. Et skræmmende omfangsrigt og kompliceret dokument til beskrivelse af specifikationssproget. Det er vigtig, at et specifikationssprog er simpelt, og at beskrivelsen af syntaks og semantik kun fylder få sider. JML s kompleksitet afspejler også, at det er lykkedes at udvide Java, så også Java er blevet en programteknisk Jumbo Jet. 1

INDHOLD 2 De fleste af bogens eksempler anvender generiske typer. Anvendelse af exception er undgået. Specifikationerne skrives i en simpel delmængde af JML, hvor kun følgende nøglebegreber anvendes: pre Q; post R; assignable x; invariant C; JML indholder muligheden for at skrive specifikationer på flere niveauer, kaldet letvægts- og sværvægtsspecifikationer. Desuden kan man i JML specificere normal behaviour og exceptional behaviour. Vi anvender kun letvægtsspecifikationer, idet de er fuldt dækkende for en komplet specifikation. Bogens eksempler benytter ikke kald af exceptions undtagen i de tilfælde, hvor der er tale om en programmeringsfejl, eller hvor Java tvinger programmøren til at bruge exceptions (try-catch), f. eks. ved input/output. Anvendelse af exceptions anser jeg for at være en form for goto-programmering og kan kun medvirke til skrivning af programmer, som er vanskelige at gennemskue. Første kapitel indeholder en præsentation af, hvorledes man udformer og udvikler specifikationer for et spil-program samt at par almindeligt anvendte typer af moduler, der repræsenterer registre (objektsamlinger). Kapitlerne 2 til og med 6 behandler kontrakter for en række abstrakte datatyper. Derefter følger et kapitel, hvor bogen behandler kontrakter for sybtyper. I kapitlet om subtyper gennemgås Liskovs subsitutionsprincip. Bogens sidste kapitel behandler kontrakter for et rammesystem, der realiserer designmønstret Observer. Bogen er udformet, så den kan læses uafhængigt af bogen Kontraktbaseret Programmering.

Kapitel 1 Kontraktbaseret Design På engelsk kaldes emnet for Design by Contract. Begrebet er introduceret af Bertrand Meyer allerede i 1986 i programmeringssproget Eiffel. Sproget Eiffel blev beskrevet i en række artikler og senere i bogen Object Oriented Software Construction. I 2009 udgav Springer Verlag Bertrand Meyers nye bog Touch of Class med undertitlen Learning to Program Well with Object and Contracts. Her finder man en grundig indføring i objektorienteret programmering i Eiffel med brug af specifikationer og løkkeinvarianter, se Thomsen [2014]. Kontraktbaseret Design (KD) er idag et omfattende forskningsområde, og teknikken er begyndt at finde anvendelse i erhvervslivet især på områder, hvor det er af stor betydning at kunne skrive korrekte programmer. Et korrekt program overholder de krav, der stilles til programmets udførelse 1. Disse krav opdeles ofte i flere typer af krav. Det kan være krav til udførelsestid, krav til pladsforbrug under udførelsen og ikke mindst krav til funktionalitet. I KD koncentrerer vi os om de funktionelle krav. Vi beskæftiger os ikke generelt med krav til programmernes effektivitet, dvs. programmernes tids- og pladsforbrug. Alligevel vil vi undertiden skele til effektiviteten, dvs. tids- og pladskompleksiteten. De funktionelle krav vil i den udstrækning, det er muligt og hensigtsmæssigt, blive formuleret som formelle specifikationer. Der findes to måder, hvorpå man kan kontrollere, om et program overholder specifikationerne. Man kan udføre programmet med forskellige input og så kontrollere, at programmets resultater svarer til kravene i specifikationerne. Dette svarer til at udføre test. Hvis specifikationerne er formulerede i et formelt sprog, kan run-time-systemet foretage en automatisk kontrol af programudførelsens resultater. Dette kaldes dynamisk prøvning eller test af programmet. Den anden måde består i at analysere programteksten (kildekoden), og gennemføre et bevis for, at programmet overholder specifikationerne. Denne kontrol kaldes en statisk prøvning, og den kræver ikke, at programmet udføres. En statisk prøvning kan afgøre, om et program er korrekt, dvs. at programmet giver korrekte resultater uanset input. Den dynamiske prøvning kan derimod ikke afgøre korrektheden af et program men blot sandsynliggøre, at programmet ikke indeholder fejl. En test af et program kan kun vise tilstedeværelsen af fejl. En test kan ikke vise fravær af fejl. I bogen Thomsen [2014] gennemgås nogle fremgangsmåder til at konstruere korrekte algoritmer. 1 Det har god mening at definere, hvad der forstås ved et korrekt program. Næsten alle større programmer indeholder fejl, som bevirker, at programmerne ikke er korrekte. De udfører ikke i alle tilfælde den ønskede beregning. 3

KAPITEL 1. KONTRAKTBASERET DESIGN 4 Vi er interesseret i at kunne skrive kontrakter for programsystemer. Hvordan skal sådanne kontrakter udformes? Kan vi skrive en kontrakt, der omfatter et helt program endsige et system af programmer? Svaret er både ja og nej. Det vil være en uoverkommelig og meget kompliceret opgave at formalisere alle funktionelle specifikationer for et system af programmer. Vi kan dog indfange mange væsentlige dele af programmernes funktion gennem formelle specifikationer. 1.1 Specifikationer En specifikation for en algoritme består af tre prædikater (betingelser): en præbetingelse, en postbetingelse og en angivelse af, hvilke variabler, der må ændres af algoritmen (assignable). En specifikation for en procedure har følgende opbygning /*@ pre Q; @ assignable x; @ post R; void <navn>(<param>); Nøgleordet void angiver i Java, at metoden er en procedure. <navn> er procedurens navn og <param> er listen af parametre. Procedurens krop indeholder den algoritme, som specifikationen vedrører. Metodesignaturen er også en del af specikationen, idet den definerer input til metoden. I java er alle metoder medlemmer i en klasse, og metoderne kan tilgå klassens variabler og i mange tilfælde også variabler i andre klasser. Metoden kan derfor operere på globale variabler. Selv om det er tilladt i Java, er det dårlig programmeringsskik at anvende globale variabler uden for den klasse, hvor metoden er medlem. Nøgleordet pre efterfølges af præbetingelsen i form af prædikatet Q. Præbetingelsen beskriver de programtilstande, i hvilke det er tilladt at kalde proceduren. Programmets variabler skal have værdier, der opfylder Q, hvis man vil kalde proceduren <navn>. Er Q ikke opfyldt, kan kalderen ikke stille krav til funktionens resultat. Procedurens parametre er input til proceduren. Nøgleordet assignable efterfølges af en liste af de variabler, som må ændres af proceduren. Det er underforstået, at ingen andre variabler må ændres af proceduren. I Java anvendes callby-value, hvilket indebærer, at argumenterne til et procedurekald kopieres til lokale variabler i proceduren. Hvis parametrenes værdier ændres af proceduren, ændrer dette ikke på argumenternes værdier. Der er derfor ingen grund til at nævne parametrene i en assignable-sætning. En ændring af parametrene i procedurens krop har ingen virkning på procedurens omgivelser og specielt ikke på objekttilstanden. Man skal altid nævne de globale variabler, der ændres af proceduren, i assignable-sætningen. Nøgleordet post efterfølges af et prædikat, der beskriver procedurens resultat. Proceduren skal, såfremt den er kaldt på legal vis, terminere i en tilstand, hvor postbetingelsen er opfyldt. Det fordres altså, at proceduren faktisk terminerer. Der må ikke være nogen uendelig løkke i procedurens krop. En algoritme er korrekt, hvis og kun hvis følgende er opfyldt:

KAPITEL 1. KONTRAKTBASERET DESIGN 5 Hvis man begynder udførelsen af algoritmen i en tilstand, hvor præbetingelsen er sand, så skal algoritmen terminere i en tilstand, hvor postbetingelsen er sand. Algoritmen må kun ændre værdierne for de variabler, der er nævnt i assignable-listen. Eksempel 1.1. Proceduren /*@ pre x>=0; @ assignable t; @ post t == \old(t)+x; void inc(int x) Her er x en global variabel, f. eks. en instansvariabel i den klasse, hvor proceduren inc er defineret. En specifikation af en funktion har denne opbygning /*@ pre Q; @ post R; <returtype> <navn>(<param>); <returtype> er angiver typen af den værdi, som funktionen returnerer. Normalt vil der ikke være nogen assignable i specifikationen, da vi normalt ikke tillader, at funktioner påvirker deres omgivelser. Dette indbærer, at globale variabler kun aflæses. Eksempel 1.2. Specifikation for en funktion: /*@ pre 0<=k && k<b.length; @ post \result == (b[k]%x==0); boolean gåropi(int[] b, int k, int x); Funktionen undersøger, om x går op i b[k]. Funktionen må kun kaldes, hvis k er et gyldigt indeks i b. 1.2 Spillet NIM Eksemplet nedenfor viser en funktionel specifikation af et spil mellem to spillere. Den ene spiller er en person. Den anden spiller er computeren.

KAPITEL 1. KONTRAKTBASERET DESIGN 6 Eksempel 1.3. Spillet NIM. Spillet NIM er et tændstikspil med to spillere A og B. Spillet består i, at der lægges nogle rækker med tændstikker, f. eks. fire rækker med henholdsvis (3, 6, 5, 8) tændstikker. Man trækker lod om, hvem der skal starte. På skift må hver af spillerne nu fjerne én eller flere tændstikker fra én række efter eget valg. Den spiller, som tager den sidste tændstik, har vundet. En specifikation for et program, der kan spille NIM mod en person, kan udvikles skridt for skridt. I første omgang beskrives programmets startbetingelse og slutbetingelse. Startbetingelsen skal beskrive, at der er n rækker, der hver indeholder en eller flere tændstikker. Dette modellerer vi med en liste, hvor hvert element i listen angiver antallet af tændstikker i den pågældende række. Lad os kalde listen tr. Udtrykket tr(i) angiver da antallet af tændstikker i række nummer i. Der skal være mindst én række tændstikker, når spillet begynder. Dette kan vi beskrive med betingelsen n > 0. At alle rækker skal indeholde mindst én tændstik, kan vi beskrive med: For alle hele tal i, hvor 0 i < n, skal tr(i) > 0. I matematisk notation skriver vi denne betingelse med prædikatet ( i : 0 i < n : tr(i) > 0) Udtrykket person() angiver, hvem der er i trækket. Hvis person() true er det personens tur til at trække tændstikker. Hvis person() f alse er det computerens (programmets) tur til at trække. Vi kan vælge at trække lod om, hvem der skal begynde spillet. I så fald er startbetingelsen for spillet true. Altså at det er uden betydning, hvilken værdi person() har, når spillet begynder. Læg mærke til, at vi anvender en boolsk funktion person() til at markere, hvem der skal trække. Hvis vi havde brugt en variabel, så ville det kræve, at denne variabel var tilgængelig i implementeringen af spillet. Ved at anvende en funktion overlades det til programmøren, hvorledes hvem skal trække skal implementeres. Spillet skal ende i en tilstand, hvor der ikke er flere tændstikker tilbage. Dette kan vi udtrykke ved at skrive, at summen af alle tallene i tr skal være 0. Specifikationen bliver i JML-notation: /*@ pre (\forall int i; 0<=i && i<n; tr(i)>0) && n>0; @ post (\sum int i; 0<=i && i<n; tr(i)==0); { glovar int n; Program Præbetingelsen fordrer, at programmet begynder i en tilstand, hvor alle rækker indeholder tændstikker. Postbetingelsen kræver, at det korrekte program ender i en tilstand, hvor alle tændstikker er fjernet. Det er slutværdien af person(), der afgør, hvem der har vundet spillet. Programmet skal ændre på resultatet af funktionen tr() og på resultatet af funktionen person().

KAPITEL 1. KONTRAKTBASERET DESIGN 7 Læg mærke til at specifikationen er abstrakt i den forstand, at den ikke indeholder information om, hvorledes spillet kan implementeres. De udtryk, der indgår i specifikationen, er abstrakte og refererer ikke til konkrete variabler. Programmøren kan frit implementere funktionerne tr() og person(), blot implementeringen opfylder specifikationen. Den spiller, som tager den sidste tændstik, har vundet spillet. Spillets resultat kan aflæses af sluttilstanden, idet (+i : 0 i < n : tr(i)) = 0 person() computeren har vundet Specifikationen beskriver ikke spillets regler. En fuldt dækkende specifikation af spillets regler er meget vanskeligt at beskrive formelt. Her må man ty til uformelle beskrivelser og eventuelt eksempler. Spillets regler foreskriver, at de to spillere skiftes til at fjerne tændstikker. Programmet må derfor indeholde en løkke, hvor der i hver iteration fjernes 1 eller flere tændstikker. En mulig skitse til et program i Java er init; while (\sum int i; 0<=i && i<n; tr(i))!=0) if (person()) persontrækker(); else computertrækker(); Både persontrækker() og computertrækker() skal reducere antallet af tændstikker og ændre på person(). /*@ pre (\sum int i; 0<=i && i<n; tr(i))>0; @ post (\exists int i; 0<=i && i<n; tr(i)<\old(tr(i)) @ && (\forall int j: 0<=j && j<n && j!=i; @ tr(j)==\old(tr(j)))); @ post person()==\old(!person()); void persontrækker(); /*@ pre (\sum int i; 0<=i && i<n; tr(i))>0; @ post (\exists int i; 0<=i && i<n; tr(i)<\old(tr(i)) @ && (\forall int j: 0<=j && j<n && j!=i; @ tr(j)==\old(tr(j)))); @ post person()==\old(!person()); void computertrækker(); Programmet kan eventuelt indeholde en metode, der kan trække et antal tændstikker fra en af rækkerne. Denne kan specificeres med /*@ pre 0<=r && r<n && x<=tr(r); @ post tr(r)==\old(tr(r)-x); proc træk(int r, int x)

KAPITEL 1. KONTRAKTBASERET DESIGN 8 Specifikationen siger, at række nummer r skal indeholde mindst x tændstikker. Man skal også have valgt den spiller, der skal begynde at trække tændstikker. Dette kan gøres ved at lade programmet generere en tilfældig værdi for person, eller ved at indgå i dialog med computerens modspiller. Der skal etableres en dialog mellem personen og programmet. Her skal programmet kunne informere om sine træk, og personen skal have mulighed for at indtaste sine træk. Brugergrænsefladen skal indeholde kontroller af, at personen taster korrekte inddata til programmet. Det er ikke nok at forlange, at brugeren taster korrekt. Programmet er nødt til at kontrollere, at personen foretager et tilladt træk. En god brugergrænseflade giver brugeren mulighed for at rette fejl, og den skal samtidig eventuelt kunne vejlede brugeren. De formelle specifikationer (kontrakter) anvendes ikke i forhold til brugeren, men anvendes kun i relationerne mellem programmets moduler (klasser). Der findes en vindende strategi for spillet NIM. Den vindende strategi går ud på at udnytte operatoren xor, som kaldes eksklusiv eller, og som defineres med sandhedstabellen P Q P xor Q f alse f alse f alse f alse true true true f alse true true true f alse Den binære operator i Java (^) er binær xor og virker således: x y x y 0 0 0 0 1 1 1 0 1 1 1 0 Den spiller, som trækker den sidste tændstik, har vundet. Den spiller, som kan sikre sig, at (xor i : 0 i < nim.length : tr(i)) = 0 efter hver gang, spilleren fjerner tændstikker, vil derfor vinde spillet. Hvis man er i trækket og (xor i : 0 i < nim.length : tr(i)) > 0 skal man fjerne et antal tændstikker fra en af rækkerne, så pseudo-invarianten (xor i : 0 i < n : tr(i)) = 0 er sand, når man har trukket. Dette er altid muligt. Hvis (xor i : 0 i < n : tr(i)) = 0 er opfyldt, når man skal trække, må man håbe, at den anden spiller ikke kender den vindende strategi.

KAPITEL 1. KONTRAKTBASERET DESIGN 9 1.3 Kontrakter Figur 1.3.1: Klient og klasse I et objektorienteret sprog er et program opbygget af et antal moduler, der indeholder variabler, funktioner og procedurer. Modulerne anvendes som skabeloner for de objekter (instanser), som programmerne opererer på under programudførelsen. I programmeringssproget Java er modultyperne bl. a. interface og class. I Java kaldes funktioner og procedurer under ét for metoder. Procedurer er karakteriseret ved at have markøren void i stedet returtype. Funktioner er karakteriseret ved at have en returtype. Desuden skal funktioner indeholde mindst én return. I procedurer kan man udelade return. Det er underforstået, at return udføres som den sidste sætning i proceduren. En interface er et modul, der kun indeholder metodesignaturer, dvs. metodens returtype, navn og parameterliste.. En interface indeholder ikke erklæringer af variabler, og det er ikke muligt at danne instanser af en interface. En interface er en type og definerer grænsefladen til et objekt eller dele af grænsefladen til et objekt. En class indeholder både variabler og metoder, og man kan danne instanser af en class. En kontrakt (specifikation) er en aftale mellem en klasse og klassens klienter. Klienterne er de klasser, som indeholder kald af metoder i klassen. En kontrakt for en klasse består blandt andet i at skrive en specifikation for alle funktioner og procedurer i klassens grænseflade. I objektorienterede sprog er dette normalt de funktioner og procedurer, der har attributten public knyttet til sig. Funktioner og procedurer, der er mærket public, er tilgængelige for klienterne, der anvender klassen. Der er dog intet i vejen for at skrive specifikationer for ikke-public metoder. Når vi designer en klasse, skal vi bestemme, hvilke funktioner og procedurer, som klienterne kan anvende, når klienterne opererer på en instans af klassen. Og for hver funktion og procedure skal vi skrive en specifikation. Vi kan skrive specifikationerne som uformelle specifikationer, hvor vi i dagligt sprog beskriver præ- og postbetingelser. Uformelle specifikationer kan også

KAPITEL 1. KONTRAKTBASERET DESIGN 10 omfatte andre former for dokumentation såsom diagrammer og tegninger. Formelle specifikationer kan vi beskrive i matematisk notation eller i et specifikationssprog. JML (Java Modelling Language) er et eksempel på et specifikationssprog til brug i Java-programmer. I specifikationssprogene beskrives præ- og postbetingelser i form af boolske udtryk, hvori der indgår konstanter, variabler og funktionskald. Objekterne indeholder næsten altid data, som kan aflæses og modificeres ved at udføre operationer (funktioner og procedurer). Med funktionerne kan vi aflæse objektets tilstand. Og med procedurerkald kan vi ændre på objekternes tilstand. Klasser er skabeloner for objekterne, og klasserne vil normalt indeholde variabler, der beskriver data. Disse variabler kaldes instansvariabler. Klasser kan også indeholde static medlemmer og dermed også static variabler. Disse variabler er ikke en del af objektets tilstand, men kan anvendes i klasserne som globale variabler. Vi tilstræber at skrive kontrakterne for vore klasser, så programmøren har størst mulig frihed til at vælge implementering af klassen. Dette indebærer, at specifikationerne bør kunne skrives, før man skriver implementeringen af klassen. Dette medfører, at specifikationerne ikke refererer til implementeringen af klassen. Vi skriver faktisk specifikationerne til klassens interface. Der må derfor i specifikationerne aldrig være referencer til klassens variabler. Hvis man i en specifikation refererer til en variabel (en instansvariabel eller en static variabel), så tvinger man programmøren til at anvende denne variabel i sin kode. Samtidig afdækker man detaljer om implementeringen, og det binder også klienten. Vi følger princippet, at specifikationer skal være abstrakte. Når vi designer en klasse, kender vi ikke den konkrete implementering, dvs. den kode, som programmøren vil skrive. Vi ved ikke, hvilke variabler, klassen vil komme til at indeholde. Klienten kender kun klassens interface. Hermed menes, at klienten kun kender klassens public metodesignaturer og de dertil hørende specifikationer. Da vi ikke kan vide, hvorledes en programmør vil realisere klassen, så den lever op til specifikationerne, kan vi ikke referere til de konkrete variabler, som programmøren introducerer i klassen. Vi må anvende en abstrakt beskrivelse af objektets data, og specifikationerne må kun referere til denne abstrakte beskrivelse gennem kald af funktioner. Dette giver mening, idet funktioner afleverer en værdi, der kan indgå i et prædikat. Procedurer beskriver en beregning, der modificerer programtilstanden. Et funktionskald kan indgå i et udtryk. Et procedurekald står alene og kan ikke indgå i et udtryk. Det kan være nødvendigt at indføre nye funktioner, hvis formål alene er at understøtte specifikationerne. Disse funktioner kan eventuelt være private, hvis de ikke skal være tilgængelige for klienten. 1.4 Introduktion til JML De følgende afsnit er en introduktion til specifikationssproget JML og til dette at designe klasser på basis af kontrakter. På engelsk kaldes dette Design by Contract. Vi bruger betegnelsen Kontraktbaseret Design.

KAPITEL 1. KONTRAKTBASERET DESIGN 11 I Java skrives funktioner og procedurer som metoder, der er medlemmer af en klasse. Metoderne kan være forsynet med markøren static. Metoder, der er markeret static, kan kaldes ved at referere til klassens navn, f. eks. A.m1(5), hvor A er en klasse og hvor m1(int x) er en static metode i A. Metoder uden markøren static er instansmetoder, og de er knyttet til en instans af klassen (et objekt). Efter erklæringen A y = new A() kan vi kalde metoden y.m2(10), hvor m2(int p) er en instansmetode. I programteksten for en klasse, kan man anføre tilgængeligheden af klassens medlemmer. Medlemmer, der er erklæret med markøren public, er tilgængelige fra alle andre klasser. Dermed menes, at man må referere til public medlemmer fra alle andre klasser. Medlemmer, der er forsynet med markøren private, kan kun refereres fra kode i samme klasse. Medlemmer, der er forsynet med markøren protected, må refereres fra subklasser til den klasse, hvor medlemmet er placeret samt fra klasser i samme package. 2 Medlemmer, som hverken er public, private eller protected, må refereres fra alle klasser i samme package. 1.4.1 Kontrakt for en procedure Følgende procedure ombytter de to værdier i et objekt af typen Pair. Specifikationen er skrevet i JML; /*@ pre p!=null; @ post p.getx()==\old(p.gety()) && p.gety()==\old(p.getx()); void ombyt(pair p) I JML anvendes pre til at angive præbetingelser på metoder, og post angiver postbetingelser på metoder. Betydningen af pre og post er som i en Hoare-triple. 3 Hvis betingelserne i presætningerne er opfyldt, når metoden kaldes, så garanteres det, at metoden terminerer med alle post-sætningerne opfyldt. Med andre ord, det garanteres, at metoden er korrekt. Det er kalderens ansvar at sikre, at præbetingelserne er opfyldt. Det er programmøren af metoden, der skal garantere korrektheden af metodekroppen. I JML refererer udtrykket \old(u) til den initiale værdi af udtrykket u. Udtrykket \old(p.gety()) refererer altså til den værdi, som funktionen p.gety() ville returnere umiddelbart før udførelsen af et kald af ombyt(pair p). Det fremgår af postbetingelsen, at typen Pair skal indeholde to funktioner getx() og gety(). Læg mærke til, at det ikke vides, om Pair er en klasse eller en interface, idet der ikke refereres til instansvariabler i typen Pair. 2 Reglen for protected er noget kompliceret. Et protected medlem m i klassen A, er synlig fra alle klasser i samme package som A. Medlemmet m er også synlig fra klasser, der ikke ligger i samme package som A, hvis klassen er en subklasse af A. Referencen skal dog ske gennem en implicit eller eksplicit this. Antag, at D er en subklasse af A, og D ligger i en anden package. Så er det tilladt i D at skrive this.m eller blot m. Derimod er det ikke tilladt at skrive: A a = new A();... a.m, altså at referere til m gennem A. 3 Hoare-triplen er beskrevet i Wikipedia

KAPITEL 1. KONTRAKTBASERET DESIGN 12 Følgende procedure anvendes i sorteringsalgoritmen QuickSort. Specifikationen er skrevet i JML /*@ pre 0<=l && l<=p && p<h && h<=b.length; @ post (\forall int i; l<=i<\result; b[i]<=b[\result]); @ post (\forall int i; \result<i<h; b[i]>b[\result]); @ post b[\result]==\old(b[p]); int partition(int[] b, int l, int h, int p){ Udtrykket \result refererer til den værdi, som returneres af funktionen. Al-kvantoren skrives som \forall, og de bundne variabler skal erklæres med angivelse af deres type. Så udtrykket (\forall int i; l<=i<\result; b[i]<=b[\result]) er ækvivalent med ( i : l i < \result : b[i] b[\result]) Det fremgår af specifikationen, at partition(t,x,y,p) kan ændre på både indholdet af argumentet t samt returnerer en int. Det er en hybrid metode, der både fungerer som en funktion og som en procedure, der ændrer på sine omgivelser. Det følgende er et eksempel på et korrekt resultat af et kald af partition. int[] t = {4,1,7,5,9,2,4,7; int x = partition(t,0,8,3); // Gyldigt resultat: t = {2,1,4,4,5,9,7,7 og x=4 I objektorienterede programmer anvendes procedurer normalt til at ændre på det objekts tilstand, hvor proceduren er medlem. Procedurer kan dog ændre på alle de variabler, som er tilgængelige fra proceduren. Her er det vigtigt at overholde reglen om nærhed, dvs. at man undlader at modificere variabler, som er fjernt placeret i programteksten. Det ideelle mønster er, at man kan afdække virkningen af en procedure ved at se på den klasse, hvor proceduren er medlem. 1.4.2 Uformelle prædikater i specifikationer I mange tilfælde må man undlade eller udskyde at skrive formelle prædikater i specifikationerne. JML indeholder muligheden af at skrive uformelle specifikationer. I JML er (* en tekst *) et prædikat, hvis værdi er true i alle programtilstande. Men kan derfor indlægge (*...*) i alle prædikater i en specifikation.

KAPITEL 1. KONTRAKTBASERET DESIGN 13 /*@ pre (* typen T skal være Comparable *); @ frame b @ post (* b skal være sorteret i ikke-aftagende orden *); void sorter(t[] b) Hvis T er en typeparameter i en generisk interface eller klasse, kan man i definitionen af interfacen eller klassen angive, at T skal være en subtype af en eller flere givne typer. I interface A nedenfor interface A<T extends Tlist> {... kan T kun substitueres med subtyper af typerne i Tlist. Tlist er en kommasepareret liste af typenavne. 1.4.3 Øvelser Øvelse 1.1. Skriv metoden partition i afsnit 1.4.1 ovenfor. 1.4.4 Kontrakt for en funktion I funktioner skal kontrakterne indeholde reference til funktionens resultat. Dette sker med udtrykket \result. /*@ pre true; @ post \result==x \result==y && \result<=x && \result<=y; int max(int x, int y) I følgende specifikation af en gammel kending kræves det, at funktionen kun anvendes på en sorteret række. I eksemplet nedenfor er b en sorteret række af Comparable elementer, og der søges efter positionen af et givet Comparable element i rækken. /*@ pre (\forall int i; 0<=i<b.length-1; b[i].compareto(b[i+1])<=0); @ post (\exists int i; 0<=i && i<b.length; @ b[i].compareto(k)<=0 && k.compareto(b[i+1])<0) @ ==> b[\result].compareto(k)<=0 && k.compareto(b[\result+1])<0; @ post!(\exists int i; 0<=i && i<b.length; @ b[i].compareto(k)<=0 && k.compareto(b[i+1])<0) @ ==> \result==-1 int binarysearch(comparable[] b, Comparable k)

KAPITEL 1. KONTRAKTBASERET DESIGN 14 Præbetingelsen bevirker, at man ikke i koden for binarysearch()behøver at kontrollere, at rækken er sorteret. Programmøren kan gå ud fra, at præbetingelsen er opfyldt, når funktionen kaldes. Herved spares man for et gennemløb af rækken. Man kan under udførelsen af programmet beslutte, om JML-systemet skal kontrollere præbetingelsen. Normalt vil man kun udføre denne kontrol under test af programmet. I produktion vil man normalt udelade kontrollen. Postbetingelsen indeholder operatoren ==> som implikation. Udtrykket p ==> q er sandt, hvis enten p er falsk eller q er sand. Vi siger også, at p medfører q. Se Thomsen [2014]. Hvis udtrykket skal evalueres under udførelsen, så må man håbe, at ==> evalueres således, at q kun udregnes, hvis p er sand. Ellers vil beregningen af udtrykket b[\result].compareto(k)<=0 && k.compareto(b[\result+1])<0 resultere i en indeksfejl. Er man i tvivl om dette, kan p==>q omskrives til!p q. Operatoren evalueres af Java med lazy evaluation. En ren funktion kaldes i nogle programmeringssprog en forespørgsel (eng. query). I en ren funktion aflæser man værdier og anvender disse i beregningen af resultatet. En ren funktion må ikke ændre på andre variabler end funktionens lokale variabler. Rene funktioner kan indgå i specifikationerne for funktioner og procedurer. Procedurer kan aldrig anvendes i specifikationerne. I det foregående kan funktionen binarysearch() indgå i en specifikation, mens proceduren partition() ikke kan anvendes i en specifikation. 1.5 Motivation for brug af kontrakter Lad os se på et stykke kode. return getnext(f.getsorted(x.medium()),g.init(p,k)); Der er to måder, hvorpå man kan forstå denne return-sætning. Man kan læse koden for de metoder, der kaldes. I eksemplet betyder det, at man skal læse koden for alle metoderne getnext(), getsorted(), medium() og init() i de klasser, hvor metoderne er defineret. Alternativt kan man læse specifikationerne (kontrakterne) for de metoder, der kaldes. I den første fremgangsmåde, vil man støde på det samme problem for hver kodestump, man læser. Koden for kaldet af getnext() indeholder antagelig kald af andre metoder, som igen indeholder kald af metoder og så fremdeles. At forstå getnext() kan være en besværlig og omfattende proces. Hvis man derimod har en kontrakt for getnext(), så kan man nøjes med at læse og forstå specifikationen. Det samme kan siges om de andre metodekald i kodestumpen. Man kan altså vælge mellem at studere en mængde kode og at studere fire specifikationer. At forstå et program ved at læse koden kan altså være en omfattende og tidskrævende proces, som man det kan være vanskeligt at gennemføre indenfor rimelig tid. Det er et problem, som mange programmører, der har skullet ændre på eller udvide eksisterende kode, har oplevet. Eksempelvis har forfatteren selv oplevet det, da han sammen med et par andre programmører skulle tilrette en kommunes skatteberegningssystem til en ny skattelovgivning. Koden var stort set udokumenteret og der forelå derfor ikke specifikationer af programmets moduler, så man kunne kun forstå koden ved at læse alle detaljer. I det aktuelle tilfælde resulterede det i, at

KAPITEL 1. KONTRAKTBASERET DESIGN 15 skatteberegningssystemet blev skrevet helt om, og det gamle system blev skrottet. Vi gjorde det samme, som man kan gøre ved gamle huse. Man fjerner det gamle hus og bygger et nyt. Nøgleordet her er dokumentation. Og specifikationer (kontrakter) er en af flere muligheder for at dokumentere funktionelle egenskaber ved programmer. Hvis specifikationerne for metoderne og andre programdele er passende abstrakte, kan man uden at skulle igennem mange specifkationer forstå, hvad et metodekald udfører. Prisen for at kunne forstå et stykke kode ved at læse og forstå specifikationerne er, at man ikke kan tillade sig at konkludere andet end det, der fremgår af specifikationerne for de metoder, der kaldes. I ovenstående kodeeksempel er forståelsen af koden baseret på læsning af de fire specifikationerf metoderne getnext(), getsorted(), medium() og init(). Vi skal gå ud fra, at specifikationerne beskriver virkningen af metodekaldene, uanset hvilken implementering, der er anvendt til at programmere metoderne. Altså - hvis man skal forstå, hvad et program udfører, skal man kun anvende specifikationerne for de metoder, der kaldes. Og man kan aldrig anvende inspektion af koden for de kaldte metoder. Når man selv skal skrive et program, hvor man anvender klasser, der er udviklet af andre, så skal man holde sig til at anvende speficikationerne for de metoder, som man vil anvende i sin kode. Man må ikke anvende kendskab til, hvorledes klasserne og metoderne er implementeret. Samtidig er man som programmør forpligtet til at sikre, at alle præbetingelser for metodekaldene er overholdt. Eksempel 1.4. I dette eksempel anvendes to assert-sætninger omkring en linie Java-kode. I eksemplet kan vi konkludere, at variablen \result efter kaldet skal indeholde en værdi, så \result*\result ligger i intervallet [x 0, 0001, x + 0.0001]. /*@ pre x>=0; @ post approxequalto(x,\result*\result,0.0001); double sqrt(double x){ return Math.sqrt(x); hvor /*@ post \result==(x-y<0?y-x<=e x-y<=e); boolean approxequalto(y,x,e) Vi har kaldt en static metode sqrt i klassen Math. Det er ikke tilladt at kigge i koden for metoden sqrt for at se, hvorledes metoden er implementeret. Antag, at kontrakten for sqrt specificerer, at sqrt beregnes med 4 decimalers nøjagtighed. Selv hvis koden til den aktuelle implementering af sqrt viser, at resultatet beregnes med 7 decimalers nøjagtighed, så må man kun anvende den nøjagtighed, der fremgår af kontrakten.

KAPITEL 1. KONTRAKTBASERET DESIGN 16 1.6 Interface Clock Vi vil benytte interface Clock til at illustrere principperne i Kontraktbaseret Design samt til at præsentere sproget JML (Java Modelling Language). Interfacet Clock beskriver en model af et ur, hvor man kan registrere et tidspunkt med angivelse af timer, minutter og sekunder. Derfor indeholder klassen funktioner, der kan aflæse timer, minutter og sekunder. Man kan initialisere en instans af Clock til et givet tidspunkt. Klassen indeholder desuden en procedure, der kan sætte uret ét sekund frem. Her er et første udkast til klassen skrevet som et interface Clock. interface Clock { /* Interfacen IClock repræsenterer et tidspunkt * med et sekunds nøjagtighed. * Et objekt af typen IClock kan anvendes * af et ur til at vise tiden. */ //@ invariant getelapsedtime()== (gethours()*60+getminutes())*60+getseconds(); /*@ post (* \result = antal sekunder siden midnat *); int getelapsedtime(); /*@ post 0<=\result && \result<24; int gethours(); /*@ post 0<=\result && \result<60; int getminutes(); /*@ post 0<=\result && \result<60; int getseconds(); /*@ pre 0<=h && h<24 && 0<=m && m<60 && 0<=s && s<60; @ post gethours()==h && getminutes()==m && getseconds==s; void settime(int h, int m, int s); /*@ @ post getelapsedtime()==\old(getelapsedtime()+1)%(60*60*24); void tick();

KAPITEL 1. KONTRAKTBASERET DESIGN 17 Denne interface med kontrakter indeholder ingen information om, hvorledes man har tænkt sig at implementere klassen. Interfacen indeholder heller ikke informationer udover i den indledende kommentar, der viser, hvad klassen skal bruges til. 1.6.1 Øvelser Øvelse 1.2. Skriv en implementering af interfacet Clock. 1.7 Klassen Medlemsregister Lad os se på endnu et eksempel nemlig en klasse, der repræsenterer et medlemsregister i en forening. Et medlemsregister kan eventuelt beskrives ved en klasse class Medlemsregister { public Medlem find(int medlnr); public void opret(medlem m) hvor vi kun har medtaget to operationer, nemlig funktionen Find og proceduren Opret. Med koden Medlemsregister reg = new Medlemsregister(); oprettes et medlemsregister-objekt, og vi kan udføre reg.find(nr) og reg.opret(m). Både find() og opret() opererer på data i medlemsregister-objektet, men vi kan ikke vide, hvorledes en programmør vil realisere klassen. Medlemmernes data kan opbevares i en kædet liste, i en indekseret liste, i et katalog eller i en database. Vi kender ikke programmørens valg. Måske har kunden forlangt, at programmøren anvender en database. Alligevel vil vi ikke udnytte dette, da vores specifikation skal kunne anvendes til andre implementeringer. Vi ønsker altså, at klienten skal kunne anvende klassen på samme måde uanset, hvordan den er implementeret. Derfor skal vore specifikationer være abstrakte. De må ikke referere til implementeringsdetaljer. I eksemplet nedenfor skriver vi specifikationerne i JML-notation. Til brug for specifikationerne vil vi danne en begrebsmæssig model af medlemsregistret. Vi vil opfatte medlemsregistret som er register, hvor medlemmernes data er identificeret ved et entydigt medlemsnummer. Medlemsdata vil vi repræsentere i en klasse Medlem. Medlemsnumrene tænker vi os placeret i en mængde, som vi kan tilgå ved at kalde funktionen medlnumre(). /*@ post (*Indeholder alle medlemsnumre i registret*); public List<Integer> medlnr(){

KAPITEL 1. KONTRAKTBASERET DESIGN 18 Funktionen medlnumre() repræsenterer vores abstrakte model af medlemsregistret. Implementeringen af funktionen medlnumre() sker i samenhæng med implementeringen af de andre metoder i klassen. Vi anvender kald af funktionen medlnumre() i specifikationer, hver gang vi skal have adgang til alle medlemsnumre i registret. class Medlemsregister { /*@ post getcount()==0; public Medlemsregister() { /*@ post (*Indeholder alle medlemsnumre i registret*); public List<Integer> medlnumre(){ return null; /*@ post \result==medlnumre.length; public int getcount(){ return medlnumre.size; /*@ pre medlnr().contains(nr); @ post (\result).getmedlnr()==nr; public Medlem find(int nr){ return null; /*@ pre!medlnr().contains(m.getmedlnr()); @ post medlnr().contains(m.getmedlenr()); @ post find(m.getmedlenr()).equals(m); public void opret(medlem m){ Programmøren er ikke bundet til at implementere klassen på en bestemt måde, men implementeringen skal overholde specifikationerne. Læg mærke til, at startbetingelsen til find(nr) er, at der findes et medlem i registret med det givne medlemsnummer. I opret(m) er startbetingelsen, at medlemmet ikke i forvejen er i registret. Funktionen getcount() er allerede implementeret på en ligefrem måde. En programmør må gerne ændre denne implementering, men programmørens nye implementering skal opfylde postbetingelsen. En alternativ specifikation for metoden find kunne være

KAPITEL 1. KONTRAKTBASERET DESIGN 19 /*@ pre true; @ post contains(medlnr)==>\result.getmedlnr()==medlnr; @ post!contains(medlnr)==>\result==null; public Medlem find(int medlnr); hvor man kan undersøge, om medlemsregistret indeholder et medlem med et givet medlemsnummer. Hvis der ikke findes et objekt i registret med det givne medlemsnummer, returneres værdien null. Som vi har set, kan specifikationerne anvendes til at konstruere algoritmerne i klassens funktioner og procedurer. Men denne proces kan kun gennemføres, hvis vi skaber en forbindelse mellem de abstrakte data og de konkrete variabler. Dette er programmørens opgave. Der er et udbredt ønske om at lade run-time-systemet afprøve, om implementeringen overholder specifikationerne. Altså, at lade specifikationerne være skabeloner for automatiske test. Dette indebærer, at specifikationerne skal skrives, så de kan fortolkes og udføres af systemet. Dette kan finde sted ved at lade programmet blive modificeret af en såkaldt præprocessor eller ved at lade oversætteren fortolke og oversætte specifikationerne. Der arbejdes flere steder på at konstruere programmeringssprog og udviklingssystemer, som understøtter kontraktbaseret udvikling. I MicroSoft-regi er der et forskningsprojekt igang, hvor man arbejder med udvikling af sproget spec #. Man kan anvende spec # i Visual Studio. Også i Java-verdenen arbejdes der med kontraktbaseret udvikling. Der er f. eks. udviklet et sprog JML (Java Modelling Language), der kan anvendes til at integrere kontrakter i Java-programmer. Der findes flere andre muligheder for at indføre kontrakter i et Java-program. Allerede i 1985 konstruerede Bertrand Meyer programmeringssproget Eiffel. Eiffel er et objektorienteret programmeringssprog, hvori Bertrand Meyer indbyggede muligheden for at skrive formelle specifikationer (kontrakter) og mekanismer, der understøtter formelle metoder til konstruktion af algoritmer, bl. a. kan man anvende invarianter og varianter ved konstruktion af løkker. Sproget understøttes af et udviklingssystem, som kaldes Eiffel Studio. Man kan også hente systemet Visual Eiffel på Internet. Visual Eiffel er et Open Source system, som udsendes under GNU-licensen. Der findes en lang række artikler og bøger om sproget og systemet Eiffel 4. 1.8 Grundlæggende regler for kontrakter på klasser En kontrakt til en klasse er en aftale mellem programmøren og klienten. Kontrakten specificerer, hvorledes instanserne af klassen skal opføre sig. Normalt er klienten ikke involveret i, hvorledes programmøren vil implementere klassen. Klienten er primært interesseret i klassens funktionalitet. Klienten skal vide, at opretter man en instans af klassen (et objekt), og hvis man ved kald af objektets metoder overholder kontraktens præbetingelser, så vil metoderne terminere efter en endelig tid og i en programtilstand, der overholder postbetingelserne. Der kan 4 Bertrand Meyer: Touch of Class. Springer 2009. ISBN 978-3-540-92144-8

KAPITEL 1. KONTRAKTBASERET DESIGN 20 dog være situationer, hvor klienten har krævet en bestemt implementering. Det skal programmøren naturligvis også tage hensyn. Man må så håbe, at klientens krav ikke er i konflikt med specifikationerne. Klienten skal kun kende til klassens grænseflade dvs. til public-medlemmer og ikke implementeringen. Konsekvensen er, at specifikationerne af klassens metoder kun må referere til de af klassens medlemmer, som klienten kender til. Og det er medlemmer, der er markeret public. Desuden skal disse medlemmer alle være rene funktioner. Specifikationerne vil desuden ofte referere til inputparametre. f. eks. hvis der stilles specifikke krav i præbetingelsen. Vi har derfor følgende princip: Kontrakter må ikke indeholde reference til implementeringsdetaljer. Systemets kontrol af kontrakternes overholdelse sker gennem udførelse af public metoder og reference til public variabler. Et kald af en metode i en kontrakt må ikke ændre på objektets tilstand, da dette vil medføre ukontrollerbare sidevirkninger. De funktioner, der refereres til i JML-specifikationerne skal alle være rene. En ren funktion er en metode, der ikke modificerer programtilstanden. Funktioner kan erklæres rene med //@ pure som vist nedenfor //@ pure; T F(A a); Hermed har man erklæret, at ingen implementering af funktionen F må ændre på objekttilstande. 1.9 Kontrakt for interface Personregister I et personregister skal vi kunne indsætte personer, slette personer, ændre på oplysningerne om personer samt kunne få relevante statistiske informationer fra registret. Vi vil begynde med at designe en abstrakt grænseflade til klassen i form af en interface. Vi beslutter at registrere personerne i registret med et entydigt personnummer og dertil hørende persondata. Persondata kunne være navn, adresse, køn og alder. Vi opererer således med to klasser til beskrivelse af personer, nemlig klassen Person og klassen Persondata. Vi vil senere skrive specifikationerne for disse klasser. Specifikationerne skrives i første omgang som uformelle specifikationer. I JML kan man skrives uformelle specifikationer ved at skrive (* en tekst *). JML opfatter (*...*) som et prædikat, der altid er true. interface Personregister { /*@ pre (* Personen p må ikke findes i registret *) @ post (* Indsætter en ny person p i registret *);

KAPITEL 1. KONTRAKTBASERET DESIGN 21 void insert(int pnr, Persondata pd); /*@ pre (* Personen med nummeret pnr skal være i registret *) @ post (* Sletter personen med nummeret pnr fra registret *) void remove(int pnr); /*@ pre (* Personen med nummeret pnr skal være i registret *) @ post (* Indsætter nye data om personen med nummeret pnr *) void modify(int pnr, Persondata pd); /*@ @ post (*returnerer antallet af personer i registret*) //@ pure; int getcount(); /*@ pre (* Personen med nummeret pnr skal være i registret *) @ post (* Returnerer data om personen med nummer pnr *) PersonData getperson(int pnr); /*@ @ post (* Undersøger, om registret indeholder en person *) @ post (* med nummeret pnr findes i registret *) //@ pure; boolean contains(int pnr); /*@ pre (* Registret skal indeholde mindst én person *) @ post (*returnerer personernes gennemsnitsalder*) //@ pure; float getaverageage(); Nu formulerer vi en kontrakt for interface-modulet. Læg mærke til, at vi ikke ved, hvorledes programmøren kunne tænke sig at implementere interfacen. Vi har også en interesse i at give programmøren så frie hænder som muligt. Ligesom med medlemsregistret vil vi opfatte personregistret som en 1 1 funktion PReg : int PersonData fra de naturlige tal ind i mængden af personer i registret. Vi betegner definitionsmængden (domænet) for funktionen PReg med dom. Billedmængden kaldes rangen. Faktisk er PReg en såkaldt partiel funktion, idet vi vil kunne udvide funktionens definitionsmængde. Dette sker med proceduren insert(), som indsætter en ny person i registret.