Programavimas

„iContract“: „Java“ projektavimas pagal sutartį

Ar nebūtų malonu, jei visos jūsų naudojamos „Java“ klasės, įskaitant ir jūsų pačių, išpildytų jų pažadus? Tiesą sakant, ar nebūtų malonu, jei iš tikrųjų tiksliai žinotumėte, ką žada tam tikra klasė? Jei sutinkate, skaitykite toliau - „Design by Contract“ ir „iContract“ ateina į pagalbą.

Pastaba: Šio straipsnio pavyzdžių kodo šaltinį galima atsisiųsti iš išteklių.

Projektavimas pagal sutartį

„DBC“ programinės įrangos kūrimo technika užtikrina aukštos kokybės programinę įrangą, garantuodama, kad kiekvienas sistemos komponentas pateisina jos lūkesčius. Kaip kūrėjas, naudojantis DBC, nurodote komponentą sutarčių kaip komponento sąsajos dalį. Sutartyje nurodoma, ko tas komponentas tikisi iš klientų ir ko klientai gali iš jo tikėtis.

Bertrandas Meyeris sukūrė DBC kaip savo Eifelio programavimo kalbos dalį. Nepaisant kilmės, DBC yra vertinga dizaino technika visoms programavimo kalboms, įskaitant „Java“.

DBC yra svarbiausia tvirtinimas - loginė išraiška apie programinės įrangos sistemos būseną. Vykdymo metu mes vertiname teiginius konkrečiuose kontrolės punktuose vykdant sistemą. Galiojančioje programinės įrangos sistemoje visi teiginiai yra teisingi. Kitaip tariant, jei kuris nors teiginys vertinamas kaip klaidingas, mes laikome programinės įrangos sistemą negaliojančia arba sugadinta.

Pagrindinė DBC sąvoka šiek tiek susijusi su #assert makrokomanda C ir C ++ programavimo kalbomis. Tačiau DBC tvirtina dar milijardą.

DBC nustatome tris skirtingas išraiškų rūšis:

  • Išankstinės sąlygos
  • Postconditions
  • Variantai

Panagrinėkime kiekvieną iš jų išsamiau.

Išankstinės sąlygos

Išankstinės sąlygos nurodo sąlygas, kurios turi atitikti metodo vykdymą. Jie yra vertinami prieš pradedant taikyti metodą. Išankstinės sąlygos apima sistemos būseną ir į metodą perduodamus argumentus.

Išankstinės sąlygos nurodo įsipareigojimus, kuriuos turi įvykdyti programinės įrangos komponento klientas, kad galėtų pasinaudoti tam tikru komponento metodu. Jei išankstinė sąlyga nepavyksta, programinės įrangos komponento kliente yra klaida.

Postconditions

Priešingai, vėlesnėse sąlygose nurodomos sąlygos, kurios turi galioti baigus metodą. Vadinasi, vėlesnės sąlygos vykdomos baigus metodą. Papildomos sąlygos apima seną sistemos būseną, naują sistemos būseną, metodo argumentus ir metodo grąžinimo vertę.

Postconditions nurodo garantijas, kurias programinės įrangos komponentas teikia savo klientams. Jei pažeista vėlesnė sąlyga, programinės įrangos komponentas turi klaidą.

Variantai

Nekintamasis nurodo sąlygą, kuri turi būti bet kada, kai klientas gali pasinaudoti objekto metodu. Variantai apibrėžiami kaip klasės apibrėžimo dalis. Praktiškai invariantai yra vertinami bet kuriuo metu prieš ir po metodo, kurį vykdo bet kurios klasės egzempliorius. Invarianto pažeidimas gali reikšti klaidą kliente arba programinės įrangos komponente.

Teiginiai, paveldėjimas ir sąsajos

Visi teiginiai, nurodyti klasei ir jos metodams, taip pat taikomi visiems poklasiams. Taip pat galite nurodyti sąsajų tvirtinimus. Visi sąsajos teiginiai turi atitikti visas klases, naudojančias sąsają.

„iContract“ - DBC su „Java“

Iki šiol mes kalbėjome apie DBC apskritai. Jūs tikriausiai jau turite idėją, apie ką kalbu, bet jei dar nesate nauji DBC, viskas vis tiek gali būti šiek tiek miglota.

