V první části tohoto článku jsem se pokusil nastínit základní koncepci DSL a na příkladu objasnit základní principy vývoje. V této části se budu věnovat problematice návrhu jazyka a generátoru kódu. Vycházím z přednášky Domain-Specific Modeling: Enabling full code generation1), kterou přednesl Juha-Pekka Tolvanen na konferenci OOP2009 v Mnichově.
DSL se snaží vyhnout promítání mentálních obrazů z hlavy zadavatele do prostoru obecného jazyka. Chce takto minimalizovat zkreslení požadavků a zanesení chyb do aplikace. Zadavatel má určitou představu o fungování aplikace, kterou je třeba transformovat do hotového produktu. Tato transformace bývá často bolestná a je tím bolestnější, čím větší propast musíme překlenout mezi abstrakcemi ve světě uživatele a prvky použitého programovacího jazyka. Popisuje-li zadavatel složité vztahy mezi tlačítky, stavy hodinek a displejem, pohybuje se s jistotou ve svém mentálním prostoru, který si vybudoval za léta praxe v oboru. Vypráví příběh, kterému se tvůrce aplikace snaží porozumět a přeformulovat si ho do svého mentálního prostoru, který je vymezen použitými technologiemi a zkušenostmi. Rozhodne-li se tvůrce promítnout tento příběh přímo do kódu Assembleru, bude muset vynaložit značné úsilí kvůli propastnému rozdílu mezi úrovněmi abstrakcí v obou světech. Může také použít nějaký vyšší programovací jazyk, dejme tomu C++, avšak rozdíl v míře abstrakce se významně nezmenší. Programátor bude stále operovat s pojmy značně vzdálenými mentálním obrazům z hlavy zadavatele. Další možností je modelovat systém vizuálně v UML, poté generovat kód do C++ nebo Javy a odtud do Assembleru. Světy zadavatele a programátora se sblíží, nicméně stále bude zapotřebí promítat mentální obrazy zadavatele do prvků jazyka UML.
Na nejmenované švédské univerzitě se kdysi prováděl experiment, který měl ověřit přenositelnost modelů navržených v UML mezi různými nástroji. Testovalo se pět nástrojů, které nabízely export a import modelů ve standardním formátu XMI. V jednom z nástrojů se navrhl jednoduchý model obsahující jedinou třídu a vyexportoval se do XMI. Poté se experimentátoři snažili nahrát model do ostatních nástrojů. Ani v jednom případě se to nepodařilo.
Důsledkem tohoto procesu je, že se o objektech z nějaké oblasti a vztazích mezi nimi vyjadřujeme v cizím jazyce. Hlavní přínos DSL je v tom, že umožňuje popisovat objekty a vztahy v rodném jazyce.
Návrhu doménově-specifického jazyka obecně předchází řada projektů, během nichž postupně roste potřeba speciálního jazyka. V okamžiku, kdy požadavky na něj vykrystalizovaly do dostatečně konkrétní podoby, je možno začít vytvářet jazyk. Předpokládá se, že ve firmě lze najít pár skutečných expertů v oboru, kteří budou jazyk vyvíjet.
Výsledkem vývoje jsou tři produkty: meta-model (gramatika) jazyka, generátor kódu a framework, který interpretuje generovaný kód. V následujícím odstavci se budu věnovat návrhu meta-modelu jazyka.
Vývoj meta-modelu jazyka
Vývoj meta-modelu standardně probíhá iterativně, přičemž v každé iteraci se postupuje ve čtyřech krocích:
1. Identifikace abstrakcí
Cílem tohoto kroku je vytvořit pojmosloví a koncepty jazyka. Vrátíme-li se k digitálním hodinkám, výsledkem by mohly být tyto koncepty: tlačítka, displej, stavy hodinek, stopky, světový čas, proměnná, systémový čas, nastavení hodinek atd.
2. Specifikace meta-modelu
V tomto kroku se pracuje s koncepty, které vzešly z předchozího kroku. Analyzují se vztahy mezi nimi a stanovují se pravidla pro interakce a závislosti mezi nimi. Např. se stanoví, že spojnice stavového přechodu se může nakreslit pouze mezi dvěma stavy, akce musí být napojena nějakou spojnici stavového přechodu, akce musí mít jednoznačný identifikátor atd. Výsledkem této analýzy je gramatika popsaná v nějakém jazyce pro meta-modelování, jako je např. Eclipse Modeling Framework (EMF).
3. Návrh notace
Pro snadnější orientaci v modelu je vhodné opatřit koncepty jazyka odpovídají notací, např. ikonou nebou stylem čáry. Každý jistě ocení, když tlačítko v modelu připomíná tlačítko a není reprezentováno prostým obdélníkem. Na přednášce Tolvanen předváděl modelovací nástroj MetaEdit+, ve kterém je možné nakreslit notaci i během navrhování modelu v DSL. Kresby se automaticky ukládají do meta-modelu jazyka, takže v příště při navrhování jiného modelu se prvky správně zobrazují.
4. Definice generátoru
Jako poslední přichází na řadu definice generátoru kódu z modelu. Je třeba uvést, že je běžné definovat více generátorů a výstupů. Postupů, jak překládat model do kódu existuje řada a budu se jimi zabývat níže. V jednom z postupů se prolézá model pomocí programátorského rozhraní a pro každý nalezený koncept se generuje předem připravený fragment kódu. Ten může být parametrizovaný, přičemž hodnoty parametrů se získávají z aktuálně zpracovávaného konceptu.
Během vývoje jazyka se vývojáři snaží znovu používat komponenty a knihovny z předchozího vývoje. Důraz se přitom klade na iterativnost procesu a předávání prototypu jazyka k vyzkoušení ostatním vývojářům, kteří v něm budou vyvíjet, co nejdříve. Takto se získá potřebná zpětná vazba která poslouží k rozšíření a úpravě meta-modelu a generátoru.
Přístupy k identifikaci konceptů
Pro začátečníky je identifikace konceptů jazyka často tvrdým oříškem. Pro nalezení vhodné sady konceptů se doporučuje analyzovat zhruba kolem 20 příkladů použití jazyka. Až dosud se podařilo zformulovat pět rozdílných přístupů k identifikaci konceptů v prvních fázích vývoje jazyka.
1. Koncepty doménového experta
Jedná se pravděpodobně o nejjednodušší přístup, který nejlépe funguje v zavedených doménách. Koncepty poskytne expert z oblasti a generování kódu je většinou přímočaré. Jazyky, které vzniknou tímto způsobem, jsou snadno srozumitelné i ne-programátorům, kteří často mohou sami model upravovat nebo vytvářet.
2. Generování výstupu
V tomto přístupu se koncepty identifikují z výstupních artefaktů. Příklad takového
artefaktu může být konfigurační skript či konfigurace hlasové aplikace. Zde obecně platí,
že není problém vystihnout statické části, tj. datové struktury. Složitější může být
postihnout chování systému. Zde návrhář čelí pokušení navrhovat plnohodnotný programovací
jazyk. S touto tendencí souvisí sklon k nedostatečnému abstrahování konceptů.
3. Fyzická struktura
Jak název napovídá, tento přístup funguje nejlépe pro fyzické systémy, např. sítě,
logistické systémy, hardwarová architektura, řízení vlakového provozu či řízení výroby v
továrnách. Většinou se navrhuje statický systém s propojeními a závislostmi. Může však
obsahovat i dynamické prvky. Identifikace konceptů probíhá většinou hladce a dosahuje se vysoké úrovně abstrakce.
4. Look and feel systému
Koncepty lze také identifikovat analýzou uživatelského prostředí. Tento přístup jsem použil v příkladu s hodinkami, kde se operovalo s koncepty jako tlačítko, displej a stav hodinek. Identifikace konceptů je většinou bezproblémová a vede k vysoké míře abstrakce. Generátory nepředstavují větší problém.
5. Prostor proměnlivosti systému
V tomto přístupu se návrhář jazyka pomocí konceptů snaží vystihnout proměnlivost systému. Modelování produktu pak spočívá v rozhodování se mezi nabízenými variantami prvků. Má se jednat o nejnáročnější proces identifikace konceptů.
V praxi se obvykle volí kombinace dvou přístupů, přičemž nejlepších výsledků bylo dosaženo kombinací identifikace konceptů z uživatelského rozhraní (4) a analýzou prostoru proměnlivosti systému (5).
Vývoj generátoru
Účelem generátoru je překlad modelu do odpovídajícího výstupu. Překlad probíhá tak, že se prolézá struktura modelu a z jednotlivých prvků modelu se získávají potřebné informace. Ty se použijí pro překlad do výstupního kódu.
Generátor je vhodné navrhnout tak, aby primárně řešil náš úkol, jelikož pokusy o vynalezení obecného generátoru obvykle končí neúspěchem. Generátor by měl generovat 100% kód, který není určen k ručním úpravám. Pokud se narazí na problém v generovaném kódu, má se upravit generátor a nikoliv kód. Generovaný kód by měl být co nejstručnější a snadno čitelný, měl by obsahovat pouze nejnutnější instrukce, většina funkcionality by měla být podchycena frameworkem (viz níže). Generátor samotný by měl být co nejjednodušší a modulární, aby jej bylo možno snadno pozměnit při výskytu problému.
Typy generátorů
1. Použití API
Zde se předpokládá, že model lze analyzovat pomocí API poskytované modelovacím nástrojem. Nevýhoda tohoto přístupu tkví v nízkoúrovňovém zpracování modelu a těsné vazbě mezi generátorem a modelovacím nástrojem.
2. Návštěvník modelu (model visitor)
V tomto přístupu se každé struktuře modelu přiřadí fragment struktury výstupního kódu. Nevýhodou je omezení na jednoznačné přiřazení.
3. Výstupní šablona
Další možností je navrhnout šablonu výstupního kódu s instrukcemi generátoru. Celá šablona je většinou uložena v jediném souboru. Výhodou je jednoduchost.
4. Lezoun (Crawler)
Tento generátor je navržen k procházení modelu a je schopen samostatně se navigovat v modelu. Generovaný kód posílá do výstupního proudu.
5. Generátor generátorů
Zde se předpokládá, že máme k dispozici generátor, který nám vygeneruje specializovaný generátor k našemu meta-modelu. Lákavá myšlenka, avšak většinou se ukáže, že takového super-generátoru není zapotřebí.
Framework
Framework je poslední ze tří základních stavebních kamenů doménově-specifického modelování. Představuje rozhraní k cílové platformě skrývající nepodstatné detaily. Usnadňuje návrh generátoru, neboť ten se může soustředit pouze na generování nezbytného kódu, který bezprostředně souvisí se zpracovávaným modelem. Framework současně zvyšuje míru abstrakce, takže generátor může pracovat v pojmosloví, které je mu bližší. Současně omezuje vznik duplicit v generovaném kódu a napomáhá znovupoužití komponent vyvinutých dříve. Dále usnadňuje přenositelnost, neboť vyvstane-li potřeba adaptovat produkt pro jinou cílovou platformu, bude nutné přepsat pouze framework.
Nástroje pro DSM
Tolvanen nabízí šest způsobů, jak si pořídit nástroj pro doménově specifické modelování:
1. Napsat si vlastní software od začátku
2. Napsat si vlastní software nad nějakým existujícím frameworkem
3. Navrhnout meta-model jazyka v nějakém existujícím nástroji a vygenerovat si kostru vlastního nástroje
4. Navrhnout meta-model jazyka a vygenerovat si plně funkční nástroj pracující nad nějakým frameworkem
5. Navrhnout meta-model a ten použít jako konfiguraci pro nějaký existující generický modelovací nástroj
6. Pořídit si integrované modelovací a meta-modelovací prostředí (např. MetaEdit+)
Na internetu jsem posbíral reference na několik nástrojů pro DSM:
- OpenArchitectureWare modulární MDA/MDD generátorový framework implementovaný v Javě
- Graphical Modeling Framework (GMF) modelovací framework pro Eclipse poskytující infrastrukturu pro vývoj grafických editorů založených na EMF a GEF.
- Text Template Transformation Toolkit (T4) - jazyk pro psaní šablon, který je součástí Visual Studia 2008
- Generic Modeling Environment (Vanderbilt) - konfigurovatelná sada nástrojů pro vytváření doménově specifických modelovacích prostředí
- XMF Mosaic - open-source nástroj pro vývoj dynamických jazyků (XMF). Další informace např. na http://www.theserverside.com/news/thread.tss?thread_id=49402
- MetaEdit+ - komerční integrované prostředí pro vytváření a používání vlastních doménově specifických jazyků
Shrnutí
Ze zkušeností vyplývá, že doménově-specifické modelování významně zvyšuje produktivitu (odhady jsou 5-10-krát.) Klade se důraz na znovupoužitelnost komponent a oddělování zájmů. V podstatě se jedná o radikální refaktoring.
Reference:
1) Domain-Specific Modeling: Enabling full code generation, Juha-Pekka Tolvanen, OOP2009, Munich
2) www.DSMforum.org
3) www.DSMbook.com