Gyakran nem is vesszük észre, vagy csak nem gondolunk rá, de mindennapjainkat szoftverek hada övezi. Vannak köztük kisebb és nagyobb méretűek. Szoftver irányítja a kezünkben tartott mobileszközt, de egy telefonhálózat működését is. A szoftverek közös jellemzője, hogy általában nemcsak megszületnek és használatba vétetnek, hanem gyakran tovább kell fejleszteni vagy karban kell tartani őket. Ez kisebb méretű szoftverek esetében programozói rutinfeladatnak tűnhet, de ipari méretű kódok esetén a forráskód mennyisége és bonyolultsága olyan méreteket ölt, amelyet humán erőforrással igen nehéz, vagy akár lehetetlen átlátni. Mindezt még nehezíti az a tény, hogy az iparban a programok gyakran csapatmunkában készülnek, így egy-egy fejlesztőnek nincs rálátása a teljes szoftver szerkezetére és részleteire, tehát nem is tudja a szükséges kapcsolatokat felderíteni; rosszabb esetben a program eredeti fejlesztője már nem is abban a csapatban dolgozik, ahol azt jelenleg karbantartják. Sorolhatnánk még a hátráltató tényezőket, de inkább adjunk megoldást a problémára!
Hiszen azoknál a feladatoknál/lépéseknél, amelyeket nem lehet manuálisan kivitelezni, jogosan merül fel a kérés: végezze el a számítógép a programozó helyett, de legalábbis segítsen neki a kivitelezésben. Így született meg az igény a statikus forráskódelemző eszközökre, amelyek egyik fő célja a nagy szoftverek forrásainak elemzése. Továbbá az elemzések eredményeit célzottan tárják a programozók elé, ezzel megkönnyítve a fejlesztők mindennapos tevékenységeit: felhasználhatók az elemzési eredmények a biztonságos program-transzformációk megvalósítására, de segíthetnek a programozóknak a hibakeresésben, programmegértésben is.
Statikus elemzőeszközök
Ilyen eszközök fejlesztésébe fogott az ELTE Szoftvertechnológia Laboratóriumának több kutatócsoportja. A Szoftvertechnológiai Labor legfőbb ipari motivációját és társfinanszírozását az Ericsson adja, emellett a projektek a Közép-magyarországi Regionális Operatív Program és az Európai Unió társfinanszírozásában is részesültek (KMOP-1.1.2-08/1-2008-0002).
A statikus programelemzési technológiák általánosíthatók, de azok a szemantikus elemzések, amelyek a kódrészletek közötti összefüggéseket, kapcsolatokat tárják fel, erősen nyelvspecifikusak. Ennek megfelelően az ELTE-n már három különböző (ám technológiai megoldásokban néhol hasonló) statikus elemzőeszközt fejlesztenek/fejlesztettek: RefactorErl (Erlang programok elemzése és átalakítása), F#-elemző (F# programok elemzése és stílusegységesítő átalakítása) és egy a C++ programok elemzésére és megértésének támogatására.
Legnagyobb múltja a RefactorErl elemzőeszköz fejlesztésének van. Az Erlang – dinamikusan típusos, funkcionális, elosztott programozási nyelv, amelyet a 80-as években kezdtek el fejleszteni az Ericsson egyik kutatólaboratóriumában. A nyelvet abból a célból alkották meg, hogy könnyen és hatékonyan lehessen telekommunikációs szoftvereket fejleszteni benne. Ennek eredményeképpen a nyelv fő jellemzőjévé vált a konkurens, elosztott, valós idejű, hibatűrő programok írására való lehetőségek biztosítása. A nyelv dinamikája megnehezíti a statikus elemzést, és bizonyos esetekben lehetetlenné teszi a pontos információk kiszámítását. Ezekben az esetekben egy elfogadható minőségű közelítést számolunk és biztosítunk a felhasználónak.
A kutatások és fejlesztések kezdetben szoftverek forráskódjának refaktorálására (a program átalakítása, transzformálása annak jelentésének, viselkedésének megőrzésével) irányultak. A refaktorálások néha egyszerűnek tűnő átalakítások lehetnek, mint például egy függvény átnevezése, de ipari méretű kódok esetén fáradságos folyamat lehet, amely sok hibalehetőséget takar. Tekintsük például az átnevezés problémáját, ahol sok ezer hivatkozás lehet egy-egy függvényre, amelyeket meg kell találni és át kell nevezni. Másrészt az Erlang dinamikus volta miatt nem elég a statikus hívásokat felderíteni, hanem azokat a nyelvi elemeket is meg kell találni, amelyek csak közvetetten hivatkozhatnak a függvényre. Ezek automatikus felderítéséhez és későbbi átalakításához módszereket dolgoztunk ki, a transzformálások helyességét véletlenszerűen generált programokon tulajdonságalapú tesztekkel ellenőriztük, és a viselkedési ekvivalenciát is megvizsgáltuk.
A refaktorálásokhoz szükséges pontos tudás a forráskód szintaxisáról és szemantikájáról azonban jól használható más jellegű feladatokra is. Így a refaktorálás mellett fontos szerepet kapott azon ipar igényeinek kielégítése, amely a programmegértés támogatására irányult.
Segítség ipari méretű kódok megértéséhez
Mint azt korábban már említettük, a szoftverek forráskódja akkorára nőhet, hogy azt már igen nehéz segítség nélkül átlátni, továbbfejleszteni, karbantartani. Ezért nagyon hasznos egy olyan eszköz, amely segítséget nyújthat ipari méretű kódok megértéséhez, összefüggések felderítéséhez. Mivel a RefactorErl refaktoráláshoz készített forráskód-reprezentáló és elemző keretrendszere már tartalmazott sok hasznos információt, a továbbfejlesztés az elemzési eredmények Erlang programozók számára jól használható módon való megjelenítése irányába mutatott. E fejlesztések eredménye lett egy lekérdező nyelv, amelynek a segítségével a programozó megfogalmazhatja saját kérdéseit (a beépített lekérdezések mellett) a forráskód szerkezetéről, szemantikus összefüggéseiről, különböző tulajdonságairól. Egy másik lehetőség, hogy bizonyos lekérdezések eredményét gráfokon ábrázoljuk (1. ábra: Modulok közötti függőségek), amelyek a szoftver különböző komponenseinek együttműködését mutatják.
1. ábra: Modulok közötti függőségek
A különböző programmegértést támogató módszerek mellett arra is figyelni kellett, milyen módon jelenítsük meg ezeket a lekérdezéseket. Ezért különböző szerkesztőkbe, fejlesztői környezetekbe is integráltuk az eszközt, ugyanakkor meghagytuk az egyszerű parancssoros vezérlés lehetőségét is. Mindemellett azt is szem előtt kellett tartani, hogy a fejlesztések nagyrészt csapatokban folynak, így támogatást adtunk egy webes interfészen keresztül arra is, hogy ugyanazon elemzett kódhoz több fejlesztő is hozzáférhessen egyszerre (2. ábra. A RefactorErl webes felhasználói felülete). Ez azért különösen fontos, mert több millió sor elemzése idő- és erőforrás-igényes feladat. A kezdeti elemzések órákig tarthatnak, és több GB memóriaigényük van. Ezt nem szeretné a csapatok minden tagja a saját gépén végrehajtani, és lényegében felesleges is lenne, hiszen ugyanazt a kódbázist használják. Ezért tettük lehetővé, hogy a RefactorErlt egy szerverre telepítve egy webböngésző segítségével bárki elérhesse.
Talán ijesztő lehet az „időigényes feladat” kijelentés, de ehhez hozzá kell tenni, hogy a modellünk szerkezetének köszönhetően ezt az időbefektetést csak egyszer kell a feladatra fordítani.
2. ábra. A RefactorErl webes felhasználói felülete
A rendszer úgy van felépítve, hogy először, egy kezdeti elemzési fázisban „betöltjük” a forráskódot a RefactorErl adatbázisába. Az elemzési eredményeket eltároljuk, így ha később arra szükség van, nem kell újra kiszámolni azokat, és bár az adatbázisban való eltárolásnak erőforrás- és időigénye van, a későbbiekben gyorsabban tudunk választ adni a programozó kérdéseire. Mindemellett az elemzések inkrementálisak, a forráskód változása esetén már nem kell mindent újraelemezni, csak a megváltozott kódrészeket és a szükséges környezetet elemezzük újra, így a változások követése nem tart sokáig.
Kedvcsináló az eszközhasználathoz
Ahhoz, hogy meghozzuk a kedvet a hasonló eszközök használatához, a teljesség igénye nélkül felsorolnék pár használati esetet.
Hibakeresés támogatása. Hibajavítás közben gyakran szükség lehet arra, hogy kiderítsük, mi lehet egy adott változónak/kifejezésnek az értéke, vagy éppen egy függvényhívási láncon keresztül szeretnénk követni a program futását. Előbbit a lekérdezéseken keresztül egy függvények és modulok közötti adatfolyam-elemzés biztosítja, utóbbit pedig a reprezentáció egy absztrakt nézete adja.
Programmegértés és karbantartás támogatása – függvényhívási gráfok megjelenítése, amely dinamikus hívási információkat is tartalmaz; üzenetküldések és folyamathálózat elemzése; referenciák követése; függőségek számítása és megjelenítése; mellékhatások kiderítése. Ezek mind olyan feladatok, amelyeket a mindennapos fejlesztések során a programozó maga is végez, hiszen ha például felcseréli a kifejezések végrehajtási sorrendjét, akkor tudnia kell, hogy nem változtatja-e meg a mellékhatások sorrendjét. A nyelv dinamikus volta miatt igen megnehezíti a kódmegértést, amikor a nyomkövetés közben a programozó egy dinamikus hívási szerkezettel találkozik. Az eszköz lehetőséget ad arra, hogy a statikusan kideríthető dinamikus hívásokat felderítse a programozók számára.
Kódolási szabványok ellenőrzése. A lekérdező nyelven keresztül különböző bonyolultsági metrikák érhetők el. Úgy tűnhet, hogy ez nem tartozik a kódmegértés támogatásához, de ezeknek a metrikáknak mégis van közvetett hatásuk a kódra. Ugyanis a metrikák magas értéke a kód bonyolultságának mérőszáma, így a magas metrikaértékek előjelzik azt, hogy a kód nehezebben érthetővé válik. Ezért olyan kódolási szabályokat fogalmazhatunk meg, amelyek a metrikaértéket bizonyos határok között tartják, és az eszköz ennek ellenőrzésére nyújt lehetőséget. Egy egyszerű példaként megemlíthetném a beágyazottsági metrikát, a különböző vezérlési szerkezetek mélyen egymásba ágyazása ugyanis nehezíti a kódmegértést, hiszen több lehetséges végrehajtási utat és értéket kell a programozónak a nyomkövetés közben fejben tartania.
Klaszterezés. Ezen azt a folyamatot értjük, amikor a nyelv különböző elemeit csoportosítjuk az azok közötti kapcsolatok alapján. A RefactorErl rendszer támogatja modulok és függvények csoportosítását. Az előbbi egyik jellemző használati esete, amikor a lehető legjobban kapcsolódó modulcsoportokat határozzuk meg egy nagyobb modulhalmazból, mindemellett pedig azonosítjuk azokat a modulokat, amelyeket könyvtári modulként több klaszter is használ. Ez a modulklaszterezés, az ennek eredményeképpen előálló kisebb csoportok már könnyebben karbantarthatók. A csoportosítás egy másik formája a függvényklaszterezés, amelynek során nagyra duzzadt modulokban implementált függvényeket csoportosítanak a közöttük lévő kapcsolatok alapján több kisebb modulra. Ennek végrehajtásához a modulok szerkezetét is át kell alakítani, amit a refaktorálásaink támogatnak (mozgatások és átnevezések).
A RefactorErl eszköz nyílt forrású, szabadon letölthető, így könnyen hozzáférhetnek azok a programozók, akik Erlang programjaikat szeretnék átalakítani, vagy csak éppen értelmezni, összefüggéseket keresni, javítani.