Šiame skyriuje viskas taps konkretesnė. Reto Kamer sukurtas „iContract“ prideda „Java“ konstrukcijas, leidžiančias nurodyti DBC tvirtinimus, apie kuriuos kalbėjome anksčiau.

„iContract“ pagrindai

„iContract“ yra „Java“ pirminis procesorius. Norėdami jį naudoti, pirmiausia apdorokite „Java“ kodą naudodami „iContract“ ir sukurkite dekoruotų „Java“ failų rinkinį. Tada jūs sukompiliuojate dekoruotą Java kodą, kaip įprasta, naudodami Java kompiliatorių.

Visos „Java“ kodo „iContract“ direktyvos yra klasių ir metodų komentaruose, kaip ir „Javadoc“ direktyvos. Tokiu būdu „iContract“ užtikrina visišką atgalinį suderinamumą su esamu „Java“ kodu ir visada galite tiesiogiai sukompiliuoti savo „Java“ kodą be „iContract“ tvirtinimų.

Įprastu programos gyvavimo ciklu, jūs perkeltumėte savo sistemą iš kūrimo aplinkos į bandymo aplinką, tada į gamybos aplinką. Kūrimo aplinkoje savo kodą suprogramuosite su „iContract“ teiginiais ir paleisite. Tokiu būdu galite anksti sugauti naujai įvestas klaidas. Bandymo aplinkoje vis tiek galbūt norėsite, kad didžioji dalis teiginių būtų įgalinti, tačiau turėtumėte juos pašalinti iš kritinių efektyvumo klasių. Kartais prasminga kai kuriuos teiginius laikyti įgalintais gamybos aplinkoje, tačiau tik klasėse, kurios tikrai nėra jokios kritinės jūsų sistemos našumui. „iContract“ leidžia aiškiai pasirinkti klases, kurias norite patvirtinti teiginiais.

Išankstinės sąlygos

„IContract“ prielaidas pateikiate metodo antraštėje naudodami @pre direktyvą. Štai pavyzdys:

/ ** * @pre f> = 0.0 * / public float sqrt (float f) {...} 

Išankstinė sąlyga užtikrina, kad argumentas f funkcijos sqrt () yra didesnis arba lygus nuliui. Klientai, kurie naudojasi tuo metodu, yra atsakingi už šios prielaidos laikymąsi. Jei jie to nepadaro, mes, kaip įgyvendintojai sqrt () tiesiog nėra atsakingi už pasekmes.

Išraiška po @pre yra „Java“ loginė išraiška.

Postconditions

Postconditions taip pat pridedami prie metodo, kuriam jie priklauso, antraštės komentaro. Programoje „iContract“ @post direktyva apibrėžia vėlesnes sąlygas:

/ ** * @pre f> = 0.0 * @post Math.abs ((return * return) - f) <0.001 * / public float sqrt (float f) {...} 

Savo pavyzdyje mes pridėjome papildomą sąlygą, kuri užtikrina, kad sqrt () metodas apskaičiuoja kvadratinę šaknį f neviršijant tam tikros paklaidos ribos (+/- 0,001).

„iContract“ pateikia keletą specifinių postcondition žymėjimų. Pirmiausia, grįžti reiškia metodo grąžinimo vertę. Vykdymo metu tai bus pakeista metodo grąžinimo verte.

Vėlesnėse sąlygose dažnai reikia atskirti argumento vertę prieš tai metodo vykdymas ir vėliau, palaikomas iContract su @pre operatorius. Jei pridedate @pre išraiškai vėlesnėje sąlygoje, ji bus įvertinta remiantis sistemos būsena prieš atliekant metodą:

/ ** * Pridėti elementą prie kolekcijos. * * @post c.size () = [email protected] () + 1 * @post c.contains (o) * / public void append (c rinkinys, objektas o) {...} 

Pirmiau pateiktame kode pirmoji postcondition nurodo, kad kolekcijos dydis turi padidėti 1, kai pridedame elementą. Išsireiškimas c @ pre nurodo kolekciją c prieš vykdant pridėti metodas.

Variantai

