Eine Ästhetik der Programmierung: kann es so etwas geben? Gibt es „schöne“ Quelltexte?
Die Abhandlung versucht, dieser Frage nachzugehen. Konzentrieren wir uns zunächst auf das funktionale Design von Quelltexten.
Folgender Ausschnitt stammt aus einem 1990 geschriebenen Turbo-Pascal-Programm:
{$G+} {$A+,B-,N-,E+,F-,O-,X-} {$M 16384,0,655360}
{$Define Testphase}
{$IfDef Testphase} {$D+,L+,R+,S+,V+,I+}
{$Else} {$D-,R-,S-,V-,I-}
{$EndIf}
Procedure IRMWr;
{die Prozedur schreibt direkt in den Bildschirmspeicher}
{im TTY mode, spalten- und zeilenweise}
var
x,y,j : word;
ch : char;
BEGIN
zeilenzähler:=DataY1; spaltenzähler:=DataX1; j:=0;
WHILE (zeilenzähler<=DataY2) and (j<remData) DO BEGIN
j:=j+1;
ch:=dataSeekPtr^[j]; gotoxy(1,1);write(zeilenzähler:2,'/',spaltenzähler:2,': ',ord(ch):3);
case ch of
#7 : write(^G);
#8 : BEGIN
if spaltenzähler>DataX1 THEN spaltenzähler:=spaltenzähler-1
else if zeilenzähler>DataY1 THEN BEGIN spaltenzähler:=DataX2;
zeilenzähler:=zeilenzähler-1; END
END;
#9 : BEGIN
tab:=1;
repeat
tab:=tab+tablen;
if tab>spaltenzähler THEN BEGIN
if tab>DataX2 THEN tab:=DataX2;
FOR x:=spaltenzähler to tab DO VRAM^[zeilenzähler,x].Ch:=' ';
spaltenzähler:=tab;
END
until tab=spaltenzähler
END;
#10 : zeilenzähler:=zeilenzähler+1;
#13 : BEGIN
FOR x:=spaltenzähler to DataX2 DO VRAM^[zeilenzähler,x].Ch:=' ';
spaltenzähler:=DataX1
END;
else BEGIN
VRam^[zeilenzähler,spaltenzähler].Ch:=ch;
if spaltenzähler<DataX2 THEN spaltenzähler:=spaltenzähler+1
else BEGIN zeilenzähler:=zeilenzähler+1; spaltenzähler:=DataX1 END;
END {case else}
END; {case}
END; {WHILE}
{Löschen des unbenutzten Datenfensters}
if zeilenzähler<=DataY2 THEN BEGIN
FOR x:=spaltenzähler to DataX2 DO VRam^[zeilenzähler,x].Ch:=' ';
FOR y:=zeilenzähler+1 to DataY2 DO FOR x:=DataX1 to DataX2 DO VRam^[y,x].Ch:=' ';
END;
{Zähler und Zeiger aktualisieren}
chC:=chC+j;
remData:=DataLen-chC;
dataSeekPtr:=ChangePtr(DataStartPtr,+chC);
END;
Was fällt auf?
Sehen wir einmal von den Design- und Pflegemängeln ab und konzentrieren uns auf die Frage, warum es so häßlich ausschaut.
Einige Jahre später hat sich der Programmierstil geändert. Die Routinen sind kleiner und wirken sehr viel leichter und klarer (Anmerkungen für Nicht-Pascalianer: Die {$}-Anweisungen sind Präprozessor-Direktiven und müssen ohne Leeraum geschrieben werden):
{ ----------------------------------------------------------------------- }
PROCEDURE IntToBig(var a : tBig; num : tNum);
{ ----------------------------------------------------------------------- }
{ Operation: Copies integer into big format. }
{ Flags : Left unchanged. }
{ Hints : No error handling if tNum > tBig! }
{ Last mod.: 1999/04. }
{ ----------------------------------------------------------------------- }
type
tNumArray = array[1..SizeOf(num)] of byte;
var
{$IFDEF ANSI}
i : Low(tBigNdx)..High(tBigNdx)+1;
{$ENDIF}
msb : byte;
BEGIN
{ Copy num and extend sign }
IF num < 0 THEN
msb := $FF
ELSE
msb := $00;
{$IFDEF ANSI}
FOR i := 1 TO SizeOf(num) DO
a[i] := tNumArray(num)[i];
FOR i := SizeOf(num)+1 TO bigMaxlen DO
a[i] := msb;
{$ELSE}
FillChar(a, SizeOf(tBig), msb);
Move(num, a, SizeOf(num));
{$ENDIF}
END;
Dasselbe noch mal für eine (spaltenorientierte) Assembler-Routine innerhalb von Pascal:
{ ----------------------------------------------------------------------- }
FUNCTION BigIsEqual(const a, b : tBig) : boolean; assembler;
{ ----------------------------------------------------------------------- }
{ Operation: Returns true if a = b. }
{ Flags : Left unchanged. }
{ Hints : This is the same comparation like CPU's CMP, JE. }
{ Last mod.: 1999/04. }
{ ----------------------------------------------------------------------- }
ASM
PUSH DS
{$IFDEF CVALPAR}
MOV DX, SS
MOV DS, DX
LEA SI, [a]
MOV ES, DX
LEA DI, [b]
{$ELSE}
LDS SI, [a]
LES DI, [b]
{$ENDIF}
XOR AL, AL { Preset @result FALSE }
MOV CX, TYPE tBig
CLD
REPE CMPSB
JNE @0
INC AL { Set @result TRUE }
@0: POP DS
END;
Durch die raumgreifenden BEGIN's, END's, THEN's etc. wirkt Pascal immer wuchtig. C kommt mit geschweiften Klammern aus, was es filigraner wirken läßt, und THEN entfällt ersatzlos.
Pascal | C |
---|---|
|
|
Insbesondere bei der Abbildung eines bestimmten Sachverhaltes entstehen oft symmetrische Strukturen. Häufig ist dies ein Hinweis darauf, daß man in diesem Fall den effektivsten und klarsten Code gefunden hat, so wie im obigen rekursiven Beispiel (Abfolge von if – printf – if). Schreibt man den Code um, so daß er nicht in dieser Reihenfolge kommt (das geht), wird er um vieles unnötig größer und unübersichtlicher: in dem Fall hat man die Struktur des zu lösenden Problems noch nicht „klar“ durchschaut.
Als ich vor Jahren anfing, eine Lösung für den Turm von Hanoi zu schreiben, kam ein Programm mit über 60 Zeilen und dutzenden Verzweigungen heraus. Ich hatte das Problem nicht wirklich begriffen. Als ich später die kurze und elegante rekursive Formulierung entdeckte, begann ich wirklich an mir zu zweifeln. Alles wichtige im Leben ist immer einfach, heißt es – man muß es nur sehen können …
Auch in der folgenden Sortier-Funktion erkennt man mehrere symmetrische Strukturen:
void sort(titem a[], int left, int right)
{
int i, j;
titem v, h;
if (right <= left)
return;
i = left - 1; j = right; v = a[right];
do {
do i++; while (a[i] < v);
do j--; while (a[j] > v);
h = a[i]; a[i] = a[j]; a[j] = h;
} while (i < j);
a[j] = a[i]; a[i] = v; a[right] = h;
sort(a,left,i-1);
sort(a,i+1,right);
}
Der Algorithmus ist ausgewogen. Das sollte er allerdings auch sein: Robert Sedgewick (nun Professor für Informatik und Dekan der Fakultät für Informatik an der Princeton University), von dem der Algorithmus in dieser Form stammt, hat einige Jahre darüber dissertiert :-)