Der ewige Kampf gegen Redundanzen

Konstanten

In Arbeitsspeicher ist jede Speicherzelle änderbar. Konstanten gab es daher im Freiburger Code nicht. In den herrschenden höheren Programmiersprachen Fortran, Algol und Cobol gab es um 1960 ebenfalls nur Variablen und Literale, die man in Ausdrücken verwenden konnte, so zum Beispiel das Literal 2 im Fortran-Ausdruck A**2 + 2*A*B + B**2. Ein großes Problem entsteht, wenn ein Literal mehrfach benötigt wird. So konnte man zum Beispiel in Cobol 66 eine Variable für eine deutsche Postleitzahl, bestehend aus vier Ziffern, deklarieren als PLZ PICTURE 9(4).. Wenn man das jedoch so an mehreren Stellen in seinem Programm getan hatte, musste man alle diese Stellen im Jahr 1993 auf PLZ PICTURE 9(5) . ändern. Es gab in den frühen Programmiersprachen keine Möglichkeit, Größenangaben redundanzfrei zu deklarieren. Sauber gelöst wurde das in Pascal (1970) mittels deklarierbarer, symbolischer Konstanten, die auch zur Dimensionierung von Arrays verwendet werden konnten, zum Beispiel:

CONST plzLaenge: integer := 4;
TYPE Plz = PACKED ARRAY[plzLaenge] OF character;
VAR kundePlz: Plz;
BEGIN
    FOR i := 1 TO plzLaenge DO write(kundePlz[i]);

Aufgrund des ebenfalls in Pascal eingeführten Konzepts der benutzerdefinierten Datentypen (hier der Typ Plz) war es auf diese Weise möglich, redundanzfrei Größenangaben für ein ganzes Softwaresystem festzulegen. C (1973) löste das Dimensionierungsproblem weniger elegant, weil man für symbolische Konstanten parameterlose Präprozessormakros verwenden musste. Andererseits konnte man redundanzfrei auch ohne explizite Längenkonstante auskommen, wenn man den sizeof-Operator nutzte. Entsprechendes also in C:

typedef char[4] Plz;
Plz kundePlz;
for(int i=0; i

Mit Java (1995) wurde es normal, Array-Größen erst zur Laufzeit festzulegen beziehungsweise mit den jederzeit wachstumsfähigen Collections zu arbeiten. Java 1.1 (1997) führte die Möglichkeit ein, an jeder Stelle im Programmcode mittels des Schlüsselworts final Variablenwerte einzufrieren und so berechnete „Konstanten“ zu bilden.

Präprozessorfeatures

Oft wurde ein Präprozessor zwecks Redundanzvermeidung eingesetzt. So ist es möglich, Deklarationen, die gleichlautend in verschiedenen Kompilationseinheiten benötigt werden, aus einer einzigen Datei zu inkludieren. Diese Technik wurde in Cobol, Fortran und C verwendet. So konnte man modulare Programmierung in C realisieren, indem man Funktionsdeklarationen eines Moduls in dessen Header-Datei schrieb und die verwalteten Modulvariablen und Funktionsdefinitionen in eine gleichnamige Implementierungsdatei. Als Beispiel sei hier die Header-Datei stack.h eines Stacks für Zeichen abgedruckt. Die mit # beginnenden Zeilen enthalten Präprozessordirektiven:

#ifndef stack_h
#define stack_h

#define MAX_STACK_SIZE 10
void stack_push(char item);
char stack_pop(void);

#endif

Jeder Benutzer dieses Stacks muss die Header-Datei stack.h inkludieren und kann dann die darin deklarierten Funktionen aufrufen:

#include "stack.h"
...
   stack_push('X');

Das war ungefähr 1985 bis 1995 die industriell vorherrschende Modularisierungstechnik, bis sie von der Objektorientierung abgelöst wurde.

Stringize-Operator: Der C-Präprozessor enthält den #-Operator, der den Namen eines übergebenen Makro-Arguments als String liefert. Damit kann man redundanzfrei einfache Dialoge, zum Beispiel für Testzwecke, generieren. So kann in C++ der folgende Makro PROMPT_READ benutzt werden, um den Namen der übergebenen Variablen auszugeben und sie dann einzulesen:

#define PROMPT_READ(var) {cout > var;}

Mit diesem Makro kann ein Konsolendialog wie folgt programmiert werden:

string name;
int alter;
PROMPT_READ(name);
PROMPT_READ(alter);
cout 

Eine Dialogbenutzung könnte dann wie folgt aussehen:

name? Ulf
alter? 22
Ulf ist 22 Jahre alt.

Im Allgemeinen wird ein Präprozessor als eine unelegante Lösung angesehen. Die meisten Fälle, für die man in C oder C++ den Präprozessor einsetzte, sind daher in Java durch eigene Sprachkonstrukte ohne Präprozessor lösbar. Nur das Ermitteln des Namens einer Variablen, der Abbruch einer Methode mit return; und das Behandeln von Ausnahmen mit try-catch-finally lassen sich in Java nicht in Dienste auslagern.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.