Die quantitativen Ergebnisse aller drei Implementationen zuerst einmal im Vergleich:
Java
- 5 Fachklassen, 1 Exception-Klasse, 1 Testklasse
- 37 Methoden inkl. Konstruktoren + 10 Methoden in Testklasse
- 246 LOC operativ + 144 LOC für Testklasse (inkl. Leerzeilen)
- 4 For-Schleifen, 4 If-Abfragen
Groovy
- 4 Fachklassen, 1 Exception-Klasse, 1 Testklasse
- 30 Methoden inkl. Konstruktoren + 9 Methoden in Testklasse
- 203 LOC operativ + 123 LOC für Testklasse (inkl. Leerzeilen)
- Alle 4 For-Schleifen der Java-Lösung wurden durch Closures ersetzt, die 4 If-Abfragen blieben bestehen.
Common Lisp
- 4 Fachklassen, 1 Testklasse - Exceptions werden mit Hilfe von Conditions modelliert (nur eine Zeile notwendig)
- 17 Methoden, 1 Condition-Funktion, 10 Testmethoden
- 76 LoC operativ, 100 LoC Test (inkl. Leerzeilen)
- Alle 4 Schleifen der Java-Lösung wurden durch Closures ersetzt, die 4 If-Abfragen blieben bestehen, allerdings in den spezialisierten Varianten when und unless.
Wow! Die Common-Lisp-Variante hat erheblich weniger Code als die Java- oder die Groovy-Variante. Woran liegt das?
Naheliegend wäre es, die Ursache bei dem Lisp-Feature zu suchen, dass keine andere Programmiersprache anbietet: Macros (Vorsicht: Lisp-Makros sind ganz anders als die berüchtigten C-Makros). Dem ist aber nicht so. Tatsächlich habe ich in dem Lisp-Code kein eigenes Makro selbst definiert und nur an einer Stelle von der Mächtigkeit der
Lisp-Makros profitiert. Mit assert-error aus Lisp-Unit kann man sehr einfach prüfen, ob eine Exception (im Lisp-Jargon Condition) geworfen wird:
(define-test benutzer-nicht-eingeladen-exception
(setup)
(let ((termin (make-termin stefans-kalender ein-datum 180 "TDD-Dojo")))
(assert-error 'benutzer-nicht-eingeladen (lehne-termin-ab termin "Henning"))))
Zum Vergleich der entsprechende Java-Code:
public void testBenutzerNichtEingeladenException() {
Termin termin = _stefansKalender.newTermin(_jetzt, 180, "TDD-Dojo");
try {
termin.lehneTerminAb(HENNING);
fail("BenutzerNichtVorhandenException erwartet");
} catch (BenutzerNichtEingeladenException e) {
assertTrue("Exception erwartet", true);
}
}
Das erklärt aber nicht, warum der operative Code in Lisp so viel kürzer ist als in Java oder Groovy. Eine nähere Analyse fördert folgende Gründe zu Tage:
- In Java und Groovy ist es üblich, Leerzeilen in Methoden einzufügen, teilweise auch zwischen Attributen. Beides macht man in Lisp eher nicht.
- Das aufschreiben von Attributen einer Klasse ist in Common-Lisp schlanker als in Groovy oder Java. Insbesondere gibt es prägnante Abkürzungen, um Setter und Getter und Defaultwerte zu definieren.
- Einige Implementierungen lassen sich nicht sinnvoll 1:1 nach Common-Lisp transferieren. Daher vergleicht man ein Stück weit dann doch Apfelsinen mit Orangen (so groß wie zwischen Äpfel und Birnen sind die Unterschiede dann doch nicht :-)
Der gewaltige Unterschied in den LoC findet sich bei der Menge der Syntaxelemente in deutlich reduzierter Form. So spart man sich in Common-Lisp zwar Einiges an Zeilen für die Attributdefinitionen in Klassen, es gibt aber die gleiche Anzahl von Attributdeklarationen. In diesem Sinne ist die Lisp-Lösung zwar kürzer als die Lösungen in Java und Groovy. Sie ist bzgl. der Komplexität aber äquivalent zur Groovy-Lösung und diese beiden Lösungen sind weniger komplex als die Java-Lösung.
Wieviel wirkt der große Unterschied in den LoC? Quelltext wird viel häufiger gelesen als geschrieben. Daher ist die Einsparung der Tastenanschläge bei der Code-Erstellung nicht wirklich von Interesse. Wenn man allerdings beim Lesen von Quelltext weder vertikal noch horizontal scrollen muss, erleichtert dies das Lesen: Im Lisp-Terminplaner passt jede operative Klasse problemlos auf eine Bildschirmseite, so dass man jede Klasse auf einen Blick erfassen kann. Das kann man als (leichten) Vorteil von Lisp werten. Allerdings muss ich zugeben, dass man den Groovy-Code an der einen oder anderen Stelle noch etwas eleganter Formulieren kann und dann auch noch etwas an LoC einsparen kann.
Quelltext des Terminplaners in Common-Lisp.
Post bewerten
1 Kommentar:
Ergänzung: In der Lisp-Variante kann man die Initialisierung der Variablen im Test durch ein kleines Makro kürzer formulieren. So sinken die Test-LOC von 100 auf 90 (inkl. Makro-Definition).
Kommentar veröffentlichen