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.
(Beispiel) 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
Art der Phase Entwicklungsphase
Umriß
  1. Problemdefinition oder Systemspezifikation
  2. Analyse der Forderungen an das System
Konzept
  1. Architektur des Systems: Entwurf auf höherer Ebene
  2. Design: detaillierter Entwurf
Realisierung
  1. Implementierung: Softwarebau, Programmierung
Test
  1. Prüfen und Bewerten der verwendeten Algorithmen
  2. Testen jedes Moduls
  3. Testen des gesamten Systems
Anpassung
  1. Wartung und Pflege: Fehlerkorrektur
  2. Verbesserungen: System überarbeiten
 
 
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).
  1. 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 (Beispiel)"'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.
  2. 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.

  3.  (Beispiel)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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

  8. (Beispiel)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?
  9. 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.
  10. 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.

  11. (Beispiel)Die Anzahl der Angestellten einer Firma beispielsweise sollte nun mal einfach nicht negativ sein dürfen.
  12. 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.
  13. 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.
(Beispiel)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.