Warum braucht Software Struktur?

Komplex?

Softwaresysteme gehören zu den komplexesten Gebilden, die Menschen erzeugen

  • Boeing 747-400 → 6 Millionen Teile (davon 50% Nieten)
  • Windows 2003 Server → 50 Millionen Zeilen Code
  • SAP Business Suite → 250 Millionen Zeilen Code
  • Menschliches Genom → 3 Milliarden Basen-Paare

Wie können wir trotzdem Kontrolle über die Software behalten?

Quellen:

Zitat

From a bit to a few hundred megabytes, from a microsecond to half an hour of computing confronts us with a completely baffling ratio of 109! The programmer is in the unique position that his is the only discipline and profession in which such a gigantic ratio, which totally baffles our imagination, has to be bridged by a single technology. He has to be able to think in terms of conceptual hierarchies that are much deeper than a single mind ever needed to face before.
E.W. Dijkstra

Teile und Herrsche (divide and conquer)

Die einzige Möglichkeit, bei großen (und kleinen Softwaresystemen) die Kontrolle zu behalten ist, Teilen und Herrschen anzuwenden. Die Software selbst und das Problem müssen in kleine, beherrschbare Teile zerlegt werden und diese Teile müssen über definierte Schnittstellen miteinander interagieren. Diese Zerlegung erfolgt im Allgemeinen auf mehreren Ebenen und mit jeder Ebene wird das Abstraktionsniveau angehoben.

Ein gutes Beispiel für den Ansatz der steigenden Abstraktion ist der Aufbau aller heutigen Computer. Auf der untersten Ebene besteht der Computer aus elektronischen Schaltkreisen, die durch einen sehr maschinennahen Microcode angesteuert werden. Programmen sprechen mit dem Prozessor aber den deutlich abstrakteren Maschinencode (Assembler), der vom Prozessor selbst in Microcode umgesetzt wird. Dieser wird aber im Allgemeinen nicht von Hand geschrieben, sondern von Compilern erzeugt, die eine noch abstraktere Hochsprache (z. B. C++) in den Maschinencode übersetzen können. Bei Java wird sogar noch die Hardware durch die Java Virtual Machine abstrahiert.

Gute Programme zeichnen sich ebenfalls durch eine gute Strukturierung und steigende Abstraktion aus.

Die einzige Möglichkeit größere Softwaresysteme zu konstruieren ist über „teile und herrsche“

  • Die Software wird in eigenständige Teile (Module) zerlegt
  • Die Schnittstelle zwischen den Modulen wird spezifiziert
  • Verwender dürfen nur über die Schnittstelle zugreifen
  • Das Innenleben (die Implementierung) des Moduls geht den Verwender nichts an und wird vor ihm versteckt

Dieser Ansatz führt zu

  • Kapselung (encapsulation) bzw. Information Hiding

Das bahnbrechende Werk zum Thema der Modularisierung und der Datenkapselung stammt von David Parnas: D.L. Parnas, On the Criteria to be Used in Decomposing Systems into Modules, Communications of the ACM, December 1972, Volume 15, Number 12

Zum zu versteckenden Innenleben eines Moduls gehören die Daten des Moduls, daher spricht man von Datenkapselung. Informationen, die der Verwender eines Moduls nicht braucht, um das Modul zu verwenden werden vor ihm versteckt, weshalb man von Information Hiding spricht.

Beispiel: Fehlende Kapselung

class Datum {
    int tag;
    int monat;
    int jahr;
}

class Verwender {

    void m() {
        Datum d = new Datum();
        d.tag = 32;

        d.tag = 30; d.monat = 2;

        d.tag = 31;
        d.tag++;
    }
}

Die Klasse Datum verheimlicht seine Implementierung nicht, sondern erlaubt dem Verwender den direkten Zugriff auf die Instanz-Variablen.

Das Problem hierbei ist, dass es damit zum einen unmöglich ist, jemals den Typ der Variablen zu ändern, z. B. das Datum intern durch ein java.util.Date abzubilden oder für tag ein Feld vom Typ byte zu verwenden. Weiterhin ist es Datum nicht möglich, Konsistenzprüfungen auf den Daten durchzuführen, da beim Zugriff auf eine Variable kein Code ausgeführt wird. Wie im Beispiel zu sehen, können so ungültige Zustände erzeugt werden. Dies führt dazu, dass die Verwender von Datum sich nicht darauf verlassen können, dass das Datumsobjekt gültige Informationen trägt. Daher müssen die Verwender selbst das Datum auf Richtigkeit prüfen, was eine weitere Verletzung der Kapselung darstellt, da jetzt Programmteile außerhalb des Datentyps Datum über dessen Konsistenz wachen müssen.

Beispiel: Bessere Kapselung

class Datum {

    private int tag;
    private int monat;
    private int jahr;

    public void setTag(int tag) {

        if (tag > 31) {
            tag = 31;
        }

        this.tag = tag;
    }

    ...
}

Natürlich ist der hier gezeigte Source-Code naiv und nicht wirklich gut implementiert. Der Test, ob es sich bei dem Tag um eine gültige Möglichkeit handelt, würde in der Realität sicherlich anders implementiert werden.

Durch die Kapselung der Felder von Datum hat man die Möglichkeit, die interne Darstellung durch eine andere zu ersetzen. Zum Beispiel könnte man statt drei einzelner int-Werte auch das Konzept des Unix-Timestamps verwenden und einfach die Millisekunden seit einem bestimmten Zeitpunkt zur Bestimmung des Datums verwenden.


Copyright © 2025 Thomas Smits