In den folgenden Kapiteln soll es um das Formale gehen:
Stilfragen. Da es über dieses Thema eine Menge zu sagen
gibt, wird sich das Thema über mehrere Kapitel erstrecken
- ich möchte vermeiden, dass hier ein Monsterkapitel
entsteht, während ich mich gleichzeitig über Monsterfunktionen
in Lisp lustigmache.
Wenn man sich die im Internet kursierenden AutoLisp-Programme
(es gibt ja eine riesige Menge an Public-Doman-Programmen)
einmal näher anschaut, dann fallen dem geübten Auge einige
Dinge auf:
-
Das setq-Syndrom
An erster Stelle steht sicherlich die Marotte, jeden
einzelnen Zwischenschritt in den Funktionen mit Hilfe
von (setq) irgendwelchen Variablen zuzuweisen,
die aber fast immer nur in der nächsten Zeile wieder
ausgelesen werden, um den Programmfaden weiter zu spinnen.
Die Variablen werden nie wieder gebraucht und sind daher
schlicht und einfach überflüssig. Oft werden sie dann
auch nicht einmal lokal deklariert, was vermutlich auf
ihre immense Anzahl zurückzuführen ist.
-
Funktions-Monster
Als zweites wird man oft erschlagen von irgendwelchen
Monsterfunktionen, die zwar möglicherweise vieles
erledigen, und das durchaus auch noch mehr oder weniger
korrekt, aber diese Funktionen sind völlig unhandlich.
Es lässt sich weder sagen, was sie wirklich tun, noch
haben sie eine irgendwie definierte Rückgabe. Die Autoren
solcher Funktionen erfinden das Rad ständig neu: Bei
einer fast identischen Aufgabenstellung lassen sich
einzelne Funktionen nicht wiederverwenden, da eine
saubere Trennung zwischen den einzelnen Aufgaben bzw.
zwischen den Einzellösungen nicht gegeben ist.
-
Stilblüten
Dann sind oft Konstruktionen zu bewundern, bei denen mir
nur eines einfällt: Das nette Büchlein 'Was blüht denn da?'
aus dem Kosmos-Verlag. Natürlich sind solche Stilblüten
im Schwierigkeitsgrad des Erkennens abgestuft - aber viele
davon sind so offensichtlich, dass man sich wirklich über die
enorme Hartnäckigkeit wundern muss, mit der sie sich wie
Schimmelpilze in den AutoLisp-Programmen dieser Welt verbreiten.
-
pretty printing
Ein weiteres Handicap, das AutoLisp-Programmierer der
Lesbarkeit ihrer Programme mitgeben, ist der beim Einrücken
angewendete Stil. Alle sind sich einig, dass Programme, die
überhaupt nicht eingerückt sind, mehr oder weniger völlig
unleserlich sind. Also rücken alle ein - aber sind denn
Programme, bei denen die Zeilen völlig willkürlich mal links,
mal rechts um eine zufällige Anzahl von Spalten verschoben
wurden, in irgendeiner Weise lesbarer?
-
Kommentare
Auch das Ausstatten von Programmen mit Kommentaren erweckt
oft den Anschein, als sei man hier mit wenig Sachverstand an
die Sache gegangen: Schlimmer noch als ein völlig unkommentiertes
Programm ist eines, in dem man vor lauter (überflüssigen)
Kommentaren die (oft wenigen) Codezeilen mit der Lupe suchen
muss. Sinn und Zweck von Kommentaren ist nicht, das Offensichtliche
nachzuplappern, sondern Hinweise auf das zu geben, was sich einem
ersten Blick entzieht. Nur wenige Programme erreichen hier das
Klassenziel.
-
Namensvergabe
Mit dem letzten Punkt hängt die Art und Weise der Namensvergabe
für Funktionen und Variablen sehr eng zusammen. Deskriptive
(d.h. beschreibende, aussagefähige, selbsterklärende) Namen
machen eine Menge von Kommentaren von vornherein überflüssig,
da die Lesbarkeit des Programmcodes selbst zunimmt.
-
Überkandidelt
Und dann gibt es auch den superstraffen Code, der auf den
ersten Blick sehr effektiv wirkt - der es aber nicht ist.
Der Verzicht auf Variablenzuweisungen ist gut und schön, aber
nur dann, wenn er sinnvoll ist. Wenn in einer Schleife
zehntausendmal die Zahl Pi durch Vier geteilt wird, dann ist
einfach davon auszugehen, dass zehntausendmal das selbe
Ergebnis dabei herauskommt. Auch wenn die Prozessoren immer
billiger werden: Muss man das arme Ding denn so quälen?
Zunächst einmal möchte ich noch kurz darauf eingehen, was ich für
die Ursachen dieser hier angesprochenen Mängel halte. Im ersten Fall
scheint mir das offensichtlich: Leute, die mit AutoLisp programmieren,
sind meist keine Informatiker, sondern Zeichner, Ingenieure, Architekten,
die irgendwann begriffen haben, dass man sich durch das Automatisieren
bestimmter Abläufe in AutoCAD das Leben enorm erleichtern kann.
In vielen Fällen war dann AutoLisp auch die erste Programmiersprache
überhaupt, mit der man zu tun hatte. Die Hilfen zu AutoLisp, die man
mit einer AutoCAD-Lizenz erwirbt, tragen ihren Teil zu dem Drama bei:
Die Funktionen werden einzeln ohne jede Gewichtung beschrieben,
Zusammenhänge und Konzepte werden aber kaum vermittelt. Kein Wunder
also, wenn die Einsteiger oft auf dem untersten Level stehenbleiben
und die geschriebenen Programme genau das widerspiegeln, was in den
Hilfen vermittelt wird: Zerstückelung.
Ein Problem übrigens, an dem nicht nur die Online-Hilfe von AutoCAD
leidet, sondern alle neuzeitlichen Online-Hilfen ebenso: Es können,
da die Struktur automatisch generiert wird, nur noch Fragmente
nebeneinander gestellt werden. Einen roten Faden kann es in solchen
Hilfen nicht mehr geben!
Oft wird von AutoLisp-Einsteigern das Argument angebracht, dass ihnen
die tiefe Verschachtelung 'zu viel', 'zu hoch', 'zu kompliziert' sei
und dass dies der Grund dafür sei, dass man sich mit den vielen
(setq ...)-Anweisungen behelfe, um die Aufgabenstellung in
kleine Häppchen einzuteilen. Es gibt aber ein gutes Argument dagegen:
Statt sich innerhalb einer Funktion mit
(setq) solche geistigen
Rastplätze zu bauen, sollte man genau dieses Konzept gleich richtig
anwenden: Modularisieren!
Damit sind wir beim zweiten Punkt angekommen: Es ist doch völlig
legitim, dass man sich diese Ruhebänkchen schaffen möchte! Eine
komplexe Aufgabe sollte man unbedingt in einfachere Teilschritte
zerlegen - aber nicht, indem man Zwischenergebnisse in Variablen
abspeichert, die niemand braucht und die nie wieder ausgelesen
werden. Lieber sollte man sich Folgendes zu Herzen nehmen: Eine
Funktion sollte immer nur einen einzigen Zweck erfüllen, und sie
sollte einfach kurz sein.
Ich möchte mich hier nicht der Meinung anschliessen, dass eine
Funktion nicht mehr als fünf Zeilen haben sollte, da ich denke,
dass dies ein praxisfremder Wert ist. Aber wie wär's mit 10?
Gezählt werden hier allerdings nur die eigentlichen Code-Zeilen,
d.h. die Zeilen, die nur schliessende Klammern enthalten, rechnen
wir einfach nicht mit.
Blödsinn? Nein, so kann man das nicht sehen: Ein vernünftiges
Programm kann durchaus so aussehen:
(defun c:mach-was( / )
(error-handling-start ...)
(zeichne(lesen "datei")...)
(error-handling-ende)
(princ)
)
Vielleicht ist ja doch was dran an der Fünf-Zeilen-Theorie?
Natürlich ist da nicht alles drin - aber genau das ist ja
das Prinzip des Modularisierens. Die Funktion
(lesen)
könnte vielleicht so aussehen:
(defun lesen(datei-name / handle rueckgabe)
(if(setq handle(open datei-name "r"))
(progn
(setq rueckgabe(datei-lesen handle))
(close handle)
)
)
rueckgabe
)
Ja, diese Funktion hat schon sechs Code-Zeilen, aber sie könnte
auch nur vier haben, wenn man die
(progn)-Anweisung in
einer Zeile zusammfassen würde. Trotzdem könnte hier vielleicht
das Gefühl entstehen, dass wir Dinge vor uns herschieben: Der
bisherige Code delegiert nur Aufgaben, aber er zeichnet bisher
nicht einen einzigen Strich, und er macht zwar eine Datei auf
und zu, aber daraus lesen tut er auch noch nicht.
Nein, wir wollen doch gar nicht die Dinge vor uns herschieben.
Wir wollen doch nur, dass jede Funktion genau einen Zweck erfüllt
und möglichst kurz und übersichtlich ist, um uns damit die
gedanklichen Rastplätze zu schaffen. Wichtig ist nur, den Code so
zu strukturieren, dass a) keine auch noch so kleine Aufgabenstellung
zweimal gelöst werden muss, und b) möglichst viele der einzelnen
Funktionen vom Zusammenhang losgelöst wiederverwendet werden können.
In unserem Beispiel-Fragment bedeutet das z.B., dass man den
Komplex 'Lesen' aus der Hauptfunktion herausnimmt - da gehört er
nämlich nicht hinein. Das Hauptprogramm ist nur dafür zuständig,
die einzelnen Aufgaben an die entsprechenden Module zu delegieren.
Dies macht deshalb so viel Sinn, weil dann die Möglichkeit besteht,
einzelne Module auszutauschen - ohne eine Änderung im Hauptprogramm
kann z.B. gewechselt werden zwischen einem Lesen aus einer Datei,
Eingabe durch den Benutzer, Auswertung von in der Zeichnung
gespeicherten Informationen.
Ganz konkrete Beispiele für eine Vermischung von Aufgabenstellungen
und daraus resultierendem Ansatz zum Funktionsmonster können
z.B. sein:
-
Vermischung von übergeordneten Aufgaben und kleinen Pfriemeleien
auf unterster Ebene. Als Beispiel: Funktionen wie (substr)
haben in einer Top-Level-Funktion (z.B. denen, die mit C: als
AutoCAD-Befehle erzeugt werden, absolut nichts zu suchen!
-
Vermischen von verschiedenen Aufgaben. Als Beispiel kann man
sich z.B. eine Funktion vorstellen, in der eine Benutzeranfrage
(z.B. Auswahl einer Polylinie) mit Geometrie-Datenbankaufgaben
sowie geometrischen Berechnungen (z.B. Berechnen des Krümmungsradius
eines Segments über den 'bulge') durcheinandergewürfelt werden.
Denken wir doch mal an die Einführung der LW-Polylinie zurück:
Bei solchen Mischfunktionen war es nicht möglich, einfach eine
neue Funktion zum Auslesen der Stützpunkte nachzuschieben - in
vielen Fällen musste das ganze Programm neu geschrieben werden,
da eine Entmischung gar nicht mehr möglich war!
-
Vermischung des 'Was' und des 'Wie'. Nehmen wir einmal an, es
geht darum, ein paar Informationen in der Zeichnung abzuspeichern.
Der falsche Weg ist auf jeden Fall, schon in der Top-Level-Funktion
mit (regapp) anzufangen, weil man sich entschlossen hat,
die Daten als XDATA abzulegen. Jede weitere Funktion speichert
oder liest auch noch ein paar XDaten - dafür bekommen wir die
Quittung, dass so ein Programm nachträglich kaum noch auf das
Speichern in einem Dictionary umstellbar ist.
-
Lesen aus einer Datei, während gleichzeitig am Bildschirm
gezeichnet wird - vielleicht wollen wir ja nächstes Jahr das
gleiche zeichnen, aber direkt aus dem Internet lesen?
Diese Liste enthält nur ein paar Beispiele - sie ist auf keinen
Fall als vollständig anzusehen. Zu den Punkten 1 und 2 der am
Anfang dargestellten Liste habe ich ein paar Dinge gesagt. Ein
guter Anlass, noch einmal das Wichtigste zum setq-Syndrom und
den Monsterfunktionen zusammenzufassen:
Versuchen Sie nicht, über ein Speichern von Zwischenwerten
in Variablen gedankliche 'Breakpoints' in Funktionen
einzubringen (nach dem Motto: 'Bis hier klappt's jetzt alles,
jetzt fange ich was neues an!). Genau da, wo Sie anfangen,
solche Überlegungen anzustellen, ist nämlich Ihre Funktion
zu Ende - machen Sie das (defun) zu und fangen Sie
eine neue Funktion für die neue Aufgabe an!
-
Vermischen Sie niemals hochrangige Aufgaben mit den lästigen
Kleinigkeiten - die gesamte Zeichenkettenverarbeitung z.B.
sollte in irgendwelchen Hilfsfunktionen und -dateien verschwinden
und in den wesentlichen Teilen des Codes völlig unsichtbar
sein!
-
Vermischen Sie ebensowenig unterschiedliche Aufgaben
miteinander! Trennen Sie Beispielsweise den DCL-Teil eines
Programms völlig ab, dann können Sie sich später immer noch
überlegen, z.B. ObjectDCL oder eine ähnlich Bibliothek zu
benutzen, ohne das ganze Programm neu schreiben zu müssen.
Trennen Sie die Eingabe von der Ausgabe, lagern Sie alle
Berechnungen in Module aus (diese Berechnungen können Sie
ganz bestimmt auch noch woanders gebrauchen) usw. usf.
-
Legen Sie sich nicht vorzeitig auf bestimmte Techniken
fest - d.h., machen Sie nicht in der Hauptfunktion schon
die Input-Datei auf. Nennen Sie den Vorgang zunächst einmal
'daten-holen' und kümmern Sie sich später darum, woher die
Daten kommen und wie sie geholt werden!
-
Lösen Sie kein Problem zweimal! Wenn in Ihrem Code die
gleichen Formulierungen mehrfach vorkommen, dann machen
Sie irgendetwas falsch!
Im nächsten Kapitel werden wir uns mit den 'Stilblüten'
befassen, im übernächsten geht es um das Einrücken und
Kommentieren von Programmen, und dann folgt ein weiteres,
in dem wir ein Programm in zwei Varianten behandeln. Sie
ahnen es schon: Ein abschreckendes Beispiel und - als
Gegenüberstellung - ein funktionelles und ordentliches
Programm.
Übungsaufgaben
-
Sagen wir einfach mal: Hitzefrei!
Zu diesem Kapitel gibt es keine Übungsaufgaben.