Donnerstag, August 24, 2006

IDEs und der K(r)ampf zwischen Statik und Dynamik

Für Java existieren heute mit Eclipse, IntelliJ IDEA und Konsorten sehr mächtige Entwicklungsumgebungen. Cross-Referencing, Refactoring und Auto-Completion sind Features, die man nicht mehr missen möchte.
Dass diese Features für die aktuellen Skriptsprachen a la Python, Ruby oder Groovy nicht oder nur eingeschränkt existieren, wird häufig als Argument gegen diese Skriptsprachen verwendet.

Aber wird über diese Features wirklich reflektiert?

Cross-Referencing
Mit den Cross-Referencing-Möglichkeiten der IDE kann man auf Tastendruck oder Mausclick von einer Codestelle zum referenzierten Element gelangen (z.B. von einer Variablendeklaration zur Klasse, von deren Typ die Variable ist).

Natürlich sind die Cross-Referencing-Möglichkeiten in den IDEs deutlich mächtiger als eine Textsuche über Dateien. Aber ist der Unterschied wirklich so groß? Und ist die Textsuche häufig nicht sogar schneller?

Refactoring
Refactoring ist eine der großen Errungenschaften in der Neuzeit der Softwareentwicklung und ihr Automatisierungsgrad ist wirklich erstaunlich hoch. Aber welche automatisierten Refactorings nutzen wir überhaupt bei der täglichen Arbeit und wie häufig? Viele Entwickler verzichten sogar absichtlich auf bestimmte einfache Automatisierungen (z.B. "Introduce Variable"), weil sie von Hand schneller sind - die Mächtigkeit der IDEs führt bei großen Projekten leider dazu, dass die Arbeit mit ihnen mitunter doch etwas zäh wird.
Viele Refactorings kann man in der Tat mit vertretbarem Aufwand und Risiko auch von Hand durchführen. Einige andere (z.B. "Rename Class") lassen sich auch auf anderem Wege automatisieren (Find&Replace über Dateien). Natürlich kann man sich dann oft nicht 100%ig sicher sein, dass alles korrekt geändert wurde. Man muss anschließend die Tests ausführen. Dass muss man in Java aber auch, weil es immer mehr untypisierte Referenzen in XML-Dateien (Struts, Spring, EJB etc.) gibt, die den IDEs auch mal durch die Lappen gehen.

Auto-Completion
Bei der automatischen Vervollständigung tippt man nur den Beginn eines Wortes ein und die IDE ergänzt die fehlenden Zeichen. Dadurch spart man Tippzeit, reduziert die Wahrscheinlichkeit von Tippfehlern und muss sich nur an den Anfang von Methodennamen erinnern.
Interessanterweise gibt es auch in programmiersprachen-unabhängigen Editoren wie Emacs oder JEdit Auto-Completion. Dabei werden viel einfachere Verfahren eingesetzt als in den Java-IDEs, die aber erstaunlich gute Ergebnisse liefern. Ein ganz einfacher Ansatz besteht z.B. darin, bei der Auto-Completion nur die Wörter zu berücksichtigen, die in der aktuellen Datei schon mal verwendet wurden. Etwas umfangreicher ist der Ansatz, die Wörter aller Dateien eines Projektes zu verwenden.
Ähnlich wie beim Cross-Referencing sind diese Auto-Completion-Features der Editoren qualitativ denen der IDEs unterlegen. Aber wie beim Refactoring sie sind schneller.

Und das ist ein Faktor, den man nicht unterschätzen sollte. Ich ertappe mich beim Java-Programmieren jedenfalls immer wieder dabei, dass ich Namen komplett manuell eintippe, weil ich damit schneller bin als die zäh reagierende Entwicklungsumgebung. Und wenn ich in einem einfachen Editor eine Skriptsprache programmiere, genieße ich es, dass der Editor immer sofort reagiert. Die IDEs sind nur Bruchteile von Sekunden langsamer, aber das reicht schon aus, um den Flow beim Programmieren zu beeinträchtigen.

Post bewerten

Samstag, August 19, 2006

Closures in Common Lisp

Martin Fowler published an online article about closures. He used the Ruby programming language for the examples. There are translations of the examples to Python and a language called Boo.

As a programming task for myself I did the examples in Common Lisp, which supported Closured already decades ago. A real lisper might be able to write the code in a more elegant way but I think the principle should become clear.

