Abstract JML är ett beteendegränssnitt specifikation språk anpassat till Java. Den är utformad för att skrivas och läsas av arbetande programvaruingenjörer, och bör endast kräva blygsam matematisk utbildning. Det använder Eiffel-stil syntax i kombination med modellbaserade semantik, som i VDM och Larch. JML stöder kvantifiers, specification-only variabler, och andra förbättringar som gör det mer uttrycksfullt för specifikation än Eiffel och lättare att använda än VDM och Larch. JML [Leavens-Baker-Ruby01], som står för "Java Modelling Language", är ett beteendegränssnitt specifikation språk (BISL) [Wing87] utformad för att ange Java [ArnoldGosling98] [Gosling-Joy-Steele96] moduler. Javamoduler är klasser och gränssnitt. En beteendegränssnitt specifikation beskriver både detaljerna i en moduls gränssnitt med kunder, och dess beteende ur kundens synvinkel. Sådana specifikationer är inte bra för specifikationen av hela program, men är bra för att spela in detaljerade designbeslut eller dokumentation av avsett beteende, för en programvara modul. Syftet med detta kapitel är att förklara JML och begreppen bakom dess inställning till specifikation. Eftersom JML används i detaljerad design av Javamoduler använder vi den detaljerade designen av ett gränssnitt och klass för prioriterade köer som exempel. Resten av detta avsnitt förklarar gränssnitt och beteendegränssnitt specifikation. I nästa avsnitt beskriver vi hur man specificerar nya typer som konceptuella modeller för detaljerad design. Därefter avslutar vi exemplet genom att ge detaljerna i en klassspecifikation. Vi avslutar efter att ha nämnt några andra inslag i JML. En moduls gränssnitt består av dess namn och namn och typer av dess fält och metoder. Java-gränssnitt deklarerar sådan gränssnittsinformation, men klassdeklarationer gör det också. Liksom i Larch familjen av BIFLs [LeavensLarchFAQ] [Wing87] [Wing90a] deklareras gränssnittsinformation i JML med hjälp av deklarationen syntax av programmeringsspråket som BIFL är skräddarsydd för; därmed använder JML Java deklaration syntax. Ett exempel ges i filen PriorityQueueUserInterface.java-refined, som visas nedan. Detta exempel ger informationen ett Java program behöver för att använda en PriorityQueueUser objekt, inklusive paketet som det tillhör, tillgängligheten av metoderna (offentlig), namnen på metoderna, typerna av deras argument och resultat, och vilka undantag de kan kasta. paket edu.iastate.cs.jml.docs.kluwer; offentligt gränssnitt PriorityQueueUser { /*@ ren @*/ boolean innehåller(Objekt argID); /*@ ren @*/ Objekt nästa () kastar PQException; tomrum bort (Objekt argID); } Även ingår i ovanstående fil är två kommentarer. Dessa kommentarer bifogas i kommentarerna till formuläret /*@. . @*/; man kan också skriva kommentarer med hjälp av formuläret //@, och sådana kommentarer sträcker sig till slutet av motsvarande rad. Kapitel 1: Beteendegränssnitt Specifikation 2 Java ignorerar båda typer av kommentarer, men de är betydande för JML. Anteckningarna på metoderna nästa och innehåller kräver att båda metoderna är rena, vilket innebär att de inte kan ha några yttre synliga biverkningar. I JML ges också information om beteendespecifikation i form av kommentarer. Precis som i Larch-metoden är sådana specifikationer modellbaserade. Det vill säga, de anges i termer av en matematisk modell [Hoare72a] [Wing83] [Wing87] av tillstånd (eller värden) av objekt. Till skillnad från de flesta Larch-stil specifikationsspråk, beskrivs dock i JML sådana modeller genom att deklarera modellfält, som endast används för specifikation. I JML kan en deklaration innehålla en modifier-modell, vilket innebär att deklarationen inte behöver visas i ett korrekt genomförande; alla icke-modelldeklarationer måste anges i ett korrekt genomförande. Som ett exempel anger filen "PriorityQueueUserModel.java-raffinaderied" nedan en modell för prioriterade köer. Denna specifikation är en förfining av den som anges i filen (visas ovan) "PriorityQueueUserInterface.java-raffinaderierad", vilket är anledningen till att raffineringsklausulen finns i specifikationen efter paketdeklarationen. Raffinaderiklausulens innebörd är att den angivna specifikationen kompletterar den i den akt som anges, genom att införa ytterligare begränsningar av den specifikationen. En sådan förfining kan till exempel göras när man börjar fatta detaljerade designbeslut eller när man börjar ange beteendet hos befintliga programvarumoduler. I en förfining är den befintliga specifikationsinformationen ärvs, dvs. metoddeklarationerna i gränssnittet PriorityQueueUser ärvs, och upprepas därför inte under paketet edu.iastate.cs.jml.docs.cluwer; //@ raffinera PriorityQueueUser <-"PriorityQueueUser.java-refined"; //@ modell import edu.iastate.cs.jml.modeller.*; Public interface PriorityQueueUser { /*@ public model example JMLValueSet postes @ initial postes!= null & & & inputs.isEmpty(); @*/ /*@ public invariant postes!= null @ & & & (\forall JMLType e; posts.has(e); @ e exempel på QueueEntry); @ public invariant @ (\forall QueueEntry e1; postes.has(e1); @ (\forall QueueEntry e2; @ postes.has(e2) & & & &!(e1.lika(e2)); @ e2.iD!= e1.iD @ & & & e2.timeStamp!= e1.timeStamp )); @*/ } Efter raffineringsklausulen ovan är en mall för importdeklaration. Detta har effekten som en Java importdeklaration för JML, men användningen av modellen innebär att importen gör kapitel 1: Beteendegränssnitt Specifikation 3 inte behöver visas i ett genomförande, eftersom det bara behövs för specifikationsändamål. Paketet som importeras, edu.iastate.cs.jml.modeller, består av flera rena klasser inklusive uppsättningar, sekvenser, relationer, kartor, och så vidare, som är användbara i beteendespecifikation. Dessa fyller rollen av de inbyggda typer som används för specifikation i VDM och Z, eller de egenskaper som används i Larch. Eftersom de är rena (bieffektfria) klasser, kan de användas i påståenden utan att påverka tillståndet i beräkningen, vilket gör att påståenden har en väldefinierad matematisk betydelse (till skillnad från Eiffels påståenden). Men eftersom de är Java klasser, deras metoder åberopas med hjälp av den vanliga Java syntax. I specifikationen ovan använder vi klassen JMLValueSet som typ av modellfältposter. Det är, för specifikation, vi föreställer oss att varje objekt som implementerar gränssnittet PriorityQueueUser har en offentlig fältposter av typ JMLValueSet. Detta modellfält förefaller (för kunder) inledningsvis ha börjat som tomt, såsom anges i den ursprungliga klausulen som bifogas dess förklaring De två invarianta klausulerna beskriver ytterligare det avsedda tillståndet för poster. Den första anger att det inte är ogiltigt, och att alla dess element har typ QueueEntry. Den \forall notation är ett tillägg till Java syntaxen för uttryck; den ger universell kvantifiering över de deklarerade variablerna. Inom ett sådant uttryck av formen (\forall T x; R(x); P(x)) anger uttrycket R(x) det område över vilket den bundna variabeln, x, kan anta värden; den separeras från termen predikat, P(x), med ett semikolon (;). Till exempel den första invariant innebär att för alla JMLType objekt e sådan att postes.has(e), e har typ QueueEntry. Den andra variant anger att varje sådan QueueEntry objekt har en unik iD och tidStamp. I filen "PriorityQueueUser.java" nedan gör vi ytterligare en förfining, för att ange beteendet hos metoderna för PriorityQueueUser. Denna specifikation, eftersom den förfinar specifikationen i "PriorityQueueUserModel.java-raffinaderierade", ärver de modellfält som anges där, liksom de inledande och invarianta klausulerna. (Särvande av specifikationer förklaras närmare nedan.) paket edu.iastate.cs.jml.docs.kluwer; //@ raffinaderi PriorityQueueUser <-"prioritetQueueUserModel. Specifikationen av innehåller ovan visar den enklaste formen av en beteendespecifikation för en metod: en enda offentlig normal_beteendeklausul följt av en metodhuvud. Denna specifikation säger att metoden returnerar sant just när dess argument är non-null och är samma som något objekt i kön. Den allmänna normal_beteendeklausulen i denna specifikation består av en enda säkerhetsklausul. Detta säkerställer klausulen ger metodens totalkorrigering postvillkor; det vill säga samtal till innehåller måste avsluta (i motsats till looping för alltid eller abort) i ett tillstånd som uppfyller postvillkoret. Det offentliga nyckelordet säger att specifikationen är avsedd för kunder, medan det "normala" i normal_behavior förbjuder kasta undantag. Innebörden av && och == är som i Java; det vill säga, & & & är kortslutning logisk förbindelse, och e.iD == argID betyder att e.iD och argID är samma objekt. Nyckelordet \resultat betecknar metodens returvärde, som i detta fall är en boolean. Operatören >=> betyder "om och endast om"; det motsvarar == för booleaner, men har lägre prioritet. Notationen \existerar används för existentiell kvantifiering. Liksom universella kvantifierare kan existentiella kvantifierare också ha ett avståndsuttryck som separeras från termen uttryck av en semikolon (;). Specifikationen av metoden nästa visar ett sätt att ange metoder med undantag i JML. Detta använder en allmän normal_beteendeklausul för fall där inga undantag kastas, och en allmän undantagsklausul för när undantag kastas. Semantiken är att ett korrekt genomförande måste tillfredsställa båda dessa vara-. I specifikationen av nästa, den offentliga undantag_behavior klausulen anger att en instans av PQException klass (inte visas här) måste kastas när poster är tom. Kravklausulen ger en förutsättning för detta fall, och när det är sant måste metoden avslutas (i detta fall genom att ett undantag görs), eftersom detta falls villkor måste uppfyllas. Nästas allmänna normala beteende måste följas när dess förutsättning är sann; det vill säga när poster inte är tomma. Det normala beteendet postcondition säger att nästa returnerar ett objekt med den lägsta tidsstämpeln i högsta prioritetsnivå. Det skulle naturligtvis vara möjligt att endast specificera det allmänna normala beteendet för nästa. Om detta gjordes, då implementationer kan bara anta förutsättningen för det normala beteendet-att poster inte är tom. Detta skulle vara en lämplig utformning för kunder som kan lita på, och skulle kunna möjliggöra ett effektivare genomförande. Detta är ett ramtillstånd [Borgida-Mylopoulos-Reiter95]; det anger att endast de fält som nämns (och alla som de är beroende av [Leino95] [Leino95a]) kan tilldelas; inga andra fält, inklusive fält i andra objekt, kan tilldelas. Om tilldelningsklausulen utelämnas innebär det att inga fält kan tilldelas. (Tekniskt sett handlar den tilldelad klausulen också om arrayelement. Lokala variabler, inklusive de formella parametrarna för en metod, och även fält av nyskapade objekt kan också fritt tilldelas med en metod [LeavensBaker-Ruby01].) Observera att förutsättningen för att ta bort använder metoden innehåller, vilket är tillåtet eftersom det är rent. Det mest intressanta med specifikationen av bort är att den använder JML reserverade ordet \old. Som i Eiffel är innebörden av \old(E ) som om E utvärderades i förtillståndet och det värdet används i stället för \old(E ) i påståendet. Medan vi har brutit upp specifikationen av PriorityQueueUser i tre delar, gjordes det delvis för att demonstrera förfining och delvis så att varje bit skulle passa på en sida. I vanlig användning skulle denna specifikation skrivas i en fil. 1 Av historiska skäl gör JML också att man kan använda modifierbara och modifiera som synonymer för tilldelning. Kapitel 2: Ange nya rena modelltyper 6 2 Ange nya rena modelltyper JML levereras med en svit av rena typer, implementeras som Java klasser, som kan användas som konceptuella modeller i detaljerad design. Som nämnts ovan finns dessa i paketet edu.iastate.cs.jml.models. Användare kan också skapa sina egna rena typer, genom att ge en klass eller gränssnitt den rena modifier. Eftersom dessa typer ska behandlas som rent oföränderliga värden i specifikationer, måste de passera vissa konservativa kontroller som ser till att det inte finns någon möjlighet till observerbara biverkningar från att använda sådana objekt. Modellklasser bör också vara rena, eftersom användningen av icke rena metoder i ett påstående i JML är ett typfel. Men modifierarnas modell och ren är ortogonala, och därför måste man räkna upp dem båda när man deklarerar en ren modellklass. Ett exempel på en ren modellklass är klassen QueueEntry, som anges i filen 'QueueEntry.jml' nedan. Eftersom det är en modellklass behöver det inte genomföras, utan används endast för specifikationsändamål. Eftersom den är ren, kan ingen av dess metoder tillåta biverkningar. Det är skrivet i en ".jml" fil, och att filnamn suffix berättar JML att det endast består av anteckningar; i själva verket, hela filen ignoreras av Java men är betydande för JML. Klassen QueueEntry har tre offentliga fält iD, priorityLevel och timeStamp. I ovanstående specifikation följer konstruktörens specifikation invarianten. Konstruktören tar tre argument och initierar fälten från dem. Förutsättningen för denna konstruktör är att den bara kan kallas om ArgID-argumentet inte är ogiltigt; om detta inte var sant, då skulle invarianten brytas. Klonen och motsvarar metoder i QueueEntry är relaterade till gränssnittet JMLType, som QueueEntry utökar. I JML när en klass implementerar ett gränssnitt, ärver det specifikationerna för det gränssnittet. Gränssnittet JMLType anger bara dessa två metoder. Specifikationerna för dessa metoder ärvs således av QueueEntry, och de specifikationer som anges här kompletterar de angivna specifikationerna. Specifikationen för metoden klon i JMLType (citerad från [Leavens-Baker-Ruby01]) är följande. /*@ också @ public normal_behavior @ säkerställer \resultat exempel på JMLType @ & & (JMLType)\ result). lika(detta); @*/ public /*@ ren @*/ Objekt klon(); Ovanstående specifikation säger att för JMLType objekt, klon inte kan kasta undantag, och dess resultat måste vara ett JMLType objekt, med samma värde som detta. (I Java namnger detta mottagaren av ett metodanrop). Arv av metodspecifikationer innebär att en implementering av klon måste uppfylla både den nedärvda specifikationen från JMLType och den givna specifikationen i QueueEntry. Innebörden av metoden arv i detta exempel visas i nedan. (Den modifierare ren från superklassen kan läggas i här, även om det är överflödigt för en metod av en ren klass.) ).equals(this); @*/ public /*@ rene @*/ Object clone(); Tillfredsställande båda fallen är möjligt eftersom QueueEntry är en subtyp av JMLType, och eftersom JML tolkar betydelsen av E1.equals(E2 ) med användning av körtiden klass E1. Bestämmelsen om garantier_redundant tillåter specifier att ange konsekvenserna av den specifikation som följer av dess innebörd [Tan94] [Tan95]. I detta fall följer predikatet som ges av den nedärvda specifikationen och den givna. Detta exempel visar en god användning av sådan redundans: att belysa viktiga nedärvda egenskaper för läsaren av (original, oexpanderad) specifikation. Fallanalys används igen i specifikationen av QueueEntrys metod. Som tidigare, beteendet måste uppfylla varje fall av specifikationen. Det vill säga när argumentet o är ett fall av typ QueueEntry, måste det första fallets postvillkor vara uppfyllt, annars måste resultatet vara falskt. Kapitel 3: Klassspecifikationer 9 Filen 'PriorityQueue.java-raffinaderied' nedan anger PriorityQueue, en klass som implementerar gränssnittet PriorityQueueUser. Eftersom denna klass implementerar ett gränssnitt, ärver det specifikationer, och därmed genomförande skyldigheter, från det gränssnittet. I den angivna specifikationen läggs därför fler skyldigheter till dem som anges i tidigare specifikationer. paket offentliga /*@ ren @*/ boolean innehåller(Objekt argID); offentliga /*@ ren @*/ Objekt nästa () kastar PQUpception; offentligt tomrum bort (Objekt argID); } Kapitel 3: Klass Specifikationer 10 Den rena modellen metod störstaTimeStamp anges endast för att göra uttalande av addEntry mer begriplig. Eftersom det är en modellmetod behöver den inte genomföras. Utan denna specifikation skulle man behöva använda den kvantifier som finns i det andra fallet av störstaTimeStamp inom specifikationen av addEntry. Den intressanta metoden i PriorityQueue är addEntry. En viktig fråga är hur tidsstämpelrna hanteras; detta klargörs förhoppningsvis med hjälp av största TimeStamp() i efterhand i det första specifikationsfallet. En mer subtil fråga gäller ändlighet. Eftersom förutsättningen för addEntrys första fall inte begränsar antalet poster som kan läggas till, verkar specifikationen innebära att genomförandet måste ge en bokstavligen obegränsad prioritetskö, vilket säkert är omöjligt. Vi undviker detta problem, genom att följa Poetzsch-Heffter att släppa implementeringar från sina skyldigheter att uppfylla postvillkor när Java tar slut lagring. Det vill säga, en metod implementering korrekt implementerar en specifikation fall om, när det kallas i ett tillstånd som uppfyller dess villkor, antingen • metoden avslutas i ett tillstånd som uppfyller sin postvillkor, efter att ha tilldelat endast de platser som tillåts genom dess tilldelningsklausul, eller • Java signalerar ett fel, genom att kasta ett undantag som ärver från java.lang.Error. beror på storlek <- theElems; representerar storlek <- size == theElems. length(); beroendeklausulen säger att modellens fältstorlek kan ändra dess värde när Elems ändras. Representationsklausulen säger hur de är relaterade, vilket ger ytterligare fakta som kan användas i resonemanget om specifikationen. Detta tjänar samma syfte som en abstraktionsfunktion i olika bevismetoder för abstrakta datatyper (såsom [Hoare72a] ). Den representerar klausulen ovan berättar hur man extraherar värdet av storlek från värdet av Elems. JML har också historiska begränsningar. En historisk begränsning används för att säga hur värden kan förändras mellan tidigare och senare tillstånd, såsom en metods pre-state och dess post-state. Detta hindrar subtyper från att göra vissa tillståndsändringar, även om de tillämpar fler metoder än vad som anges i en viss klass. Till exempel, följande historik begränsning MAX_SIZE == \old(MAX_SIZE); säger att värdet av MAX_SIZE inte kan ändras. JML har möjlighet att ange vilka metoder en metod kan kalla, med hjälp av en inkallbar klausul. Detta gör att man kan veta vilka metoder som behöver ses över när man överskuggar en metod, och att tillämpa idéerna om "återanvända kontrakt". [Wing90a] tillvägagångssätt för specifikation. Från Eiffel har vi använt oss av idén att påståenden skrivs med hjälp av Javas uttrycksyntax så mycket som möjligt och därmed undvika stora mängder av speciella logiska anmärkningar. JML anpassar också \old notation från Eiffel, istället för Larch stil annotation av namn med statliga funktioner. För närvarande JML kommer inte med verktyg för att köra förutsättningar för att hjälpa felsökningsprogram, som i Eiffel. Vi planerar att så småningom utöka JML: s verktyg för att stödja testning av postvillkor vid körtid också. Eiffel specifikationer, som skrivits av Meyer, är dock vanligtvis inte lika kompletta som modellbaserade specifikationer skrivna, till exempel i Larch BISLs eller VDM [Jones90]. Till exempel anger Meyer delvis en borttagning (dvs. pop) metod för stackar som kräver att stacken inte är tom, och se till att stackvärdet i post-tillståndet har en mindre objekt än i för-tillståndet (se s. 339 av [Meyer97]). Den enda karakteriseringen av vilken punkt som tas bort ges dock informellt som en kommentar. Ingenting sägs formellt som säkerställer att de andra delarna av stacken är oförändrade. För att möjliggöra mer fullständiga specifikationer behöver vi idéer från modellbaserade specifikationsspråk. JML:s semantiska skillnader från Eiffel (och dess kusiner Sather och Sather-K) gör det lättare att skriva mer kompletta specifikationer, efter idéerna om modellbaserade specifikationsspråk. Den viktigaste av dessa är JML:s användning av endast specifikationer. Dessa modelldeklarationer tillåter mer abstrakta och exakta specifikationer av beteende än vad som vanligtvis görs i Eiffel. Till exempel, eftersom man har en modell av de abstrakta värdena av stack objekt, kan man exakt ange både vilket element tas bort av pop och att de andra elementen på stacken är oförändrade. Användningen av modellfält i JML gör det möjligt att skriva specifikationer som liknar andan i VDM eller Larch BISL. En mer liten skillnad från Eiffel är att i JML kan man ange ramvillkor, med hjälp av den tilldelade klausulen. Vår tolkning av klausulen om tilldelning är mycket strikt, eftersom även välvilliga biverkningar inte tillåts om klausulen om tilldelning utelämnas [Leino95] [Leino95a]. En annan skillnad från Eiffel är att vi har utökat syntaxen för Javauttryck med kvantifieringar och andra konstruktioner som behövs för logisk uttrycksfullhet, men som inte alltid är körbara. Slutligen förbjuder vi bieffekter och andra problematiska inslag i koden i påståenden. Å andra sidan har vår erfarenhet av Larch/C++ [Leavens96b] [Leavens99] lärt oss att anpassa det modellbaserade tillvägagångssättet på två sätt, i syfte att göra det mer praktiskt och lätt att lära. Den första anpassningen är återigen användningen av endast specificeringsmodellvariabler (eller spökvariabler). Ett objekt kommer således (i allmänhet) att ha flera sådana modellfält, som endast används för att beskriva, abstrakt, objektens värden. Detta förenklar användningen av JML, jämfört med de flesta Larch BILS, eftersom specifiers (och deras läsare) nästan aldrig behöver veta om algebraisk stil specifikation. Det gör också utforma en modell för en Java klass eller gränssnitt liknande, i vissa avseenden, att utforma en genomförande datastruktur i Java. Vi hoppas att denna likhet kommer att göra specifikationsspråket lättare att förstå. Kapitel 5: Relaterat arbete Den andra anpassningen döljer detaljerna i matematisk modellering bakom en fasad av Java klasser. I Larch förhållningssätt till beteendegränssnitt specifikation [Wing87], den matematiska notation som används i påståenden presenteras direkt för specifier. Detta gör att samma matematiska notation kan användas på många olika specifikationsspråk. Det innebär dock också att användaren av en sådan specifikation språk måste lära sig en notation för påståenden som skiljer sig från deras programmeringsspråk notation för uttryck. (En förstudie gjord av Finney [Finney96] visar att ett stort antal speciella, grafiska matematiska anmärkningar, såsom de som finns i Z [Hayes93] [Spivey92] kan göra sådana specifikationer svåra att läsa, även för programmerare som är utbildade i notationen.) I JML använder vi en kompromiss strategi, döljer dessa detaljer bakom Java klasser. Dessa klasser är rena, i den meningen att de återspeglar den underliggande matematiken, och därför inte använder biverkningar (åtminstone inte på något observerbart sätt). Förutom att isolera användaren av JML från detaljerna i den matematiska notationen, isolerar denna kompromiss tillvägagångssätt också utformningen av JML från detaljerna i den matematiska logik som används för JML: s semantik och för teorem bevis. Vi anser att användningen av något förlängd Java notation för påståenden är lämpligt, med tanke på att JML används i detaljerad design, och därmed kommer mestadels att läsas och skrivas av personer som är bekanta med Java.
Historikinvarianter stöds också av Java Modeling Language (JML) REF, som använder synlig-state semantik.
JML: A notation for detailed design
{'venue': 'Behavioral Specifications of Businesses and Systems', 'journal': None, 'mag_field_of_study': ['Computer Science']}