Naudodami „iContract“ galite nurodyti invariantus klasės apibrėžimo antraštės komentare:

/ ** * „PositiveInteger“ yra sveikasis skaičius, kuris garantuotai bus teigiamas. * * @inv intValue ()> 0 * / klasė „PositiveInteger“ pratęsia sveikąjį skaičių {...} 

Šiame pavyzdyje nekintamasis garantuoja, kad Teigiamas skaičiusreikšmė visada yra didesnė arba lygi nuliui. Šis teiginys patikrinamas prieš ir po bet kurio tos klasės metodo vykdymo.

Objekto suvaržymo kalba (OCL)

Nors „iContract“ tvirtinimo išraiškos yra galiojančios „Java“ išraiškos, jos yra modeliuojamos po objekto apribojimų kalbos (OCL) pogrupiu. OCL yra vienas iš standartų, kuriuos palaiko ir koordinuoja „Object Management Group“ arba OMG. (OMG rūpinasi CORBA ir su tuo susijusiais dalykais, jei praleidžiate ryšį.) OCL buvo skirtas nurodyti apribojimus objekto modeliavimo įrankiuose, kurie palaiko Unified Modeling Language (UML), dar vieną OMG saugomą standartą.

Kadangi „iContract“ išraiškų kalba yra modeliuojama pagal OCL, ji teikia keletą pažangių loginių operatorių, išskyrus pačios „Java“ logikos operatorius.

Kiekybiniai rodikliai: visi ir egzistuoja

„iContract“ palaiko visiems ir egzistuoja kiekybininkai. visiems kiekybinis koeficientas nurodo, kad sąlyga turėtų atitikti kiekvieną kolekcijos elementą:

/ * * @variantasvisi IEdarbuotojai e „getEmployees“ () | * getRooms (). yra (e.getOffice ()) * / 

Pirmiau nurodytas nekintamas variantas nurodo, kad kiekvienas darbuotojas grįžo „getEmployees“ () turi biurą kambarių, kuriuos grąžino, kolekcijoje „getRooms“ (). Išskyrus visiems raktinio žodžio sintaksė yra tokia pati kaip egzistuoja išraiška.

Čia yra pavyzdys naudojant egzistuoja:

/ ** * @post egzistuoja „IRoom r“ „getRooms“ () | r.isAvailable () * / 

Šioje sąlygoje nurodoma, kad atlikus susietą metodą, kolekcija grąžinama „getRooms“ () bus bent vienas laisvas kambarys. egzistuoja tęsia rinkinio elemento „Java“ tipą - IRoom pavyzdyje. r yra kintamasis, nurodantis bet kurį kolekcijos elementą. į po raktinio žodžio yra išraiška, kuri pateikia kolekciją (Surašymas, Masyvasarba Kolekcija). Po šios išraiškos eina vertikali juosta, po kurios seka sąlyga, susijusi su elemento kintamuoju, r pavyzdyje. Įdarbinti egzistuoja kvantorius, kai bent vieno kolekcijos elemento sąlyga turi atitikti.

Tiek visiems ir egzistuoja galima pritaikyti įvairioms „Java“ kolekcijoms. Jie palaiko Surašymass, Masyvassmėlis Kolekcijas.

Poveikis: reiškia

„iContract“ teikia reiškia operatorius nurodyti formos suvaržymus: „Jei A turi, tada turi atitikti ir B“. Mes sakome: „A reiškia B“. Pavyzdys:

/ ** * @invariant getRooms (). isEmpty () reiškia getEmployees (). isEmpty () // nėra kambarių, nėra darbuotojų * / 

Tas nekintamasis išreiškia, kad kai „getRooms“ () kolekcija tuščia, „getEmployees“ () kolekcija taip pat turi būti tuščia. Atkreipkite dėmesį, kad jame nenurodyta, kada „getEmployees“ () Yra tuščias, „getRooms“ () taip pat turi būti tuščias.

Taip pat galite sujungti ką tik pristatytus loginius operatorius ir sudaryti sudėtingus teiginius. Pavyzdys:

/ ** * @invariantas visas IEmployee e1 „getEmployees“ () | * visi IEmployee e2 „getEmployees“ () | * (e1! = e2) reiškia e1.getOffice ()! = e2.getOffice () // vienas biuras vienam darbuotojui * / 

