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
...