Kurt Nørmark ©
Department of Computer Science, Aalborg University, Denmark
September 2001
Abstract Previous lecture Next lecture Index References Contents | I denne lektion tager vi udgangspunkt i en meget vigtig kvalitet ved software, nemlig korrekthed. Vi stiller os selv spørgsmålet: 'korrekt i forhold til hvad'? Svaret på spørgsmålet er korrekthed i forhold til en specifikation. Vi ser på forskellige muligheder for at specificere et program, og vi vælger at koncentrere os om brugen af logiske udtryk i termer af pre- og postbetingelser. Ud over specifikationsproblemet ser vi også på problemstillingen omkring ansvarsfordeling. I et stort program er der meget vundet ved at være bevidst og eksplicit om fordelingen af ansvar mellem forskellige dele af programmet. Disse dele er helt naturligt objekter og operationer i det objekt-orienterede paradigme. Under behandlingen af ansvarsfordeling læner vi os op ad en god metafor, nemlig kontraktbegrebet, som vi ofte støder på i vores hverdag. Det viser sig at kontraktbegrebet - og subkontrakter - kan forklare væsentlige aspekter af nedarvningsmekanismen. Dette ser vi naturligvis på. Vi slutter af med forskellige praktiske spin off effekter af de foregående ideer, såsom kontraktovervågning (i forhold til programbeviser) samt undtagelseshåndtering. |
Specifikationsproblemet |
Softwarekvalitet Slide Note Contents Index References Speak | Vi ønsker at bibringe den software, vi producerer, en række positive egenskaber. Sådanne positive egenskaber vælger vi at kalde 'softwarekvaliteter'. Vi diskuterer her bredt og overordnet, hvilke kvaliteter vi kan gå efter |
|
Table. En række kvaliteter ved software, deres beskrivelse, og som kontrast, deres modsætning. Kvaliteter er positivt ladede ord. |
|
Korrekthed Slide Note Contents Index References Speak | Korrekthed er den oplagte softwarekvalitet. Her udfordrer vi hinanden på, hvad vi egentlig forstår ved korrekthed |
|
|
Specifikation Slide Note Contents Index References Speak | Specifikation går forud for implementation. Vi kan opfatte specifikation som en del af designfasen. Her ser vi på forskellige aspekter af specifikationer |
The concept specifikation: En program specifikation er en eksakt forskrift som skal opfyldes af program implementationen En specifikation besvarer 'hvad spørgsmål' i modsætning til 'hvordan spørgsmål' | En specifikation beskriver og foreskriver egenskaberne af en efterfølgende program implementation. Ideelt beskæftiger en specifikation sig med 'hvad' et program skal gøre, ikke noget om 'hvordan' det skal ske |
Algebraisk specifikation er en elegant teknik, som tillader os at specificere abstrakte datatyper. Kerneideen er en angivelse af, hvordan de forskellige operationer (funktioner) virker på såkaldte konstruktorer (også funktioner) I denne lektion vil vi koncentrere os om axiomatiske specifikationer |
Program: Et eksempel på en algebraisk specifikation af en stak.
Øverst ser vi en syntaktisk definition af signaturerne af de operationer,
som virker på en stak. Alle operationer skal være funktioner.
Nederst ses den semantiske specifikation af stakken.
Princippet er at vise hvordan de forskellige stakfunktioner virker på
konstruktorerne, som er new og push.
|
|
Program: Et eksempel på en axiomatisk specifikation af en kvadratrodsfunktion.
|
|
Specifikation med pre- og postbetingelser |
Logiske udtryk samt pre- og postbetingelser Slide Note Contents Index References Speak | Pre- og postbetingelser er rygraden i axiomatiske specifikationer |
The concept logisk udtryk: Et logisk udtryk er et udtryk af typen boolean | 'Logiske udtryk' er betegnelsen for den delmængde af udtryk som beregnes til et boolsk resultat (altså true eller false resultat). Et logisk udtryk kaldes også ofte for et udsagn | |
The concept prebetingelse: En prebetingelse af en operation er et logisk udtryk, som skal være opfyldt umiddelbart før operationen kaldes | En prebetingelse er et udsagn om situationen før kaldet af en operation. I prebetingelsen er der mulighed og behov for at referere til operationens parametre | |
The concept postbetingelse: En postbetingelse af en operationer et logisk udtryk, som skal være opfyldt umiddelbart efter at operation er udført | En postbetingelse er et udsagn om situationen efter kaldet af operationen. Hvis operationen er en procedure karakteriserer postbetingelsen programtilstanden efter operationskaldet. Hvis operationen er en funktion beskriver postbetingelsen resultatet (output) i forhold til parametrene (input) |
|
|
Eksempel på brug af pre- og postbetingelser i et objekt-orienteret program Slide Note Contents Index References Speak | Vi har talt om pre- og postbetingelser i forhold til operationer. I objekt-orienteret programmering hører operationerne til i klasser. Vi går nu over til at studere pre- og postbetingelser af operationer i en klasse. Vi ser indledningsvist på et konkret eksempel |
|
Program: Klassen CircularList med pre- og postbetingelser.
Alle andre detaljer er ignoreret.
Prebetingelser angives med nøgleordet require og postbetingelse med
nøgleordet ensure. Denne syntaks stammer direkte fra Eiffel. Bemærk
venligst at programmet ikke er programmeret i Java, idet Java ikke understøtter
require og ensure. Vi antager at countElement er en operation som rent faktisk tæller antallet af elementer i Listen. Endvidere at prædikaterne isFirst og isLast er funktioner, der fortæller hvorvidt et element er det første hhv. det sidste element i listen. Vi benytter også funktionerne retrieveSecond og retrieveButLast til returnering af hhv. listens andet og næstsidste element. Bemærk specielt udtryksformen old og noChange. Old foran et udtryk i postbetingelsen beregner udtrykket
i den programtilstand der var gældende umiddelbart før kaldet af operationen. NoChange er opfyldt, hvis
operationen ikke har ændret programtilstanden. (NoChange er med andre ord opfyldt, hvis operationen
er en funktion). Old og NoChange giver kun mening i postbetingelser. |
|
Exercise 9.1. Pre- og postbetingelser i CircularList | På den tilknyttede slide er der vist pre- og postbetingelser for klassen CircularList.
I betingelserne anvendes funktionerne empty(), countElements(), isCircular(), isFirst(Object),
isLast(Object), retrieveSecond() og retrieveButLast(). Denne opgave går ud på at implementere ovennævnte funktioner som private operationer i klassen CircularList. Simuler pre- og postbetingelserne ved at kalde funktionerne hhv. først og sidst i klassen's metoder. Hvis der brydes en prebetingelse skal man udføre: throw new PreconditionBroken() Tilsvarende skal et brud af en postbetingelse føre til udførelse af: throw new PostconditionBroken() Download filen med min løsning af opgaven om Cirkulære lister. Denne fil indeholder endvidere de nødvendige erklæringer af de ovenfor nævnte exception klasser. Udtrykkene i 'old ...' kan volde særlige problemer. Ideen med old er at kunne evaluere et udtryk i den programtilstand som bestod netop inden metoden blev kaldt. Derfor vil det være en mulighed at forudberegne disse 'old udtryk' i starten af metoden. |
Et sprog til formulering af 'assertions' Slide Note Contents Index References Speak | Hvis vi ønsker at behandle assertions formelt skal der naturligvis eksistere et sprog, hvori assertions kan formuleres |
|
Egenskaberne af et muligt specifikationsssprog er direkte inspireret af det objekt-orienterede programmeringssprog Eiffel Årsagen til, at kvantorer er problematisk, er udtryk for er ønsket om at kunne efterchecke assertions i et kørende program. Hvis vi ønsker 'kvantorudtryksform' kan vi falde tilbage på at programmere boolske funktioner, som returnerer hvorvidt 'alle ... opfylder en betingelse' eller 'om der eksisterer ... der opfylder en betinglse' Programmerede udsagn er problematiske derved, at specifikationen ikke ønskes afhængiggjort af programmet. Hvad skal vi f.eks. stille op med specifikationen af de programmerede udsagn; og hvad hvis der er fejl i disse dele af programmet? |
Ansvarsfordeling og kontrakter |
Ansvarsfordeling Slide Note Contents Index References Speak | Bevidsthed om ansvarsfordeling er en mulig nøgle til at holde programkompleksiteten i ave. Her ser vi først generelt på problemstilling om 'ansvarsfordeling' |
|
|
Det overansvarlige program Slide Note Contents Index References Speak | For at skabe en kontrast til vores videre diskussion af ansvarsfordeling vil vi se på et program, hvor de enkelte dele (klasser) påtager sig et for stort ansvar. |
Figure. Et eksempel på et program med overlappende ansvarsforvaltning. Programmet er en karikatur af et overansvarligt program. Vi ser flere eksempler på at både klient og Kontoklassen tester for de samme forhold. Endvidere er kontoklassen designet ud fra en overansvarligheds filosofi; Hvis man hæver et negativt beløb 'får man det bedste ud af situationen' ved nemligt at indsætte det tilsvarende positive beløb. Det tilsvarende gør sig gældende hvis man indsætter et negativt beløb | ![]() |
Exercise 9.2. Kontrakter i Bankkonto | Denne opgave handler igen om én af vore favoriteksempler, nemlig klassen Konto. Ved øvelserne i lektion 3
blev der stillet en opgave om at repræsentere en Konto med en transaktionshistorie.
Brug løsningen på den tidligere opgave hvis I har behov for at hente et passende udgangspunkt for denne opgave. Forsyn klassen konto med en kontrakt. Mere specifikt:
Vær specielt opmærksom på invarianten, som skal sikre konsistens mellem datarepræsentationen (transaktionshistorien) og de eksterne egenskaber af kontoen. (Man kan iøvrigt hente meget konkret inspiration til denne opgave i artiklen 'Applying Design by Contract'). |
Det ansvarsløse program Slide Note Contents Index References Speak | Som en kontrast til programmet på forrige side viser vi her 'det ansvarsløse program. |
Figure. En kontrast til det overansvarlige program. Vi har blot strippet programmet for al overansvarlighed. Dette viser, hvor store mængder af program der skal vedligeholdes, hvis vi ønsker at gå med både livrem, seler, og hvad man nu ellers går rundt med for at holde bukserne oppe... | ![]() |
Ansvarsfordeling specificeret med pre- og postbetingelser Slide Note Contents Index References Speak | Vi fastlægger nu hvordan man kan bruge pre- og postbetingelser til at definere en ansvarsfordeling mellem klienter og forsyner objekter. I forhold til det overansvarlige program, som blev diskuteret ovenfor, trækker vi nogle tests (betingelser) ud af programmet til fordel for en placering i specifikationen. Pre- og postbetingelser, som bliver brudt, er udtryk for meget alvorlige fejl, som for de fleste programmer bør være fatale. Altså, hvis en pre- eller postbetingelser bliver brudt, giver det ikke mening at fortsætte programudførelsen |
|
|
Kontraktbegrebet Slide Note Contents Index References Speak | Vi introducerer nu kontraktbegrebet for software, som en metafor i forhold til kontrakter kendt fra vores hverdag. Vi omgås alle med mange forskellige kontrakter. Eksempelvis indgår vi i et kontrakt-lignende forhold med vores forsikringsselskab; kontrakten udtrykker gensidige forpligtigelser hvis der opstår en skade. Hvis vi køber en bil skriver vi under på en kontrakt med bilforhandleren om leveringsbetingelser mv. Hvis vi optager et lån i en bank indgår der en kontrakt. Der er også talrige kontrakter i spil i forhold til offentlige myndigheder, som oftest er påtvungen os ved lov. |
|
The concept kontrakt: En kontrakt udtrykker de gensidige forpligtelser mellem to dele af et program, som samarbejder om løsningen af et problem | En kontrakt mellem to dele af et program svarer til kontrakt begrebet som vi kender det fra den virkelige verden. Her er
altså tale om en gensidig forpligtigelser mellem to parter, som har indgået en aftale. Ifølge Nudansk ordbog er en kontrakt en 'bindende skriftlig overenskomst'. Ordbogen eksemplificerer dette med leje-, engagement-, eller købssituationer. |
Vi kan også sige, at kontraktbrud opfattes som en fatal fejl i programmet, som er udtryk for et brud på specifikationen |
Kontrakter: ydelse og nydelse Slide Note Contents Index References Speak | En kontrakt indebærer som regel både fordele og forpligtelser. Vi benytter her de lidt poppede, men ganske sigende betegnelser 'ydelse' og 'nydelse' |
Figure. Figuren udtrykker at der både er forpligtelser (ydelser) og lettelser (nydelser) når man indgår en kontrakt. Vi viser her nydelse og ydelse i forhold til en forsyner klasse og en klient klasse | ![]() |
En operation i klassen lover at opfylde post-betingelsen såfremt
klienten kalder operationen med opfyldt prebetingelse. Kontrakten er
udtryk for en klar ansvarsfordeling mellem klient og leverandøren. Det
er klientens ansvar at undersøge, om prebetingelsen af
supplier-operationen er opfyldt. Hvis dette ikke er tilfældet, bliver
supplier-operationen ikke kaldt, og der opstår en fejlsituation i
klienten. Det er således ikke nødvendigt i kroppen af en operation i
leverandør-klassen at teste, om operationens pre-betingelse er
opfyldt. Den skal være opfyldt. Hvis supplier-operationen ikke opfylder sin post-betingelse opstår der en fejlsituation i supplier klassen. Denne klare ansvarsfordeling, og deraf følgende minimale, test af betingelser forud for udførelsen af en operation, står i skarp kontrast til defensiv programmering. Som vi har omtalt går man i defensiv programmering både med livrem og seler i den forstand, at man gerne udfører samme test (f.eks. prebetingelsen) to forskellige steder i programmet, for at forhindre misforståelser om ansvarsfordelingen. I og med at opfyldelse af prebetingelsen er klientens ansvar, skal alle dele af en prebetingelse være tilgængelig for klienterne. |
Ydelse og nydelse i kvadratrods funktionen Slide Note Contents Index References Speak | For at konsolidere forståelseen af idéen om ydelse og nydelse i forbindelse med kontrakter viser vi her i specifikationen af kvadratrodsfunktionen hvad der ydelse og hvad der er nydelse |
Kvadratrod(x: Real) -> Real require x >= 0; ensure result * result = x; |
Table. Ydelse og nydelse i kvadratrodsfunktionen. Klienten er kalderen af funktionen. Forsyneren er selve funktionen. |
|
Generelle kontraktaspekter: klasseinvarianter |
Klasseinvarianter Slide Note Contents Index References Speak | Pre- og postbetingelser er specifikationselementer, som er knyttet til operationer. Vi kan drage fordel af pre- og postbetingelser i både konventionel imperativ programmering og i funktionsorienteret programmering. I forbindelse med vores introduktion af klasseinvarianter vil vi nu for alvor drage fordel af, at vi organiserer et program i objekter |
|
The concept klasseinvariant: En klasseinvariant udtrykker egenskaber af et objekt, som er uforanderlige i forhold til en række påvirkninger af objektet | En invariant er en egenskab som er uforanderlig når et objekt (eller lignende) bliver udsat for en række påvirkninger |
|
Eksempel. Der skal altid være én kop kaffe på kanden | Invarianter kendes også fra vores ikke-tekniske hverdag. Det er f.eks. en regel, at kanden på afdelingens kaffemaskiner altid skal indeholde mindst én krus kaffe. Alle transaktioner på kaffemaskinen skal overholde denne invariant. Bemærk at handlingerne, der beståer i at tømme kaffekanden samt at lave ny kaffe er at betragte som én transaktion i denne model. På et tidspunkt er kanden tom, men da dette tidspunkt ikke forekommer mellem to (eksterne) transaktioner på maskinen, bryder vi ikke invarianten. |
Exercise 9.3. Klasseinvariant i DoubleLinkable | Vi har i en tidligere lektion studeret dobbeltkædede lister.
Se her for den relevante slide. Formuler en passende klasseinvariant for en dobbeltkædet liste, som udtrykker at dobbeltkædningen er i orden. Skitser et prædikat (boolsk funktion) på klassen DoubleLinkable, som udtrykker at et enkelt led i kæden er 'i orden'. |
Eksempel på klasseinvariant: CircularList Slide Note Contents Index References Speak | Lad os her se på et konkret eksempel på benyttelse af klasseinvarianter. Det er naturligt at vende tilbage til det eksempel, som vi studerede lidt tidligere omkring pre- og postbetingelser |
Program: Klassen CircularList med en invariant.
Bemærk at flere af pre- og postbetingelserne er blevet simplere
end vist ovenfor. Dette hænger sammen med at klasseinvarianten
skærper alle pre- og postbetingelser
|
|
|
Hvornår skal en invariant være opfyldt? Slide Note Contents Index References Speak | Vi beskrev løst ovenfor, at en invariant skal være opfyldt på ethvert stabilt tidspunkt i et objekts levetid. Vi vil nu gør dette udsagn væsentligt mere præcist |
Vi ser, at klasseinvarianten er et 'sigtepunkt' for konstruktorerne i en klasse. Objekter skal starte livet et en sund tilstand. De sunde tilstande af et objekt beskrives af klassens klasseinvariant |
En privat operation spiller ofte rollen som en hjælpeoperation. Udførelse af en sådan hjælpeoperation bringer ikke nødvendigvis objektet i en sund tilstand. Der skal muligvis flere påvirkninger til inden objektet havner i en tilstand, hvor klasseinvarianten gælder |
Subkontrakter og nedarvning |
Forholdet mellem nedarvning og kontrakter Slide Note Contents Index References Speak | Når en klasse B arver fra klassen A søger vi retningslinier for, hvorledes assertions i B og A forholder sig til hinanden. |
Figure. Problemstillingen omkring forholdet mellem assertions når en klasse B arver fra klassen A | ![]() |
|
Subkontrakter Slide Note Contents Index References Speak | Vi har indset at mængden af assertions (pre- og postbetingelser samt invarianten) på en klasse C kan opfattes som en kontrakt. Vi vil nu studere hvordan denne kontrakt udvikler sig når vi danner subklasser af klassen C |
|
En operation i en specialiseret klasse må ikke forhindre et kald af sig selv, som er garanteret af superklassens tilsvarende operation. Derfor må prebetingelsen ikke strammes En operation i en specialiseret klasse skal gøre arbejdet mindst lige så godt som den tilsvarende operation i superklasenklasse. Derfor må postbetingelsen ikke svækkes |
Reglerne ovenfor formaliserer, at operationerne der redefineres ned gennem et specialiseringshierarki, skal være tæt beslægtede - også semantisk. Hidtil har vi kun set lagt bånd på navne og parametre af redefinerede operationer (covarians). Her kommer vi altså med nogle semantiske forventninger (krav) i forbindelse med redefintion. Dette er meget tilfredsstillende |
Eksempel på klasseinvarianter: Trekantshierarkiet Slide Note Contents Index References Speak | Vi har tidligere studeret hierarkiet af trekanter. Først nu hvor vi er bevæbnet med klasseinvarianter kan vi på en tilfredsstillende måde karakterisere specialiseringen i dette klassehierarki |
Figure. Invarianter i klassehierarkiet af trekanter:
3 sider og 3 vinkler Vinkelsum er 180 grader Ligebenet trekant: Retvinklet trekant: Ligesidet trekant: Retvinklet ligebenet trekant: | ![]() |
|
Assertions i abstrakte klasser Slide Note Contents Index References Speak | Assertions giver i høj grad mening i abstrakte klasser. Via reglerne for redefinerede operationers assertions er det meget nyttigt at angive assertions allerede på et meget generelt niveau i et klassehierarki |
|
Program: Den abstrakte klasse Stack forsynet med assertions.
Vi har taget udgangspunkt i den abstrakte klasse Stack fra forrige lektion.
Pre- og postbetingelser i "..." er uformelle, kommentarlignende udsagn, som
ikke vil kunne eftercheckes når programmet kører.
|
|
|
Abstrakte klasser er i overvejende grad udtryk for design, som kontrast til implementation. Når vi forsyner abstrakte klasser med kontrakter vil vi tale om 'design by contract' |
Kontrakter i praktisk programudvikling |
Løbende check af kontrakter under programudførelse Slide Note Contents Index References Speak | En væsentlig praktisk gevinst ved anvendelse af kontrakter høstes ved at checke kontrakternes gyldighed i takt med at programmet udføres |
|
Assertions er udtryk for redundans, og assertions kan som sådan anvendes til check. I lidt mere jævnt sprog kan vi sige, at assertions er et supplerende element i vore programmer i forhold til 'det der sker i operationerne'; Assertions beskriver programmets handlinger på højere og logisk niveau. Vi kan sammenligne det der faktisk sker når vi kører med programmet med det, der er udtrykt i vore assertions. |
Når eller hvis der opdages et brud på en kontrakt, f.eks. en invariant som ikke holder når man kører programmet, har vi identificeret et stort problem. Det giver ikke mening at fortsætte programudførelsen. Der kan være tale om en programmeringsfejl i forhold til specifikationen af programmet. I en debugging situation er det eneste fornuftige at få rettet denne fejl. I en driftssituation har vi identificeret en undtagelsessituation, som typisk håndteres ved en kontrolleret nedlukning af programmet. I nogle meget kritiske programmer vil vi ønske at genoptage programudførelsen med en ændret 'beregningsstrategi' (recovery). |
Udbyttet af kontrakter i programudviklings processen Slide Note Contents Index References Speak | Vi vil her opsummere udbyttet af kontrakter og assertions i hele programudviklingsprocessen |
Når vi taler om dokumentation i forbindelse med kontrakter tænker vi på, at kontraktforhold vil være meget nyttige at dokumentere via javadoc genereret dokumentation |
|
Assertions i objekt-orienterede programmeringssprog Slide Note Contents Index References Speak | Eiffel er et objekt-orienterede programmeringssprog som understøtter assertions. Den primære inspirationskilde til materialet i dette afsnit er således Bertrand Meyer, som har lavet Eiffel |
|
|
|
|
Andre anvendelser af assertions |
Løkkeinvarianter Slide Note Contents Index References Speak | Vi vender os her kort tilsidst mod andre anvendelser af assertions |
|
Program: Et eksempel på en iterativ funktion med en løkke invariant.
|
|
Collected references Contents Index |
|
Chapter 9: Designkontrakter og ansvarsfordeling
Course home Author home About producing this web Previous lecture (top) Next lecture (top) Previous lecture (bund) Next lecture (bund)
Generated: March 31, 2008, 12:09:01