Apribojimai, paveldėjimas ir sąsajos

„iContract“ skleidžia apribojimus paveldėjimo ir sąsajų įgyvendinimo santykiuose tarp klasių ir sąsajų.

Tarkime, klasė B pratęsia klasę A. Klasė A apibrėžia invariantų, išankstinių sąlygų ir papildomų sąlygų rinkinį. Tokiu atveju klasės invariantai ir prielaidos A kreiptis į klasę B taip pat ir metodai klasėje B turi tenkinti tas pačias vėlesnes sąlygas, kaip ir klasė A tenkina. Į klasę galite įtraukti daugiau ribojančių teiginių B.

Minėtas mechanizmas taip pat veikia sąsajose ir realizacijose. Tarkim A ir B yra sąsajos ir klasė C įgyvendina abu. Tuo atveju, C yra abiejų sąsajų invariantai, išankstinės ir vėlesnės sąlygos, A ir B, taip pat tiesiogiai klasėje apibrėžtus C.

Saugokitės šalutinių poveikių!

„iContract“ pagerins jūsų programinės įrangos kokybę leis iš anksto sugauti daugybę galimų klaidų. Bet jūs taip pat galite šaudyti sau į koją (tai yra, pristatyti naujų klaidų) naudodami „iContract“. Taip gali atsitikti, kai „iContract“ tvirtinimuose naudojate funkcijas, kurios sukelia šalutinį poveikį, pakeičiantį jūsų sistemos būseną. Tai veda prie nenuspėjamo elgesio, nes, kai jūs sukompiliuosite kodą be „iContract“ instrumentų, sistema elgsis kitaip.

„Stack“ pavyzdys

Pažvelkime į išsamų pavyzdį. Aš apibrėžiau Sukrauti sąsaja, apibrėžianti mano mėgstamos duomenų struktūros pažįstamas operacijas:

/ ** * @inv! isEmpty () reiškia top ()! = null // neleidžiami jokie nuliniai objektai * / viešosios sąsajos kaminas {/ ** * @pre o! = null * @post! isEmpty () * @post viršuje () == o * / void push (Objekto o); / ** * @pre! isEmpty () * @post @return == top () @ pre * / Object pop (); / ** * @pre! isEmpty () * / Object top (); loginis yra tuščias (); } 

Mes teikiame paprastą sąsajos įgyvendinimą:

importuoti java.util. *; / ** * @inv isEmpty () reiškia elementus.size () == 0 * / public class StackImpl įgyvendina Stack {private final LinkedList elements = new LinkedList (); public void push (Object o) {elements.add (o); } public Object pop () {final Object popped = top (); elementai.removeLast (); grįžimas iššoko; } public Object top () {return elements.getLast (); } public Boolean isEmpty () {return elements.size () == 0; }} 

Kaip matote, Sukrauti diegime nėra jokių „iContract“ tvirtinimų. Atvirkščiai, visi teiginiai daromi sąsajoje, o tai reiškia, kad komponento „Stack“ sutartis sąsajoje apibrėžta visa. Tiesiog žiūrėdamas į Sukrauti sąsaja ir jos teiginiai, Sukrautielgesys yra visiškai apibrėžtas.

Dabar pridedame nedidelę bandymų programą, kad pamatytume „iContract“ veikimą:

public class StackTest {public static void main (String [] args) {final Stack s = new StackImpl (); s.push („vienas“); s.pop (); s.push („du“); s.push („trys“); s.pop (); s.pop (); s.pop (); // sukelia teiginio nesėkmę}} 

Tada paleisime „iContract“, kad sukurtume kamino pavyzdį:

java -cp% CLASSPATH%; src; _contract_db; instr com.reliablesystems.iContract.Tool -Z -a -v -minv, pre, post> -b "javac -classpath% CLASSPATH%; src" -c "javac -classpath % CLASSPATH%; instr "> -n" javac -classpath% CLASSPATH%; _ contract_db; instr "-oinstr / @ p / @ f. @ E -k_contract_db / @ p src / *. Java 

Aukščiau pateiktas teiginys reikalauja šiek tiek paaiškinimo.