Ich gehöre zu denjenigen, die sich gerne immer mal wieder eine neue Programmiersprache ansehen. Nicht um in jeder dieser Programmiersprachen letztendlich auch produktiv zu arbeiten, aber um mal „übern Tellerrand zu gucken“.
Das Ganze ist nicht nur interessant, sondern hat auch den Vorteil, dass man sehr viel über unterschiedliche Herangehensweisen und Programmierkulturen erfährt. Die einzelnen Sprachen unterscheiden sich nicht nur in der Syntax, sondern auch in der Denkweise, wie Probleme gelöst bzw. Code geschrieben wird. Sowas ist manchmal augenöffnend und kann auch mal helfen, kreative Lösungen für schwierige Probleme zu finden, die man in „seiner“ Programmiersprache hat.
Das heißt natürlich nicht, dass man anfangen sollte, die eigene Sprachkur zu missachten. Im Gegenteil: Manchmal ist es sinnvoller, eine schlechte, aber allseits bekannte Lösung zu verwenden, als eine elegante, die für andere Entwickler „fremdartig“ wirkt. Code schreibt man i.d.R. ja nicht nur für sich selbst, sondern auch für die anderen Entwickler im Team, etc.
Aber für schwierige, nicht-alltägliche Probleme kann man auch mal eine kreative Lösung gebrauchen. Und dafür ist es ganz gut, wenn man mit anderen Sprachen und Programmierkulturen in Kontakt kommt. Außerdem ist es mit Programmiersprachkulturen ähnlich wie mit natürlichen Sprachen: Sie verändern und beeinflussen sich gegenseitig und das ist eine Bereicherung. [1]
Ich bin nicht der einzige, der so denkt. In „The Pragmatic Programmer“ [2] geben Andy Hunt und Dave Thomas den Rat: „Learn at least one new language every year.“ Und da komm ich sogar in etwa hin. Die meisten davon kann ich nicht (mehr) wirklich gut. Wirklich produktiv einsetzen kann ich momentan davon nur ne Hand voll. Aber darum gehts ja auch nicht.
Nun, ich hab mich jetzt ein bisschen mit Eiffel beschäftigt. Aus Zeitgründen leider nur oberflächlich. So hab ich praktisch keinen echten Code geschrieben sondern nur Tutorials und Code gelesen. Das bedeutet natürlich, dass viel von der Syntax bald wieder verflogen sein wird. Aber wie gesagt: Darum gehts mir gar nicht.
Ich hab also einen Eindruck von Eiffel bekommen. Um jetzt mehr als ein Bauchgefühl vermitteln zu können, müsste ich wohl wirklich mal ein paar hundert Zeilen Code schreiben. Aber dafür hab ich momentan keine Zeit. Hier also nur mal ein erster Eindruck.
Eiffel — Die Syntax
Esperanto
Wenn man sich die Syntax von Eiffel betrachtet, so fallen einem zwei Dinge auf: Zum einen steht hinter Eiffel die Idee, eine möglichst einfache Syntax zu haben und nicht darauf Rücksicht zu nehmen „was die anderen alle so tun“. Das geht so weit, dass es in Eiffel nicht die altbekannten for-, while- und repeat-Schleifen gibt. Stattdessen gibt es als einzige Schleifenart eine Abwandlung der for-Schleife: from..until..loop..end. Das sieht in etwa so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | make -- Run application. local i: INTEGER do from i := 0 until i >= 10 loop print(i) i := i + 1 end end |
Eiffel versucht alle möglichen Sprachfeatures erstmal wegzulassen. Es gibt auch kein switch/case und keine separate Syntax für Arrays oder records/structs. Jeder Typ ist eine Klasse und jeder Wert ein Objekt. [3] Und mehr noch: Bezeichner sind standardisiert. Ob man etwas in eine Queue stellt, auf einen Stack legt, in ne Liste packt oder in ner HashMap speichert: Jedes Mal heißt die zugehörige Operation (in Eiffel-Sprech „Feature“) put
. Einfach, aber… ungewohnt. Meiner Meinung nach ist es aber für die Lesbarkeit vorteilhaft, wenn man Bezeichner hat, die zur zugehörigen Metapher passen(z.B. enqueue
statt put
). Das macht Eiffel quasi zu dem was ich in der Überschrift als Resümee angegeben habe: Eiffel ist eine Art Esperanto unter den Programmiersprachen. Wenige sprechen es, aber Grammatik und Vokalar sind künstlich einfach gehalten.
Ich will nicht sagen, dass das schlecht ist. Es ist nur… ungewohnt. Und irgendwie überkommt mich das Gefühl, dass ich, obwohl ich while, do..while/repeat und switch/case nicht allzu häufig verwende, diese Konstrukte doch manchmal sinnvoll finde. Eiffel ist einfach anders. Wie Esperanto eben. [4]
Design by Contract und (kaum) Exceptions
Das zweite, was auffällt ist Design By Contract (DBC). DBC wird in Eiffel ausgiebig genutzt (stammt ja auch da her) und durchzieht den gesamten Entwicklungsprozess.
Wenn ich also aus meinem kurzen Eiffel-Ausflug etwas mitnehme, dann ist es vermutlich, wie DBC dort eingesetzt wird. DBC ist dort wirklich zentral. Zentraler als für jede andere Sprache, bei der DBC nachträglich dazu kommt. Das liegt wohl daran, dass DBC dort einfach zur Sprachkultur gehört.
Das bringt es mit sich, dass bestimmte Dinge ganz anders angegangen werden. Exceptions, die für andere Sprachen (ganz vorne dabei: Java) eine große Rolle spielen, werden in Eiffel viel seltener verwendet. Zuerst musste ich überhaupt mal herausfinden, ob es dort überhaupt so etwas wie Exceptions gibt. Exceptions sind in Eiffel sowas wie eine Ultima Ratio.
Zuerstmal wird versucht, ohne Exceptions auszukommen. Fehlercodes gibts aber auch nicht (das würde dem Command-Query-Separation-Prinzip widersprechen). Stattdessen muss man nach einer Operation ggf. überprüfen, ob sie geklappt hat. Das ist also kein besonderer Vorteil gegenüber Fehlercodes. Exceptions trennen hier eigentlich schön den Normalfall von den Ausnahmefällen. Das ist hier nicht immer der Fall. In Eiffel heißt es „Das ist zwar nicht der Normalfall, aber normaler Kontrollfluss und sollte deshalb mit normalen Kontrollstrukturen behandelt werden. Nur wenns wirklich nicht anders geht, werden Exceptions verwendet. Ich bin da bisher noch nicht so überzeugt davon, dass das gut ist. Bisher finde ich Exceptions äußerst hilfreich.
Ein Beispiel zu was das führt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | failed := False if host.item (1) = '[' then if host.count > 2 and then host.item(host.count) = ']' then host := host.substring (2, host.count - 1) ipv6_expected := True; else -- This was supposed to be a IPv6 address, but it's not! -- TODO report error failed := True end end if not failed then if host.item(1).is_hexa_digit or else host.item(1) = ':' then addr_array := text_to_numeric_format_v4 (host) if addr_array = Void then ... elseif ipv6_expected then -- Means an IPv4 litteral between brackets! -- TODO throw new UnknownHostException("["+host+"]"); -- TODO report error failed := True end if not failed then ... end elseif ipv6_expected then -- TODO We were expecting an IPv6 Litteral, but got something else -- throw new UnknownHostException("["+host+"]"); -- TODO report error failed := True Result := Void end if Result = Void and then not failed then Result := get_all_by_name_0 (host) end end |
Ich kommentiere das mal nicht und lasse die Kommentare der Entwickler für sich sprechen…
Das ist also der Weg, wie man Fehler anfängt, die man nicht verhindern kann. Das ist der Teil, der mir nicht so gefällt. Für Fehler, die nicht auftreten sollen, wird DBC verwendet. Und das ist der Teil, der mir — zumindest auf den ersten Blick — zusagt. Vorbedingungen, Nachbedingungen und Invarianten werden explizit formuliert und zur Laufzeit geprüft. Das ist schöner als ständig selbst RuntimeExceptions werfen zu müssen. Dadurch, dass das Ganze in die Sprache integriert ist, wird man gezwungen, sich über Randfälle Gedanken zu machen. Auch interessant: Dadurch kann man Tests automatisieren. Also nicht nur das Durchführen automatisieren, sondern tatsächlich auch das Schreiben der Testfälle. Durch Vor- und Nachbedingungen sind schon genügend Informationen vorhanden um gewisse Testdaten und erwartete Ergebnisse abzuleiten.
Vererbung
Vererbung wird in Eiffel deutlich häufiger verwendet, als man das von anderen Sprachen gewohnt ist. Oder anders: Wie man es bei anderen nur von Leuten gewohnt ist, die nicht wissen, wie man Vererbung richtig einsetzt. Das wirkt… merkwürdig. Das heißt nicht, dass die Eiffel-Entwickler von Vererbung keine Ahnung haben. Bertrand Meyer ist niemand dem man nachsagen müsste, er hätte keine Ahnung von Objektorientierung.
Verschiedene Sprachkulturen bewerten bestimmte Prinzipien unterschiedlich. Für Eiffel ist Command-Query-Separation (CQS) beispielsweise sehr wichtig. Ein Feature (d.h. eine Methode) ist entweder eine Anfrage an das Objekt, das keinerlei Seiteneffekte hat (Query) oder ein Auftrag ohne Rückgabewert (Command). Deshalb Funktionieren in Eiffel Stacks nicht mit push
und pop
, wie man das gewohnt ist, sondern mit put
, top
und remove
. Ich wäre hier weniger dogmatisch, aber die Eiffel-Philosophie ist hier anders.
Auf der anderen Seite wird in Eiffel das Liskovsche Substitutionsprinzip (LSP) IMHO eher stiefmütterlich behandelt. Vererbungshierarchien sollten eine is-a-Relation (oder genauer: is-substitutable-for) aufbauen. In Eiffel ist das oft nicht der Fall. Beispielsweise ist in Eiffel jedes ARRAY
eine TABLE
und jede TABLE
eine BAG
. [5] Normalerweise lernt man, dass man aus gutem Grund Sichtbarkeiten von Methoden nur erhöhen, nicht aber verringern kann. In Eiffel ist das anders. Hier kann man auch Sichtbarkeiten verringern. und genau das passiert hier. BAG
definiert ein Feature put
über das man Elemente hinzufügen kann. In TABLE
wird das Feature quasi entfernt (die Implementierung geleert und private gemacht). Wirklich weg ist es aber natürlich nicht. Man kann immer noch drauf zugreifen:
1 2 3 4 5 6 7 8 9 10 11 12 | local a: ARRAY[INTEGER] b: BAG[INTEGER] do create a.make_filled(42, 0, 3) a.put(12, 0) print(a) b := a b.put (77) print(b) end |
b.put(77)
wirft eine PRECONDITION_VIOLATION
-Exception. So etwas sollte eigentlich besser nicht möglich sein. Das LSP wurde verletzt. Eiffel-Entwickler stört das wohl nicht so.
Weiteres
Genug an der Eiffel-Syntax gemeckert, es gibt auch einiges, was ich sehr interessant und auch für andere Sprachen wünschenswert finde. DBC hab ich ja schon erwähnt. Sehr interessant finde ich auch noch, wie Eiffel mit Sichtbarkeiten umgeht. Es gibt nicht die altbekannten Kategorien private, protected und public. Stattdessen gruppiert man Features semantisch und gibt jeder Feature-Gruppe eine Liste von Klassen, die darauf zugreifen können. {ANY}
bedeutet, dass jeder drauf zugreifen kann (also public; ANY
ist sowas wie Object in Java/.NET bzw. TObject in Delphi) und {NONE}
bedeutet, dass niemand drauf zugreifen kann (private). Man kann aber auch ne Liste von konkreten Klassen angeben.
Auch sehr schon ist das once
-Schlüsselwort, mit dem man leicht Lazy Initialization implementieren kann. Der Code wird nur einmal ausgeführt und das Ergebnis ebenfalls für weitere Calls benutzt. So gibt es noch ein paar interessante Sprachfeatures, die auch in anderen Sprachen sinnvoll wären. Und vieles wäre es noch wert, dass ich ein Wort drüber verliere. Aber vielleicht guckt man sich das besser selbst an.
Eiffel als Softwareentwicklungs-Methode
Aber Eiffel begreift sich nicht nur als Programmiersprache, sondern als Methode, die sich von Analyse, über Design und Implementierung bis zum Test durchgängig verwenden lässt. Der Code soll für verschiedenste Stakeholder der Dreh- und Angelpunkt sein (also auch für den Kunden, der nicht programmieren kann) und außer dem Code selbst gibt es keine Dokumentation.
Um das zu bewerkstelligen gibt es den „Contract-View“ in der IDE EiffelStudio, der die Implementierung ausblendet und nur noch Kommentare und den Vertrag anzeigt. Außerdem lässt sich, ähnlich wie bei JavaDoc/Doxygen, eine Dokumentation aus Signaturen und Kommentaren generieren, die ebenfalls den Contract-View darstellt. Das ist hilfreich, keine Frage. Aber ob das reicht?
Zum zweiten gibt es die Möglichkeit aus dem Code Diagramme zu erzeugen. Statt UML wird hier BON verwendet. Die ist in der Tat deutlich einfacher als UML, hat aber auch ziemliche Einschränkungen. Viele Notationselemente, die in der UML helfen, ein hilfreiches Abstraktionsniveau zu erhalten, fehlen hier. Und die Client-Suppilier-Pfeile zeigen, anders als die Assoziationspfeile aus der UML, nicht immer in Richtung der Abhängigkeit. Zwei Punkte, die die Notation meiner Meinung nach deutlich im Wert sinken lassen.
Eiffel als Methode scheint also fast eine agile zu sein. Zumindest was Dokumentation und die Kritik am Wasserfall angeht. Ob das die Eiffel-Entwickler auch so sehen, hab ich auf die Schnelle nicht heraus gefunden.
Eiffel aus der Eiffel-Sicht
Aus Sicht derer, die Eiffel verwenden ist Eiffel eine einfache, saubere und klare Sprache, die sich strickt an gewisse Prinzipien hält. Es wird Wert darauf gelegt, dass alles seine Begründung in Prinzipien des Software-Engineering hat. Besonders deutlich sieht man das Selbstverständnis in diesen Audio-Vorträgen, die C#, Java, Delphi und UML mit Eiffel vergleichen. Die Vergleiche gehen IMHO teilweise etwas an der Realität vorbei. So wird an Delphi tatsächlich das Dangline-Else-Problem kritisiert. In der Praxis spielt das kaum eine Rolle (jeder benutzt einfach begin..end) und wenn ich an der Delphi-Syntax etwas kritisieren wollte, dann gibts da ganz andere Stellen. Trotzdem sind die Präsentationen ganz interessant.
EiffelStudio
Die Eiffel-IDE „EiffelStudio“ hab ich mir nur marginal betrachtet. Auf den ersten Blick wirkt sie, wie viele IDEs, etwas überladen, aber letztendlich findet man sich doch zurecht. Die Features scheinen einer modernen IDE angemessen, was Refactoring angeht, sind sie aber noch etwas dürftig. Daneben gibts noch diverse Eiffel-spezifische Features wie den Contract-View.
Was etwas gewöhnungsbedürftig (und nicht unbedingt besser als normale Interaktionsmechanismen) ist, ist „Pick-and-Drop“. Das ist eine Variante von Drag-and-Drop, bei der man im Kontextmenü erstmal „Pick“ auswählen muss und dafür aber nicht die Maustaste gedrückt halten muss. Naja… das Gedrückthalten der Maustaste hab ich jetzt bisher nicht als großen Nachteil gesehen, aber, dass ich jedes mal übers Kontextmenü gehen muss, find ich unpraktisch.
Und es wird noch lustiger: Pick-and-Drop geht auch mit Buttons. Und man wird teilweise gezwungen, das zu benutzen. Um in der Diagrammansicht ein oder mehrere Elemente zu entfernen, gibt es einen Button. Man kann aber nicht einfach ein paar Elemente auswählen und auf den Button drücken. Wenn man das tut, erscheint ein Fenster das einen anweist, doch lieber Pick-and-Drop mit den Elementen auf den Button zu machen…
Compiler
Zum Schluss noch ein Wort zum Kompilieren: Damit das Kompilieren auch schnell funktioniert, setzt Eiffel auf eine spezielle Technologie unter dem Markennamen „Melting Ice“. Statt den Eiffel-Code in C-Code zu kompilieren und den dann in Maschinencode zu übersetzen, wird teilweise auf ner internen Repräsentation als Abstract-Syntax-Tree gearbeitet. Das bringt einiges an Geschwindigkeit.
Man kann es auch mit weniger Marketing ausdrücken: Der eigentliche Kompilierprozess ist dermaßen langsam, dass man zu solchen Tricks greifen muss, damit die Tastatur nicht anfängt, Staub anzusetzen.
Außerdem sind die generierten Executables exorbitant groß: Ein Konsolen-HelloWorld kommt auf 22MB. Durch Optimierung kommen wir bis auf 1,2MB runter. Naja… einigermaßen akzeptabel.
Fazit
Nun, was halte ich jetzt von Eiffel? Eiffel ist eine interessante Sprache, die einige sehr schöne Features bereit hält, anderes aber einfach anders und dabei manches IMHO nicht unbedingt besser macht. Eiffel ist nicht besonders schön, aber auch nicht besonders hässlich. Ob ich Eiffel produktiv einsetzen würde, weiß ich nicht. Aber ich hab einiges über DBC gelernt (mehr, als ich hier darstellen konnte). Eiffel ist also auf jeden Fall einen Block wert, auch, wenns nur einer übern Tellerrand ist.
[1] Es gibt Leute, die behaupten, dass es Wörter wie „nichtsdestotrotz“ und „letztendlich“ nicht gibt (mein ehemaliger Deutschlehrer beispielsweise). Sprache verändert sich. Und Sprache definiert sich durch Benutzung, nicht durch zugestaubte Bücher. Es mag also sein, dass es diese Wörter nicht immer gab. Jetzt gibt es sie.
[2] Ein Buch, das ich nur empfehlen kann und über das ich unbedingt mal bloggen muss.
[3] Wobei ich das gut finde. In C# ist das auch so. Und in Ruby. OK, nicht ganz. Aber fast. Ich finde das jedenfalls sehr angenehm.
[4] Auch, wenn ich zugegebenermaßen keine Ahnung von Esperanto hab. Vielleicht guck ichs mir auch mal an…
[5] Ich hab jetzt nicht lang für Beispiele gesucht. Das war das erstbeste, was ich gefunden hab.
Permalink
Eiffel ist in jedem Fall interessant. Mir persönlich gefällt OOP überhaupt nicht, aber Eiffel scheint dies durchaus annehmbar umgesetzt zu haben (Cpp und Java sind meiner Ansicht einfach schrecklich und entstellen OOP vom Grundgedanken her).
Ich denke, der größte Kritikpunkt an Eiffel ist die Lizenzierung: Die kommerzielle Nutzung ist exorbitant teuer; sehr viele Unternehmen erwogen Eiffel statt Java, entschieden sich jedoch letztlich wegen der Kosten dagegen. Bertrand Meyer will sich eben seine Ideen horrend bezahlen lassen…
Erschwerend kommt hinzu, dass Eiffel ansonsten nur unter der GPL verfügbar ist. Copyleft geht einfach gar nicht und jegliche GPL-lizenzierte Software ist langfristig – zu recht – zum Scheitern verurteilt, weil kein ernsthafter Entwickler das Zeug anfassen wird, da er andernfalls seine Eigenentwicklung ebenfalls unter die GPL stellen muss.
Mit einer BSD-artigen Lizenz für feie Software und wesentlich geringeren Gebühr bei kommerzieller Verwertung wäre Eiffel vielfach verbreiteter heute.
Permalink
Deine Kritik an der GPL teile ich nicht. Guck dir die schiere Anzahl an GPL-Software an. Alleine schon mit Linux verdienen etliche Entwickler ihr Geld. Copyleft funktioniert. Klar, das kann man nicht in jedem Fall gebrauchen. Unterschiedliche Copyleft-Lizenzen lassen sich nicht kombinieren und wenn du damit Shrinkwrap-Software schreiben willst, brauchst du ein angepasstes Geschäftsmodell. Für unternehmensinternes Zeug sehe ich aber keine sonderlichen Hindernisse.
Den eigentlichen Hinderungsgrund, Eiffel produktiv einzusetzen, sehe ich wo anders: Die Verbreitung ist zu gering. Nachbesetzung von Stellen ist also noch schwieriger als eh schon. Wie groß das Ökosystem hinter Eiffel ist, kann ich nicht beurteilen, aber das ist beispielsweise etwas, das bei Java großartig ist. Das Tooling ist (im Gegensatz zu dem von vielen anderen Sprachen) sehr ausgereift, die Community ist riesig, es gibt überall Konferenzen und User Groups, etc.