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:
- Boeing Corporation
- Vincent Maraia, The Build Master, Addison-Wesley 2005, Seite xxxiii
- SAP interne Zahlen, bestätig durch SAP-Lab
- Human genome project
Zitat
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.