In diesem Kapitel zählen - wie leider meistens im Leben -
nicht die inneren Werte, sondern nur die Äusserlichkeiten!
Warum sollte das wichtig sein - reicht es denn nicht, wenn
der Code fehlerfrei läuft? Manchmal schon - aber auch
fehlerfreie Programme bleiben nicht davon verschont, dass
Änderungen fällig werden.
Es beginnt damit, dass sich mit jedem neuen AutoCAD-Release
die bange Frage stellt, ob bisherige Lisp-Programme ohne
Anpassung weiterlaufen, oder ob Änderungen fällig werden.
Bei meinen Programmen war es meistens so, dass sie
nicht ohne kleine oder grössere Änderungen weiterlaufen
wollten. Nicht etwa, weil AutoLisp grössere Umstellungen
erfahren hätte - nein es waren ganz banale Kleinigkeiten,
so klein, dass AutoDESK sie vielleicht nicht einmal selber
wahrgenommen hat.
Bei der Umstellung vom alten AutoLisp auf den neuen
VisualLisp-Interpreter hat es allerdings ganz massive
Änderungen gegeben. Auch hervorragend funktionierende
Programme müssen also geändert werden - und wenn es nicht
an einem neuen Release liegt, dann vielleicht daran, dass
die Qualität eines Programms den Appetit des Auftraggebers
geweckt hat (vielleicht aber auch den des Programmierers).
Das Programm, an dem man gerade arbeitet, versteht man ja
in der Regel auch hervorragend. Ist das Programm aber
ein paar Monate oder vielleicht sogar Jahre alt, sieht die
Sache anders aus: man muss sich neu hineindenken. Das fällt
bei einem gut strukturierten, ebenso gut kommentierten und
sauber geschriebenen Programm recht leicht, aber bei einem
Wust von unsauber formatiertem Spaghetti-Code kann es sehr
schwer bis nahezu unmöglich werden.
Befassen wir uns zunächst einmal mit dem Einrücken: Dies ist
einfach notwendig, um auf einen Blick zu sehen, welche
Funktion welche Argumente bekommt und wo der Funktionsaufruf
zu Ende ist. Prinzipiell zweifelt an dieser Notwendigkeit
niemand - aber trotzdem findet man im Internet unglaublich
viel AutoLisp-Code, der überhaupt nicht eingerückt ist. So
viel übrigens, dass sich Programmierer-Kollegen aus anderen
Sprachen schon mal über die AutoLispler lustig machen.
Kaum ein AutoLisp-Programmierer ist über andere Lisp-Dialekte
zu AutoLisp gekommen - fast alle haben zuerst mit AutoCAD
gezeichnet und dann angefangen, ein bisschen mit AutoLisp
zu 'spielen'. Daher ist es auch kein Wunder, dass die
AutoLisp-Welt eine ganz andere ist als die übrige Lisp-Welt.
Insbesondere beim Einrücken haben sich zwei völlig
unterschiedliche Stile herausgebildet. Zum Vergleich eine
Funktion in mehreren Varianten, die sich nur durch das
Einrücken unterscheiden:
; der CommonLisp-Stil
(defun fakultaet(zahl / )
(if(= zahl 1)
1
(* zahl(fakultaet(1- zahl)))))
; der AutoLisp-Stil
(defun fakultaet(zahl / )
(if(= zahl 1)
1
(* zahl(fakultaet(1- zahl)))
)
)
; der halbherzige Stil
(defun fakultaet(zahl / )
(if(= zahl 1)
1
(* zahl(fakultaet(1- zahl)))
) )
; gar kein Stil
(defun fakultaet(zahl / )
(if(= zahl 1)
1
(* zahl(fakultaet(1- zahl)))
)
)
; chaotischer Stil
(defun fakultaet(zahl / )
(if(= zahl 1)
1 (* zahl
(fakultaet(1- zahl)
))
)
)
Der sog. CommonLisp-Stil ist natürlich nicht nur bei
CommonLisp üblich, sondern bei allen Lisp-Dialekten
ausser AutoLisp. Ich nenne ihn nur einfach so, damit
das Kind einen Namen hat - CommonLisp ist einfach der
am weitesten verbreitete Dialekt. Dieser Einrückstil
ist kompakter als der AutoLisp-Stil, bei dem schliessende
Klammern oft allein auf einer Zeile stehen, insbesondere
am Ende von Funktionen. Dass die beiden letzten Beispiele
nur abschreckend sein können, liegt auf der Hand - deshalb
gehe ich auch gar nicht weiter darauf ein.
Es ist also eine Entscheidung treffen zwischen den beiden
ersten Beispielen (auf den halbherzigen Stil gehe ich noch
ein!). Vorschriften will ich da natürlich niemandem
machen - aber ich plädiere eher für den AutoLisp-Stil. Er
ist zwar etwas umständlicher und verlängert den Code, aber
er hat für mich einen großen Vorteil: Man kann mehr oder
weniger auf einen Blick die Klammern zuordnen, denn jede
öffnende Klammer steht entweder weiter rechts in der
selben Zeile, oder aber genau senkrecht darunter (wenn
auch evtl. einige Zeilen weiter unten).
Der 'halbherzige' Stil versucht, beide Vorteile miteinander
zu verbinden. Die schliessenden Klammern stehen exakt in der
selben Spalte wie die öffnenden - aber Vorsicht: Wenn man
genau hinsieht, bemerkt man, dass sie vertauscht werden! Das
(defun) wird in Spalte 1 auf- und in Spalte 3
zugemacht, das
(if) wir in Spalte 3 auf- und in Spalte
1 zugemacht. Das jedoch macht einem das Leben wirklich schwer,
wenn man mit einem Editor mit automatischer Klammerprüfung
arbeitet.
Der AutoLisp-Stil hat für mich noch einen weiteren Vorteil
gegenüber dem CommonLisp-Stil: Es macht es wesentlich einfacher,
mit Copy/Paste Programmzeilen einzufügen bzw. herauszunehmen.
Beim CommonLisp-Stil ist man da sehr oft darauf angewiesen,
die Klammern regelrecht abzuzählen. Deshalb plädiere ich für
den AutoLisp-Stil, auch wenn er von anderen Lispern manchmal
als Anfänger-Stil belächelt wird. Noch wichtiger als die
Frage, für welchen der beiden Stile man sich entscheidet, ist
die Frage, ob man sich überhaupt zu etwas entschliesst.
Werfen Sie noch einmal einen Blick auf das letzte Beispiel:
Schlamperei war noch nie ein Zeichen von Professionalität.
Eine letzte Frage sollte noch geklärt werden: Um wieviele
Spalten einrücken? Ich denke, dass 2 hier eine gute Zahl ist.
Durch den hohen Grad der Verschachtelung von Lisp-Code halte
ich größere Abstände für ungeeignet - würde man z.B. die in
C üblichen vier Spalten verwenden, laufen die Zeilen sehr
schnell rechts aus dem Editor-Fenster. Für eine Unsitte halte
ich das Einrücken mit Tabulatoren - die gesamte Formatierung
wird oft genug zerstört, wenn die Dateien auf anderen Rechnern,
mit anderen Editoren usw. geöffnet werden - insbesondere dann,
wenn Tabs und Leerstellen gemischt eingesetzt wurden.
Hier die Abbildung von einem Stück Code, in der deutlich
gemacht wird, wie leicht man die Zusammengehörigkeit von
Klammern sehen kann, wenn man sich an die Regel hält, dass
eine Klammer entweder noch in der selben Zeile geschlossen
wird, oder aber genau senkrecht unter der öffnenden:
Nun zum zweiten Thema dieses Kapitels: Kommentare. Sie werden
ja vom Interpreter ignoriert - warum sollte man also ein
Programm kommentieren? Auch hier ist die Antwort (wie beim
Einrücken) wieder: Wegen der Lesbarkeit! Warum ein Programm
lesbar sein sollte, haben wir ja schon erörtert. Allerdings
sollten Kommentare auch so beschaffen sein, dass sie wirklich
einen Beitrag dazu leisten - das folgende Beispiel tut das
nicht:
; i um 1 inkrementieren
(setq i(1+ i))
Solche Kommentare sind schlicht und ergreifend Blödsinn, denn
dass i hier inkrementiert wird, sieht man auch ohne den Kommentar.
So etwas erhöht die Lesbarkeit nicht, es vermindert sie. Und
was für Kommentare machen Sinn? Dazu wieder Beispiele:
; erstes Element überspringen, weil ...
(foreach item(cdr liste)
So ein Kommentar kann sehr viel Nachdenken ersparen, weil man
nicht erst herausfinden muss, WARUM das erste Element ausgelassen
wird
; die Liste der Inserts sortieren und
; alle diejenigen entfernen, die ...
(mapcar
(function
(lambda(inserts / )
(remove-if-not
(vl-sort ....)
...
...
Hier wird eine vielleicht nicht ganz einfache Stelle
des Programms mit ein paar Worten beschrieben - eine
Hilfe, um schnell zu erfassen, was da passiert. Grundsätzlich
sollte auch jede Funktion als Ganzes kommentiert sein. So
könnte das absolute Minimum aussehen:
;prüft, ob Entity eine Ellipse ist
(defun ellipse-p(ent / )
(= "ELLIPSE"(cdr(assoc 0(entget ent))))
)
;prüft, ob Entity ein Kreis ist
(defun circle?(ent / )
(= "CIRCLE"(cdr(assoc 0(entget ent))))
)
Die Schreibweise mit '-p' am Ende des Namens entspricht den
Lisp-Konventionen, wobei 'p' für 'predicate' steht.
Prädikatfunktionen testen etwas und geben entweder
T oder
nil zurück. Daher braucht man, wenn man sich an solche
Konventionen hält, auch nicht kommentieren, wie die Rückgabe
der Funktion aussieht.
Die Schreibweise mit dem Fragezeichen ist an den Lisp-Dialekt
Scheme angelehnt und nach meiner Ansicht noch intuitiver.
Sie kennzeichnet aber genau den selben Sachverhalt und erspart
das Kommentieren der Rückgabe. Funktionen, die ihres Seiteneffekts
wegen geschrieben werden, kennzeichnet man dort übrigens mit
einem Ausrufungszeichen am Ende. Ein Beispiel:
; Zweck: Destruktive Form von append
; Effekt: Gesamtliste
; Seiteneffekt: Bindung von symbol1 wird
; geändert
(defun append!(symbol1 liste2 / )
(set symbol1(append(eval symbol1)liste2))
)
Näheres zu destruktiven Formen sind auf den Seiten für Fortgeschrittene
im Kapitel
Destruktiv
zu finden. Eine vollständige Kommentierung einer Funktion könnte
übrigens etwa so aussehen:
; ****************************************
; Funktion: mapin
; Zweck: wendet ein lambda-expression auf
; eine verschachtelte Liste an
; Autor: Axel Strube-Zettler
; Datum: 01.10.95
; Effekt: modifizierte Liste
; Seiteneffekt: Keiner
; Known bugs: Keine
; siehe auch: nothing-happens
; Argumente: expr - der Lambda-Ausdruck
; a - die Liste
; Beispiel: (mapin
; '(lambda(elem)
; (if(='STR(type elem))
; (strcase elem)
; elem
; )
; )
; '("a"(1 "b")("c" 2 ("d"(3 "e"))))
; )
; =>("A"(1 "B")("C" 2 ("D"(3 "E"))))
; Definition:
(defun mapin(expr a / )
(cond
( (null a)nil)
( (atom a)((eval expr)a))
( (and(=(type a)'LIST)(cdr a)(atom(cdr a)))
(cons
(mapin expr(car a))
((eval expr)(cdr a))
)
)
('T
(append
(list(mapin expr(car a)))
(mapin expr(cdr a))
)
)
)
)
; ****************************************
Näheres zur Funktion mapin sind auf den Seiten für Fortgeschrittene
im Kapitel
Tiefer rein!
zu finden.
Wer alle seine Funktionen so auskommentiert, wird sicherlich
keine Probleme haben, auch nach Jahren wieder nachzuvollziehen,
was in einem Programm eigentlich passiert, und auch jemand
anders wird keine Schwierigkeiten haben, sich in ein Programm
einzuarbeiten. Der kommerzielle Wert von Sourcecode hängt übrigens
weniger davon ab, ob er elegant, effektiv oder sonst etwas ist.
Viel preisbestimmender ist die Qualität der Dokumentation, die
herrschende Ordnung und die daraus resultierenden Einarbeitungszeiten!
Nur ganz kurz möchte ich hier noch auf Kommentar-Systeme verweisen:
Es ist durchaus möglich, Sourcecode mit anderen Programmen zu
bearbeiten. Bekannt sind in AutoLisp z.B. Kommentierungsregeln
mit einem, zwei oder drei Semikola am Zeilenanfang, die dann von
Skripten (auch in Lisp geschrieben, aber auch in perl usw.) in
Dokumentations-Seiten (z.B. HTML oder Textdateien) umgesetzt werden.
Ein Bibliotheks-Handbuch kann dann auf Knopfdruck erzeugt werden.
Und ein letztes Wort noch zum Thema 'pretty printer': Keine Drucker
im Designer-Gehäuse, sondern Funktionen, die den AutoLisp-Code
lesen und anschliessend sauber eingerückt wieder ausgeben. Der
Editor der Visual-Lisp-IDE von AutoCAD hat eine solche
'pretty printing'-Funktion eingebaut. Aber man kann in AutoLisp
selbst geschriebene pretty printer auch im Netz finden und
herunterladen. Es ist nicht einmal soooo schwer, sich eine solche
Funktion selbst zu schreiben!
Übungsaufgaben
-
Sagen wir einfach mal: Hitzefrei!
Zu diesem Kapitel gibt es keine Übungsaufgaben.