1 Grundlagen der Softwareentwicklung
Zunächst werden einige notwendige Grundlagen der Softwareentwicklung
vorgestellt, die für das Verständnis der Materie förderlich
sind. Diese kleine Einführung hilft auch dabei, eine Vorstellung davon
zu erhalten, wie die Programmentwicklung in größeren Projekten
überhaupt abläuft, und an welchen Stellen die jeweiligen theoretischen
OO-Programmierkonzepte ansetzen. Außerdem wird anhand dieser Übersicht
am ehesten deutlich, bei wie vielen Methoden noch intensive theoretische
Grundlagenforschung nötig sein wird, um die Softwareentwicklung zu
vereinfachen und einen wissenschaftlich eindeutigen, automatisch durchführbaren
Prozeß für die Programmierung zu realisieren. Durch genauere
theoretische Grundlagen wird es auch ermöglicht, planbare Ergebnisse
aus den einzelnen Entwicklungsschritten zu gewinnen, um die Softwareproduktion
von vornherein besser auf Arbeitsgruppen verteilen zu können. Wenn
ein Entwicklerteam planen kann, mit welcher Art Ergebnis sie bei der Arbeit
einer anderen Gruppe rechnen darf, so kann es seine eigene Tätigkeit
schon darauf einstellen, mit dem noch gar nicht fertigen Arbeitsteil der
anderen Gruppe optimal zusammenzuarbeiten. Dies funktioniert natürlich
nur sehr eingeschränkt, wenn zwei Teams auf ganz unterschiedlichen
Planungsebenen arbeiten.
Um eine Basis für das Verständnis der grundsätzlichen
Vorgehensweise bei der Programmentwicklung zu schaffen, folgt eine Übersicht
über die einzelnen dabei nötigen Schritte, die hier als Phasen
bezeichnet werden. Da die Phasen aufeinander aufbauen, werden sie meist
sukzessiv bearbeitet. Aber da sie unlösbar miteinander verknüpft
sind, können sich während der Durchführung auch notwendige
Abhängigkeiten voneinander ergeben, die überarbeitet werden müssen.
Sollten an einer Stelle Fehler auftreten, so sind diese häufig auf
Konzeptschwächen in der Planung und Durchführung der vorangehenden
Phasen zurückzuführen. Es muß also eine Phase erneut abgearbeitet
werden, damit fehlerfreie Software entsteht.
Hat
man sich beispielsweise schon dabei verplant, was ein Programm leisten
soll, und programmiert eifrig auf das vermeintliche Ziel hin, so kann man
am Ende noch mal ganz von vorne anfangen. Denn dann hat man zwar ein schön
entwickeltes Programm geschaffen, das aber dummerweise das falsche Problem
löst. Um so wichtiger ist es, daß jeder Programmierer, aber
auch jeder Projektleiter, der mit Softwareproduktion beschäftigt ist,
einen genauen Überblick über die Form der diversen Phasen hat.
1.1 Entwicklungsphasen
Die Bezeichnungen der Phasen sind keine speziellen Begriffe der Informatik,
sondern den allgemeinen Prinzipien zur Produktion integrierter Systeme
entliehen. Ihre spezielle Interpretation bei der Softwareerstellung2
wird im Anschluß an die Übersicht aufgeführt. Selbstverständlich
wird das Produkt der Softwareentwicklung ein Programmsystem sein und kein
System im herkömmlichen Sinn. Die Softwareentwicklung besteht unabhängig
von den verwendeten Design und Entwicklungsmethoden aus:
Phasen der Softwareentwicklung
|
|
Anm: Fehler in frühen Phasen wirken
sich weit drastischer auf das Projekt aus als Fehler späterer Phasen
Die Entwicklung von Software objektorientiert zu gestalten statt konventionelle
Produktionsmethoden zu verwenden, erfordert nur besonders intensive Änderungen
in den Phasen der Architektur (III), des Designs (IV) und syntaxbedingt
der Implementierung (V).
-
Problemdefinition: Erst
muß bei der Problemdefinition festgelegt werden, was das System überhaupt
lösen soll. Das geschieht in einfacher, natürlichsprachlicher
Form und völlig unabhängig von der Computerwelt. Schließlich
könnte ja bei einer Problemdefinition wie
"'Wir
müssen die Anzahl der Angestellten unserer Firma wissen" auch als
Lösungsvorschlag herauskommen: "Gib einem Angestellten einen Tag Zeit,
jeden zu zählen". Das ist jedenfalls sinnvoller als mühsam ein
Programm zu schreiben, was die Daten der Angestellten verwaltet, nur um
sie im Endeffekt zu zählen.
-
Forderungsanalyse: Die
Forderungsanalyse ist der erste Lösungsansatz. Sie legt explizit fest,
was genau ein System leisten soll. Eine frühe exakte Festlegung
gewährleistet, daß der Anwender die Funktion festlegt indem
er sagt, was benötigt wird, und nicht der Programmierer einbaut, was
gerade fehlerfrei geht. Je besser und ausführlicher die Forderungen
aufgeführt sind, desto weniger Funktionalität muß während
der Programmierung geändert werden, was zeitaufwendig und fehleranfällig
wäre.
Während
aus der Problemdefinition klar hervorgeht, wozu man ein Programm benötigt,
sollte in der Forderungsanlyse beschrieben werden, daß zum Beispiel
eine ansprechende graphische Oberfläche gewünscht wird, das System
mit anderen Programmen zusammenarbeiten soll und es zwischen mehreren Computern
Kommunikation und Datenaustausch ermöglichen soll.
-
Architektur: Bei der Architektur
wird eine sogenannte Architekturspezifikation festgelegt, die auf einem
hohen Abstraktionsniveau einen generellen Überblick über die
funktionell relevanten Teile eines Softwaresystems bietet. Dazu soll vor
allem ein Überblick über die Module und deren Zusammenspiel gegeben
werden. So soll im Endeffekt zu jedem Modul eine Beschreibung der erwarteten
Funktionalität und eine Einordnung seiner Schnittstellen in das restliche
System vorliegen. Die wichtigen Datenstrukturen zur Datenhaltung (Speicherung
von Daten) und Kommunikation müssen ausführlich dokumentiert
werden. Die Lösungsprinzipien der entscheidenden Schlüsselalgorithmen
werden festgelegt. Die Architektur ist in erster Linie auch die Phase,
in der erst entschieden wird, ob mit einer objektorientierten Lösung
die Forderungen an das System realisierbar sind. Während der Architektur
kann bei konventioneller Programmierung nur die Aufrufhierarchie der Funktionen
und die Beziehungen der Daten zueinander und deren Verwendung betrachtet
werden. Der OO-Ansatz hingegen verspricht bei komplexeren Systemen eine
bessere Art der Übersichtlichkeit und kategorisiert Bezüge zwischen
Architektureinheiten (z.B. Objekten, Modulen, Prozessen, Netzwerkservern)
viel feiner und strukturierter. Die Spezifikation wird dann in den folgenden
Phasen der Softwareentwicklung weniger interpretationsbedürftig. Des
weiteren müssen schon während der Architekturphase Überlegungen
in das Konzept einfließen, wie eventuelle spätere Änderungen
im System flexibel durchgeführt werden können. Wie Fehler während
des Programmlaufs behandelt werden sollen, muß auch schon festgelegt
sein, damit beim Design vereinfachende und trotzdem adäquate Annahmen
darüber gemacht werden können, daß das Programmsystem zu
jedem Zeitpunkt in einem fehlerfreien, konsistenten und eindeutigen Zustand
ist. In eine gute Architekturspezifikation gehört zusätzlich
eine Abgrenzung zu Alternativvorschlägen zusammen mit Gründen,
warum diese verworfen wurden. Im letzten Schritt sollte bei größeren
Projekten anhand dieser Spezifikation auch abgeschätzt werden, mit
welcher Leistung und welchem Hardwarebedarf zu rechnen ist. Sind die Forderungen
an die Hardware zu groß, sollten unmittelbar alternative Algorithmen
und Datenstrukturen eventuell an Testprogrammen ausprobiert werden.
-
Design: Beim Softwaredesign wird ein
detailliertes Konzept erstellt, nach dem das Programmsystem implementiert
werden soll. In der Designphase fallen im Idealfall alle endgültigen
Entscheidungen, die das Verhalten der Software in jedem möglichen
Bedienungsfall, die Algorithmen zu deren Ausführung und die Datenstrukturen
der zugrundeliegenden Informationen bestimmen. Während des Designs,
soll ein allgemeines, also von der exakten Programmiersoftware unabhängiges
Modell des endgültigen Systems entstehen, welches wiederum ein Modell
der Wirklichkeit sein kann. Das Modell muß alle relevanten Elemente
(Klassen, Objekte, Zustände, Operationen, Attribute) enthalten
und alle Beziehungen zwischen ihnen aufzeigen, die für die Problemlösung
oder die Programmverwaltung zuständig sind. Dazu wird grundsätzlich
klassifiziert in problemspezifische Sachverhalte und Notwendigkeiten auf
Anwendungsebene. Auf Anwendungsebene könnten zum Beispiel Objekte
zur Verwaltung der Benutzereingabe und Klassen für die graphische,
strukturierte Ausgabe benötigt werden. Alle Interaktionsstellen und
Schnittstellen zwischen funktional getrennten Softwaremodulen werden weitsichtig
entworfen. Das so entstandene Modell kann auch nach Abschluß des
Projekts noch als Dokumentation des Softwaresystems für spätere
Erweiterungen verwendet werden.
-
Implementierung: Um die in
der Designphase festgelegten Softwarekonzepte zu implementieren, werden
die Module und Routinen, die bisher nur konzipiert wurden, einzeln in eine
Sequenz von Befehlen umgewandelt (programmiert). Natürlich
wird die Implementierung von einem OO-Design bestimmt. Doch hier muß
quasi nur die entworfene Struktur mit den Syntaxelementen der zugrundeliegenden
Programmiersprache beschrieben werden. Siehe OO
in Java / Pascal.
-
Algorithmenverifizierung:
Während der Algorithmenverifizierung müssen die in der Implementierung
angewandten und häufig schon in der Designphase festgelegten, verwendeten
Algorithmen darauf hin überprüft werden, ob sie erstens korrekt
und fehlerfrei arbeiten und zweitens auch das gewünschte Ziel haben.
Außerdem sollte spätestens hier noch überlegt werden, ob
nicht andere, ähnliche Algorithmen das Teilproblem besser lösen
können.
So könnte
sich zum Beispiel mit einer eigens entworfenen Simulation ergeben, welches
Zählverfahren sinnvoller ist, um eine Menge Personen abzuzählen:
sie in eine Reihe stellen und vom ersten solange zum nächsten gehen
und mitzählen, bis man am Ende der Reihe ist; oder sollte man alle
Personen durcheinander aufstellen und jeweils zu einem hingehen, mitzählen,
und, damit man ihn nicht nochmals zählt, ihm einen Schuh ausziehen,
bis man niemanden mehr findet, der noch beide Schuhe an hat?
-
Modultest: Jetzt muß noch
jedes Modul, jeder funktionell zusammenhängende Teil, separat darauf
überprüft werden, ob es auch so funktioniert, wie es soll. Speziell
muß man darauf achten, ob ein Modul nur korrekte Anfragen
an andere Module stellt und nur fehlerfreie Ergebnisse auf
äußere Anfragen liefert.
-
Systemtest: Um die Zusammenarbeit
des gesamten Systems zu testen, kann nur in einem aufwendigen Probierverfahren
festgestellt werden, ob sich die einzelnen Module gegenseitig alle korrekt
verwenden und aufrufen. Dazu werden vor allem übergebene Daten protokolliert,
und jeder Wert wird bei seiner Verwendung auf seine Plausibilität
hin überprüft.
Die Anzahl
der Angestellten einer Firma beispielsweise sollte nun mal einfach nicht
negativ sein dürfen.
-
Wartung: Auch wenn ein Softwaresystem
korrekt arbeitet, erfordert es doch Wartung, um es dauerhaft sicher einzusetzen.
Neue Anforderungen an das System können dadurch entstehen, daß
Anpassungen an andere verwendete Programme benötigt werden. Oder es
zeigen sich einfach nur "aus unerfindlichen Gründen" noch nie aufgetretene
Fehler.
-
Verbesserungen: Nichts scheint
jemals perfekt zu sein. Besonders an den ständig neuen Programmversionen
kann man sehen, daß es fast immer wieder Verbesserungen an einem
System durchzuführen gibt. Dies liegt unter anderem daran, daß
Software an neue Hardware angepaßt werden muß oder erst mit
Hilfe dieser neuen Maschinen die Möglichkeit erhält, noch einfacher
anwendbare oder mächtigere Funktionen anzubieten.
1.2 konventionelle Entwicklungsmethoden:
Die konventionellen Methoden der Softwareentwicklung schreiben vor, Funktionen
in bestimmter Reihenfolge zu entwerfen und zu implementieren. Sie werden
hier vorgestellt um einen Vergleich zu den vollständigeren Methoden
objektorientierter Programmierung zu ermöglichen. Außerdem sind
auch die konventionellen Methoden an einigen Stellen anwendbar. Die wichtigsten
Arten herkömmlicher Entwicklungsmethoden sind:
-
Bottom-Up-Komposition:
-
Bei der Komposition werden erst untergeordnete Probleme gelöst, in
Programmcode umgesetzt und dann gemeinsame Aspekte untereinander zusammengefaßt
bis die höchste Ausführungs- oder Datenebene erreicht ist2.
-
Top-Down-Dekomposition:
-
Von der höchsten Ebene an wird stark formalisiert der Programmablauf
erstellt und die Implementierungsdetails der einzelnen Schritte werden
hierarchisch an die nächstniedrigere Ebene delegiert3,4.
Die in dieser Arbeit vorgestellten Hilfsmittel zur OO-Entwicklung dienen,
wie die (De-)Kompositionsmethoden nicht als grundsätzlich zum Ziel
führende Lösungsmethoden im Softwaredesign, sondern als zusätzlich
existierende Ansätze, die kombiniert und auf unterschiedlichen Ebenen
angewandt werden können.
So kann
z.B. die Funktionensammlung einer Mathematikbibliothek bottom-up als einfache
Zusammenstellung und Gruppierung von einzelnen Funktionen entstanden sein,
die intern aber Rechenoperationen durchführen, deren Funktionalität
top-down entwickelt wurde. Ein Framework für ein GUI
(Graphical User Interface für Oberflächen wie Windows)
kann objektorientiert verknüpfte Elemente enthalten, die in ihrer
Funktionalität und verwaltenden Anordnung aber top-down entworfen
sind.
Im Folgenden soll nun der OO-Ansatz genauer diskutiert werden, die
anderen werden hier nur noch für Vergleiche und Erläuterungen
angeführt. Auf keinen Fall jedoch sollte man aus dieser Konzentration
auf die Aspekte der OO-Entwicklung eine unbedingte Präferenz dieser
Methode ableiten. Prinzipiell muß immer problemabhängig abgewogen
werden, welches Entwicklungskonzept an welcher Stelle und in welcher Programmebene
greift.