Start > Algorithmik > Ästhetik

Ästhetik der Programmierung

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.

Design

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?

  1. Designmängel wie die Benutzung globaler Variablen wie zeilenzähler und spaltenzähler innerhalb einer Routine.
  2. Pflegemängel wie das Fehlen einer guten Dokumentation.
  3. Die Routine schaut häßlich und unbeholfen aus. Man erkennt sofort den Anfänger als Autor.

Sehen wir einmal von den Design- und Pflegemängeln ab und konzentrieren uns auf die Frage, warum es so häßlich ausschaut.

  1. Die Routine ist größer als eine Bildschirmseite. Man muß immer scrollen, um alles zu sehen, das macht sie unübersichtlich. Man erkennt nicht das Ganze, den Sinn. Fehler schleichen sich so eher ein.
  2. Alle schaut hingeklatscht aus. Von Kontroll­strukturen abhängige Anweisungen sind häufig nicht eingerückt. Es ist somit schwer, den Kontrollfluß zu erkennen.
  3. Wenn schon Einrückungen verwendet wurden, dann nicht einheitlich breit, das Ganze wirkt zerrupft.
  4. Dasselbe gilt für die Leerzeilen, die nicht einheitlich verwendet wurden.
  5. Der Variablen­definitions­block ist nicht durch eine Leerzeile vom Rumpf getrennt. Schwer zu lesen.
  6. Operatoren und Kommentarzeichen sind nicht durch Leerräume abgeteilt. Das bewirkt ebf. einen klobigen, gedrängten Eindruck.
  7. Die Variablennamen sind sehr lang, das macht den Text sehr breit und damit wieder schwer zu lesen. (Anmerkung: Insbesondere deutsche Wörter sind häufig 30–50% länger als die entsprechenden englischen.)
  8. Variablennamen sind mal in Englisch, mal in Deutsch. Kein guter Stil (wenn auch tausendfach bei Programmierern so anzutreffen).
  9. Zusammengesetzte Variablennamen wie z.B. zeilenzähler sind komplett klein geschrieben. Schneller zu lesen wäre zeilenZähler (typisch für Pascal- u. Java-Entwickler) oder zeilen_zähler (typisch für C-Entwickler).

Einige Jahre später hat sich der Programmier­stil 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 (spalten­orientierte) 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;

Platzbedarf von C und Pascal

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.

PascalC
PROGRAM Hanoi(input,output);

PROCEDURE MvDisk(n:integer; a,b,c:char);
  BEGIN
  IF n > 1 THEN
    MvDisk(n-1,a,c,b);
  WriteLn(
  'Setze Disk ',n,' von ',a,' nach ',b);
  IF n > 1 THEN
    MvDisk(n-1,c,b,a)
  END;

  var
    n : integer;

  BEGIN
  Write('Wieviele Disks?: ');
  ReadLn(n);
  MvDisk(n,'A','B','C')
  END.
#include <stdio.h>

void mvdisk(int n, int a, int b, int c)
  {
  if (n > 1)
    mvdisk(n-1,a,c,b);
  printf(
  "Setze Disk %d von %c nach %c\n",n,a,b);
  if (n > 1)
    mvdisk(n-1,c,b,a);
  }

void main()
  {
  int n;

  printf("Wieviele Disks?: ");
  scanf("%d", &n);
  mvdisk(n, 'A', 'B', 'C');
  }

Symmetrie

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über­sichtlicher: 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 :-)

© 2002, 2003 asdala.de: Kon­takt & Daten­obhut