I didn't use the Common Lisp Object System (CLOS) since for this simple example an ad-hoc modeling with hashes is simpler.


;; Ruby
; def managers(emps)
; return emps.select {|e| e.isManager}
; end

;; Common Lisp
(defun is-manager (emp) (getf emp :is-manager))

(defun managers (emps)
(remove-if-not (lambda (emp)
(when (is-manager emp) emp))
emps))


;; Ruby
; def highPaid(emps)
; threshold = 150
; return emps.select {|e| e.salary > threshold}
; end

;; Common Lisp
(defun high-paid (emps)
(let ((threshold 150))
(remove-if-not (lambda (emp)
(when (> (getf emp :salary) threshold) emp))
emps)))


;; Ruby
; def paidMore(amount)
; return Proc.new {|e| e.salary > amount}
; end

;; Common Lisp
(defun paid-more (amount)
(lambda (emp) (when (> (getf emp :salary) amount) emp)))

;; Ruby
; highPaid = paidMore(150)
; john = Employee.new
; john.salary = 200
; print highPaid.call(john)

;; Common Lisp
(let ((high-paid (paid-more 150))
(john '(:name John :is-manager nil :salary 200)))
(princ (funcall high-paid john)))


;; Tests
(defparameter manager-list
'((:name Stefan :is-manager nil :salary 150)
(:name Henning :is-manager t :salary 151)
(:name Martin :is-manager nil :salary 120)
(:name Christian :is-manager t :salary 200)))

(princ #\newline)
(princ "Test function managers")
(princ #\newline)
(princ (managers manager-list))
(princ #\newline)

(princ #\newline)
(princ "Test function high-paid")
(princ #\newline)
(princ (high-paid manager-list))
(princ #\newline)

Post bewerten

Freitag, August 04, 2006

Sensing Variables for Refactorings

Michael Feathers hat ein Konzept namens Sensing Variables beschrieben, mit dem sich Refactorings an Methoden sicherer durchführen lassen. Ein häufiges Problem mit dem Refactoring "Extract Method" besteht darin, dass der selektierte Code-Block sich nicht automatisch in eine Methode verschieben lässt (z.B. weil die neue Methode dann mehrere Rückgabewerte haben müsste). Also muss man erstmal manuell die Methode manipulieren, bevor man "Extract Method" ausführen kann. Dazu muss man häufig die Reihenfolge von Anweisungen in der Methode ändern, kann aber nur schwer feststellen, ob sich durch das Verschieben die Semantik der Methode ändert. Sensing Variables helfen, an dieser Stelle mehr Sicherheit zu bekommen.

Post bewerten

DSL: Domain Specific Languages

Martin Fowler hat einen kurzen Artikel über DSLs in seinem Blog veröffentlicht. In dem Artikel erklärt er den Unterschied zwischen General Purpose Languages (GPL) und DSLs. Außerdem argumentiert Fowler, dass der Übergang zwischen APIs und DSLs fließend ist. Das API einer Bibiothek könne durchaus vergleichbar sein mit einer DSL.

Das finde ich sehr überzeugend, vor allem wenn man sich die dynamischen Skriptsprachen a la Ruby, Groovy oder Lisp ansieht. Dort wo in Java eigene DSLs für Build-Skripte (ANT), Datenbankabfragen (Hibernate Query Language, HQL) oder Oberflächenbeschreibung (HTML, JSP) verwendet werden, haben Ruby und Konsorten einfach nur Bibiotheken, die innerhalb der Programmiersprache verwendet werden.

Interessant finde ich, dass alle Artikel zum Thema DSL, die ich kenne (ich gebe zu, dass das nicht allzu viele sind), sich ausschließlich um Technologie-Domänen kümmern. Es kommen immer wieder dieselben technischen Beispiele für DSLs: SQL, ANT, etc. Das finde ich schade. Schließlich sind die technischen Aspekte in einem Softwareprojekt eher uninteressant im Gegensatz zu den fachlichen. DSLs nur für technische Aufgaben zu verwenden, riecht nach einer Optimierung an der falschen Stelle (wenn ich in einem Bereich 50% Aufwände spare, der nur wenige Prozent meiner Gesamtaufwände ausmacht, ist der Gesamtgewinn marginal).

Eigentlich müssten wir nach fachlichen DSLs suchen. Interessiert das Niemanden, ist das so schwer oder lese ich einfach nur die falschen Quellen?

Post bewerten