PROGRAMMIERUNG VON UNTEN NACH OBEN
OriginalJanuar 1993
(Dieser Aufsatz stammt aus der Einleitung zuOn Lisp.)
Es ist ein langjähriges Prinzip des Programmierstils, dass die funktionalen Elemente eines Programms nicht zu groß sein sollten. Wenn eine Komponente eines Programms über den Punkt hinauswächst, an dem sie leicht verständlich ist, wird sie zu einer Masse von Komplexität, die Fehler ebenso leicht verbirgt wie eine große Stadt Flüchtlinge. Solche Software wird schwer zu lesen, schwer zu testen und schwer zu debuggen sein.
In Übereinstimmung mit diesem Prinzip muss ein großes Programm in Teile unterteilt werden, und je größer das Programm ist, desto mehr muss es unterteilt werden. Wie teilt man ein Programm auf? Der traditionelle Ansatz wird als Top-Down-Design bezeichnet: Man sagt: "Der Zweck des Programms ist es, diese sieben Dinge zu tun, also teile ich es in sieben Hauptunterprogramme auf. Das erste Unterprogramm muss diese vier Dinge tun, also wird es seinerseits vier eigene Unterprogramme haben," und so weiter. Dieser Prozess wird fortgesetzt, bis das gesamte Programm das richtige Maß an Granularität hat – jeder Teil groß genug, um etwas Substantielles zu tun, aber klein genug, um als eine einzelne Einheit verstanden zu werden.
Erfahrene Lisp-Programmierer teilen ihre Programme anders auf. Neben dem Top-Down-Design folgen sie einem Prinzip, das als Bottom-Up-Design bezeichnet werden könnte – die Sprache an das Problem anpassen. In Lisp schreibt man sein Programm nicht einfach in Richtung der Sprache, man baut auch die Sprache in Richtung seines Programms auf. Während man ein Programm schreibt, könnte man denken: "Ich wünschte, Lisp hätte einen solchen oder solchen Operator." Also geht man und schreibt ihn. Danach erkennt man, dass die Verwendung des neuen Operators das Design eines anderen Teils des Programms vereinfachen würde, und so weiter. Sprache und Programm entwickeln sich gemeinsam. Wie die Grenze zwischen zwei kriegführenden Staaten wird die Grenze zwischen Sprache und Programm gezogen und neu gezogen, bis sie schließlich entlang der Berge und Flüsse, den natürlichen Grenzen Ihres Problems, zur Ruhe kommt. Am Ende wird Ihr Programm so aussehen, als wäre die Sprache dafür entworfen worden. Und wenn Sprache und Programm gut zueinander passen, hat man Code, der klar, klein und effizient ist.
Es ist wichtig zu betonen, dass Bottom-Up-Design nicht bedeutet, einfach dasselbe Programm in einer anderen Reihenfolge zu schreiben. Wenn man von unten nach oben arbeitet, hat man normalerweise ein anderes Programm. Anstelle eines einzigen, monolithischen Programms erhält man eine größere Sprache mit abstrakteren Operatoren und ein kleineres Programm, das darin geschrieben ist. Anstelle eines Türsturzes erhält man einen Bogen.
In typischem Code, sobald man die Teile abstrahiert, die lediglich Buchführung sind, bleibt viel weniger übrig; je höher man die Sprache aufbaut, desto weniger Distanz muss man von oben nach unten zurücklegen. Dies bringt mehrere Vorteile mit sich:
Indem man die Sprache mehr Arbeit verrichten lässt, liefert das Bottom-Up-Design Programme, die kleiner und agiler sind. Ein kürzeres Programm muss nicht in so viele Komponenten unterteilt werden, und weniger Komponenten bedeuten Programme, die leichter zu lesen oder zu modifizieren sind. Weniger Komponenten bedeuten auch weniger Verbindungen zwischen den Komponenten und damit weniger Fehlerquellen. Während industrielle Designer bestrebt sind, die Anzahl der beweglichen Teile in einer Maschine zu reduzieren, nutzen erfahrene Lisp-Programmierer das Bottom-Up-Design, um die Größe und Komplexität ihrer Programme zu verringern.
Bottom-Up-Design fördert die Wiederverwendbarkeit von Code. Wenn man zwei oder mehr Programme schreibt, werden viele der Hilfsprogramme, die man für das erste Programm geschrieben hat, auch in den nachfolgenden nützlich sein. Sobald man ein großes Substrat an Hilfsprogrammen erworben hat, kann das Schreiben eines neuen Programms nur einen Bruchteil des Aufwands erfordern, den es benötigen würde, wenn man mit reinem Lisp beginnen müsste.
Bottom-Up-Design macht Programme leichter lesbar.
Ein Beispiel für diese Art der Abstraktion fordert den Leser auf, einen allgemeinen Operator zu verstehen; ein Beispiel für funktionale Abstraktion fordert den Leser auf, ein spezialisiertes Unterprogramm zu verstehen. [1]
Da es dazu führt, dass man immer auf der Suche nach Mustern im eigenen Code ist, hilft das Arbeiten von unten nach oben, die eigenen Ideen über das Design des Programms zu klären. Wenn zwei entfernte Komponenten eines Programms in ihrer Form ähnlich sind, wird man auf die Ähnlichkeit aufmerksam und vielleicht das Programm auf einfachere Weise neu gestalten.
Bottom-Up-Design ist bis zu einem gewissen Grad auch in anderen Sprachen als Lisp möglich. Wann immer man Bibliotheksfunktionen sieht, geschieht Bottom-Up-Design. Allerdings gibt Lisp einem in diesem Bereich viel breitere Möglichkeiten, und die Erweiterung der Sprache spielt eine verhältnismäßig größere Rolle im Lisp-Stil – so sehr, dass Lisp nicht nur eine andere Sprache ist, sondern eine ganz andere Art des Programmierens.
Es ist wahr, dass dieser Entwicklungsstil besser für Programme geeignet ist, die von kleinen Gruppen geschrieben werden können. Gleichzeitig erweitert er jedoch die Grenzen dessen, was von einer kleinen Gruppe geleistet werden kann. In The Mythical Man-Month schlug Frederick Brooks vor, dass die Produktivität einer Gruppe von Programmierern nicht linear mit ihrer Größe wächst. Mit zunehmender Gruppengröße sinkt die Produktivität der einzelnen Programmierer. Die Erfahrung des Lisp-Programmierens legt eine optimistischere Formulierung dieses Gesetzes nahe: Wenn die Gruppengröße abnimmt, steigt die Produktivität der einzelnen Programmierer. Eine kleine Gruppe gewinnt, relativ betrachtet, einfach weil sie kleiner ist. Wenn eine kleine Gruppe auch die Techniken nutzt, die Lisp ermöglicht, kann sie überlegen gewinnen.
Neu: Laden Sie On Lisp kostenlos herunter.
[1] "Aber niemand kann das Programm lesen, ohne all Ihre neuen Hilfsprogramme zu verstehen." Um zu sehen, warum solche Aussagen normalerweise falsch sind, siehe Abschnitt 4.8.