A Cobra programozási nyelv

Helyesség



Assert

A Cobra assert utasításai lehetőséget biztosítanak a kód belső állapotának futásidejű ellenőrzésére. Az assert utasítás után írhatunk egy logikai feltételt, amely, ha nem teljesül, a programban kivétel dobódik, tartalmazva a megszegett feltétel forráskódját, a file nevét, az utasítás sorszámát a programban és az assert opcionálisan megadható, második paraméterében leírt információt.

... assert i>0, i ... assert name ... assert x<y, 'x=[x], y=[y]'

Hasonló funkcionalitás biztosítható könyvtári függvényhívásokkal, például a Util.assert(condition, information) hívással, de ekkor az information argumentum minden alkalommal kiértékelődik, abban az esetben is, amikor az assert feltétele igaz értéket ad (és természetesen az esetek többségében ez a helyzet), így az assert utasítással kiváltva a library hívásokat, jelentős mértékben növelhetjük a hatékonyságot.
Nyilvánvaló módon az asserteket kiválthatjuk, ha egy if szerkezetet kombinálunk a throw exception, kivételt dobó utasítással, de az assertek használata sokkal kényelmesebb. Mint a Cobra többi átgondoltan megalkotott, minőségi, biztonságos program fejlesztését támogató szolgáltatását, az asserteket is arra tervezték, hogy a mindennapi programozás részévé váljanak.
A helyességbizonyító, unit tesztelő nyelvi elemek mindegyike könnyen, egyszerűen különösebb időveszteség nélkül alkalmazható, míg a kód minőségén jelentős mértékben javítanak.

Trace

A trace utasítás a programkód debuggolásakor nyújthat segítséget, mivel loggolja a végrehajtás körülményeit, és a trace kulcsszó után megadott kifejezések értékeit.

trace expr1, expr2, ... expr3 trace all trace off trace on

Egy trace utasítás loggolja a file nevét, a sornak a számát a programkódban, a deklaráló osztály (esetlegesen alosztály) és metódus nevét, ahol az adott trace utasítás elhelyezkedik. A loggolt kifejezések értéke mellett a megfelelő forráskódot is mellékeli.
A trace all utasítással kényelmesen rögzíthetjük a this értékét, a metódus minden argumentumával és minden lokális változójával együtt.
A trace off és trace on utasításokkal ki- illetve bekapcsolatjuk az adott metódus loggolását.
class Foo var _z as int def computeStuff(x as int, y as int) if x > y trace return _z = x * y trace all trace _z trace: this=Foo; x=2; y=4; at Foo.cobra:10; in Foo.computeStuff trace: this=Foo; _z=8; at Foo.cobra:11; in Foo.computeStuff

A trace utasítások általában kiválthatóak megfelelő print utasításokkal, azonban így nem kapunk további információt a forráskóddal kapcsolatban (pl. sornak a száma), és a print utasítások megírása sokkal több munkát igényel.
... print 'trace: x=[x]; y=[y]'

trace x, y

Design by contract

A Cobra a contractok fogalmát átörökítette az Eiffel nyelvből. Contractok segítségével megkövetelhetjük, hogy bizonyos elő-, utófeltételek és invariáns tulajdonságok teljesüljenek egy metódus végrehajtásakor.
A require kulcsszó vezeti be, hogy milyen előfeltételek szükségesek a függvény meghívásához, és az ensure kulcsszó után adhatjuk meg, hogy milyen feltételeket biztosít a függvény végrehajtása. A feltételeket egymás alatt felsorolva, logikai kifejezések listájaként adhatjuk meg, a kifejezések az assert utasítás után írt feltételekhez hasonlóak lehetnek.

class Person def drive(v as Vehicle) require not v.hasDriver v.isOperable ensure v.miles > old v.miles body ...

A contractok előnyös tulajdonsága, hogy az adott metódus dokumentációjának részévé válnak, viszont a betartásuk ténylegesen kötelező, hiszen ezek a feltételek a működő kód részei, nem csak dokumentációs megjegyzések, tehát a kódrészletek a program futtatásakor végrehajtódnak, a feltételek ellenőriződnek. A futás idejű hibákat már igen korán, a kiváltódás helyének közelében diagnosztizálhatjuk contractok segítségével, és mindezt egy jól áttekinthető, egységes formában érhetjük el. Contractok írásakor és tesztelésekor a programozó jobban rá van kényszerülve, hogy átgondolja a viselkedését az osztályainak, metódusainak, ellentétben azzal, ha csak odavetett megjegyzéseket írna.
A contractok emellett nem rónak túl nagy terhet a programozóra, hiszen öröklődéskor a contractbeli feltételek is öröklődnek, a megfelelő szabályok mellett. A túlterhelt metódus az eredetihez képest kevesebb előfeltételt követelhet meg, így általánosabb megoldást adva a problémára, illetve több feltételt biztosíthat, értelemszerűen. Úgy is fogalmazhatunk, hogy az örökölt requirementek, előfeltételek „OR”-ral kapcsolódnak az eredetiekhez, az utófeltételek pedig „AND”-del. Hogy felhívják a programozó figyelmét ezekre a kapcsolódásokra, az újabb kiadásokban az örökölt metódusoknál ki is kell írni a megfelelő kulcsszót a contractok elé.
class ContiguousList<of T> implements IList<of T> def insert(index as int, item as T) require index >=0 and index < count ensure .count = old .count + 1 this[index] is item body ...

class NonContiguousList<of T> inherits ContiguousList<of T> """ Allows insertions past the end of the list. """ def insert(index as int, item as T) or require index >= 0 body ...

Ha egy contract egy feltétele sérül futási időben, akkor a feltétel típusának megfelelően RequireException illetve EnsureException kivétel dobódik.

Unit test

Unit testek megadása, közvetlenül az adott metódus mellett, lehetővé teszi, hogy a tesztelést már a függvény megírásakor elkezdjük, ekkor még a program írója jobban észben tartja, hogy milyen ellenőrzéseket érdemes megtenni. A unit testek a kód részévé válnak, így karbantartásuk, a metódus megváltoztatásakor frissítésük sokkal inkább garantált, mint a dokumentációs leírások, megjegyzések átírása.
A Python doctest szolgáltatása hasonló funkciót biztosít, mint a Cobra unit testek, azonban a Python tesztek docstringen belülre kerülnek, így nem alkalmazható rájuk a kényelmes fejlesztést biztosító automatikus kiegészítés a különböző integrált fejlesztő környezetekben és a szintaktikai kiemelés sem, mindez a Cobra explicit test szekcióiban megvalósítható.

class Foo get copy as Foo """ (Cobra docstring) Copy konstruktor """ test f = Foo() c = f.copy() assert c inherits Foo assert f is not c assert f==c ensure result is not this result==this body ...