Dr. Christian Maurer, FU Berlin
Allgemeines zur Spezifikation
Zwischen der Anforderungsdefinition und der Programmierung im engen Sinn, d.h.
der Implementierung, liegt die Phase des Entwurfs mit dem Ziel einer geeigneten
Zerlegung des Gesamtsystems in Komponenten und deren Spezifikation. Das
Leitmotiv ist die Frage:
WAS sind die einzelnen Teile des Systems?
Das Hauptanliegen in dieser Phase ist die Reduktion der Komplexität des
Systems auf ein beherrschbares Maß, die sich möglichst stringent aus den
Ergebnissen der Anforderungsdefinition ergibt.
Der Gewinnung sinnvoller Kriterien an die Zerlegung dienen folgende
Forderungen an die Komponenten:
- ein starker innerer Zusammenhang jeder einzelnen Komponente
- und eine von den anderen Komponenten weitgehend unabhängige
- Verständlichkeit,
- Planbarkeit,
- Konstruierbarkeit,
- Prüfbarkeit und
- Wartbarkeit.
Zur Erfüllung dieser Forderungen ist es sinnvoll, jede Komponente mit zwei
Sichtweisen auszustatten:
- der Spezifikation, der Aufzählung aller ihrer Leistungen und der
exakten (im Idealfall mathematisch formulierten) Beschreibung der
Voraussetzungen und Effekte für jede einzelne Leistung und
- der Implementierung dieser Leistungen gemäß der Spezifikation,
in der die Entwurfsentscheidung unter Einbeziehung der vorhandenen
Ressourcen und Beachtung des Anforderungsprofils an das Systemverhalten (wie
z.B. Optimierung des Laufzeitverhaltens, Effizienz der Speicherverwaltung,
Anforderungen an die Datensicherheit) getroffen wird.
Häufig wird für die Spezifikation das Bild eines "Vertrags" zwischen
Implementor und Nutzer eines Moduls benutzt. Das ist insofern zutreffend, als
die Spezifikation gegenseitige Verpflichtungen regelt:
- für den Implementor die Pflicht zur Konstruktion gemäß ihren
Zusicherungen und
- für den Klienten die zur Benutzung nach den in ihr festgelegten
Voraussetzungen.
Eine strikte Trennung der beiden Teile bietet die Gewähr dafür, daß der
Klient einer Komponente nicht implizite Annahmen über ihr Verhalten macht, die
er aus der Kenntnis von Implementierungsdetails hat. Nur so ist die Komponente
vor unkontrollierten Zugriffen von außen sicher, die ihr Verhalten entgegen der
Spezifikation verändern und Nebeneffekte erzeugen können, die sich
unvorhersagbar auf das Systemverhalten auswirken.
Moduln als Komponenten einer Zerlegung
Als Sammelbegriff für kleinere Komponenten eines Systems hat sich der des Moduls
(in Anlehnung daran der Begriff der modulorientierten
Programmiersprachen in Verallgemeinerung der von Wirth als Weiterentwicklung von
Pascal - über die Zwischenstufe Modula geschaffenen Sprache Modula-2)
eingebürgert. Zur genaueren Charakterisierung dieses Begriffs werden im
folgenden die allgemeinen Forderungen des vorigen Abschnittes präzisiert.
Notwendige Bedingungen an einen sauberen Modulbegriff sind
- die Einfachheit der Spezifikation des Moduls und
- die Kontextunabhängigkeit seiner Implementierung.
Zur Einfachheit der Spezifikation eines Moduls gehören
- eine geeignete Sprachebene zu deren Beschreibung: mindestens eine genaue
umgangssprachliche Ausdrucksweise, besser eine halbformale oder formale
Spezifikationssprache,
- Verständlichkeit und Überschaubarkeit, d.h.
- Minimalität seines Leistungsangebotes durch die Bereitstellung eines
zusammenhängenden, nicht in Teilprobleme zerlegbaren Problemkreises,
- ein möglichst hohes Maß an Unabhängigkeit (im Idealfall überhaupt
keine Abhängigkeiten) von den Spezifikationen anderer Moduln,
- die Reduktion von Datentransporten auf ein möglichst geringes Maß -
sowohl intramodular (innerhalb eines Moduls) als auch intermodular
(zwischen verschiedenen Moduln),
- die Wahrung des Geheimnisprinzips ("information hiding"), d.h.
- die Beachtung des Grundsatzes, daß die Spezifikation genau die Menge
aller Annahmen repräsentiert, die andere Teile des Systems von dem
betreffenden Modul machen,
- folglich die rigide Vermeidung der Offenlegung irgendwelcher
Implementierungsdetails,
- ein hoher Allgemeinheitsgrad, d.h.
- weitestgehende Abstraktion von den konkreten Gegebenheiten des
vorgesehenen Verwendungszweckes und von implizitem Wissen über die
Benutzung des Moduls in übergeordneten Schichten,
- Maximalität des Leistungsangebotes innerhalb des umschriebenen
Problemkreises mit dem Ziel der Verwendbarkeit auch für andere Zwecke
als dem ursprünglich geplanten,
- Geschlossenheit im Sinne eines gewissen Reifegrades seines
Entwicklungsstadiums,
- aber trotzdem Offenheit für Anpassungen oder Erweiterungen seines
Leistungsumfanges.
Der Kontextunabhängigkeit der Implementierung eines Moduls lassen sich
folgende Punkte zuordnen:
- Beherrschbarkeit der Komplexität, d.h.
- Beschränkung auf die Erledigung der durch die Spezifikation gegebenen
Aufgabe, die durch eine starke innere (logische wie sachliche) Bindung
gekennzeichnet ist,
- Verzicht auf die Konstruktion von Systemteilen, die nicht unmittelbar
aus der gegebenen Spezifikation erwachsen,
- einerseits Begrenzung der Anzahl der benutzten Moduln auf das Minimum
dessen, was für die Erledigung der Aufgabe notwendig ist,
- andererseits aber ggf. auch Erhöhung der Übersichtlichkeit durch
Auslagerung von Aufgabenteilen in separate Moduln,
- konsequente Ausnutzung des Geheimnisprinzips, d.h.
- Festlegung auf Datenstrukturen oder Algorithmen erst bei seiner
Implementierung, dadurch die Offenhaltung von alternativen
Implementierungen, z.B. aufgrund wechselnder Anforderungen an die
verfügbaren Ressourcen,
- Einbeziehung der Möglichkeit des Einsatzes anderer
Programmiersprachen oder von Werkzeugen,
- Isolierung derjenigen Systemteile, die von der zugrundeliegenden
Hardware, der Einbettung in ein Betriebssystem, der verwendeten
Entwicklungsumgebung oder von benutzten Werkzeugen, die nicht allgemein
verfügbar sind, abhängen,
- als Folge des letzten Punktes die Erleichterung der Portierbarkeit,
d.h. der Implementierbarkeit zur Ausführung auf anderen Maschinen,
unter anderen Betriebssystemen oder unter Einsatz anderer Werkzeuge oder
Entwicklungsumgebungen,
- und damit Interferenzfreiheit, dh von der Implementierung anderer Moduln
unabhängige
- Realisierbarkeit durch die Auswahl von Datenstrukturen und
Algorithmen, die dem Anforderungsprofil angepaßt sind,
- Entwickelbarkeit durch eine möglichst lose Kopplung an die benutzten
Moduln, also nur über deren Spezifikation, ohne jede Voraussetzung der
Kenntnis ihrer internen Daten oder Abläufe,
- Testbarkeit, d.h. Prüfung seiner einwandfreien Funktion gemäß der
Spezifikation - sowohl ohne Einbindung der Implementierung der benutzten
Moduln, als auch ohne Berücksichtigung des Kontextes, in dem er benutzt
wird,
- Wartbarkeit, d.h. Lokalisierbarkeit und Behebbarkeit auch spät
entdeckter Fehler, Anpaßbarkeit an andere Bedingungen der Benutzung
sowie Erweiterbarkeit seines Leistungsumfanges (nach Erweiterung der
Spezifikation) und damit Weiterentwickelbarkeit.
Die Unterscheidung dieser beiden Blickwinkel läßt die Forderung nach
getrennter Übersetzbarkeit von Spezifikation und Implementierung eines jeden
Moduls mit den Vorteilen erwachsen, daß
- ein System nach der Spezifikation aller Komponenten bereits vom Compiler
zumindest auf seine syntaktische Richtigkeit geprüft werden kann (was einen
Ansatz in Richtung auf eine Prüfung auf Widerspruchsfreiheit des geplanten
Systems darstellt und Probleme bei der Integration der Moduln zum ganzen
System vermeiden hilft),
- die Spezifikationen nach ihrer Festlegung gegen die nachträgliche
Veränderung von Implementoren geschützt werden können (eine Maßnahme,
die einen Schutzmechanismus gegen typische Schwierigkeiten bei der
Konstruktion größerer Systeme darstellt),
- bei einer alternativen Implementierung eines Moduls (bei gleicher
Spezifikation) nicht das ganze System, sondern lediglich diese
Implementierung neu übersetzt werden muß
- und daher die gleichzeitige Konstruierbarkeit verschiedener Moduln durch
verschiedene Personen von Grund auf unterstützt wird.
Als Folgerung aus diesen Überlegungen ergibt sich, daß in einem Projekt eine
Programmiersprache benutzt werden sollte, in der dieses Konzept von vornherein
als fester Sprachbestandteil enthalten ist. Zur Zeit wird Modula-2 eingesetzt;
diese Sprache bietet die
- Zerlegungsmöglichkeit von Moduln in Definitions- und
Implementierungsteil,
- Übersetzbarkeit der Implementierung eines Moduls bereits nach
Übersetzung seines eigenen und der benutzten Definitionsteile (also ohne
die Notwendigkeit der vorherigen Übersetzung der Implementierungen der
benutzten Moduln)
- und damit die Möglichkeit, die genannten Grundsätze einhalten zu
können.
Diese Vorteile zeichnen sie vor nicht modulorientierten imperativen Sprachen wie
z.B. FORTRAN, Cobol, Algol, BASIC, C oder Pascal deutlich aus, deren
Ausdrucksmöglichkeiten durch die postulierte Softwarearchitektur - selbst bei
bescheidenen Ansprüchen an deren Umsetzung - überfordert sind.
Eine Ausnahme bildet der Zugriff auf vorhandene fremdsprachliche
Bibliotheken: die Einbindung vorgefertigter Programmteile unter Einsatz von
Compilern mit entsprechenden Sprachanschlüssen kann ein taugliches Werkzeug zur
Lösung bestimmter Aufgaben sein.
In der Regel wird aber die Größenordnung eines Lehrprojektes den Einsatz
derartiger Hilfsmittel kaum erlauben, soweit sie über die direkte Nutzung von
Diensten des Betriebssystems z.B. zur Abfrage und Ansteuerung von Rechner- oder
Betriebssystemkomponenten wie Bildschirm, Tastatur, Dateisystem o.ä.
hinausgehen.
Christian Maurer, 10.5.1999