Kampf um Redundanzen Teil 2: höhere Konzepte

Eine redundante Geschichte der Programmierung: Von Parnas bis Scala

Christoph Knabe

Seit den Anfängen der Programmierung behinderten Redundanzen im Quellcode die Wartung und Wiederverwendung. Die Notwendigkeit, das zu vermeiden [1], führte zu zahlreichen Programmierkonstrukten, deren Zusammenhang einem im heutigen Programmiereralltag oft nicht mehr bewusst ist. Die im zweiten Teil behandelten höheren Konzepte werden anhand verbreiteter Programmiersprachen besprochen und veranschaulichen die mühsamen Fortschritte der Programmierung der vergangenen 50 Jahre.

Artikelserie

Wenn wir über Möglichkeiten zur Vermeidung von Redundanzen sprechen, dann meinen wir damit, dass der gleiche Sachverhalt nicht an mehreren Stellen des Quellcodes auftreten soll. Zum Einstieg in den zweiten Teil unserer Serie widmen wir uns zunächst dem Geheimnisprinzip (Information Hiding), das etwa 1972 von David L. Parnas entwickelt wurde. Es fordert, eine Datenstruktur nicht direkt zu manipulieren, sondern nur über Operationen, die in einer Schnittstelle zusammengefasst sind. Information Hiding war das vorherrschende Paradigma in der modularen Programmierung und spielt auch in der objektorientierten Programmierung noch eine wichtige Rolle. Die Befolgung des Geheimnisprinzips garantiert, dass die vorgesehenen Verwaltungsoperationen durch Modulbenutzer nicht umgangen werden können. Das trägt zur Redundanzvermeidung bei, weil die Verwaltungslogik nicht in die benutzenden Module abwandert und dort dupliziert werden kann. In Sprachen ohne Unterstützung von Information Hiding war diese Gefahr immer gegeben. Ein sicheres Information Hiding war in C (1973) durch die Deklaration dateiweiter Variablen als static möglich. Solche Variablen blieben über einen Funktionsaufruf hinaus am Leben, waren aber von außerhalb der Quelldatei nicht zugreifbar. Weitere, konzeptionell bedeutende, modulare Programmiersprachen waren Modula-2 und Ada. In C++ (1983) wurde das Geheimnisprinzip auf benutzerdefinierte Datentypen (Klassen) erweitert, indem Attribute und Funktionen standardmäßig als privat eingestuft wurden, aber mittels public: öffentlich sichtbar gemacht werden konnten.

Rückblick auf Teil 1

Teil 1: Der ewige Kampf gegen Redundanzen [2] beinhaltete folgende Punkte:

  • Relative Adressierung
  • Symbolische Adressierung
  • Formelübersetzung
  • Parametrierbare Unterprogramme
  • Steuerstrukturen
  • Mittelprüfende Schleifen
  • Symbolische Konstanten
  • Präprozessorfeatures
  • Array-Initialisierung
Generizität

Pascal (1970) führte Records für benutzerdefinierte, zusammengesetzte Datentypen ein. C folgte 1973 mit den structs. Diese Konstrukte erhöhten die Robustheit von Programmen, da Verwechslungen zum Beispiel von Personen mit Fenstern, Kalenderdaten oder Aufträgen durch den Compiler aufgedeckt wurden. Jedoch führte diese neue Strenge zu Problemen beim Erstellen universeller Dienste. Obwohl Pascal elegante Operationen für dynamische Datenstrukturen enthielt, war es nicht möglich, eine verkettete Liste so zu programmieren, dass sie für einen beliebigen Nutzdatentyp verwendbar war. Die Art der Verkettung und der Aufbau der Nutzdaten mussten im Typ für einen Listenknoten fest verknüpft werden, zum Beispiel folgendermaßen:

TYPE 
   PersonList = ^PersonNode;
   PersonNode = RECORD
      info: Person;
      nextPtr: ^PersonNode;
   END;

In Pascal konnte man sich für die Erstellung einer universellen Listenverwaltung nur durch eine Kopie des Quelltextes und die globale Substitution des Nutzdatentypnamens behelfen. Das etwas weniger strenge C konnte solche Probleme durch die Verwendung des ungetypten Pointers void* umgehen. In C war es also möglich, wenn auch auf Kosten der Typsicherheit, eine Listenverwaltung für beliebige Nutzdaten zu schreiben. Deren Listenknoten konnte wie folgt formuliert werden:

struct List {
   void* infoPtr;
   struct List* nextPtr;
};

Erst Ada (1980) gelang eine Synthese der benutzerdefinierten, strukturierten Datentypen (Records) mit einer flexiblen Typsicherheit. Dieses Konzept wurde Generizität genannt und von allen modernen, statisch getypten Programmiersprachen wie C++ (Templates), Java, C# und Scala aufgegriffen. Dadurch können Redundanzen bei der Definition gleichartiger Dienste für verschiedene Nutzdatentypen vermieden werden. Die darauf aufbauenden, generischen Collection-Klassen werden in allen aktuellen Programmiersprachen sehr häufig benutzt. Dynamisch getypte Sprachen wie Smalltalk oder Ruby umgehen das hier beschriebene Problem, indem sie die Typprüfung auf die Laufzeit verschieben.

Geschrieben von
Christoph Knabe
Kommentare

Schreibe einen Kommentar

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