Februar 2015 IST DEKLARATIV WIRKLICH INSTRUKTIV? Eine kritische Betrachtung Dr. Jürgen Lampe Agon Solutions IST DEKLARATIV WIRKLICH INSTRUKTIV 1 Abstract Code wird vor allem gelesen. Deshalb ist es wichtig, dass Programmiersprachen intuitiv zu verwenden sind und Beschreibungen unterstützen, die beim Lesen instruktiv empfunden werden. Wenn es darum geht, die Vorteile von Sprachelementen wie Lambdas oder StreamProcessing herauszustellen, wird sehr oft darauf verwiesen, dass sie einen deklarativ geprägten Programmierstil erlauben. Deklarative Programmierung wird dabei per se als erstrebenswert und vorteilhaft bewertet. Ist diese Annahme wirklich richtig, und was ist deklarative Programmierung überhaupt? Im Folgenden wird zunächst eine praktikable Abgrenzung des Begriffs versucht. Darauf aufbauend werden einige grundlegende Probleme genauer erörtert und danach die Rahmenbedingungen geeigneter Anwendungsszenarien beschrieben. Was ist deklarative Programmierung? Der Begriff Deklarative Programmierung ist nicht neu. Das Thema ist inzwischen über 40 Jahre alt, hat seine suggestive Kraft aber offensichtlich nicht verloren. Es scheint, dass neben Microsofts Sprache F# insbesondere die Diskussionen um die Spracherweiterungen für Java 8 ihm neues Leben eingehaucht haben. Nicht alle sind darüber glücklich, recht polemisch schreibt zum Beispiel Robert Haper in einem Blog-Beitrag „..I was surprised by the declarative zombie once again coming to eat our brains.” [1] Um die Berechtigung dieser Bemerkung bewerten zu können, ist es erforderlich, zunächst ein tieferes Verständnis zu gewinnen. Wie so oft bei Schlagworten zeigt sich auch hier, dass eine klare und allgemein anerkannte Begriffsbildung fehlt. Meist werden logische und funktionale Programmierung dazu gerechnet, manchmal auch mathematische Modelle und domänenspezifische Fachsprachen. Gemeinsam ist allen Definitionsansätzen, dass sie als wichtiges Kennzeichen die Beschreibung des Was anstelle des Wie hervorheben. Während ein imperatives oder prozedurales Programm den Weg beschreibt, auf dem durch schrittweise Zustandsänderungen das gewünschte Ziel (=Ergebnis) erreicht werden kann, ist ein deklaratives Programm die genaue Beschreibung (Spezifikation) eben dieses Ergebnisses. Es gibt keine Manipulationen eines zeitlich veränderbaren Zustands. Die deklarative Sicht ist daher eine vorwiegend statische und entspricht am ehesten der Verwendung von Blaupausen als Produktvorlage. Wenn auf die Spezifizierung der Ausführung verzichtet wird, öffnet das die Freiheit, unterschiedliche Wege nutzen zu können. Besonders betont wird gern die Chance für Optimierungen, die unabhängig von der gerade behandelten Aufgabe sind. Im Idealfall kann der deklarative Code später einmal durch ein Verfahren ausgeführt werden, dass zum Zeitpunkt des Schreibens noch gar nicht bekannt war. Ein oft zitiertes Beispiel dafür ist die implizite Parallelisierung, die allerdings wieder ganz andere Fragen aufwirft und deshalb hier IST DEKLARATIV WIRKLICH INSTRUKTIV 2 nicht weiter betrachtet werden soll. Um auf den erwähnten Idealfall zurückzukommen, der begegnet einem auch in der Programmierung so selten wie im normalen Leben. Das Verführerische an deklarativen Beschreibungen liegt darin, dass sich unzählige sofort einleuchtende Beispiele konstruieren lassen, deren Kompaktheit und Klarheit überzeugend wirkt. Bei solchen kleinen Beispielen ist der Effekt, der sich durch den Entfall relativ konstanter Codemuster (sogenannter „Boilerplate-Code“) besonders beeindruckend. Was dabei leicht übersehen wird, ist, dass dieser Effekt nicht skaliert. Je größer und vielfältiger ein Problem ist, desto geringer wird der relative Anteil der konstanten Fragmente. Gleichzeitig steigen die Ansprüche an die deklarative Sprache bezüglich der Ausdrucksstärke, um solche komplizierteren Zusammenhänge darstellen zu können. Nur wenn dann noch eine echte Vereinfachung zu sehen ist, lohnt es, ernsthaft über deklarative Programmierung nachzudenken. Der reine Wegfall von Standard-Rahmencode allein rechtfertigt es jedenfalls nicht. Für die weitere Diskussion soll als Beispiel für eine eher deklarativ geprägte Sprache SQL benutzt werden. Ein anderer prominenter Kandidat für diese Rolle wäre HTML. Ebenso sind Java-Annotationen deklarative Ausdrucksmittel. Als typischer Vertreter prozeduraler Sprachen dient der Kern von Java. Das Beispiel 1 stellt die beiden Paradigmen gegenüber. Während das (deklarative) SQLStatement nur die Ergebnismenge definiert, beschreibt der Java-Code genau, wie das Ergebnis erzeugt werden soll. (Korrekterweise gehörte auch der Code der verwendeten toString-Methode noch dazu.) Java: for (datensatz: datensaetze) { if (datensatz.id == 0) System.out.println(datensatz.toString()); } SQL: SELECT * FROM datensaetze WHERE id=0 Beispiel 1: Vergleich prozedurale und deklarative Programmierung Es ist deutlich zu erkennen, dass in diesem Fall der SQL-Code wesentlich kürzer ist und eine nicht geordnete Liste aller Datensätze, deren id gleich null ist, übersichtlicher beschreibt. Allerdings erhält man im Gegensatz zur Java-Lösung keinerlei Information über die physische Reihenfolge, die in der Liste eventuell die Folge der Einfügungen ist. Operationalisierung Die Tatsache, dass deklarative Programme das Was, aber nicht das Wie einer Problemlösung beschreiben, macht es notwendig, dieses Wie automatisch zu ermitteln. Die IST DEKLARATIV WIRKLICH INSTRUKTIV 3 Implementierung einer deklarativen Programmiersprache erfordert daher zwingend die Existenz eines Algorithmus für das Finden und Zuordnen eines Ausführungsverfahrens. Diese Zuordnung wird Operationalisierung genannt. Sie setzt voraus, dass jedem korrekten Ausdruck in der deklarativen Programmiersprache eine eindeutige Bedeutung zugeordnet werden kann. Die Definition der Bedeutung erfolgt dabei durch ein formales Modell des Objekt- oder Domänenbereichs. In diesem Abschnitt werden einige wichtige Fragen, die bei der Implementierung deklarativer Programmiersprachen auftreten, diskutiert. Existenz eines effektiven Verfahrens Dies ist die Grundbedingung. Es muss mindestens ein Verfahren existiert, welches unter allen denkbaren Bedingungen einen zulässigen Ausgangszustand in den gewünschten (deklarierten) Endzustand überführt, d. h. effektiv ist. Das setzt ein gutes Verständnis und die Modellierbarkeit der zugrunde liegenden Domäne voraus. Für das Beispiel SQL ist diese theoretische Basis durch das relationale Datenmodell gegeben. Typischerweise handelt es sich um einen klar abgegrenzten, beschränkten Objektbereich. In semantisch reicheren Modellen zeigt sich leider oft, dass wichtige Fragen prinzipiell unentscheidbar sind. Dann bleibt zum einen der Rückgriff auf heuristische Verfahren mit dem Risiko einer unvollständigen Operationalisierung. Ein anderer Weg mit dieser Situation fertig zu werden ist der, die formale Sprache zusätzlichen Restriktionen zu unterwerfen. Ein typisches Beispiel für diesen Weg liefern Parser für kontextfreie Sprachen. Sie werden üblicherweise durch die Regeln einer Grammatik beschrieben – ein klassischer Fall von deklarativer Programmierung. Die Form der zulässigen Regeln wird jedoch fast immer durch den verwendeten Analyse-Algorithmus eingeschränkt. Genau genommen widersprechen solche Restriktionen dem deklarativen Charakter der Beschreibung. Aber während sich für Parser die meisten dieser zusätzlichen Bedingungen auf der Ebene des Formalismus (als z. B. rechts- bzw. linksrekursive, LL(n)- oder LR(n)-Grammatik) beschreiben und statisch überprüfen lassen, ist das in anderen Anwendungsbereichen seltener der Fall. Effizienz des Verfahrens Wenn ein effektives Verfahren existiert, d. h. die Lösung prinzipiell möglich ist, stellt sich als nächstes die Frage nach der Effizienz dieses Verfahrens. Trotz der rasanten Entwicklung der Computertechnik wird die Verarbeitungsleistung immer ein Thema bleiben, weil dadurch u. a. Grenzen der Anwendbarkeit bestimmt werden. Grundsätzlich wird ein Verfahren umso effizienter gestaltet werden können, je besser es an die ganz konkrete Aufgabe angepasst wird. Es ist ein prinzipielles Problem der deklarativen Programmierung, dass bei der Operationalisierung besondere Eigenschaften des konkreten Anwendungsfalls nur begrenzt, nämlich nur soweit sie sich in der Beschreibung manifestieren, berücksichtigt werden können. Aufgaben mit einem begrenzten Lösungsraum können immer durch systematisches Durchprobieren aller Möglichkeiten (Brute-Force- oder Exhaustionsmethode) gelöst werden. Dieser Ansatz ist jedoch wenig effizient und schränkt die mögliche Größe der behandelbaren Probleme stark ein. Doch auch bei ausgefeilteren Verfahren kann es vorkommen, dass die IST DEKLARATIV WIRKLICH INSTRUKTIV 4 allgemeine Effizienz nicht den Anforderungen der Praxis genügt. Wenn kein besserer Algorithmus bekannt ist, gibt es unterschiedliche Wege, trotzdem akzeptable Implementierungen zu erreichen: • Der deklarative Formalismus wird um operative Elemente erweitert, die helfen, den Suchraum einzuschränken. Um sie richtig zu verwenden sind gute Kenntnisse des (eigentlich verborgenen) Verfahrens unerlässlich. In diese Kategorie fallen die Indizes in SQL. Sie können – richtig angewandt - die Laufzeit erheblich beeinflussen, haben aber auch das Potential, aus einem (deklarativ) korrekten Statement eines zu machen, das (operativ) einen Deadlock erzeugt. • Für die Implementierung wird ein „besserer“ Algorithmus ausgewählt, der jedoch weitere Einschränkungen für den deklarativen Formalismus erfordert. Das muss keine schlechte Lösung sein. In vielen Fällen ist es allerdings schwierig, diese Einschränkungen in den Kategorien des Formalismus zu definieren.1 Muss man aber das unterliegende Ausführungsmodell zu genau kennen, entfällt ein wichtiger Vorteil des deklarativen Paradigmas. Wenn auf andere Weise die erforderliche Effizienz nicht erreicht werden kann, endet allerdings der praktische Einsatz eines deklarativen Programmiermodells viel zu oft in einer schwer zu beherrschenden Mischform mit zahlreichen prozeduralen Erweiterungen. Noch deutlicher als bei den Ergänzungen, die SQL zu einer mehr oder weniger „normalen“ Programmiersprache machen sollen, zeigt sich diese Konsequenz, wenn HTML durch JavaScript erweitert wird. Implizite Abhängigkeiten Deklarative Programme sollen effiziente Implementierungen dadurch unterstützen, dass sie bewusst Freiheiten lassen. Festlegungen der Art „In diesem Fall ist das Ergebnis undefiniert.“ haben sich in der Praxis allerdings nicht bewährt. Das ist leicht erklärbar: Die Richtigkeit eines formalen Ausdrucks wird häufig nicht durch eine abstrakte Prüfung, sondern durch einen Test mit einer konkreten Implementierung ermittelt. Ob dabei das undefinierte Verhalten adäquat berücksichtigt wird, ist praktisch nicht verifizierbar. Das Gefährliche an solchen verdeckten Implementationsabhängigkeiten ist, dass sie schwer oder gar nicht zu erkennen sind und dadurch kaum vermieden werden können. Neben solchen funktionalen Abhängigkeiten können auch nichtfunktionale auftreten, zum Beispiel in Bezug auf Laufzeit oder Speicherplatzbedarf. Dieser Punkt ist keine akademische Spitzfindigkeit, wie jeder bestätigen wird, der schon einmal SQL-Skripte von einem auf ein anderes DBMS migriert hat. 11 Ein Beispiel dafür ist die bei naiver Implementierung von Parsern mittels rekursiven Abstiegs anzutreffende Bedingung, dass Alternativen so geordnet sein müssen, dass bei gemeinsamen Präfixen, der längere immer vor dem kürzeren stehen muss, also nicht (int|integer), sondern umgekehrt, was bei Beteiligung von Nichtterminalen schwierig zu erkennen ist. IST DEKLARATIV WIRKLICH INSTRUKTIV 5 Das Basismodell Das Modell, welches wie bereits erwähnt, die Bedeutung der deklarativen formalen Sprache definiert, ist der Dreh- und Angelpunkt. Es entscheidet sowohl über die Operationalisierbarkeit als auch die dauerhafte Anwendbarkeit in der Praxis. Ein eindrucksvolles Beispiel für die Rolle des Modells ist die Entwicklung von HTML. Entworfen als logische Auszeichnungssprache für Web-Dokumente ist sie ohne Zweifel deklarativ. Was jedoch von Anfang an gefehlt hat, ist ein klar definierter Begriff dessen, was ein Web-Dokument im Detail ist und welche Struktur es haben kann. Das ist ein grundsätzlicher Unterschied zu beispielsweise Donald Knuths Satzsystem TeX. Die Folgen sind bekannt und heute noch spürbar. Über Jahre haben inkompatible Browser allen Beteiligten das Leben schwer gemacht. Es hat lange gedauert, bis einigermaßen klar war, was überhaupt zulässiger HTML-Text ist. Einen wirklichen Fortschritt hat erst das Document Object Model (DOM) des W3C gebracht, indem es ein verbindliches Strukturmodell definiert, auf das sich die Auszeichnungen beziehen. (Und das mit Scriptsprachen manipuliert werden kann.) Das semantische Modell Deklarativen Ausdrücken liegt eine im Vergleich zu prozeduralen Ausdrücken abstraktere Semantik zugrunde. Das ermöglicht auf der einen Seite kompaktere Beschreibungen, erfordert auf der anderen jedoch, dass dieses semantische Modell von allen, die den Code schreiben, modifizieren oder lesen, gleichermaßen verstanden wird. Das ist eine wesentliche Restriktion des deklarativen Paradigmas: Es setzt voraus, dass ein hinreichend bekanntes Basismodell existiert. In der Informatik trifft das für viele einfache Strukturen zu, aber es fällt schwer, Beispiele zu finden, die mit der Komplexität des relationalen Datenmodells vergleichbar sind. Wichtig ist, dass die Konzepte des semantischen Modells möglichst orthogonal sind, wie das im Fall von SQL für Relationen und Datentypen gilt. Denn da es sich beim semantischen Modell um eine letztlich frei gewählte Abstraktion handelt, ist auch die Definition der Interaktionen der beteiligten Konzepte frei wählbar. Die Erfahrung zeigt, dass abhängig vom jeweiligen Modellierungsziel, solche Interaktionen verschieden interpretiert werden können und auch werden. Semantische Modelle mit Varianten, besonders wenn sich diese nur auf Details beziehen, sind jedoch eine denkbar schlechte Basis für die Verständigung. Schließlich ist Programmtext immer auch Kommunikationsmedium zwischen Menschen. Diese Rolle kann eine deklarative Sprache nur erfüllen, wenn ihr semantisches Modell eindeutig verstanden wird und klar abgegrenzt ist. Aus diesen Überlegungen folgt weiter, dass das semantische Modell nicht zu komplex sein darf, weil die (lesende) Interpretation der deklarativen Beschreibung erfordert, alle jeweils für das Verständnis relevanten Konzepte parat zu haben. IST DEKLARATIV WIRKLICH INSTRUKTIV 6 Modellbeschränkungen Ein Modell repräsentiert stets nur einen Teilaspekt der Wirklichkeit, der durch die Mengen der modellierten Objekte und Operationen beschränkt wird. Leider halten sich die Wünsche der Anwender nicht an die Grenzen der Modelle. Ein kleines Beispiel aus der täglichen Praxis soll die Problematik illustrieren: Gefordert sei ein SQL-Select mit fortlaufender Nummerierung der Ergebnisdatensätze. Diese scheinbar triviale Funktion ist nicht einfach zu realisieren. Das liegt nicht an der Unzulänglichkeit von SQL, sondern daran, dass das Konzept Nummerierung kein Bestandteil der Relationentheorie ist. Wenn die Ergebnismenge vor der Nummerierung nicht sortiert wird, ist die Menge der Datensätze einschließlich Zeilennummer nicht mehr unabhängig von Implementationsdetails, wie z. B. der physischen Anordnung. D. h., eine einfache Nummerierung, wie sie bei prozeduraler Programmierung mittels einer Indexvariablen realisiert würde, führt aus dem Definitionsbereich des Modells hinaus. Wie eine modellkonforme Nummerierung aussehen könnte, zeigt das Beispiel 2 (entnommen aus [2]). Man sieht deutlich, dass an den Grenzen des Modells die Vorteile der deklarativen Beschreibung verschwinden. Probleme dieser Art werden oft durch Ergänzung eingebauter Funktionen wie hier beispielsweise ROWCOUNT oder ROW_COUNT zu entschärfen versucht. SELECT rank=count(*), a1.au_lname, a1.au_fname FROM authors a1, authors a2 WHERE a1.au_lname + a1.au_fname >= a2.au_lname + a2.au_fname GROUP BY a1.au_lname, a1.au_fname ORDER BY rank Beispiel 2: MS SQL-Server – Ausgabe einer alphabetisch geordneten Liste von Autorennamen und -vornamen mit vorangestellter laufender Nummer Handhabbarkeit Neben den erwähnten eher technischen und konzeptionellen Fragen wirft die deklarative Programmierung in ihrem Gebrauch durch den Menschen Probleme auf. Menschen denken nur sehr eingeschränkt deklarativ Die meisten Menschen sind in ihrem Denken stark handlungsorientiert, insbesondere wenn es um das Erreichen eines Ziels geht. Deklarative Beschreibungen werden, wie die schon erwähnten Blaupausen, vorrangig für die Darstellung statischer Objekte eingesetzt. Nur in Fällen, wo eine standardisierte Darstellungsform existiert und genügend Erfahrungen bezüglich der Umsetzung vorliegen, reicht eine solche Beschreibung als Handlungsanweisung aus. Wie schwer es Menschen fällt, deklarativ zu denken, kann man leicht am Beispiel von Zeitungsanzeigen demonstrieren. Selbst bei lauterstem Bemühen klafft zwischen implizit Vorgestelltem und explizit Spezifiziertem oft eine erhebliche Lücke. IST DEKLARATIV WIRKLICH INSTRUKTIV 7 Deklarative Programmierung beschreibt Zustandsveränderungen in einem Schritt, ohne Berücksichtigung interner Zwischenzustände und abstrahiert damit in gewisser Weise von der Zeit. Leben ist aber durch Zeit geprägt. Es besteht aus einer fortlaufenden Folge von Operationen, die den globalen Zustand verändern: Leben ist grundsätzlich prozedural. Der Verzicht auf die zeitliche Komponente in verschiedenen Modellierungen ist eine erhebliche intellektuelle Leistung. Ein Blick in die Wissenschaftsgeschichte zeigt, dass der Weg der Herausbildung solcher Abstraktionen nicht einfach und konfliktfrei verläuft. Es ist eine Illusion zu glauben, dass gute von der Zeit abstrahierende Modellvorstellungen im erforderlichen Umfang ad hoc während der Programmerstellung entwickelt werden können. Notation Nicht in allen Fällen ist ein deklarativer Programmtext kompakter und übersichtlicher als ein prozeduraler. Das gilt selbst, wenn man vollständig innerhalb der Grenzen der deklarativen Sprache bleibt, wie das folgende SQL-Beispiel (Beispiel 3) veranschaulicht. Die Aufgabe besteht darin, eine Liste von Dateinamen so auszugeben, dass eine Endung „txt“ durch einen Punkt abgetrennt wird, also z. B. „alfatxt“ als „alfa.txt“. Der Code ist vereinfacht und berücksichtigt nicht, ob der Punkt schon vorhanden ist. Trotzdem enthält er bereits fünfmal den Teilausdruck length(datei) und zweimal length(datei)-2. Das Problem wiederkehrender Teilausdrücke ist aus der funktionalen Programmierung bekannt. Den Auswirkungen auf die Laufzeit wird mit Optimierungen (Common subexpression elimination) versucht zu begegnen. Das hilft jedoch nicht gegen den offensichtlichen Mangel, dass der zu schreibende Code unübersichtlicher und länger ist, als wenn man den Teilausdruck einer Programmvariablen zuweisen und diese dann stattdessen verwenden würde. SELECT if(length(datei)>3 AND (substr(datei, length(datei)-2, 3)='txt'), concat(substr(datei, 1, length(datei)-3), '.', substr(datei, length(datei)-2, 3)), datei) FROM dateien WHERE length(datei)>0; Beispiel 3: Wiederholung identischer Teilausdrücke Ein weiterer Nachteil deklarativer Sprachen, der sich zwangsläufig aus ihrer Natur ergibt, ist die Unmöglichkeit Interaktionen, beispielsweise Ein- und Ausgaben, angemessen zu beschreiben. In der Regel werden diese Aufgaben als Seiteneffekte realisiert und sind als solche in der Beschreibung eher verborgen als klar erkennbar. Beeinflussbarkeit der Ausführung Es ist gerade der größere Abstand zwischen Programmcode und Ausführung, der die anwendungsorientierte Beschreibung erleichtert. Kein Vorteil ohne Preis: Durch diesen größeren Abstand kann der durch die Maschine ausgeführte Code weniger beeinflusst werden. In der Theorie ist das ein Vorteil. Auftretende Performanceprobleme sind durch andere oder bessere Operationalisierungen zu beheben und keine Aufgabe für IST DEKLARATIV WIRKLICH INSTRUKTIV 8 Anwendungsentwickler. In der Praxis muss man oft mit dem gerade Vorhandenen auskommen. Das heißt nicht selten, statt der kurzen und klaren Formulierung auf eine solche mit trickreichen Umwegen auszuweichen, um ein gestecktes Performanceziel doch noch zu erreichen. Die möglichen Vorteile deklarativen Programmierens werden dadurch gleich in zweierlei Hinsicht konterkariert: (1) Dadurch, dass die Sprachmittel, die zur Beschreibung des Was entworfen wurden, zur Beschreibung des Wie missbraucht werden, wird die Lesbarkeit zerstört. (2) Es wird genau das erreicht, was vermieden werden sollte: Die Beschreibung wird von einer speziellen Implementierung abhängig. Fehlersuche, Debugging, Test Der große Abstand zwischen Beschreibung und Ausführung erschwert die Analyse von Fehlern oder anderen unerwarteten Effekten. Spezielle Tools müssen gebaut und gewartet werden. Außerdem steigt die Wahrscheinlichkeit, dass die Implementierung der beteiligten Software selbst fehlerhaft ist, weil diese relativ komplizierter ist und wegen der höheren Spezialisierung weniger häufiger verwendet wird. Die Fehlersuche setzt ebenso wie die Suche nach Performancekillern oft eine intensive Auseinandersetzung mit der jeweiligen Operationalisierung voraus – also genau das, was der Gebrauch der deklarativen Sprache ersparen sollte. Nur wenn die Wahrscheinlichkeit, dass derartige Probleme auftreten, hinreichend gering ist, rentiert sich die Verwendung einer deklarativen Sprache. Unser Beispiel SQL deckt einen breiten Anwendungsbereich ab, in dem solche speziellen Kenntnisse über die Ausführung nicht benötigt werden. Wenn die Anforderungen hinsichtlich Größe oder Performance ein bestimmtes Maß übersteigen oder bei bestimmten Fehlern, sind jedoch Spezialisten gefordert, die das jeweilige DBMS sehr genau kennen. In der Regel brauchen sie sich aber nur um einen kleinen Teil des Codes oder des Datenmodells zu kümmern, sodass der größte Teil der SQL-Entwickler mit diesen Problemen nicht konfrontiert ist. Wenn das anders wäre, hätte SQL wohl nie die Verbreitung gefunden, die es heute hat. Modellgrenzen Im Zusammenhang mit dem Basismodell wurde bereits auf dessen Grenzen hingewiesen. Wenn das Modell die jeweilige Anwendungsdomäne nur teilweise überdeckt, wird man früher oder später an dessen Grenzen stoßen. Denn Software lebt. Erfolgreiche Anwendungen wachsen und werden erweitert. Beim Erreichen der Grenzen stellt sich die Frage: Wie weiter? Die logisch konsequente Antwort müsste sehr oft lauten: komplette Neuentwicklung auf allgemeinerer Basis. Praktisch ist das nur selten umsetzbar. Als Ausweg wird deshalb häufig versucht, die deklarative Sprache so zu erweitern, dass sie den zusätzlichen Anforderungen entspricht. Diese Erweiterungen werden sehr wahrscheinlich den deklarativen Charakter der Beschreibung zerstören. Es stellt sich die Frage, ob eine solche hybride Sprache überhaupt noch Vorteile bietet. Am Beispiel SQL lässt sich das gut zeigen. Für Anwendungsfälle, die sich nicht mehr im Rahmen des Relationenmodells beschreiben lassen, gibt es prozedurale Erweiterungen (PL – Procedural Language). Sie sind für fast jede Datenbank verfügbar. Die damit verfassten Skripte stehen allerdings nicht in dem Ruf gut wartbar zu sein, da schon bei einfachen IST DEKLARATIV WIRKLICH INSTRUKTIV 9 Dingen Fallen lauern. (Vgl. z. B. für PL/SQL: „Types in PL/SQL can be tricky. … variable assignments and comparisons may not work the way you expect.” [3]) Ein anderes Beispiel für den Umgang mit Modellgrenzen bieten die Build-Werkzeuge Ant und Maven. Beide verfügen über einen Satz vordefinierter Funktionen, mit deren Hilfe ein Build-Verlauf deklarativ beschrieben werden kann. Das ist sehr praktisch, so lange man sich im Rahmen dessen bewegt, was vorgesehen ist. Davon abweichende Logik ist schwieriger zu beschreiben: „All too often, when adding on to a build script, you can't shake the feeling that you're implementing a workaround or hack.“ [4] Gar nicht so selten müssen deshalb zusätzliche Funktionsbausteine (Module, Tasks) implementiert werden, um die Liste der Grundfunktionen zu ergänzen. Abgesehen davon, dass solche Erweiterungen als „Nebenprodukt“ nicht immer alle Qualitätsstandards erfüllen, erhält man in der Konsequenz eine Menge (leicht) unterschiedlicher Modelle. Das ist eine für das Verständnis und die Wartbarkeit der deklarativen Build-Beschreibungen ebenfalls ungünstige Situation. Die verbleibende Unbestimmtheit Ein deklaratives Programm beschreibt die unbedingt notwendigen Eigenschaften der gewünschten Implementierung. Im Allgemeinen ist es nicht möglich, die Vollständigkeit einer Spezifikation zu beweisen. Unterschiedliche Sichten auf das Anwendungsgebiet können zu unterschiedlichen Interpretationen dessen führen, was als vermeintlich unstrittig oder selbstverständlich nicht explizit festgelegt wird. Andererseits kann der Versuch, jedes Detail explizit erfassen zu wollen, zu einer unhandlichen Überspezifikation führen und damit die Wahrscheinlichkeit, dass Fehler passieren, erhöhen. An diesen Problemen ist schon vor mehr als 20 Jahren die automatische Verifikation von Programmen gescheitert. Deren Ziel war es, zu zeigen, dass ein zu prüfender Code, d. h. das Ergebnis der Operationalisierung, der Spezifikation, also der deklarativen Beschreibung entspricht. Obwohl viele technische Probleme gelöst werden konnten, spielt dieses Thema heute nur noch eine untergeordnete Rolle. Die verbleibende Unbestimmtheit kann zu einer Bremse für die Weiterentwicklung werden. Um eine bestehende Code-Basis nicht völlig zu entwerten, muss unter Umständen die Kompatibilität auf die bereits erwähnten impliziten Abhängigkeiten ausgedehnt werden. Radikale Wechsel der Implementierungsstrategie werden somit praktisch ausgeschlossen, selbst wenn sie deutliche Verbesserungen bringen würden. Spezielle Probleme in hybriden Sprachen In der Praxis werden deklarative Ausdrucksmittel häufig als Teil einer prozeduralen Programmiersprache eingesetzt. Die Integration derart unterschiedlicher Betrachtungsweisen ist nicht trivial. Am Beispiel Java/JavaEE zeigt sich, dass damit zwar die Fähigkeiten des Werkzeugs Programmiersprache erweitert werden, aber gleichzeitig die Anforderungen an Disziplin und Verantwortungsbewusstsein bei der Verwendung steigen. IST DEKLARATIV WIRKLICH INSTRUKTIV 10 Verschiedene Idiome Als einer der großen Vorzüge von Java gegenüber C++ galt immer die größere Homogenität der Sprachmittel. Durch die deklarativen Erweiterungen wird dieser Vorsprung etwas kleiner. Dabei muss die Möglichkeit, in unterschiedlichen Idiomen programmieren zu können, nicht automatisch schlecht sein, Solange es gelingt, in Projekten oder Teilen davon, einen Stil konsequent durchzuhalten, ist alles in Ordnung. Die Lambda-Ausdrücke von Java 8 machen es in einigen Bereichen jedoch komplizierter. Das soll am Beispiel der Listener gezeigt werden. Listener sind die in Java übliche Implementation des Beobachter-Designmusters. Beispiel 4 zeigt den bisher üblichen Code für das Hinzufügen zweier Listener (TreeExpansionListener und TreeSelectionListener), die als anonyme Klassen geschrieben werden. JTree tree= new JTree(); tree.addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { handleExpand(); } @Override public void treeCollapsed(TreeExpansionEvent event) { handleCollapse(); } }); tree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent event) { handleSelect(); } }); Beispiel 4: Listener als anonyme Klassen Unter Verwendung von Lambdas lässt sich das kürzer schreiben, allerdings nur für den TreeSelectionListener. Beispiel 5 zeigt die „lambdafied“ Version des Beispielcodes. Das sieht auf den ersten Blick nur unschön aus. Tatsächlich handelt es sich um gänzlich verschiedene Abstraktionen, die beim Betrachten einen Wechsel des „Lesemodus“ erfordern, also zusätzlichen geistigen Aufwand verursachen. Code zu verstehen ist aber bereits schwierig genug, da sollte jede Erschwerung, und sei sie noch so gering, vermieden werden. Natürlich ist es möglich, den Code so zu gestalten, dass solche Wechsel zumindest innerhalb einer Klasse nicht vorkommen. Die verbreiteten IDE bieten Refactoring-Funktionen für beide Umwandlungen an, sodass auch, wenn beispielsweise der TreeExpansionListener erst später dazu kommt, der Code in der Umgebung (Klasse) ohne großen Aufwand angepasst werden kann. IST DEKLARATIV WIRKLICH INSTRUKTIV 11 JTree tree= new JTree(); tree.addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { handleExpand(); } @Override public void treeCollapsed(TreeExpansionEvent event) { handleCollapse(); } }); // Listener in Lmabda-Notation: tree.addTreeSelectionListener(event -> handleSelect()); Beispiel 5: „Lambdafied“ Listener Was steigt sind allerdings die Anforderungen an die fachliche Projektleitung, weil die Entscheidung für ein Idiom weitgehend Ermessenssache ist und die Angemessenheit nicht durch Tools wie PMD automatisch überprüft werden kann. Nur durch regelmäßige und qualifizierte Codereviews kann gesichert werden, dass die neuen Möglichkeiten nicht dazu missbraucht werden schwerer lesbaren Code zu erzeugen. Deklarativ objektorientiert? Das ist zweifellos eine spannende Frage, aber sie kann hier nicht beantwortet werden. Bei der Erweiterung von Java hat man sie auch nicht gestellt, sondern sich ganz pragmatisch auf eine möglichst harmonische Integration der neuen Features in die Implementierung konzentriert. Das ist bemerkenswert gut gelungen, was leider nicht in methodischer Hinsicht gilt. Java 8 erlaubt da Fehler, die bisher nicht möglich waren. Ein Ziel objektorientierter Programmierung besteht darin, den Einfluss, den lokale Änderungen auf die Korrektheit des Gesamtprogramms haben können, zu minimieren. Eine bisher gültige Regel lautet: Wird ein Interface erweitert, so sind davon nur Klassen betroffen, die dieses Interface implementieren, aber nicht diejenigen, die es verwenden. Durch die Einführung von Default-Methodenimplementierungen erlaubt Java 8 nun sogar eine Erweiterung, ohne implementierende Klassen ändern zu müssen. Das geschieht allerdings um den Preis, dass in Klassen, die das Interface verwenden, nachträglich Fehler auftreten können. Die Schuld an diesem kritikwürdigen Umstand trägt eine Entscheidung, die LambdaAusdrücke breiter anwendbar machen soll, nämlich die, alle Interfaces mit genau einer Methode als funktionale Interfaces zu akzeptieren. Die möglichen Auswirkungen seien anhand des im vorangehenden Abschnitt benutzten Beispiels erläutert. Im (hypothetischen) Fall, dass das Interface TreeSelectionListener wie im Beispiel 6 dargestellt, erweitert wird, wäre der Code aus Beispiel 5 (im Unterschied zu dem aus Beispiel 4) nicht mehr korrekt, weil die ursprünglich implizit vorhandene Eigenschaft „funktionales Interface“, die die Voraussetzung für die Verwendung als Lambda-Ausdruck ist, durch die neue, zweite Methode nicht mehr gegeben ist. IST DEKLARATIV WIRKLICH INSTRUKTIV 12 public interface TreeSelectionListener extends EventListener { void valueChanged(TreeSelectionEvent e); /** Hypothetical new method */ default void valueChanged(TreeSelectionEvent e, int clickCount) { // as default ignore } }); Beispiel 6: Erweitertes Interface Das Problem ist leicht zu beheben, indem der Lambda-Ausdruck in eine innere Klasse umgewandelt wird, trotzdem ist so etwas ärgerlich. Man kann, soweit es sich um Code im eigenen Einflussbereich handelt, diesen Fehler vermeiden, indem man für LambdaAusdrücke nur Interfaces verwendet, die mit @FunctionalInterface annotiert sind. Auch das ist wieder eine Frage von Disziplin und Kontrolle. (TreeSelectionListener ist laut API übrigens nicht so annotiert.) Annotationen Die Annotationen haben im Laufe der Java-Entwicklung den größten Bedeutungswandel erlebt. Ursprünglich als Hilfsmittel bei der Dokumentationsgenerierung erdacht, sind sie mit der Zeit zu einem wichtigen Bestandteil der Sprache geworden. Ohne Zweifel sind Annotationen deklarative Sprachelemente mit sehr einfacher Syntax und ohne fest umrissene Abgrenzung möglicher Bedeutungen. Das Letztere ist besonders kritisch zu sehen. Annotationen werden für ganz unterschiedliche Zwecke verwendet, z. B.: • Dokumentation, Hinweise (@Deprecated, …) • Fehlervermeidung, Dokumentation (@Override, …)) • Compilersteuerung (@SuppressWarnings, …)) • Definition neuer Annotationen (@Retention, …)) • Frameworkfunktionen (@Entity, @Inject, …) • .. Das alles in ein und derselben syntaktischen Form. Es macht den Eindruck, dass Annotationen ganz allgemein als Mittel der dynamischen Spracherweiterung dienen müssen. Damit sind sie aber überfordert. So bequem sich das beim Schreiben darstellen mag, so unbequem ist es beim Lesen, weil sich der Inhalt nicht aus der Form erschließt und die jeweilige Bedeutung an ganz unterschiedlichen Stellen definiert sein kann. Ganz generell wird der Nutzen dynamisch erweiterbarer Sprachen in der Softwareentwicklung kritisch gesehen. Die dadurch erreichbare Flexibilität macht die Systeme insgesamt deutlich komplexer zu verwalten. Das gilt uneingeschränkt auch für die leichtfertige Verwendung von Annotationen. In der aktuellen Version JavaEE 7 ist die Grenze offensichtlich bereits überschritten. Man denkt unwillkürlich an Datenbankadministratoren, die beim Anlegen einer neuen Tabelle vorsichtshalber immer gleich ein paar CustomXXSpalten ergänzen (, was ja nicht schlecht sein muss). Um Probleme schnell zu lösen, werden in solche Spalten später dann u. U. schon mal Fremdschlüssel, gern auch auf unterschiedliche Tabellen, eingetragen. So kann man zwar oft einfach und schnell eine Lösung finden, baut sich längerfristig aber eine echte Herausforderung für die Wartung, IST DEKLARATIV WIRKLICH INSTRUKTIV 13 insbesondere den Erhalt der Integrität, auf. Annotationen scheinen auf dem besten Weg, innerhalb JavaEE die Rolle solcher magischen Joker zu übernehmen. In Frameworks werden Annotationen u. a. für die folgenden zwei Aufgaben eingesetzt: 1. Als Kennzeichen, dass ein Objekt, Methode, o. Ä. in einer bestimmten Weise verwendet werden kann oder soll, ohne große Annahmen über seine Umgebung zu machen, z. B. @Entity, @WebService 2. Als Kennzeichen wie 1, aber unter der Voraussetzung, dass in der Ausführungsumgebung (Container) bestimmte Bedingungen erfüllt sind, z. B. @Inject Im ersten Fall handelt es sich gewissermaßen nur um ein Angebot, mit Hilfe von Tools beispielsweise ergänzende Funktionalität zu generieren, d. h., die Annotationen haben aus Sicht des Java-Programms informierenden Charakter. Das ist eine originär deklarative und angemessene Funktion. Der zweite Fall ist der kritischere. Die Gesamtheit aller derartigen Annotationen stellt eine verteilte (teilweise) Spezifikation des globalen Zustands dar. Quasi beiläufig hat man sich ein ernsthaftes Problem eingehandelt, zu dessen Beherrschung angemessene Werkzeuge fehlen. Verteilte Spezifikationen werfen immer die Frage auf, wie die globale Konsistenz gewährleistet werden kann. Bei Annotationen ist das im Allgemeinen zur Entwurfszeit nicht möglich. Beispiel 7 zeigt einen kleinen Ausschnitt aus einer Bean-Anwendung, die mittels Contextund Dependency-Injection (CDI) initialisiert werden soll. Das Feld service ist durch die @Inject-Annotation als sogenannter Injection-Point deklariert worden. Service muss ein Interface sein. Bei der Erzeugung von ServiceClient-Objekten wird durch die Laufzeitumgebung (Container) ein entsprechendes, den Service implementierendes Objekt gesucht und dem Feld zugewiesen. Diese Injection funktioniert aber nur dann in der beschriebenen Form, wenn es genau eine Bean-Klasse gibt, die das Interface Service implementiert. Da eine Anwendung aus mehreren Bean-Bibliotheken zusammengesetzt sein kann, lässt sich diese Bedingung frühestens beim Deployment verifizieren. Im Klartext heißt das: Allein durch das Zufügen einer Bean, die ein bestimmtes Interface (ein zweites Mal) implementiert, kann die Lauffähigkeit einer Anwendung zerstört werden. Das steht im totalen Widerspruch zur Intention von Interfaces, die ja genau diese Kopplung von Definition und Implementierung aufbrechen sollen. Es gibt zwar Möglichkeiten, die Einschränkung auf genau eine Implementierung zu umgehen, aber am grundsätzlichen Problem ändert sich nichts, weil dabei nur, z. B. durch ergänzende Qualifier-Annotationen, eine engere Eingrenzung erfolgt, die letztlich aber ebenfalls wieder genau eine Klasse liefern muss. public class ServiceClient { @Inject private Service service; … } Beispiel 7: Context-Injection IST DEKLARATIV WIRKLICH INSTRUKTIV 14 Man sollte Annotationen als das verwenden, was sie ihrem Namen nach sind: Anmerkungen, ergänzende Hinweise, die das Verständnis des annotierten Programmtexts für den Leser, den Compiler, die JVM oder andere Tools erleichtern. 2 Als Ersatz für fehlende Methoden und Werkzeuge zum Modul- und Konfigurationsmanagement beispielsweise, sind sie fehl am Platz. Annotationen sind ein exzellentes Beispiel für den Fall, dass eine einfache Lösung für ein einfaches Problem so erfolgreich ist, dass diese einfache Lösung danach so lange auf immer größere Probleme angewandt wird, bis sie selbst zum Problem geworden ist. In dieser Hinsicht gibt es verblüffende Parallelen zur Sprunganweisung (Goto). Aus gutem Grund hat Letztere aber nur in gezügelter (break, continue, return) bzw. impliziter (if, for, …) Form in Java Eingang gefunden. (Für alle Fälle darf goto trotzdem nicht als Bezeichner verwendet werden.) Kurz zusammengefasst: Annotationen sind ein problematischer Bestandteil von Java. Das liegt, abgesehen von der durch die primitive Form verursachten schlechten Erfassbarkeit, allerdings weniger an ihrem deklarativen Charakter als an der ungezügelten Verwendbarkeit. Schlussfolgerungen Auch wenn bisher vorrangig die problematischen Seiten thematisiert wurden, ist deklarative Programmierung nicht prinzipiell abzulehnen. Für den sinnvollen Einsatz müssen aber einige Voraussetzungen erfüllt sein. Eine geeignete Aufgabe Wenn es um ein Verfahren selbst geht, das beschrieben werden soll, ist der deklarative Weg ungeeignet. Prozedurale Programmiersprachen sind aus dem Bemühen heraus entstanden, Algorithmen exakt formulieren zu können. Man brauchte diese Ergänzung der viel älteren deklarativen mathematischen Formalismen einfach für die Steuerung von Computern. Deklarative Programmierung abstrahiert u. a. von der Zeit. Reale Aufgaben sind jedoch immer in einen zeitlichen Ablauf eingebunden. So werden SQL-Statements z. B. in einer gewissen Reihenfolge ausgeführt. Die Zeit wird dabei gewissermaßen diskretisiert zu Zeitpunkten vor und nach der Ausführung, der Zwischenzeitraum ignoriert. Daraus kann man folgern, dass geeignete Aufgaben immer nur Teile der Gesamtaufgabe seien können, die in ein umfassenderes prozedurales Programm eingebettet sind. (Im Beispiel ist dieses prozedurale Programm einfach die Nacheinanderausführung, im Fall einer HTML-Seite die Einbettung in den Request-Response-Zyklus.) 2 So wird es auch in der Java-Sprachspezifikation beschrieben: An annotation is a marker which associates information with a program construct, but has no effect at run time. [6] Ob die Veränderung des auszuführenden Codes beim Laden oder erstmaligen Ausführen ein Effekt auf die Laufzeit ist, mag im Detail kontrovers sein. Klar ist, dass man damit zumindest in den Grenzbereich gerät. IST DEKLARATIV WIRKLICH INSTRUKTIV 15 Hinreichende Bekanntheit des Basismodells Nur wenn das semantische Modell ausreichend präzise definiert und in dieser Definition bekannt und verstanden ist, kann ein darauf basierender deklarativer Formalismus sinnvoll für die Beschreibung von Programmieraufgaben eingesetzt werden. Das ist vorrangig ein Problem der generischen Anwendung, wenn durch Abstraktionen wie Prozeduren, Funktionen, Lambdas u. Ä. implizit neue Modelle entstehen. Deren Semantik wird vor allem durch die Namen transportiert, die deshalb besonders sorgfältig gewählt werden müssen. Vorsicht ist geboten, wenn Konzepte zu kompliziert oder zu innovativ sind, um mit einer Bezeichnung treffend charakterisiert werden zu können. Existenz von Verfahren zur Operationalisierung Deklarative Programmierung kann erfolgversprechend nur für Domänen und Modelle eingesetzt werden, für die hinreichend allgemeine Algorithmen für die effiziente Operationalisierung existieren. Sobald es notwendig ist, die deklarativen Ausdrücke um Hinweise für die Operationalisierung zu ergänzen, muss gefragt werden, ob ein prozeduraler Ansatz nicht geeigneter wäre. Die Existenz der Verfahren ist auf die Dauer nicht hinreichend. Da Projekte bekanntlich wachsen, sind Skalierbarkeit und Interoperationalität ebenfalls wichtige Faktoren. Insbesondere die bei der Operationalisierung entstehenden wechselseitigen und zeitlichen Abhängigkeiten bergen das Potential überproportional schnell zuzunehmen. Dass diese Gefahr auch außerhalb der IT besteht und schnell alle anderen in den Hintergrund rücken kann, zeigt der Blick auf einige aktuelle Großbaustellen. (Bauen ist ein lange praktiziertes Paradebeispiel für deklarative Programmierung in der materiellen Welt. Es illustriert ebenso gut die Probleme, die bei der Operationalisierung, d. h. der Bauausführung auftreten können.) Kombinierbarkeit mit prozeduralen Ansätzen Wegen der unvermeidbaren Beschränkungen sind klare Schnittstellen wichtig. Anforderungen, die nicht im modellierten Bereich liegen, können dann sauber abgetrennt beschrieben werden. Allerdings müssen sich die zu lösenden Aufgaben sinnvoll in entsprechende Teile zerlegen lassen, um häufige Wechsel der Beschreibungsebene zu vermeiden. Der Umgang mit Grenzen der deklarativen Beschreibung muss von Anfang an Bestandteil des Entwurfs sein, um der späteren Degeneration des Formalismus vorzubeugen. Zusammenfassung Wenn diese prinzipiellen Beschränkungen beachtet werden, ein kommunizierbares Basismodell existiert und ausreichend viele Anwendungsfälle vollständig innerhalb dieses Modells formalisiert werden können, ist das deklarative Paradigma ein nützliches Werkzeug. Wenn diese Voraussetzungen nicht erfüllt sind, ist es ein ziemlich sicherer Weg in die Probleme, die man eigentlich vermeiden wollte. Das bedeutet speziell für die eingangs erwähnten Java-Erweiterungen, dass sie sorgsam und nicht nur unter dem Gesichtspunkt kompakteren Codes verwendet werden sollten. Ein IST DEKLARATIV WIRKLICH INSTRUKTIV 16 Lambda-Ausdruck verbessert die Lesbarkeit nur dann, wenn seine Bedeutung unmittelbar einsichtig ist. Andernfalls ist die explizite Schreibweise instruktiver. Besonders groß ist die Gefahr der deklarativen Überfrachtung bei Annotationen. Sie machen es sehr einfach, verteilte Spezifikationen ohne abgegrenztes semantisches Modell zu kreieren, was vor allem für die Wartung enorme Risiken schafft. Leider scheint diese Erkenntnis angesichts der technischen Machbarkeit3 bei vielen Entwicklern von Frameworks übersehen zu werden. Schließlich sei noch daran erinnert, dass John Backus bereits 1977 in einer vielbeachteten Rede [5] leidenschaftlich dafür plädiert hat, den prozeduralen Programmierstil zu überwinden. Dieser Appell ist nicht ungehört geblieben und hat erheblichen Einfluss auf die Forschung gehabt. 35 Jahre später sieht man kleine Schritte in dieser Richtung wie die Lambdas in Java, aber es ist auch klar geworden, dass andere Paradigmen andere Probleme verursachen. Es gibt neue, oft spezialisiertere Werkzeuge, die - richtig angewandt - dabei helfen, die immer größer werdenden Aufgaben zu bewältigen. Deklarative Programmierung ist ein Werkzeug in diesem Kasten. 3 Die Möglichkeiten sind tatsächlich fast unbegrenzt. Man kann nicht nur, wie bereits angewandt, Methodenaufrufe in zusätzlichen Code einbetten. Es ist durchaus machbar, Programmteile selbst in Form von Annotationen zu schreiben, und diese dann noch auf unterschiedliche Weise ausführen zu lassen. – Was heute noch sinnlos erscheint, wird früher oder später gemacht werden, einfach weil es gemacht werden kann. Der Geist ist aus der Flasche und wird sich nicht leicht wieder einfangen lassen. IST DEKLARATIV WIRKLICH INSTRUKTIV 17 Literaturverzeichnis [1] [2] [3] [4] [5] [6] https://existentialtype.wordpress.com/2013/07/18/what-if-anything-is-a-declarativelanguage/ http://support.microsoft.com/kb/186133/de http://infolab.stanford.edu/~ullman/fcdb/oracle/or-plsql.html http://www.drdobbs.com/jvm/why-build-your-java-projects-with-gradle/240168608 John Backus: Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs. In: Communications of the ACM. Vol. 21, No. 8, August 1978, S. 613–641 The Java Language Specification,. Abschn. 9.7, http://docs.oracle.com/javase/specs/jls/se8/ Der Autor Dr. Jürgen Lampe ist IT-Berater bei der Agon Solutions in Frankfurt. Seit mehr als 15 Jahren befasst er sich mit Design und Implementierung von Java-Anwendungen im Bankenumfeld. Sein Interesse gilt besonders Fachsprachen (DSL) und Werkzeugen für deren Implementierung. Email: [email protected] Agon Solutions Die Agon Solutions, 2004 gegründet, ist ein dynamisches und erfolgreiches ITDienstleistungsunternehmen mit Firmensitz in Eschborn bei Frankfurt und weiteren Standorten in Hamburg und Berlin. Das branchenübergreifende Dienstleistungsportfolio von Agon Solutions umfasst das „Agon IT-Consulting“, eine bewährte, herstellerneutrale ITBeratung; sowie die „Agon IT-Solutions“, zu denen maßgeschneiderte, individuelle Softwareentwicklung und passgenaue Softwareintegration gehören. Agon Solutions stellt sich individuell auf seine Kunden ein und setzt auf professionelles Projektmanagement, proaktives Anforderungsmanagement sowie änderbare Softwarearchitekturen („Design for Change“) – für Ergebnisse von besonders hoher Qualität. Die Stärken liegen in der Integration und Migration von Systemen, der Vernetzung von Mainframe-, Java-Backendund Web-Frontend-Anwendungen und der Architekturberatung von Software-Landschaften. In ausgewählten Branchen wie Banken, Versicherungen, Touristik/Aviation und Health Care bietet Agon Solutions gemeinsam mit seinen Partnern Lösungen, die auf fundiertem Geschäftsprozess-Know-how beruhen und speziell auf die jeweilige Branche zugeschnitten sind: die „Agon Business Solutions“. Die plattformübergreifende technologische Kompetenz bei Agon Solutions reicht von klassischen Mainframe-Anwendungen bis hin zu modernen Java/JEE Web- und Portal-Architekturen. Zu den Referenzkunden von Agon Solutions IST DEKLARATIV WIRKLICH INSTRUKTIV 18 gehören unter anderen die die Commerzbank, die BMW Bank - Segment Financial Service, die Deutsche Börse, die Finanz Informatik, die Provinzial Nordwest, die Deutsche Lufthansa, die Thomas Cook und die AOK Berlin-Brandenburg. Copyright: A:gon Solutions GmbH Frankfurter Strasse 71-75 D-65760 Eschborn Telefon : +49 6196 80269 0 Telefax : +49 6196 80269 11 http://www.agon-solutions.de Handelsregister Frankfurt HRB 58185 St.-Nr. 4022826171 Geschäftsführer: Udo Peters IST DEKLARATIV WIRKLICH INSTRUKTIV 19