193 74 10MB
German Pages 306 [308] Year 1979
de Gruyter Lehrbuch Kimm • Koch • Simonsmeier • Tontsch Einführung in Software Engineering
Einführung in Software Engineering von
Reinhold Kimm Wilfried Koch Werner Simonsmeier Friedrich Tontsch
W DE G Walter de Gruyter • Berlin • New York 1979
Die Verfasser sind Mitarbeiter im Institut für angewandte Informatik Fachgebiet Softwaretechnik der Technischen Universität Berlin. Das Buch enthält 56 Abbildungen und 9 Tabellen.
CIP-Kurztitelaufnahme der Deutschen Bibliothek Einführung in Software Engineering / von Reinhold Kimm . . . — Berlin : de Gruyter, 1979. ISBN 3-11-007836-8 NE: Kimm, Reinhold [Mitarb.]
© Copyright 1979 by Walter de Gruyter & Co., vormals G. J. Göschen'sche Verlagshandlung, J. Guttentag, Verlagsbuchhandlung Georg Reimer, Karl J. Trübner, Veit & Comp., Berlin 30. Alle Rechte, insbesondere das Recht der Vervielfältigung und Verbreitung sowie der Übersetzung, vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (durch Photokopie, Mikrofilm oder ein anderes Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. Printed in Germany. Druck: Hildebrand, Berlin. Bindearbeiten: Dieter Mikolai, Berlin.
5
Vorwort Seit 1976 führen die Autoren an der Technischen Universität Berlin Lehrveranstaltungen zum Thema "Software Engineering" durch. Ein Ergebnis dieser Arbeit ist das vorliegende Buch. "Software Engineering", dieser vor nun zehn Jahren als Antwort auf die Softwarekrise geprägte Begriff, der die geplante, systematische, ingenieurmäßige Herstellung großer Software betonen soll, ist auch heute noch eher Programm als Wirklichkeit. Noch immer verstehen Programmierer häufig allenfalls ihre eigenen Programme, werden Programme vor der Anwendung nur oberflächlich ausgetestet, sind erhebliche Verzögerungen in der Fertigstellung nicht Ausnahme, sondern Regel. In der Praxis gibt es kein gesichertes Bündel von Methoden und Hilfsmitteln für die einzelnen Phasen der Softwareproduktion, ja es herrscht nicht einmal Einigkeit über die Abgrenzung der Phasen. Aber auch in der Forschung sieht es nicht besser aus. Unser Buch soll deshalb eine "Einführung" in doppeltem Sinne sein: Der Leser soll an ein Gebiet herangeführt werden, das selbst noch am Anfang steht, eine Weiterführung muß nicht nur auf Seiten des Lesers, sondern auch auf Seiten der Forschung stattfinden. Außerdem wollen wir das Interesse für eine Auswahl von Konzepten, Methoden und Hilfsmitteln wecken, auch wenn diese häufig nicht hier und heute in die Praxis umzusetzen sind. Sie repräsentieren aber wichtige Entwicklungslinien der Softwareherstellung. Wir haben bewußt darauf verzichtet, mehr oder minder fragwürdige Rezepte zu liefern, auch wenn der Markt dafür im Augenblick sehr aufnahmebereit ist. Wir wollen vielmehr Diskussionen zur Systematisierung und Konsolidierung des Softwareherstellungsprozesses anregen und wünschen auch ausdrücklich Resonanz. Aus englischsprachiger Literatur entnommene Textstellen haben wir übersetzt.
6 Frau Doris Fähndrich hat uns nicht nur technisch unterstützt, sondern auch an allen unseren Manuskriptdiskussionen teilgenommen und damit den Inhalt mitbestimmt. Ihr und Frau Gabriele Ambach, die das Manuskript herstellte, gilt unser besonderer Dank. Außer vielen Studenten unserer Lehrveranstaltungen haben uns insbesondere Herr Ernst Denert, Frau Christiane Floyd, Herr Reinhold Franck, Herr Rainer Hahn, Herr Hans-Jörg Kreowski und Frau Ilse Schmiedecke Verbesserungsvorschläge gemacht oder uns auf Fehler hingewiesen. über den Sinn und Geschmack von Zitaten am Anfang von Kapiteln oder Abschnitten mögen di-e Leser geteilter Meinung sein; wir haben uns dabei an der von Informatikern so gerne zitierten "Alice im Wunderland" (Lewis Carroll) orientiert: "Und wen ffiji ex wen. Zweck habin BückeA," &a.gti kLLdi, keine
8iideA
"in dinm
ichZizßtich überhaupt
und UnteAhaZtungen vo>ikorme.n ?"
Im Februar 1979,
Reinhold Kimm Wilfried Koch Werner Simonsmeier Friedrich Tontsch
7
Inhaltsverzeichnis 1. Gegenstandsbestimmung
11
1.1. Historische Entwicklung
12
1.2. Was ist Software Engineering ?
15
1.3. Die Phasen der Softwareentwicklung und -Verwendung
18
1.3.1. 1.3.2. 1.3.3. 1.3.4. 1.3.5. 1.3.6. 1.3.7. 1.3.8.
Problemanalyse Entwurf Implementierung Funktions- und Leistungsüberprüfung Installation und Abnahme "Wartung" Rückgriffe im Entwicklungsprozeß Projektorganisation und Dokumentation
1.4. Zusammenfassung 2. Zielbestimmung
20 21 22 24 25 25 26 27 28 29
2.1. Softwareproduktion wozu ?
29
2.2. Anforderungen an den Softwareherstellungsprozeß
31
2.3. Anforderungen an das Produkt
32
2.3.1. 2.3.2. 2.3.3. 2.3.4. 2.3.5.
Zuverlässigkeit Benutzungsfreundlichkeit Flexibilität Lesbarkeit Effizienz
33 36 39 43 43
2.4. Zusammenhang der Qualitätsanforderungen
44
2.5. Zusammenfassung
47
3. Problemanalyse
48
3.1. Istanalyse
49
3.2. Erarbeitung eines Sollkonzepts
52
3.3. Darstellungsmethoden
54
3.3.1. Gebräuchliche Darstellungsmittel 3.3.2. Die SADT-Methode 3.3.3. Das ISDOS-Projekt 3.4. Durchführbarkeitsstudie
55 59 64 68
8 3.5. Projektplanung 3.5.1. 3.5.2. 3.5.3. 3.5.4. 3.5.5.
Umfang des Softwareprodukts Die Arbeitsproduktivität der Programmierer Zeitplan und Personal Verteilung Kostenschätzung und Sachmittel Automatische Unterstützung
3.6. Zusammenfassung 4. Dokumentation 4.1. Benutzerdokumentation 4.1.1. Inhalt der Benutzerdokumentation 4.1.2. Sprachmittel der Benutzerdokumentation 4.1.3. Form der Benutzerdokumentation 4.2. Entwicklungsdokumentation
70 70 73 76 79 81 83 85 85 86 87 88 90
4.2.1. Inhalt und Aufbau der Entwicklungsdokumentation
90
4.2.2. Gestaltung der Entwicklungsdokumentation
93
4.3. Technische Dokumentation
94
4.4. Zusammenfassung
95
5. Entwurf
96
5.1. Ein Zerlegungsbeispiel
101
5.2. Typen von Moduln
109
5.3. Beziehungen zwischen Moduln
112
5.4. Kriterien der Zerlegung
117
5.4.1. Einfachheit der Schnittstellen 5.4.2. Getrennte übersetzbarkeit 5.4.3. Geringe Modul große 5.5. Entwurfsmethodik 5.5.1. Die Top-down- oder Outside-in-Methode 5.5.2. Die Bottom-up- oder Inside-out-Methode 5.5.3. Vorschläge für praktikable Entwurfsmethoden 5.6. Zusammenfassung 6. Spezifikation
117 119 120 121 126 128 129 136 137
6.1. HIPO-Diagramme
139
6.2. Informelle Inhaltsverzeichnisse
143
9 6.3. Parnassche Spezifikation
145
6.4. Algebraische Spezifikation
150
6.4.1. 6.4.2. 6.4.3. 6.4.4. 6.4.5. 6.4.6. 6.4.7. 6.4.8.
Eine Beispielspezifikation Die Vorteile der algebraischen Spezifikation Datentypgeneratoren Das Problem der versteckten Funktionen Fehlerbehandlung Erweiterung auf andere Modul arten Schrittweise Implementierung und Verifikation Notwendige Weiterentwicklung der algebraischen Spezi fi kati onsmethode
151 154 156 157 159 161 164 169
6.5. Petri-Netze
169
6.6. Zusammenfassung
174
7. Realisierung von Modulkonzepten in Programmiersprachen
175
7.1. Modulare Zerlegung und algorithmische Sprache
176
7.2. Beurteilung von Zerlegungskonzepten in Programmiersprachen
176
7.3. Assemblersprachen
177
7.4. Klassische Programmiersprachen
179
7.4.1. Klassische Sprachen ohne Blockstruktur 7.4.2. Klassische Sprachen mit Blockstruktur 7.4.3. Weiterentwicklung klassischer Sprachen
179 181 182
7.5. Sprachen mit abstrakten Datentypen
186
7.6. Trennung von typ- und Modul begriff
193
7.7. ALPHARD: von der Spezifikation zur Implementierung
201
7.8. CDL2: eine Sprache mit mehrstufigem Zerlegungskonzept
206
7.9. Zusammenfassung
210
8. Programmierung im Kleinen 8.1. Elemente der Programmierung im Kleinen 8.1.1. 8.1.2. 8.1.3. 8.1.4.
Schrittweise Verfeinerung und Verbalisierung Beschreibung des Steuerflusses Datenkonstrukte Prozeduren
211 214 214 220 228 234
8.2. Graphische Darstellungen der Programmstruktur
234
8.3. Programmentwicklung und algorithmische Sprachen
237
10 8.4. Programmverifikation 8.4.1. Verifikation mit Hilfe von Zusicherungen 8.4.2. Zusicherungen als Konstruktionshilfsmittel
9.
239 247
8.5. Zusammenfassung
253
Testen
255
9.1. Fehlerarten und -Ursachen
255
9.2. Testphasen und Teststrategie
258
9.3. Durchführung des Testens
260
9.3.1. Kriterien zur Auswahl der Testdaten
260
9.3.2. Automatische Unterstützung
263
9.4. Symbolische Programmausführung
10.
238
265
9.4.1. Grundlagen 9.4.2. Anwendung der symbolischen Programmausführung
265 270
9.4.3. Praktische Bedeutung
271
9.5. Finden und Beheben von Fehlern
272
9.6. Hilfsmittel zum Auffinden von Fehlern
273
9.7. Effizienztest und Optimierung
275
9.8. Zusammenfassung
277
Projektmanagement
278
10.1. Einbettung der Softwareherstellung in die BetriebsOrganisation
278
10.2. "Klassische" Projektmodelle
281
10.3. Das Chef-Programmierer-Team
284
10.4. Probleme der Team-Organisation
286
10.5. Zusammenfassung
289
Literaturverzeichnis
291
Stichwortverzeichnis
301
11
ComputeA iplette veAAückt Stockholm (dpa). Ein neues VatenveAaAbectungay-item hat die Finanzen deA ichwedtschen FM.nmeXde.vMwaltu.ng gründlich durcheinandergebracht. VeA ComputeA wandelte alte. Anforderungen beim zentralen MatetlaJULager In NcLiijö In Aufträge für die. luLieferlndu&tALe um. Vle folge, wasi, daß deA. MateAlalnachichub In den FMnmeJ.debeztA.ken ausblieb, da6 lentAaltager überquoll und In deA Kaue deA Telefonverwaltung wegen deA Flut von Rechnungen plötzlich Ebbe waA. Um Löhne und Gehälter zahlen zu können, mußte die Fernmeldeverwaltung einen 120-Millionen-Kredit aufnehmen. Der Tagesspiegel, 16.9.78
1. Gegenstandsbestimmung Die obige Meldung i s t nicht das einzige Beispiel fehlerhafter EDVSysteme; nur die spektakulärsten oder erheiterndsten F ä l l e gehen durch die Presse. Zwar i s t es dort meist "der Computer" oder "das Elektronengehirn", das "einen Befehl verweigert", etwas "ausspuckt" oder Dinge "durcheinanderbringt", aber in den wenigsten Fällen l i e g t der Fehler w i r k l i c h in der Hardware, meistens dagegen in der Systemsoftware (Betriebssystem) oder in der Anwendersoftware. Dies führt
12
1.1.
Gegenstandsbestimmung
gelegentlich zu komischen Effekten: eine britische Elektrizitätsgesellschaft, deren Mahnwesen über EDV lief, mahnte einen Kunden dreimal, die Stromrechnung Uber 0 Pfund, 0 Shilling, 0 Pence zu begleichen, und drohte schließlich mit Stromsperre, bis der entnervte Kunde den fälligen Betrag überwies.
1.1. Historische Entwicklung E-tne GZQ&WXIHZ o hm VeAgangznheJjt hat keXm
Zukunft.
Seit es Rechner und damit auch Programme gibt, existieren auch "fertige" und in Gebrauch befindliche Programme, die fehlergespickt sind. Dies war in der Anfangsphase kein so erhebliches Problem wie heute. Die ersten Rechner wurden vorwiegend im Bereich von Naturwissenschaft und Technik eingesetzt, wo es vor allem Gl ei chungs- und Differential gl eichungssysteme zu lösen galt (z.B. zur Berechnung von Geschoßbahnen). Hierbei bestanden die Aufgaben der Programmierer nicht - wie heute - im Lösen von Anwendungsproblemen, sondern vielmehr darin, bereits bekannte Lösungsverfahren in effiziente Programme umzusetzen. Außerhalb dieses Anwendungsbereiches wurden Rechner vor allem in Entwicklungslabors eingesetzt, wo die Hardware-Entwicklung im Vordergrund stand. Insgesamt gilt für diese Anfangsphase, daß die entwickelten Programme noch vergleichsweise klein und die zu lösenden Probleme weniger komplex waren als heute. Dementsprechend hielt sich die Anzahl der Programmfehler in Grenzen. Durch den eng begrenzten Einsatzbereich der Rechner waren die Auswirkungen der Fehler ebenfalls begrenzt. Zum wirklichen Problem wurde die Fehlerhaftigkeit von Software erst, als die Rechner als Rationalisierungsinstrument Einzug in die Büros hielten und in großer Anzahl eingesetzt wurden. Die durch die Hardwareentwicklung bis zur 3. Rechnergeneration (Einsatz von integrierten Schaltkreisen) geschaffenen Möglichkeiten überstiegen bei weitem
1.1.
Historische Entwicklung
13
die bis dahin entwickelten Fertigkeiten der Programmierung. Die Programmiertechniken, mit deren Hilfe man immer größere Programme schrieb, waren die gleichen geblieben wie zuvor. Programmieren war nicht Technik, sondern Kunst. Hinzu kam, daß die meisten Programmierer nicht für ihre Tätigkeit ausgebildet, sondern nur angelernt waren. Auf der organisatorischen Seite der Programmierung versuchte man unter dem Druck der enormen Expansion der DV-Industrie, der viele und große Programme erzwang, die fehlende Programmiererausbildung und die ungenügenden Techniken durch den "million monkey approach" wettzumachen: Programmsysteme wurden von hunderten und tausenden Programmierern entwickelt. Dies alles führte in einem solchen Maße zu unkorrekten Programmen und Verzögerungen von Projektabschlüssen, daß man um 1965 von der "Softwarekrise" zu sprechen begann. Da Anwender und Hersteller von Software nicht in der Lage waren, Wege zur Oberwindung der "Softwarekrise" zu erforschen und zu begehen, wurden von Seiten des Staates auf nationaler und internationaler Ebene Forschungs- und Ausbildungsprogramme ins Leben gerufen und finanziert, die zur Bewältigung der neuartigen DV-Probleme Methoden entwickeln und Fachleute ausbilden sollten. In dieser Zeit wurden die ersten beiden Datenverarbeitungs-Förderungsprogramme verabschiedet, mit deren Hilfe auch die InformatikStudiengänge an den Hochschulen der BRD und an der TU Berlin aus der Taufe gehoben wurden. Im Informatik-Studium soll eine neue Generation von EDV-Fachleuten ausgebildet werden, die die Generation der meist in Firmenregie schlecht ausgebildeten "Bit-Fummler" ablöst. Eigentliche Softwaretechnik-Ausbildung, die die mit der Herstellung von größerer Software verbundenen Probleme systematisch angeht, befindet sich an westdeutschen Hochschulen entsprechend der historischen Entwicklung der Forschung heute jedoch zumeist noch im Aufbau.
14
Gegenstandsbestimmung
1.1.
Auf der Forschungsebene fanden die Anstrengungen zur Entwicklung einer guten Softwaretechnologie ihren sichtbarsten Niederschlag in den beiden von der NATO veranstalteten Software-Engineering-Konferenzen in Garmisch 1968 [Naur68] und Rom 1969 [Buxton69]. Der Begriff "Software Engineering" wurde auf diesen Konferenzen wohl in provokatorischer Absicht geprägt. Er sollte anzeigen: Abkehr von der "Kunst" des trickreichen, egoistischen Individualprogrammierens, Hinwendung zu einem planvollen, kooperativen Teamprogrammieren. Man versuchte erstmals, die Softwareproduktion als integrierten Prozeß wissenschaftlich zu durchdringen, wodurch Fragen der Spezifikation und Dokumentation, die bis dahin nie Gegenstand systematischer, geschweige denn wissenschaftlicher Diskussion gewesen waren, in den Mittelpunkt des Interesses rückten. Inzwischen sind wichtige Schritte auf dem Weg zur ingenieurmäßigen Herstellung von Software getan worden. So ist Software Engineering seit der Zeit der NATO-Konferenzen in der wissenschaftlichen Diskussion immer weniger Nebenbeschäftigung ansonsten mit anderem befaßter Forscher
und avanciert zur Zeit zum selbständigen Fachgebiet
neben anderen wie Betriebssysteme, Compilerbau, etc. Diese Entwicklung trägt der wachsenden ökonomischen Bedeutung der Softwareproduktion Rechnung. Boehm [Boehm76] schätzt die in den USA 1976 für Software aufgewendeten Mittel auf $ 20 Milliarden. Das sind mehr als 2% des US-Bruttosozialprodukts. Diese Zahl ist im Steigen begriffen, da sich die Gesamtausgaben für die DV ständig erhöhen und innerhalb dieser die Software gegenüber der Hardware einen immer größeren Raum einnimmt.
1.2.
Bild 1-1:
Was ist Software Engineering
15
Hardware/Software-Kostenverhältnis aus [Boehm76]
1.2. Was ist Software Engineering? In der ENCYCLOPAEDIA BRITANNICA finden wir, daß das Engineers Council for Professional Development der USA Engineering als "die schöpferische Anwendung von wissenschaftlichen Prinzipien auf Entwurf und Entwicklung von Strukturen, Maschinen, Apparaten oder Herstellungsprozessen, ...; alles im Hinblick auf eine gewünschte Funktion, Handlungsökonomie und Sicherheit von Leben und Eigentum ..." definiert hat. Dennis wendet diese Definition auf Software Engineering an: "Software Engineering ist die Anwendung von Prinzipien, Fähigkeiten und Kunstfertigkeiten auf den Entwurf und die Erstellung von Programmen und Systemen von Programmen." [Dennis75]
16
Gegenstandsbestimmung
1.2.
Parnas beschreibt die äußeren Umstände, unter denen Programmieren zu Software Engineering wird: "Software Engineering ist Programmierung unter mindestens einer der folgenden beiden Bedingungen: -
mehr als eine Person ist befaßt mit der Erstellung und/oder dem Gebrauch des Programms und
-
mehr als eine Version des Programms wird erstellt werden." [Parnas75]
F.L.Bauer schließlich beschreibt die Ziele von Software Engineering: "ökonomisch Software zu erhalten, die zuverlässig ist und effizient auf realen Maschinen arbeitet." [Bauer73a] Diese Aussagen ergeben zusammen genommen ein brauchbares erstes Bild vom Fachgebiet und vom Inhalt dieses Buches. Dennoch bedarf es einer Abgrenzung gegenüber anderen Teilbereichen der Informatik und gegenüber anderen Wissensgebieten. Dazu die folgenden Thesen: 1.
Die Herstellung großer Software unterscheidet sich qualitativ von der Herstellung kleiner Software.
Die "traditionellen" Methoden und Werkzeuge der Programmierung sind auf die "Programmierung im Kleinen" zugeschnitten. Dies gilt nicht nur für die Programmiersprachen, sondern auch für Darstellungsmittel (Flußdiagranme u.ä.) und Programniermethoden wie die "Strukturierte Programmierung". Es sind deshalb besondere Methoden und Hilfsmittel für die Entwicklung und Betreuung großer Programmpakete zu entwikkeln, die mit für die Programmierung im Kleinen geeigneten Methoden und Mitteln verträglich sind. 2.
Zentraler Punkt der Programmierung auf allen Ebenen ist die Bewältigung von Komplexität.
Die Fähigkeit des Computers, viele logische Operationen sehr schnell auszuführen, hat vielfach zu dem Mißverständnis geführt, daß ein Rechner komplizierte logische Zusammenhänge auflösen könne. Dies ist jedoch die Aufgabe des Programmierers. Ohne eine Zerlegung, die die
1.2.
Was ist Software Engineering
17
zu jedem Zeitpunkt zu bewältigende Komplexität gegenüber der gesamten reduziert, sind die Programmierer nicht in der Lage, das Problem in allen seinen Facetten zu überschauen; und es ist deshalb für sie unmöglich, ein Programm(teil) zu schreiben, auf das sie selbst vertrauen können oder dessen Korrektheit sie gar anderen hinreichend plausibel machen können. Welche verheerenden Folgen unbewältigte Komplexität auf die Programmqualität hat, hat Dijkstra sehr einfach vorgerechnet [Dijkstra72]: "Wenn die Wahrscheinlichkeit, daß eine einzelne Komponente korrekt ist, p ist, dann ist die Korrektheitswahrscheinlichkeit des gesamten Programms, das aus N solchen Komponenten besteht, sowas wie P = pN. Wenn nun N sehr groß ist, so muß p schon sehr, sehr nahe bei 1 liegen, wenn wir möchten, daß P überhaupt wesentlich von Null di fferiert." Zur Verdeutlichung ein kleines Zahlenbeispiel: für N = 10 ist die Wahrscheinlichkeit p^ der Korrektheit des Gesamtsystems für p = 0.99 immerhin noch 0.9, für p = 0.9 jedoch nur noch 0.35 . Hat man gar N = 100 Programmteile, deren Korrektheitswahrscheinlichkeit p = 0.99 ist, so ist p N = 0.37 , für p = 0.9 sogar nur noch p N = 0.000027 ! 3.
Geregelte Zusammenarbeit zwischen den am Softwareherstellungsprozeß Beteiligten ist integraler Bestandteil der Programmierung im Großen.
Ebensowenig wie es bei großen Programmen möglich ist, ein Problem und seine Lösungen zu einem Zeitpunkt in Gänze zu überschauen, sowenig ist es auch möglich, ohne formalisierte, d.h. geregelte
Kom-
munikation und Dokumentation Terminverzögerungen, falsch aufeinander abgestimmte Arbeiten, unvollständige und fehlerhafte Arbeiten zu vermeiden. Worüber, wann und wie in einem Softwareentwicklungs-Team kommuniziert wird, muß auf den Herstellungsprozeß abgestimmt sein. Da dafür lange Zeit ein entsprechendes Problembewußtsein fehlte und darüber auf den ersten Blick nicht sehr viel Wissenschaftliches gesagt
18
Gegenstandsbestimmung
1.3.
werden kann, ist dazu auch in der traditionellen Softwareforschung und -ausbildung kaum etwas gesagt worden. Software Engineering muß sich aber, will es seine Ansprüche einlösen, damit ebenso beschäftigen, wie mit unmittelbar auf das Produkt gerichteten Methoden. 4.
Die Herstellung großer Programme ist keine schwarze Kunst, sondern weist Ähnlichkeiten mit der Produktion anderer technischer Produkte auf.
Die Tatsache, daß mit der EDV geistige Tätigkeiten automatisiert werden, führte zu einer Verabsolutierung des Besonderen an der Software- gegenüber anderer Produktion. Gegen diese Anschauung zielt vor allem der Begriff "Software Engineering", der die Anwendung ingenieurmäßiger Prinzipien auf die Softwareherstellung, insbesondere in der Entwurfsphase, postuliert. So liegen denn auch allen in den letzten Jahren entwickelten Methoden Prinzipien zugrunde, die in anderen Wissensgebieten längst bekannt sind. Dies gilt auch dann, wenn es den Autoren nicht bewußt ist, von denen manche von Zeit zu Zeit "das Rad der Softwaretechnologie" erfinden.
1.3. Die Phasen der Softwareentwicklung und -Verwendung Nachdem wir auf allgemeiner Ebene einen Begriff von Software Engineering entwickelt haben, wollen wir im folgenden einen Überblick über Fragestellungen und Vorgehensweisen bei Softwareentwicklung und Verwendung geben. Dazu wurde das Modell eines "Software Life Cycle" entwickelt, das den Weg eines Softwareprodukts vom Beginn seiner Erstellung bis zum Ende seiner Verwendung skizziert.1
1
Andere Autoren geben andere Software Life Cycles an, die sich darin unterscheiden, daß sie einige der Phasen zusammenfassen oder weiter aufteilen. Ein Überblick über verschiedene Modelle findet sich in [Peters78]. Warum wir uns für den in Bild 1-2 dargestellten entschieden haben, wird aus den Erläuterungen des Buches hervorgehen.
Entwicklungsphasen
1.3.
Bild 1-2:
19
Software Life Cycle
Software nach dem Schema eines Software Life Cycle zu entwickeln, bedeutet bereits einen ersten Versuch, Projekte so zu organisieren, daß ihr rechtzeitiger erfolgreicher Abschluß durch Kontrolle des Projektfortschritts an festgelegten Meßpunkten (Phasenabschluß) und durch rechtzeitiges Eingreifen sichergestellt wird. Es bedeutet auch, einen Problemwust derart zu organisieren, daß das gerade Bearbeitete in übersichtlichem Rahmen bleibt. Wir werden im folgenden einige Erläuterungen zu den jeweiligen Phasen geben. Die für die jeweiligen Projektabschnitte in Frage kommenden Methoden und Hilfsmittel werden dann ausführlich ab Kapitel 3 behandelt.
20
Gegenstandsbestimmung
1.3.1.
1.3.1. Problemanalyse In der Problemanalysephase geht es darum, das zu lösende Problem und alle wichtigen Umgebungsbedingungen möglichst vollständig und eindeutig zu beschreiben und die Durchführbarkeit der geplanten Softwareentwicklung zu untersuchen. Zu den Umgebungsbedingungen zählen vor allem: -
Benutzerprofi 1 (Art und Anzahl der Benutzer, Fachwissen);
-
Systemumgebung (Hardwarekapazitäten, Systemsoftware).
Den Kern der Problemanalyse bildet die Erstellung der Systembeschreibung, bei der es darum geht, die mehr oder minder verschwommenen Wünsche des Auftraggebers als Systemfunktionen und als Leistungsparameter verständlich aber möglichst exakt zu formulieren. Teile der Systembeschreibung sind: -
gewünschte Funktionen (ggf. schon in Form des Benutzerhandbuchs);
-
Beschreibung korrekter und falscher Eingabedaten und gewünschter Ausgabedaten;
-
mögliche Systemerweiterungen in der Wartungsphase;
-
Dokumentationsanforderungen;
-
Leistungsparameter (Effizienz; Zuverlässigkeit).
In einer Durchführbarkeitsstudie wird untersucht, ob die entwickelten Vorstellungen über das Programmsystem überhaupt realisierbar sind: -
technische Durchführbarkeit (Lösbarkeit der Probleme überhaupt; Realisierbarkeit der Anforderungen unter den Umgebungsbedingungen);
-
ökonomische Durchführbarkeit (Gesamtkosten; Zeitplan und Personalaufwand; Kosten/Nutzen-Verhältnis; Risiken).
1.3.2.
Entwurf
21
Vom Ergebnis der Durchführbarkeitsstudie hängt es ab, ob das Projekt fallen gelassen wird, die Anforderungen revidiert werden, oder entschieden wird, das Projekt durchzuführen. Je sorgfältiger die Problemanalyse, desto wahrscheinlicher sind die dabei gefällten Entscheidungen r i c h t i g . In den meisten Fällen i s t es daher unbedingt erforderlich, daß die Problemanalyse in Zusammenarbeit von Fachleuten des Anwendungsgebietes und von Programmierern (das sind alle an der Softwareentwicklung unmittelbar Beteiligten) durchgeführt wird. Die Anforderungsdefinition 3 als resultierendes Dokument der Phase dient der Verständigung von Auftraggebern und Softwareentwicklern. Sie i s t die einzige und verbindliche Grundlage für die Abnahme der erstellten Software und damit auch juristisches Dokument. 1.3.2. Entwurf
Ja, mach nuA. ebenen Plan Sei nun ein gieße,& Licht !
...
Bert Brecht: Die Dreigroschenoper In der Problemanalysephase wurden Anforderungen an ein Softwaresystem losgelöst von ihrer Realisierung gestellt und in einer Form beschrieben, in der sie keine Implementierungsanleitung geben. Aufgabe der Programmierer i s t es in der Entwurfsphase, ein Modell des Gesamtsystems zu entwickeln, das, zum codierten Programm konkretis i e r t , die Anforderungen e r f ü l l t . Solch ein Modell zu bilden bedeutet, das komplexe Gesamtsystem in überschaubare Einzelteile
2
Den Begriff "Anforderungsdefinition" haben wir der neueren Diskussion im englischen Sprachraum entnommen. Er findet sich dort als "requirement definition" [Ross77] und lehnt sich eng an die Bezeichnung für die Problemanalyse an: "requirement analysis" [IEEE77]. Der im deutschen Sprachraum öfters verwendete Begriff "Pflichtenheft" hebt uns zu stark den juristischen Charakter des Dokuments hervor und vernachlässigt einige Inhalte. Häufig verwendet wird auch der Begriff "Spezifikation", was u.E. auf Unklarheiten über die Trennung von Entwurfs- und Implementierungsphase zurückzuführen i s t .
22
Gegenstandsbestimmung
1.3.3.
- Moduln - zu zerlegen, die Funktionen dieser Einzelteile und ihre Beziehungen - Schnittstellen - zu beschreiben. Mit Hilfe von Spezifikationsmethoden werden die Moduln und ihre Schnittstellen beschrieben. In der modernen Softwareentwicklung nimmt die Entwurfsphase gegenüber der Implementierungsphase einen immer breiteren Raum ein. Die traditionelle Einschätzung von Problemanalyse und Entwurf als lästige Vorarbeiten zur "Programmierung" hat sich als verheerend in ihrer Wirkung auf die Qualität von Programmen herausgestellt. In Projekten, die nach dieser veralteten Anschauungsweise durchgeführt wurden, wuchs scheinbar die Programmiererproduktivität (weil diese in Statements/Monat gemessen wurde), in Wirklichkeit aber wuchs der Softwaremüllabladeplatz. Der Sinn der Trennung von Entwurfs- und Implementierungsphase besteht nämlich darin, das Entwickeln eines Programmkonzepts von der Realisierung des Programms zu trennen. Dadurch kann man sich eher auf die Entwurfsmaximen konzentrieren und wird nicht abgelenkt durch Detailprobleme bei der algorithmischen Ausformulierung von Prozeduren. Neuere Spezifikationsmethoden versuchen daher, die Modulfunktionen eher im Sinne der Mathematik durch Angabe von Definitions-, Wertebereich und Effekt zu beschreiben, als durch Algorithmen. Ein so formuliertes Systemmodell, das wir als Spezifikation bezeichnen, i s t dann auch einerseits geeignet, Bezugspunkt für den Nachweis der Korrektheit einer Implementierung zu sein. Andererseits läßt sich von einem solchen Modell besser zeigen, daß es der Anforderungsdefinition gerecht wird als von einem fertig codierten Programm. 1.3.3. Implementierung Auf dieser Stufe der Softwareentwicklung stehen für die Programmierer nicht mehr das Programmsystem als Ganzes, sondern die einzelnen Moduln im Mittelpunkt. Es kann von der Spezifikation der Modulfunk-
1.3.3.
Implementierung
23
tionen in Form ihres Ein-/Ausgabe-Verha1tens und ihres Effektes ausgegangen werden. Datenrepräsentationen und Steuerfluß jedes einzelnen Moduls und seiner Funktionen werden dann definiert und implementiert. In der Regel schließt die Implementierung auch den Modul test, d.i. die Überprüfung der Modulfunktionen, mit ein. Dazu muß die Applikationsumgebung, d.h. die den Modul benutzenden Moduln, geeignet simuliert werden. Wie wir die herkömmliche Programmierung in die beiden konzeptionell verschiedenen Phasen Entwurf und Implementierung aufgetrennt haben, so läßt sich ähnlich die Implementierungsphase selbst unterteilen. Hat man das Projekt mit einer der üblichen Programmiersprachen COBOL, FORTRAN, Assembler o.a. durchzuführen, so bietet es sich an, einen Modul zunächst in abstracto, d.h. in einer "Sprache" zu "implementieren", die die Abstraktion von für die Problemlösung unwichtigen Details erlaubt. Damit verhindert man, daß bestimmte Eigenschaften der Implementierungssprache zu unzweckmäßigen Lösungen verleiten. Hat man die Algorithmen und Datenstrukturen entwickelt, codiert man sie dann in der gewünschten
Implementierungssprache.
Obwohl in dieser Phase die einzelnen Untergruppen, die am Projekt beteiligt sind, ihre Moduln aufgrund der vorgegebenen Spezifikationen unabhängig voneinander bearbeiten können, ist eine gut organisierte Kommunikation notwendig. Denn: die Spezifikationen könnten fehlerhaft (mehrdeutig) sein, und sie könnten falsch interpretiert werden. Ziel der Implementierung ist es, ein in der gewünschten Systemumgebung lauffähiges Programmpaket herzustellen, das so codiert und dokumentiert ist, daß es übersichtlich, leicht verständlich und Korrektheitsbetrachtungen zugänglich ist. Dagegen sollen nicht durch ausgebuffte Tricks "optimierte" Programmteile abgeliefert werden, da darunter zum einen die Verständlichkeit und Änderbarkeit (und garantiert auch die Korrektheit) leidet, zum anderen im allgemeinen auch das Gesamtprogramm keineswegs effizient wird.
24
Gegenstandsbestimmung
1.3.4.
1.3.4. Funktions- und Leistungsüberprüfung VeA Spaß iit,
uiznn mit &eÄ.nm eA.gz.nzn
PuZvzA de/i FzuzAweAkeA
aa^Zxzgt.
Shakespeare: Hamlet Was in dieser Phase geschieht, ist von den in Entwurf und Implementierung gewählten Methoden abhängig. War man beispielsweise in der Implementierung strikt nach der Top-down-Methode vorgegangen, d.h. von einer abstrakten, problemnahen algorithmischen Formulierung durch schrittweise Verfeinerung in Richtung zugrundeliegendes System, so muß während der Implementierung auf jeder algorithmischen Ebene verifiziert werden, so daß am Ende der Implementierung auch das Ende der Verifikation erreicht ist. Diese Vorgehensweise setzt aber sowohl voraus, daß die Top-down-Methode wirklich einsetzbar ist, als auch, daß den Programmierern geeignete Verifikationsverfahren zur Verfügung stehen. Da beide Voraussetzungen nicht erfüllt sind, ist die Vorgehensweise eher Programm der Forschung als Realität. In der Regel liegt die erste Fassung des Gesamtsystems erst mit dem Abschluß der Implementierung vor: Eine Funktionsüberprüfung, der Integrationstest, schließt sich an. Für diesen ist es wichtig, daß man während aller vorherigen Projektphasen die Testfälle bereits gesammelt hat, da einem während der Systementwicklung
Fehlerfälle
eher auffallen, als wenn man sie nach Beendigung der Implementierung zu suchen beginnt. Leistungsmessungen werden vorgenommen, nachdem das Programm funktional korrekt ist. Entspricht die gemessene Leistung nicht der Anforderungsdefinition, müssen die kritischen Stellen ermittelt und dort Optimierungen vorgenommen werden. Optimiert werden darf tatsächlich erst, wenn die Funktionsfähigkeit des Systems hinreichend nachgewiesen ist. Hält man das nicht ein, schafft man ungeahnte (und unauffindbare) Quellen neuer Fehler.
1.3.6.
Wartung
25
Ziel dieser Phase ist es, das den Anforderungen genügende Softwaresystem in der Entwicklungsumgebung dokumentiert erstellt zu haben. 1.3.5. Installation und Abnahme Die Installation ist als Meilenstein gewichtiger denn als eigene Phase. Viele Softwaresysteme werden mit simulierter Basismaschine (Rechner, Systemsoftware) entwickelt. Installation heißt dann zunächst: Obertragen des Systems in seine reale Umgebung. Schwierigkeiten kann diese Phase bereiten, wenn (z.B. bei einem EchtzeitSystem) ein altes Programm in der laufenden Produktion ersetzt werden muß. In der Regel wird das System dann zunächst parallel mit dem zu ersetzenden betrieben. Der Auftraggeber nimmt ein Softwaresystem nach der Installation ab, wenn es den bereits in der Problemanalysephase festgelegten Anforderungen genügt. 1.3.6. „Wartung" ...
und -Lit
de dzutiahz
¿¿M'heAvoAAagzn-
E¿gentuiX, eÂn weJüt-
geAckicIvtLidieA
Guchahnib
durch cuu> d&n Welt -icha^zn
dazu
wo ¿¿in,
daß man zi -in An^ühAungi-
AtfLicht
&ztzt.
Kurt Tucholsky, 1928 Die meisten Programme, die nicht zu Ausbildungszwecken geschrieben werden, sind so groß und ihre Erstellung so teuer, daß ihre Lebenszeit oft viel größer als ursprünglich erwartet ist. Diese überdauert nicht selten die der Rechner, auf denen die Programme entwickelt bzw. erstmalig eingesetzt wurden. Die Kosten für die Betreuung laufender Programme werden heute dementsprechend auf über 50% der gesamten für Software aufgewandten Kosten geschätzt. In der Zukunft wird der Kostenanteil noch höher sein (s. Bild 1-1).
26
1.3.7.
Gegenstandsbestimmung
Obwohl Programme nicht technisch verschleißen können, haben Computerhersteller für die Betreuung laufender Programme den irreführenden Begriff "Wartung" geprägt. Damit wird die mühsame Flickarbeit an fehlerdurchsetzten Programmen als normale Kundendienstarbeit kaschiert. Eigentlicher Hauptinhalt dieser Phase sollten jedoch Anpassungen an neue Anforderungen sein. Solche Veränderungen oder Erweiterungen an einem korrekten Programm sind z.B. notwendig, wenn bestimmte Algorithmen gegen bessere ausgetauscht werden, das System an eine neue Basismaschine angepaßt werden muß, wenn neue Benutzerfunktionen benötigt werden oder schon bestehende verändert werden sollen. Alle Programme veralten schließlich einmal derart, daß eine Verbesserung nicht mehr sinnvoll ist. Häufig ist dies der Ausgangspunkt für ein neues Projekt: der Programmentwicklungszyklus beginnt von neuem.
1.3.7. Rückgriffe im Entwicklungsprozeß —
Und mach dann noch
zweJjtm Gzhn tun
'nzn
Plan e bzldz
nickt.
Bert Brecht: Die Dreigroschenoper Natürlich ist es nicht möglich, Software in der hier vorgestellten Reinform eines Modells zu entwickeln. Da Softwareentwicklung oft Umsetzung von etwas Verschwommenem, nicht voll Ausformulierbarem (Wünsche eines Auftraggebers) in etwas Formales (Programm) ist, kann auch angezweifelt werden, daß diese Reinform prinzipiell möglich ist. Dies darf aber auf keinen Fall als Vorwand dafür gelten, Software in hemmungsloser Wurstelei zu entwickeln. Vielmehr soll unsere Einschränkung der Gültigkeit des Software-Life-Cycle-Modells nur auf die Notwendigkeit bestimmter Rückgriffe auf bereits abgeschlossene Phasen verweisen.
1.3.8.
Projektorganisation
27
Solche Rückgriffe sollten sich weitestgehend auf die jeweils zuletzt abgeschlossene Phase beschränken, was um so leichter fällt, je gründlicher gearbeitet worden ist. Beispiele für solche Rückgriffe können sein, daß in der Entwurfsphase notwendige Eigenschaften der Basismaschine entdeckt werden, die man bei der Problemanalyse nicht gesehen hat. Ebenfalls einen Rückgriff auf die Problemanalyse bewirken mißverständliche oder fehlende Informationen über die Aufgaben des Programms (leicht möglich bei Extremfällen). Rückgriffe von der Implementierungs- auf die Entwurfsphase sind nötig, wenn sich etwa herausstellt, daß eine bestimmte zu implementierende Funktion Informationen benötigt, die ihr noch nirgendwo zur Verfügung gestellt werden. Bei Verwendung einer wenig formalen Spezifikationssprache ist es auch möglich, daß dem Implementierer die Spezifikation nicht genau genug ist. Vom Modell des Software Life Cycle regelrecht induziert sind Rückgriffe von der Phase der Funktions- und Leistungsüberprüfung aus. Die Anzahl der hier erforderlichen Rückgriffe sollte zwar sehr klein sein. Sie wirklich klein zu machen ist aber ein Schwerpunkt der Forschung auf dem Gebiet des Software Engineering - nämlich die Entwicklung von Methoden, die es möglich machen, Programme von vornherein anforderungsgerecht zu entwickeln. Da in der Wartungsphase fertige Programme geändert werden, versteht es sich von selbst, daß von dort aus Rückgriffe bis in die Problemanalysephase zurück vorgenommen werden.
1.3.8. Projektorganisation und Dokumentation Bei der bisherigen Schilderung der Softwareentwicklung standen technische Aspekte im Vordergrund. Softwaresysteme werden aber von mehreren Personen gleichzeitig entwickelt. Das bringt viele organisatorische Probleme mit sich: Aufgaben werden auf verschiedene Bearbeiter aufgeteilt. Entscheidungen werden getroffen, rückgängig gemacht und durch neue ersetzt. Programmierer scheiden aus, neue werden eingestellt. Die Wartungsphase beinhaltet ständige Rückgriffe, stän-
28
Gegenstandsbestimmung
1.4.
diges Nachvollziehen von Entscheidungen der Programmentwickler. Dies alles sind Probleme, die hohe Anforderungen an Methoden des Projektmanagements und der Dokumentation stellen. Geeignete Methoden zu entwickeln und einzusetzen ist ein ebenso wichtiger Aufgabenbereich des Software Engineering, wie es die technische Bewältigung der Schwierigkeiten bei der Programmentwicklung ist.
1.4. Zusammenfassung Der Begriff "Software Engineering" ist als Reaktion auf die Softwarekrise der 60er Jahre geprägt worden und sollte ein Umdenken in der Softwareproduktion und -ausbildung auslösen. Software Engineering ist die ökonomische Erstellung großer Softwaresysteme. Der Softwareentwicklungsprozeß läßt sich grob in die folgenden Phasen gliedern, die jedoch nicht strikt sequentiell ablaufen: Problemanalyse, Entwurf, Implementierung, Funktions- und Leistungsüberprüfung, Installation, Wartung. Probleme der Softwareentwicklung sind nicht nur technischer Natur. Projektmanagement und Dokumentation sind unverzichtbarer Bestandteil des Software Engineering.
Softwareproduktion wozu
2.1.
29
2. Zielbestimmung 2.1. Softwareproduktion wozu? Software wird nicht um ihrer selbst willen produziert, sondern um einen bestimmten Nutzen zu erzielen. Für verschiedene, vom Softwareeinsatz mittelbar oder unmittelbar Betroffene kann dieser Nutzen vor allem unter drei Aspekten gesehen werden: -
Erledigung anderswie unlösbarer Aufgaben; dazu gehören die ersten Anwendungsgebiete der Rechner wie Physik und Kriegstechnik, wo umfangreiche Berechnungen anzustellen sind. Ebenso dazu gehören Informationssysteme, die erst durch Rechnerunterstützung ihre heutigen Ausmaße erreichen und im Falle mißbräuchlicher Anwendung große Gefahren mit sich bringen konnten.
-
Weiterentwicklung von Produktion und Reproduktion; darunter verstehen wir den Einsatz von Rechnern als Werkzeug für neue Technologien. Ein Beispiel dafür ist die Einführung des Lichtsatzes in der Drucktechnik. Gemeint sind auch die Auswirkungen sogenannter benutzerfreundlicher Software (s. Abschnitt 2.3.2.): Erleichterung des Umgangs der Menschen untereinander (komfortables Telefonsystem) und mit Institutionen (Lohnsteuerjahresausgleich) oder erleichterte Arbeitsbedingungen.
-
ökonomischer Nutzen; er kann erzielt werden, wenn eingesetzte Software das Verhältnis von Gewinn (bzw. Leistungen in nicht auf Gewinnbasis arbeitenden Institutionen) zu den Kosten verbessert. Personal kann eingespart werden durch direkte Automatisierung
30
Zielbestimmung
2.1.
menschlicher Arbeit oder indirekt durch höhere Zuverlässigkeit der Ausführung. Material wird eingespart durch bessere Produktionsplanung und Lagerhaltung. Ein verbessertes Bestell- und Rechnungswesen verkürzt die Umlaufzeit des Geldes (längere Zeit verfügbar). Diese drei Arten des Nutzens von Software stehen nicht zusammenhanglos und gleichberechtigt nebeneinander. Mit der Steuerung eines sehr schnellen Verfahrens bei der Blechverarbeitung ist ein Prozeßrechner Werkzeug für die Weiterentwicklung einer Technologie, die vielleicht ohne ihn nicht möglich wäre. Weder der erste noch der zweite Aspekt aber ist ausschlaggebend für seinen Einsatz. Denn könnte mit dem Einsatz des neuen Verfahrens nicht ein höherer Gewinn erzielt werden als zuvor, würde es bestenfalls zum Ausstellungsobjekt im Technischen Museum. Es ist naiv anzunehmen, daß Datenbanksysteme entwickelt werden, um der Hausfrau den Preisvergleich zu erleichtern oder den Sportbegeisterten mit allen gewünschten Informationen über Olympische Spiele zu versorgen. Körperliche und geistige Arbeit wird auch nicht vorrangig automatisiert, um die betroffenen Arbeiter und Angestellten zur Durchführung von höher qualifizierter Arbeit freizusetzen, sondern um deren Bezahlung einzusparen. Softwareentwicklungs- und ihre Folgekosten (z.B. Wartung) sind viel zu hoch, als daß Unternehmen es sich langfristig leisten könnten, sie vorrangig mit anderen Motiven als der Steigerung der Arbeitsproduktivität in Auftrag zu geben oder selbst durchzuführen. Daß diese der Hauptgrund der Softwareentwicklung ist, soll aber nicht den Blick darauf versperren, daß Softwareeinsatz auch für die Arbeitnehmer oder andere vom Einsatz Betroffene nützlich ist oder sein kann: Der Ersatz einfacher Arbeit durch die Automatisierung erhebt den Menschen über das Eingebundensein in eine Reihe mechanischer, seinen geistigen Fähigkeiten nicht adäquater Tätigkeiten. Dieser Effekt gehört zu den oft angegebenen Begründungen für den Rechnereinsatz. Allzu häufig steht dieses propagierte Ziel aber in Widerspruch zum vorrangigen Ziel - der Steigerung der Arbeitspro-
2.2.
Anforderungen an Herstellung
31
duktivität. Denn unter Bedingungen privat organisierter Produktion wird die Automatisierung lokal - auf ein Unternehmen beschränkt geplant und durchgeführt. Dabei steht die Freisetzung von Arbeitskräften im Vordergrund. Daß diese sich für höherwertige Arbeit qualifizieren und diese dann auch bekommen, liegt aber außerhalb des Verantwortungsbereichs derer, die die Freisetzung planen. Um den beabsichtigten Nutzen zu erreichen, müssen zwei Forderungen erfüllt werden. Die Forderung nach der zweckmäßigen Kostengestaltung als eine Forderung an den Softwareherstellungsprozeß (eigentlich an dessen Organisation), sowie die Forderung nach Qualität als Forderung an das Produkt.
2.2. Anforderungen an den Softwareherstellungsprozeß Wenn wir in den Ausführungen dieses Buches von Softwareentwicklung sprechen, so meinen wir in der Regel Projekte, die unter industriellen Bedingungen durchgeführt werden. Zwar nimmt sie als Rationalisierungsinstrument oft eine gewisse Sonderstellung ein; unter betriebswirtschaftlichen Gesichtspunkten ist sie aber ein Tätigkeitsbereich wie jeder andere und steht daher genau so unter dem Diktat der Forderung nach Kostenminimierung. Vor allem die starke Betonung der Forderungen, gesetzte Termine und vorgegebene Kosten einzuhalten, unterscheidet industrielle Projekte von wissenschaftlichen oder solchen, die auf der Spielwiese der akademischen Ausbildung durchgeführt werden. Projektplanungs- und -durchführungsmethoden haben sich hier am ehesten zu bewähren. In Vertrag und Anforderungsdefinition (s. Kapitel 3) sind Kostenumfang und Zeitplan als Anforderungen festgehalten, deren Überschreitung zumindest anteilig zu Lasten des Entwicklers geht. Die durch zu spät fertiggestellte Software verursachten, indirekten Kosten können erheblich höher sein als die direkten Softwarekosten. So verursachte z.B. bei einem großen militärisch eingesetzten System eine halbjährige Verzögerung Kosten in Höhe von $ 100 Millio-
32
Zielbestimmung
2.3.
nen - das fünfzigfache der direkten Kosten dieser Verzögerung [Boehm73]. Die bei der Softwareentwicklung eingesetzten Methoden müssen daher gewährleisten, daß Projekte wie vorgesehen abgeschlossen werden. Diese Methoden stammen aus drei Bereichen: -
Projektplanung; die Aufteilung des Projekts in einander folgende Phasen, das Formulieren von Meilensteinen als Abschluß dieser Phasen haben wir in Kapitel 1 dargestellt. Hinzu kommt die quantitative Abschätzung des Projekts (Zeit, Umfang, Resourcen, Kosten) und die Personalverteilung (s. Abschnitt 3.5.).
-
Projektmanagement; die Einhaltung der Projektplanung muß überwacht, gegebenenfalls muß sie detailliert oder korrigiert werden. Die Arbeit muß so aufgeteilt und organisiert werden, daß das Projekt termingerecht abgeschlossen werden kann (s. Kapitel 10).
-
Entwurfs- und Programmiermethoden; diese umfassen alle technischen Methoden der Programmentwicklung, die die Arbeitsproduktivität der Programmierer erhöhen und dazu geeignet sind, die Herstellung von Programmen zu unterstützen, die den Anforderungen genügen. Diese Methoden werden oft unter dem Begriff "Improved Programming Techniques" (IPT) zusammengefaßt (s. Kapitel 4 bis 9).
2.3. Anforderungen an das Produkt Bleib
nicht
au & ebnem VeJLdl
Stiig
nickt
zu hoch hina.uA!
Am i>ckön!>te.n iieht Von haJLb&n Höht
diz iileJLt aui.
F. Nietzsche: Die fröhliche Wissenschaft
2.3.1.
Zuverlässigkeit
33
Die Forderung nach anforderungsgerechter Software z i e l t auf Q u a l i t ä t des Produkts, auf äußerlich wahrnehmbare Eigenschaften. Es i s t zunächst keine Forderung nach Verwendung bestimmter Methoden und H i l f s m i t t e l bei der Softwareentwicklung, auch keine Forderung nach bestimmten inneren Strukturen des Produkts. Von einem guten Slalomski erwartet man, daß er s i c h l e i c h t drehen l ä ß t . Die Auswahl des verwendeten M a t e r i a l s , Holz, Kunststoff oder M e t a l l , der Aufbau der Schichten und Lamellen sowie die Verklebungstechnik s i n d die Probleme des Konstrukteurs bei der E r f ü l l u n g der Anforderung, einen l e i c h t drehbaren Ski zu entwerfen, nicht aber die Probleme des künftigen S k i f a h r e r s . Diesen Unterschied von Qualitätsanforderungen, für die das Produkt eine Black Box mit gewissen Eigenschaften i s t , auf der einen S e i t e und den Methoden zur optimalen E r f ü l l u n g der Anforderungen auf der anderen S e i t e g i l t es zu beachten. Im folgenden s o l l die Q u a l i t ä t zentraler Gegenstand unserer Betrachtungen s e i n .
2.3.1. Zuverlässigkeit "Betrachten wir unsere Lage e h r l i c h , so suchen wir niemals nach dem besten Programm, s e l t e n nach einem guten, aber immer nach einem, das die Anforderungen e r f ü l l t " .
[Weinberg71, S.17]
Das Hauptproblem hierbei i s t , die Anforderungen so zu formulieren, daß mit i h r e r E r f ü l l u n g das g e s t e l l t e Problem t a t s ä c h l i c h g e l ö s t wird. Es i s t dies das Problem, einen A u s s c h n i t t der realen Welt f o r m a l i s i e r t a l s Modell widerzuspiegeln. Da diese s e l b s t nicht f o r m a l i s i e r t v o r l i e g t , wird es nie möglich s e i n , ein Maß zu finden, mit dem eindeutig f e s t g e s t e l l t werden kann, ob bestimmte f u n k t i o nale Eigenschaften eines Softwaresystems ein gegebenes Problem l ö sen. Es wird vielmehr weiterhin den Anstrengungen und Auseinandersetzungen von Benutzern und Systementwicklern überlassen bleiben, die Funktionen des Softwaresystems festzulegen. Man s t e l l t dann die E r f ü l l u n g der funktionalen Anforderungen nicht mehr anhand des Problems s e l b s t , sondern anhand dessen Formulierung f e s t .
34
Zielbestimmung
2.3.1.
Korrektheit Ein Programm i s t korrekt, wenn es für jede Eingabe aus dem Definitionsbereich der Funktion, deren Implementierung es sein s o l l , die richtige Ausgabe erzeugt.
A: B: B-C: D: E: F: B i l d 2-1:
Eingaben (Definitionsbereich des Programms) zulässige Eingaben (Definitionsbereich der Funktion) als " F e h l e r f a l l " behandelte Eingaben Ausgaben Ergebnisse der "Fehlerbehandlung" "normale" Ausgaben Ein Programm
Auf Bild 2-1 übertragen heißt dies, daß ein Programm dann korrekt i s t , wenn die Abbildungen B - C - E ( " F e h l e r f a l l " ) und C - F
("Normal-
f a l l " ) wie definiert implementiert sind. Zu beachten i s t , daß das Programm natürlich auf jede denkbare - also nicht nur im D e f i n i tionsbereich der Funktion liegende - Eingabe irgendwie reagiert. (Das haben wir durch A - * D - (Eu F) dargestellt.) Vom Standpunkt der Korrektheitsbetrachtung i s t es unwichtig, ob ein Teil der definierten Eingaben ( B - C ) als " f a l s c h " angesehen wird und zu Fehlermeldungen führt. Sie sind genauso definiert wie die als " r i c h t i g " angesehenen. Wenn man ein Programm daraufhin betrachtet, ob es korrekt i s t , so hängt die Zuverlässigkeit des Untersuchungsergebnisses von der Art
Zuverlässigkeit
2.3.1.
35
und Weise der Funktionsdefinition ab. Einen vollständig formalen Beweis kann man - theoretisch - durchführen, wenn die Funktion mathematisch definiert ist (Spezifikation). Ist sie das nicht, sondern nur mehr oder weniger formal beschrieben
(Anforderungsdefini-
tion), muß man sich mit Plausibilitätsbetrachtungen oder Testen begnügen. Inwieweit die Funktion bzw. das Programm die gewünschte Problemlösung ist, kann nicht mehr Gegenstand von Korrektheitsbetrachtungen sein. Robustheit Die Robustheit ist eine Aussage darüber, wie viele, vom problemlösenden Algorithmus aus gesehen, falsche Eingaben vom Programm erkannt und gemeldet werden (Größe von B - C ) . überhaupt nicht robust ist ein Programm, das die Behandlung von Eingaben, die eine Fehlermeldung als Ausgabe verursachen müssen, nicht vorsieht ( B = C ) . Das Maximum an Robustheit ist erreicht, wenn es keine Eingabe gibt, die das Programm zu Fehlreaktionen veranlassen kann ( A = B ) . Dies ist natürlich ein Idealfall, der nicht erreichbar ist angesichts der vielen Eingabefehlermöglichkeiten wie z.B. -
falsche Anzahl von Eingabedaten
-
falscher Stringaufbau
-
Verlassen bestimmter Wertebereiche bei richtigem Aufbau
-
verletzte Dateninterdependenzen.
Leicht einsichtig ist, daß Robustheit und Effizienz einander widersprechende Qualitätsanforderungen sind, da das Abfangen vieler Fehlerfälle die Laufzeit verlängert. Ausfallsicherheit Ausfall sicher heißt ein Programm dann, "wenn Ausfall, Fehler oder Störungen des Grundsystems, wie der Ausfall von Speichermoduln, Plattenspeichern, Magnetbandgeräten oder Druckern, übertragungsfehler beim E/A-Verkehr, Paritätsfehler bei Speichermoduln oder Fehler
36
Zielbestimmung
2.3.2.
im Betriebssystem das korrekte Arbeiten des Programms nicht unmöglich machen, sondern höchstens behindern." [Goos76, S.17] Je weiter ein Programm von der Maschinenebene entfernt ist, desto schwieriger ist es, Ausfall Sicherheit zu realisieren. Bei Anwenderprogrammen würde dies nämlich bedeuten, daß für den Notfall alle möglichen Systemprogramme mit erstellt werden müßten. Ausfall Sicherheit heißt hier daher vor allem Datensicherung und schnelle Verfügbarkeit von Daten und Programmen bei Ausfall oder Störungen des Grundsystems. Systeme, die eine große Datenmenge verwalten (z.B. On-Line-Reisebuchungen), müssen sicherstellen, daß durch Störungen keine Daten verschwinden können. Ein wichtiger anderer Aspekt der Ausfall Sicherheit beschränkt sich nicht auf die Software, sondern betrachtet ihr Verhalten im Gesamtsystem. Vor allem bei risikobehafteten Prozessen (Atomkraftwerke) ist es notwendig, daß sich das Steuerungssystem auch bei Zusammenbruch definiert verhält (z.B. durch Ausbleiben eines Zeittakts, der das korrekte Arbeiten des Systems anzeigt). Da wie bei der Robustheit erhöhte Ausfall Sicherheit Vergrößerung der Menge der zulässigen Eingaben bedeutet, widerspricht auch sie der Effizienz. 2.3.2. Benutzungsfreundlichkeit Eine veAMÜiAmdz Elge.nie.ha6t d u
ComputeAA, duK in kormeAzieJLt&n VaXinveAaAbejJungianZagzn exnge-
•4eZzt wild, ¿51 dmizn Neigung, ie.hA gioße. VapizAmmgm am zugeben. [Daniels71, S.195] Die Benutzungsfreundlichkeit ist nicht hauptsächlich eine Forderung an die Implementierung von Funktionen wie die Zuverlässigkeit, sondern an deren Definition. Diese sollen so definiert werden, daß das
2.3.2.
Benutzungsfreundlichkeit
37
Softwaresystem für denjenigen, der es nicht e r s t e l l t hat - den Benutzer
handhabbar i s t . Einen verbindlichen Katalog von Krite-
rien anzugeben, der aufführt, was ein System benutzerfreundlich macht und was nicht, i s t nicht möglich, da es den Benutzer an sich nicht gibt - und damit auch nicht die Benutzungsfreundlichkeit. Einerseits gibt es eine Vielzahl von Bereichen, in denen Software angewendet wird. Andererseits gibt es innerhalb eines einzigen Anwendungsgebietes Benutzer mit unterschiedlicher Vorbildung, Erfahrungen, Wünschen. Diese g i l t es beim Entwurf eines benutzerfreundlichen Systems zu berücksichtigen. Anhaltspunkte für die genaue Festlegung benutzerfreundlicher Funktionen sind Verständlichkeit, Angemessenheit, vernünftiges Fehlerverhalten. Die Verständlichkeit orientiert sich zuerst an der Vorbildung der Benutzer. Die ihm zur Kommunikation zur Verfügung gestellte Sprachebene muß entsprechend dieser Vorbildung gewählt werden. Es kann nötig sein, zur Benutzung desselben Systems verschiedene sprachliche Ebenen zu definieren. Bei Datenbanksystemen verwenden gelegentliche Benutzer andere Sprachen als Programmierer oder Datenbankverwal ter. Die Verständlichkeit schließt Punkte ein wie Erlernbarkeit, konzeptionelle Klarheit der Sprachen (Anzahl der Ausnahmen), Übereinstimmung von Systemreaktionen mit den Erwartungen, Hilfestellung durch Erklärungen und Fehlermeldungen. Besondere Unverständlichkeit erreicht ein kommerziell vertriebenes Datenbanksystem, das Fehlermeldungen im Maschinencode absetzt. Die Angemessenheit fordert, daß die Systemfunktionen auch die Funktionen sind, die der Benutzer braucht - und nicht solche, aus denen die benötigten Funktionen geschickt zusammengebastelt werden müssen. Schneider [Schneider74, S.2-25] gibt als Maß dafür die Resultatorientierung an, d . i . das Verhältnis von zu ermittelnden T e i l r e s u l taten zu den gewünschten Gesamtresultaten. Bei der Ausgabe bezieht sich die Angemessenheit darauf, daß nur ausgegeben wird, was ge-
38
Zielbestimmung
2.3.2.
wünscht war, und nicht etwa riesige Papierberge voller Zahlen, wenn nur charakteristische Werte benötigt werden. Ein vernünftiges Fehlerverhalten zeichnet sich dadurch aus, daß Eingabefehler dem Benutzer so mitgeteilt werden, daß er sein Fehlverhalten erkennen und ggf. korrigieren kann. Interne Programm- und Dateifehler sollten nur im Fall der Unausweichlichkeit zum Abbruch des Programmlaufs führen. Solche Fehler sollten auch nur dort gemeldet werden, wo sie beseitigt werden können. Bei einer mißlungenen Flugbuchung s o l l t e auf dem Terminal des Reisebüros nur mitget e i l t werden, daß die Buchung erfolglos war, und e v t l . , wie man sie doch noch vornehmen könnte. Der interne Grund, ein Fehler in der Dateiorganisation zum Beispiel, s o l l t e nur dem Systempfleger mitget e i l t werden. Die Benutzungsfreundlichkeit von Systemen i s t im Einzelfall
nicht
leicht durchsetzbar. Der Systementwerfer hat aus seiner Kenntnis heraus andere Vorstellungen über sie als der Benutzer. Diese Schwier i g k e i t kann dadurch behoben werden, daß Benutzungsfreundlichkeit durch eine genaue Untersuchung der Benutzerwünsche unterteilt wird in Unterziele - Eigenschaften, die mit bestimmten Prioritäten erf ü l l t sein müssen. Dzida et a l . [Dzida78] haben dies für interaktive Systeme versucht. 233 Fragebögen, auf denen die Wichtigkeit von 100 Anforderungen an ein interaktives System bewertet worden waren, wurden ausgewertet. Nach Auswahl von 53 Anforderungen wurden diese mit Hilfe von Faktorenanalyseverfahren 1 in sieben überschneidungsfreie Gruppen (Faktoren) eingeteilt und innerhalb dieser ihrer Wichtigkeit nach angeordnet.
1
Faktorenanalyseverfahren sind s t a t i s t i s c h e Verfahren, die eine Vielzahl von Faktoren in bezug auf ihren Einfluß auf ein Gesamtresultat quantifizieren. Ursprünglich wurde die Faktorenanalyse vorwiegend in der Psychologie angewendet. Ihr Anwendungsbereich dehnt sich aber ständig aus (z.B. Wirtschaftswissenschaften).
2.3.3.
Flexibilität
39
Ein Beispiel: "Faktor 5: 0.76 0.71
'Übereinstimmung mit Benutzererwartungen1
in ähnlichen Situationen ähnlich verhalten vom Benutzer bei ähnlichen Aufgaben einheitliche Aktionen fordern
0.65
geringe Überraschungseffekte bieten . . . "
Je nach Erfahrungshintergrund spielten für verschiedene Benutzerklassen bestimmte Faktoren eine mehr oder weniger wichtige Rolle. Der Einsatzbereich der von Dzida et al. vorgestellten Methode i s t auf Benutzer eingeschränkt, die schon EDV-Erfahrung haben. Andere Benutzer kennen die Möglichkeiten der EDV nicht und können daher keine gewünschten Systemeigenschaften angeben. Eine andere Schwierigkeit bei der Durchsetzung der Benutzungsfreundl i c h k e i t besteht darin, daß sie in gewissem Maße der Standardisierung und Universalität von Software entgegensteht. Auch verteuert s i e die Softwareentwicklung dadurch, daß weniger komfortable Systeme in der Regel b i l l i g e r zu implementieren sind. Das Kosten/Nutzen-Verhältnis s o l l t e aber sehr gründlich untersucht werden. 2.3.3. Flexibilität Ein
Automobil
mit
St&ueAung 'ist
{¡lexlblen
iam
alA
ein
InteAeAie,
Vofidwiä.deA
abe.fi wenige* gewöhnlichem.
hauptiächLich
übeA
unabhänglgeA
beide.
von abe/i
dem Go tt
feindlich
ilcheAeA
{¡olgE-i
-ist
metaphyiZichm gegenSt/iaßen.
[Denning76] Wie schon erwähnt, i s t der Anteil der Wartungskosten an den gesamten für Software aufgewandten Kosten mit 50% (Bild 1-1) sehr hoch.
40
Zielbestimmung
2.3.3.
In Anwendungsbereichen ist der Anteil noch höher. So ergab eine Studie bei General Motors sogar 75% [Boehm76a]. Die Größe dieser Zahlen kommt nicht nur durch die Beseitigung der ständig neu auftretenden Fehler zustande, sondern hauptsächlich durch die Anpassung vorhandener Software an neue Anforderungen. Da deren späteres Auftreten bei jedem zu entwickelnden Softwaresystem erwartet werden kann, ist von ihm zu verlangen, daß solche Anpassungen mit angemessen kleinem Aufwand vorgenommen werden können. Diese Eigenschaft bezeichnet man als Flexibilität. Die neuen Anforderungen an die Software können die Benutzerschnittstelle und die Schnittstelle zum Grundsystem (Rechner und Systemsoftware) betreffen. Wir unterteilen daher die Flexibilität in Adaptabilität und Portabilität. Adaptabilität Die Adaptabilität ist die Eigenschaft eines Softwaresystems, sich auf neue Benutzeranforderungen hin leicht ändern zu lassen. Mögliche hinzukommende, nicht realisierte Funktionen können schon in der Anforderungsdefinition benannt sein. Ein Lohnabrechnungsprogramm könnte in einem Betrieb z.B. darauf angelegt werden, daß später noch nicht erfaßte Bereiche mit variierten Lohnsystemen und die Gehaltsabrechnung angeschlossen werden. Daneben stünden noch die Änderungen, die neue TarifVereinbarungen mit sich bringen. Es ist sicher eine Illusion zu glauben, die Adaptabilität auf die vorhersehbaren Fälle begrenzen zu können. Daraus soll nicht gefolgert werden, daß es möglich sein muß, die erwähnte Lohnabrechnung zum Compiler umzukonstruieren. Technische Änderungen in einem Produktionsprozeß sollten jedoch nicht das völlige Neuerstellen des Prozeßsteuerungsprogramms zur Folge haben. Portabi Ii tat Ein Softwaresystem heißt portabel, wenn es mit im Verhältnis zu den Herstellungskosten geringem Aufwand auf andere Grundsysteme umge-
2.3.3.
Flexibilität
41
stellt werden kann. Die Notwendigkeit dieser Eigenschaft war längere Zeit nicht gesehen worden, was die Anzahl der in Assemblern geschriebenen Programme zeigt. Dies hat sich noch bis heute in von der Informatik wenig beeinflußten Bereichen erhalten: In den letzten Jahren wurde an der Universitätsbibliothek Bielefeld mit mehreren hundert Mannmonaten eines der am weitesten entwickelten deutschen Bibliotheksautomatisierungssysteme in Assembler programmiert [Briesenick76]. Kaum fertiggestellt, gibt es Probleme damit, daß der Rechner gewechselt wird. Die Portabilität ist eine Eigenschaft von Programmen, auf die Softwareanwender nicht verzichten können, wollen sie nicht die Anschaffung neuer Grundsysteme für immer ausschließen. Dazu ist die Verwendung von weitgehend maschinenunabhängigen
Programmiersprachen
notwendige Voraussetzung. Die weite Verbreitung der zur Implementierung vorgesehenen Sprache kann ein wichtiger Aspekt sein, wenngleich verschiedene Compiler, File-Systeme etc. die Übertragung nicht aufwandlos machen. Exkurs:
Das UNCOL-Problem
Um ein hohes Maß an Portabilität zu gewährleisten, ist für den Compilerbau zunächst ein immenser Aufwand vonnöten. Um m Sprachen auf n Rechnern zur Verfügung zu stellen, sind m * n Übersetzer zu entwickeln.
Sprachen
Übersetzer
Rechner • • • Bild 2-2:
m*n
Übersetzer
42
Zielbestimmung
2.3.3.
In den fünfziger Jahren wurde ein Projekt ins Leben gerufen, das man vielleicht als das erste (fehlgeschlagene!) Software-EngineeringProjekt bezeichnen könnte. Seine Aufgabe war, einen Vorschlag zur Reduzierung des oben geschilderten Aufwands zu erarbeiten. Das Projekt wurde mit der Empfehlung abgeschlossen, eine universelle Zwischensprache UNCOL (UNiversa! Computer Oriented Language) zu konstruieren, was den Aufwand auf die Erstellung von m + n Übersetzern reduzieren würde [Share58].
Bild 2-3:
m + n Übersetzer
Bei der Verschiedenheit der Programmiersprachen (etwa SIMULA, COBOL, APL) nimmt es nicht wunder, daß bis heute niemand UNCOL entwerfen konnte. Die Verkleinerung des Aufwandes von m * n ist praktisch gelungen u.a. durch die Entwicklung der Programmiersprache CDL2 (Compiler Description language) [Dehottay76], [Bayer78a]. CDL2 ist eine Programmiersprache, die keine zur Datenmanipulation geeigneten Sprachmittel zur Verfügung stellt (Zuweisung, Addition, Ein-/Ausgabe, ...). Diese müssen einer fremden Sprache (Zielsprache) entliehen und als CDL2-Makros deklariert werden. Zur Ausführung wird das gesamte CDL2Programm in diese Zielsprache übersetzt. Hat man einen Compiler für ALGOL 60 nach IBM 370 Assembler geschrieben, braucht man nur diese Makros, deren Rümpfe in 370 Assembler geschrieben sind, in PDP 11 Assembler umzuschreiben und die Codeerzeugung auszutauschen, um
2.3.5.
Effizienz
43
einen Compiler f ü r ALGOL 60 nach PDP 11 Assembler zu erhalten. Je umfangreicher das in CDL2 geschriebene Programm i s t , desto geringer wird der Anteil dieser elementaren Makros und damit der Aufwand beim Portieren. Beim gegenwärtigen CDL2-Compiler wird der P o r t i e r aufwand auf weniger a l s 10% des Aufwands zum Schreiben des Gesamtcompilers geschätzt. Nimmt man 10% Übertragungsaufwand an, e r h ä l t man a l s Gesamtaufwand beim Lösen des UNCOL-Problems m * ( l + - ^ n) + l . Für eine Sprache muß ein Compiler geschrieben werden und z u s ä t z l i c h f ü r jede Maschine 1/10 Compiler. Das e r g i b t 1 + - ^ n. Um m Sprachen abzudecken, muß dies mit m m u l t i p l i z i e r t werden. Hinzu kommt der Aufwand, einen Comp i l e r zu schreiben, der CDL2 nach irgendeiner Maschinensprache übersetzt. 2.3.4. Lesbarkeit Die Lesbarkeit eines Programms i s t unabdingbare Voraussetzung für eine hohe F l e x i b i l i t ä t . Denn Anpassungen können nur mit angemessenem Aufwand vorgenommen werden, wenn der Programmierer, der die Anpassung vornehmen muß, s i c h im Programm schnell zurechtfindet. Dieses E r f o r d e r n i s a l l e i n genügt a l l e r d i n g s n i c h t , die Lesbarkeit a l s Qualitätsanforderung zu formulieren. Sie wäre eher Methode zum E r reichen einer Anforderung ( F l e x i b i l i t ä t ) , vergleichbar mit "Modular i t ä t " , " s t r u k t u r i e r t e Programmierung" oder anderem. Ein gewichtiger Grund, die Lesbarkeit a l s Anforderung zu formulieren, i s t aber die Forderung des Bundesdatenschutzgesetzes
[BDSG77],
Datenschutzbeauftragten E i n s i c h t in Programme zu gewähren. Diese E i n s i c h t i s t nur dann eine wirksame K o n t r o l l e , wenn aus dem Programmtext der Programmablauf mit vertretbarem Aufwand e r s i c h t l i c h , d.h. das Programm lesbar i s t
[Bayer78].
2.3.5. Effizienz Die E f f i z i e n z i s t ein Maß dafür, wie lang die L a u f z e i t und wie groß der Speicherbedarf eines Programms i s t .
44
Zielbestimmung
2.4.
Speicher- und Laufzeiteffizienz sind gegeneinander wirkende Größen. Versucht man ein Problem mit minimalem Speicherbedarf zu lösen, wird man eine Verlängerung der Laufzeit in Kauf nehmen müssen. Für die Umkehrung g i l t dasselbe. Die Entscheidung für das eine, das andere oder das Optimum von beiden läßt sich nicht allgemein f ä l l e n , sondern wird bestimmt durch Aufgabenstellung (z.B. Real-Time) und Systemumgebung (Kleinrechner, Großrechner). Mit zunehmender Qualität der Compiler und Optimierungstechniken hat die Frage der Laufzeitund Speicherplatzeffizienz immer weniger Einfluß auf Programmierungsentscheidungen. So gibt es heute Compiler, die besseren Code aus höheren Programmiersprachen erzeugen als solchen, der aus handgeschriebenen Assembler-Programmen r e s u l t i e r t .
2.4. Zusammenhang der Qualitätsforderungen Ein Programmierer in Detroit s o l l t e ein Programm testen, das die Konstruktion von Autos unterstützen s o l l t e . Beim Test fand er heraus, daß bestimmte Eingaben zur Folge hatten, daß das Programm Autos ohne Motor, mit acht Rädern und drei verschiedenen S i t z p o l s t e rungen vorschlug. Nachdem er ein neues Programm entwickelt hatte, s t e l l t e er es vor. Von einem Programmierer des alten Systems befragt, erläuterte er, daß sein Programm 10 Sekunden pro Eingabekarte benötige. "Aha", triumphierte der Fragende, "aber mein Programm benötigt nur 1 Sekunde pro Karte". - "Aber Ihr Programm funktioniert nicht. Wenn das Programm nicht funktionieren muß, kann ich eines schreiben, das 1 Millisekunde pro Karte braucht". [Weinberg71, S.17f] Dies Beispiel zeigt, daß die Korrektheit eines Programms als seine wichtigste Eigenschaft zu betrachten i s t . Die Abwägung anderer E i genschaften kann erst erfolgen, wenn die Korrektheit gewährleistet i s t . Die Lesbarkeit möchten wir wegen der erwähnten Kontrollmöglichkeit bezüglich ihrer Wichtigkeit neben der Korrektheit ansiedeln. Dies i s t auch dadurch gerechtfertigt, daß s i e eine Methode zur E r f ü l lung anderer Qualitätsanforderungen i s t (Korrektheit, F l e x i b i l i t ä t ,
2.4.
Anforderungshierarchie
45
Effizienz). Möglichst große Effizienz war lange Zeit Hauptanforderung an Programme. Da dies die Entwurfsmethodik beeinflußte, wurden unübers i c h t l i c h e , unkorrekte und unflexible Programme geschrieben. Man kann sagen, daß nicht zuletzt die Überbetonung der Effizienz die Softwarekrise mitbegründete. Die Einbeziehung des falschen Begriffs der Personaleffizienz in den Effizienzbegriff in verschiedenen Literaturstellen versucht, die Relativierung der Effizienzforderung durch Qualitäten wie Portabil i t ä t und F l e x i b i l i t ä t zu fassen. Gemeint i s t wirklich, daß die Kostenersparnis durch höhere Effizienz dadurch aufgehoben wird, daß oft ein Vielfaches der eingesparten Kosten durch erhöhten Personalaufwand bei Entwurf, Test und Änderung eines Programms entsteht und dies bei der Forderung nach Effizienz bedacht werden muß. Die E f f i zienz i s t als Qualitätsanforderung daher letztrangig. Sie wird im wesentlichen Compilern überlassen, die immer effizienteren Code erzeugen, oder durch nachträgliche Optimierung von Hand erreicht. Ein Beispiel dafür i s t das Betriebssystem MULTICS, das mit mehreren hundert Mannjahren Aufwand e r s t e l l t wurde. Mit einem halben Jahr Nachoptimierung konnte die Laufzeit um den Faktor 200 schneller gemacht werden [Goos76, S.49]. Eine Ausnahme von der Einordnung der Effizienz in die Rangfolge der Qualitätsanforderungen muß natürlich dann gemacht werden, wenn das Problem (Dialog mit maximalen Antwortzeiten) oder die Systemumgebung (Kleinrechner) es erfordern und dies in der Anforderungsdefinition festgehalten i s t . Da die Kosten ihrer Entwicklung aus Gründen der Wirtschaftlichkeit (Amortisierung) eine lange Lebenszeit der Software erfordern, i s t die F l e x i b i l i t ä t deren wichtigste Eigenschaft nach der Korrektheit und Lesbarkeit. Die notwendige Lebenszeit der Software überdauert oft die des ursprünglichen Rechners, und zudem treten viele neue Anforderungen an ein Programm auf. Ein hohes Maß an Portabilität und Adaptabilität ermöglichen erst geringen Änderungsaufwand.
46
Zielbestimmung
2.4.
Ausfallsicherheit und Robustheit sind in gewissem Maße so wichtig wie die funktionale Richtigkeit. Der Grad, inwieweit sie r e a l i s i e r t werden, wird aber in stärkerem Maße durch Fragen der Wirtschaftlichkeit bestimmt als die F l e x i b i l i t ä t . Der Aufwand zu ihrer Verwirklichung nimmt tatsächlich nicht linear mit ihrem Grad zu. Die Benutzungsfreundlichkeit i s t schwer in die bisherige Hierarchie einzuordnen, da sie keine Anforderung an die Implementierung, sondern an die Definition der Systemfunktionen i s t . Ihre Durchsetzbarkeit hängt nicht unerheblich von Wirtschaftlichkeitsüberlegungen ab. Es l i e g t also nahe, s i e in ihrer Wichtigkeit neben Robustheit und Ausfallsicherheit anzusiedeln. Unsere Hierarchie von Qualitätsanforderungen besteht demnach aus zwei Ebenen: 1.
Korrektheit, Lesbarkeit
2.
durch Wirtschaftlichkeit bestimmte Eigenschaften
Die zweite Ebene bildet selbst eine Hierarchie: 2.1.
Adaptabilität, Portabilität
2.2.
Robustheit, Ausfallsicherheit, Benutzungsfreundlichkeit
2.3.
Effizienz
Die Hierarchisierung der zweiten Ebene i s t nur eine Orientierung. Der Grad der Erfüllung einzelner Qualitäten kann in einem Projekt nur durch Abschätzung a l l e r anderen festgelegt werden.
2.5.
Zusammenfassung
47
2.5. Zusammenfassung Software wird hauptsächlich entwickelt, um die Arbeitsproduktivität in einem bestimmten Bereich zu erhöhen. Ihr Nutzen kann zudem in der Erledigung anderswie unlösbarer Aufgaben und in der Weiterentwicklung von Technologie bestehen. An den Softwareherstellungsprozeß werden Anforderungen wie Kostenminimierung und exakte Terminierung gestellt. Durch Anwendung von Methoden aus den Bereichen Projektplanung, Projektmanagement, Entwurfs- und Programmierungsmethoden muß dem Rechnung getragen werden. Die qualitativen Anforderungen an ein Softwareprodukt können nach ihrer Wichtigkeit geordnet werden: 1.
Korrektheit, Lesbarkeit
2.1.
Adaptabilität, Portabi Ii tat
2.2.
Robustheit, Ausfallsicherheit, Benutzungsfreundlichkeit
2.3.
Effizienz
Korrektheit und Lesbarkeit sind unbedingt notwendig. Die zweite Hierarchieebene ist nach dem Einfluß auf die globale Wirtschaftlichkeit geordnet, die die gesamte Lebensdauer der Software umfaßt. Spezielle Erfordernisse eines Projekts können diese Hierarchie verändern.
48
Problemanalyse
3.
3. Problemanalyse Das Ziel der Problemanalyse i s t , die Anforderungen an das Softwareprodukt zur Lösung eines Problems präzise zu beschreiben und dabei a l l e wichtigen Umgebungsbedingungen des Einsatzgebietes und der Softwareherstellung zu berücksichtigen. Dagegen bezeichnet der gebräuchliche Begriff der "Systemanalyse" meist ganz allgemein die Entwicklung von EDV-Anwendungssystemen und erstreckt sich über a l l e Phasen des Software Life Cycle [Wedekind73], [Daniels71]. Diese Begriffsbildung i s t aber nicht angemessen angesichts des doch größtent e i l s synthetischen Charakters der Softwareherstellung. Zwar enthält auch die Problemanalyse in unserem Phasenmodell synthetische Elemente bei der Festlegung der Anforderungen (im englischen Sprachgebrauch findet man deshalb auch den Begriff "requirement d e f i n i t i o n " ) , doch l i e g t der Schwerpunkt im Kontext des Software L i f e Cycle auf der Ermittlung dessen, was zur Problemlösung notwendig i s t , und nicht in der Konstruktion der Lösung. Die Problemanalyse in einem nicht automatisierten Bereich i s t insofern allgemeiner und schwieriger als bei einer beabsichtigten Erweiterung bestehenden EDV-Einsatzes, als noch keine EDV-gerechte Systemstruktur 1 vorhanden i s t . Hier besteht die besondere Schwier i g k e i t , daß EDV-Anwender und Softwarehersteller nur über geringe Kenntnis des jeweils anderen Fachgebietes verfügen und deshalb zusammenarbeiten müssen, um zu einer beiderseits akzeptierbaren und verwendbaren Festlegung der Anforderungen an das Softwareprodukt zu kommen. Wegen der Vielzahl der Anwendungsgebiete i s t es schwierig,
1
Ein System i s t eine Einheit von Objekten und Beziehungen, die nach einem bestimmten Kriterium von ihrer Umgebung abgegrenzt werden kann.
3.1.
Istanalyse
49
allgemein einsetzbare Methoden und Hilfsmittel für die Zusammenarbeit zu entwickeln. Neben die hier beschriebenen Grundsätze und Ansätze allgemeiner Methoden müssen daher anwendungsspezifische Methoden treten. Die Problemanalyse gliedert sich in drei Unterphasen, die z e i t l i c h nicht s t r i k t voneinander zu trennen sind. Zunächst wird das System so untersucht und beschrieben, wie es vorgefunden wird - Istanalyse. Dabei wird f e s t g e s t e l l t , wo genau die Probleme l o k a l i s i e r t sind, die einen EDV-Einsatz erfordern. Das Sollkonzept i s t die Beschreibung einer Lösung dieser Probleme für den Anwender und Definition einer Aufgabe für den Softwarehersteller. Im Rahmen der Durchführbarkeitsstudie, der Untersuchung, ob diese Lösung technisch und ökonomisch realisierbar i s t , beginnt bereits die Projektplanung, die im Falle der Entscheidung, das Projekt durchzuführen, vervollständigt wird.
3.1. Istanalyse Vom Gesamtsystem, z.B. dem Unternehmen, innerhalb dessen der Einsatzbereich der Software l i e g t , wird das zu analysierende T e i l s y stem nach dem Kriterium abgegrenzt, daß außerhalb liegende Teile durch eine Umorganisation mit Hilfe von EDV nicht betroffen werden. Gerade in den Anfängen der Automatisierung wurde sehr häufig der Fehler gemacht, zum betroffenen System nur die unmittelbar auf den Rechner zu übertragenden Funktionen zu zählen, und man mußte dann bald f e s t s t e l l e n , daß die Einführung eines Rechners umfangreiche Änderungen in weiteren Teilen des Gesamtsystems nach sich zog. Diese setzten sich dann meist ungeplant durch. I s t bereits ein Rechner eingesetzt, wird dieses Problem eher Berücksichtigung finden. Aus der Systemabgrenzung f o l g t , welche Fakten für das Projekt relevant sind und ermittelt werden müssen. Ein System zu analysieren heißt, seine Komponenten zu betrachten und deren Zusammenwirken zu verstehen. Dabei geht man schrittweise
50
Problemanalyse
3.1.
vor und konzentriert sich jeweils auf einen bestimmten Aspekt. Die statischen Bestandteile zu erfassen macht im allgemeinen keine Schwierigkeiten. Man kann beobachten, wieviele Personen in einem Bereich arbeiten, welche Werkzeuge und Hilfsmittel sie benutzen und welche Gegenstände bearbeitet werden. Die Untersuchung des z e i t l i chen Verhaltens des Systems kann man gliedern nach -
Ort; was geschieht hier während eines bestimmten Zeitraums?
-
Personen; welche Arbeiten verrichten s i e ?
-
bearbeitetem Objekt; in welchen Schritten wird das Objekt beoder verarbeitet?
-
Informationen; welche Kanäle durchlaufen s i e ?
Weiter kann man das Systemverhalten unter dem Aspekt untersuchen, welche Ursachen und welche beabsichtigten und unbeabsichtigten Folgen ein Vorgang hat. Es i s t notwendig festzulegen, welche Informationen gesammelt werden müssen, bevor man mit der Systemerhebung beginnt. Ein M i t t e l , die Vollständigkeit der Informationssammlung zu kontrollieren, sind z.B. Checklisten [DeMicheli76], die alle wichtigen Fragen enthalten. Von besonderer Bedeutung sind die Datenverarbeitungsaspekte des betrachteten Systems. Wichtig bezüglich der bearbeiteten Daten sind Fragen nach -
Datenträgern (Material, auf dem die Daten dargestellt s i n d ) ,
-
Darstellungsformen (Syntax),
-
Bedeutung (Semantik),
-
Ordnungsstrukturen und anderen Relationen,
-
Fehlermöglichkeiten und - h ä u f i g k e i t ,
-
Gesamtzahl und z e i t l i c h e r Verteilung des A n f a l l s .
3.1.
Istanalyse
51
Die datenverarbeitenden Tätigkeiten, gleich ob von Menschen oder Maschinen durchgeführt, werden anhand folgender Stichpunkte charakterisiert: -
Was wird getan (Ein-/Ausgaberelation)?
-
Wie wird es getan (Verarbeitungsablauf)?
-
Leistungsfähigkeit (Bearbeitungszeit, Durchsatz),
-
Häufigkeit und zeitliche Verteilung,
-
Möglichkeiten und Wahrscheinlichkeit von Fehlfunktionen.
Informationen, aufgrund derer das System gesteuert wird, fließen sowohl Uber formelle Kommunikationswege (z.B. Betriebshierarchie) als auch über informelle (z.B. persönliche Kontakte). Letztere sind meist schwer zu ermitteln und werden in ihrer Bedeutung oft unterschätzt. Die letztlich entscheidenden Informationen, ohne die die Analyse sinnlos wäre, werden aufgrund der ökonomischen Bewertung des Systems gewonnen. All diese Informationen können nicht allein durch Beobachtungen, Messungen und Schätzungen zusammengetragen werden. Persönliche Gespräche, Konferenzen und Ermittlungen per Fragebogen sind wichtige Methoden der InformationsSammlung. Dabei ergeben sich zwei hauptsächliche Schwierigkeiten in der Zusammenarbeit mit den Befragten: -
Der Fragende geht nicht genügend auf die Kenntnisse und Fähigkeiten der Befragten ein und erhält deshalb nicht die gewünschten Informationen.
-
Die Befragten sind mißtrauisch gegenüber geplanten Änderungen und verweigern die Mitarbeit oder geben absichtlich falsche Informationen.
Die Schwierigkeiten im ersten Fall sind durch Erfahrung und verstärkte Bemühungen behebbar. Um Mißtrauen abzubauen, ist es notwendig, die Betroffenen über die geplanten Änderungen rechtzeitig zu
52
Problemanalyse
3.2.
informieren und an den Entscheidungen über die Veränderungen zu beteiligen. Dem steht im allgemeinen das Interesse der Unternehmensleitung gegenüber, die Betroffenen nicht an den Entscheidungen zu beteiligen, weil z.B. geplant ist, eine größere Zahl von Beschäftigten zu entlassen. Die Lösung dieses Konflikts ist nicht durch di« Anwendung besserer Methoden zu erreichen, sondern ist Gegenstand sozialpolitischer Auseinandersetzungen. So sind seit einiger Zeit Rationalisierungsfragen fester Bestandteil der TarifVerhandlungen zwischen Gewerkschaften und Unternehmern: Rationalisierungsschutzabkommen sollen die Folgen für die jeweils betroffenen Arbeitnehmer so gering wie möglich halten. Bei darüberhinaus gehenden Problemen - veraltete Ausbildung, zu kurzfristige Umstellungsplanung, Arbeitszeit, betriebsspezifische Qualifikation - befinden sich die Auseinandersetzungen noch im Anfangsstadium, sind umfassende Konzepte der Arbeitnehmer und ihrer Gewerkschaften noch nicht erarbeitet. In der Beschäftigung mit den Folgen des Rechnereinsatzes liegt jedoch auch die gesellschaftliche Verantwortung des Informatikers.
3.2. Erarbeitung eines Sollkonzepts Die Istanalyse umfaßt alle Teile des Systems, die durch den Rechnereinsatz beeinflußt werden können. Entsprechend erstreckt sich auch die Erarbeitung eines Sollkonzepts einerseits auf die Aufgaben der Software, andererseits auf die Umorganisation des übrigen Systems. Aus der Sicht der Softwareherstellung ist das zu erstellende Softwaresystem das Ziel des Projekts. Die benutzte Rechenanlage mit der Grundsoftware und die Benutzer des Systems bilden die Umgebungsbedingungen, unter denen die Software arbeiten soll. Die Umorganisation von weiteren Teilen des Sy-
3.2.
Sollkonzept
53
stems liegt im allgemeinen nicht im Aufgabenbereich der Softwareherstellung. Die Benutzerschnittstel 1 e ist der Kern des Sollkonzepts. Sie legt zum einen die Funktionen fest, die die Benutzer des zukünftigen Systems von ihm erwarten, zum anderen die Art und Weise, wie die Benutzer mit dem System kommunizieren. Durch Kenntnisse und Aufgaben unterschieden gibt es drei Grundtypen von Benutzern: -
EDV-Laien, die den Rechner als Hilfsmittel bei ihrer fachlichen Tätigkeit benutzen,
-
EDV-Fachleute, die mit der gelieferten Software arbeiten, den Rechnerbetrieb, der dafür sorgt, daß das Softwareprodukt den anderen Benutzern zur Verfügung steht, aber nicht selbst mit ihm arbeitet.
Ein Beispiel für Systeme, bei denen alle drei Benutzerklassen anzutreffen sind, sind Leihbibliotheken: am Ausleihschalter wird der Leihverkehr mit Hilfe des Rechners durchgeführt, Bibliotheksangestellte mit Systemkenntnissen überwachen z.B. den Zustand von Dateien, und der Rechnerbetrieb stellt Magnetbänder und -platten zur zur Verfügung. Im weitesten Sinn kann man auch diejenigen als Benutzer betrachten, die nur in einer Richtung mit dem Rechner kommunizieren, die z.B. EDV-gerechte Formulare ausfüllen oder lesen müssen. Die Beschreibung der zukünftigen Benutzerklassen ist das Benutzermodel 1. Es enthält Angaben zu -
Aufgaben der Benutzer
-
vorhandenen Fach- und EDV-Kenntnissen
-
Anzahl der Benutzer und Häufigkeit der Benutzung
-
besonderen Wünschen und Ansprüchen der Benutzer.
54
Problemanalyse
3.3.
Die Beschreibung der Funktionen darf keine Lösungsalgorithmen vorwegnehmen. Sie besteht in der Angabe der Ein-/Ausgabe-Relationen. Zur vollständigen Beschreibung des Softwareprodukts gehören weiterhin -
die geplanten oder absehbaren Änderungen und Erweiterungen der Systemfunktionen
-
die Anforderungen an Ausfall Sicherheit und Effizienz
-
die zu erstellende Dokumentation.
Die Auswahl der Basismaschine richtet sich nach den Leistungsanforderungen und nach der Finanzkraft des Auftraggebers. Oft wird diese von ihm festgelegt. Die für die Durchführung des Projekts vom Softwarehersteller einzusetzenden Resourcen (Personal, Zeit, Rechnerzeit
usw.) können erst
im Rahmen einer Durchführbarkeitsstudie (s. Abschnitt 3.4. und 3.5.) soweit abgeschätzt werden, daß sie fest vereinbart werden können. Alle den Rechner und die Software betreffenden Teile des Sollkonzepts werden in dem für Auftraggeber und Softwarehersteller verbindlichen Dokument Anforderungsdefinition zusammengefaßt. Sie ist die Grundlage für den abzuschließenden Vertrag, wenn die Software von einem Unternehmen für einen Kunden hergestellt wird. An sie muß deshalb die Forderung gestellt werden, in Form (Gliederung) und sprachlicher Gestaltung für beide Seiten verständlich und präzise zu sein. Sprachmittel, die generell für diesen Zweck verwendbar wären, stehen nicht zur Verfügung, so daß die Wahl einer geeigneten Kommunikationsebene Erfahrung und Einfühlungsvermögen verlangt.
3.3. Darstellungsmethoden Die Darstellungsmethode in der Problemanalyse beeinflußt sowohl den Verlauf der Analyse als auch die Vollständigkeit und Oberprüfbarkeit der angefertigten Systembeschreibung. Verlangt z.B. eine Me-
Darstellungsmittel
3.3.1.
55
thode die Darstellung bestimmter Details, so zwingt sie ihren Anwender, diese Details auch zu ermitteln. Läßt sie dagegen keine d i f f e renzierte Darstellung bestimmter Objekttypen zu, behindert sie die Genauigkeit der Analyse. Aus solchen Überlegungen können wir einige Kriterien zur Beurteilung einer Darstellungsmethode ableiten: -
Sie s o l l t e es fördern, komplexe Systeme mit unterschiedlicher Detaillierung (verschiedene Abstraktionsstufen) darzustellen, indem sie Verfeinerüngsmechanismen zur Verfügung s t e l l t .
-
Sie s o l l t e gute Differenzierungsmöglichkeiten für unterschiedliche Objekte zur Verfügung stellen.
-
Sie s o l l t e mit anderen Methoden verträglich sein, d.h. es s o l l t e möglich sein, ein Teil problem mit einer dafür besonders geeigneten Methode darzustellen, ohne daß das Ganze dadurch unverständl i c h wird.
-
Sie s o l l t e verständliche und eindeutige Konstrukte zur Verfügung stellen.
Eine Methode s o l l t e nur für den Zweck eingesetzt werden, für den sie sich eignet. Keine Methode i s t universell. Wir stellen im folgenden einige gebräuchliche und zwei neuere Methoden vor. Weitere wurden nicht berücksichtigt, weil ihre Einsatzmöglichkeiten in der Problemanalyse noch nicht hinreichend erforscht (Petri-Netze [Holt76]), oder weil sie sehr speziell sind (Interaktionsdiagramme [Denert77]).
3.3.1. Gebräuchliche Darstellungsmittel WVL könnten
auch ex.new FzdeAAtieZ
MiiAtzhzn, uienn man ¿hm iA.no. Bedeutung gegeben
hätte..
Wittgenstein: Philosophische Grammati k
56
Problemanalyse
3.3.1.
Programmabi aufpläne (Flußdiagramme) nach DIN 66001 sind ursprünglich für die Programmierung im Kleinen (s. Kapitel 8) vorgesehen, werden aber in der Praxis auch zur Problemanalyse eingesetzt. Der einzige Darstellungsaspekt i s t der Ablauf von Operationen. Für Ein-/AusgabeOperationen, manuelle Tätigkeiten und Verzweigung sind spezielle Symbole vorgesehen. Das Symbol für Unterprogrammaufrufe kann zur Darstellung von Abstraktionen benutzt werden. Objekte können in Flußdiagrammen nicht dargestellt werden. Auch die Differenzierungsmöglichkeiten für Operationen sind unzureichend. Deren Bedeutung geht a l l e i n aus den gewählten Bezeichnungen hervor. Außerdem gibt es keine Anhaltspunkte für eine Gliederung der Operationen nach Abstraktionsstufen oder anderen Kriterien. Dies heißt nicht, daß es ausgeschlossen i s t , sinnvolle und verständliche Flußdiagramme herzustellen. Wegen der aufgeführten Mängel sollten sie bei der Systembeschreibung a l l e n f a l l s als Ergänzung zu anderen Methoden benutzt werden. In Datenflußplänen können Bearbeitungsschritte und Datenträger dargestellt werden. Verbindungen zwischen diesen Elementen bedeuten die Benutzung oder Erzeugung von Daten auf dem jeweiligen Datenträger. Differenzierte Darstellungsmittel sind für Datenträger und für typische Operationen (z.B. Sortieren) vorhanden. Eine Möglichkeit zur Verfeinerung i s t nicht vorgesehen; dadurch werden Darstellungen komplexer Systeme unübersichtlich. Wie bei Programmabi aufplänen kann die Bedeutung der Verarbeitungsschritte nur aus ihrer Benennung und Beschreibung in natürlicher Sprache ersehen werden.
3.3.1.
Bild 3-1:
Darstellungsmittel
57
Datenflußplan für eine Nettolohnberechnung [Wedekind73]
Datenflußpläne sind geeignet, einen überblick liber den Informationsfluß in einem mit EDV organisierten System zu geben. Für nicht automatisierte Systeme und für d e t a i l l i e r t e Darstellungen sind sie ungeeignet. Entscheidungstabellen sind Hilfsmittel zur Darstellung von Alternativen. Eine Entscheidungstabelle i s t eine zweigeteilte Matrix, deren Reihen durch Bedingungen und Aktionen bezeichnet werden; die Spalten bezeichnen Regeln. Bedingungen können sowohl boolesche als auch andere Werte annehmen. Innerhalb einer Spalte sind sie im Normalf a l l konjunktiv verknüpft. Die Reihenfolge der Aktionen i s t durch ihre Anordnung von oben nach unten gegeben. Sind Abweichungen von dieser Folge notwendig, können sie durch eine entsprechende Numerierung angegeben werden [Strunz70].
58
Problemanalyse
3.3.1.
Regeln Eingegangene-^^^ Publikationen^""---^^^
1
2
3
4
5
6
7
8
9
Art des Eingangs
A
A
A
A
G
G
G
G
B
In "Bestellt"-Kartei registriert ?
J
N
N
J
N
N
J
N
N
N
N
N
J
N
J
Schon vorhanden ?
J
Paßt zum Bestand ?
J
sonst
Bestellen und in "Bes t e l l t"-Kartei r e g i s t r . Rechnung zur Bearbeitung weiterleiten
X
X
Eintrag in " B e s t e l l t " Kartei löschen Zurücksenden
X X
X
X
Aussondern (Einstampfen oder Verschenken) Zur Titel aufnähme weiterleiten An Sonderfallbearbeitung weiterleiten Erläuterung der Abkürzungen:
Bild 3-2:
X X
X X
X X
X X
zur Ansicht bestellt Geschenk Ja Nein
Tätigkeiten in einer Bibliothek beim Erwerb von Publikationen (Akzession)
3.3.2.
Die SADT-Methode
59
Die Tabelle i s t z.B. so zu interpretieren: Erhält man eine Publikation zur Ansicht, die nicht schon bestellt und auch nicht vorhanden i s t , aber zum Bestand der Bibliothek paßt, so wird sie zur T i t e l aufnahme weitergeleitet und die Rechnung bearbeitet (Regel 4). Entscheidungstabellen sind zur Darstellung solcher Probleme geeignet, bei denen die Handlungsalternativen von einer großen Zahl von Bedingungen abhängen. Sie sind wesentlich übersichtlicher als eine rein verbale Darstellung. Die jeweiligen Entscheidungen werden vom Standpunkt der Entscheidungsinstanz aus beschrieben. Erfordert die Abfrage von Bedingungen Aktionen, die wegen ihrer Wichtigkeit auch aufgeführt werden sollen, ergeben sich Schwierigkeiten. In unserem Beispiel i s t die Beantwortung der Frage "Paßt zum Bestand?" eine Tätigkeit, die besonderen Fachreferenten obliegt. Um diese in der gleichen Tabelle als Aktion darstellen zu können, müßte man zu Hilfskonstruktionen greifen wie "Tätigkeit der Fachreferenten beendet?". Die klarere Lösung i s t in solchen Fällen, die Tabelle aufzuteilen. Dadurch wird der Vorteil der größeren Übersichtlichkeit etwas gemindert. In Grenzfällen benötigt man ein große Zahl von Entscheidungstabellen, die jeweils nur eine Bedingung enthalten. 3.3.2. Die SADT-Methode Die SADT-Methode ^Structured Analysis and JDesign Technique, [Softech76]) i s t nicht nur ein Darstellungsmittel, sie beinhaltet auch Handlungsanweisungen, wie ein System untersucht werden s o l l . Darüber hinaus beansprucht sie auch, geeignetes Mittel für den Software-Entwurf zu sein. Die folgende Beschreibung dieser Methode beschränkt sich auf den Aspekt des Einsatzes in der Problemanalyse. Die Modelle werden in SADT aufgrund mehrerer Prinzipien gebildet: 1.
Der Anwender wird angehalten, ein System jeweils von einem bestimmten Standpunkt aus zu beschreiben. Im allgemeinen i s t es zu einem besseren Verständnis notwendig, nacheinander mehrere Standpunkte einzunehmen. Z.B. kann man eine Bibliothek vom Standpunkt des Lesers und der Bibliotheksverwaltung betrachten.
60 2.
Problemanalyse
3.3.2.
Die Beschreibung beginnt auf der höchsten Abstraktionsstufe. Verfeinerungen führen zu einer hierarchischen Zergliederung des Systems.
3.
Die Zergliederung in Teilsysteme erfolgt so, daß jedes T e i l s y stem unabhängig von den anderen Teilen der gleichen Abstraktionsebene verfeinert werden kann. Eine Verfeinerung s o l l zu mindestens drei, höchstens sechs neuen Teilsystemen führen. (Diese Restriktion erscheint zumindest qualitativ gerechtfert i g t , da sie die jeweils zu bewältigende Komplexität beschränkt und dazu zwingt, die Abstraktionsebene möglichst nicht zu durchbrechen. )
4.
Jedes Modell kann und soll zweifach dargestellt werden, einmal unter dem Aspekt der Funktionen und einmal unter dem der Objekte. Von den Darstellungsmitteln Kästen, Pfeile und Benennungen werden im ersten Fall die Kästen für die Funktionen und die Pfeile für die Objekte verwandt, im zweiten umgekehrt. Die Symbole sind entsprechend ihrem Zweck verbal bzw. nominal zu benennen (Bild 3-3).
erzeuge^
verarbeite Eingabe"^ Objekte
B i l d 3-3:
zu
Ausgabt Objekte
Objekt
verwende
Funktionenaspekt und Datenaspekt
Die duale Darstellung von Systembeziehungen ermöglicht die wechs e l s e i t i g e Oberprüfung des Modells auf Vollständigkeit und Korrektheit.
3.3.2. 5.
Die SADT-Methode
61
SADT enthält keine Darstellungsmöglichkeiten für den Steuerfluß. Dadurch soll verhindert werden, daß bei der Beschreibung der funktionalen Anforderungen eines Systems algorithmische Aspekte der Lösung einfließen. Da eine Funktion nur dann ausgeführt werden kann, wenn a l l e notwendigen Objekte bereitstehen, kann imp l i z i t eine Aufeinanderfolge dargestellt werden.
Anhand der Darstellung von Teilaufgaben einer Bibliothek sollen die Anwendung der SADT-Sprache veranschaulicht und weitere Sprachelemente eingeführt werden. Aus der Sicht der Bibliotheksverwaltung kommuniziert die Bibliothek mit Buchhändlern, von denen sie Publikationsankündigungen und Publikationen erhält, sie l e i h t Bücher aus ihrem Bestand an Benutzer aus und nimmt sie wieder zurück,und sie beschäftigt Angestellte. S,
S2
Haushaitsplan
Ei: E2: E3:
Eingänge von Buchhändlern Eingänge von Benutzern Personal
>
Bestandspolitik
verwalte die Bibliothek
Ausgänge an Buchhändler Ausgänge an Benutzer Personal
:A,
:A 2 :A 3
SADT-Diagramm Bib 0 Bild 3-4:
Funktionsdiagramm einer Bibliothek aus der Sicht der Verwaltung
Der Haushaltsplan und die Bestandspolitik sind Informationen, die die Verwaltung steuern. Solche Informationen werden in SADT-Diagrammen als Pfeile dargestellt, die von oben in einen Kasten hineinführen.
62
Problemanalyse
3.3.2.
In der nächsttieferen Abstraktionsstufe gliedert sich die Bibliothek in die Teilbereiche Bucherwerb, Benutzerdienst und Personalverwal-
Bild 3-5:
Teilaufgaben der Bibliothek
Die Bezeichnungen E^, S^, A-j identifizieren die Eingangs-, Steuerund Ausgangsdaten des Diagramms mit den entsprechenden des Vaterdiagramms. Pfeile, deren Ausgangspunkt in Klammern eingeschlossen ist, kennzeichnen Informationen oder Daten, die auf der höheren Abstraktionsstufe unwichtig waren. Von unten in einen Kasten hineinführende Pfeile bedeuten im Falle von Funktionen den ausführenden Prozessor, im Falle von Daten den Datenträger.
3.3.2.
Die SADT-Methode
63
Zum Abschluß des Beispiels verfeinern wir das Teildiagramm Bib 1.1 - "Erwirb Publikationen" - und zeigen es als Datendiagramm.
Bib 1.1 Bild 3-6:
Verfeinerung in Datendarstellung
Vorteile dieser Methode sind: -
Die Methode zwingt dazu, ein System gut zu verstehen, um es beschreiben zu können.
-
Sie s t e l l t differenzierte Konzepte zur Verfügung, um auch komplexe Zusammenhänge beschreiben zu können.
-
Sie i s t r e l a t i v leicht erlernbar und mit etwas Übung gut verständlich.
64
Problemanalyse
3.3.3.
Dem stehen auch einige Nachteile gegenüber: -
Die Notwendigkeit zur hierarchischen Darstellung erfordert gekünstelte Abstraktionen, wenn ein Prozeß aus mehr als sechs gleichwertigen Verarbeitungsschritten besteht oder ein System sich in viele parallele Untersysteme gliedert.
-
Die Erstellung der Diagramme ist recht aufwendig. Auch kleinere Diagramme werden durch ein ungeschicktes Layout unleserlich, so daß Änderungen häufig eine neue Zeichnung des Diagramms erfordern.
Die Vor- und Nachteile der SADT-Methode lassen sich erst dann fundiert beurteilen, wenn Erfahrungen mit ihrem Einsatz in größeren Projekten dokumentiert sind, was zur Zeit nicht der Fall ist. Insgesamt ist die SADT-Methode ein guter Ansatz, für die Problemanalyse ein Verständigungsmittel zwischen Softwareherstellern und -anwendern zu entwickeln. Zwar bleibt die Schwierigkeit, zur Bezeichnung der Diagrammelemente geeignete Namen zu finden, doch stellt SADT einen Sprachrahmen zur Verfügung, der hinreichend präzise und Benutzern und Softwareherstellern verständlich ist. 3.3.3. Das ISDOS-Projekt Im Forschungsprojekt ISDOS an der University of Michigan wählte man einen anderen Weg zur Unterstützung der Problemanalyse. Es wurde eine Sprache entwickelt, die "Problem Statement Language" (PSL), in der Systemmodelle formuliert werden können. Hauptintention ist dabei der Einsatz zur Beschreibung des Sollkonzepts [Teichroew74]. Diese Systembeschreibungen werden über den "Problem Statement Analyser" (PSA) in einer Datenbank abgespeichert, wobei verschiedene Vollständigkeits- und Konsistenzüberprüfungen vorgenommen werden können. Die in der Datenbank gespeicherten Informationen können durch PSA unterschiedlich ausgewertet und dokumentiert werden. Zur Modellierung der Informationsverarbeitung in einem System stellt PSL u.a. folgende Sprachelemente zur Verfügung:
3.3.
Das ISDOS-Projekt
65
REAL WORLD ENTITY (RWE) Eine RWE ist die Bezeichnung eines Objekts der realen (Außen-) Welt, das mit dem System kommuniziert, z.B. eines Benutzers. PROCESS Das System wird als ein Prozeß dargestellt, der mit REAL WORLD ENTITIES kommuniziert. INPUT, OUTPUT bezeichnen die zwischen System und Umgebung ausgetauschten Informati onsei nheiten. SET Menge von Daten, die systemintern gesammelt und bearbeitet werden. Typische Sets sind Dateien. GENERATE, RECEIVE, UPDATE Die Operationen, die Daten erzeugen, ändern oder empfangen werden als Relationen dargestellt: RWE GENERATES INPUT; PROCESS GENERATES OUTPUT RWE RECEIVES OUTPUT; PROCESS RECEIVES INPUT PROCESS UPDATES SET SUBPARTS ARE Objekte und Prozesse können mit Hilfe dieser Relation verfeinert werden. CONSISTS OF Informationseinheiten können als Datenstrukturen dargestellt werden. Auf unterschiedlichen Abstraktionsstufen werden sie als ELEMENT, GROUP, ENTITY bezeichnet.
66
Problemanalyse
3.3.3.
Zu jeder Relation i s t auch ihre Umkehrrelation definiert. Sprachbeschreibung und ausführliche Beispiele finden sich in [Teichroew74] und [Hoehner75]. B i l d 3-7 zeigt einen Ausschnitt aus der Tätigkeit einer Bank, 3-8 eine Einzeldarstellung einer Datenstruktur.
B i l d 3-7:
Tätigkeit einer Bank
3.3.3. INPUT
Das ISDOS-Projekt GROUPS
GROUPS
67
ELEMENTS
Name
Kontonummer
Betrag
Bild 3-8:
CONSISTS OF - Datenstruktur der Bankeingänge
Eine vollständige Systembeschreibung in PSL besteht aus mehreren Abstraktionsebenen. Auf der detailliertesten Ebene enthält das Modell zum Beispiel Angaben über zeitliche Abhängigkeiten von Prozessen und über den Umfang von anfallenden Daten. Weitere Informationen oder Erläuterungen zu den in PSL definierten Objekten können in natürlicher Sprache zugefügt werden. Semantische Überprüfungen des Modells durch PSA beruhen auf der Zulässigkeit von Relationen zwischen Objekten und Redundanz bei der Eingabe. Dadurch können inkonsistente Verwendungen von Objekten und Relationen, fehlerhafte Relationen oder Fehler im zeitlichen Ablauf erkannt werden. Weitere Fehlersuche und Kontrolle wird unterstützt durch die zahlreichen Auswertungsmöglichkeiten der PSA-Datenbank: -
Vollständige Berichte; diese enthalten alle eingegebenen und aus diesen abgeleiteten Informationen
-
Inhaltsverzeichnisse, Namen-Typ-Listen
-
Graphische Darstellungen der Modell strukturen
-
Analysen des dynamischen Verhaltens des Modells.
68
3.4.
Problemanalyse
Das PSL/PSA-System ist wegen der Rechnerunterstützung in der Softwareentwicklung ein Fortschritt. Es wurde konzipiert für die Beschreibung von kommerziellen Anwendungssystemen. In darauf aufbauenden Projekten werden derzeit Erweiterungen und Anpassungen an spezielle Anwendungsbereiche entwickelt (z.B. [Ludewig78]). Die Aspekte der Informationsverarbeitung sind mit dem System gut modellierbar. Andere Aspekte, z.B. Anforderungen an die Benutzerschnittstelle, können nur schwer dargestellt werden.
3.4. Durchführbarkeitsstudie E£n Mznich eAhofät ilch {¡tiomm und i&JJL, Vaß eA olm,t dai k>Uzgt, wen zu mJUL. Kii eA. dann doch dm Wahn eAtizgt Und iahlleßllah don, will, wai zu kniagt. Eugen Roth: Ein Mensch Bevor ein Sollkonzept zur Grundlage eines Projekts gemacht wird, muß sichergestellt sein, daß es auch durchführbar ist. So wurde bei einem Projekt zur Erstellung eines Planungsinformationssystems erst nach mehrjähriger Arbeit festgestellt, daß es unmöglich sein würde, die notwendigen Daten zu erfassen, um mit dem System arbeiten zu können. Die technische Durchführbarkeit hängt einerseits von der Verfügbarkeit einer Rechenanlage ab, die zusammen mit der zu erstellenden Software die gewünschten Leistungen erbringen kann, andererseits davon, ob die Umgebung des Rechners, die Benutzer und die Ein-/Ausgabe-Geräte, die erforderlichen Daten in der gewünschten Menge und Genauigkeit zur Verfügung stellen oder bearbeiten können. In der persone!len Durchführbarkeitsstudie wird untersucht, ob für die Herstellung, Einführung und den Betrieb des neuen Systems Fachkräfte mit geeigneter Qualifikation zur Verfügung stehen. Darüberhinaus ist die Frage, was mit den bisherigen Beschäftigten geschieht.
3.4.
Durchführbarkeitsstudie
69
Werden sie im neuen System weiterbeschäftigt? Erhalten sie nach einer Umschulung einen anderen Arbeitsplatz? Häufig werden die in nicht mehr benötigten Funktionen Beschäftigten entlassen, denn, wie BASF-Direktor Dr. Bischoff formuliert: "Der Betrieb braucht die Menschen nicht als die Menschen, die Gott bei ihrem Namen gerufen hat, sondern als Funktionen. Er braucht nicht den Franz S., nicht den Ernst K., nicht den Heinz B., sondern er braucht einen Schlosser, einen Kraftfahrer, einen Buchhalter ... . Braucht er keinen Buchhalter mehr, weil dessen Arbeit von einer Rechenmaschine übernommen wird, so muß er sich von Heinz B. trennen, so wertvoll dieser als Mensch auch sein mag ... . Der Mensch als solcher ist für den Betrieb nichts, die Funktion, die er ausüben kann, alles. Ganze Berufe fallen weg, und die Menschen, die sie ausübten, werden überflüssig, wenn sie nicht anders nutzbar sind ... ." [Bischoff62] Vom Widerstand und den Forderungen der Betroffenen und ihrer Interessenvertretungen hängt es ab, ob sie Einfluß auf die geplanten Konzepte nehmen können. Dies gilt auch für die Gestaltung der Arbeitsplätze der zukünftigen Benutzer. Die Benutzungsfreundlichkeit ist ein Faktor, der die Bereitschaft, mit dem EDV-System zu arbeiten (Akzeptanz), entscheidend mitbestimmt. Die Akzeptanz kann bei Systemen, die völlig neu entwickelt werden, nur ungefähr im voraus beurteilt werden. Außer durch den Nutzen des Systems (s. Abschnitt 2.1.) wird die ökonomische Durchführbarkeit durch die Projektkosten bestimmt. Diese zu ermitteln, ist unter anderem die Aufgabe der Projektplanung, die im nächsten Abschnitt behandelt wird.
70
Problemanalyse
3.5.1.
3.5. Projektplanung Von.hzA6a.gen iit - btiondeJU
AchwiMÄg
{¡(in. diz
Zukunft.
Projektplanung hat zum Ziel die Schätzung der Gesamtkosten eines Softwareprojektes, die Erstellung eines Zeitplanes, den Personaleinsatz (PersonalVerteilung) zu planen und die notwendigen Resourcen zu ermitteln. Diese Projektparameter werden vor allem bestimmt durch das zu erstellende Softwareprodukt selbst - durch seine Größe und durch seine Schwierigkeiten. Planung muß demnach mit dessen Schätzung beginnen. Je gründlicher das System abgeschätzt wird, desto mehr wird die Planung Teil der Entwurfsphase. Ist es eine Voraussetzung zur Vergabe des Projekts, daß die Parameter mit der Anforderungsdefinition festgelegt werden, so beruht ihre Angabe entweder auf grober Schätzung, oder Anforderungsdefinition und Vertrag werden erst abgeschlossen, nachdem ein Teil des Systems entworfen wurde - genau genommen also während der Entwurfsphase. 3.5.1. Umfang des Softwareprodukts Die angestrebte Genauigkeit bestimmt die Methode, mit deren Hilfe man Umfang und Schwierigkeitsgrad der zu erstellenden Software schätzt. Es gibt drei wesentlich verschiedene Methoden [Wolverton74]: Top-down-Schätzung: Die Schätzung orientiert sich an ähnlichen Projekten oder an Teilen davon. Diese Methode führt leicht dazu, projektspezifische Schwierigkeiten zu Ubersehen oder zu unterschätzen und ist umso weniger geeignet, je größer das Projekt ist. Das Resultat wird besonders stark von Erfahrung und Persönlichkeit des Schätzers beeinflußt.
3.5.1.
Softwareumfang
71
Bottom-up-Schätzung: Das Problem wird vollständig in Einheiten zerlegt, von denen der Aufwand zur Erstellung klar i s t . Extrapolation ergibt den gesamten Aufwand. Da die Anwendung dieser Methode den Abschluß der Entwurfsphase voraussetzt, i s t sie eigentlich nur als Korrektur oder Detaillierung einer vorausgegangenen Schätzung denkbar. Faktorenschätzung (Ratio Estimating): Ein System wird so weit entworfen, daß man für seine Bestandteile Schwierigkeitsgrad, Typ (Ein-/Ausgabe, Dateihandling, . . . ) und Größe angeben oder schätzen kann. Diese Methode dürfte für größere Projekte am geeignetsten sein, weil sie eine q u a l i f i z i e r t e Grundlage für die Erstellung eines Zeitplans und der Personal Verteilung sein kann, ohne den gesamten Systementwurf vorauszusetzen. Bei der Abschätzung eines zu entwickelnden Softwareprodukts gibt es vor allem zwei Probleme: die Festlegung des Maßes, in dessen Einheiten das System geschätzt wird, und die Subjektivität des Schätzers. Je weniger eine Systemschätzung aus den Systemcharakteristika abgeleitet wird - bei der Top-down-Schätzung t r i f f t dies am stärksten zu - , desto stärker wird s i e von der Persönlichkeit des Schätzers beeinflußt. Sein Ehrgeiz, ein bestimmtes Projekt unbedingt durchführen zu wollen, ein Hang zum Pessimismus und andere Ursachen können die Schätzung erheblich von der Realität abweichen lassen. Die üblichen Maßgrößen für den Umfang von Software sind die Anzahl von Instruktionen oder Code-Zeilen. Das sind problematische Maße, da in jedem Fall genau definiert werden muß, was gezählt wird und was nicht: -
nur der endgültige Code oder auch Zwischenversionen ? der gesamte Programmcode, oder Code ohne Kommentare und Datendeklarationen ?
-
Anzahl der Maschineninstruktionen, Anzahl der Zeilen im Programm, oder Anzahl der Statements ?
-
nur der Programmcode oder auch der der Programmsteuerung ?
72
Problemanalyse
3.5.1.
Ein Problem bleibt aber dennoch: Aus dem geschätzten Umfang möchte man ja Erstellungszeit und benötigtes Personal ableiten. Das kann man umso zuverlässiger tun, je mehr Daten aus vergangenen Projekten ausgewertet werden können. Da man sich bisher nicht geeinigt hat, was man unter Code-Zeilen versteht (was auch für die Zukunft nicht zu erwarten ist), ist es sehr schwer, Daten miteinander zu vergleichen. Prinzipiell schwierig ist zudem der Vergleich von Projekten, bei denen unterschiedliche Programmiersprachen verwendet wurden. Die Anzahl der Programmzei1en kann z.B. für Assembler- und höhere Programmiersprachen nur schwer verglichen werden; ebensowenig die Anzahl der erzeugten Maschineninstruktionen (Compiler!). Die reinen Quantitätsangaben müssen um Abschätzungen der Schwierigkeit und des Typs der Software ergänzt werden, denn sie beeinflussen ebenso wie die Größe den Erstellungsaufwand.
Instruktionen/Mannjähr
Art des Programms Kontrollprogramm
500 -
700
Compiler
1000 - 1700
Anwendungsprogramm
2000 - 4000
Bild 3-9
[Endres75, S. 3-23f]
Instruktionen/Mannmonat 6-12 Mon.
12-24 Mon.
> 24 Mon.
wenig Wechselwirkung
400
500
800
einige
"
200
250
400
starke
II
100
125
125
Bild 3-10
[Endres75, S. 3-24]
3.5.2.
Arbeitsproduktivität
73
Die genauen Daten der Bilder 3-9 und 3-10 können hier nicht diskutiert werden. Sie stimmen mit anderen bei IBM gemachten Erfahrungen überein [Brooks75, S. 88-94], Vielmehr sollen die Bilder demonstrieren, daß Aussagen über die Größe des Programms allein nicht ausreichen. Weitergehende Ansätze versuchen verstärkt qualitative Aussagen über ein geplantes Programmsystem zu gewinnen und einer Planung zugrunde zu legen: -
In [Chrysler78] wird versucht, COBOL-Programme durch Anzahl der Dateien, Datensätze, Datenfelder, Ausgabeformate, mathematischen Operationen zu charakterisieren und Beziehungen zum Programmieraufwand herzustellen.
-
In [Chen78] wird der Steuerfluß eines Programms anhand eines Flußdiagramms topographisch untersucht und die Abfolge und die Verschachtelung von Steuerkonstrukten gewichtet.
-
In [Halstead77] werden Programme nach Anzahl, Häufigkeit und Unterschiedlichkeit ihrer Operatoren (das sind Operatoren im gewöhnlichen Sinn und die Steuerkonstrukte) und ihrer Operanden bewertet.
Man kann vermuten, daß langfristig solche genaueren Untersuchungen das einfache Durchzählen und Kategorisieren von Software ablösen werden.
3.5.2. Die Arbeitsproduktivität der Programmierer EifiahAung -Lit dai,
u m man
nickt
heut, wenn man ei am d>U.nge.nd&£e.n braucht. R. Boll er Will man aus Umfang und Schwierigkeitsgrad der Software den Gesamtaufwand und einen Zeitplan ermitteln, d.h. feststellen, wieviele Programmierer wie lange benötigt werden, muß man deren Arbeitspro-
74
Problemanalyse
3.5.2.
duktivität kennen. Man mißt s i e , indem man beobachtet, wieviel Software welcher Art in welchem Zeitraum e r s t e l l t wird. Ein übliches Maß i s t zum Beispiel: Code-Länge/Mannmonat. Beispiele dafür sind die Bilder 3-9 und 3-10. Dieses - übliche - Maß für die Produktivität übernimmt selbstverständlich a l l e Mängel, die schon der Bestimmung der Code-Länge anhaften. Hinzu kommen Probleme mit der zweiten Größe des Maßes: dem Mannmonat. Auch bei diesem muß geklärt werden, was er bezeichnet und auf welchen Messungen er basiert: -
wird er ermittelt über ein ganzes Projekt oder während der Programmierung im engeren Sinn ?
-
schließt er Ausfall durch Krankheit und Urlaub sowie Programmiererwechsel (Ausscheiden - Neueinstellung) ein ?
Das Maß Instruktionen/Mannmonat i s t genaugenommen vor allem deshalb zu einfach, da die Arbeitsproduktivität auf v i e l f ä l t i g e Weise beeinflußt werden kann.
Art des Rechnerzugangs
Rechnerkonfiguration
Projektorgani sation individuelle Eigenschaften
Produktivität)^
Programmiersprache
Problem
Programmi ermethodi k Bild 3-11:
Einflüsse auf Produktivität
nach [Chrysler78]
Bild 3-11 s t e l l t dar, welche Faktoren die Produktivität beeinflussen. Dazu einige Erläuterungen: -
Art des Rechnerzugangs: Studien ergaben 20% Erhöhung der Produktivität durch Dialoggegenüber Stapelbetrieb [Sackman70] nach [Boehm73].
3.5.2. -
Arbeitsproduktivität
75
Projektorganisation: Bei Erstellung eines Informationssystems sollen durch eine neue Management-Technik ("Chief-Programmer-Team"; s . Kapitel 10) 50% der erwarteten Kosten und 25% der Zeit eingespart worden sein [Baker72].
-
Programmiersprache: S i g n i f i kante Erhöhungen der Arbeitsproduktivität (bis zu 500%) wurden beim Einsatz höherer Programmiersprachen e r z i e l t [Brooks75, S. 94].
-
Programmierungstechniken: Produktivitätserhöhung durch Anwendung moderner Programmiermethoden i s t noch nicht ernsthaft untersucht worden. Sie i s t aber sicher nicht unerheblich.
-
individuelle Eigenschaften: Mannmonat i s t eine Durchschnittsgröße. Programmierer sind aber je nach Ausbildung und Erfahrung unterschiedlich produktiv. S o l che Unterschiede sind e r s i c h t l i c h aus Bild 3-12 2 , dem ein Vergleich von zwölf Programmierern bei der Lösung eines Problems zugrunde l i e g t . Auch die Vertrautheit mit einem Problem erhöht die Produktivität erheblich. B i l d 3-13 2 zeigt die Leistungsunterschiede einer Gruppe beim Schreiben dreier FORTRAN-Compiler.
2
Die Daten der angeführten Tabellen sind nur eingeschränkt sinnvoll interpretierbar, da ihr Zustandekommen und die verwendeten Maßgrößen nicht dokumentiert sind. Sie scheinen uns aber geeignet zu zeigen, daß es beträchtliche Produktivitätsunterschiede gibt.
76
Probi emanaiyse
3.5.3.
Leistung bei
schlechtester/bester
Testzeit
26
/
1
Rechenzeit
11 25
/ /
1 1
Codierzeit
Bild 3-12:
Bild 3-13:
Codelänge
5
/
1
Laufzeit
13
/
1
Leistungsunterschiede bei Programmierern
Compiler
Mannmonate
1
72
2
36
3
14
Leistungsanstieg bei Wiederholung
[David68]
[McClufe68]
Aus den Faktoren, die die Produktivität beeinflussen, sieht man, daß in der Zukunft gründliche Projektplanung nicht darauf verzichten kann, auch die Bedeutung dieser Faktoren quantitativ zu erfassen und in die Produktivitätsmaße einzubeziehen. Der Einfluß der Programmiererpersönlichkeit auf die Produktivität läßt sich sicher nicht gener a l i s i e r e n ; er muß beachtet werden, wenn bei Erstellung des Z e i t plans und der Personal Verteilung tatsächliches Personal aus abstrakten Mannmonaten abgeleitet wird. 3.5.3. Zeitplan und Personalverteilung
Nachdem Größe und Schwierigkeit der zu erstellenden Software sowie der abstrakte Personalbedarf (Mannmonate) geschätzt sind, steht der für das Projekt voraussichtlich benötigte Zeitraum ungefähr f e s t .
Zeitplan
3.5.3.
77
Es bleiben die Aufgaben, den optimalen Personaleinsatz zu planen und die Phasenaufteilung der Softwareentwicklung zu quantifizieren. Für letzteres haben sich - firmenabhängig - Faustregeln herausgebildet:
Evaluation (2%) [Brooks75, S. 20] Bild 3-14:
[Wolverton74]
[Mobil]
Faustregeln für Phasenaufteilung
Die erheblichen Differenzen und die unterschiedliche Bedeutung der Begriffe deuten darauf hin, daß diese Regeln bei der Projektstrukturierung nur zur ersten Orientierung h i l f r e i c h sein können. In einem Projekt müssen konkrete und nachprüfbare Etappenziele - Meilensteine - gesetzt werden, die Resultat der logisch analytischen Zergliederung des Projekts sind. Beispiele für solche Meilensteine: -
Spezifikation für Modul "Eingabe" verabschiedet
-
Modul "Eingabe" codiert
-
Testumgebung für Modul "Eingabe" f e r t i g g e s t e l l t
-
Modul "Eingabe" getestet
78
Problemanalyse
3.5.3.
Solch eine Untergliederung des Projekts in Arbeitsschritte verfolgt verschiedene Zwecke: -
Personal kann optimal v e r t e i l t werden.
-
Kritische Wege im Projektablauf werden erkannt. (Verzögerungen bei bestimmten Aktivitäten verzögern das ganze Projekt.)
-
Der Projektfortschritt i s t kontrollierbar. Bei Verzögerungen können rechtzeitig Maßnahmen getroffen werden.
-
Das Projekt kann an definierten Punkten unterbrochen oder beendet werden.
Um dies wirklich zu ermöglichen, i s t es nötig, als Meilensteine nur nachprüfbare Tätigkeitsabschlüsse zu wählen. Meilensteine wie: "zu 50% codiert" sind nicht s i n n v o l l , da ihr Erreichen nicht überprüfbar i s t . Für eine gute Personal Verteilung i s t die Untergliederung eines Projekts in Einzelaktivitäten insofern wichtig, als sie Hilfestellung dabei l e i s t e t , die Personal Verteilung aus Mannmonaten abzuleiten. Die einzelnen Tätigkeiten können miteinander verglichen und abgeschätzt werden, die Qualifikation der Programmierer kann bei der Zuteilung zu Tätigkeiten berücksichtigt, nötigenfalls können Programmiererschulungen geplant werden. Die s t r i k t e Phasenaufteilung eines Projekts (wie im Software Life Cycle vorgestellt) würde zu sehr unterschiedlichem Personalbedarf zu verschiedenen Zeitpunkten führen. Die Definition von Einzelaktivitäten ermöglicht, die Phasen nicht streng hintereinander, sondern überlappt anzulegen, um eine gleichmäßige Verteilung der Programmierer zu erzielen, ohne die Terminierung der Phasen aufzuweichen. Die Überlappung von Tätigkeiten und Phasen i s t dargestellt in B i l d 3-15. Man sieht, daß sich einige Tätigkeiten (z.B. Dokumentation) über das gesamte Projekt erstrecken. Die einzelnen Phasen überlappen sich z e i t l i c h . Die Überlappung von Modulentwicklung und Modultest i s t z.B. so zu verstehen, daß einige Moduln bereits f e r t i g
3.5.4.
Kostenschätzung
79
codiert sind und getestet werden, während andere noch codiert werden.
FREIGABE KOMPONENTEN-TEST
KUNDENBENÜTZUNG
PROJEKTINITIIERUNG
Bild 3-15:
ZEIT
Die Systementwicklung. Phasen und Funktionen
[Nash68]
Verwendete Darstellungsmittel sind Balkendiagramme und Netzpläne, da in ihnen jede einzelne Tätigkeit und jeder eingesetzte Programmierer erfaßt werden kann. Sie bieten sich für den Einsatz automatischer Projektplanungshilfsmittel an. 3.5.4. Kostenschätzung und Sachmittel Die Kosten für ein Projekt setzen sich aus drei Komponenten zusammen: den Kosten für die Programmierer, den Kosten für anderes benötigtes Personal und den Kosten für die Sachmittel. Den Hauptanteil bilden die Programmiererkosten. Kostenschätzung kann daher nur so genau sein, wie es die vorausgegangene Projektplanung war.
80
Problemanalyse
3.5.4.
Zur Schätzung des neben Programmierern benötigten Personals gibt es Faustregeln, die natürlich firmenabhängig sind. Endres (IBM) gibt an: "In einigermaßen großen Projekten kommen auf 100 Programmierer, die Kode schreiben, folgende Leute, die keinen Kode schreiben: 20 technische Autoren, 10 Manager, 10 Sekretärinnen und Verwaltungsspezialisten, 8 Programmierer für Systemverwaltung, Leistungsmessung usw., 2 Instruktoren, 6 Operateure, 2 Locherinnen, 2 Wartungsingenieure
sowie diverse Anteile an dem Personal der
Küche, Poststelle, ..." [Endres75, S. 3-23] Der Anteil der aus Rechnerbenutzung entstehenden Kosten beträgt ungefähr ein Fünftel der Gesamtkosten [Wolverton74]. Andere hinzukommende Kosten sind solche für Miete, Reisen, Büromaterial, die einem Unternehmen durch das Projekt verursacht werden, und solche, die von Kosten des Gesamtunternehmens zur Abrechnung auf die Projekte verteilt werden. Die Kosten für die Rechnerbenutzung ergeben sich aus Terminalbenutzung und Rechenzeit. Bild 3-16 zeigt solche Daten für vier Projekte, die bei TRW.durchgeführt wurden:
CPU-Std./Mannmonat
Termi nalstd./Mannmonat
36
2.20
7.60
35
1.27
4.40
33
1.30
4.50
33
2.02
7.00
Dauer in Monaten
Bild 3-16:
Rechenzeit und Terminalbenutzung
[Wolverton74]
Für gute Kostenschätzungen und Sachmittelplanung wird es notwendig sein, solche Daten über längere Zeit nach Projekttyp, -große u.ä. klassifiziert zu sammeln.
3.5.5.
Automatische Unterstützung
81
3.5.5. Automatische Unterstützung
Die bisherigen Ausführungen zeigen, daß Projektplanung oft hemdsärmelig aufgrund grober Schätzungen betrieben wird. Sehr viele S o f t ware-Projekte werden nicht zum geplanten Termin f e r t i g . Zeitmangel i s t immer noch der häufigste Grund für das Scheitern von Projekten. Daher gibt es v i e l f ä l t i g e Bemühungen, die Projektplanung automatisch zu unterstützen. Datenbanken und Informationssysteme sollen Erfahrungen sammeln, auswerten und verallgemeinern helfen. Projektablauf und Personal Verteilung sollen mit Methoden des Operations Research optimiert werden. -
TRW sammelt in einer Datenbank über a l l e Phasen der Projekte quantitatives Material. Aus den auf Routinen bezogenen Daten Zahl der Instruktionen, Typ, Schwierigkeitsgrad werden Kosten/ Instruktion, Kosten/Phase, Kosten/Tätigkeit, Zeitplan, Personalaufwand, Gesamtkosten ermittelt. Durch geeignete Anordnung der einzelnen Tätigkeiten wird die Personalverteilung optimiert [Wolverton74].
-
Das Rome Air Development Center (RADC) hat in einer Datenbank die Daten von 400 Projekten gespeichert: Objektcode-Zeilen, Zeiten für Problemanalyse, Entwurf, Codierung, Code-Oberprüfung, Test und Integration, Systemtest. Daraus werden Formeln für die Projektplanung abgeleitet [Schneider78].
-
IBM hat 60 Projekte mit einem Umfang zwischen 4000 und 467000 Zeilen Quellcode in einer Datenbank erfaßt. Die Programme werden nach ihrer Schwierigkeit einer von 4 Haupt- und 45 Unterkategorien zugeordnet; die Codezeilen der Programme und von Zwischenversionen sowie der Dokumentationsumfang werden erfaßt. Der Ant e i l der einzelnen Phasen, die Höhe der Sachmittel, die Anzahl der Mannmonate
sowie Fehlerstatistiken werden ebenfalls aufbe-
82
Problemanalyse
3.5.5.
wahrt. Abgeleitet werden aus der Datenbank die Produktivität, die durchschnittlich benötigten Personen, auf die Produktivität einwirkende Größen, die Projektdauer, der Gesamtaufwand -
[Walston77],
Weitere Systeme verarbeiten eingegebene Netzpläne. Sie ermitteln kritische Wege, erstellen Terminpläne, optimieren Tätigkeitsabfolgen. Sie beruhen auf Methoden des Operations Research.
Die angeführten Systeme setzen zumeist die anfangs erwähnte Hemdsärmeligkeit der Projektplanung (das IBM-System am wenigsten) fort: Bei der Untersuchung der Programme beschränken sie sich auf das Zählen von Zeilen und die subjektive Zuordnung zu Schwierigkeitsgraden und Problemkategorien. Durch die Auswertung einer großen Menge von Daten machen sie aber die Projektplanung zuverlässiger. Zu dem methodisch ungelösten Problem, den Erstellungsaufwand aus Programm- und Problemstruktur zu ermitteln, gesellt sich die Schwierigkeit, gesammelte Informationen auszuwerten. Die Auswertungsmöglichkeit beschränkt sich auf die Unternehmen, die die Informationen sammeln. Software-Projekte werden aber nicht nur von Großunternehmen und Großinstitutionen durchgeführt, die sich teure Datenbanken leisten können (die Entwicklungskosten der RADC-Datenbank betragen $ 600 Millionen), sondern auch von vielen kleinen - z.B. von Softwarehäusern. Die Kostensenkung durch gute Projektplanung beschränkt sich also auf Einzel unternehmen. Im gesellschaftlichen Rahmen betrachtet werden durch ungenaue Planungen weiterhin große Geldsummen verschwendet.
Zusammenfassung
3.6.
83
3.6. Zusammenfassung In der Istanalyse wird das System, in dem die zu erstellende Software eingesetzt werden soll, so beschrieben, wie es vorgefunden wird. Das Sollkonzept beschreibt aus Anwendersicht die Problemlösung, für den Softwarehersteller definiert es eine Aufgabe. Kern des Sollkonzepts ist die Anforderungsdefinition. Sie legt fest -
Benutzermodel 1
-
Benutzerschnittstelle
-
Qualitätsanforderungen
-
Dokumentationsanforderungen
-
Basismaschine
-
voraussichtliche Anforderungsänderungen in der Wartungsphase
-
Resourcen zur Herstellung.
In einer Durchführbarkeitsstudie wird untersucht, ob das Sollkonzept technisch, personell und ökonomisch realisierbar ist. Gebräuchliche Darstellungsmethoden weisen erhebliche Mängel auf oder sind begrenzt einsetzbar. SADT ist eine neuere Methode, die relativ breit einsetzbar ist. Das PSL/PSA-System bietet erstmals eine erhebliche Rechnerunterstützung in der Problemanalyse. Die Projektplanung beginnt in der Problemanalysephase, umfaßt aber auch spätere Phasen. Sie geht aus von einer Abschätzung des Umfangs, des Typs und des Schwierigkeitsgrades des zu erstellenden Softwaresystems und seiner Bestandteile. Vermittelt durch die Arbeitsproduktivität läßt sich aus dieser Abschätzung der notwendige Bedarf an Programmierern ableiten. Die Definition von Ein-
84
Problemanalyse
3.6.
zelaktivi täten, deren Abschluß nachprüfbare Meilensteine darstellen, ermöglicht optimale Zeitplanung und Personal Verteilung. Mit dem ermittelten Personalbedarf und den geschätzten Sachresourcen lassen sich die Projektkosten schätzen. Projektplanung i s t keine einmalige A k t i v i t ä t , sondern ihre Ergebnisse werden während des gesamten Projekts k o r r i g i e r t und d e t a i l l i e r t . Sie i s t Teil des Projektmanagements.
4.1.
Benutzerdokumentation
85
4. Dokumentation In Bild 3-15 ist die Dokumentation als eine Tätigkeit aufgeführt, die sich über alle Phasen des Software Life Cycle erstreckt. Das hat seinen Grund darin, daß wir unter Dokumentation alle Dokumente (und ihre Erstellung) verstehen, die das Arbeiten der Benutzer mit der Software und die zu deren Erstellung und Änderung nötige Kommunikation unterstützen. Es gibt demnach nicht eine Dokumentation, sondern eine größere Zahl von Dokumenten, die jeweils einen bestimmten Zweck erfüllen und verschiedene Adressaten haben.
4.1. Benutzerdokumentation
Aus dem DEUTSCHEN MAD
86
Dokumentation
4.1.1.
Die Benutzerdokumentation beschreibt ein fertiggestelltes Softwaresystem so, daß es ohne Zuhilfenahme anderer Dokumente benutzt werden kann. Es handelt sich um eine Gebrauchsanweisung, wie sie auch Bügeleisen oder Eierkochern beigegeben ist. Ausschlaggebend für die Gestaltung der Benutzerdokumentation ist das der Systementwicklung zugrunde gelegte Benutzermodell. Diesem entsprechend müssen die drei Leitfragen bei der Dokumentation beantwortet werden: 1.
Wem soll was mitgeteilt werden?
2.
Welche sprachlichen Mittel sollen verwendet werden?
3.
In welcher Form soll es mitgeteilt werden?
4.1.1. Inhalt der Benutzerdokumentation Ebensowenig wie die Bedienungsanleitung für ein Telefon Angaben über Relaisschaltungen beim Bewegen der Wählscheibe enthält, darf die Benutzerdokumentation für ein Softwaresystem Angaben über den Steuerfluß im Programm oder andere für Benutzer irrelevante Implementierungsdetails enthalten. Das Programm wird als Black Box nur durch Angabe der Benutzerschnittstelle beschrieben. Diese Beschreibung enthält: -
vollständige und eindeutige Informationen über die Systemfunktionen und ihre Zusammenhänge, also über Benutzeraktionen und Systemreaktionen;
-
eine Liste der Fehlermeldungen, deren Bedeutung und der möglichen Benutzeraktionen;
-
Hinweise auf Benutzerreaktionen bei außergewöhnlichen Ereignissen (Störung des Grundsystems, Undefiniertes Verhalten des Programms);
-
die Voraussetzungen zur Benutzung (benötigte Dateien, technische Resourcen).
4.1.2.
Sprachmittel
87
Die Benutzerdokumentation wird in drei Hauptabschnitte eingeteilt [Goos73]: Das Einführungshandbuch enthält eine informelle Einführung und einen Überblick über die mit Hilfe des Systems lösbaren Probleme. Daneben führt es in den "Standard"-Gebrauch des Programms ein. Es enthält dabei die Informationen über Verfügbarkeit und Zugang zum System (Voraussetzungen und Kommandos zum Systemstart) sowie die Erläuterung der Funktionen, die zur "standardmäßigen" Benutzung ausreichen. Das Einführungshandbuch muß jeder Benutzer ohne Voraussetzungen, die über sein Anwendungsfachwissen hinausgehen, verstehen können. Das Nachschlagehandbuch (reference manual) enthält die vollständige und eindeutige Beschreibung der Anwendungsschnittstelle des Systems. Sie ist deren definierendes Dokument. Beim Benutzer des Nachschlagehandbuchs kann die Kenntnis des Einführungshandbuchs vorausgesetzt werden. Das Operateur-Handbuch findet sich in einer Benutzerdokumentation natürlich nur, wenn das System eine Schnittstelle zum Operateur besitzt. In ihm sind die zur Verfügung zu stellenden technischen Resourcen und Eingriffe an der Konsole aufgeführt. Für Systeme, die Anwender verschiedener Stufen mit unterschiedlichen Benutzerschnittstellen kennen, muß die Benutzerdokumentation zusätzlich nach Benutzerklassen untergliedert oder es müssen verschiedene Benutzerhandbücher erstellt werden.
4.1.2. Sprachmittel der Benutzerdokumentation Mit der Wahl geeigneter Sprachmittel steht und fällt die Güte der Benutzerdokumentation. Sie muß zwei Ansprüchen genügen: -
Da sie die Benutzerschnittstelle eines Systems definiert, muß sie diese ausreichend genau beschreiben können.
-
Da sie die "voraussetzungslose" Anwendung ermöglichen soll, muß sie dem Benutzer verständlich sein.
88
Dokumentation
4.1.3.
Demgemäß müssen in einer Benutzerdokumentation verschiedene sprachliche Mittel eingesetzt werden. Dazu gehören - aufgrund des Anspruchs, definierend zu sein - formale Notationen zur Festlegung der Systemfunktionen. Weniger formale umgangssprachliche Beschreibungen, die z.B. bei Systemfunktionen gegliedert sind nach Parameterversorgung, Wirkung, Fehlermöglichkeiten, sind geeignet, einen Kompromiß zwischen Exaktheit und Verständlichkeit zu schließen. Soweit natürliche Sprache verwendet wird (vor allem bei der Einführung in ein System und bei der Erläuterung der Intention von Kommandos), ist diese in verständlicher und korrekter Weise zu benutzen. (Vor allem aus dem Englischen übersetzte Dokumentationen genügen dieser Selbstverständlichkeit häufig nicht.) Der Text soll zwar knapp und präzise sein.1 Jedoch ist darauf zu achten, daß redundanzfreie Texte von menschlichen Lesern nur schwer verstanden werden. Jede Beschreibung wird verständlicher durch Beispiele, welche sie aber nur unterstützen und nicht ersetzen können. Graphische Darstellungen sind dazu ebenfalls geeignet. Die verwendeten graphischen Elemente müssen aber semantisch definiert sein. Beispielsweise tragen Kästen und Pfeile gleicher Form aber verschiedener Bedeutung eher zur Verwirrung als zur Erhellung bei. Da es in vielen Beschreibungen nicht möglich ist, auf (evtl. selbstgeprägte) Fachwörter zu verzichten, ist der Benutzerdokumentation ein erklärendes Fachwortverzeichnis (Glossar) beizugeben. 4.1.3. Form der Benutzerdokumentation Die Form der Benutzerdokumentation bestimmt wesentlich, ob die Dokumentation dem Benutzer die leichte Erschließung der Informationen ermöglicht. Ein Beispiel: Ein Nachweis über alle Bücher im Bestand
1
Im Gegensatz zu universitären Programmierprojekten, bei denen eine Benutzerdokumentation häufig "aus Zeitgründen" entfällt, zumal die einzigen Benutzer meist die Implementierer selbst sind, leiden industrielle Softwareprojekte häufiger unter einem Wust von Dokumentationen.
4.1.3.
Form Benutzerdokumentation
89
einer Leihbibliothek ist der alphabetische Katalog. Damit ein Benutzer den Bestand auch erschließen kann, wenn er nicht schon exakt weiß, was er will, stehen ihm in der Regel Sachkataloge (z.B. systematischer Katalog) zur Verfügung. Auch bei einer Benutzerdokumentation ist es wichtig, nicht nur die genau auf Funktionseigenschaften abzielenden Fragen - wie z.B. nach der Parameterversorgung - zu beantworten, sondern die Erschließung der Systemeigenschaften zu ermöglichen. Dazu sind notwendig Inhaltsverzeichnis, Stichwortverzeichnis, eine Liste aller Systemfunktionen und Querverweise im Text. Dazu gehören aber auch überblicksartige Darstellungen (z.B. Tabellen) und eine Gliederung der Funktionen nach ihrer Intention. Diese hilft einem Benutzer bei Fragen wie: "Was mache ich, wenn ...". Insbesondere das Einführungshandbuch soll fortlaufend gelesen werden können. Seine Beschreibung der Standard-Benutzung des Systems und die anderen Teile der Benutzerdokumentation dienen dem schnellen Auffinden von Informationen. Änderungsfreundlich sollte nicht nur das Programm, sondern auch die Dokumentation sein: Teile sollen austauschbar sein; ein Oberblick über die aktuell gültigen Teile gehört zur Dokumentation
(Änderungsstand).
Die Gestaltung der Benutzerdokumentation durch Inhalt, Sprachmittel und Form beeinflußt erheblich die Benutzerfreundlichkeit des Systems. Die benutzerfreundlichsten Funktionen verlieren andererseits an Wert, sind sie nicht angemessen dokumentiert. Ein in seinen Funktionen benutzerunfreundliches System kann allerdings mit der schönsten Dokumentation nicht benutzerfreundlich gemacht werden. Sinnvollerweise wird die Benutzerdokumentation erst abgeschlossen, nachdem das System selbst fertiggestellt ist. Da mit der Anforderungsdefinition (spätestens mit der Spezifikation) die Benutzerschnittstelle festgelegt ist, sollte aber schon dort eine Vorversion angefertigt werden. Auf ihrer Grundlage können Testsätze entworfen und kann geprüft werden, inwieweit das implementierte System den Anforderungen genügt. Frühzeitiges Erstellen der Benutzerdoku-
90
Dokumentation
4.2.1.
mentation verringert auch die Wahrscheinlichkeit dafür, daß sie aufgrund von Zeitdruck nicht mit gebotener Gründlichkeit erarbeitet wird.
4.2.
Entwicklungsdokumentation
Im Gegensatz zur Benutzerdokumentation, die ein Softwaresystem als Black Box beschreibt, legt die Entwicklungsdokumentation Struktur und Arbeitsweise des Systems sowie den Prozeß seiner Herstellung offen. Sie hat mehrere Funktionen: -
Sie hilft ein Projekt zu organisieren, indem sie Kommunikation formalisiert: Kommunikationswege werden definiert, Kommunikationsergebnisse festgehalten; Tätigkeitsabschlüsse und damit auch der Projektfortschritt sind überprüfbar.
-
Sie ermöglicht das Verständnis des Systems: jeder Arbeitsschritt zur Konzeption des Gesamtsystems und von da aus zur Realisierung jedes einzelnen Systembestandteils ist dokumentiert. Dies ist aus mehreren Gründen wichtig: Gründe für Entscheidungen können nachvollzogen, Fehlentscheidungen mit angemessenem Aufwand revidiert oder Änderungen vorgenommen werden.
4.2.1. Inhalt und Aufbau der Entwicklungsdokumentation Die Bestandteile einer Entwicklungsdokumentation gliedern sich in drei Gruppen: -
Im wesentlichen für die Projektverwaltung vorgesehen ist die Verwaltungsakte, die, die Anforderungsdefinition ausgenommen, alle Vereinbarungen zwischen Auftraggeber und Auftragnehmer enthält (z.B. Vertrag, Projektabschlußbericht, Abnahmeprotokoll). Projektintern enthält sie die verschiedenen Stadien des Projektplans und Projektfortschrittsberichte. Ihre Erstellung beginnt mit der Problemanalyse. Korrekturen und Neueinträge sind während der gesamten Projektdauer vorzunehmen.
4.2.1.
Bild 4-1: -
Inhalt Entwicklungsdokumentation
91
Bestandteile der Entwicklungsdokumentation
Das Projekttechnikhandbuch enthält organisatorische und administrative projektbezogene Informationen. Dazu gehören Dokumentat i o n s r i c h t l i n i e n , Programmierkonventionen, die Beschreibung verfügbarer Hilfsmittel (z.B. Programmbibliotheken), ein Verzeichnis a l l e r projektspezifischen Fachbegriffe. Es i s t ein zentrales Korn-
Dokumentation
4.2.1.
munikationsmittel für a l l e im Projekt Arbeitenden. Seine E r s t e l lung beginnt mit der Konstituierung des Projekts (nach Vertragsabschluß). Da es in einem Unternehmen nicht sinnvoll i s t , das Projekttechnikhandbuch für jedes Projekt ab ovo zu entwickeln, steht über diesem eine Sammlung von Projekttechniken, die in j e dem Projekt zum Einsatz gelangen, bzw. nur modifiziert oder det a i l l i e r t werden. Die Systementwicklungsbeschreibung umfaßt a l l e phasenabschließenden Dokumente (Anforderungsdefinition, Spezifikation, Code, Testprotokolle). Dabei werden nicht nur die Resultate festgehalten, sondern es wird auch ihr Entstehungsprozeß dokumentiert: In Logbüchern werden zu diesem Zweck Diskussionsprotokolle, Vereinbarungen, Notizen, Entwürfe aufbewahrt. Ein Logbuch verfehlt seinen Zweck, kommt es nicht über Anhäufung a l l e r irgendwie erstellten Papiere hinaus. Eine Kategorisierung solcher Dokumente (Notiz, Protokoll, Vereinbarung, etc.) i s t genauso unumgänglich, wie es Vorschriften über die Inhalte sind. Beispiele dafür: softwaretechnische Methoden, die zur Anwendung kamen (z.B. Zerlegungskriterien) .
Verfahren aus dem Anwendungsgebiet (z.B. Jacobi- oder GaußSeidel-Verfahren zur Lösung linearer Gleichungssysteme)
.
Standardalgorithmen und ihre Variation (z.B. Wirbeltraversieren).
Die Testvorbereitung wird im Testplan festgehalten. Er enthält Vorgehensweise, Testfälle und ihre erwarteten Resultate abgestuft nach Integrations-, I n s t a l l a t i o n s - und Abnahmetest. (Wir haben diese in Bild 4-1 der Einfachheit halber zusammengefaßt.)
4.2.2.
Gestaltung Entwicklungsdokumentation
93
4.2.2. Gestaltung der Entwicklungsdokumentation Aus B i l d 4-1 geht hervor, daß die Entwicklungsdokumentation nicht ein Dokument, sondern der Oberbegriff für verschiedene Dokumente i s t . S o r g f ä l t i g e dokumentarische Arbeit darf s i c h nicht auf ihre Untergliederung beschränken, sondern muß Regeln für das Anfertigen jedes einzelnen Dokuments entwickeln und anwenden. Für die Dokumente r s t e l l u n g i s t auf ein e i n h e i t l i c h e s Aussehen a l l e r oder wenigstens gewisser Klassen von Projektunterlagen zu achten. Dokumentbeschreibende Angaben wie Verfasser, Datum, V e r t e i l e r , Bezug zu anderen Dokumenten dürfen auf keinen Fall fehlen. Im Rahmen eines Projekts sind dafür feste Vorschriften zu entwickeln (z.B. durch Projektformulare). Ein besonderes Problem i s t die Festlegung der Zugriffsrechte und der Vertei1ung: -
Wer darf welche Dokumente lesen?
-
Wer darf Festlegungen treffen? Wer darf s i e verändern?
-
Wie gewährleistet man, daß Festlegungen und ihre Veränderungen bekannt werden?
-
Wer v e r t e i l t Dokumente, wer erhält s i e und wer zieht s i e bei Ung ü l t i g k e i t wieder ein?
Diese Fragen verdeutlichen die Notwendigkeit einer systematischen Projektorganisation. Es sind auch hier genaue Festlegungen zu t r e f fen, Verantwortlichkeiten festzulegen. Wegen des Umfangs der Dokumentation und der Schwierigkeiten ihrer Organisation bietet s i c h bei größeren Projekten der Einsatz des Rechners a l s H i l f s m i t t e l an.
94
Dokumentation
4.3.
4.3. Technische Dokumentation Einblick in das System und Nachvollziehbarkeit des Entwicklungsprozesses sind nicht nur für die Entwickler selbst vonnöten, sondern auch für die Systempfleger, die in der Wartungsphase Änderungen vornehmen. Für sie ist eine bereinigte Entwicklungsdokumentation, die Technische Dokumentation, zu erstellen: Projektinterne Teile (Projekttechnikhandbuch, Projektakte) werden entfernt. Die Systementwicklungsbeschreibung wird auf das Wesentliche reduziert. Dies bedeutet nicht, daß nur die phasenabschließenden Dokumente in die Technische Dokumentation übernommen werden. Vielmehr muß auch dem Wartungspersonal übermittelt werden, welche Entwicklungsschritte einer Entscheidung vorangegangen waren. Reduktion auf das Wesentliche heißt also Entfernung nicht als für das Verständnis des Systems relevant eingestufter Zwischendokumente. In einem gesonderten Teil wird angegeben, welche technischen Resourcen (minimale Konfiguration, Betriebssystem) bereitgestellt werden müssen, damit das Programm seine Funktionen wahrnehmen kann, sowie seine Leistungsparameter (Platz- und Zeitbedarf). Diese Angaben und die Beschreibung der Programmteile, die die Maschinenschnittstelle realisieren, sind für die Installation und spätere Übertragung auf andere Maschinen besonders wichtig.
4.4.
Zusammenfassung
95
4.4. Zusammenfassung Die Dokumentation eines Softwaresystems und seiner Herstellung i s t kein Anhängsel des
"eigentlichen"
Programmierens, sondern
i n t e g r a l e r Bestandteil a l l e r Phasen des Software L i f e Cycle. Die Benutzerdokumentation beschreibt die äußeren Eigenschaften eines Systems. Für die Operateure a l s s p e z i e l l e Benutzer wird ein eigenes Dokument, das Operateur-Handbuch, e r s t e l l t . Den anderen Benutzern wird mit einem Einführungs- und einem Nachschlagehandbuch die Systembenutzung ermöglicht und e r l e i c h t e r t . Die Entwicklungsdokumentation enthält einen Management-orientierten (Verwaltungsakte), einen t e c h n i s c h - o r g a n i s a t o r i s c h e n jekttechnikhandbuch) und einen systembezogenen Teil wicklungsbeschreibung).
(Pro-
(Systement-
In der Entwicklungsdokumentation werden
n i c h t nur Resultate, sondern auch a l l e A r b e i t s s c h r i t t e
festge-
halten. Die Technische Dokumentation wird aus der Systementwicklungsbeschreibung zusammengestellt und dem Auftraggeber f ü r die Wartungsphase übergeben.
96
Entwurf
5. Entwurf
«vchl1 Erat e i n O a c h ' er d e m K o p l falls e s regnet! j
Entwurf Ich m a c h ' e i n e n K n i c k dann paßt's
rein,
Hm. e m fescher» zu fang aber b e s s e r a l s j u Würz.
N a bitte?
Ich finde, das Fenster ,st z u niedfig.
Außerdem flibt's ja Treppen*
Gerade das finde ich apart
' Û i e L o u i e "fachen u i > e r * d « m \ Haus, weii'8 s o v e r r u c k l a u s - J J AHN.
Jetzt n o c h der N a m e '
Da kann ich nichts dafür O a s F a b r i k a t taugt nichts.
Micky Maus, Heft 32/71, © Walt Disney Productions
98
Entwurf
5.
Jede zielgerichtete menschliche Tätigkeit verlangt eine Festlegung der Strategie, eine Planung. Will man mit öffentlichen Verkehrsmitteln von einem Punkt der Stadt zu einem anderen kommen, so muß man - wenn man sich noch nicht gut auskennt - die Karte studieren, geeignete Verkehrsmittel und die günstigste (voraussichtlich schnellste) Strecke auswählen und den Plan mit der Karte unter dem Arm in die Wirklichkeit umsetzen. Je besser man sich auskennt, umso größere Teile des Planes werden nicht mehr durch ausführliches Nachdenken und Abschätzen, sondern durch Routine ausgefüllt werden. Ein anderes Beispiel: ein Mensch oder eine Gruppe von Menschen bekommt folgenden Auftrag: "Bauen Sie ein 10-geschossiges Wohnhaus mit 10 Drei-Zimmer-Wohnungen (ä80m 2 ), 20 Zwei-Zimmer-Wohnungen ( ä 5 5 m 2 ) , 10 Ein-Zimmer-Wohnungen (ä 35 m 2 ). Jede Wohnung soll außer den Wohnräumen Bad, Toilette und Küche haben. Das Haus soll a l l e üblichen Service-Einrichtungen besitzen". Wir wollen einmal von der Ungenauigkeit dieser Anforderungsdefinition absehen. Jedermann würde nun diese Baugruppe für verrückt erklären - mit Recht - , würde diese beispielsweise an der Nordwestecke anfangen, ein Loch zu graben, Zement hineinzuschütten und die ersten Steine darauf zu setzen. Genau so aber wurde und wird vielfach noch programmiert. Viele möchten "keine Zeit verschwenden" und das Programm so schnell wie möglich auf die Maschine bringen. Unterstützt wird diese Haltung durch eine Managementpolitik, die den Programmierer für den besten hält, der am schnellsten ein Stück Code vorweisen kann (das obligatorische Flußdiagramm wird anschließend dem Code entlang gemalt), und die Programmiererproduktivität in Codezeilen pro Monat mißt. Nun i s t nicht erst s e i t gestern bekannt, daß dies eine dem Problem unangemessene Arbeitsweise i s t . Schon bei der Herstellung von kleinen Programmen wird mit Einfach-Drauflos-Programmieren meist das Ziel verfehlt - wir werden in Kapitel 8 kurz darauf eingehen. Umso mehr g i l t dies bei größeren Programmen. Der entscheidende Punkt i s t , daß die Komplexität des Programmierproblems im allgemeinen gewaltig unterschätzt wird. Größenunterschiede
5.
Entwurf
99
in den Programmen werden angesichts der hohen Verarbeitungsgeschwindigkeiten moderner Rechner nicht ernst genommen; er braucht eben ein paar Millisekunden länger. Aber im Gegensatz zur Fähigkeit des Rechners, elementare Operationen sehr schnell in einer Anzahl durchzuführen, die die menschlichen Fähigkeiten weit ü b e r s t e i g t , handelt es s i c h bei der Bewältigung der Problemkomplexität um eine genuin g e i s t i g e , also menschliche Aufgabe (wobei der Mensch s e l b s t v e r s t ä n d l i c h je nach F o r t s c h r i t t der Methoden in gewissem Maße vom Rechner unterstützt werden kann). Wie aber kann man nun die Problemkomplexität bewältigen? Kommen wir auf unser Baubeispiel zurück: man unterscheidet gewöhnlich gewisse Teilsysteme: das Fundament, das Mauerwerk, die Dachkonstruktion, die Wasserzu- und - a b l e i t u n g , das e l e k t r i s c h e System, die Heizung, usw.; der Hausbauplan wird a l s o in funktionale Bestandteile z e r l e g t :
B i l d 5-1:
Funktionale Zerlegung eines Hausbaus
Man wird auch den z e i t ! i c h e n Ablauf des Hausbaus planen, den wir stark vereinfacht d a r s t e l l e n :
100
Entwurf
5.
Hausbau Aushub
Wasserleitunger legen
Bild 5-2:
Funda- mentbau
Mauern aufziehen
--*
Dachstuhl aufsetzen
Strom Heizung leitungen(-» einbauen legen
— •
Verputzen
Innenwände/ Treppen einbauen
Böden legen
Innen einrichten
Zeitliche Zerlegung eines Hausbaus
Schließlich müssen auch die Resourcen geplant werden: Hausbauresourcen
Arbeitskraft
Material
Steine
Holz
Platten
Bild 5-3:
Sand
Zement
Rohre -L Ziegel
Maurer
Klempner --
Architekt
Masch inen u. Ge räte
Betonmischmasch.
Zimmerleute
Bagger
Elektriker
Gerüst -- Kräne
Heizungsmonteure
Resourcenzerlegung eines Hausbaus
Bohrer
5.1.
Ein Zerlegungsbeispiel
101
Diese drei Aspekte s i n d n a t ü r l i c h nicht unabhängig voneinander: die einzelnen funktionalen T e i l e müssen zu bestimmten Zeiten gebaut werden, die Resourcen werden zu bestimmten Zeiten für die verschiedenen T e i l e gebraucht. Ein Hausbauplan wird deshalb u.a. einen Netzplan enthalten, in dem die oben genannten drei Zerlegungen i n t e g r i e r t sind. Darauf wollen wir hier jedoch nicht weiter eingehen. Grundlegendes P r i n z i p der Bewältigung von Problemkomplexität
ist
a l s o die Zerlegung des Problems in Teil aufgaben geringerer Komplexit ä t . Dabei wird nicht die Komplexität des Problems v e r r i n g e r t ;
viel-
mehr geht es darum, Teilaufgaben so zu d e f i n i e r e n , daß jede einzelne die menschlichen Fähigkeiten zur Komplexitätsbewältigung nicht übers t e i g t , mit der Lösung a l l e r Teilaufgaben jedoch das Gesamtproblem gelöst
ist.
Versuchen wir dieses P r i n z i p auf die Softwareherstellung anzuwenden, so s t e l l e n s i c h die folgenden zentralen Fragen: nach welchen K r i t e r i e n und mit welchen Methoden s o l l e n Programmieraufgaben z e r l e g t werden? Wir wollen zwei grundsätzlich verschiedene Herangehensweisen zunächst an einem ausführlichen B e i s p i e l
verdeutlichen.
5.1. Ein Zerlegungsbeispiel Wir werden in diesem Abschnitt zunächst ohne D e f i n i t i o n Programmt e i l e a l s "Moduln" bezeichnen. Wodurch die Moduln c h a r a k t e r i s i e r t s i n d , geht aus der konkreten Beschreibung des Beispielproblems
her-
vor. Ein exakterer Modulbegriff findet s i c h im nächsten Abschnitt. Unser B e i s p i e l
i s t der zentrale Teil eines einfachen Telegrammab-
rechnungssystems 1 : Bei der Abrechnungsstelle t r e f f e n in rechnerlesbarer Form Telegramme a l s endloser Textstrom e i n , der zu bestimmten
1
Weitere B e i s p i e l e dieser A r t werden ausgeführt i n [Parnas72], [Schnupp76] (beide: KWIC-Index-System) und [Parnas78] (Adressenverarbei tungssystem).
102
Entwurf
5.1.
Zeitpunkten zum Zwecke der Abrechnung gestoppt wird. Die Abrechnung besteht darin, eine Liste anzufertigen, die für jedes Telegramm Wortzahl und Preis sowie die Gesamtzahl der Wörter aller Telegramme und den Gesamtpreis enthält. Das zu entwerfende Programm erwartet als Eingabe eine Zeichenkette der Form
(d 6 B + (c+B + ) + ST0PB + ST0PB + ) + , wobei
de{0,...,9},
6 das
Leerzeichen, c ein Element aus der Menge der druckbaren Zeichen mit Ausnahme des Leerzeichens bedeutet und das hochgestellte +
die be-
liebig häufige Wiederholung des so gekennzeichneten Ausdrucks symbolisiert. Die sechsstellige Zahl am Anfang jedes Telegramms ist als der Telegrammidentifizierungscode (TID-Code) zu interpretieren, die c-Folgen sind die Wörter des Telegramms (einschließlich STOP), die Folge ST0PB+ST0P ist das Telegrammendezeichen. Als Ausgabe soll das Programm liefern: 1.
Eine nach aufsteigendem Identifizierungscode geordnete Liste von Tripeln (TID-Code, Telegrammwortzahl, Preis des Telegramms);
2.
Das Paar (Gesamtzahl der Wörter aller Telegramme, Gesamtpreis).
Der Preis wird nach folgenden Regeln berechnet: Ein Wort kostet DM -,60. Jedes Telegramm kostet jedoch mindestens DM 4.20 . Unter einem Wort wird hier eine in Leerzeichen eingeschlossene Leerzeichen-freie Zeichenkette verstanden, die nicht länger als 12 Zeichen ist. Längere Zeichenketten zählen pro angefangene 12 Zeichen als jeweils 1 Wort mehr. Die Zeichenkette STOP wird nicht gezählt. Eingabe
635444 UEBER DAS FREIMACHEN STOP STOP 634560 IM VERKEHR INNERHALB DER BUNDESREPUBLIK DEUTSCHLAND MIT BERLINCWEST) MUESSEN SIE ALLE POSTSENDUNGEN FREIMACHEN STOP STOP 634575 BEDENKEN SIE BITTE STOP DASS DIE EMPFAENGER IHRER NICHT ODER UNZUREICHEND FREIGEMACHTEN SENDUNGEN NACHGEBUEHREN ZAHLEN MUESSEN STOP STOP 635003 IM AUSLANDSVERKEHR STOP
5.1.
Ein Zerlegungsbeispiel
103
BESTEHT STOP GRUNDSAETZLICH FREIMACHUNGSZWANG STOP FUER ALLE SENDUNGEN STOP STOP Ausgabe
TELEGRAMMABRECHNUNG TIP-CODE
WÖRTER
634560
15
9.00
634575
17
10.20
635003
11
6.60
635444
3
4.20
46
30.00
SUMME Bild 5-4:
29.2.79 PREIS
Ein-/Ausgabebeispiel einer einfachen Telegrammabrechnung
Wir werden nun zwei mögliche Zerlegungen dieses Systems angeben, eine naheliegende, die sich an der zeitlichen Folge der Verarbeitung orientiert, und eine andere, auf deren zugrunde liegende Prinzipien wir später eingehen werden. Zerlegung 1: Modul Eingabe Ein Telegramm wird vom Eingabemedium gelesen und in geeigneter Form im Arbeitsspeicher abgelegt. Modul Auswertung Sobald der Modul Eingabe seine Aufgabe beendet hat, analysiert dieser Modul das Telegramm, stellt die Wortzahl fest, berechnet den Preis und stellt TID-Code, Wortzahl und Preis im Arbeitsspeicher bereit. Modul Tabelleneintrag Nach dem Modul Auswertung tritt dieser Modul in Aktion: er trägt die zur Verfügung gestellten Daten geordnet in eine Tabelle ein und erhöht die Werte für Gesamtwortzahl und Gesamtpreis.
104
Entwurf
5.1.
Modul Ausgabe Wenn die Eingabe gestoppt i s t , g i b t dieser Modul die im Modul Tabelleneintrag geführte Tabelle im gewünschten Format aus. Modul Hauptsteuerung Dieser Modul a k t i v i e r t die einzelnen Moduln in der benötigten Reihenfolge. Er kann auch Fehlerbehandlung,
Speicherplatzzuord-
nung - f a l l s nötig - , usw. enthalten. Es handelt s i c h hier n a t ü r l i c h nicht um einen v o l l s t ä n d i g e n Entwurf, v i e l e Einzelheiten fehlen, anderes i s t ungenau f o r m u l i e r t . Für unseren Zweck, Z e r l e g u n g s k r i t e r i e n zu untersuchen, genügt das jedoch zunächst. Steuerfluß C> Datenfluß
Telegramm
Eingabe
B i l d 5-5:
gramm
V
Auswertung
daten Tabel-f) l e n eines e i n t r a g Telegramms
format. le der Tabelle > U" Ausgabe Abrechnungsdaten
Das Telegrammabrechnungssystem, a b l a u f o r i e n t i e r t (Zerlegung 1)
zerlegt
Zerlegung 2: Modul Eingabe Dieser Modul enthält Funktionen, die den TID-Code und Telegrammwörter lesen können.
5.1.
Ein Zerlegungsbeispiel
Modul
105
Telegrammdatenverwaltung
Dieser Modul enthält die Tabelle der Abrechnungsdaten und die Gesamtdaten sowie Funktionen, die das Einfügen von Abrechnungsdaten in die Tabelle, das geordnete Lesen der Abrechnungsdaten, die Aktualisierung und das Lesen der Gesamtdaten ermöglichen. Modul Auswertung Dieser Modul errechnet die Abrechnungsdaten eines Telegramms unter Benutzung der von den Moduln Eingabe und Telegrammdatenverwaltung zur Verfügung gestellten Funktionen. Modul Ausgabe Dieser Modul erstellt die formatierte Tabelle der Abrechnungsdaten, indem er die Lesefunktionen des Moduls Telegrammdatenverwaltung benutzt. Modul
Hauptsteuerung
Wie in Zerlegung 1 steuert dieser Modul den Programmablauf durch die Reihenfolge, in der er die Moduln aktiviert.
>• Steuerfluß
Bild 5-6:
Zerlegung 2 des Telegrammabrechnungssystems
106
Entwurf
5.1.
Wenn wir nun diese beiden Modularisierungen vergleichen (und annehmen, sie seien exakter formuliert), so können wir vermuten, daß beide " r i c h t i g " in dem Sinne sind, daß bei korrekter Implementierung der einzelnen Moduln ein korrektes Programm herauskommt. Möglicherweise erhalten wir sogar Programme, die bezüglich Ausführungszeit und Speicherplatzbelegung gleich gut sind. Der Unterschied l i e g t auf der Entwurfsebene, und er wird deutlich in Programmversionen, die der menschlichen Kommunikation (Verstehen, Dokumentieren, Ändern) dienen. Nach welchen Kriterien sollen Modularisierungen beurteilt werden? Das erste Kriterium i s t die Möglichkeit, die Moduln weitestgehend unabhängig voneinander schreiben zu können. Gelingt dies nicht, i s t der eigentliche Sinn der Zerlegung, nämlich die von einem Bearbeiter zu jedem Zeitpunkt zu bewältigende Komplexität möglichst gering zu machen und die Arbeit aufzuteilen, verfehlt. Sehen wir uns daraufhin die beiden Entwürfe an: In Zerlegung 1 müssen die Moduln "Auswertung", "Tabelleneintrag" und "Ausgabe" umfangreiche DatenformatInformationen vom jeweils vorangehenden Modul erhalten ( b e i s p i e l s weise müssen die Ausgabefunktionen über jedes Detail der im Modul "Tabelleneintrag" geführten Telegrammdatentabelle Bescheid wissen), in Zerlegung 2 brauchen die Moduln l e d i g l i c h Spezifikationen von Zugriffsoperationen aus einem oder zwei anderen Moduln. Eng damit zusammen hängt das zweite Kriterium: die Verständlichkeit nicht nur des Gesamtsystems, sondern auch der einzelnen Moduln. Wegen der großen Datenschnittstellen in Zerlegung 1 setzt das Verständnis eines Moduls das Verständnis von Teilen der " Z u l i e f e r " Moduln voraus: man muß nicht nur die Datenformate kennen, sondern auch wissen, was in Extremfällen im anderen Modul geschieht, etc. Eine eindeutige und verständliche Spezifikationssprache vorausgesetzt, braucht in der Zerlegung 2 kein Modul Interna eines anderen zu kennen. Das dritte Kriterium i s t das der Änderbarkeit. Eine Systementwicklung besteht aus einer Folge von Entwurfsentscheidungen und deren
5.1.
Ein Zerlegungsbeispiel
107
Realisierung. Diese Entscheidungen werden jedoch nie ein für a l l e mal getroffen, sondern müssen revidiert werden können, weil sie sich als falsch oder ungünstig herausgestellt haben
oder weil sich die
Umstände geändert haben. Was für das fertige Produkt g i l t , g i l t erst recht für die Vorstufen des Produkts: änderungsfreundlich müssen sie sein. Welche Entwurfsentscheidungen in unserem Bei spielsystem könnten v i e l l e i c h t einmal geändert werden? 1.
Das Format des Identifizierungscodes.
2.
Es wird eine sortierte Ausgabe der Abrechnungsdaten verlangt. Je nach den Möglichkeiten der Speicherung und der Anzahl der Telegramme könnte es entweder günstiger sein, die Tabelle geordnet anzulegen oder erst später zu sortieren.
3.
Die Implementierung der Telegrammdatentabelle (Baum, verkettete lineare L i s t e , Tripel von Feldern).
Vergleichen wir daraufhin die beiden Zerlegungen: Die erste Änderung b e t r i f f t in beiden Zerlegungen a l l e Moduln außer "Hauptsteuerung", da in allen Moduln auf "Identifizierungscode" als Variable oder Parameter oder Wert einer Funktion Bezug genommen wird. Der Entwurf der Zerlegung 1 geht davon aus, daß der Modul "Tabelleneintrag" die Abrechnungsdaten geordnet in die Tabelle einträgt. Wäre es nun günstiger (2. Änderung), die Tabelle zunächst ungeordnet aufzubauen und erst nach Beendigung a l l e r Einträge zu sortieren, so müßte nicht nur - was selbstverständlich nicht zu umgehen wäre - im Modul "Tabelleneintrag" eine Sortierroutine angesiedelt werden, sondern auch der Modul "Hauptsteuerung" geändert werden, weil nach der durch Eingabeende terminierten Iteration Eingabe - Auswertung - Tabelleneintrag und vor der Ausgabe noch einmal eine Funktion aus dem Modul "Tabelleneintrag" a k t i v i e r t werden müßte. In Zerlegung 2 dagegen würde es genügen, die Funktion "Lies nächstes Tripel" im Modul
108
Entwurf
5.1.
"Telegrammdatenverwaltung" so umzuschreiben, daß von i h r beim ersten Aufruf das S o r t i e r e n der Tabelle veranlaßt wird - ohne daß andere Moduln t a n g i e r t werden. Eine Änderung der Tabellenimplementierung beträfe i n Zerlegung 1 sowohl den die Tabelle erzeugenden Modul " T a b e l l e n e i n t r a g " a l s auch den Modul "Ausgabe", der die Tabelle a l s Eingabe hat. In Zerlegung 2 dagegen blieben a l l e Moduln von der im Modul
"TelegrammdatenVer-
waltung" vorzunehmenden Änderung unberührt. Auf dem Hintergrund dieses Vergleiches können wir nun endlich Schlußfolgerungen über die den verschiedenen Modularisierungsmethoden zugrundeliegenden K r i t e r i e n ziehen. Bei Zerlegung 1 haben o f f e n s i c h t l i c h die Ausführungsabschnitte des zu implementierenden Programms Pate gestanden. Sie hat den V o r t e i l , daß s i e im allgemeinen recht schnell aus der Grobalgorithmierung des Problems f o l g t , d.h. diese Zerlegung o r i e n t i e r t s i c h a u s s c h l i e ß l i c h am Steuerfluß des Programms ohne Rücksicht auf die Komplexität des Datenflusses. Der Hauptnachteil besteht in der U n ü b e r s i c h t l i c h k e i t der S c h n i t t s t e l l e n , was solche Programme und i h r e Entwürfe extrem änderungsunfreundlich macht. Zerlegung
2 dagegen l i e g t das P r i n z i p des Verbergens von Informa-
tionen ("information h i d i n g " ) zugrunde. Die Moduln werden so gewählt, daß Entwurfsentscheidungen möglichst in einem Modul werden, besonders s o l c h e , die Informationsdarstellungen
isoliert
betreffen,
weil diese i n starkem Maße änderungsanfällig s i n d . Diese Modularisierungsmethode o r i e n t i e r t s i c h a l s o an den Datenstrukturen, die j e weils zusammen mit den Z u g r i f f s o p e r a t i o n e n i n Moduln i s o l i e r t werden. Dieser i n [Parnas72] vorgeschlagene Gedanke i s t in den letzten Jahren unter dem Schlagwort "Abstrakte Datentypen" weiterentwickelt worden; wir werden darauf in den nächsten Abschnitten eingehen.
5.2.
Typen von Moduln
109
5.2. Typen von Moduln Es i s t nun an der Z e i t , s i c h dem B e g r i f f "Modul" etwas eingehender zu widmen. Schon am B e i s p i e l im vorigen Abschnitt müßte k l a r geworden s e i n , daß wir unter Moduln nicht b e l i e b i g e Programmteile v e r stehen, sondern solche, die eine unter ganz bestimmten Gesichtspunkten gebildete l o g i s c h e E i n h e i t bilden. Schauen wir ganz grob auf die beiden Zerlegungen, so können wir sagen, daß diese Moduln im a l l g e meinen aus Daten und aus Funktionen bestehen, die auf diesen Daten operieren. Diese " D e f i n i t i o n " kommt auch dem i n t u i t i v e n Verständnis eines Moduls a l s Baustein - wie man es in v i e l e n technischen B e r e i chen hat - entgegen. Insbesondere i s t damit auch jedes v o l l s t ä n d i g e Programm ein Modul. Da keine allgemein verbindlichen einschränkenden Programmiernormen e x i s t i e r e n , läßt s i c h der B e g r i f f Modul nicht genau d e f i n i e r e n . Es lassen s i c h jedoch durchaus Modul typen unterscheiden, die von den Z e r l e g u n g s k r i t e r i e n und -methoden i n d u z i e r t werden und i n Wechselwirkung mit den möglichen Beziehungen zwischen Moduln stehen. Schauen wir uns zu diesem Zweck noch einmal das B e i s p i e l in Abschnitt 5.1. an: In Zerlegung 1 besteht jeder Modul aus genau einer Hauptfunktion (und null oder mehr von dieser benutzten Unterfunktionen, was unwesentlich i s t ) sowie einer Menge von Datenstrukturen, die von dieser Hauptfunktion manipuliert werden. Der Modul
"Hauptsteuerung"
nimmt eine Sonderstellung a l s "Anstoßer" e i n und hat keine Daten. In Zerlegung 2 können wir drei Ebenen unterscheiden: Für den e i n z i gen Modul der obersten Ebene, "Hauptsteuerung", g i l t das für Z e r l e gung 1 Gesagte. Die Moduln der mittleren Ebene, "Auswertung" und "Ausgabe", haben ebenso wie die anderen Moduln der Zerlegung 1 j e weils eine Hauptfunktion, die vom Modul "Hauptsteuerung" aufgerufen wird. Diese Hauptfunktionen spiegeln in i h r e r Reihenfolge ten - ausgeben
auswer-
die algorithmische Lösung des Gesamtproblems auf
110
Entwurf
5.2.
höchster Ebene wider. Die beiden Moduln der unteren Ebene, "Eingabe" und "Telegrammdatenverwaltung", haben jedoch ganz anderen Charakter: s i e s t e l l e n Funktionen zur Verfügung, mit denen die wesentlichen Datenstrukturen des Programms gelesen bzw. beschrieben werden können, ohne daß der das Lesen bzw. Schreiben veranlassende Modul über S p e i cherungsform und - o r t der Datenstrukturen und ihren Zustand Bescheid wissen müßte. Diese Moduln r e a l i s i e r e n a l s o die D e t a i l s des Datens t r u k t u r z u g r i f f s ; welche Speicherungsform für die Tabelle der Abrechnungsdaten und welche Z u g r i f f s a l g o r i t h m e n der Modul "Telegrammdatenverwaltung" auch immer wählt, die benutzenden Moduln der m i t t leren Ebene bleiben davon unberührt. An diesem B e i s p i e l haben wir die beiden Haupttypen von Moduln kennengelernt. Daneben kann man noch e i n i g e Varianten i d e n t i f i z i e r e n , die wir im folgenden zusammen mit den Haupttypen allgemein beschreiben wollen. Die aufgezählten Typen stimmen im wesentlichen mit den von Goos/Kastens [Goos77] unterschiedenen Kategorien überein: Typ 1
Der Modul besteht aus einer Hauptfunktion, deren Ausgabe auss c h l i e ß l i c h von der Eingabe abhängt, a l s o einer Funktion im mathematischen Sinne.
Dazu gehören a l s Moduln eines Systems aufgefaßte v o l l s t ä n d i g e Programme. Dazu gehören auch Moduln wie "Hauptsteuerung" aus unseren B e i s p i e l e n , die ja gegenüber dem Benutzer das Programm " v e r t r e t e n " , (die übrigen Moduln werden dann a l s Teilmoduln von "Hauptsteuerung" aufgefaßt). Dazu gehören aber auch die Moduln der m i t t l e r e n Ebene aus Zerlegung 2, für die wiederum die Moduln der unteren Ebene a l s Teilmoduln aufgefaßt werden können. Andere wichtige B e i s p i e l e s i n d Algorithmen zur D a r s t e l l u n g mathemat i s c h e r Funktionen. Den Charakter dieses Typs nicht verändernde Variationen s i n d :
Exi-
stenz von Unterfunktionen und von Daten, die mit dem Ausführungsabschluß der Hauptfunktion verschwinden.
5.2. Typ 2
Typen von Moduln
111
Der Modul besteht aus einer Datenstruktur und einer Menge von Funktionen, die den Schreib-/Lese-Zugriff auf diese Datenstruktur realisieren.
Diesen Modultyp gibt es in einer Reihe von Ausprägungen, die teils von der Verwendung der Moduln, teils von den Möglichkeiten ihrer programmiersprachlichen Realisierungen induziert sind. Typ 2a
Der Modul dient dazu, die Realisierung wesentlicher Datenstrukturen des Programms von ihrer Benutzung zu isolieren: Abstrakte Datenstruktur.
Die Moduln "Eingabe" und "Telegrammdatenverwaltung" unseres Beispiels sind von diesem Typ. Sie stellen alle nötigen Zugriffe (und keine anderen!) auf die Tabelle für die Moduln der mittleren Ebene zur Verfügung. Moduln dieses Typs sind auch Symboltabellenmoduln in Compilern, Zeilenverwaltungsmoduln in Textformatierern u.a. Typ 2b
Der Modul stellt den Prototyp einer häufig gebrauchten Datenstruktur samt den nötigen Zugriffsfunktionen dar: Abstrakter Datentyp.
Datenstrukturen wie Keller, Schlangen, Bäume zusammen mit den Operationen Einfügen, Löschen, Lesen, Verändern, Grenzfalltests sind Moduln dieses Typs. Im Unterschied zu Moduln vom Typ 2a können in einem Programm mehrere Exemplare eines Moduls vom Typ 2b koexistieren. Typ 2c
Der Modul ist ein abstrakter Datentyp, dessen Zugriffsfunktionen durch die Datenstrukturbildung implizit gegeben sind und nicht mehr vom Modul Verfasser geschrieben werden.
Zu diesem Typ gehören die elementaren und zusammengesetzten Datentypen klassischer Programmiersprachen, z.B. integer, real, boolean, array in ALG0L60, aber auch höhere Formen wie die Record-Definition in PASCAL 2 oder ALG0L68.
112 Typ 2d
5.3.
Entwurf
Der Modul besteht aus einer Funktion, deren Ausgabe außer von der Eingabe auch von einem internen Zustand abhängig ist.
Das Besondere an diesem Typ ist also, daß die zu manipulierende Datenstruktur "nur" eine Zustandsvariable ist, deren Wert nicht konkret, sondern nur als die Ausgabe beeinflussende Größe interessant ist. Ein klassisches Beispiel dafür sind Pseudozufallszahlengeneratoren, die - ausgehend von einem einzugebenden Initialisierungswertdurch eine Folge von Aufrufen eine Folge scheinbar zufällig verteilter Zahlen liefern, deren Folge jedoch tatsächlich durch eine bei jedem Aufruf veränderte Zustandsvariable gesteuert wird. Auf dem Hintergrund dieser Modulklassifizierung gehen wir nun daran, Beziehungen zwischen Moduln zu untersuchen.
5.3. Beziehungen zwischen Moduln Liest man von einem Programmsystem, es sei "hierarchisch strukturiert" oder gar "strukturiert", so ist man genauso schlau wie zuvor. Wenn man die Struktur eines Systems angibt, so muß man natürlich auch sagen, um welche Beziehung es geht. DeRemer/Kron [DeRemer76] lassen z.B. die folgenden Beziehungen zwischen Moduln zu:
2
In PASCAL kann der Programmierer Datentypen als "records" von Variablen anderer Datentypen definieren, z.B.: type complex = record re, im : real end; Er kann sowohl Variable des neuen Typs vereinbaren (Exemplare des Moduls "complex" generieren): var x : complex; als auch auf die Komponenten zugreifen und auf diese alle für den Komponententyp (hier: real) erlaubten Operationen anwenden: x.re := 7; x.im := - 2 .
5.3.
Beziehungen zwischen Moduln
-
"Modul A enthält Teilmodul B",
-
"A darf auf Resourcen von B zugreifen",
-
"A g r e i f t aktuell auf Resourcen von B zu".
113
Und wenn wir uns das Beispiel im Abschnitt 5.1. ansehen, so können wir f e s t s t e l l e n , daß dort solche Beziehungen wie -
"Modul A l i e f e r t Daten an Modul B",
-
"A folgt auf B (im Programmablauf)",
-
"A benutzt (Daten, Funktionen von) B"
existieren. Uns interessieren jedoch nicht in erster Linie a l l e möglichen Beziehungen, die man zwischen den Moduln eines fertigen Programms f e s t stellen kann, sondern vielmehr diejenigen, die bewußt als Anhaltspunkt für die Bildung der Moduln benutzt werden. Eine geringe Komplexität der intermodularen Beziehungen i s t nämlich eine notwendige Voraussetzung dafür, das wesentliche Entwurfsziel zu erreichen, die Komplexität jedes Lösungsschritts so klein wie möglich zu halten. Maß für die Komplexität der Beziehungen sind jedoch Menge und Art der Informationen, die die Moduln voneinander haben - also potent i e l l verwenden. Andere als die "Benutzt"-Beziehung, wie die "Folgtauf"-Beziehung oder die "Enthält"-Beziehung, können zwar das Verständnis des Gesamtsystems unterstützen, haben aber keinen Einfluß auf die Beziehungskomplexität. Wir werden deshalb im folgenden, wenn nicht anders gesagt, immer die "Benutzt"-Beziehung meinen: Modul A "benutzt" Modul B genau dann, wenn Modul A in Modul B d e f i nierte Variablen lesen oder beschreiben, Konstanten lesen oder Funktionen aufrufen kann. Es gibt selbstverständlich sehr viele Möglichkeiten, Benutzt-Strukturen zu prägen. Diese reichen vom Chaos (jeder Modul benutzt jeden) bis zur t r i v i a l e n Ordnung (jeder Modul benutzt nur sich s e l b s t ) .
114
Entwurf
5.3.
Aus Zerlegung 2 des Beispiels in Abschnitt 5.1., die ja dem Entwurfsziel näher kommt als Zerlegung 1, wird deutlich, daß die Benutzt-Struktur in engem Zusammenhang mit den Abstraktionsebenen steht: Moduln benutzen nur Moduln niedererer, also näher an der Basismaschine liegender Ebenen. Die Benutzt-Struktur entpuppt sich also als Hierarchie, in diesem Falle sogar als strenge Hierarchie, d.h., Moduln benutzen nur Moduln der nächstniedrigeren Ebene (siehe Bild 5-7).
Bild 5-7:
Hierarchische Strukturen
5.3.
Beziehungen zwischen Moduln
115
Im Rückblick erkennen wir jetzt den Kern des Unterschiedes der beiden Zerlegungen: während Zerlegung 1 ausschließlich nach dem Prinzip "Zerlegung in Teilfunktionen" strukturiert i s t und somit jeder Modul sich über a l l e Abstraktionsebenen erstreckt, wurde in Zerlegung 2 zusätzlich das Prinzip "Schichtung in Abstraktionsebenen" angewendet, und die dadurch induzierte hierarchische Benutzt-Struktur i s t für die Reduzierung der intermodularen Kommunikation verantwortlich. Jedoch i s t es nicht s i n n v o l l , jedes Programm als strenge Hierarchie strukturieren zu wollen. Betrachten wir einmal die beiden folgenden Beispiele. Das erste Beispiel i s t ein Dialogprogramm zur Arbeit mit einem einfachen Datenbanksystem: Ein Benutzer soll in einem beliebigen gerichteten Graphen mit markierten Kanten und Knoten neue Knoten und Kanten einfügen, vorhandene Knoten und Kanten löschen, Knoten- und Kanteninformation lesen, löschen und verändern können. Eine mögliche Zerlegung besteht darin, Moduln für die Textimplementierung, die Knotenimplementierung, die Kantenimplementierung, die Ein-/Ausgabeverwaltung, die Knotenverwaltung, die Kantenverwaltung, die Verwaltung der Benutzerkommandos und den Benutzerdialog zu definieren. Dabei i s t klar, daß die Knotenverwaltung die Knotenimplementierung benutzt, die Kantenverwaltung die Kantenimplementierung usw. (s. Bild 5-8). Es läßt sich also eine hierarchische Struktur erkennen; dieses Modulsystem ließe sich sogar - abgesehen davon, daß eine Reihe von Moduln weggelassen wurde als strenge Hierarchie darstellen. Jedoch i s t eine hierarchische Programmstruktur nicht an sich erstrebenswert, sondern nur unter dem Aspekt der konzeptionellen Klarheit des Programmsystems. Der konzeptionellen Klarheit widerspricht jedoch in unserem Beispiel die Plazierung des Moduls "Textimplementierung", dessen Formulierung eher auf der gleichen sprachlichen Ebene anzusiedeln wäre wie Kanten- und Knotenimplementierung als darunter. Es besteht nämlich kein einleuchtender Grund, die Implementierung von Texten als elementarer anzusehen als die von Knoten und Kanten eines Graphen. Dies i s t nur
116
Entwurf
5.3.
dann der Fall, wenn die Basismaschine beispielsweise schon den Typ Text enthält (z.B. SIMULA).
Bild 5-8:
Hierarchische Benutzt-Struktur eines einfachen Datenbankdialogs
Das Verlangen nach einer "reinen" Hierarchie ist hier also in Konflikt geraten mit der Bildung von Abstraktionsebenen. In diesem Falle wäre die Hierarchie noch mit einem vertretbaren Zugeständnis beim Prinzip der Abstraktionsebenen zu retten. Aber sehen wir uns das nächste Beispiel an: einen Compiler (Bild 5-9).
Syntaktische Analyse
Semantische Analyse
Scanner
Symbolbehandlung
Codegeneri erung
Fehlererkennung
Verwaltung
Tabellen
Characterbehandlung
Ein-/ Ausgabe
Fehlermeldung
Primitive
Bild 5-9:
Analyse
Schichtenzerlegung eines Compilers
5.4.1.
Einfachheit der Schnittstellen
117
Hier benutzen zwar nach wie vor Moduln einer Ebene Moduln der nächstniedrigeren Ebene, aber auch durch allerlei Anordnungstricks - die überdies die Konzeption stören würden - läßt sich nicht kaschieren, daß auch Moduln der gleichen Ebene sich gegenseitig benutzen: beispielsweise greift die "Codegenerierung" auf die "Fehlererkennung" und auf den "Scanner" zu, "Fehlermeldung" benutzt die "Ein-/Ausgabe". Die Benutzt-Relation ist hier keine Hierarchie zwischen den Moduln mehr, sondern nur noch zwischen den Schichten als Mengen von Moduln. In jeder Schicht dagegen herrscht Chaos in dem Sinne, daß konzeptionell jeder Modul jeden Modul derselben Schicht benutzen kann [Koster76], (Allerdings nur konzeptionell, denn in einem vernünftigen System sollten die aktuellen Zugriffe doch - abhängig vom Modulinhalt - stärker eingeschränkt sein.) Die Zerlegungsprinzipien "Zerlegung in Teilfunktionen" und "Schichtung in Abstraktionsebenen" führen also zu einer hierarchischen Benutzt-Struktur, die allerdings im allgemeinen nur zwischen den Abstraktionsebenen besteht und dann auf jeder Ebene durch kontrolliertes Chaos ergänzt wird.
5.4. Kriterien der Zerlegung Das wichtigste Kriterium ist selbstverständlich das Entwurfsziel, zur Reduzierung der bei jedem Schritt zu bewältigenden Komplexität die Beziehungen zwischen den Moduln so klar, einfach und gering wie möglich zu halten. In diesem Abschnitt wollen wir die aus dieser Forderung erwachsenden Folgen erörtern und außerdem weitere Zerlegungskri terien erläutern. 5.4.1. Einfachheit der Schnittstellen Die Schnittstelle zweier Moduln ist die Menge aller Annahmen, die die Moduln übereinander machen [Parnas72]. Zu solchen Annahmen gehören: Name, Bedeutung, Typ, Repräsentation und Wirkung von Daten,
118
Entwurf
5.4.1.
Funktionen und Programmabschnitten. Welche dieser Informationen die Schnittstelle bilden, hängt in erster Linie von der gewählten Entwurfsmethode und in zweiter Linie von den sprachlichen Möglichkeiten der Entwurfs- bzw. Implementierungssprache ab. "Einfachheit der Schnittstellen" ist also ein anderer Ausdruck für "Einfachheit der intermodularen Kommunikation", ein Kriterium, das wir schon als das wichtigste erkannt haben. Die Vorteile einfacher Schnittstellen liegen auf der Hand: -
die Kommunikation zwischen den Programmierern wird vereinfacht; je weniger umfangreich die Umgebungsannahmen eines Moduls, umso leichter ist seine Korrektheit zu zeigen;
-
die Wahrscheinlichkeit, daß Änderungen in einem Modul andere betreffen, wird geringer;
-
weil sie leichter in Programmpakete einpaßbar sind, wird die Wiederverwendbarkeit solcher Moduln gefördert.
Was aber sind einfache Schnittstellen konkret und wie bekommt man sie? Im Beispiel in Abschnitt 5.1. haben wir gesehen, daß in Zerlegung 1 im wesentlichen Datenstrukturen von einem Modul einem anderen geliefert wurden, während in Zerlegung 2 stattdessen Zugriffsfunktionen von den Moduln bereitgestellt wurden. Zweifellos sind die Schnittstellen im zweiten Fall geringer, weil kein Modul Annahmen über die - leicht einer Änderung unterliegenden - Datenorganisationen anderer Moduln machen muß. Generell kann man sagen, daß das "Verbergen" von Datenstrukturen in Moduln vor dem unkontrollierten Zugriff von außen notwendige Bedingung für das Bilden einfacher Schnittstellen ist. Selbstverständlich sind damit nicht alle Probleme gelöst; so kann man die Schutzwirkung des Zwangs, auf Datenstrukturen nur über Funktionen zuzugreifen, dadurch entscheidend schwächen, daß man die Funktionen mit Parameterlisten versieht, die umfangreiche Datenstrukturinformationen vermitteln und damit die Daten doch wieder ungeschützt präsentieren. Umso wichtiger ist es, nicht nur als Regel
5.4.2.
Getrennte übersetzbarkeit
119
zu lernen, daß man Zugriffsfunktionen zu verwenden hat, sondern den eigentlichen Sinn und Zweck zu begreifen. Dieses Prinzip des Verbergens unwesentlicher Informationen findet seine beste Verwirklichung in der Bildung abstrakter Datenstrukturen bzw. -typen, die wir schon als Modultypen 2a und 2b in Abschnitt 5.2. kennengelernt haben. Ihre Schnittstellenseiten werden von den Funktionen gebildet, die das Lesen und Schreiben auf einer Datenstruktur ermöglichen, alle anderen Interna der abstrakten Datenstrukturen braucht der benutzende Modul nicht zu kennen. Wie abstrakte Datenstrukturen (-typen) in der Spezifikation, dem in der Entwurfsphase zu erstellenden Dokument, und im fertigen Programm dargestellt werden können, werden wir in den Kapiteln 6 und 7 zeigen.
5.4.2. Getrennte Übersetzbarkeit Ein klassisches Zerlegungskriterium ist das der separaten Kompilierbarkeit von Moduln (in [Goos77] "physikalische Modularität"genannt). Dieses Kriterium ist zum einen an der unabhängigen Entwickelbarkeit durch verschiedene Programmierer, zum anderen an der Austauschbarkeit von Moduln im fertigen Programm orientiert. Dies sind zwar erstrebenswerte Ziele, aber ob das Kriterium zu ihrem Erreichen beiträgt, hängt entscheidend von der benutzten Programmiersprache ab. So werden z.B. in FORTRAN-IV-Compilern von IBM alle Unterprogramme (Subroutines, Functions, Block Data) separat kompiliert und beim Laden des Programms geprüft, ob alle aufgerufenen Unterprogrammnamen auch als Namen von Unterprogrammdeklarationen vorkommen. Da jedoch keinerlei Schnittstellenbeschreibungen gefordert werden, können die Beziehungen zwischen den Unterprogrammen beliebig wild sein, ohne daß der Compiler (und meist auch ohne daß das Laufzeitsystem) dies entdeckt. Dies kann zu grotesken Ergebnissen führen (s. Bild 5-10), die angestrebten Zwecke sind jedenfalls bei weitem verfehlt. Die separate Kombilierbarkeit von FORTRAN-Unterprogrammen wird deshalb erst zum Vorteil, wenn durch auf anderen Prinzipien gegründete Modularisierungsmethoden sinnvolle Zerlegungen erzielt worden sind. Der
120
Entwurf
5.4.3.
Osloer SIMULA-Compi1er für die IBM/370 erlaubt die separate Kompilation von "externen" Klassen und Prozeduren. Jedoch können Klassen und Programme, in denen externe Klassen oder Prozeduren (durch ihre Namen) deklariert sind, nur nach diesen kompiliert werden. So sind die Ziele der getrennten Herstellbarkeit und leichten Austauschbarkeit nur eingeschränkt erreicht.
type miracle fortran
100
CALL CHANGE(5) K = 5 WRITE (6, 100) K FORMAT (13) STOP END SUBROUTINE CHANGE(I) I = 3 RETURN END
fortran miracle COMPILING: MIRACLE R; load miracle (start EXECUTION BEGINS... 3 R; Bild 5-10:
Wie die Konstante 5 zu 3 wird
Die separate übersetzbarkeit ist also ein wichtiges Kriterium, das jedoch nur in Verbindung mit in der Sprache enthaltenen Mitteln zur Schnittstellenspezifikation und deren Überprüfung sinnvoll ist. 5.4.3. Geringe Modulgröße Ein althergebrachtes Kriterium ist die Modul große (in Codezeilen oder -Seiten). Solche Vorschriften wie "nicht länger als eine Seite" orientieren sich an der Verständlichkeit des Programms; dabei wird - richtig - angenommen, daß ein menschlicher Betrachter bei Programmstücken, die eine bestimmte Länge überschreiten, die Übersicht
5.5.
Entwurfsmethodik
121
verliert. Abgesehen von der Ungenauigkeit einer solchen Vorschrift führt ein solches Kriterium aber leicht zu dem Mißverständnis, daß die Komplexität eines Programmstückes wesentlich von der Textlänge abhänge, was schon angesichts der himmelweiten Unterschiede in den Ausdrucksmöglichkeiten verschiedener Programmiersprachen absurd erscheint. Um etwas ernsthaftere Aussagen über geeignete Modul großen bemüht sich seit einiger Zeit ein neuer Informatik-Zweig, der sich "Software Science", "Software Physics", "Software Dynamics" oder auch noch anders nennt. Hier wird versucht, Zusammenhänge zwischen gewissen quantitativ meßbaren Charakteristika von Programmen, wie Anzahl aller Operatoren- und Operandenvorkommen, Anzahl aller verschiedenen Operatoren und Operanden, Anzahl verschiedener Eingabe- und Ausgabeparameter auf der einen Seite und der vom Programmierer gemachten Fehler auf der anderen Seite aufzudecken und daraus Gesetzmäßigkeiten abzuleiten, die wiederum als Anleitung zur optimalen Programmaufteilung dienen sollen [Haistead77]. Halstead hat u.a. mit verschiedenen Berechnungsmethoden optimale Modulanzahlen in Abhängigkeit von der Anzahl verschiedener Eingabe- und Ausgabeparameter gefunden [Halstead77, Kap. 12]. Die Methoden sind jedoch zur Zeit noch so wenig gesichert, daß es nicht empfehlenswert ist, die Modularisierung an solch einer Berechnung zu orientieren.
5.5. Entwurfsmethodik "Jetzt
hott
Schreiben
mal alle
Zeitig
zu",
-ihm zl^fiig
chen
folgende*
ihnen
Kaninchen,
aZi ti> mit
wan, und Pu und Fe.tike.1 setzten
hin und höhten fai
iagte.
mit eigenen
Mündeln zu.
von.:
PLAN, KLE1N-RUH ZU FANGEN
Aich Kanin-
122
5.5.
Entwurf
1. klZgmzA.no. Bemerkungen: Känga läuft idw.eM.eA als alle anderen von um, ielbit ichneller als ¿e.h. I. Allgemeinere Bemerkungen: Känga läßt Klein-Ruh nie aui den Augen, außen., wenn ile ei ¿¿ehest ¿n Ihren Beutel eingeknöpft hat. 3. Votum: wenn Min. also Klein-Ruh fangen wollen, meinen wir einen guten Anlauf haben, denn Känga läuft schneller ah, ¿ngend jemand von uns, selbit schneller ali ¿eh. (S¿ehe Nummer eini.) 4. Ein Gedanke: wenn Ruh aui Kängai Taiehe ipränge, und Ferkel ¿n dLLe Tai che hinelmpränge, würde Känga gar nicht den Unterschied merken, weil f erkel ein iehr kleine* Tier ¿it. 5. Wie Ruh. 6. Aber Känga müßte zuerst ¿n eine andere Richtung iehen, damit i¿e nicht iieht, daß Ferkel hineinspringt. 7. S¿ehe Nummer zwei. S. Ein anderer Gedanke: aber wenn Pu mit ¿hl sehr aufgelegt spräche, könnte sie vielleicht einmal woanders kirnehen. 9. Und dann könnte Ich mit Ruh 10.
fortlaufen.
Schneit.
II. Und Känga würde erst später den Unterschied
A.A. Milne: Pu der Bär, © Atrium Verlag, Zürich
bemerken.
5.5.
Entwurfsmethodik
123
Bisher haben wir uns mit der Frage beschäftigt, wie ein Softwaresystem strukturiert sein sollte; jetzt wollen wir darauf eingehen, wie es strukturiert werden sollte. Entsprechend unserer Aufteilung des Softwareentwicklungsprozesses steht am Anfang der Entwurfsphase eine Anforderungsdefinition bereit, die sowohl die zu lösende Aufgabe beschreibt als auch - neben anderen Umgebungsbedingungen - die Basismaschine, auf der die Lösung zu realisieren ist. Entwurfsmethoden sind nun in erster Linie danach zu unterscheiden, welche Informationen über das zu verfertigende Produkt, seine Umgebung und die zur Verfügung stehenden Hilfsmittel zum Ausgangspunkt gemacht werden und in welcher Reihenfolge andere Informationen benutzt und neue hinzugewonnen werden. Betrachten wir zunächst ein Beispiel "aus dem täglichen Leben": Zwei Kinder haben je einen Beutel zusammensteckbarer Bauklötze bekommen und möchten mit diesen je einen Bahnhof bauen. Das eine Kind steckt vielleicht als erstes einige Rechtecke verschiedener Größe (teils aus durchsichtigen Steinen), mehrere längere Stangen und einige u-förmige Kästen zusammen. Sodann werden Rechtecke zu Wänden, Dächern, Fenstern und Türen für das Bahnhofsgebäude, das Weichenstellerhäuschen und die Bahnsteigüberdachung zusammengefügt, Stangen zu Geleisen, Signalen, Stützsäulen und Elektromasten und die Kästen zu Bahnsteigen und Gleisbetten. Nachdem noch einige Details hinzugefügt sind, wird alles geeignet zusammengestellt und das Kind ist mit seinem Bahnhof zufrieden (s. Bild 5-lla). Das andere Kind überlegt erst einmal, was alles zu einem Bahnhof gehört: Gleiskörper, Bahnsteige, Bahnhofsgebäude, Weichenstellerhäuschen, Signale, etc.
Nach einem Überblick über diese - vielleicht
sogar schriftlich festgehaltene - Aufzählung ist das Kind zufrieden mit dem Bahnhof, den es haben wird, wenn alle Einzelteile gebaut sind. Jetzt überlegt es, was alles zu einem Bahnhofsgebäude gehört: Mauern, Dach, Fenster, Türen, Beschilderung, Wartesaal, etc.
Nach-
dem es auch mit dieser Aufgliederung zufrieden ist, geht es daran, die für Fenster, Türen, usw. notwendigen Steine zusammenzusuchen
124
5.5.
Entwurf
und zusammenzusetzen. Sodann wird das Bahnhofsgebäude konstruiert. Dieser Vorgang wiederholt sich nun für die anderen Bahnhofsbestandteile, bis der Bahnhof fertig ist (s. Bild 5-llb). Steine
Kästen
Bahnhofsgebäude
Weichenstel.haus
überdachung
Signale Bahn^^stei(
Gleise
Bahnhof ist Bauelement für Bild 5-lla:
1. Methode zum Bau eines Bahnhofs
Bahnhof
Bahnhofsgebäude
Weichenstellerhäuschen
Wände
Wände
über- Sig- Bahndchg. nale steig
Gleise . .
; I I ; /\ Ii I I I I Il I \ [I 1 1 l I
Türen
Dächer Türen
Dächer
Böden
Säulen
Recht. Recht. Recht. Recht. Recht. Recht.
Kästen Stangen
Steine Steine Steine Steine Steine Steine
Steine Steine
benötigt zum Bau Bild 5-llb:
2. Methode zum Bau eines Bahnhofs
5.5.
Entwurfsmethodik
125
Zwar sind beiden Kindern sowohl die Aufgabe - der Bahnhofsbau - als auch die Basismittel - die Bausteine - bekannt, jedoch bastelt das erste Kind zunächst allgemeinere und dann speziellere Bauelemente, von denen es vermutet, daß sie zum Bau der Bahnhofsteile gebraucht würden. Dieses Kind kann also Bauelemente für verschiedene komplexe Teile verwenden, andererseits kann es aber auch vorkommen, daß Teile gebaut werden, die dann überhaupt nicht gebraucht werden. Das zweite Kind dagegen zerlegt den Bahnhof zunächst einmal in seine Hauptbestandteile, diese (voneinander unabhängigen) Teile in elementarere, bis es auf der Ebene der Steine ankommt. Dadurch werden genau diejenigen Teile gebaut, die den gewünschten Bahnhof konstituieren, jedoch müssen für jedes Gebäude von neuem Türen konstruiert werden, für Überdachung, Signale und Gleise jeweils Säulen. Obwohl diese Vorgehensweisen nicht mechanisch auf die Softwareproduktion übertragen werden können, so können sie doch gewisse Aspekte der in der Programmierung verwendeten Entwurfsmethoden veranschaulichen: Zu Beginn der Entwurfsphase findet der Entwerfer (im Idealfalle) folgende Informationen vor: auf der einen Seite die Benutzerschnittstelle, d.h. Inhalt und Form der Funktionen des Gesamtsystems, auf der anderen Seite Hardware, System- und Hilfssoftware der für das Softwarepaket zur Verfügung stehenden Rechenanlage, die sogenannte Basismaschine. Je nachdem, ob man nun von der Benutzerschnittstelle ausgehend auf die Basismaschine zu entwirft oder umgekehrt, spricht man von der Top-down-Methode (oder Outside-in-Methode) oder von der Bottom-up-Methode (oder Inside-out-Methode).
126
5.5.1.
Entwurf
Bild 5-12:
Top-down/Bottom-up bzw. Outside-in/Inside-out
5.5.1. Die Top-down- oder Outside-in-Methode We/i obzn
i>ltzt,
am we_nlgit&n
bi&ht
niemals
un FaZZ du
alZeA, Falles.
G. Branstner: Der Esel als Amtmann Wie schon gesagt, geht man bei dieser Methode von der (den) Hauptfunktion(en) des Gesamtsystems, dem "top" aus und entwickelt daraus durch geeignete Zerlegung und Konkretisierung ("down") das auf der Basismaschine funktionierende Produkt. Dieses Vorgehen hat den wichtigen Vorteil, daß man sicher sein kann (wenn man keine Spezifikationsfehler macht), genau das gewünschte System und nicht ein "ähnliches" zu entwerfen. Die Schritte beim Top-down-Entwurf sind gekennzeichnet durch eine Folge von Entwurfsentscheidungen. In jedem Schritt des Entwurfsprozesses wird darüber entschieden, auf welche Weise die im vorherigen Schritt benutzten Funktionen durch primitivere, d.h. der Basismaschine näher liegende Funktionen realisiert werden sollen. Der letzte Entwurfsschritt schließlich ist der, in dem das System in Funktionen der Basismaschine dargestellt ist.
5.5.1.
Top-down-Methode
127
Die Anwendung der Top-down-Methode setzt u.a. voraus, daß das gewünschte System vollständig beschrieben ist. Obwohl es ein einleuchtendes Ingenieurprinzip ist, Systeme erst dann zu entwerfen, wenn sie vollständig spezifiziert sind ("Immer wenn man mit der Konstruktion eines Objektes beginnt, von dem man nur eine verschwommene Vorstellung hat, bekommt man auch ein verschwommenes Objekt" [Parnas75a, S. 408]), liegen doch gerade in dieser Voraussetzung die Hauptschwierigkeiten der Methode. Wir denken dabei gar nicht in erster Linie an die subjektiven Probleme, eine den oben ausgeführten Forderungen nach Verständlichkeit und Exaktheit entsprechende Anforderungsdefinition zu verfassen, sondern vielmehr an objektive Probleme: wie soll die Anforderung exakt gefaßt werden, daß ein System "allgemein verwendbar" sein soll? Meist besteht auch die Aufgabe, Programme von vornherein so flexibel zu entwerfen, daß sie an zukünftig entstehende und zur Zeit der Problemanalyse noch nicht bekannte Anforderungen leicht angepaßt werden können. Darüberhinaus gibt es andere Nachteile der Top-down-Entwurfsmethode (nach [Parnas75a]): -
Mit der Basismaschine im Kopf neigt der Entwerfer dazu, in seine obersten Entwurfsentscheidungen Kenntnisse über die Basismaschine einfließen zu lassen, was zur frühzeitigen (unnötigen) Einengung der Lösungsmöglichkeit führt. Dieser Effekt wird noch verstärkt dadurch, daß in oberen Entwurfsschritten oft keine anderen Entscheidungskriterien zur Hand sind als eben die Kenntnisse über die Basismaschine. Ein Beispiel: Beim Entwurf einer Benutzerkommandosprache für ein Dialogsystem steht die Entscheidung an, ob der Benutzer lange und aussagekräftige oder kurze und Schreibaufwand sparende Kommandonamen zur Verfügung gestellt bekommen soll. Statt nun das Benutzerbild zu Rate zu ziehen (viele sporadische oder wenige ständige Benutzer), könnte der Entwerfer dazu verleitet werden, von seiner Kenntnis über die gewählte Implementierungssprache auszugehen: Ist es SIMULA, dann lange Kommandos, ist es FORTRAN, dann kurze Kommandos, weil die Textverarbeitung in FORTRAN natürlich sehr mühevoll ist.
128 -
Entwurf
5.5.2.
Der Top-down-Entwurf v e r l e i t e t dazu, Probleme vor s i c h her zu schieben, i n dem Sinne, daß auf höherer Ebene "bequeme" R e a l i s i e rungen gewählt werden, die i h r e r s e i t s aber schlecht zu r e a l i s i e ren sind.
Wir haben die Nachteile des Top-down-Entwurfs nicht z u l e t z t deshalb so a u s f ü h r l i c h dargelegt, weil häufig die bewährte Methode der schrittweisen Verfeinerung aus der Programmierung im Kleinen (siehe Kapitel 8) mechanisch auf die Programmierung im Großen übertragen wird.
5.5.2. Die Bottom-up- oder Inside-out-Methode Bei dieser Methode geht man von einer Menge von Grundfunktionen aus, die man vermutlich für die R e a l i s i e r u n g des Programms benötigen wird und die auf der zur Verfügung stehenden Basismaschine
("bottom")
r e a l i s i e r t werden können. Sodann s c h r e i t e t man durch f o r t g e s e t z t e A b s t r a k t i o n der Anforderungsdefinition entgegen ( " u p " ) . Diese Vorgehensweise hat den V o r t e i l , daß man ein wesentlich f l e x i b l e r e s S y stem entwerfen kann a l s beim Top-down-Verfahren, weil die unteren Funktionen ein b r e i t e r e s Leistungsspektrum zur Verfügung s t e l l e n a l s es f ü r die an "Top" stehende Systemfunktion nötig i s t . Dies i s t wicht i g , wenn die genauen Anforderungen an das System e r s t im Laufe der Systementwicklung k l a r werden oder wenn eine aufgabenähnliche Famil i e von Programmsystemen entworfen werden muß. Daß der Entwurf auf eine etwas im Nebel stehende Aufgabenstellung z i e l t , i s t aber zugleich auch der entscheidende Nachteil der Bottomup-Methode. Denn dadurch entsteht die Gefahr, daß man am Ziel
vor-
beischießt und dies e r s t i n einem r e l a t i v späten Stadium des Entwurfs bemerkt. Dies meint n a t ü r l i c h n i c h t , daß wir beim Bottom-upEntwurf eines Compilers am Ende merken, ein Datenbanksystem entworfen zu haben, aber es kann s i c h h e r a u s s t e l l e n , daß zur R e a l i s i e r u n g wichtiger Systemfunktionen nicht die geeigneten unteren Schichten zur Verfügung g e s t e l l t wurden. Eine aufwendige E n t w u r f s r e v i s i o n wird notwendig.
5.5.3.
Praktikable Methoden
129
5.5.3. Vorschläge für praktikable Entwurfsmethoden Die Aufzählung der Vor- und Nachteile der Top-down- und der Bottomup-Methode für den Entwurf großer Programme hat schon deutlich gemacht, daß beide zumindest in ihrer Reinform nicht der Weisheit letzter Schluß sind. In manchen Artikeln wird deshalb der Ratschlag gegeben, in einem Projekt abwechselnd top-down und bottom-up vorzugehen ("Jo-Jo-Verfahren"), doch stellt dies sicherlich keine wirkliche Anleitung zum Handeln dar. Es wäre auch sicherlich verfehlt, nach der Entwurfsmethode, die für alle Softwareprojekte gleichermaßen gültig wäre, zu suchen. Vielmehr muß die Entwurfsmethodik orientiert werden an -
Art und Umfang des Problems,
-
Bekanntheitsgrad des Problems,
-
Qualifikation und Zahl der Programmierer,
-
den zur Verfügung stehenden Hilfsmitteln.
In der Literatur sind schon viele Ansätze beschrieben worden, von denen wir einige wesentliche herausgreifen wollen. Einige methodische Ansätze, wie sie sich z.B. in den Arbeiten von Parnas finden, orientieren sich vernünftigerweise an den wesentlichen Zerlegungszielen, wie wir sie in Abschnitt 5.4. beschrieben haben. Es sei jedoch ausdrücklich davor gewarnt, Programmstruktur und Entwurfsmethoden zu identifizieren. Genausowenig, wie ein mathematischer Beweis so entworfen wird, wie er hinterher dasteht ("wähle e = 7 X g - f P " ) , ist ein hierarchischer Programmaufbau durch einen reinen Top-down-Entwurf erzielt worden. Parnas [Parnas78] schlägt vier Anhaltspunkte vor, die den Programmierer beim Entwurf leiten sollen:
130 1.
5.5.3.
Entwurf
Identifizierung von Teilsystemen. Das Softwarequal itätsmerkmal "Flexibilität" muß so früh wie irgend möglich die Entwurfsentscheidungen beeinflussen. Deshalb scheint es sinnvoll, zunächst sich das kleinste Teilsystem zu überlegen, das noch etwas sinnvolles leisten kann, sowie eine Folge minimaler Erweiterungen dieses Minimalsystems, deren Endglied das aktuell
gewünschte
Programmsystem ist. Dadurch wird erreicht, daß später gewünschte Änderungen keine unverhältnismäßig großen Entwurfsrevisionen erfordern. 2.
Schnittstellen- und Moduldefinition nach dem Prinzip des Verbergens überflüssiger Informationen. Die gute Änderbarkeit, die leichtere Programmiererkommunikation und die verbesserte Verifikationsmöglichkeit sind die Hauptvorteile dieses Vorgehens, wie wir sie ausführlich in Abschnitt 5.4.1. dargelegt haben.
3.
Die Bildung abstrakter Maschinen. Dieses eng mit dem Prinzip des Verbergens überflüssiger Informationen zusammenhängende Konzept ist die logische Fortsetzung der Aufgabe von Systemsoftware in die Anwendungssoftware: ein Anwendungsprogrammierer wird ja schon lange nicht mehr gezwungen, bis auf den Maschinencode oder gar die nackte Hardware herunter zu programmieren, auch die Verwendung von Assemblersprachen wird zugunsten des Einsatzes höherer Programmiersprachen zurückgedrängt. Dadurch muß der Programmierer eine Multiplikation zweier Zahlen nicht mehr durch kompliziertes Hin- und Herladen von Register- und Speicherinhalten ausdrücken, sondern benutzt die von der Sprache zur Verfügung gestellte Multiplikationsoperation. Die Fortsetzung dieses Konzepts in die zu erstellende Software heißt nun z.B., auch kompliziertere und damit speziellere Operationen zwischen elementaren (von der Sprache bereitgestellten) und auch komplexen Daten so zu "verpacken", daß diese Operationen benutzt werden können, ohne über ihre Implementierung informiert zu sein. Zu diesen abstrakten Maschinen gehören auch die schon mehrfach erwähnten abstrakten Datentypen.
5.5.3. 4.
Praktikable Methoden
131
Entwurf der Benutzt-Struktur. Die Bedeutung dieser Struktur haben wir schon in Abschnitt 5.3. erläutert. Wie dort schon begründet, sollte diese Struktur eine Hierarchie auf das Programm prägen.
Es wäre etwas hochgegriffen, diese vier Konzepte als eine Entwurfsmethode zu bezeichnen, jedoch bieten sie zweifellos wichtige Anhaltspunkte, die der Programmierer beim Entwerfen und bei der kritischen Betrachtung seiner Arbeit heranziehen sollte. Im Gegensatz dazu hat Jackson den Anspruch, mit der Michael-JacksonEntwurfsmethode [Jackson75] etwas entwickelt zu haben, was die Bezeichnung "Methode" verdient. Er selbst stellt an eine "solide Methode" die Ansprüche, daß sie nicht auf die Intuition des Entwerfers bauen muß, daß sie auf durchdachten Prinzipien beruht, daß sie lehrbar und in der Anwendung unabhängig vom Anwender ist, daß sie einfach und leicht zu verstehen ist [Jackson76]. Die Grundidee der MJ-Methode besteht darin, die Ein- und Ausgabedatenstrukturen zum Ausgangspunkt des Entwurfs zu machen und aus diesen durch einfache Transformationen die Programmstruktur zu ermitteln. Diese Idee basiert zum einen auf der Tatsache, daß zwischen den einfachen Datenund den Algorithmenstrukturierungsmitteln - Sequenz, Iteration, Auswahl - e i n e eindeutige Korrespondenz besteht, zum anderen auf der These, daßein sinnvoller Algorithmus in seinen Teilen die Struktur der Ein- und Ausgabedaten in jeder Einzelheit nachvollzieht. Jacksons für Daten- und Algorithmenstrukturen einheitliche Darstellung ist in Bild 5-13 wiedergegeben. Das folgende sehr einfache Beispiel ist [Jackson76] entnommen: Aus einer Datei, die Zugänge und Abgänge von Posten in einem Warenlager enthält, soll eine Liste der Nettobewegungen erstellt werden. Die Eingabe und die Ausgabe haben dabei die in Bild 5-14 dargestellte Struktur.
132
5.5.3.
Entwurf Wiederholung
Sequenz
C
R
B
0 0
Auswahl
B°
C°
D°
Bild 5-13:
Daten-/Algorithmenstrukturdarstellungen in der MJ-Methode
Bild 5-14:
E/A-Strukturen eines Postenbewegungsprogramms
Zunächst müssen die Korrespondenzen zwischen Komponenten der Eingabe- und der Ausgabedatenstruktur festgestellt werden. In unserem Beispiel entsprechen sich erstens Eingabedatei und Ausgabeliste und zweitens Postengruppe und Nettobewegungszeile. Jetzt kann die mit beiden Datenstrukturen verträgliche Programmstruktur gewonnen werden (Bild 5-15).
5.5.3.
B i l d 5-15:
Praktikable Methoden
133
Programmstruktur auf der Grundlage der in B i l d 5-14 d a r g e s t e l l t e n Datenstrukturen
Auch bei r e l a t i v primitiven Problemen i s t jedoch die Beziehung zwischen Eingabe- und Ausgabedatenstruktur nicht immer so einfach. Schon ein Programm, das eine zeilenweise eingegebene Matrix s p a l t e n weise a u s g i b t , hat mit einem " S t r u k t u r k o n f l i k t " zu kämpfen. Das Grundrezept der MJ-Methode zur Lösung dieser K o n f l i k t e besteht dar i n , zwei Programme (Programmteile) zu entwerfen, von denen das e r ste die M a t r i x z e i l e n i n eine Menge von Matrixelementen "zerhackt"
134
5.5.3.
Entwurf
und das zweite diese Matrixelemente zu Matrixspalten zusammenfügt. Ein zweites wesentliches Problem tritt dann auf, wenn Ausnahmebehandlungen bestimmter Ereignisse notwendig sind, deren Auftreten jedoch erst im Laufe der (normalen) Abarbeitung einer Datenstruktur festgestellt werden kann. Typisches Beispiel ist die Verarbeitung von Listeneinträgen, die eine Ausnahmebehandlung für den Fall erfordert, daß ein bestimmter Eintrag nicht vorhanden ist. Die MJ-Methode verlangt jedoch, die Liste als Auswahl aus "Liste mit dem gesuchten Element" und "Liste ohne das gesuchte Element" darzustellen (Bild 5-16).
4 Ges. Element * Eintrag
Ges. Element * Eintrag
Bild 5-16 Jackson empfiehlt, zunächst so zu tun, als ob die Entscheidung zwischen diesen beiden Listen tatsächlich schon beim Einstieg in die Liste getroffen werden könne, und diese falsche Annahme erst später bei der Verfeinerung des Entwurfs bzw. bei der Implementierung zu reparieren: Zunächst wird der erste Ast der Alternative (Liste mit gesuchtem Element) bearbeitet, und nach der Feststellung, daß das
5.5.3.
Praktikable Methoden
135
gesuchte Element nicht vorhanden ist, wird zum zweiten Ast verzweigt. Die MJ-Methode hat innerhalb recht kurzer Zeit Freunde in der Industrie gefunden. Wir müssen jedoch einige grundsätzliche Bedenken vorbringen: -
Jackson bezieht sich in allen seinen Ausführungen und Beispielen auf Programme, die große Datenmengen von der Eingabe zur Ausgabe schaufeln und eine wenig komplizierte Programmlogik haben, insbesondere keine komplizierten internen Datenstrukturen, also typisch kommerzielle Programme. Nur bei solchen Programmen kann überhaupt die Programmlogik so eng an die E/A-Strukturen angelehnt werden;
-
Die das ganze Programm bestimmende Widerspiegelung der speziellen E/A-Strukturen macht das Programm gegenüber Strukturänderungen extrem anpassungsunfähig. Das Grundkonzept, das Programm jedem Zweig einer konkreten Datenstruktur nachzubauen, steht dem Konzept der Datenabstraktion - Unabhängigkeit der Programmlogik von der konkreten Datenstruktur - diametral gegenüber.
Daß in diesem Abschnitt die Darlegung einer rundum positiv zu bewertenden Entwurfsmethode fehlt, ist zum einen Ausdruck des niedrigen Entwicklungsstandes der Softwaretechnik auf dem Gebiet des Entwurfs, zum anderen hat dies aber auch seinen Grund darin, daß die kreative Tätigkeit des Entwerfens nicht durch einen Satz von Merkregeln mechanisiert werden kann.
136
Entwurf
b.b.
5.6. Zusammenfassung Der Kern der Programmierertätigkeit besteht in der geeigneten Zerlegung eines mit EDV-Mitteln zu lösenden Problems mit dem Ziel der Bewältigung der Problemkomplexität. Das Produkt der Zerlegung ist ein System von Einheiten, die aus Daten und auf diesen operierenden Funktionen bestehen, die Moduln. Die wichtigste Beziehung zwischen Moduln ist die BenutztBeziehung, die im allgemeinen eine Hierarchie bilden sollte. Das wichtigste Kriterium der Zerlegung ist die Einfachheit der Schnittstellen, die durch das Verbergen überflüssiger Informationen, insbesondere in abstrakten Datentypen, erzielt wird. Daneben können auch die getrennte übersetzbarkeit und die Modulgröße Kriterien der Zerlegung sein. Weder das Top-down- noch das Bottom-up-Verfahren sind geeignet, neuartige Probleme zu lösen. Der Entwerfer muß sich an den Hauptkriterien der Zerlegung orientieren.
6.
Spezifikation
137
6. Spezifikation Spaz-i^lzleAzn
guht
übeA
CodieAm.
Alte Programmiererweisheit Wir erinnern uns, daß es Ziel des Entwurfs i s t , eine eindeutige, v o l l s t ä n d i g e und i n der Komplexität bewältigbare Systemspezifikation herzustellen: -
Während in der Anforderungsdefinition die Aufgabe beschrieben wird (was nicht a u s s c h l i e ß t , daß V o r s c h r i f t e n über zu benutzende Lösungswege enthalten s i n d ) , i s t die S p e z i f i k a t i o n eine Lösung der Aufgabe, die a l l e r d i n g s noch einer Reihe von D e t a i l l i e r u n g e n bedarf, ehe s i e "ausführbar" i s t , d . h . , auf der Basismaschine aufsitzt.
-
Sie d e f i n i e r t Moduln und deren gegenseitige Beziehungen, b i l d e t a l s o die V o r s c h r i f t für die Implementierung. S i e steuert damit die Kommunikation der Programmierer über das Programm, weil
sie
für jeden Modul a l l e notwendigen Informationen enthält. S i e b i l det den Bezugspunkt dafür, die Korrektheit der Implementierung zu zeigen, das i s t der Grund dafür, daß manche S p e z i f i k a t i o n s h i l f s m i t t e l auch für die V e r i f i k a t i o n verwendet werden. Wir wollen uns in diesem Abschnitt mit den Methoden und sprachlichen M i t t e l n beschäftigen, die für das S p e z i f i z i e r e n entwickelt worden s i n d . Entsprechend den sehr weit auseinanderklaffenden Aufgabenbereichen der einzelnen Methoden - große/kleine Programme, System-/Anwenderprogramme, Programmiersprachendefinitionen, Dialogsysteme unterscheiden s i c h die Methoden (Sprachen) bezüglich Schwerpunktsetzung, Exaktheit, V e r s t ä n d l i c h k e i t , P r a k t i k a b i l i t ä t
voneinander.
Keine der unten besprochenen oder erwähnten Spezifikationsmethoden oder -sprachen i s t i n jeder Beziehung den anderen vorzuziehen. Man
138
Spezifikation
6.
kann sogar sagen, daß es bisher keinen befriedigenden Vorschlag g i b t , der die grundlegenden Forderungen an eine Spezifikationsmethode, nämlich hinreichend exakt und g l e i c h z e i t i g f ü r große Programme operationabel zu s e i n , e r f ü l l t . Wir räumen diesen Methoden dennoch so breiten Raum e i n , weil die S p e z i f i k a t i o n eines der zentralen Probleme, wenn nicht das zentrale Problem überhaupt in der i n d u s t r i e l l e n Softwareherstellung
ist.
Aus den oben genannten Aufgaben von S p e z i f i k a t i o n e n l e i t e n wir die folgenden grundsätzlichen Qualitätsmerkmale ab ( v g l . auch [ L i s k o v 7 5 ] ) : a)
V o l l s t ä n d i g k e i t und W i d e r s p r u c h s f r e i h e i t ; Die S p e z i f i k a t i o n muß a l l e geforderten Funktionen des Programms beschreiben, die T e i l e müssen exakt aufeinander abgestimmt s e i n .
b)
Minimalität (Allgemeinheit): Die S p e z i f i k a t i o n s o l l a l l e zur Lösung des Problems notwendigen Entwurfsentscheidungen enthalten und keine weiteren. Jede i n die S p e z i f i k a t i o n aufgenommene Implementierungsentscheidung schränkt die Menge der Lösungsmögl i c h k e i t e n auf i r r a t i o n a l e , d . h . , nicht von Problem, Basismaschine oder Umgebungsbedingungen gegebene Weise ein (Oberspezifi kation).
c)
V e r s t ä n d l i c h k e i t ( f ü r Programmierer): Da es die Aufgabe von Programmierern i s t , die S p e z i f i k a t i o n in Implementierungen umzusetzen, müssen s i e in der Lage s e i n , die S p e z i f i k a t i o n zu v e r stehen.
Aus diesen Qualitätsmerkmalen von S p e z i f i k a t i o n e n ergeben s i c h nun wiederum Anforderungen an die Spezifikationsmethode: A)
F o r m a l i s i e r t h e i t : Die Methode s o l l es zum einen ermöglichen, S p e z i f i k a t i o n e n auf V o l l s t ä n d i g k e i t und Widerspruchsfrei h e i t zu prüfen, zum anderen, Implementierungen gegenüber d i e s e r S p e z i f i kation zu v e r i f i z i e r e n .
B)
Implementierungsunabhängigkeit: Die Methode s o l l t i o n vermeiden helfen.
Überspezifika-
HIPO-Diagramme
6.1.
C)
139
K o n s t r u i e r b a r k e i t und R e z i p i e r b a r k e i t : Voraussetzung dafür, daß S p e z i f i k a t i o n e n Uberhaupt verfaßt werden können, i s t es, daß es f ü r einen Programmierer mit vertretbarem Aufwand erlernbar
ist,
Entwurfsideen mit der Spezifikationsmethode zu beschreiben. Das Pendant dazu b i l d e t die F ä h i g k e i t , mit dieser Methode verfaßte S p e z i f i k a t i o n e n p r i n z i p i e l l zu verstehen. D)
Änderbarkeit: Geringe Veränderungen in der Anforderungsdefinit i o n s o l l t e n nur einen geringen Aufwand bei den entsprechenden Änderungsmaßnahmen in der S p e z i f i k a t i o n erfordern.
Die im folgenden betrachteten Methoden werden wir an diesen K r i t e r i e n messen. Flußdiagramme, Struktogramme und ähnliche Programmabiauf-Beschreibungsmittel werden zwar häufig a l s S p e z i f i k a t i o n s h i l f s m i t t e l
be-
zeichnet. Sie gehören jedoch zu den H i l f s m i t t e l n der Programmierung im Kleinen (Kapitel 8) und werden deshalb dort behandelt.
6.1. HIPO-Diagramme IBM nimmt für ihre Hausentwicklung HIPO (Hierarchy plus Jnput-Process-Output) in Anspruch, ein Beschreibungsmittel zur Verfügung zu s t e l l e n , das den gesamten Softwareentwicklungsprozeß von der Problemanalyse b i s zur Wartung begleiten kann [IBM74]. Wir werden uns, entsprechend dem Thema dieses K a p i t e l s , darauf beschränken, zu untersuchen, wie brauchbar HIPOs in der Entwurfsphase, a l s o a l s S p e z i fikationsmittel,
sind.
Die HIPO-Darstellung eines Programms besteht im wesentlichen aus zwei Komponenten: der "Visual Table of Contents" (VTOC), einem Baum, der die Zerlegung des Programms in Teilfunktionen d a r s t e l l t , und einem Eingabe-Verarbeitung-Ausgabe-Schema für jede T e i l f u n k t i o n 1 [Stay76] ( s . B i l d 6 - 1 ) . In einem E-V-A-Schema werden die Ausgabedaten einer Funktion mit den notwendigen Eingabedaten über die wes e n t l i c h e n V e r a r b e i t u n g s s c h r i t t e der Funktion verknüpft.
140
6.1.
Spezifikation
Betrachten wir als Beispiel eine HIPO-Darstellung des Telegrammanalysebeispiels aus Abschnitt 5.1.
Man erwartet vielleicht, daß wir
die von uns als besser erkannte Zerlegung 2 darstellen; die HIP0Konzeption läßt dies jedoch nicht zu, wie wir gleich sehen werden.
Bild 6-la:
VTOC (Visual Table of Contents) des Telegrammabrechnungsprogramms (Ausschnitt)
Mit den beiden Elementen der HIPO-Technik kann eine Programmstruktur klar und übersichtlich - sauber nach Abstraktionsebenen getrennt - dargestellt werden. Wegen der vorgeschriebenen baumartigen Zerlegung scheinen HIPOs insbesondere für den Top-down-Entwurf geeignete Darstellungen zu sein. Einige Mängel sind jedoch nicht zu übersehen:
1
In [IBM74] wird von drei Komponenten gesprochen: außer dem VTOC gibt es "Überblicksdiagramme" und "Detaildiagramme", die aber beide E-V-A-Schemata sind und sich lediglich darin unterscheiden, wie detailliert die Datenflüsse ausgeführt sind.
6.1. -
HIPO-Diagramme
141
Wichtige Zwischendaten werden entweder bei den Eingabe- oder bei den Ausgabedaten angesiedelt, ihre Stellung als Zwischendaten durch Datenflußpfeile ausgedrückt, die zur Eingabe hin bzw. von der Ausgabe weg führen - also der konzeptionellen Datenflußrichtung genau entgegengesetzt (s. Bild 6-lb,
-
0 , 2 ) .
Im Gegensatz zur Verfeinerungsmöglichkeit bei den Funktionen existiert kein Datenverfeinerungsmechanismus: die Datenstrukturen können also beliebig über- oder unterspezifiziert sein.
-
Für die Zwecke der Programmierung im Großen wäre die Einschränkung der Strukturierungsmittel auf Sequenz und Verfeinerung durchaus angemessen. Die in der HIPO-Methode angelegte Verfeinerung in sehr niedrige algorithmische Stufen läßt jedoch bald das Bedürfnis nach reicheren Strukturierungsmitteln entstehen, so im Beispiel, Punkt 2.2 - Wörter zählen - , wo die Verarbeitungsschritte eigentlich der Körper einer Iteration sein müßten.
-
Es gibt keine Möglichkeit, Ausnahme- und Fehlerbedingungen bzw. -behandlungen zu spezifizieren. In unserem Beispiel wird das besonders im Punkt 2.1 - TID-Code prüfen - deutlich.
-
Zur Behebung all dieser Mängel wird dem Programmierer die "Erweiterte Beschreibung" angeboten: er kann unter jedes HIPO-Diagramm einen Kasten zeichnen, in den er alles das in natürlicher Sprache hineinschreibt, was in der HIPO-Darstellung nicht unterzubringen ist, also Datenstrukturbeschreibungen,
Fehlerbedingun-
gen/-behandlung, Bedingungen für Algorithmenausführungen, Implementierungsvorschriften, usw.
Diese Möglichkeit bringt HIPOs in
die Nähe von formlosen Aufsätzen. Außer dieser Kritik an Einzelheiten der Beschreibungstechnik müssen wir jedoch auch grundsätzliche Bedenken vorbringen: Die HIPO-Methode schreibt die Zerlegung eines Programms in diejenigen Teilfunktionen vor, die in der Ausführung aufeinander folgen. Daten und Funktionen werden gleichberechtigt nebeneinander gestellt, die einen sind jeweils "zwischen" den anderen. Dadurch wird keine Zerlegung in Moduln
142 0
Spezifikation
6.1.
Telegrammabrechnung Eingabe
Verarbeitung
Telegrammstrom
Telegrammdatentabelle Gesamtdatenfeld 2
1 Eingabe aufbereiten
m
2 Telegramm auswerten 3
Eingabe
2.1
Eingabe
2.2
Abrechnungsliste
* Liste ausgeben
Verarbeitung 1 TID-Code prüfen 2
Wörter zählen
3
Preis berechnen
TID-Code prüfen
Telegrammzwischenspeicher
Telegrammzwischenspeicher
Telegrammdaten abspeichern
Telegramm auswerten Telegrammzwischenspeicher
Ausgabe
Verarbeitung 1
Erstes Wort lesen
2
Auf numerisch prüfen
3
TID-Code abspeichern
S
Ausgabe TID-Code-Feld Wortzähler Preisfeld
Ausgabe
TID-Code-Feld
Wörter zählen Eingabe
Verarbeitung
Telegrammzwischenspeicher
1 Wort lesen 2
STOP ausscheiden
3
Zeichen zählen Wortzähler verändern
Ausgabe
Wortzähler
6.2. 2.3
I n h a l t s v e r z e i chni sse
143
Preis berechnen Eingabe
Verarbeitung
Wortzähler
1 Wortzahl lesen
P r e i s pro Wort/Mindestprei s
2 Preis nach Vorschrift biIden 3
Bild 6-lb:
Preis abspeichern
Ausqabe
Preisfeld
HIPO-Diagramme f ü r Tei1funktionen des Telegrammabrechnungsprogramms
a l s E i n h e i t von Datenstrukturen und zugehörigen Funktionen e r r e i c h t . Wesentliche Entwurfsziele wie Änderbarkeit und Erweiterbarkeit werden v e r f e h l t , weil die S c h n i t t s t e l l e n zwischen den Programmteilen zwangsläufig sehr groß s i n d , Datenstrukturen gegenüber unberechtigtem Z u g r i f f und Z u g r i f f e gegenüber Datenstrukturänderungen n i c h t geschützt sind. Aus diesen Gründen s i n d HIPOs a l s S p e z i f i k a t i o n s m i t t e l
nicht g e e i g -
net.
6.2. Informelle Inhaltsverzeichnisse Die meisten halbformalen und formalen Methoden sind f ü r v i e l e Programmierer beim heutigen Ausbildungsstand schwer v e r s t ä n d l i c h .
Des-
halb wird heute häufig noch ein Verfahren zur S p e z i f i k a t i o n von Modulfunktionen und - S c h n i t t s t e l l e n benutzt, das darin besteht, einen Katalog von Angaben über einen Modul in n a t ü r l i c h e r Sprache aufzuschreiben ( s . z.B. [ V o i g t 7 5 ] ) . Es handelt s i c h dabei
selbstverständ-
l i c h nicht um ein e i n h e i t l i c h e s Verfahren, vielmehr werden i n jedem Programmierprojekt eigene Pvichtlinien dafür a u f g e s t e l l t , was in d i e ser Modulspezifikation zu stehen hat. Insbesondere i s t die Auswahl und der sprachliche Ausdruck der gegebenen Informationen stark von
144
Spezifikation
6.2.
der gewählten Implementierungssprache abhängig. Die Beschreibung eines Moduls aus der Zerlegung 2 des Telegrammabrechnungsbeispiels aus Abschnitt 5.1. könnte z.B. wie in Bild 6-2 dargestellt aussehen, wobei hier sicherlich nicht alle denkbaren Informationen über den Modul aufgeführt sind. Modul
Telegrammdatenverwaltung
Strukturgraphnummer: 3.2 (vgl. Bild 5-6) Beschreibung: Enthält die Liste der Tripel (TID-Code, Wortzahl, Preis), die Gesamtdaten sowie alle nötigen Zugriffsfunktionen. Benutzte Moduln: keine Wird benutzt von: Auswertung (2.1), Ausgabe (2.2) Intermodulare Daten: keine Globale Daten (intramodular): Dl
Tripel liste
D2
Gesamtdaten
D3
Aktuelles Tripel
Liste von Preis), wobei TID-Code Wortzahl Preis
Tripeln (TID-Code, Wortzahl, numerisch, 6-stellig ganzzahlig positiv ganzzahlig positiv
Paar (Gesamtwortzahl, Gesamtpreis) wobei Gesamtwortzahl ganzzahlig positiv Gesamtpreis ganzzahlig positiv Zeiger auf "Tri pelliste"; steht vor der ersten Anwendung von "Lies nächstes Tripel" auf erstem Tripel entsprechend Schlüssel TID-Code
Funktionen: Fl
Speichere Tripel
hat als Eingangsparameter TID-Code, Wortzahl, Preis und fügt das Tripel entsprechend dem Schlüssel TID-Code geordnet in "Tripel 1iste" ein. Ausnahmebeh.: Wenn TID-Code schon vorhanden, Tripel dennoch einfügen, Meldung absetzen.
F2
Lies nächstes Tripel
liefert als Wert das Tripel, auf dem der Zeiger "Aktuelles Tripel" steht, und setzt "Aktuelles Tripel" auf das nach dem Schlüssel TID-Code nächste
6.3.
Parnassche S p e z i f i k a t i o n
145
Tripel in " T r i p e l l i s t e " . Ausnahmebeh.: Wenn Tripel l i s t e abgea r b e i t e t oder l e e r , vereinbarten Sonderwert a b l i e f e r n . F3
Aktualisiere Gesamtdaten
. '
hat a l s Eingangsparameter Wortzahl, Preis und addiert diese Werte auf "Gesamtdaten". Ausnahmebeh.: keine
F4
L i e s Gesamtdaten
:
l i e f e r t a l s Wert "Gesamtdaten". Ausnahmebeh.: keine
Ende Modul
Telegrammdatenverwaltung
B i l d 6-2:
Modulbeschreibung für Telegrammabrechnungsprogramm nach der "Methode" der informellen I n h a l t s v e r z e i c h n i s s e
Informelle I n h a l t s v e r z e i c h n i s s e haben zwar den V o r t e i l , ziemlich l e i c h t v e r s t ä n d l i c h zu s e i n , doch wird dieser V o r t e i l bei weitem aufgewogen durch eine Reihe von Nachteilen: -
es g i b t kein formal abzuprlifendes Kriterium, ob die Beschreibung vollständig
-
ist;
die Beschreibung i s t sowohl von der Auswahl der aufzuführenden Punkte her a l s auch, was die Sprache a n b e t r i f f t , mit Implementierungsdetails
-
überladen;
die Beschreibung i s t trotz einer Reihe von Formulierungsnormen wegen der extensiven Verwendung n a t ü r l i c h e r Sprache mißverständlich.
Dies sind die Gründe, warum informelle I n h a l t s v e r z e i c h n i s s e
nicht
w i r k l i c h a l s Methode bezeichnet werden können und höchstens einen Notbehelf für fehlende Spezifikationsmethoden d a r s t e l l e n .
6.3. Parnassche Spezifikation In der Parnasschen Spezifikationsmethode [Parnas72a] werden grunds ä t z l i c h Moduln a l s abstrakte Datentypen aufgefaßt. Wir haben in Kapitel 5 g e z e i g t , daß die Zerlegung nach dem Kriterium der
Isolie-
146
Spezifikation
6.3.
rung von Datenstrukturen samt zugehörigen Zugriffsfunktionen wesentliche Vorteile gegenüber der Zerlegung nach anderen Kriterien, z.B. der zeitlichen Abfolge von Teilprozessen besitzt. Es i s t deshalb gerechtfertigt, Spezifikationstechniken für abstrakte Datentypen und verwandte Modularten besonders zu betrachten, auch wenn die S p e z i f i kation ganzer Systeme mit solchen Techniken noch große Schwierigkeiten bereitet. (So s p e z i f i z i e r t Parnas in [Parnas72a] zwar die Moduln eines Systems als abstrakte Datentypen in (halb-)formaler Weise, das gesamte System jedoch in natürlicher Sprache.) Der Spezifikation eines Moduls l i e g t bei dieser Methode das Prinzip zugrunde, daß die Beschreibung a l l e Informationen enthält, welche die benutzenden Moduln über die benutzbaren Elemente - also die Zugriffsfunktionen - benötigen und die die korrekte Implementierung des Moduls ermöglichen, und nichts weiter. Dies i s t nur möglich, i n dem man ganz auf eine Beschreibung der Daten verzichtet und ausschließlich die wechselseitigen Abhängigkeiten der von außen benutzbaren Funktionen s p e z i f i z i e r t . Diese Funktionen werden in zwei Klassen eingeteilt: solche, die zustandsändernd wirken, d.h., Veränderungen an den Daten des Moduls bewirken, und solche, die Werte abliefern und außer im Fehlerfalle keinen Effekt haben. Für jede Funktion werden die folgenden Angaben gemacht: -
Die Menge der möglichen Werte (bei wertabliefernden Funktionen).
-
Anfangswert, d.h., der Wert der Funktion, solange noch keine zustandsändernde Funktion aufgerufen wurde; ein spezieller Anfangswert i s t "undefined" (bei wertabliefernden Funktionen).
-
Typen und Namen der Parameter. Effekt: Hier wird zum einen eine Folge von Fehlerbedingungen und der jeweils notwendigen Reaktionen angegeben, zum anderen die Wirkung auf die Moduldaten für den F a l l , daß keine der Fehlerbedingungen z u t r i f f t .
Die Spezifikation der Funktionen wird ergänzt durch notwendige Erläuterungen.
6.3.
Parnassche S p e z i f i k a t i o n
147
A l s B e i s p i e l geben wir die S p e z i f i k a t i o n eines K e l l e r s ganzer Zahlen, der die maximale Tiefe "max" hat: module stack (max): function PUSH (a) parameters : integer a effect : i f 'DEPTH' = max then c a l l
ERR0R1
e l s e (TOP = a, DEPTH = 'DEPTH' + 1) function POP parameters : none effect : i f 'DEPTH' = 0 then c a l l ERR0R2 e l s e DEPTH = 'DEPTH' - 1 function TOP p o s s i b l e values : integer i n i t i a l value : undefined parameters : none e f f e c t : i f 'DEPTH' = 0 then c a l l ERR0R3 function DEPTH p o s s i b l e values : integer i n i t i a l value : 0 parameters : none effect : none Die A u f r u f f o l g e "PUSH(a); POP" hat keinen E f f e k t , wenn kein Fehleraufruf a u f t r i t t . endmodule stack. Die E f f e k t s p e z i f i k a t i o n e n enthalten keine algorithmischen Elemente, es werden l e d i g l i c h die Werte von veränderten Daten nach Ausführung der Funktion angegeben. Das Gleichheitszeichen drückt a l s o die G l e i c h h e i t s r e l a t i o n aus (und nicht etwa den Zuweisungsoperator wie
148
Spezifikation
6.3.
in FORTRAN und PL/I), und in Hochkommata eingeschlossene Funktionsnamen drücken den Wert dieser Funktion vor Ausführung aus. Was beim Auftreten von Fehlern geschieht, bleibt dem aufrufenden Modul überlassen. Während die Effekte, wenn kein Fehler auftritt, in beliebiger Reihenfolge notiert werden können, ist die Reihenfolge der Notierung der Fehlerbedingungen (falls mehrere möglich sind) bedeutungsvoll insofern, als man eine Kontrolle darüber haben möchte, welche Fehlerbedingung zuerst abgeprüft wird. Diese Art der Modulspezifikation hat eine Reihe von Qualitäten, die sie über die bisher betrachteten hinaushebt: -
Der Datentypmodul wird vollständig durch die von ihm zur Verfügung gestellten Funktionen spezifiziert, ohne auf die von diesen manipulierte Datenstruktur Bezug zu nehmen. Dadurch gerät man nicht in Gefahr, auf die Implementierung der Daten einzugehen, was man in der Entwurfsphase ja gerade vermeiden möchte.
-
Dadurch, daß die Effektspezifikation nicht algorithmisch ist, wird auch die Angabe von Funktionsimplementierungen vermieden. Lediglich über die Reihenfolge der Fehlerabfragen wird der Benutzer informiert, damit er entsprechend reagieren kann.
Durch eine solche Spezifikation wird also nicht genau eine Implementierung festgelegt, sondern eine Klasse von Implementierungen. Eine geeignete Implementierung kann nun aufgrund anderer Kriterien ausgewählt werden. Die obige Kellerspezifikation erlaubt beispielsweise Kellerimplementierungen als Feld, als verkettete Liste, als Feld von Zeigern auf die Elemente, als verkettete Liste von Zeigern auf die Elemente und andere. Die Parnassche Spezifikationsmethode erfüllt also eine der wichtigsten Anforderungen, nämlich implementierungsunabhängige Spezifikationen zuzulassen. Jedoch hat diese Methode auch einige Schwächen und Nachteile: -
Direkte Zusammenhänge zwischen zustandsändernden Funktionen können nicht im Formalismus ausgedrückt werden. So mußte im Keller-
6.3.
Parnassche S p e z i f i k a t i o n
149
bei spiel der Zusammenhang von push und pop durch einen der ModulS p e z i f i k a t i o n hinzugefügten Satz in n a t ü r l i c h e r Sprache ausgedrückt werden. Fehlte der Satz, so würde auch z.B. eine Implementierung einer Schlange die S p e z i f i k a t i o n e r f ü l l e n 2 . -
Die V o l l s t ä n d i g k e i t und Widerspruchsfreiheit einer s o l c h e r a r t gegebenen S p e z i f i k a t i o n i s t n i c h t l e i c h t einzusehen. Mag dies im K e l l e r b e i s p i e l noch möglich s e i n , so s t e i g t die Schwierigkeit der überprüfbarkeit mit der Länge der S p e z i f i k a t i o n enorm an. Diese S c h w i e r i g k e i t kann durch Übung gemildert werden.
-
Da keine Funktion für s i c h , sondern nur im Zusammenhang mit den anderen Funktionen des Moduls v e r s t ä n d l i c h i s t , sind solche Spez i f i k a t i o n e n nicht ganz l e i c h t zu verstehen. Ähnliche Schwierigkeiten hat man bei der Konstruktion. Es g i b t zwar einige Hinweise dafür: a l l e von außerhalb des Moduls benutzbaren Funktionen müssen aufgeführt s e i n , in den E f f e k t s p e z i f i k a t i o n e n der zustandsändernden Funktionen müssen die Wirkungen auf die Werte der wertliefernden Funktionen beschrieben s e i n usw.
Aber V o l l s t ä n d i g k e i t
und Widerspruchsfreiheit zu erreichen b l e i b t im wesentlichen dem Geschick und der Erfahrung des Entwerfers überlassen. Die Möglichk e i t - und Notwendigkeit - , neben den einigermaßen formalen Funkt i o n s s p e z i f i k a t i o n e n z u s ä t z l i c h e Informationen in n a t ü r l i c h e r Sprache zu geben, i s t dabei ein besonders neuralgischer Punkt, da s i e dazu v e r l e i t e n kann, in die F u n k t i o n s s p e z i f i k a t i o n e n nur T r i v i a l i t ä t e n und a l l e s Wesentliche in diese hinzugefügten Sätze zu schreiben. An dieser Methode hat es e i n i g e Verbesserungen gegeben (zum B e i s p i e l [Parnas76]), die jedoch im wesentlichen n o t a t i o n e l l e r Natur s i n d .
2
Daß ein Programmierer v i e l l e i c h t auch ohne den Satz die gegebene S p e z i f i k a t i o n a l s K e l l e r implementieren würde, l i e g t an der Namensgebung für die Funktionen. S i n n v o l l e Namensgebung a l l e i n i s t aber weder für eine maschinelle Überprüfung ausreichend noch für einen Programmierer, der n i c h t weiß, was ein K e l l e r i s t .
150
Spezifikation
6.4.
Trotz der oben beschriebenen Schwächen und Nachteile hat die Parnassche Spezifikationsmethode wegen ihrer grundlegenden Ideen - keine Bezugnahme auf die internen Daten, Einteilung der Funktionen in zustandsändernde und wertliefernde, Beschreibung der Wirkungen der Funktionen aufeinander - nachhaltige Einflüsse auf die Entwicklung der Spezifikationsmethodik gehabt. Darüberhinaus bietet sie durch ihren halbformalen Charakter zumindest für kleinere Projekte oder Projektteile einen brauchbaren Kompromiß zwischen formalen, aber noch nicht zur Verständlichkeit entwickelten Spezifikationsmethoden und natürlich-sprachlichen und deshalb mehrdeutigen Modulbeschreibungen.
6.4. Algebraische Spezifikation Wir werden nun eine weitere Methode vorstellen, die auf die Spezifikation von Datentypen zugeschnitten ist. Sie heißt algebraisch, weil sie abstrakte Datentypen als algebraische Strukturen auffaßt, also als Mengen mit einigen darauf definierten (inneren und äußeren) Operationen, für die ein System von Gesetzen (Axiomen) gilt. Die Methode geht im wesentlichen zurück auf Zilles [Zilles75] und Guttag [Guttag75] und ist inzwischen zu einem zentralen Thema der Informatik-Forschung auf dem Gebiet der Softwarespezifikation geworden. Im Gegensatz zu den bisher betrachteten Methoden sind algebraische Spezifikationen vollständig formal. Dabei sind noch längst nicht alle Probleme gelöst; sowohl was die theoretische Fundierung als auch was die praktische Anwendbarkeit anbetrifft, müssen hier noch Forschungsarbeit geleistet und Erfahrungen gesammelt werden. Mehr noch als bei den meisten Themen dieses Buches handelt es sich bei unserer Darstellung also um einen Zwischenbericht über ein sich stürmisch entwickelndes Forschungsgebiet.
6.4.1.
Beispielspezifikation
151
6.4.1. Eine Beispielspezifikation Wir wollen die Methode erläutern, indem wir als Beispiel die algebraische Spezifikation eines binären Baumes geben, der dynamisch unbegrenzt wachsen kann und dessen Knoteninhalte Zeichen (characters) sind. type BTREE
(1)
emptytree make
BTREE
0
(2)
BTREE x CHAR x BTREE - BTREE
is empty
BTREE
-
(3)
BOOL
(4)
left
BTREE
- BTREE
(5)
right
BTREE
-
BTREE
(6)
data
BTREE
CHAR
(7)
BOOL
(8)
is in
-
—
BTREE x CHAR
axioms for 1, r e BTREE, c, d e CHAR Jet is empty (emptytree)
= true
(9) (10)
is empty (make (1, c, r)) = false
(11)
left (emptytree)
= emptytree
(12)
left (make (1, c, r))
= 1
(13)
right (emptytree)
= emptytree
(14)
right (make (1, c, r))
= r
(15)
data (emptytree)
= undefined
(16)
data (make (1, c, r))
= c
(17)
is in (emptytree, d)
= false
(18)
is in (make (1, c, r), d) =
(19)
rf c = d then true else is in endtype BTREE
1, d) or is in (r, d) (20)
152
Spezifikation
6.4.1.
Diese algebraische Spezifikation definiert den Typ BTREE. Dabei wird vorausgesetzt, daß die Typen BOOL und CHAR schon vordefiniert sind - entweder a p r i o r i als "elementare Typen" oder durch eine andere algebraische Spezifikation. Man beachte, daß an diese beiden Typen Mindestanforderungen g e s t e l l t werden: BOOL hat genau zwei, voneinander verschiedene Werte - true und false - und in CHAR gibt es eine Gleichheitsrelation (verwendet in Zeile (19)). Der erste Teil der Spezifikation (Zeilen (2) bis (8)) besteht aus Angabe der Definitions- und Wertebereiche der den Datentyp charakterisierenden Funktionen. Sie haben a l l e gemein, daß sie jeweils eine einzige Menge als Wertebereich haben (und nicht etwa ein Kartesisches Produkt) und daß sie keine Seiteneffekte haben; es sind also Funktionen im mathematischen Sinn. Seiteneffekte sind deshalb verboten, weil dann die Ausführung solcher Funktionen nicht mehr nur die in den Gleichungen (10) bis (19) ausgedrückten Wirkungen hätte, sondern auch in der Spezifikation unsichtbare. Die Funktionen können in Klassen eingeteilt werden: die n u l l s t e l l i g e Funktion emptytree in Zeile (2) und die Funktion make in Zeile (3) sind die aufbauenden Funktionen: mit emptytree wird ein (leerer) Baum ins Leben gerufen, mit make wird er erweitert. Die Funktionen l e f t , right und data dienen zur Traversierung des Baumes, und die Funktionen i s empty und i s in erlauben Tests auf leeren Baum und Anwesenheit von Information. Diese fünf Funktionen tragen also nicht zum Aufbau der Datenstruktur bei. Man könnte sich noch andere nützliche Operationen vorstellen - Ändern der Knoteninformation, Abhängen von Blättern, ... - ; diese sind in dem obigen Spezifikationsschema jedoch nicht vorgesehen. Der zweite Teil der Spezifikation (Zeilen (9) bis (19)) enthält die Axiome der algebraischen Struktur BTREE. Ein Baumexemplar wird dabei repräsentiert durch die Hintereinanderausführung von aufbauenden Funktionen, und in den Axiomen werden die Wirkungen der nichtaufbauenden Funktionen auf in dieser Weise dargestellte Baumexemplare beschrieben.
6.4.1.
Beispielspezifikation
153
Die Axiome werden in Form von Gleichungen gegeben, in denen die Ausdrücke der rechten Seiten aus Konstanten und Variablen (im mathematischen Sinne) der Wertebereichstypen mit Hilfe der aussagenlogischen Operatoren, der Fallauswahl und der Rekursion gebildet werden. Die Ausnahme-/Fehlerbehandlung bereitet in der Methode der algebraischen Spezifikation gewisse Schwierigkeiten. Für den Augenblick wollen wir jedoch einfach annehmen, daß der in Zeile (16) verwendete Funktionswert "undefined" eine Konstante vom Typ CHAR ist. Woher wissen wir nun aber, daß Strukturen, die diese alle Axiome erfüllenden Funktionen haben, tatsächlich binäre Bäume sind? Fehlt vielleicht nicht eine ganz wesentliche Eigenschaft, oder ist eine Eigenschaft "zu viel" da, ist also die Spezifikation vielleicht nicht allgemein genug? Um zu rechtfertigen, daß diese Spezifikation die Binärbaumstruktur definiert, müssen wir uns klar darüber werden, daß die charakteristischen Eigenschaften, die einen der mathematischen Definition entsprechenden Baum auszeichnen, auch auf einen dieser Spezifikation nachgebildeten Baum zutreffen. Dies erscheint in unserem Beispiel plausibel, sind doch die aufbauenden Funktionen emptytree und make so gewählt, daß sie die rekursive Definition nachbilden: "Ein Binärbaum ist entweder leer oder er besteht aus einem Knoten, gefolgt von einem linken und einem rechten Binärbaum". Eine solche Eigenschaft, nämlich die, daß das Vorhandensein eines Knoteninhalts unabhängig von der Reihenfolge des Baumaufbaus festgestellt werden kann, wollen wir jetzt zeigen. In die Begriffe der BTREE-Spezifikation übersetzt heißt das, o.B.d.A. zu zeigen, daß is in (make (make (Ii, c,, r-i), c 2 , r 2 ) , d) = is in (make (make (Ii, c 2 , r-,), c-i, r 2 ) , d) . Bei Anwendung von Zeile (19) erhalten wir für den ersten Ausdruck jf c 2 = d then true else is in (make (l-i, c-i, r-,), d) or is in (r 2 , d) und für den zweiten Ausdruck vf c-i = d then true eise is in (make (1-,, c 2 , r-i), d) or is in (r 2 , d) .
154
6.4.2.
Spezifikation
Nach den Regeln der Logik können wir den Ausdruck
is in (r 2 , d)
jeweils aus dem else-Zweig herausnehmen und disjunktiv mit dem übrigen Ausdruck verknüpfen, so daß wir nur noch die beiden anderen Ausdrucksteile zu vergleichen haben. Dazu wenden wir noch einmal Zeile (19) an und erhalten für den ersten Ausdruck: vf c 2 = d then true eise vf Ci = d then true eise is in (1 15 d) or is in (r-,, d) und für den zweiten Ausdruck: vf Ci = d then true else if c 2 = d then true eise is in (l,, d) or; is in (r 1 s d) . Da die Reihenfolge der Fallunterscheidung keine Rolle spielt, haben wir die Gleichheit gezeigt. Auf ähnliche Weise müßten andere allgemeine Baumeigenschaften gezeigt werden. 6.4.2. Die Vorteile der algebraischen Spezifikation Bevor wir auf die Probleme der algebraischen Spezifikation eingehen, möchten wir ihre wesentlichen Qualitäten zusammenfassend darstellen: -
Algebraische Spezifikationen sind formal. Dadurch ist eine Verifikation von Implementierungen mit mathematischen Mitteln möglich, wie wir an einem Beispiel im Abschnitt 6.4.7. zeigen werden.
-
Die Spezifikation eines Datentyps ist implementierungsunabhängig, was bedeutet, daß Repräsentationen aufgrund anderer Kriterien gewählt und auch bei Bedarf ausgetauscht werden können, ohne daß die Spezifikation geändert werden muß.
-
Die Spezifikation ist leicht erweiterbar: eine zusätzliche Funktion erfordert lediglich die Angabe ihres Definitions- und Wertebereichs im functions-Teil und das Hinzufügen der Regeln über die Anwendung dieser Funktion auf die aufbauenden Funktionen im axioms-Teil.
6.4.2. -
V o r t e i l e algebraischer S p e z i f i k a t i o n
155
Die S p e z i f i k a t i o n i s t zumindest für r e l a t i v kleine abstrakte Datentypen mit e i n i g e r Übung sowohl konstruierbar a l s auch verständl i c h . Was die V e r s t ä n d l i c h k e i t a n b e t r i f f t , darf a l l e r d i n g s
nicht
die Bedeutung der mnemonisehen Namensgebung vergessen werden. A l s B e i s p i e l betrachte man B i l d 6 - 3 , in dem die algebraische S p e z i f i kation eines allgemein bekannten Datentyps gegeben wird, a l l e r dings unter Verwendung ungewöhnlicher Namen. An der S c h w i e r i g k e i t , den Datentyp zu i d e n t i f i z i e r e n , mag der Leser die Bedeutung der Wahl suggestiver Namen für das Verständnis einer solchen S p e z i f i kation ermessen. type MYSTERY functions delete follow
- MYSTERY
0 MYSTERY x CHAR
- MYSTERY
say
MYSTERY
- MYSTERY
make
MYSTERY
- CHAR
i s cold :
MYSTERY
- BOOL
destroy : MYSTERY x MYSTERY - MYSTERY axioms f o r m, n e MYSTERY, c
E
CHAR l e t
i s cold (delete)
= true
i s cold (follow (m, c ) )
= false
say (delete)
= delete
say (follow (m, c ) ) i f i s cold (m) then delete el se follow (say (m), c) make (delete)
= undefined
make (follow (m, c ) ) i f i s cold (m) then c el se make (m) destroy (m, delete)
=m
destroy (m, follow (n, c ) ) follow (destroy (m, n ) , c) endtype MYSTERY B i l d 6-3:
Algebraische S p e z i f i k a t i o n einer bekannten Datenstruktur
156
Spezifikation
6.4.3.
6.4.3. Datentypgeneratoren Im Abschnitt 6.4.1. haben wir einen Baum s p e z i f i z i e r t , dessen Knoteninhalte vom Typ CHAR sind. Ein implementierter Baum mit ganzen Zahlen a l s Knoteninhalten würde diese S p e z i f i k a t i o n also nicht e r f ü l l e n ; eine S p e z i f i k a t i o n dieser Implementierung i s t jedoch l e i c h t aus der gegebenen zu gewinnen, indem a l l e Vorkommen von CHAR durch INT ersetzt werden. Es l i e g t nun nahe, danach zu fragen, ob man nicht einen "allgemeinen" Binärbaum ohne Bezug auf konkrete Knoteninhalte s p e z i f i z i e r e n kann. Dies erreicht man, indem man Spezifikationen von parametrisierten Datentypen oder Datentypgeneratoren zuläßt, aus denen S p e z i f i k a t i o nen einzelner Datentypen durch Parametersubstitution gewonnen werden. Dazu das Beispiel einer Menge: type generator SET [ITEM] functions emptyset :
0
- SET
add
: SET x ITEM - SET
i s in
: SET x ITEM - BOOL
delete
: SET x ITEM - SET
axioms for s e SET, i , j E ITEM l e t add (add ( s , i ) , j ) i_f i = j then add ( s , i ) else add (add ( s , j ) , i s in (emptyset, j )
= false
i s in (add ( s , i ) , j )
=
i)
vf i = j then true else i s in ( s , j ) delete (emptyset, j )
= emptyset
delete (add (s, i ) , j ) = r f i = j then s else add (delete ( s , j ) , endtype generator SET
i)
6.4.4.
Versteckte Funktionen
157
Einen Mengentyp mit speziellen Elementen erhält man nun dadurch, daß man den formalen Typ ITEM durch einen konkreten ersetzt. Diese Vorgehensweise ist jedoch nicht ganz problemlos, werden doch an den einzusetzenden Typ implizit gewisse Minimalforderungen gestellt, die nicht immer auf den ersten Blick zu erkennen sind. Im Beispielfalle muß der ITEM ersetzende Typ einen Gleichheitstest besitzen, was nicht immer trivial ist, wenn man z.B. daran denkt, eine Menge von Bäumen bilden zu wollen. Auch die Ersetzung von ITEM durch SET [SET [INT]] wäre erst erlaubt, wenn der Spezifikation ein Gleichheitstest hinzugefügt würde. 6.4.4. Das Problem der versteckten Funktionen Als herausragende Eigenschaft derjenigen Spezifikationen, bei denen Datentypen ausschließlich durch ihr äußeres Verhalten charakterisiert werden, was auf die Parnassche und die algebraische Spezifikationsmethode zutrifft, haben wir die Implementierungsunabhängigkeit gekennzeichnet. Implementierungsentscheidungen werden nicht durch Darstellungszwänge, also problemfremde Gründe, herbeigeführt, sondern können durch gewünschte Qualitäten des Softwareprodukts und des Herstellungsprozesses induziert werden. Die Implementierungsunabhängigkeit wird in der algebraischen Spezifikation dadurch erreicht, daß weder auf die konkrete Darstellung der Datenstruktur Bezug genommen wird noch andere als die von außen benutzbaren Funktionen angegeben werden. Majster [Majster77] hat jedoch ein Beispiel eines abstrakten Datentyps gegeben, für den es ihrer Ansicht nach nicht möglich ist, eine endliche algebraische Spezifikation ohne Zuhilfenahme zusätzlicher Beschreibungsmittel zu geben. Die Schwierigkeit, diesen Datentyp in der üblichen Weise algebraisch zu spezifizieren, rührt daher, daß es im Gegensatz zu den bisherigen Beispielen nicht genügt, bei Anwendung von Operationen leere und nicht-leere Datentypexemplare zu unterscheiden, sondern die "Geschichte" der Datenstruktur berücksichtigt werden muß. Das heißt, die Legalität der Anwendung einer be-
158
Spezifikation
6.4.4.
stimmten Operation hängt davon ab, welche Operationen und wieviele Anwendungen welcher Operationen auf dieses Datentypexemplar bisher gewirkt haben. Die Daumenregel, Datentypexemplare einfach durch wenige "aufbauende" Funktionen darstellen zu können (im Binärbaum: emptytree, make (1, d, r)), trifft hier nicht zu. Eine Lösung besteht darin, versteckte Funktionen (auch private und nicht-öffentliche genannt) in die Spezifikation einzuführen, mit deren Hilfe Buch über die wichtigen Zustandsveränderungen der Datenstruktur geführt wird, die aber dem Benutzer nicht zur Verfügung stehen. Thatcher et al. [Thatcher78] haben nachgewiesen, daß es Datentypen gibt, die nur mit Hilfe versteckter Funktionen algebraisch spezifiziert werden können. Nun gibt es keinen zwingenden Grund, auf versteckte Funktionen vollständig zu verzichten, aber es besteht die Gefahr, daß auch solche versteckte Funktionen in die Spezifikation aufgenommen werden, die zur vollständigen Charakterisierung des Datentyps gar nicht notwendig sind und damit überflüssigerweise eine
Implementierungsentschei-
dung enthalten. Vor der Suche nach geeigneten versteckten Funktionen - die schwierig genug ist - gilt es also zu entscheiden, ob die Spezifikation überhaupt einen nicht-öffentlichen Teil enthalten muß. Die Klasse der ohne versteckte Funktionen algebraisch spezifizierbaren Datentypen konnte bisher nicht bestimmt werden. Ein wichtiges Merkmal dieser Klasse ist jedoch die unbeschränkte Anwendbarkeit der aufbauenden Funktionen: So kann in der BTREE-Spezifikation in jedem Zustand eines BTREE-Exemplars die Funktion "make" und in der SETSpezifikation in jedem Zustand eines SET[•]-Exemplars die Funktion "add" angewendet werden. Die gleiche Beobachtung machen wir bei der Spezifikation endlicher Datentypen: "make" bzw. "add" waren vor allem deshalb unbeschränkt anwendbar, weil die Baum- bzw. Mengenexemplare beliebig wachsen konnten. Ist dies nicht erlaubt, wie z.B. bei einem Keller mit maximaler Tiefe (s. Beispiel in Abschnitt 6.3.), so gibt es Zustände, in denen die Anwendung aufbauender Funktionen als Fehler spezifi-
6.4.5.
Fehlerbehandlung
159
ziert werden muß. Auch hier hilft die Verwendung von versteckten Funktionen. Dazu das Beispiel des endlichen Kellers aus Abschn. 6.3. ("mt" steht für "empty"): type generator FSTACK [ITEM] mtstack
-
FSTACK
hiddenpush
FSTACK x ITEM
NAT
-
FSTACK
push
FSTACK x ITEM
-
FSTACK
pop
FSTACK
-
FSTACK
top
FSTACK
-
ITEM
depth
FSTACK
1 imit
FSTACK
NAT -
NAT
axioms for k e NAT, i e ITEM, s e FSTACK let pop (mtstack (k))
= errorl
pop (hiddenpush (s, i))
= s
top (mtstack (k))
= undefined
top (hiddenpush (s, i))
= i
depth (mtstack (k))
= 0
depth (hiddenpush (s, i))
= depth (s) + 1
limit (mtstack (k))
= k
limit (hiddenpush (s, i))
= 1imit (s)
push (s, i)
= jf depth (s) < limit (s) then hiddenpush (s, i) else error2
endtype generator FSTACK Zusammenfassend kann man sagen, daß solche abstrakte Datentypen, bei denen die aufbauenden Funktionen nicht unabhängig vom Zustand des Typexemplars angewendet werden können, nur mit Hilfe von versteckten Funktionen algebraisch spezifiziert werden können.
6.4.5. Fehlerbehandlung Wir haben in den bisherigen Beispielen zwar schon Funktionswerte "error" und "undefined" verwendet, aber uns noch nicht um eine kon-
160
6.4.5.
Spezifikation
sistente Behandlung von Fehler- und Ausnahmefällen gekümmert. Eine gelungene Integration der Fehlerbehandlung in das Konzept der algebraischen Spezifikation ist jedoch für die Brauchbarkeit der Methode wesentlich, kommt doch kein Programmodul ohne Ausnahmefälle aus. Ein mögliches Vorgehen haben wir in der FSTACK-Spezifikation im vorigen Abschnitt gesehen. Ist jedoch schon die Einführung versteckter Funktionen problematisch, so werden auf diese Weise Spezifikationen von Datentypen mit vielen Fehlerfällen schnell
unübersichtlich.
Guttag et al. [Guttag77] schlagen deshalb die Einführung eines "restrictions"-Teils vor. Die FSTACK-Spezifikation sieht dann z.B. so aus: type generator FSTACK [ITEM] functions (wie oben, ohne hiddenpush) axioms for k e NAT, i e ITEM, s e FSTACK let pop (push (s, i))
= s
top (push (s, i))
= i
depth (mtstack (k))
=0
depth (push (s, i)) = depth (s) + 1 limit (mtstack (k)) = k limit (push (s, i)) = limit (s) restrictions depth (s) > limit (s) = > push (s, i) = error2 depth (s) =
0
=>
pop (s)
= errorl
depth (s) =
0
=>
top (s)
= undefined
endtype generator FSTACK
Zur Sicherung der Vollständigkeit ist es notwendig, darauf hinzuweisen, daß die Anwendung von Funktionen auf Fehlerwerte wieder Fehlerwerte liefert. Mit den "restrictions" wird nicht einfach eine andere Schreibweise für ein ansonsten unverändertes Konzept eingeführt. Die Verwendung solcher "conditional axioms" führt zu mächtigeren Spezifikationen, als wenn nur Gleichungen als Axiome zugelassen werden
[Thatcher78].
6.4.6.
Andere Modul arten
161
Eine konsequente Weiterentwicklung des Ansatzes, "Fehlerwerte" a l s Konstanten des jeweiligen Typs, also a l s null s t e l l ige Funktionen aufzufassen, besteht darin, in der S p e z i f i k a t i o n neben den functions " e r r o r - f u n c t i o n s " einzuführen und die axioms in " o k - s p e c i f i c a t i o n s " und " e r r o r - s p e c i f i c a t i o n s " zu unterteilen [Goguen78]. Dieser Ansatz gewinnt dadurch an Wert, daß er in einem Projekt der D e f i n i t i o n und Implementierung einer Spezifikationssprache [Goguen77] v e r f o l g t wird. Das Fehlerbehandlungsproblem wird jedoch so lange nicht zufriedenstellend gelöst s e i n , a l s die allgemeine Problematik der Fehler- und Ausnahmebehandlung in Programmen - was sind Fehler, wie s o l l auf Fehler reagiert werden, wer s o l l Fehler behandeln, etc. - nicht h i n reichend geklärt i s t . 6.4.6. Erweiterung auf andere Modularten Bisher haben wir nur die S p e z i f i k a t i o n abstrakter Datentypen behandelt. Eine Methode, die nur auf eine bestimmte Modulart zugeschnitten i s t , i s t jedoch kaum brauchbar. A l l e anderen Modularten ( v g l . Abschnitt 5.2.) lassen sich jedoch a l s Einschränkung, Erweiterung oder Zusammensetzung von abstrakten Datentypen auffassen. Wir bedürfen also eines Mechanismus, der die Konstruktion komplizierterer aus einfachen Moduln erlaubt. Auch bisher schon haben wir zur S p e z i f i k a t i o n von Datentypen andere Datentypen benutzt: In der SET-Spezifikation beispielsweise verwenden wir den "Urtyp" BOOL und den Parametertyp, in der BTREE-Spezifikation ebenfalls BOOL und CHAR. Die L i t e r a t u r der mathematischen Fundierung der algebraischen Spezifikationsmethode s p r i c h t von Sorten, von denen in der S p e z i f i k a t i o n eines Datentyps im allgemeinen mehrere vorkommen, und die S p e z i f i k a t i o n i s t eine many-sorted algebra. Man kann also auch solche Moduln aus abstrakten Datentypen bilden, die s e l b s t keine Datentypen im engeren Sinne sind. Dazu das folgende Beispiel eines Moduls, der Funktionen zur Umwandlung von Binärbäumen in Schlangen und von Schlangen in Binärbäume
162
Spezifikation
6.4.6.
enthält. Wir haben zwei abstrakte Datentypen v o r l i e g e n , BTREE, wie in Abschnitt 6 . 4 . 1 . s p e z i f i z i e r t , sowie QUEUE: type QUEUE s o r t s QUEUE, CHAR, BOOL functions mtq
:
0
QUEUE
append : QUEUE x CHAR
- QUEUE
remove :
QUEUE
- QUEUE
front
:
QUEUE
- CHAR
i s mt
:
QUEUE
- BOOL
concat
QUEUE x QUEUE - QUEUE
axioms f o r q, r e QUEUE, c e CHAR l e t remove (mtq)
= mtq
remove (append (q, c ) )
= i f i s mt (q) then mtq e l s e append (remove ( q ) , c)
f r o n t (mtq)
= undefined
f r o n t (append (q, c ) )
= _if i s mt (q) then c
i s mt (mtq)
= true
i s mt (append (q, c ) )
= false
concat (q, mtq)
= q
e l s e f r o n t (q)
concat (q, append ( r , c ) ) = append (concat (q, r ) , c) endtype QUEUE Wir wollen eine Funktion " i n o r d t r a v e r s " s p e z i f i z i e r e n , d i e d i e Knoteninhalte eines Baumexemplars in der Reihenfolge der symmetrischen Traversierung zu einer Schlange zusammenbaut, sowie eine Funktion " a i p h a b e t " , die das umgekehrte macht. Keine der beiden Funktionen kann s i n n v o l l einem der beiden s p e z i f i z i e r t e n Moduln zugeordnet werden. So l i e g t es nahe, einen eigenen Modul unter Verwendung der b e i den Datentypen zu s p e z i f i z i e r e n :
6.4.6.
Andere Modul arten
163
module BTREE-QUEUE-CONVERSION sorts BTREE, QUEUE, CHAR functions inordtravers : BTREE - QUEUE aiphabet
: QUEUE - BTREE
axioms for 1, r e BTREE, q e QUEUE, c e CHAR let inordtravers (emptytree)
= mtq
inordtravers (make (1, c, r)) = concat (append (inordtravers (l),c), inordtravers (r)) aiphabet (mtq)
= emptytree
alphabet (append (q, c)) rf is mt (q) then make (emptytree, c, emptytree) el if c < data (aiphabet (q)) then make ( alphabet (append (inordtravers (left (alphabet (q))), c)), data (alphabet (q)), right (alphabet (q)) ) el if c > data (alphabet (q)) then make ( left (alphabet (q)), data (alphabet (q)), alphabet (append (inordtravers (right (alphabet (q))), c)) ) else alphabet (q) endmodule BTREE-QUEUE-CONVERSION (< und > sind in CHAR definierte Vergleichsoperatoren.) Was wir hier informell und für ein kleines Beispiel durchgeführt haben, ist in ähnlicher Weise systematisch für ein Informations-Sicherheitssystem von Walter et al. [Walter75] durchgeführt und von Ehrig et al. [Ehrig77] kategorientheoretisch fundiert worden.
164
Spezifikation
6.4.7.
Dieser Mechanismus der schrittweisen S p e z i f i k a t i o n eröffnet die Mögl i c h k e i t , neben mehr oder minder elementaren Bausteinen auch ganze Programme in Ubersichtlicher und verständlicher Weise algebraisch zu s p e z i f i z i e r e n . 6.4.7. Schrittweise Implementierung und Verifikation Der wesentliche Vorteil der algebraischen Spezifikationen,
vollstän-
dig formal zu s e i n , kommt vor allem dann zum Tragen, wenn ein formaler Kalkül e x i s t i e r t , mit dem Implementierungen a l s korrekt gegenüber solchen Spezifikationen gezeigt werden können. Auch hier i s t es nicht s i n n v o l l , die Lücke zwischen der algebraischen S p e z i f i k a t i o n eines Moduls und der programmiersprachlichen Codierung in einem einzigen S c h r i t t zu schließen: die zu bewältigende Komplexität wäre im allgemeinen zu groß. Es l i e g t nahe, die Spezifikationen der Datenstrukturen eines Programms (z.B. "Telegrammdatenverwaltung"
in
Abschnitt 5.1.) auf Spezifikationen von Standarddatenstrukturen wie lineare L i s t e n , Bäume, etc. zurückzuführen und e r s t diese in Programmiersprachencode umzusetzen. Der "Code" dieser Vermittlungsstufe(n) i s t also eine Implementierung der ursprünglichen S p e z i f i k a tion und eine S p e z i f i k a t i o n der "eigentlichen"
Implementierung.
Der zweite Vermittlungsschritt kann mit H i l f e " t r a d i t i o n e l l e r " Methoden (Zusicherungsmethode, Prädikatentransformation, s . Kapitel 8) v e r i f i z i e r t werden. Die V e r i f i k a t i o n einer Implementierung einer a l gebraischen S p e z i f i k a t i o n in Form einer algebraischen S p e z i f i k a t i o n möchten wir am Beispiel einer Symboltabelle vorführen, das wir [Guttag76] entnommen haben. Symboltabellen sind Datenstrukturen in Compilern, in denen die an einer S t e l l e des Programmtextes jeweils gültigen Variablennamen verzeichnet sind. In einer blockstrukturierten Sprache b i l d e t jeder Block einen Gültigkeitsbereich, in dem Variablen d e k l a r i e r t und angewendet werden können. Mit dem Verlassen des Blocks verschwinden die Variablennamen. Die Gültigkeitsbereiche bilden eine Hierarchie insofern, a l s von Variablen gleichen Namens, die in verschiedenen
6.4.7.
Schrittweise Implementierung
165
Blöcken deklariert wurden, immer die zuletzt deklarierte gültig ist, während die anderen "schlafen". Mehrere gleichnamige Variablen im selben Block sind nicht erlaubt. type SYMTAB sorts SYMTAB, IDFIER, ATTRLIST, BOOL functions mtsyta enter block leave block add
0
- SYMTAB
SYMTAB
- SYMTAB
SYMTAB
- SYMTAB
SYMTAB x IDFIER x ATTRLIST - SYMTAB
is in block
SYMTAB x IDFIER
- BOOL
retrieve
SYMTAB x IDFIER
- ATTRLIST
axioms for s e SYMTAB, i, j e IDFIER, a e ATTRLIST let (1)
leave block (mtsyta)
= error
(2)
leave block (enter block (s))
= s
(3)
leave block (add (s, i, a))
= leave block (s)
(4)
is in block (mtsyta, i)
= false
(5)
is in block (enter block (s), i) = false
(6)
is in block (add (s, j, a), i) ijf i = j then true else is in block (s, i)
(7)
retrieve (mtsyta, i)
= error
(8)
retrieve (enter block (s), i)
= retrieve (s, i)
(9)
retrieve (add (s, j, a), i)
=
rf i = j then a endtype SYMTAB
else retrieve (s, i)
IDFIER, ATTRLIST und BOOL sind vordefinierte Typen, wobei BOOL die bekannte Semantik besitzt und IDFIER einen Gleichheitsoperator enthält. Es ist naheliegend, Symboltabellen als Keller von Feldern zu implementieren: Ein Gültigkeitsbereich wird durch ein Feld dargestellt, in das die Variablennamen (vom Typ IDFIER) und die Attribute (vom Typ ATTRLIST) der Variablen eingetragen werden.
166
6.4
Spezifikation
type generator STACK [ITEM] sorts STACK, ITEM, BOOL mtstack push
0
-
STACK
STACK x ITEM - STACK
pop
STACK
STACK
top
STACK
ITEM
is mt
STACK
replace
-
BOOL
STACK x ITEM - STACK
axioms for s e STACK, i e ITEM let pop (mtstack)
= error
pop (push (s, i))
= s
top (mtstack)
= error
top (push (s, i))
= i
is mt (mtstack)
= true
is mt (push (s, i)) = false replace (s, i)
= vf is mt (s) then error else push (pop (s), i)
endtype generator STACK type ARRAY sorts ARRAY, IDFIER, ATTRLIST, BOOL functions mtarray assign
0
- ARRAY
ARRAY x IDFIER x ATTRLIST - ARRAY
read
ARRAY x IDFIER
- ATTRLIST
is undef
ARRAY x IDFIER
- BOOL
axioms for r e ARRAY, i, j e IDFIER, at e ATTRLIST let read (mtarray, i)
= error
read (assign (r, j, at), i)
= vf i = j then at else read (r, i)
is undef (mtarray, i)
= true
is undef (assign (r, j, at), i) = vf i = j then false else is undef (r, endtype ARRAY
Schrittweise Implementierung
6.4.7.
167
Eine Implementierung des Typs SYMTAB besteht zum einen aus den Repräsentationen der SYMTAB-Operationen in STACK [ARRAY]-Operationen und zum anderen aus einer "Abstraktionsfunktion", die für jeden Term der Implementierung denjenigen Term der Spezifikation angibt, den er modelliert. Diese Abstraktionsfunktion i s t im allgemeinen nicht um~ kehrbar, weil ein Spezifikationsterm auf verschiedene Weise implementiert werden kann. imp! SYMTAB as STACK [ARRAY] functions mtsyta' enter block' leave block
1
add'
0
- STACK
STACK
- STACK
STACK
- STACK
STACK x IDFIER x ATTRLIST - STACK
i s in block'
STACK x IDFIER
- BOOL
retrieve'
STACK x IDFIER
- ATTRLIST
reps for s e STACK, i e IDFIER, a e ATTRLIST, r e ARRAY let mtsyta'
:= push (mtstack, mtarray)
enter block'
(s)
:= push (s, mtarray)
leave block'
(s)
:= i f i s mt (pop ( s ) ) then error else pop (s)
add' (s, i , a)
:= replace (s, assign (top ( s ) , i , a))
i s in block' (s, i ) := rf i s mt (s) then false else -i i s undef (top ( s ) , i ) retrieve'
(s, i )
:= i f i s mt (s) then error else i f i s undef (top ( s ) , i ) then retrieve' (pop ( s ) , i ) else read (top ( s ) , i )
abstract f c t
STACK [ARRAY] - SYMTAB
4> (error)
= error
4> (mtstack)
= error
4> (push (s, mtarray)) = i f i s mt (s) then mtsyta else enter block (0 ( s ) ) (push (s, assign (r, i , a ) ) ) = add (4> (push ( s , r ) ) , i , a) endimpl SYMTAB
168
Spezifikation
6.4.7.
Eine Verifikation dieser Implementierung besteht darin zu zeigen, daß die mit Strichen gekennzeichneten Funktionen die Axiome der SYMTAB-Spezifikation erfüllen, d.h., die linken und rechten Seiten der angewendeten Axiome müssen Funktionswerte l i e f e r n , die mit $ auf den gleichen Term in SYMTAB abgebildet werden. Zu zeigen i s t beispielsweise: (1)
4> (leave block 1 (mtsyta 1 )) = 4> (error) Beweis:
4> (leave block' (mtsyta 1 )) = 0 (vf is mt (pop (push (mtstack, mtarray))) then error else pop (push (mtstack, mtarray))) = 4> (vf i s mt (mtstack) then error eise . . . ) = O (error)
(2)
$ (leave block' (enter block" ( s ) ) ) = O (s) Beweis:
4> (leave block' (enter block' ( s ) ) ) = $ (vf i s mt (pop (push ( s , mtarray))) then error else pop (push (s, mtarray))) =
D - S
übeA^ühiung eine,6 Elementen In eine Se.que.nz
last
S - D
letzte*
leader
S - S
vondene TeJJÜ>equenz ohne letztes
first
S - D
e/u>te6 Element einen. Sequenz
trailer
S - S
klnteAe Teltiequ&nz
length
S - INT
Sequenzlänge
Element etneA Sequenz
ohne, eutet>
Element Element
Vom formalen Typ-Parameter Item wird zusätzlich verlangt, daß für Objekte dieses Typs eine Zuweisung " — " definiert ist; der Stack wird abstrakt als eine endliche Sequenz von Item-Objekten dargestellt; die Invariante legt die Maximalausdehnung des Stack durch die Längenbeschränkung der realisierenden Sequenz mit Hilfe des Parameters stacksize fest, für den eine zusätzliche Eingangszusicherung formuliert wird. Das Stack-Objekt wird als leere Sequenz initialisiert. Die Bezeichnung s 1 in den Ausgangszusicherungen der spezifizierten Funktionen kennzeichnen den Wert des Objektes vor Ausführung der Funktion. Im Repräsentationsteil werden für jede Modulinstantiierung zwei Objekte (Vector-Variable v, Integer-Variable stackpointer) vereinbart, durch die ein Stack konkret realisiert wird, Vector ist ein in der Sprache realisierter vorgegebener Typgenerator. Der Zusammenhang zwischen der konkreten Realisierung durch Vector- und IntegerVariablen und der abstrakten Realisierung durch eine Sequenz wird durch die Funktion
7.7.
ALPHARD
205
seq (v, n, m) = vf n < m then < v n » Vn+i, .... v m > eise nullseq hergestellt, die die m - n + 1 Vectorkomponenten v n , ... , v m als Sequenz anordnet. Die aus dem Spezifikationsteil bekannte Invariante wird in Termen der konkreten Realisierung neu formuliert. Die Zustände empty, normal, füll und error beschreiben u.a. die Eingangszusicherungen der Funktionen und die bei Verletzung dieser Zusicherungen zu erfolgenden Operationen im Implementierungsteil. Für push etwa ist die Eingangszusicherung die disjunktive Verknüpfung (empty v normal) < = > (0 < stackpointer < stacksize) . Der Ausdruck
a (s.v 1 , s.stackpointer, x)
bezeichnet das gleiche
Objekt wie s.v' mit der Ausnahme, daß s.v [s.stackpointer] = x
ist.
Der im Beispiel vorgeführte Kalkül schafft die Voraussetzung für formale Verifikation der Implementierung gegenüber der Spezifikation mit Hilfe der Abstraktionsfunktion rep (v, stackpointer) = seq (v, 1, stackpointer). So muß z.B. gezeigt werden, daß die abstrakte Invariante aus der konkreten folgt: (0 < stackpointer < stacksize) = > (0 < length (seq (v, 1, stackpo inter)) ^ stacksize) . Die wichtigsten Verifikationsschritte sind die für jede Funktion zu führenden Beweise, daß aus der konkreten Invarianten und der abstrakten Vorbedingung die konkrete Vorbedingung folgt und aus der konkreten Invarianten und der konkreten Nachbedingung die abstrakte Nachbedingung folgt, wenn die Abstraktionsfunktion angewendet wird. Eine ausführliche Darstellung findet sich in [Wulf761. Eine ALPHARD-Form ist kein statischer Modul. Durch beliebige ModulInstantiierungen können Objekte des Modultyps erzeugt werden. Durch sl : Stack (integer, 100); s2 : Stack (real, 200);
206
Modulkonzepte in Programmiersprachen
7.8.
werden zwei Stack-Variablen vereinbart und generiert, auf welche die Operationen
push ( s l , 3); push (s2, 3.14)
angewendet werden kön-
nen. Für ALPHARD-Forms gelten die folgenden Sichtbarkeitsregeln: -
nur solche Bezeichner, die im Spezifikationsteil auftreten, sind nach außen sichtbar,
-
nur Form-Bezeichner können implizit in eine Form importiert werden (z.B. integer und vector), a l l e anderen Bezeichner können innerhalb einer Form nur dann benutzt werden, wenn sie als aktuelle Formparameter auftreten.
Im übrigen gelten die Sichtbarkeitsregeln der Blockstruktur.
7.8. CDL2: eine Sprache mit mehrstufigem Zerlegungskonzept Die Sprache CDL2 [Koster74], [Kerutt77], [Stahl78] i s t zur Implementierung von Systemsoftware entwickelt worden. Sie enthält ein dreistufiges Zerlegungskonzept, das sich an einem einfachen Programm nur schwer zeigen ließe. Wir werden daher eine mehr schematische Darstellung verwenden. Getrennt kompilierbare "Moduln" (oder Programmeinheiten) bilden die Bausteine einer aus der Anforderungsdefinition ableitbaren Zerlegung eines Softwaresystems. Moduln ergeben sich also auf dieser obersten Stufe aus einer funktionalen Grobzerlegung. Sie kommunizieren untereinander über explizite Export- und Import-Schnittstellen, die den Austausch von Algorithmen und Konstanten zwischen Moduln regeln. Weitergehende Regeln für die Anordnung der Moduln gibt es nicht, jedoch wird angenommen, daß die Import-Struktur eine Hierarchie definiert. Das folgende Beispiel zeigt ein in CDL2 (teilweise) r e a l i s i e r t e s Benutzersystem auf einem Kleinrechner mit Plattenperipherie, Terminals und Drucker, in das ein Sprachcompiler und ein Editor integriert sind.
7.8.
CDL2
207
Compi1er
Filesystem
Disk -I/O
Processhandling
Bild 7-1:
Modulstruktur eines Benutzersystems
Die Einführung des Modulbegriffs in die Sprache CDL2 erfolgte relativ spät [Bayer78a]. Die Notwendigkeit ergab sich zunächst aus dem Fehlen eines geeigneten Sprachkonstruktes zur Formulierung getrennt kompilierbarer Programmteile. Diese sollten aus einer vertikalen Auftrennung eines streng hierarchisch (in horizontale Schichten) zerlegten CDL2-Programmes entstehen. Die gegenwärtige Realisierung des Modulkonzeptes ist die Konsequenz aus der Einsicht der Sprachentwerfer, daß sich nicht jedes Programmsystem sinnvoll als strenge Hierarchie realisieren läßt (vgl. dazu Abschnitt 5.3.). Die vor Einführung des Modulkonzeptes einem ganzen CDL2-Programm aufgeprägte streng hierarchische Zerlegungsstruktur findet sich nun innerhalb der Moduln: Ein Modulrumpf besteht aus einer oder mehreren benannten Schichten (layers), deren Abstraktionsgrad von der untersten zur obersten Schicht zunimmt. Eine Schicht kann ausschließlich Algorithmen und Konstanten der nächsttieferen Schicht applizieren.
208
Modulkonzepte in Programmiersprachen
7.8.
Schichten wiederum bestehen aus Abschnitten (sections), welche eine Gliederung von begrifflich zusammenhängenden Einheiten einer Schicht unterstützen sollen. Abschnitte sind die kleinsten Zerlegungseinheiten. Ein Abschnitt enthält Daten- und Algorithmenvereinbarungen, die er mit Hilfe einer "Extensions-Schnittstelle" (ext) anderen Abschnitten derselben Schicht oder mit Hilfe einer "Abstraktions-Schnittstelle" (abstr) der nächsthöheren Schicht sichtbar machen kann. Diesen die modulinterne Sichtbarkeit steuernden Schnittstellen entspricht die explizite "Export-Schnittstelle" (export) eines Abschnitts, durch die solche Algorithmen und Konstanten aus dem Modul außen sichtbar gemacht werden, die zur funktionalen Aufgabe des Moduls beitragen. Gewöhnlich exportieren nur Abschnitte der obersten Schicht. Es gibt zwei Konstrukte, welche die potentielle Sichtbarkeit aktuell begrenzen: Jeder Abschnitt muß außer den Algorithmen und Daten, die er selbst definiert, alle benötigten sichtbaren Objekte aus demselben Modul in einer "Invoke-Schnittstelle" (üiv) aufführen. Von anderen Moduln exportierte Objekte müssen explizit importiert werden, gewöhnlich leisten dies nur Abschnitte der untersten Schicht. Häufig ergibt sich eine Programmierung in drei Schichten (Bild 7-2): Die oberste Schicht enthält die Realisierung der modultypischen Aufgabe (Kommandoanalyse). Die zweite Schicht enthält die öffentlichen Funktionen von verschiedenen benötigten abstrakten Datentypen (symbols, lines) oder abstrakten Datenstrukturen (z.B. directory). Die dritte Schicht enthält neben den Abschnitten, die den Import regeln, diejenigen Algorithmen und Daten, die zur Realisierung der abstrakten Datentypen
bzw. Datenstrukturen notwendig sind. Im Ge-
gensatz zu anderen Sprachen ist nämlich CDL2 arm an Sprachmitteln zur lokalen Programmierung; z.B. sind Daten typlos und nur nach Variablen, Konstanten und Listen zu unterscheiden, so daß alle sonst impliziten Operationen z.B. zur Datengenerierung, Realisierung und zum Komponentenzugriff durch algorithmische Abstraktion in der untersten Schicht explizit realisiert werden müssen. Das fast völlige
CDL2
7.8.
209
Fehlen von implizit durch die Sprache vorgegebenen primitiven Operationen (z.B. jeglicher Arithmetik) wird durch einen "Makromechanismus" ausgeglichen, der es gestattet, Operationen einer unterliegenden Sprache zu benutzen. Dadurch werden CDL2-Programme nicht nur leicht portierbar, sondern sie lassen sich an vorhandene Zielsprachen und Rechnerarchitekturen gut adaptieren [Stahl78].
Editor
Bild 7-2:
Schichtenzerlegung eines Moduls
210
Modulkonzepte in Programmiersprachen
7.9.
7.9. Zusammenfassung Modulkonzepte in Programmiersprachen unterstützen die algorithmische Umsetzung der in einer Spezifikation formulierten modularen Zerlegung. Kriterien für die Beurteilung von Modulkonzepten in Programmiersprachen sind Typbegriff und Sichtbarkeitsregeln für ßezeichner. Die Möglichkeit der getrennten Übersetzung und die die Sprache auf dem Rechner unterstützenden Softwaresysteme müssen zur Beurteilung mit herangezogen werden. Assembler-Sprachen und klassische Sprachen wie FORTRAN und ALG0L60 unterstützen die Realisierung von Funktionen, deren Ausgabe ausschließlich von der Eingabe abhängt (Kategorie 1). Abstrakte Datenstrukturen lassen sich in Assembler-Sprachen und in COBOL realisieren. In PASCAL und seinen Nachfolgern ist die erheblich erweiterte Konstruktion und Benennung von Datentypen möglich. Die Sprachen SIMULA67, CLU, EUCLID, ALPHARD und LIS unterstützen die Realisierung abstrakter Datentypen durch Erweiterung des klassischen Typbegriffs für Datenobjekte. Die Sprachen LIS, ELAN und GREEN haben neben Datentypen einen Modulbegriff, der die Realisierung aller Modulkategorien unterstützt. ALPHARD gestattet die Formulierung von Spezifikation und deren Realisierung in einer Sprache und erlaubt damit die Verifikation der Realisierung gegenüber der Spezifikation. CDL2 unterstützt durch ein mehrstufiges Modulkonzept sowohl die Grobgliederung eines Softwaresystems als auch die Realisierung sämtlicher Modulkategorien durch Schichten und Abschnitte mit klar definierten Schnittstellen.
8.
Programmierung im Kleinen
211
8. Programmierung im Kleinen Im Kapitel 7 stand die übertragbarkeit der Modulspezifikation in eine Programmiersprache im Vordergrund. Modulare Zerlegung und Übertragung der Modul struktur wird häufig als Programmierung im Großen bezeichnet. Die Datenrealisierung abstrakter Typen und die Realisierung der Funktionen eines Moduls in der Implementierungssprache sind die danach noch zu bearbeitenden Teile der Implementierung, auch Programmierung im Kleinen genannt. Dabei kann es durchaus zweckmäßig und notwendig sein, weitere nicht in der Spezifikation aufgeführte Moduln zur besseren Programmstrukturierung in das Programm einzufügen. Die Programmierung im Kleinen beeinflußt also die endgültige modulare Struktur des Programmsystems. Die durch die Spezifikation gegebene Modulstruktur wird dabei nicht zerstört, sondern ergänzt. Diese Verbesserung der Gesamtstruktur ist von Rückgriffen aus der Implementierungsphase in die Entwurfsphase zu unterscheiden, die in Abschnitt 1.3.7 bereits erwähnt wurden. Seit es Programmierung gibt, haben Programmierer nach Regeln und Verfahren gearbeitet, die häufig aus Intuition und Erfahrung entstanden und selten explizit formuliert und begründet wurden. Erst Ende der 60iger Jahre unternahmen Dijkstra u.a. den Versuch, Regeln für eine systematische Programmierung aufzustellen und zu begründen. Dabei wurde der Begriff der "Strukturierten Programmierung" geprägt [Dijkstra69]. Seither ist die "strukturierte Programmierung" zu einem Schlagwort degeneriert, in unzähligen Publikationen zitiert, definiert und häufig mißverstanden. Häufig wurde "strukturierte Programmierung" mit "goto-freier Programmierung" f .Dijkstra68 ] gleichgesetzt. Vorliegende Definitionen betonen entweder nur einen einzigen Aspekt der Programmierung oder sie sind so allgemein, daß
212
Programmierung im Kleinen
8.
sich alle Aspekte der Softwareherstellung subsumieren lassen. Wir werden daher auf die Verwendung dieses Begriffes verzichten. Ausgangspunkt für die Entwicklung einer systematischen Programmierung im Kleinen ist die Frage nach der Struktur eines Programmes.für das ein Beweis seiner Korrektheit gegeben werden kann. Dabei ist unter Korrektheitsbeweis irgendeine Methode zu verstehen, welche es dem Programmierer gestattet, sich - mit formalen Mitteln oder informell - von der Richtigkeit seines Programms zu überzeugen. Wir wollen für die folgenden Überlegungen annehmen, daß sich die Korrektheit einer Folge von N Anweisungen N
S ^ S 2 ; ... ; S^
in
Schritten und die Korrektheit der vollständigen Alternative
if B then S., else S 2 end if
in zwei Schritten zeigen läßt. Für ein
Programm der Form if Bi then S n el se Si 2 end if; if B 2 then S 2 i eise S 2 2 end _if;
if Bfj then Sr, eise Sfj2 end if gibt es dann 2^ Programmpfade, woraus
N*2^
Schritte zum Nachweis
der Korrektheit folgen. Führt man nun abstrakte Anweisungen A-j derart in das Programm ein, daß
A-j =if B-j then S ^ eise S-j2 end if ,
so erhält man das Programm als Folge von Anweisungen A-ii A 2 ; ... ; A n auf einem höheren Abstraktionsniveau. Für das abstrakte Programm benötigt man N, für jede Anweisung A^ zwei Beweisschritte, insgesamt also
2*N+N=3*N
Schritte. Aus dieser einfachen Überlegung läßt
sich folgern, daß die Einführung von Abstraktion in ein Programm den Aufwand zum Nachweis der Korrektheit erheblich reduziert. Wenn wir im folgenden von Programmstruktur sprechen, dann soll dieser Begriff immer sowohl die Anweisungsstruktur als auch die Struktur der Datenbeschreibung umfassen, auf denen der Algorithmus operiert. Es erscheint plausibel, daß es nicht nur notwendig ist, ab-
8.
Programmierung im Kleinen
213
strakte Anweisungen, sondern auch abstrakte Datenbeschreibungen in gleicher Meise zu verwenden, um den Aufwand für Korrektheitsüberlegungen des gesamten Programms zu reduzieren. Auf der Basis der grundlegenden Erkenntnis, daß die Einführung von Abstraktion in ein Programm notwendig ist, um die Problemkomplexität zu bewältigen, sind Techniken abgeleitet worden, welche die Programmentwicklung im Kleinen unterstützen. Dazu gehört insbesondere die Methode der schrittweisen Verfeinerung [Wirth71]. Daneben erweist es sich als zweckmäßig, normierte Sprachkonstrukte zur Formulierung sowohl des Programmsteuerflusses als auch der Programmdaten zu verwenden. Zur Programmierung im Kleinen gehört neben der Wahl geeigneter Techniken und Methoden der Programmentwicklung auch die Auswahl für die Problemlösung geeigneter Algorithmen. Dabei ist der Aufwand des Algorithmus an Laufzeit und Speicherplatz zu berücksichtigen, auch "Komplexität des Algorithmus" genannt. Die "Komplexität eines Algorithmus" ist von der Problemkomplexität zu unterscheiden. Letztere umschreibt ungenau die Schwierigkeiten der intellektuellen Bewältigung des Problemlösungsvorganges. Erstere ist quantifizierbar und beeinflußt die Güte des Produkts. Besonders die Zeitkomplexität ist wesentlich. Zu untersuchen sind besonders Suchalgorithmen und Sortieralgorithmen. Die Ordnung der Komplexität, d.h. die Abhängigkeit der Anzahl der Speicherzugriffe von der Elementanzahl kann entscheidend für die Realisierbarkeit eines Projektes sein. Es ist z.B. eine wesentliche Frage, ob der gewählte Sortieralgorithmus die Ordnung n 2 oder n • logn
hat. Eine einigermaßen befriedigende Darstellung von Metho-
den der Komplexitätsanalyse von Algorithmen würde allerdings den Rahmen dieses Buches sprengen. Betrachtungen dieser Art spielen auch bei der Programmoptimierung eine Rolle, die in Kapitel 9 behandelt wird.
214
Programmierung im Kleinen
8.1.1.
8.1. Elemente der Programmierung im Kleinen Bei der folgenden Darstellung der Strukturierungselemente werden wir nicht mehr als notwendig auf existierende Programmiersprachen Bezug nehmen und nicht den Versuch unternehmen, diese daraufhin zu untersuchen, inwieweit sie die Programmierung im Kleinen durch geeignete Konstrukte unterstützer,.
8.1.1. Schrittweise Verfeinerung und Verbalisierung Die Idee der schrittweisen Verfeinerungstechnik besteht darin, Algorithmen und Daten zunächst auf einer aus der Spezifikation eines Moduls ableitbaren hohen Sprachebene durch Einführung von Anweisungen und Datenbeschreibungen zu formulieren. Diese Anweisungen und Datenbeschreibungen grenzen Teil aufgaben der Gesamtaufgabe ab
oder be-
nennen Problemdaten, ohne deren konkrete programmiersprachliche Formulierung anzugeben. Im nächsten Schritt werden die formulierten Teilaufgaben verfeinert, d.h. die Anweisungen und Datenbeschreibungen der ersten sprachlichen Ebene konkretisiert. Die Konkretisierung der zweiten Schicht kann wiederum Teilaufgaben enthalten, die in einer dritten Schicht verfeinert werden. Das Verfahren wird fortgesetzt, bis durch den letzten Verfeinerungsschritt die Ebene der Programmiersprache erreicht ist. Jeder Schritt der Konkretisierung drückt eine Reihe von Entwurfsentscheidungen aus. Je komplexer das Problem ist, d.h. je mehr Entwurfsentscheidungen nacheinander zu treffen sind, um das Problem in die Programmiersprache abzubilden, um so mehr Verfeinerungsschritte werden im allgemeinen benötigt. Die Anzahl der Verfeinerungsschritte ist auch davon abhängig, wieviele Entwurfsentscheidungen der Programmierer in einem Schritt überschaut oder zu überschauen glaubt.
8.1.1.
Schrittweise Verfeinerung
215
Durch die Formulierung von Verfeinerungsschritten wird die Programmentwicklungsstrategie und damit die Programmstruktur dokumentiert und die Änderbarkeit des Programms unterstützt. Da jede nicht konkretisierte Anweisung oder Datenbeschreibung einer Schicht durch eine algorithmische Konstruktion oder eine Datenkonstruktion der nächsten Schicht verfeinert wird, ist sie abstrakte Anweisung oder abstrakte Datenbeschreibung. Vereinfacht kann diese Methode als Übergang von der Abstraktion zur Konstruktion beschrieben werden, gelegentlich weniger einsichtig Top-down-Methode genannt. Die Einführung von abstrakten Anweisungen und Datenbeschreibungen vor ihrer Konkretisierung ist eng mit der Benennungsproblematik verknüpft. Jede sinnvoll die Bedeutung einer Konstruktion wiedergebende Benennung ist eine Abstraktion. Die intellektuelle Bewältigung einer Programmieraufgabe besteht nicht nur im Auffinden von klar voneinander abgrenzbaren Abstraktionsebenen, sondern ebenso in der Wahl geeigneter Namen für abstrakte Anweisungen und Daten. Dafür gibt es kaum allgemeine Regeln. Zwei Sünden bei der Namensgebung seien hier genannt: -
die in der Mathematik übliche Verwendung von einzelnen Buchstaben wie x, y, i, n, s
für Daten oder Anweisungen, wobei zur Unter-
scheidung noch Ziffern angehängt werden (z.B.il, s25); -
die Wahl von Namen, welche zwar eine kontextunabhängige Bedeutung haben, die aber in keiner klaren Relation zum Problem und zur Verfeinerungsstufe stehen; Beispiele hierfür sind Namen wie "Feld", "Schalter" für Daten, "wiederhole", "Aktionl" für Anweisungen.
Die Qual der Namenswahl wird verschlimmert durch Verwendung von Programmiersprachen, die durch Restriktionen der Bezeichnerlänge jede Abstraktion verhindern (z.B. BASIC) oder doch erschweren (z. Beisp. FORTRAN durch Beschränkung auf 6 oder 8 Zeichen). Der Zwang zur Verkürzung führt häufig zu sinnentstellenden Bezeichnern. Eine Kurzbezeichnung "stable" für "Symboltabelle" ist sicher verfehlt. Zur Demonstration der Verfeinerungstechnik soll hier das Sortieren
216
8.1.1.
Programmierung im Kleinen
von 2 n Elementen durch sequentielle Verschmelzung [Wirth74] angeführt werden: Aus einer unsortierten Folge sollen jeweils zwei Elemente zu einem (der Größe nach aufsteigend) sortierten Elementepaar zusammengefaßt werden; im nächsten Schritt sollen sortierte Paare zu sortierten Quadrupeln verschmolzen werden. Das Verfahren soll fortgesetzt werden, bis ein sortiertes Tupel der Länge 2 n entstanden ist. Die dargestellten Verfeinerungsschritte spiegeln die Programmentwicklung wider, nachdem folgende Entscheidungen gefällt wurden: Es wird ein Feld "Tabelle" der Länge 2 n + 1
angenommen, in dessen un-
terer Hälfte sich anfangs die unsortierte Elementfolge befinden soll. Die sortierten Elementepaare entstehen in der oberen Hälfte, die Quadrupel wieder in der unteren usw. Die sich nach jedem neuen Verschmelzungsschritt umkehrende Kopierrichtung wird durch eine boolesche Variable "aufwärts" angegeben. Ferner sei die Elementzahl als Zweierpotenz vorgegeben. Die kontextunabhängige Benennung "Tabelle" spiegelt die Tatsache wider, daß es sich um einen Programmausschnitt handelt. §!Z°bfqrmul_ierung1: Richtung aufwärts; Tupellänge := 1; repeat alle Tupel von Quelle nach Ziel; ändere Richtung; bestimme neue Tupellänge until Tupellänge = Elementzahl end repeat.
1
Die folgenden Beispiele werden in Anlehnung an die Syntax der Programmiersprache ELAN formuliert; decr und incr sind Dekrementierungs- bzw. Inkrementierungsoperatoren.
8.1.1.
Schrittweise Verfeinerung
Richtung aufwärts: aufwärts := true. alle Tupel von Quelle nach Ziel: ermittle erste Quellen- und Ziel-Tupel; while es sind noch Tupel zu untersuchen repeat zwei Tupel von Quelle nach Ziel; ermittle nächste Quellen- und Ziel-Tupel end repeat. ändere Richtung: aufwärts := not aufwärts. bestimme neue Tupellänge: Tupel länge := 2*Tupellänge.
Verfeine^ungsschicht: ermittle erste Quellen- und Ziel-Tupel: int var erste Quelle, zweite Quelle, Ziel; if aufwärts then erste Quelle := 1; zweite Quelle := erste Quelle + Tupellänge; Ziel := Elementzahl + 1 eise erste Quelle := Elementzahl + 1; zweite Quelle := erste Quelle + Tupellänge; Ziel := 1 end if; int var Anzahl nicht untersuchter Elemente; Anzahl nicht untersuchter Elemente := Elementzahl. es sind noch Tupel zu untersuchen: Anzahl nicht untersuchter Elemente > 0.
218
Programmierung im Kleinen
8.1.1.
zwei Tupel von Quelle nach Z i e l : i n t var Anzahl erstes Tupel, Anzahl zweites Tupel, Index erstes Tupel, Index zweites Tupel, Index Ziel ; Anzahl erstes Tupel := Tupellänge; Anzahl zweites Tupel := Tupellänge; Index erstes Tupel := erste Quelle; Index zweites Tupel := zweite Quelle; Index Ziel := Ziel ; repeat r f Tabelle [ I n d e x e r s t e s Tupel] < T a b e l l e [Index zweites TupeT] then Tabelle [Index Ziel ] := Tabel le [Index erstes Tupel ] ; Index erstes Tupel incr 1; Anzahl erstes Tupel deer 1; Index Ziel incr 1 el se Tabell e [Index Ziel ] := Tabel le [Index zweites Tupel ] ; Index zweites Tupel incr 1; Anzahl zweites Tupel deer 1; Index Ziel incr 1 end i f u n t i l Anzahl erstes Tupel = 0 or Anzahl zweites Tupel = 0 end repeat; kopiere Rest erstes Tupel; kopiere Rest zweites Tupel. ermittle nächste Quellen- und Ziel-Tupel: erste Quelle incr 2 * T u p e l l ä n g e ; zweite Quelle incr 2 * T u p e l l ä n g e ; Ziel incr 2 * T u p e l l ä n g e ; Anzahl nicht untersuchter Elemente decr 2 *Tupellänge.
8.1.1.
Schrittweise Verfeinerung
219
kopiere Rest erstes Tupel: while Anzahl erstes Tupel * 0 repeat Tabelle [Index Ziel] := Tabelle [Index erstes Tupel]; Index erstes Tupel incr 1; Anzahl erstes Tupel deer 1; Index Ziel incr 1 end repeat. kopiere Rest zweites Tupel: while Anzahl zweites Tupel * 0 repeat Tabelle [Index Ziel] := Tabelle [Index zweites Tupel]; Index zweites Tupel incr 1; Anzahl zweites Tupel deer 1; Index Ziel incr 1 end repeat.
Im Beispiel wird die primitive Ebene der Programmiersprache nach drei Verfeinerungsschritten erreicht. Die Konkretisierung abstrakter Anweisungen durch
Refinements
führt zu einer (bezüglich der ab-
strakten Anweisungen) baumförmigen Programmstruktur, d.h. jedes Refinement wird hier nur einmal benutzt. Konkretisierung heißt dabei sowohl die weitere Verfeinerung des Algorithmus durch Auswahl einer geeigneten Strategie (z.B. das Verschmelzen benachbarter Quelltupel) als auch die dazu parallele Einführung notwendiger Daten, deren Verwendung möglichst in einer Verfeinerungsschicht übersichtlich ablesbar sein sollte (wie z.B. "erste Quelle"), sofern es sich nicht um Problemdaten (wie "Tabelle") handelt. Bisher haben wir nur Refinements zur Beschreibung des Steuerflusses betrachtet. Es sollte klar sein, daß eine ähnliche Technik notwendig ist, um alle Objekte, auf denen ein (Teil )-Algorithmus arbeitet, adäquat zu benennen. Es ist dabei gleichgültig, ob das Datenobjekt in seiner programmiersprachlichen Realisierung zu einem Ausdruck, einer (Literal)-Konstanten oder zu einer komplizierten Datenstruktur ver-
220
Programmierung im Kleinen
8.1.2.
feinert werden kann. Verfeinerungen von Datenobjektbenennungen werden im allgemeinen durch Objekt-Deklarationen der Programmiersprache geleistet. So läßt die Formulierung if gelesenes Zeichen = Doppelpunkt then ... offen, ob bei einer Verfeinerung von "Doppelpunkt" die Verschlüsselung dieses Zeichens durch eine ganze Zahl oder die Realisierung durch einen Text (":" oder "..") gewählt wird. Die Entscheidung hängt vom Typ der Variablen "gelesenes Zeichen" ab. Die Semantik der Abstraktionselemente für die Programmierung im Kleinen ist im allgemeinen stark kontextabhängig. Das Refinement "zwei Tupel von Quelle nach Ziel" der zweiten Verfeinerungsstufe ist, für sich allein betrachtet, ohne Sinn. Seine Bedeutung wird erst klar durch Betrachtung der Applikationsumgebung der ersten Verfeinerungsschicht und durch die übrigen Refinements "ermittle erste Quellen- und Ziel-Tupel",
"es sind noch Tupel zu untersuchen" und
"ermittle nächste Quellen- und Ziel-Tupel" derselben Schicht. Die Kontextabhängigkeit der Refinements zeigt sich auch in der Tatsache, daß sie keine explizit gekennzeichneten Parameter haben. Diese kontextabhängige "schwache" Abstraktion der Verfeinerungstechnik ist zu unterscheiden von der "starken" Abstraktion durch Funktionen einer abstrakten Datenstruktur oder anderer Moduln. So hat z.B. der Operator "*" des Datentyps int weitgehend kontextunabhängige Bedeutung. Seine Wirkung auf den Applikationskontext ist durch explizite Parameter (Operanden) und Resultat beschreibbar. Operatoren und Prozeduren sind daher auch in erster Linie wegen ihrer Parametrisierbarkeit Strukturierungsmittel der Programmierung im Großen, Refinements dagegen ausschließlich Strukturierungsmittel der Programmierung im Kleinen. 8.1.2. Beschreibung des Steuerflusses Die Auswahl von normierten Sprachkonstrukten zur Beschreibung des Steuerflusses kann wieder damit begründet werden, die Korrektheit von Programmen sicherzustellen [Dijkstra72]. Wir sprechen im folgen-
8.1.2. den von
Beschreibung des Steuerflusses Steuerkonstrukten2.
221
Steuerkonstrukte sollen es dem Pro-
grammierer bei Entwurf und Nachvollzug der Entwurfsentscheidungen möglich machen, den Zustand der Programmausführung eines bestimmten Zeitpunkts einer Stelle des Programmtextes zuzuordnen. Die Möglichkeit einer solchen Zuordnung von dynamischem Programmzustand zu statischem Programmtext ist wesentlich für jede Art von formalem Korrektheitsbeweis. Es erscheint plausibel, daß die Abbildung von Zuständen der sequentiellen Programmausflihrung auf Stellen des sequentiellen Programmtextes dann leichter überschaubar ist, wenn man von den Steuerkonstrukten fordert, daß sie genau einen Eingang und genau einen Ausgang haben (siehe Abbildungen). Diese Einsicht spricht auch gegen die unbeschränkte Verwendung des Steuerkonstrukts des Sprunges von jeder Stelle des Programmtextes zu einer beliebigen anderen, weil sie die gewünschte Struktur des Programmtextes gerade zerstört. Die einfachste Form, den Steuerfluß durch Teilalgorithmen zu beschreiben, ist die Aneinanderreihung oder sequentielle Verknüpfung von endlich vielen Anweisungen S-j zu einer neuen Anweisung.
i L
I I
Für das Steuerkonstrukt Auswahl sind drei unterschiedliche Formen gebräuchlich (B Bedingung, S-j Anweisungen, I Ausdruck, der einen ganzzahligen Wert liefert):
2
Wir benutzen den Begriff "Steuerkonstrukt", da er die Bedeutung des englischen "control structure" besser wiedergibt als das häufig gebrauchte "Kontrollstruktur".
222
8.1.2.
Programmierung im Kleinen
-
die einseitige Alternative vf B then S end if
-
die vollständige Alternative _if B then Si else S 2 end if r
1
die Mehrfach-Selektion, nach dem Wert des Ausdruckes I select I jiji case 1 : Si case 2 : S 2 ... otherwise S n end select
8.1.2.
Beschreibung des Steuerflusses
223
Die allgemeine Form der Miederholung kann aus endlich vielen Anweisungen und Terminationsbedingungen folgendermaßen aufgebaut werden: repeat S-, until B-, occurred S2 until B 2 occurred
Sn'
unti1 B n occurred Sn + i end repeat i
1
Sie beschreibt die wiederholte Ausführung der Anweisungsfolge S ; S ; ... ; S n + 1 solange, bis eine der Terminationsbedingungen Bi, ... , B n zutrifft. In den meisten Fällen tritt nur eine Terminationsbedingung auf. repeat St until Bi occurred S 2 end repeat Wenn jetzt sowohl S-i als auch S 2 nicht-leere Anweisungen sind, spricht man vom Fall des "Break". Ist St nicht vorhanden, ergibt sich der Fall der "abweisenden Schleife" (prechecked loop), der häufig durch Negation der Terminationsbedingung notiert wird: while B repeat S end repeat
224
8.1.2.
Programmierung im Kleinen i
r
3
S
Ist S 2 nicht vorhanden, ergibt sich der Fall der "nichtabweisenden Schleife" (postchecked loop), der häufig in der Form repeat S until B end repeat notiert wird. In sehr vielen Fällen wird durch eine Wiederholung die Bearbeitung einer Folge von Datenelementen nach folgendem Muster beschrieben: Vorbereitung zur Ermittlung der Elemente;
} Initialisierung
repeat versuche nächstes Element zu ermitteln
} Inkrementierung
until es gibt keine Elemente mehr occurred bearbeite Element end repeat. Wenn die Algorithmen zur Initialisierung und Berechnung des ersten Elementes sich wesentlich von dem Algorithmus zur Inkrementierung unterscheiden, wie dies beispielsweise in Abschnitt 8.1.1 der Fall ist, dann hat man folgendes häufiges Wiederholungsmuster: Ermittle erstes Element; while es gibt noch Elemente repeat bearbeite Element; versuche nächstes Element zu ermitteln end repeat.
8.1.2.
Beschreibung des Steuerflusses
225
Ein Spezialfall beider Wiederholungsformen ist die sogenannte "Zählschleife", bei der Initialisierung bzw. Inkrementierung die Wertbelegung bzw. Wertänderung einer die Wiederholung kontrollierenden Zähl variablen darstellen. Die Bedingung "es gibt noch Elemente" des zweiten Musters hat in diesen Fällen die Form "Zählvariable überschreitet noch nicht Endwert". Für int-Zählvariablen gibt es die Wiederholungsform for Zählvariable from Anfangswert by Inkrementwert to Endwert repeat bearbeite Element end repeat Wir haben bereits erwähnt, daß die unbeschränkte Verwendung der Sprunganweisung aus Gründen der Programmstrukturierung nicht wünschenswert ist. Zunächst muß festgestellt werden, daß sich jedes sequentielle Programm mit Hilfe der bisher angegebenen Steuerkonstrukte formulieren läßt [Böhm66]. Ohne Sprunganweisungen formulierte Programme verwenden häufig schwer verständliche Schaltervariablen. In solchen Fällen können Sprünge ein geeigneteres Ausdrucksmittel sein. Um zu einer kontrollierten Verwendung der Sprunganweisung zu gelangen, wollen wir deshalb versuchen, Fälle zu isolieren, welche deren Verwendung nahelegen [Zahn74]: Fall 1: Die "nichtreguläre" Termination eines Teilalgorithmus. Dieser Fall sei durch folgenden Teilalgorithmus eines Textformatierers verdeutlicht [Knuth74]: Ein fortlaufender Text soll zeichenweise gelesen und als eine Folge von Zeilen ausgegeben werden, wobei gewisse Tabulatorpositionen innerhalb der Zeile gesetzt sind. Das Zeichen "/" in der Eingabe steuert den Tabulatorvorschub, die Zeichenfolge "//"den Zeilenwechsel; nach einem Punkt soll ein Leerzeichen ausgegeben werden. Der Algorithmus soll ohne Vorgriff arbeiten. Wir formulieren zwei Refinements:
226
Programmierung im Kleinen
8.1.2.
nächster Verarbeitungsschritt: lies Zeichen; if Schrägstrich then tabuliere oder wechsle Zeile end if; schreibe Zeichen; rf Punkt then schreibe Leerzeichen end if. tabuliere oder wechsle Zeile: lies Zeichen; if Schrägstrich then wechsle Zeile; goto Aktion im Anschluß an nächster Verarbeitungsschritt eise tabuliere end if. Der Teilalgorithmus "tabuliere oder wechsle Zeile" hat jetzt zwei Ausgänge: Der reguläre Ausgang beendet nur den Algorithmus selbst; der durch die Sprunganweisung angegebene Ausgang beendet auch den eine Abstraktionsstufe höher stehenden Algorithmus "nächster Verarbeitungsschritt". Um dies mit Hilfe der Sprunganweisung zu realisieren, muß die Folgeoperation markiert (und benannt) werden, was bei unserem Programmausschnitt schwer möglich ist. Dies rechtfertigt die Ersetzung der Sprunganweisung durch das Terminationskonstrukt leave nächster Verarbeitungsschritt. Dieses Konstrukt kann also immer benutzt werden, um die irreguläre Termination eines Teilalgorithmus direkt oder auch indirekt durch Verfeinerungen niedrigerer Ebenen zu formulieren (irreguläres Verlassen einer Schachtelung). Das Terminationskonstrukt wird auch in der Form leave Refinementname with Wert verwendet (z.B. in BLISS [Wulf71] oder ELAN), wenn das Refinement die Berechnung eines Wertes erfordert.
8.1.2.
Beschreibung des Steuerflusses
227
Terminationsbedingungen für Teilalgorithmen lassen sich allgemeiner mit Hilfe des sogenannten Zahn-Konstruktes
[Zahn74] beschreiben, das
auch in der Sprache BALG [Goos76a] unter dem Namen "Situationsklausel" vorhanden ist. Das Zahn-Konstrukt erlaubt eine gleichwertige Formulierung (d.h. Fall Unterscheidung) aller Terminationsbedingungen eines Teilalgorithmus durch Benennung der die Termination
bewirkenden
Situation (also ohne Verwendung des Algorithmus-Namens).
Wesentlich
ist, daß durch das Zahn-Konstrukt die zu einer signalisierten Situation gehörenden Folgeoperationen an die Applikation des Algorithmus syntaktisch angeklammert werden, der diese Situationen
hervorruft.
Dadurch wird das Wiederzusammenführen von verschiedenen Ausgängen eines Algorithmus an seiner Applikationsstelle erreicht. Definition eines Teilalgorithmus: suche Element: repeat ...; Signal Element gefunden until
Tabellenende
end repeat; Signal Element nicht gefunden.
Appiikation eines Teilalgorithmus: (suche Element on Element gefunden: verarbeite Element on Element nicht gefunden: trage Element in Tabelle ein
)
228
Programmierung im Kleinen
8.1.3.
Fall 2: Die Termination von Algorithmen bei Auftreten von Fehlersituationen, wobei die Fehlersituation im Kontext der Konkretisierung des Algorithmus (d.h. im Kontext seiner Definition) nicht sinnvoll behandelt werden kann. Fehlersituationen oder allgemeiner: Ausnahmesituationen (exceptions) führen zur Termination der verursachenden Operation und bei Verwendung des Sprunges zum Übergang zu den die Ausnahmesituation behandelnden Routinen (handlers) einer anderen Abstraktionsstufe. Dabei wird im allgemeinen nicht nur eine schwache Abstraktionsebene verlassen, sondern sogar ein Modul, d.h. eine starke Abstraktionsebene der globalen Zerlegungsstruktur. Die systematische Behandlung von Ausnahmesituationen ist Gegenstand der Forschung auf dem Gebiet der Definition und Implementierung moderner algorithmischer Sprachen [Goodenough75]. 8.1.3. Datenkonstrukte Wir haben in 8.1.1 bereits gesagt, daß die Konkretisierung einer Objekt-Benennung durch eine Deklaration erfolgt, welche die Eigenschaften des Objektes festlegt. Für die Objektkonkretisierung ist wieder der Typ eines Objektes von entscheidender Bedeutung. Die Einführung eines abstrakten Datentyps (s. Kapitel 5) in ein Programm bewirkt, daß seine Benutzung außerhalb des definierenden Moduls sich nicht von der Benutzung anderer primitiver Typen einschließlich ihrer Zugriffsfunktionen unterscheidet, über die Konkretisierung abstrakter Typen ist bei ihrer Benutzung nichts bekannt. Anders verhält es sich nun bei der Programmierung im Kleinen, deren Aufgabe es gerade ist, die konkrete Typrealisierung zu formulieren. Zur Bewältigung dieser Aufgabe benötigt man eine dem vorangehenden Abschnitt vergleichbare Zusammenstellung verschiedenartiger Typkonstruktoren, mit deren Hilfe man aus primitiven Typen neue Typen komponieren und benennen kann. Wir geben im folgenden eine Reihe von Konstruktionsmechanismen an, die mengentheoretischen Konstruktionen entsprechen [Hoare72]. Dies
8.1.3.
Datenkonstrukte
229
ist verständlich, da mit einem Objekttyp stets eine Menge von Werten festgelegt wird, die ein Objekt dieses Typs annehmen kann. Mit den Typkonstruktoren sind aber stets auch Zugriffsfunktionen gekoppelt. Das direkte Produkt (kartesische Produkt) von n Typen t-j führt zu einem Typ, dessen Elemente geordnete n-Tupel sind. Die i-te Komponente eines n-Tupels ist ein Element aus ti. Das direkte Produkt wird in algorithmischen Sprachen als Record (PASCAL) oder Struktur (ALG0L68 oder ELAN) realisiert. Beispielsweise ist der Typ datum, definiert durch type datum = struct (tag monatstag, monat monatsname, jähr jahreszahl) das direkte Produkt aus den Typen tag, monat und jähr. Der Zugriff auf Komponenten erfolgt durch implizite Zugriffsfunktionen mit den zur Typkomposition gehörenden Selektoren, die häufig auf das konkrete Objekt statt auf den Typbezeichner bezogen werden. Die Deklaration "datum var geburtsdatum" geht der Selektion "geburtsdatum.jahreszahl" voraus. Dem direkten Produkt entspricht das Steuerkonstrukt der Aneinanderreihung. Ein Element aus der
disjunkten Vereinigung
von Typen t-j ist Ele-
ment aus genau.einer der den Typen t-j entsprechenden Wertemengen. Diese Strukturart ist am klarsten in ALG0L68 durch die Vereinigungsmodes (Mode entspricht Typ) verwirklicht: mode mensch = union (mann, frau). Die für Vereinigungstypen in ALG0L68 vorgegebene Selektionsanweisung erlaubt eine auf das konkrete Objekt bezogene Fallunterscheidung: nach der Deklaration "mensch bürger" können durch case bürger jm mann :... frau :... esac der Typ des aktuellen Wertes von Bürger abgefragt und entsprechende Aktionen veranlaßt werden. Der disjunkten Vereinigung entspricht das Steuerkonstrukt der Auswahl. Die Sequenz ist die null- oder mehrfache Aneinanderreihung von Elementen ein- und desselben Typs. Eine Sequenz ist prinzipiell von
230
Programmierung im Kleinen
8.1.3.
beliebiger Mächtigkeit. In algorithmischen Sprachen mit primitivem Typ char
ist die Realisierung der Typen text oder string als Se-
quenz vorstellbar, ebenso wie der Datentyp file. In den meisten algorithmischen Sprachen ist die Sequenz nicht als Typkonstruktor explizit zugelassen; die Typen string und file sind meistens vorgegebene Typen und somit samt Zugriffsoperationen primitiv. Sequenzen sind allerdings durch rekursive Datentypen realisierbar. Als primitive Zugriffsoperationen, die mit diesem Konstruktor gekoppelt sind, sind mindestens die Auswahl des ersten Elementes (first), die Auswahl der Restsequenz ohne erstes Element (tail) und die Verknüpfung von Sequenzen (concat) notwendig, ferner die Abfrage auf die leere Sequenz. Der Sequenz entspricht das Steuerkonstrukt der Wiederholung. Rekursive Datentypen können zur Darstellung von Datenobjekten verwendet werden, deren Komponenten dieselbe Struktur haben können wie das gesamte Objekt; Beispiele sind Listen und Bäume. Konstruktionsmechanismen sind z.B. in ALG0L68, SIMULA67 und CLU vorhanden. In ALG0L68 kann der Typ nicht-leerer Binärbäume mit Hilfe des Referenzkonstruktors und der Strukturbildung folgendermaßen repräsentiert werden: mode binärbaum = struct (int value, ref binärbaum links, rechts). Zugriffsoperationen auf Objekte rekursiver Datentypen werden häufig mit Hilfe rekursiver Prozeduren und der impliziten Zugriffsoperationen für das direkte Produkt formuliert. Durch Abbildungen von endlichen Definitionsmengen in die Menge eines vorgegebenen Typs können ebenfalls neue Typen gebildet werden. So ist durch alle Abbildungen einer endlichen Indexmenge (z.B. eines Abschnittes von int-Werten) in den Typ real der Typ Vektor, durch die Abbildungen des direkten Produktes zweier Indexmengen nach real der Typ Matrix darstellbar. In den meisten Programmiersprachen werden diese speziellen Abbildungen durch das vorgegebene Konstrukt array realisiert. Dazu gibt es zwei auf ein Objekt dieses Typs (also eine Abbildung) bezogene implizite Operationen, die Indizierung (d.h. die Auswahl des zu einem Index gehörenden Bildelementes) und
8.1.3.
Datenkonstrukte
231
der Übergang von einem Objektwert zu einem anderen durch "komponentenweise Zuweisung". In PASCAL gibt es noch weitere spezielle Typkonstruktoren. Sehr bequem ist die Typbildung durch Aufzählung, z.B. type color = (blue, green, yellow, red). Man kann die Aufzählung als Spezialfall der disjunkten Vereinigung der einelementigen Typen blue, green, yellow und red auffassen, deren Repräsentation durch die Implementierung gegeben ist. Gerade daraus entstehen Probleme. Wenn man einen anderen Typ type dod language = (green, red, blue, yellow) vereinbart, so ist mit dem Komponentenbezeichner green im zweiten Fall sehr wahrscheinlich eine andere Repräsentation verknüpft (angedeutet durch die Anordnung) und damit seine Verwendung im Programmtext nicht eindeutig. Wichtig ist auch die Typkonstruktion durch Teilmengenbildung, z.B. "type index = 1..100", ein Spezialfall der Aufzählung für Typmengen, auf denen eine Ordnung der Elemente definiert ist. Schließlich ist die Potenzmengenbildung zu erwähnen, z.B. durch "type colorselection = set of color". Dieser Typ beschreibt die Menge aller Teilmengen des Typs color, wobei aber nur endliche Basistypen zulässig sind. Die Potenzmenge kann (in der Implementierung) repräsentiert werden als Aufzählung aller Abbildungen der Basismenge in den zwei elementigen Typ boolean. Bei der Konstruktion von rekursiven Datentypen haben wir zur Darstellung der Rekursion den Referenzkonstruktor verwendet. Der Begriff der Referenz ist als Abstraktion der Relation zwischen Adresse eines Speicherwortes und Wert-Inhalt dieses Wortes entstanden. Weil man beim Entwurf algorithmischer Sprachen den Mechanismus der "Obergabe" von aktuellen an formale Parameter nicht unabhängig von einer möglichen Implementierung beschreiben wollte oder konnte, führte man die Referenz als Vehikel ein: die Referenz des aktuellen Parameters wird dem formalen Parameter zugewiesen (call by reference). Damit sollte lediglich implementierungstechnisch der Sachverhalt beschrieben werden, daß formaler und aktueller Parameter dasselbe Objekt
232
Programmierung im Kleinen
8.1.3.
bezeichnen. Nunmehr erschien die Einführung von Referenzen als Objekte der Programmierung in algorithmischen Sprachen (z. Beispiel in SIMULA67, PL/I und ALG0L68) völlig logisch [Wirth74], Dies zeigt sich am klarsten in der Einführung der Referenz als eigenständigen Typ und in der Ausdehnung der Zuweisungsoperation auf Referenzobjekte. In algorithmischen Sprachen mit Referenztypen können beliebige Zeigervariablen oder Pointer definiert werden. Damit aber enstehen Probleme, die gewisse Ähnlichkeiten zum Problem der unbeschränkten Verwendung der Sprunganweisung haben ([Hoare73], [Hoare75]): Referenzen werden zur Darstellung beliebiger Relationen zwischen Datenobjekten mißbraucht und Programme sind wegen der entstehenden unübersichtlichen "Spaghetti-Programmierung" nicht ohne weiteres als korrekt nachweisbar. In PL/I ist es z.B. möglich, daß ein und derselbe Pointer auf Objekte beliebigen Typs zeigen kann. Da man aber mit Referenzen (anders als mit Adressen auf der konkreten Maschinenebene) in algorithmischen Sprachen nie "rechnet", sondern allenfalls mit den referierten Objekten, ist der Nachweis der korrekten Verwendung von Datentypen durch Analyse des Programmtextes bei derart nicht "qualifizierten" Referenzen unmöglich. Beim Entwurf von ALG0L68 ist dieser Fehler vermieden worden durch den auf genau einen Basistyp bezogenen Referenztyp, z.B. ref int, ref mensch. Allerdings sind in ALG0L68 als Folge der Anwendung des Prinzips der "beliebigen Kombinierbarkeit unabhängiger Konzepte" auch Referenztypen höherer Stufe (z.B. ref ref struct (ref int Vorgänger, nachfolger)) möglich. Außerdem mißbraucht ALG0L68 das Referenzkonzept zur Erklärung des Unterschiedes zwischen Konstanten und Variablen durch Unterscheidung zwischen Nichtreferenz- und Referenztypen. Beide Ausdehnungen der Anwendung des Referenzprinzips, Pointer höherer Ordnung und Variableneigenschaft als Pointereigenschaft sind entweder unnötig oder schwer verständlich und verwirrend. In Sprachen mit impliziten oder expliziten Operationen zur Speicherbereichszuweisung oder -freigäbe
kön-
nen zudem "hängende Referenzen" (dangling references) entstehen, nämlich Pointer auf nicht existierende Objekte. Das Auftreten ist im allgemeinen nicht laufzeitunabhängig überprüfbar und damit die
8.1.3.
Datenkonstrukte
233
Korrektheit eines Programmes nicht nachweisbar, das syntaktisch richtig ist. Eine befriedigende Lösung, nämlich eine, ähnlich der Sprunganweisung, eingeschränkte Verwendung von Referenzen ist bisher nicht bekannt. Doch führt die Klassifizierung der bisherigen Verwendung der Referenzen weiter: -
Darstellung der Beziehungen zwischen Bezeichner und Objekt;
-
Darstellung der zur Strukturbeschreibung von Objekten notwendigen definierenden Relationen, z.B. der Nachfolger- oder Vorgänger-Relationen bei der Darstellung von Listen;
-
Darstellung von beliebigen anderen Relationen zwischen Objekten verschiedenen oder gleichen Typs, die keine definierenden Relationen sind.
Beziehungen zwischen Bezeichner und Objekt sind ohne Verwendung von Referenzen darstellbar. Es ist wahrscheinlich möglich, auf Pointer und damit auf Referenztypen ganz zu verzichten; denn Operationen mit Pointers lassen sich durch Operationen zur Änderung der Relationen zwischen Bezeichnern und Objekten ersetzen. Sinnvoll und unverzichtbar scheint die Verwendung des Referenzkonzeptes bei der Darstellung definierender Relationen von Datentypen innerhalb von Typkonstruktoren zu sein, d.h. nur innerhalb eines Moduls. Zur Darstellung anderer Relationen sind algorithmische Konstruktionen wie Prozeduren und Operatoren sinnvoll
[Berry77J.
Bei der Charakterisierung konkreter Eigenschaften von Datenobjekten haben wir bisher den durch Konstruktoren darstellbaren Typ genannt. Andere Eigenschaften von Objekten können getrennt vom Typ bei Deklaration unveränderbar oder auch dynamisch festgelegt werden. Dazu gehört die Festlegung der "Zugriffseigenschaft" (access), d.h. die Variableneigenschaft
oder Konstanteneigenschaft (manifest oder nur
auf gewisse Programmteile bezogen) von Objekten.
234
8.2.
Programmierung im Kleinen
8.1.4. Prozeduren Prozeduren, wenngleich bisher als notwendige Strukturierungsmittel der Programmierung im Großen verwendet, haben neben den Refinements auch ihren Platz in der Programmierung im Kleinen. Modul-lokale, d.h. nicht exportierte Prozeduren können neben den Refinements wichtige Abstraktionselemente sein aus mehreren Gründen: -
zur Realisierung von Hilfsfunktionen auf der Ebene der Modulprogrammierung, z.B. zur Realisierung der "versteckten
Funktionen"
der Spezifikation des Datentyps fstack (Seite 159); -
zur Realisierung von Teilalgorithmen, die mehr als einmal in einem Modul appliziert werden;
-
zur Realisierung von Teilalgorithmen, für die explizite Parametrisierung sinnvoll erscheint, weil ihr algorithmischer Inhalt klar von der Umgebung absetzbar ist (meistens auf hoher Ebene der Verfeinerung);
-
zur Realisierung von rekursiv formulierten Algorithmen, wie z.B. von Backtrack-Algorithmen. Häufig werden in diesem Falle nicht alle Parameter des Algorithmus explizit von Aufruf zu Aufruf durchgereicht.
8.2. Graphische Darstellungen der Programmstruktur Die bis heute am weitesten verbreiteten Darstellungsmethoden für die Programmentwicklung im Kleinen sind Flußdiagramme und Struktogramme. Flußdiagramme nach DIN 66001 gestatten es, einen Algorithmus durch die graphischen Elemente Rechteck, Raute und Pfeil für Anweisung, Bedingungsabfrage bzw. Steuerfluß darzustellen. Daneben gibt es spezielle Rechtecke, um die Anwendung von Teilalgorithmen
(Verfeine-
8.2.
Graphische Hilfsmittel
235
rungen oder Prozeduren) abzubilden, und Kreise, um Verbindungen zwischen Diagrammen herzustellen. Die zunächst natürlich erscheinende Darstellungsform hat einige entscheidende Nachteile: -
der Steuerfluß kann beliebig kompliziert formuliert werden, weil die Verwendung der Pfeile keinerlei Einschränkungen unterliegt und alle normierten Steuerkonstrukte für Auswahl und Wiederholung in primitivere Bestandteile zerlegt werden müssen; dadurch wird der Steuerfluß unübersichtlich und schwer verifizierbar;
-
Datenstrukturen und zugehörige Operationen können nicht beschrieben werden, da es keine deklarativen Darstellungsmittel gibt. Daten können entweder ungenau umgangssprachlich verwendet oder durch Einzelheiten ihrer Konkretisierung überspezifiziert werden (zum Teil Darstellung auf Maschinenebene);
-
als Kästcheninschriften können Bezeichnungen aus unterschiedlichen Abstraktionsniveaus gewählt werden;
-
das unterste Abstraktionsniveau ist (im Gegensatz zu algorithmischen Sprachen) nicht festgelegt und damit sind die Darstellungsmittel nicht vollständig definiert.
Flußdiagramme sind zur Darstellung einfacher algorithmischer Abläufe sehr gut geeignet, wenn man sich ihrer Grenzen bewußt ist. Als einziges Hilfsmittel zur Darstellung der Programmstruktur sind sie nur bei sehr diszipliniertem Gebrauch bedingt geeignet. Einen gewissen Fortschritt bringen die Struktogramme nach Nassi und Shneiderman [Nassi73]. Es gibt graphische Darstellungsmittel für Aneinanderreihung, Auswahl und Wiederholung. Durch Verzicht auf Pfeile zur Darstellung des Steuerflusses ist eine erheblich übersichtlichere Algorithmus-Darstellung möglich als bei Flußdiagrammen. Der Zwang zur Aufteilung des Papierbogens zwingt zur Aufteilung des Algorithmus in Teilalgorithmen. Insoweit unterstützen Struktogramme Verbalisierung und Verfeinerungstechnik. Genau wie Flußdiagramme sind sie wegen fehlender primitiver Darstellungsmittel
unvollständig, und
ebensowenig gestatten sie die Darstellung von Daten.
236
Programmierung im Kleinen
Element in Tabelle suchen
B i l d 8-1:
Flußdiagramm für Tabellensuche
8.2.
8.3.
Algorithm!sehe Entwurfssprachen
237
Element in Tabelle suchen Tabelle [Länge + 1] := Element Zähler := 1 Element in Tabelle suchen Zähler > Länge T
^
^
^ ^ ^ ^ ^
Tabelle [Zähler] * Element
F
Länge incr 1 Häufigkeit [Zähler] := 1
Bild 8-2:
Häufigkeit [Zähler] incr 1
Zähler incr 1
Struktogramm für Tabellensuche
8.3. Programmentwicklung und algorithmische Sprachen Die am meisten Erfolg versprechenden Darstellungsmittel
für die Pro-
grammentwicklung im Kleinen sind geeignete algorithmische Sprachen. Die Programmentwicklung unterstützende Konstrukte finden sich schon in klassischen Programmiersprachen. Hier sei die Möglichkeit erwähnt, in einem COBOL-Programm Abschnitte und Paragraphen zu benennen und sie durch die PERFORM-Anweisung zu applizieren. Damit läßt sich die Verfeinerungstechnik direkt in der Sprache darstellen. Im allgemeinen unterstützen aber selbst moderne Programmiersprachen die dargestellte Programmiermethodik nicht optimal. Zwar gibt es Sprachen mit reichhaltigen Steuerkonstrukten und Datenkonstruktoren, jedoch kaum solche, in denen es möglich ist, die Verfeinerungstechnik im Programmtext zu verwirklichen. Es bleibt in diesem Falle dem Programmierer nichts weiter übrig, als den algorithmischen Entwurf mechanisch in den Programmtext in seiner Programmiersprache umzusetzen oder durch Generatoren umsetzen zu lassen. In diesem Falle sind Algorithmenentwurf und Programmtext zwei verschiedene Dokumente, deren Verwaltung bei Programmänderungen vom Programmierer ein hohes
238
Programmierung im Kleinen
8.4.
Maß an Disziplin verlangen. Es ist daher naheliegend, Programmiersprachen zu entwickeln, in welchen der Algorithmenentwurf mit dem Programmtext zusammenfällt. Ein erster Schritt in diese Richtung ist mit der Entwicklung von ELAN getan worden. Die in Abschnitt 8.1.1 angegebene Programmentwicklung ist, abgesehen von marginalen Abweichungen, ein ELAN-Programm. In der Praxis der Software-Entwicklung kann man jedoch nicht auf die ideale Programmiersprache warten. Um bessere Programmentwicklungsmethoden zu unterstützen, bedarf es geeigneter algorithmischer Entwurfssprachen mit einfacher Semantik und klarer Syntax, in denen der zu entwicklende Algorithmus sauber formulierbar ist. Eine solche Sprache braucht keine Programmiersprache zu sein; wohl aber müssen klare Regeln für die Umsetzung der Konstrukte der Entwurfssprache in eine vorhandene Programmiersprache angegeben werden.
8.4. Programmverifikation Wir haben bei der Programmierung im Kleinen schon häufiger den Begriff der Korrektheit oder des Korrektheitsbeweises benutzt. Dabei waren Plausibilitätsbetrachtungen genau so gemeint wie formale Methoden, die es gestatten,eine algorithmische Lösung gegenüber einer vorgegebenen Anforderung - der Spezifkation - als richtig nachzuweisen. Im engeren Sinne wollen wir im folgenden unter Verifikation nur weitgehend formale Methoden des Korrektheitsnachweises verstehen. Allerdings kann keine informelle oder formale Methode die Richtigkeit der Spezifikation garantieren, genauso wenig wie ein mathematischer Beweis die Richtigkeit der bewiesenen Aussage zeigen kann, wenn das mathematische Modell die Wirklichkeit nicht adäquat wiedergibt. Ausserdem ist zu fragen, ob der formale Kalkül überhaupt ausreicht, um die bereits modellierte Anforderung entsprechend zu beschreiben. Auch innerhalb eines formalen Kalküls selbst können Fehler gemacht werden. Man kann daraus schließen, daß ein formaler Korrektheitsbe-
8.4.1.
Zusicherungen
239
weis die richtige Aufgabenlösung nicht sicherstellen kann. Trotzdem haben aber formale Methoden den Vorteil, fehlerhafte Schlüsse zu minimieren. Und sie definieren eine Sprache, welche die Kommunikation über Beweisverfahren erst ermöglicht. Der Gegenstand der im folgenden dargestellten Verifikationsmethoden ist die algorithmische Lösung der durch eine formale Spezifikation gegebenen Aufgabe als Programmtext in einer algorithmischen Sprache. Die algorithmische Sprache definiert ein abstraktes Modell der Realität durch Objekte, Operationen und Steuerkonstrukte. Voraussetzung für jede Programmverifikation ist die Abbildbarkeit von (dynamischen) Programmzuständen auf (statisch formulierbare) Aussagen über abstrakte Objekte des Algorithmus, welche genau definierten Stellen im Programmtext zugeordnet werden können. Voraussetzung ist ferner eine formale Beschreibung der Objekteigenschaften und deren Operationen mit Hilfe axiomatischer Grundannahmen. Aussagen über Programmzustände und Objekteigenschaften werden mit Hilfe der Prädikatenlogik erster und höherer Stufe konstruiert. Dabei wird die Wirkung von Steuerkonstrukten durch Regeln angegeben, welche die Transformation einer Aussage in eine andere definieren.
8.4.1. Verifikation mit Hilfe von Zusicherungen Zusicherungen (assertions) sind als wahr postulierte Aussagen über den Programmzustand. Sie werden im Programmtext an bestimmten Stellen eingefügt. Gelingt es, die Voraussetzungen, unter denen ein Algorithmus arbeiten soll, als Zusicherung V (Eingangszusicherung oder Vorbedingung) und das Resultat des Algorithmus als Zusicherung R (Ausgangszusicherung oder Nachbedingung) anzugeben, so wird ein Programm als korrekt angesehen, wenn sich mit formalen Umformungsregeln auf dem Programmtext P die Aussage V in die Aussage R überführen läßt. Dieser Sachverhalt wird für sequentielle Programmnotation mit {V} P {R}
und in graphischer Form durch V —
angegeben.
[T] —
R
240
8.4.1.
Programmierung im Kleinen
Die Umformungsregeln in der in [Floyd67] und [Hoare69] angegebenen Form sagen allerdings nichts darüber aus, ob ein durch P notiertes Programm bei Ausführung terminiert. Deswegen drückt der Mechanismus lediglich aus, daß aus V niemals das falsche Resultat durch das Programm produziert werden kann; ob überhaupt ein Resultat unter der Voraussetzung V produziert wird, bleibt offen. Der Nachweis der Termination bedarf getrennter Betrachtung. Deshalb sichert der Kalkül nur die "partielle Korrektheit". Durch Formulierung des Textes P mit Hilfe von Steuerkonstrukten mit einem Eingang und einem Ausgang kann der Algorithmus in Teilalgorithmen zerlegt werden:
Für die Teilalgorithmen S-j werden wieder Zusicherungen formuliert; damit ist die Transformation von V nach R durch P auf die Transformation von V.,- nach R^ durch S-j rückführbar unter der Voraussetzung, daß gewisse allgemeine Schlußregeln gelten, welche die Transitivität der Beweisschritte sicherstellen. Die Schlußregeln 3 {V}
(l)
(2)
s
{R}, Q = > V {Q} S {R}
und
{V} S {R}, R = > Q {V} S {Q}
sichern, daß V gültige Vorbedingung einer Anweisung bleibt, wenn aus V eine gültige, aber schwächere Vorbedingung derselben Anweisung ableitbar ist, und daß R gültige Nachbedingung einer Anweisung bleibt,
A
A wenn A-i und A 2 und ... A n gelten, dann gilt auch A.
8.4.1.
Zusicherungen
241
wenn eine stärkere gültige Nachbedingung derselben Anweisung R zur Folge hat. Es ist zu sehen, daß unkontrollierte Sprunganweisungen die transitive Beweisstruktur zerstören, weil aus der Nachbedingung einer Anweisung Si nicht ohne zusätzliche - aus einem anderen Anweisungskontext stammende - Annahmen die Vorbedingung der nächsten Anweisung S 2 zu folgern ist, wenn S 2 Sprungziel ist.
— - L i d —• ° —-L§aJ —* Bei der Aufstellung der Transformationsregeln für die einzelnen Anweisungsschemata ist eine wichtige Einschränkung für die Anwendbarkeit des Kalküls zu beachten: die strikte Unterscheidung zwischen Anweisungen (Zuweisung, Aneinanderreihung, Auswahl, Wiederholung, Prozedur) und Ausdrücken. Der Effekt von Anweisungen in der Zeit wird auf Transformationen von Eingangs- in Ausgangszusicherungen abgebildet, während die Berechnung eines einen Wert liefernden Ausdrucks mit Hilfe von Operatoren und Funktionen auf Datenobjekten als zeitfrei (oder doch terminierend) angenommen wird. Es muß weiter einschränkend vorausgesetzt werden, daß Funktionen neben dem zeitfreien Effekt der Berechnung eines Wertresultats keine anderen (nicht zeitfreien) Effekte, wie Zuweisungen an Parameter oder andere sichtbare Variable, haben dürfen. Sonst ist nämlich wiederum die Transitivität der Beweisschritte nicht sicherzustellen. Die letzte Forderung macht existierende Programme in Programmiersprachen mit sehr allgemeinem Funktionskonzept außerordentlich schwer verifizierbar. Nicht zuletzt wegen dieser Eigenschaft ist auch der Verifikationskalkül in erster Linie als Hilfsmittel zur Konstruktion von Programmen zu verstehen. Die Semantik der Zuweisung innerhalb des Kalküls kann nur durch ein entsprechendes semantisches Modell der Variablen erklärt werden: Eine Variable im Programmtext ist ein Bezeichner für Werte eines bestimmten Typs. Die Wirkung der Zuweisung x := f(x, y)
242
Programmierung im Kleinen
8.4.1.
besteht in der Umbenennung des zeitfrei berechnete^ Wertes f(x, y) der rechten Seite durch x. Dabei bedeutet f(x, y) eine Funktion, die im allgemeinen vom Wert von x vor der Zuweisung und Werten anderer Variablen (ausgedrückt durch y) abhängt und deren Resultatwert denselben Objekttyp wie x hat. Es ist darauf zu achten, daß als linke Seite nur Bezeichner zulässig sind, nicht aber Ausdrucke, welche z.B. den Wert einer Variablenkomponente berechnen: x[i]
ist Wert
des Aufrufs einer Zugriffsfunktion, nicht aber Bezeichner einer Variablen! Man könnte, ausgehend von der Eingangszusicherung nun die Transformationsregel der Zuweisung formulieren: jede Eingangszusicherung, die textuel! den Ausdruck f(x, y) enthält, wird in eine Ausgangszusicherung überführt, die aus der Eingangszusicherung durch textuelle Ersetzung von f(x, y) durch x entsteht, da die Aussage durch Umbenennung ihren Wahrheitswert behält. Daß diese Vorgehensweise für Zuweisungen sinnlos ist, zeigt die Anwendung dieser Regel auf Aussagen, deren Wahrheitswert stets "true" ist, wie z.B. 1 = 1. Für die Zuweisung x := 1 folgt z.B. {1 = 1} x := 1 {x = x}, aber auch
{1 = 1} x := 1 {x = 1},
aber auch
{1 = 1} x := 1 {1 = x}.
Unsere Regel sagt nämlich nichts darüber aus, welche Vorkommen der rechten Seite in der Eingangszusicherung zu ersetzen sind. Es ist sinnvoller, von der erwarteten Resultataussage, ausgedrückt als Aussage in x, auszugehen und aus ihr die Voraussetzung zu folgern: (3)
{P x f ( x '
y)
}
x := f(x, y) {P (x)}
zu lesen als: Die Eingangszusicherung für die Zuweisung entsteht aus der Ausgangszusicherung durch Ersetzung aller freien Vorkommen des Bezeichners der linken Seite durch die rechte Seite. Für die Aneinanderreihung zweier Anweisungen S-,; S 2 gilt die Transformationsregel
8.4.1. (4i
Zusicherungen
243
{Pi> Si {Qi>, {Q,} S 2 {Q 2 } { P ^ S,; S 2 {Q 2 }
Die Transformationsregeln für Auswahl und Wiederholung werden aus der Gültigkeit von Überführungsregeln für Zusicherungen des Anweisungsrumpfes abgeleitet. Für die vollständige Alternative gilt (5)
{P A B} S 1 {Q}
,
( P A i B} S 2 {Q}
{P} rf B then Si else S 2 end j f {Q} Daraus ist der Spezialfall der einseitigen Alternative ableitbar {P A B) S, {Q} , P A B => {P> rf B then S, eind rf {Q}
Q
Die Regel für die allgemeine Form der Miederholung ist {P} S, {Qi> (6)
{Qi A :
Bi> S 2 {Q 2 }
{Q n A
B n } S n +i {Q n +i>
Qn+i = >
p
{P} repeat S-i until Bj occurred S 2 until B 2 occurred ... S n + ! end repeat {(Qi A B-,) V (Q 2 A B 2 ) V ... V (Q n A B n ) } . Wichtiger ist der Spezialfall {P A B} S {Q}
,
Q => P
{P} while B repeat S end repeat {Q A t B} , woraus für die abweisende Schleife nach Anwendung der Schlußregel (2) folgt (7)
{P A B} S {P} {P> while B repeat S end repeat (P A
B) .
P ist eine Aussage über den Programmzustand, der für den Schleifenrumpf und damit für die gesamte while-Schleife invariant bleibt.
244
Programmierung im Kleinen
8.4.1.
Die Betrachtungen zeigen, daß wesentliche Aufgabe bei Anwendung der Verifikationsregeln für Wiederholungen das Auffinden der Schleifeninvarianten ist. Dafür gibt es keine festen Regeln. Es liegt aber nahe, sie bei Kenntnis der Resultatsbedingung für die Schleife aus dieser abzuleiten. Um die Problematik der Verifikation für Terminationskonstrukte zu demonstrieren, geben wir noch eine Verifikationsregel für das ZahnKonstrukt an: {PiJ S, {QiJ {P n } S n {Q n } (8)
{P,} signal event 1 {false} = > { P } S {Q} {P n } Signal event n {false} = > { P } S {Q> {P} (S on event 1 : S,; ... on event n : S n ) {QV (Q, V...VQ n )}.
Die Idee dabei ist, als Nachbedingung für jede signal-Anweisung "false" anzunehmen, aus der jede andere Bedingung zu folgern ist und unter dieser Annahme die Gültigkeit der Regel
{P} S {Q}
nach-
zuweisen [Clint72]. Bei vollständiger Fallunterscheidung ist auch für Q "false" anzunehmen. Die Eingangszusicherungen für die signalAnweisungen werden zu Eingangszusicherungen der den Situationen zugeordneten Folgeoperationen Si, S 2 , ... , S n . Deren Nachbedingungen gehen disjunktiv verknüpft in die Nachbedingung des Konstruktes ein. Der problematischste Fall ist die Aufstellung einer allgemeinen Verifikationsregel für den Prozeduraufruf p (x) [Hoare73]. Man kann die Wirkung dieses Konstruktes auffassen als Sequenz von Zuweisungen an diejenigen aktuellen Parameter
x1
xn
der Parameterliste x,
deren Werte geändert werden und an andere prozedurglobale Variablen y = (y , ... , y n ), deren Werte sich ändern. Dabei muß vorausgesetzt werden, daß es gelingt, den Effekt der Prozedurausführung durch werteliefernde Funktionen
f^ (x, y) , (1 < i < m)
und
gj (x, y) , (1 < j < n)
zu beschreiben, die seiteneffektfrei sind.
8.4.1.
Zusicherungen
245
Es g i l t dann: (9)
{P
X1
J J fi(*.y)
Xm
, _ fm(x>y)
y1
J J 9i(x»y)
yn
„ , } p (x) {P} . gn(x»y)
Die Verwendung der Transformationsregeln zum Beweis eines A l g o r i t h mus setzt eine formale Beschreibung der Eigenschaften der Objekte und der auf sie anwendbaren Funktionen voraus. Wir wollen dies am Beispiel des Datentyps int demonstrieren. Eine Axiomatik des Datentyps int [Hoare69] enthält neben den bekannten Axiomen zur Beschreibung der Kommutativität von Addition und Multiplikation, der D i s t r i butivität der Multiplikation, der Eigenschaften der neutralen Elemente 0 und 1 und der Eigenschaften der Subtraktion noch zusätzliche Regeln zur Beschreibung der Endlichkeit des Wertebereiches. Wir beschränken uns im folgenden auf die nichtnegativen Werte des Datentyps int. Dann läßt sich die Endlichkeit des Wertebereiches durch die beiden Axiome V A x < max max x
und
-i (V x = max + 1)
beschreiben; die übrigen Axiome bleiben uneingeschränkt g ü l t i g . Aus dem Axiomensystem i s t der Satz ableitbar, daß (y < r) = > r + y * q
= ( r - y ) + y * ( l
+ q) .
Mit diesen Voraussetzungen i s t dann beweisbar, daß für a l l e nichtnegativen int-Werte x und y das folgende Programm QUOT den Quotienten q und den ganzzahligen Rest r bei ganzzahliger Division von x durch y berechnet. Es soll bewiesen werden: {true} QUOT {x = q * y
+
r A r < y }
QUOT i s t dabei: r : = x; q : = 0; whiley < r repeat r : = r - y ; q : = q + 1 end repeat .
246
Programmierung im Kleinen
8.4.1.
Das Programm enthält eine Wiederholung, bei der durch Vergleich der Ausgangszusicherung mit der Terminationsbedingung aus (7) ohne Schwierigkeit die Schleifeninvariante P = x = q * y + r zu entnehmen i s t . Auch {P A y < r} r := r - y ; q := q + 1 {P} läßt sich unter Anwendung der Regel (3) und des Satzes aus der Ausgangszusicherung P ableiten. Die großen Schwierigkeiten für eine Anwendung dieser V e r i f i k a t i o n s methode auf relevante Probleme liegen darin, daß sich die dort vorkommenden Objektklassen einer einfachen axiomatischen Beschreibung weitestgehend entziehen. Damit sind Beweise nicht vollständig f o r mal zu führen, geschweige denn zu automatisieren. Einen kleinen Einblick in die Schwierigkeiten bietet die V e r i f i k a tionsregel für die Wertzuweisung an Feldkomponenten. Wie schon bemerkt, i s t die Zuweisung x [ i ] := v im Kalkül nicht z u l ä s s i g . Man muß daher schreiben ([Gries78], [Hoare73]) x := (x; i : v) wobei (x; i : v) eine Funktion i s t , welche den Übergang von einem Feld-Wert x zu einem anderen beschreibt, der sich an der i - t e n S t e l le von x unterscheidet. Es i s t (x- i1 • VviJ LMJ Jl = 1{ v ' w e n n j = 1 " x [ j ] sonst . Rechenregeln für diese Funktion sind z.B. durch r v 2 , wenn j = i 2 ((x; i i : V i ) ; i 2 : v 2 ) [ j ] = \ v , , wenn j * i 2 A j = i , l x [ j ] , wenn j + i ^ A j + i-, gegeben, wobei die Reihenfolge der Auswertung eine Rolle s p i e l t . Mit dieser Funktion i s t die Verifikationsregel für komponentenweise Zuweisung durch {P y — * m : = x n y > x - » m : = y fi_. Der Endzustand kann im Falle x = y auf zwei verschiedenen Wegen berechnet werden. Die Wiederhol ung hat die Form do B-, —»St n B 2 — - S 2 n ... n B n — > S n od. Hier wird bei jedem Wiederholungsschritt geprüft, ob wenigstens ein Guard B-j gültig ist, und eine zugehörige Anweisung S-j ausgeführt. Die Wiederholung terminiert genau dann, wenn keines der Guards gültig ist. Dies geschieht nach endlich vielen Schritten. In den Transformationsregeln der guarded commands wird stets (wie auch in den Anwendungen des Kalküls) aus einer Nachbedingung eine eindeutig bestimmte schwächste Vorbedingung (weakest precondition) abgeleitet. Die Notation dafür ist w p ( S , R ) . Sie besagt, daß für alle Bedingungen Q gilt: ({Q} S {R}) = > (Q = > w p ( S , R ) ) .
248
Programmierung im Kleinen
8.4.2.
In dieser Notation ist die Semantik der Zuweisung durch wp ("x := f(x, y)", R) =
^
und die der Aneinanderreihung durch wp ("Si; S a ", R) = wp ( S „ wp (S a , R)) gegeben. Auch die allgemeinen Schlußregeln können anders formuliert werden. Es gilt -
wp (S, false) = false , das "Gesetz vom ausgeschlossenen Wunder"; es besagt, daß aus der Nachbedingung "false" nie eine gültige Vorbedingung ableitbar ist;
-
(P = > Q ) = > (wp (S, P) = > w p (S, Q))
Monotonie
-
(wp (S, P) A wp (S, Q)) = wp (S, P A Q )
Homomorphieeigenschaft
der Konjunktion. In dieser Notation ist die formale Transformationsregel der Auswahl wp ("if B, — S-, n ... H B
n
— Snfi_", R) =
(B, V ß 2 V ... V B n ) A ( A B - = > w p (S-j, R)) l 0 {P A (B-, V ... V B n ) A f < f 0 + 1} vf B, — Si n ... n B n — S n fi_{f < f 0 > {PJdoB,—Sin...nBn^Snod{PA- 0 A ( A Anfang [i] = W i ) . 1 < i < wz
Nach Berechnung von "links", "rechts" und "t" sollen u.a. die Beziehungen gelten (B)
links > 0 A rechts > 0 A links * (t - 1) + rechts * (wz - t) = lz A ((gerade (zeile) A 1 inks = rechts + 1) V (ungerade (zeile) A rechts = 1 inks + 1)).
Schließlich sind die neuen Wortanfänge zu berechnen. Danach gelten die Ausgangszusicherungen (A-,)
A Anfang [i] = Wi + l i n k s * (i - 1) 1< i Berechne links, rechts und t; {(B) A (E)} Berechne Anfang von 1 bis t; {(B) A (A,)} Berechne Anfang von t bis wz; {(B) A (A,) A (A a )}.
Wir beginnen nun mit der ersten Verfeinerungsstufe. Für die Berechnung der neuen Zwischenräume betrachten wir zuerst den Fall gerade (zeile) . Es folgt: lz = rechts + ( w z - 1 ) + ( t - 1 ) . Weil wegen l < t < w z 0 1 —
(*)
IIBei der Berechnung des Falles ungerade (zeile) überraschenden Fälle mehr, sodaß nunmehr gilt
ergeben sich keine
252
8.4.2.
Programmierung im Kleinen berechne links, rechts und t: if gerade (zeile)
—
rechts := lz div (wz - 1); links
:= rechts + 1;
t := lz + 1 - rechts * (wz - 1) n ungerade (zeile) —
links
:= lz div (wz - 1);
rechts := 1inks + 1; t := links * (wz - 1 ) + w z + lz fi_. Das nächste Refinement "berechne Anfang von 1 bis t" ist offenbar eine Wiederholung. Als Schleifeninvariante wählen wir die Beschreibung eines Zwischenzustandes, für den für ein k mit l < k < t
die
ersten k neuen Wortanfänge berechnet sind, während die t - k restlichen Wortanfänge noch ihren alten Wert besitzen. Außerdem ist der Zuschlag zum alten Wortanfang, um den neuen zu erreichen, links* (k-1), den wir mit Inkrement bezeichnen. Insgesamt ergibt sich eine Invariante P der Formel (2) zu P = l < k < t A Inkrement = links (k - 1) A (B) A A (Anfang [i] =Wi +links (i - 1)) A l y
und
y > x A y > z
alle Statements einmal ausgeführt und das Programm liefert das richtige Ergebnis. Eingabefälle, bei denen x das Maximum ist, führen jedoch zu einem Fehler. Ein weitergehendes Kriterium ist, ob alle Programmpfade mindestens einmal getestet wurden. Der Begriff Programmpfad leitet sich aus der
262
9.3.1.
Testen
Betrachtung von Flußdiagrammen ab, die hier verwendet werden, um die Struktur von Programmen graphisch darzustellen. Ein Programmpfad i s t ein Weg durch einen solchen Graphen. Da die Anzahl der Pfade in e i nem Programmgraphen nur von den Knoten abhängt, die mehrere Ausgänge haben, kann man den Programmgraphen für diese Zwecke auf einen Entscheidungsgraphen reduzieren, der nur diese Knoten enthält. Der im folgenden Programmgraphen dargestellte Algorithmus entscheidet, ob aus drei Strecken, deren Länge einzulesen i s t , ein g l e i c h s e i t i g e s , ein gleichschenkliges, ein anderes oder gar kein Dreieck gebildet
Dreieck Bild 9-3:
Dreieck
Dreieck
Dreieck
Algorithmus zur Dreiecksbestimmung nach [Myers76]
Automatische Unterstützung
9.3.2.
263
O f f e n s i c h t l i c h benötigt man acht Testdatensätze, um jeden Programmpfad genau einmal zu testen. Also z.B.
(A, B, C) = (6, 4, 1),
(4, 6, 1), (4, 1, 6 ) , (3, 3, 3), (3, 3, 5 ) , (5, 3, 5 ) , (5, 3, 3 ) , (5, 3, 7).
Hätte man im Programm s t a t t
A = B
A < B
abgefragt,
würden mit obigem Testsatz nach wie vor a l l e Programmpfade getestet und auch die r i c h t i g e n Ergebnisse erzeugt. Das Programm würde aber f ä l s c h l i c h e r w e i s e die Eingabe (3, 5, 7) a l s g l e i c h s c h e n k l i g e s
Drei-
eck i d e n t i f i z i e r e n . Eine weitere Schwäche dieses Kriteriums i s t , daß das Fehlen von Programmpfaden nicht aufgedeckt werden kann, d.h. wenn der Algorithmus gegenüber der S p e z i f i k a t i o n unvollständig i s t , b l e i b t dieser Fehler unentdeckt. S c h l i e ß l i c h s t e l l t zwar die Einschränkung auf Programmpfade beim Testen eine erhebliche Reduzierung der Eingabemenge dar, dennoch b l e i b t auch diese reduzierte Eingabemenge p r a k t i s c h unendlich. Eine einfache S c h l e i f e n k o n s t r u k t i o n , die maximal 100-mal durchlaufen werden kann, führt bei einer einzigen Entscheidung im Schleifenkörper zu
2
1 0 0
« 10 3 0
Programmpfaden.
Obwohl eine v o l l s t ä n d i g e Z u v e r l ä s s i g k e i t der Testdatenauswahl nach diesem Kriterium aufgrund theoretischer Überlegungen nicht e r r e i c h t werden kann, zeigen praktische Untersuchungen [Howden77], [Howden78], daß etwa 50% der bekannten Fehler in den betrachteten Programmen zuv e r l ä s s i g entdeckt werden konnten. 9.3.2. Automatische Unterstützung In den letzten Jahren i s t eine Vielzahl von automatischen T e s t h i l fen entwickelt worden, meistens für ein bestimmtes System. Der prakt i s c h e Nutzen l ä ß t s i c h noch nicht exakt abschätzen. Zwar g i b t es begeisterte Erfolgsmeldungen ("67% - 100% a l l e r Fehler gefunden und 2 - 5
Monate früher a l s s o n s t " ) , aber solche Angaben s i n d s t a t i -
s t i s c h nicht hinreichend abgesichert. Man kann aber davon ausgehen, daß automatische T e s t h i l f e n die Testphase e r l e i c h t e r n und deren E r gebnisse verbessern.
264
Testen
9.3.2.
Systeme zur Unterstützung von programmpfadorientierten Tests Erste Anwendungsmöglichkeit i s t die automatische E r s t e l l u n g von Progranmgraphen aus dem Programmtext. Ein System, das diese M ö g l i c h k e i t zur Verfügung s t e l l t , i s t b e i s p i e l s w e i s e RXVP [RXVP74], das FORTRANProgramme i n dieser Weise verarbeiten kann. Notwendig, weil manuell p r a k t i s c h nicht zu l e i s t e n , i s t d i e automat i s c h e Unterstützung bei der Oberprüfung, welche Programmteile bzw. -pfade von den b i s h e r i g e n Tests erfaßt wurden. Systeme wie PET (Program Evaluator and Tester) [ S t u c k i 7 2 ] , PACE (Product Assurance Confidence Evaluator) [Pace74], RXVP und v i e l e andere - die meisten f ü r FORTRAN - können die Ausführungen bestimmter Statements zählen, Maxima und Minima von Variablenwerten r e g i s t r i e r e n usw. Weiter entwickelte Systeme generieren aus der Programmstruktur Testdaten, die bestimmte Programmteile testen. S i e beruhen im a l l g e m e i nen auf der symbolischen Programmausführung (siehe 9 . 4 . ) . Zu nennen sind hier wiederum RXVP und TDG (ein i n t e r a k t i v e s System bei der NASA). Problematisch erscheint bei solchen Systemen a l l e r d i n g s die Verletzung eines Grundsatzes beim Testen, nämlich der Vorausbestimmung der erwarteten Testergebnisse. Automatische Modul einbettung Modul einbettungen s o l l e n den Aufwand v e r r i n g e r n , j e w e i l s geeignete Testumgebungen schreiben zu müssen, um einen einzelnen Modul oder eine Gruppe von Moduln testen zu können. Der zu testende Modul wird in eine allgemeine Testumgebung eingebettet. Diese s t e l l t dem Benutzer eine Kommandosprache zur Verfügung, i n der er -
die Namen von aufzurufenden Prozeduren und deren Parameterversorgung,
-
die Ergebnisse und Ausgangsparameter, die aufgerufene modulexterne Prozeduren a b l i e f e r n s o l l e n ,
-
die erwarteten Ergebnisse und Ausgangsparameter
angeben kann. Es wird dann automatisch ein Vergleich zwischen den '
9.4.1.
Grundlagen
265
erwarteten und den tatsächlichen Ergebnissen durchgeführt sowie der Testverlauf geeignet protokolliert. Dies leisten Programme wie AUT (Automated Unit Test) für Objektprogramme und MTS (Module Testing System) für Quel1 Programme in Assembler, COBOL, PL/I und FORTRAN [Heuermann74].
9.4. Symbolische Programmausführung 9.4.1. Grundlagen Durch Verwendung von Methoden, Programme nicht mit aktuellen Daten» sondern mit symbolischen, also Repräsentanten für eine Menge von aktuellen Daten zu testen, wird eine höhere Zuverlässigkeit des Testens angestrebt [King75], [Howden75], Die symbolische Programmausführung gelangt dabei infolge der notwendigen Formalisierung in die Nähe von Veri fi kationsmethoden. Zunächst wird der Wertebereich von Variablen um symbolische Werte (im folgenden mit kleinen griechischen Buchstaben bezeichnet) erweitert. Für diese Wertebereiche wird entsprechend die Semantik der Programmiersprache erweitert. Kommen in einem Programmstück nur Integer-Variablen vor, so kann etwa die Semantik der Formel im Sinne der Arithmetik gedeutet werden. Für die Werte ist
3 * i + k = 14.
Für
i=a, k = a
ist
3*i+k
i =3, k = 5
3*i+k = 3*a +a = 4*a.
Diese Erweiterung der Semantik ist natürlich problematisch, da sie von den Beschränkungen realer Wertedarstellungen im Rechner und der dort nur eingeschränkten Gültigkeit der algebraischen Verknüpfungsregeln abstrahiert. Durch Erweiterung der Semantik der Assignation sind auch algebraische Ausdrücke als Werte für die Zielvariable zulässig. In entsprechender Weise wird die Semantik aller Steuerkonstrukte der benutzten Programmiersprache erweitert. Als symbolische Programmausführung wird die Ausführung eines Programmtextes bezeichnet, wenn erweiterte Wertebereiche und erweiterte Semantik der Sprachkonstrukte benutzt werden. Dabei enthält die
266
9.4.1.
Testen
symbolische Programmausführung die normale als Spezialfall. Den Effekt von vertrackten Programmstücken kann man sich durch eine symbolische Programmausführung leichter verständlich machen. So ist bei dem folgenden Programmstück anhand einer Tabelle der - symbolischen Werteänderungen leicht zu sehen, daß die Werte der beiden Variablen vertauscht werden: X
y
a
ß ß
X := x + y ;
a
+ ß
y := x - y ;
OL
+ ß
a
ß
a
X := x - y ;
Zur Erweiterung der Semantik des Auswahlkonstrukts if condition then thenpart eise elsepart end if wird eine Pfadbedingung pc (Path-condition) eingeführt, die bei Ausführung des Programms alle Bedingungen, die für die Eingabewerte und sonstigen Programmvariablen gelten, konjunktiv verknüpft. Pc hat zu Beginn der Ausführung den Wert der Eingangszusicherung. Bei Elaboration einer Alternative wird geprüft, ob nicht schon ein Zweig der Alternative durch die gerade gültigen Werte ausgeschlossen werden kann, d.h. ob entweder die Auswahlbedingung oder ihre Negation aus der Path-condition folgt. In diesem Fall braucht nämlich bei der symbolischen Ausführung nur entweder thenpart oder elsepart
ausge-
führt zu werden. Man spricht in diesem Fall von aufgelöster Ausführung (Resolved bzw. Nonforking Execution). Ein solcher Fall liegt vor bei der Sequenz rf x = y then thenpart eise elsepart end vf; rf x > y then thenpart else elsepart end if; Im Normalfall, der nicht aufgelösten Ausführung (Unresolved bzw. Forking Execution) gabelt sich der Programmpfad in zwei neue Pfade auf mit den Pfadbedingungen
Grundlagen
9.4.1. pc A
267
condition für den then-Pfad
pc A - c o n d i t i o n für den else-Pfad. Die Ausführung eines Programms i s t ein Pfad durch den entstehenden Ausführungsbaum. Z u s ä t z l i c h zu den vom Programmtext elaborierten Pfadbedingungen können weitere Bedingungen in der Art von Zusicherungen ( a s s e r t i o n s ) mit H i l f e der Anweisung " a s s e r t " angegeben werden, die symbolisch ausführbar s i n d . S p e z i e l l e assert-Anweisungen sind "assume" und " p r o v e " , die a l s die Eingangs- bzw. Ausgangszusicherungen eines Programmes oder Programmstückes fungieren. Anhand eines kleinen B e i s p i e l s wird gezeigt, daß der Ausgangswert von y der S p e z i f i k a t i o n , d.h. den assume/prove-Anweisungen, genügt. Bei symbolischer Ausführung e r h ä l t man folgenden Ausführungsbaum, der s i c h bei 4 aufgabelt:
0
pc: true x: a
y: - a
y: a
- a i s t Resultat
a i s t Resultat
pc: a < 0 a < 0 => ( ( - a = a Va = a) A - a > 0) ist gültig
pc: a > 0 a > 0 => ((a = a Va = - a ) A a > 0) i s t gültig
0
B i l d 9-4:
Ausführungsbaum der Prozedur abs
268
Testen
9.4.1.
Programme mit Schleifen haben im allgemeinen einen unendlichen Ausführungsbaum. Bei der symbolischen Programmausführung gibt es zwei Möglichkeiten, diese Schwierigkeit zu behandeln. Die erste i s t , die Anzahl der Pfade durch Festlegen einer bestimmten Anzahl der Iterationen w i l l k ü r l i c h zu beschränken. Die zweite o r i e n t i e r t sich mehr an der formalen Verifikation mit Hilfe von Zusicherungen. Der S c h l e i fe wird eine induktive Invariante zugeordnet, und es werden zwei Ausführungsbäume erzeugt, wovon der eine die Schleifeninvariante als Eintrittspunkt hat. In der Prozedur 1
int proc gcd (int const m, n):
2
assume (m > 0 A n > 0 ) ;
3
int var a, b;
4
a := m;
5
b := n;
6
while a * b
7 8
repeat 1
assert (GGT (a, b) = GGT (m, n) A a * b);
9
if a > b
10
then a := a - b
11
eise b := b - a
12
end i f
13
end repeat;
14
a;
15
prove (a = GGT (m, n))
16
end proc gcd
definiert die induktive Zusicherung in Zeile 8 einen Schnitt und damit zwei Ausführungsbäume:
1
GGT (a, b) sei die mathematische Schreibweise für den größten gemeinsamen Teiler zweier natürlicher Zahlen mit den Eigenschaften 1) a > b => GGT (a, b) = GGT (a - b, b) 2) GGT (a, b) = GGT (b, a) 3) GGT (a, a) = a
9.4.1. -
Grundlagen
269
den ersten von 1 b i s 8 bzw. 15 Die assert-Anweisung f u n g i e r t a l s prove-Anweisung.
-
den zweiten von 8 b i s 8 bzw. 15 Die assert-Anweisung sowohl a l s assume- wie a l s prove-Anweisung.
B i l d 9-5:
Ausführungsbäume der Prozedur gcd
270
9.4.2.
Testen
Mit Hilfe der assert-Anweisung ist es möglich, kleinere Programmteile getrennt auf Korrektheit zu überprüfen. Diese können dann an beliebiger Stelle in ein Programm eingebettet werden, indem die Eingangs- und Ausgangszusicherungen wie normale Anweisungen auf die betreffenden symbolischen Werte angewandt werden. Nach dem Herauslösen der Schleife aus dem vorigen Beispiel durch zwei zusätzliche assertAnweisungen (Zeile 5a und 13a) reduziert sich die Prozedur zu 1
int proc gcd (int const m, n):
2
assume (m > 0 A n > 0);
3
int var a, b;
4
a := m;
5
b := n;
5a
prove (GGT (a, b) = GGT (m, n));
13a
assume (GGT (a, b) = GGT (m, n) A a = b);
14
a;
15
prove (a = GGT (m, n))
16
end proc gcd
Mit dieser Technik ist es möglich, auch größere Programmteile schrittweise symbolisch zu testen. 9.4.2. Anwendung der symbolischen Programmausführung Weil die symbolische Programmausführung von Hand eine recht mühselige Arbeit ist, wurden gleichzeitig mit der Entwicklung der Grundlagen auch entsprechende Programme zur Unterstützung der symbolischen Programmausführung entwickelt. Das System EFFIGY [King76] arbeitet auf einer PL/I-ähnlichen Minisprache, die Integer-Variablen, eindimensionale Felder, die üblichen Steuerkonstrukte, Prozeduren und einfache Ein-/Ausgabe enthält. Der Benutzer arbeitet im Dialog mit dem System, das dazu auch Debug-Möglichkeiten wie Tracing und Breakpoints zur Verfügung stellt. Zur Programmausführung kann der Benutzer Programme und Prozeduren mit aktuellen und symbolischen Werten und mit beiden gemischt aufrufen.
9.4.3.
Praktische Bedeutung
271
Kommt die Ausführung des Programms zu einer Fallunterscheidung, die nicht aufgelöst werden kann (Forking Execution), kann der Benutzer den Programmzustand speichern, sich für eine Alternative entscheiden und später zurückkehren und die andere Alternative abarbeiten. Mit assume-Anweisungen können Restriktionen über die Werte von symbolischen Variablen eingeführt werden. SELECT [Boyer75] ist ein ähnliches System für ein LISP-Subset. Es kann auch dazu benutzt werden, aus dem Programmtext die Wertebereiche der Eingabedaten zu berechnen, die die Ausführung eines bestimmten Programmpfades zur Folge haben. Ein weiteres ähnliches System DISSECT [Howden77] - interpretiert FORTRAN-Programme. Neben der symbolischen Programmausführung verwenden diese Systeme als weitere grundlegende Technik die automatische Manipulation von logischen Ausdrücken, z.B. um Formeln zu vereinfachen. 9.4.3. Praktische Bedeutung Die Methode der symbolischen Programmausführung ist ein wichtiger Schritt zur Formalisierung des Testprozesses wie zu dessen automatischer Unterstützung, trotz der Einschränkung, daß sie noch der Weiterentwicklung bedarf. Da sie auf einer Definition und Erweiterung der Semantik von Programmiersprachen mit Hilfe der Logik beruht, ist sie gegenwärtig nur für Probleme handhabbar, die sich mit relativ einfachen Datentypen darstellen lassen. Es ist daher ein Forschungsziel für die nähere Zukunft, die Methode auf kompliziertere Datenobjekte auszudehnen. Ein Ansatz in dieser Richtung findet sich z.B. in [Persch78] für PASCAL. Da sie auf der internen Programmstruktur und den Programmpfaden basiert, unterliegt sie, abgesehen von der verbesserten Schleifenbehandlung, den prinzipiellen Einschränkungen dieser Teststrategie (siehe Abschnitt 9.3.).
272
Testen
9.5.
Die Anwendung der Methode auf 12 Beispielprogramme [Howden77] z e i g t , daß solche Fehler mit hoher Wahrscheinlichkeit entdeckt werden, die d a r i n bestehen, daß f ü r eine Teilmenge der Eingabe die Funktionswerte f a l s c h berechnet werden. Fehler der A r t , daß die Eingabemenge für einen bestimmten Programmpfad f a l s c h gebildet wurde oder daß Pfade fehlen, können nur mit geringer Wahrscheinlichkeit oder gar n i c h t gefunden werden. Daraus f o l g t , daß die symbolische Ausführung a l s e i n z i g e Testmethode nicht ausreichend i s t , daß s i e aber eine s i n n v o l l e Ergänzung d a r s t e l l t .
9.5. Finden und Beheben von Fehlern E i n i g e der Testmethoden und T e s t h i l f e n geben bei E r f o l g , d.h. beim Auftreten eines F e h l e r s , auch g l e i c h einen Hinweis auf das Codestück,
das den Fehler h e r v o r r i e f . Dies i s t jedoch nicht der Nor-
m a l f a l l . Eine unangenehme Eigenschaft von Fehlern i s t zudem, daß der Typ des Fehlers im Code recht wenig mit seiner Erscheinungsform beim Test zu tun hat. Ein S c h r e i b f e h l e r kann ein Programm genauso zum Abs t u r z veranlassen wie ein Entwurfsfehler. Die Suche nach der S t e l l e , die den Fehler enthält (debugging), muß daher gleichermaßen systemat i s i e r t werden. Dazu gehört zunächst, daß geeignete P r o t o k o l l e gef ü h r t werden, wann, wo, unter welchen Bedingungen der Fehler aufgetreten i s t . Wenn man ein Programm daraufhin s o r g f ä l t i g l i e s t , kann es notwendig s e i n , dies zu unterbrechen und s i c h anderen Problemen zu widmen. Oft lösen s i c h dabei gedankliche Sperren, die s i c h bei längerer Konzentration auf ein und dasselbe Problem aufbauen können. Bei der Korrektur von Fehlern sind mehrere Dinge zu beachten. Es muß die t a t s ä c h l i c h e Ursache im Programm b e s e i t i g t und n i c h t nur das Symptom i n einigen F ä l l e n unterdrückt werden. L i e g t die Entstehung des Fehlers im Entwicklungsprozeß weiter zurück, müssen die entsprechenden Phasen t e i l w e i s e erneut durchlaufen und die Dokumente aktual i s i e r t werden. Es i s t sehr sorgsam zu prüfen, ob nicht durch
Behe-
ben eines Fehlers mehrere andere neu in das Programm eingefügt wer-
Finden von Fehlern
9.6.
273
den. Eine gute Programmstruktur u n t e r s t ü t z t dieses Bemühen. I n der b e r e i t s erwähnten Endres-Studie wird angegeben, daß im DOS/VS-System immerhin 85% der gefundenen Fehler durch Änderungen i n j e w e i l s einem Modul k o r r i g i e r t werden konnten ( B i l d 9 - 6 ) . Anzahl der Fehler
Anzahl der betroffenen Moduln
371
1
50
1
6
3
3
4
1
5
1
8
432 Bild 9-6:
F e h l e r v e r t e i l u n g nach Anzahl der betroffenen Moduln (DOS/VS) [Endres75a]
Die Korrektur von Fehlern i s t so früh wie möglich vorzunehmen, um zu verhindern, daß Folgefehler das Vorhandensein weiterer Fehler verschleiern.
9.6. Hilfsmittel zum Auffinden von Fehlern Das k l a s s i s c h e H i l f s m i t t e l des Programmierers beim Fehlersuchen i s t der Dump. Snapshots werden während eines Programmlaufs e r s t e l l t , Post-Mortem-Dumps nach dessen Absturz. Die meisten vorhandenen DumpProgramme zwingen den Programmierer noch immer, s i c h durch Speicherinhalte in schwer verständlicher Darstellung durchzufinden. An ihre S t e l l e s o l l t e n Programme treten, die eine Rückübersetzung i n dem Programmierer verständliche Symbole l e i s t e n und Bezüge zum Quellprogramm h e r s t e l l e n . A l s Beispiel dafür sei ein ALGOLW-System genannt [Satterthwaite71].
274
Testen
9.6.
Online-Debug-Programme für Assemblersprachen sind hinlänglich bekannt. EXDAMS (Extendable Debugging and Monitoring System) i s t ein Dialog-System für mehrere Sprachen (PL/I, FORTRAN, ALG0L60). Es führt das zu untersuchende Programm aus und zeichnet das Laufzeitverhalten des Programms auf. Der Benutzer kann dann mit dieser Aufzeichnung arbeiten, indem er sich die Werte von Variablen ausgeben läßt, den Programmlauf vorwärts wie rückwärts beobachtet und weitere Informationen abfragt. Beim Tracing werden in das Programm an geeigneten Stellen Funktionen eingefügt, die Informationen über die Werte von Variablen oder über den Steuerfluß ausgeben. Einige Compiler erstellen Traces optional, die allerdings meist nicht sehr komfortabel sind. Eine nützliche, aber leider seltene Compilereigenschaft i s t es, benutzereigene Traceprozeduren optional zu übersetzen. Programme zur statischen Analyse des Programmtextes ergänzen die Leistungen von Compilern. In modernen Programmiersprachen werden weitgehend Oberprüfungen schon durch den Compiler durchgeführt, überprüft werden können: -
Konsistenz von Schnittstellen
-
Typen von verwendeten Parametern
-
I n i t i a l i s i e r u n g von Variablen vor ihrer Applikation
-
Einhaltung von Programmierkonventionen.
Wertvolle Unterstützung erhält der Programmierer auch durch die Erstellung von Cross-Reference-Listen.
9.7.
Effizienztest
275
9.7. Effizienztest und Optimierung Wo4 doa Optimie.ie.n bn&vLfät, UUJI
bt^olgtn
zwei Regeln:
Regel 7: Tu'-i
nicht
Regel 2: (nu/i ¿iL* Experten) T V .4 noch nicht
- d.h.
nicht
bevoA. Vu eine vollkommen kZaAe und
nichtoptimieAte
Lösung heut. M.A. Jackson Der E f f i z i e n z t e s t i s t die Überprüfung von Laufzeit und Speicherplatzbedarf eines Programms. Mittel zu deren genauer Bestimmung sind Routinen, die vom Betriebssystem oder der Hardware zur Verfügung ges t e l l t werden. Bei der Herstellung von Testbedingungen i s t zu beachten, daß die E f f i z i e n z zum einen durch das Programm s e l b s t bestimmt wird (z.B. Algorithmus-bestimmte Verarbeitungszeiten für Eingabemengen), zum anderen durch das Grundsystem (z.B. verlängerte Verarbeitungszeit durch erhöhte Auslastung der Rechenanlage durch andere Programme). Nachträgliche Änderungen eines Programms, die bis in die Entwurfsphase zurückreichen, können nicht mehr a l s Optimierungen aufgefaßt werden. Schreibt die Anforderungsdefinition eine bestimmte E f f i z i e n z vor, so i s t dies beim Entwurf zu berücksichtigen. Das kann zur Folge haben, daß einzelne Programmteile noch während des Entwurfs bis zur Codierung weiterentwickelt werden, um aus deren E f f i z i e n z die des Gesamtsystems zu extrapolieren. Nachträgliche Optimierungen haben zur Voraussetzung, daß die Programmteile bestimmt werden, die lange Laufzeit verursachen oder
276
Testen
9.7.
hohen Speicherbedarf haben. Einen guten Optimierungseffekt erreicht man im allgemeinen dadurch, daß in diesen Teilen schnellere Algorithmen oder geschicktere Datenstrukturen verwendet werden, z.B. ein anderes Sortierverfahren oder eine platzsparende Tabellenorganisation. Optimierung des Speicherbedarfs kann auch heißen, ein Programm so an die Möglichkeiten des Betriebssystems anzupassen, daß Overlay-Technik angewendet und die Speicherhierarchie besser ausgenutzt werden kann. Lokale Laufzeitoptimierungen können sinnvoll nur in den Teilen vorgenommen werden, die einen hohen Anteil an der gesamten Laufzeit haben, z.B. Schleifen, die sehr oft durchlaufen werden. Nach Untersuchungen von [Knuth71] wurden in mehr als 50% der Laufzeit der untersuchten Programme 4% des Codes ausgeführt. Die weitestgehende Lösung ist, solche Codeteile in Assembler oder Maschinensprache umzuschreiben. Ist das nicht möglich, können folgende Methoden angewandt werden (nach [Infotech76a]): -
Verlagerung von Berechnungen von der Laufzeit auf die Codierung; z.B. durch Linearisierung mehrdimensionaler Felder und Berechnen konstanter Ausdrücke,
-
Verlagerung von Berechnungen aus häufig ausgeführten Codestücken in seltener ausgeführte,
-
Ersetzen von aufwendigen Operationen durch einfachere; z.B. Multiplikation statt Exponentiation, Iteration statt Rekursion, textuelle Einbettung statt Prozeduraufruf.
Einige solcher Techniken sind auch in manchen Compilern implementiert, wodurch der gesamte Code optimiert wird. Sie verteuern andererseits die Compilierung erheblich. Die Ausnutzung der Hardware-Eigenschaften ist nur auf der Assembleroder Maschinensprache möglich. Dies sollte weitestgehend vom Compiler realisiert werden, insbesondere bei spezieller Hardware wie Pipeline-Prozessoren.
9.8.
Zusammenfassung
277
9.8. Zusammenfassung Testmethoden dienen dazu, die Funktion und Leistung von f e r t i g codierten Programm(tei1)en zu überprüfen. Beim Modultest werden einzelne Moduln mit einer Testumgebung gegenüber der S p e z i f i k a t i o n getestet. Zum I n t e g r a t i o n s t e s t werden die Moduln s c h r i t t w e i s e zum v o l l s t ä n d i g e n Programm zusammengefügt. Ein I n s t a l l a t i o n s t e s t i s t notwendig, wenn das Softwareprodukt nicht auf dem Rechner entwickelt wurde, auf dem es einges e t z t werden s o l l . Zu diesen Tests unter Verantwortung des S o f t w a r e h e r s t e l l e r s werden die Testdaten mit Kenntnis des Programmtextes entwickelt. Der Abnahmetest hat zum Z i e l , die E r s e t z b a r k e i t des Programms zu zeigen. Er bezieht s i c h auf die Anforderungsdefinition. Testdaten können aus dem Programmtext aufgrund der S t r u k t u r der Programmpfade entwickelt werden. Eine S i c h e r h e i t , a l l e Fehler zu finden, l ä ß t s i c h aber n i c h t erreichen. Die Z u v e r l ä s s i g k e i t von Tests kann für bestimmte Fehlerklassen durch Verwendung symbolischer Eingabewerte vergrößert werden. Testergenisse s o l l t e n reproduzierbar sein und mit erwarteten E r gebnissen verglichen werden können. Programmsysteme unterstützen den Testprozeß bei der E r s t e l l u n g von Modultestumgebungen, Entwicklung von Testdaten, Kontrolle der Testergebnisse, symbolischen Programmausführung und beim Lok a l i s i e r e n von Fehlern. Nachträgliche Optimierungen s i n d nur in solchen Programmteilen s i n n v o l l , die einen besonders hohen Anteil an der L a u f z e i t oder am Speicherbedarf haben.
278
10.1.
Projektmanagement
10. Projektmanagement An verschiedenen Stellen unserer bisherigen Ausführungen haben wir bereits darauf hingewiesen, daß es nicht möglich ist, bei der Herstellung großer Programme das Augenmerk nur auf die technischen Aspekte der Problemlösung zu richten. Große Software wird nicht von Einzelpersonen, sondern von Teams hergestellt. Um der Zielbestimmung anforderungsgerechte Software ökonomisch herzustellen
Genüge zu
leisten, muß daher die Organisation der Teams genauso bewußt angegangen werden
wie etwa der Entwurf eines Programmsystems. Tatsäch-
lich ist das Scheitern von Projekten meistens auf Organisationsmängel zurückzuführen. Unter Projektmanagement wollen wir alle Tätigkeiten fassen, die die Organisation des Herstellungsprozesses zum Gegenstand haben. Dazu gehören -
die Untergliederung des Herstellungsprozesses in Phasen und Tätigkeiten,
-
die Arbeitsteilung,
-
die Regelung der Kommunikation.
Den ersten Punkt haben wir vor allem im Abschnitt 3.5. (Projektplanung) behandelt; Aspekte des dritten Punktes in Kapitel 4 (Dokumentation). In diesem Kapitel wollen wir die noch offenen Probleme der Kommunikation und vor allem der Arbeitsteilung behandeln.
10.1. Einbettung der Softwareherstellung in die Betriebsorganisation Ohne auf den Begriff näher eingegangen zu sein, sprachen wir bisher von Projekten als der Organisationsform der Softwareproduktion.
10.1.
Einbettung in die Betriebsorganisation
279
Projekt meint dabei eine "Organisationsform, die in ihrer Existenz zeitlich begrenzt, deren Aufgabenstellung genau definiert und die in Zusammensetzung und Arbeitsstil weder an die hierarchisch gegliederte Unternehmensstruktur gebunden ist, noch in sie hineinpaßt" [Reif67]. Diese letztgenannte Schwierigkeit taucht bei Unternehmen auf, bei denen die Software nur eines neben anderen Produkten ist oder vor allem eigenen Zwecken dient. Sie liegt darin begründet, daß zur Durchführung eines Projektes Mitarbeiter aus verschiedenen Abteilungen oder Bereichen benötigt werden. Projekt- und Abteilungsstruktur überlagern sich also insofern, als jedes Projekt Mitarbeiter aus verschiedenen Abteilungen und jede Abteilung Mitarbeiter hat, die in keinem oder in verschiedenen Projekten arbeiten. Diese Überlagerung nennt man Matrix-Organisation (wobei Organisation weniger die interne Projektorganisation als vielmehr die Einbettung des Projekts in die Unternehmensstruktur bedeutet).
Bild 10-1:
Matrix-Organisation (Groß-/Kleinschreibung bedeutet Vollzeit-/Teilzeitmitarbeit)
Die Projektmitarbeiter bleiben während eines Projekts ihren Abteilungen zugeordnet. Ihre Arbeitskraft muß nicht ausschließlich einem
280
Projektmanagement
10.1.
Projekt zur Verfügung stehen; sie können außerhalb des Projekts auch noch andere Arbeiten ausführen. Der Projektleiter plant ein Projekt und koordiniert die Arbeiten. Er ist keiner Abteilung zugeordnet und hat demgemäß keine Weisungsbefugnis gegenüber den Projektmitarbeitern. Die Nachteile der Matrix-Projektorganisation sind schwerwiegend: -
leicht auftretende Konflikte zwischen Abteilungsleitern und Projektleiter,
-
verminderte Konzentration der Mitarbeiter auf das Projekt, da Arbeit im Abteilungskontext,
-
Kommunikationsprobleme durch (räumliche) Trennung der einzelnen Mitarbeiter voneinander und vom Projektleiter,
-
mangelhafte Kontrollmöglichkeit des Projektfortschritts.
Für die Beschäftigten hat diese Organisationsform den Vorteil, daß sie über Projekte hinweg in derselben Arbeitsumgebung bleiben und nicht der Unsicherheit ausgesetzt sind, nicht zu wissen, wo und mit wem sie nach Projektende arbeiten werden. Die Schwierigkeiten zu beheben, die durch Mitarbeiter aus verschiedenen Bereichen entstehen, ist nicht Gegenstand von Software Engineering. Uns interessiert vielmehr die Organisation der Mitarbeiter, die unmittelbar mit der Herstellung der Software befaßt sind. Diese sind in Bild 10-1 in der Abteilung Programmierung zusammengefaßt. Ihre Zusammenfassung in Projekten unterscheidet sich von ihrer Projektzuordnung in der Matrix-Organisation dadurch, daß das Projekt auch betriebswirtschaftlich und nicht nur funktional gesehen eine eigene Einheit bildet. Es hat einen eigenen Etat; der Projektleiter bzw. ein ihm für Organisations- und Personal fragen zugeordneter Systemmanager koordiniert nicht nur, sondern übernimmt auch typische Funktionen von Abteilungsleitern wie Etatplanung, Personal Verantwortung. Für die Mitarbeiter ist das Projekt die Arbeitsumgebung, die nach dessen Auflösung wechselt. Diese Projektform findet sich in der Praxis vor allem in Softwarehäusern, da im Fall der Trennung von
10.2.
281
"Klassische" Projektmodelle
Auftraggeber und Auftragnehmer automatisch eine scharfe Trennung von Mitarbeitern aus Fachabteilungen und Softwareherstellern vorliegt. Wir wollen für diesen Typ von Projekt im folgenden zwei Modelle der Projektorganisation diskutieren.
10.2. „Klassische" Projektmodelle Die Organisation von Softwareprojekten orientierte sich lange Zeit an der pyramidenförmigen Organisation anderer Bereiche. Nach der Analyse des Problems wird es in Teilprobleme zerlegt. Im danach konstituierten Projekt ist deren Bearbeitung die Aufgabe von Mitarbeitern auf unteren Hierarchieebenen. Je größer ein Problem, desto grösser die Anzahl der Mitarbeiter und die Anzahl der Hierarchiestufen. Ein Softwareentwicklungsprojekt für 50 Mitarbeiter kann so aussehen:
8 Bild 10-2:
8
8
6
Organisation eines Programmierprojektes [Endres75, S. 5-6]
282
10.2.
Projektmanagement
Der Projektleiter unterhält die Verbindungen zu Auftraggeber und Management des eigenen Unternehmens. Er initiiert die Arbeitsteilung im Projekt, empfängt Fortschrittsberichte und trifft entsprechende Maßnahmen. Daneben hat er die Aufgabe der Personalführung
(Bewer-
tung, Karriereförderung, ...). Der Projektstab erfüllt im wesentlichen administrative Aufgaben wie Termin- und Kostenkontrolle, Schulungen, Anfertigung von Richtlinien etc. Entwurf & Analyse ist der technische Stab, der für die Entwicklungsdokumentation als Ganzes zuständig ist, Änderungen am Entwurf kontrolliert, die Leistung der hergestellten Software analysiert. Die Programmentwicklung leistet die Implementierung der Moduln, die Funktions- und Leistungsüberprüfung, sowie deren Dokumentation. Die Testentwicklung bereitet den Integrations- und den Installationstest vor. Dieses Beispiel einer Projektorganisation hat einige Schwächen, die unschwer zu beseitigen sind: So ist schwer einzusehen, warum die Vorbereitungen für Funktionstest (Testentwicklung) und Leistungstest (Entwurf & Analyse) getrennt sind. Auch liegt dem Modell offenbar keine deutliche Trennung von Entwurfs- und Implementierungsphase zugrunde. Dem Projektleiter kann auch ein Systemmanager über- oder beigeordnet werden, der für den wirtschaftlichen Teil des Projekts verantwortlich wäre und somit den Projektleiter entlastete. Nach [Schnupp76, S. 213] ist dies die Regel. Typisch für klassische Projektmodelle ist die mehrstufige Management-Hierarchie. Zuoberst steht der Projektleiter; auf der nächsten Stufe die Leiter der drei Hauptkomponenten des Projekts (Programmentwicklung, Entwurf & Analyse, Testentwicklung); im kritischen Teil des Projekts, der Programmentwicklung (d.h. hier Entwurf und Implementierung), gibt es eine weitere Managementstufe: die Leiter der jeweiligen Komponentenentwicklung
bzw. von Integration & Dokumen-
tation. Diese mehrstufige Management-Hierarchie behindert eine wirksame
10.2.
"Klassische" Projektmodelle
283
Projektfortschritt.skontrolle. Wir haben bereits ausgeführt (S. 77f), daß eine wirksame Kontrolle nur möglich ist mit Hilfe genau definierter Arbeitsschritte innerhalb eines Meilensteinplanes. Um Korrekturen im Fall von Verzögerungen vornehmen zu können, sollten die Arbeitsschritte zeitlich kurz bemessen sein. Da für die Arbeitsverteilung und deren Korrektur in oben vorgestelltem Modell der Projektleiter zuständig ist, können diese Forderungen nicht erfüllt werden. Auf der dritten Managementstufe ist er einfach zu weit entfernt von den durchgeführten Tätigkeiten, um diese, wie nötig, genau genug planen und kontrollieren zu können. Selbst wenn er den nötigen Einblick in das System hätte, so wäre er aufgrund der hohen Programmiererzahl weit überlastet. Der Projektleiter ist damit angewiesen auf vage Aussagen und kontrolliert den Fortschritt nicht mehr wirklich. Ein anschauliches Beispiel für dieses Problem findet sich in [Weinberg71, S. lOOf]: In einem teuren militärischen Softwareprojekt mußte am Ende jedes Monats ein Fortschrittsbericht an den Auftraggeber abgeliefert werden. Die Bearbeitungszeiten des Berichts von Drucker, Firmenmanagement, Systemmanager, Projektleiter und weiteren Managern war so lange, daß der Bericht von den Programmierergruppen noch vor Beginn des Monats, über den jeweils Rechenschaft abzulegen war, angefertigt werden mußte. Sie berichteten also nicht wirklich über den Projektfortschritt, sondern über die Vorhaben für den folgenden Monat. Ein weiteres Problem der "klassischen" Projektorganisation besteht darin, daß sich in der Regel die finanzielle und soziale Lage aus der Höhe einer erklommenen Pyramidenstufe ergibt. Auf der untersten Stufe der Projektpyramide stehen die am wenigsten Erfahrenen - und damit die am wenigsten Produktiven. Haben sie Erfahrung angesammelt, steigen sie (im nächsten Projekt) auf vom Programmierer zu deren Manager. Genau damit wird aber ihre Qualifikation nicht nutzbar gemacht. Sie programmieren nicht mehr, obwohl sie sich gerade als Programmierer qualifiziert haben.
284
Projektmanagement
10.3.
10.3. Das Chef-Programmierer-Team Mit dem Konzept des Chef-Programmierer-Teams [Baker72a], [Baker73] wird der Versuch unternommen, die wesentlichen Nachteile klassischer Projektmodelle zu vermeiden. Durch Verzicht auf Manager, die nicht an der Implementierung beteiligt sind, und durch den Einsatz von Spezialisten wird eine hohe Arbeitsproduktivität erzielt. Dadurch kann die Projektgröße reduziert werden. Ein nach diesem Konzept organisiertes Projekt soll nicht mehr als zehn Mitarbeiter umfassen. Die Idee des Chef-Programmierer-Teams besteht darin, daß der Projektleiter nicht nur für alle Arbeit verantwortlich ist, sondern deren größten Teil auch alleine durchführt. Alle anderen Projektmitglieder assistieren ihm dabei. Die Entwickler des Chef-ProgrammiererTeams vergleichen es daher mit einem medizinischen Operationsteam. Der Kern eines Chef-Programmierer-Teams wird gebildet durch -
den Chef-Programmierer,
-
den Projektassistenten,
-
den Projektsekretär.
Der Chef-Programmierer ist der Projektverantwortliche. Er entwirft das Programmsystem vollständig und implementiert dessen wesentliche Teile selbst. Die verbleibenden Teile verteilt er an andere TeamMitglieder und kontrolliert deren Arbeiten. Er hat also Einblick in das gesamte System und kontrolliert den Projektfortschritt. Der Projekt-Assistent assistiert dem Chef-Programmierer bei allen wesentlichen Entscheidungen in Programmentwurf und -entwicklung. Er existiert in diesem Management-Konzept vor allem, damit das Projekt bei Ausfall des Chef-Programmierers nicht dadurch gefährdet ist, daß ihn niemand ersetzen kann. Unabhängig vom Chef-Programmierer kann er die Systemtests planen. Der Projektsekretär entlastet die Programmierer vom bürokratischen
10.3.
Das Chef-Programmierer-Team
Teil ihrer Arbeit. Er verwaltet alle Projektergebnisse
285
(Listings,
Ausführungsresultate, Testdaten, ...) und kann jederzeit Auskunft über den Stand des Projektes geben. Ein Off-Line-System vorausgesetzt, wird die Kommunikation der Programmierer mit dem Rechner durch ihn vermittelt. Er erhält die Programme und Job-Anforderungen, läßt die Programme vom Rechner ausführen, speichert sie und ihre Auswertungsdaten in einer Bibliothek und gibt den Programmierern die Listings sowie die Veränderungen der den aktuellen Stand des Projekts beschreibenden Dokumentation zurück. Dieser Kern des Chef-Programmierer-Teams wird vom Chef-Programmierer erweitert: Spezialisten für die verwendete
Implementierungssprache,
für die Programmsteuerung, für das Entwickeln von Testfällen, oder einfach Programmierer, die nicht zum Kern des Systems gehörige Teile nach vorgegebener Spezifikation implementieren, können hinzukommen. Wie auch bei klassischen Projektformen wird der Chef-Programmierer durch einen Projektmanager entlastet, der die vertraglichen, finanziellen und Verwaltungsaufgaben übernimmt, um dem Chef-Programmierer die Konzentration auf die technischen Aspekte zu ermöglichen. In der dargestellten Form hat das Konzept des Chef-ProgrammiererTeams einige Schwächen. -
Die Konstruktion des Projektsekretärs ist nur geeignet bei OffLine-Betrieb, der in der Systementwicklung seltener wird. Für den On-Line-Betrieb scheint es zweckmäßig, die Projektbibliothek zu einer Datenbank auszubauen, die weitgehend automatisch erstellt und aktualisiert wird. Der Projektsekretär kann durch einen Projektverwalter ersetzt werden, der produktiv an der Projektleitung mitarbeitet [Schnupp76, S. 225ff]. Dieser ist nicht mehr Hauptprozessor eines Projektbibliothekssystems, sondern Benutzer, der die zur Kontrolle notwendigen Informationen entnimmt. Der eventuelle Ausfall des Projektverwalters behindert daher das Projekt nicht in dem Maße
-
wie der des Projektsekretärs.
An den Chef-Programmierer werden sehr hohe Anforderungen gestellt. Er muß sehr gute sowohl technische als auch Management-Fähigkei-
286
Projektmanagement
10.4.
ten besitzen, da es keine Trennung zwischen Programmierung und Management gibt. -
Während im medizinischen Operationsteam Mitglieder verschiedener Ausbildung arbeiten (Chirurg, Anästhesist, Krankenschwester), besteht das Chef-Programmierer-Team aus Programmierern, die sich im wesentlichen nur durch ihre Erfahrung unterscheiden. Es kann daher nicht ausbleiben, daß Entscheidungen des Chef-Programmierers hinterfragt und kritisiert werden. Die Konzentrierung aller Entscheidungsbefugnisse auf den Chef-Programmierer ist sicher kein gut geeignetes Mittel, Konflikte zu lösen.
-
Das Chef-Programmierer-Team wurde erstmalig eingesetzt bei einem Projekt mit einem Jahr Laufzeit bei elf Teammitgliedern. Es wurden über 83 000 Zeilen Quellcode (davon über 6 000 in Assembler und Linkage-Editor-Statements, der Rest in PL/I, COBOL und JCL) geschrieben. Dies ist gewiß kein kleines Projekt. Wie sieht aber die Organisation für wesentlich größere Projekte aus? Es wird vorgeschlagen, solche Systeme von einem Team entwerfen zu lassen, dessen einzelne Mitglieder danach als Chef-Programmierer in Unterprojekten fungieren. Ein wesentlicher Vorteil des Chef-Programmierer-Teams, die Minimierung der Kommunikation, wird dadurch allerdings zumindest teilweise wieder aufgehoben. Denn Unterprojekte lassen sich nicht schnittstellenfrei bilden, neben den einzelnen Projektbibliotheken müssen zusätzlich ein Kommunikationssystem eingerichtet werden und eine nicht programmierende Managementstufe.
10.4. Probleme der Team-Organisation In den bisherigen Ausführungen haben wir die organisatorische Seite der Softwareherstellung vor allem unter Gesichtspunkten der organisierten Kommunikation und der Festlegung von Kompetenzen und Entscheidungsbefugnissen behandelt. Diese mehr oder weniger technische Abhandlung von Organisationsproblemen reicht aber nicht aus, da wir
10.4.
Team-Organisation
287
mit Softwareprojekten s o z i a l e Systeme o r g a n i s i e r e n , deren Elemente nicht technische E i n z e l t e i l e , sondern Menschen sind. Die Untersuchung der Probleme menschl icher Gruppen f ü h r t natürl ich aus dem Bereich der I n formatik hinaus. ( B i s auf wenige Ausnahmen [Weinberg71],
[Kraft77]
1 iegen auch keine I n f o r m a t i k - s p e z i f i s c h e n Arbeiten dazu v o r . ) Da diese Probleme zum einen am A r b e i t s p l a t z jedes Informatikers aktuell s i n d , ihre Beachtung zumanderen für die e r f o l g r e i c h e Organisation von Projekten unbedingt notwendig i s t , möchten wir einige von ihnen s k i z z i e r e n . Wir haben im Kapitel 4 (Dokumentation) und im vorigen Abschnitt 10.3. die o r g a n i s i e r t e Dokumentation a l s Festlegung der Kommunikationswege und der über s i e ausgetauschten Informationen behandelt. Nun wäre es eine I l l u s i o n anzunehmen, damit a l l e Kommunikationsprobleme g e l ö s t zu haben. Die Programmierer arbeiten s c h l i e ß l i c h in der Regel räuml i c h eng verbunden: Sie s i t z e n , auch wenn s i e verschiedene Aufgaben bearbeiten, miteinander im selben Zimmer oder auf demselben F l u r , s i e gehen zusammen essen und t r e f f e n s i c h am Rechner. Ein k o n t i n u i e r l i cher Informationsaustausch auch über Projektprobleme wird al so durch v i e l e r l e i Faktoren gewährleistet. Diese K o n t i n u i t ä t überlagert die o r g a n i s i e r t e Kommunikation, die Informationsaustausch vor allem zu bestimmten Zeitpunkten (z.B. an Meilensteinen) v o r s i e h t . Diese i n formelle Kommunikation s o l l t e nun nicht a l s zu beseitigendes übel betrachtet werden. S i e l i e g t im Interesse der Beschäftigten, die a l s s o z i a l e Wesen an guter Kommunikation mit ihren A r b e i t s k o l l e g e n
inte-
r e s s i e r t s i n d . Darüber hinaus kann s i e den P r o j e k t f o r t s c h r i t t erhebl i c h unterstützen, wenn gewährleistet i s t , daß die formale Kommunikation nicht umgangen wird. Da die Programmierung eine der g e i s t i g e n T ä t i g k e i t e n i s t , deren Geschwindigkeit und E r f o l g s i c h (noch) nicht genau durch Vorgaben r e geln l ä ß t , beeinflußt die Zufriedenheit der Programmierer mit ihren Arbeitsbedingungen den P r o j e k t e r f o l g erheblich. Beeinflussende Faktoren entstammen dabei sowohl den Besonderheiten eines Unternehmens ( U r l a u b s - , A r b e i t s z e i t r e g e l u n g , Entlohnung) a l s auch denen eines konkreten Projekts. Zu letzterem gehören unter anderem
288
Projektmanagement
-
Oberblick Uber das Projekt,
-
Interesse am Projektgegenstand,
-
Zufriedenheit mit der Aufgabenverteilung,
-
Leitungsstil und Kompetenz des Projektmanagers,
-
Verhältnis zu den Arbeitskollegen,
-
Organisationsform des Teams.
10.4.
Eine ausführliche Sammlung von Beispielen dazu findet sich in [Weinberg71, S. 47-115]. Wir wollen zum Schluß die beiden letzten Punkte ansprechen. Eine gute Teamarbeit beinhaltet kollegiales und solidarisches Verhalten der Teammitglieder. Dieses ermöglicht erst die Offenheit, die an allen Arbeitsplätzen wünschenswert, bei der Softwareherstellung aber eigentlich unerläßlich i s t : Die Verwendung bestimmterSprachkonstrukte als Strukturierungsmittel für AIgorithmen, die Festlegung von Programmierkonventionen, die Archivierung und Auswertung a l l e r vom Programmierer erstellten Versionen eines Programms, das Kontrollesen von Programmen als Testmethode - diese und andere Aspekte des S o f t ware Engineering haben gemein, daß sie vormals der "Privatsphäre" des Programmierers zugehörige Tätigkeiten öffentlich machen. Diese Transparenz wird notwendig durch die Anforderungen der Sache - durch die Ziele der Softwareherstell ung. Von einem guten Team i s t zu erwarten, daß die notwendige sachliche Auseinandersetzung nicht auf die persönliche Ebene übertragen wird; daß also etwa K r i t i k an einem Programm nicht gleichgesetzt wird mit K r i t i k an dessen Programmierer. Der solidarischen Verhaltensweise im Team stehen aber die ManagementKonzepte entgegen, die vor allem die Leistung Einzelner beurteilen und finanzielle Besserstellung mit dem Aufstieg in der ManagementHierarchie koppeln. Projekte werden dadurch nicht hauptsächlich als Organisationen gesehen, die eine Aufgabe zu erfüllen haben, sondern als soziale Pyramiden, die es zu erklimmen g i l t . Das hat zur Folge, daß die Zusammenarbeit unter der Konkurrenz der Teammitglieder l e i det und damit auch die Software nicht mehr unter optimalen Bedingungen hergestellt wird.
10.5.
Zusammenfassung
289
10.5. Zusammenfassung Der B e g r i f f Projektmanagement faßt a l l e T ä t i g k e i t e n , welche die Organisation der Softwareherstellung zum Gegenstand haben. Dazu gehören die Untergliederung des H e r s t e l l u n g s p r o z e s s e s , der Arb e i t s t e i l u n g und der Kommunikation. Softwareprojekte können analog zu anderen Projekten in Unternehmen Abteilungs-Liberlagernd e i n g e g l i e d e r t werden:
Projektmitarbei-
ter bleiben in ihren Abteilungen. Der P r o j e k t l e i t e r
koordiniert
nur und hat keine Personalkompetenz. Vor allem wegen der Kommunik a t i o n s s c h w i e r i g k e i t e n i s t diese Eingliederung
(Matrix-Organisa-
t i o n ) n a c h t e i l i g . A l t e r n a t i v zu dieser Eingliederung stehen von der A b t e i l u n g s s t r u k t u r unabhängige Projekte, die selber o r g a n i s a t o r i s c h e Einheiten eines Unternehmens s i n d . Es finden s i c h vor allem zwei
Projektorganisationsformen:
Das " k l a s s i s c h e " Projektmanagement u n t e r t e i l t ein Problem v o l l ständig und d e l e g i e r t die E r s t e l l u n g der E i n z e l t e i l e . Die K o n t r o l le mit H i l f e von den Programmierern e r s t e l l t e r F o r t s c h r i t t s b e r i c h te i s t aber entweder u n z u v e r l ä s s i g oder mit zu hohem b ü r o k r a t i schem Aufwand verbunden. Das Chef-Programmierer-Team ordnet diesen bürokratischen Teil
ei-
ner Person im Projekt zu: dem Projektsekretär. Der Chef-Programmierer entwirft das System und programmiert die wesentlichen Teile s e l b s t . Der P r o j e k t a s s i s t e n t unterstützt ihn dabei und kann ihn bei A u s f a l l ersetzen, übrige Programmierer haben nur s p e z i e l le und genau d e f i n i e r t e Aufgaben zu erledigen. Neben r e i n organisationstechnischen Aspekten sind beim Projektmanagement auch s o z i o l o g i s c h e und psychologische zu b e r ü c k s i c h t i gen. Dabei geht es darum, den Programmierern gute Arbeitsbedingungen zu schaffen, die s i e auch zur Arbeit am Projekt motivieren.
290
VeA Iztztz
l/eA4 - t,indi>
Vin zHAtzn
VeAi iang -ich am Anfang,
Ven ieXztzn die
$ioh.
V&n -itng ¿ch
a.ndeAn Vau
zum
waAn zuxii, chen
Schluß, dälun&n,
Vai Ganze WOA eÄ.n Hochgenuß. Schad,
daß .ich jetzt
au&höizn
muß!
Karl Valentin
Literatur
291
Literaturverzeichnis Verwendete_Abkürzungen : CACM - Communications of the ACM IEEE ToC - IEEE Transactions on Computers IEEE ToSE - IEEE Transactions on Software Engineering IFB - Informatik-Fachberichte, Beriin(West): Springer-Verlag LNCS - Lecture Notes in Computer Science, Berlin: Springer-Verlag SIGPLAN - ACM SIGPLAN Notices Software P&E - Software - Practice and Experience [Alberts76] Alberts,D.S.: The Economics of Software Quality Assurance. National Computer Conference. AFIPS Conference Proceedings Vol.45. New York: AFIPS Press 1976, 222 - 231 [Baker721 Baker,F.T.: System Quality through Structured Programming. Fall Joint Computer Conference (72), 339 - 344 [Baker72a] Baker,F.T.: Chief Programmer Team Management of Production Programming. IBM Systems Journal 1 (72), 56 - 73 [Baker73] Baker,F.T., Mills,H.D.: Chief Programmer Teams. Datamation ^9, 12 (73), 58 - 61 [Bauer731 Bauer,F.L. (ed.): Software Engineering. An Advanced Course. Beriin(West): Springer-Verlag 1973, 1975 2 LNCS30 lBauer73a]
Bauer,F.L.: Software Engineering. [Bauer73], 522 - 543
[Bayer78] Bayer,R.: Datenschutz und Datenverarbeitung. Einige grundsätzliche Betrachtungen zum Bundesdatenschutzgesetz. Informatik-Spektrum 1, 1 (78), 17 - 24 [Bayer78a] Bayer,M., Dehottay,J.-P., Feuerhahn,H.: Getrennte Übersetzung von CDL2-Programmteilen. TU Berlin, Forschungsprojekt CDL2, 1978 2 [BDSG77] Gesetz zum Schutz vor Mißbrauch personenbezogener Daten bei der Datenverarbeitung (Bundesdatenschutzgesetz - BDSG). Bundesgesetzblatt Teil I 27.1.1977, 201 - 214 [Berry77] Berry,D.M., Erl ich, Z., Lucena,C.J.: Pointers and Data Abstractions in High Level Languages-I: Language Proposals. Computer Languages 2, 4 (77), 135 - 148 [Bischoff62] Bischoff,H.-A.: Vortrag in einer kirchlichen Veranstaltung. Informationsdienst des Männerwerkes in RheinlandPfalz, Nr. 22, 1962
292
Literatur
[Böhm66] Bönm.C., J a c o p i n i , G . : Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules. CACM 9, 5 ( 6 6 ) , 366 - 371 [Boehm73] Boehm,B.W.: Software and I t s Impact: A Quantitative Assessment. Datamation 19, 5 ( 7 3 ) , 48 - 59 [Boehm75] Boehm.B.W.: Some Experience with Automated Aids to the Design of Large-Scale Reliable Software. [ICRS75], 105 - 113 [Boehm76] Boehm.B.W.: Software Engineering. IEEE ToC C-25, 12 ( 7 6 ) , 1226 - 1241 ~~~ [Boehm76a] Boehm,B.W.: Software Engineering Education: Some Industry Needs. [Wasserman76], 13 - 19 [Boyer76] B o y e r . R . S . , E l s p a s s , B . , L e r i t t . K . N . : SELECT - A Formal System f o r Testing and Debugging Programs by Symbolic Execution. [ICRS75], 234 - 245 [Brainerd78]
Brainerd.W.: Fortran77. CACM 21, 10 (78), 806 - 820
[Briesenick76] B r i e s e n i c k , C . , Felsch.H.: Automatisierte A u s l e i h e in der U n i v e r s i t ä t s b i b l i o t h e k B i e l e f e l d . München: Verlag Dokumentation 1976 [Brooks75] Brooks,F.H.: The mythical Man-Month. Essays on Software Engineering. Reading, Mass.: Addison Wesley [Burstal 177] Burstal 1,R.M., Goguen,J.A.: Putting Theories Together to Make S p e c i f i c a t i o n s . International J o i n t Conference on A r t i f i c i a l I n t e l l i g e n c e , Boston/Mass., Aug. 77 [Buxton69] Buxton,J.N., Randell,B. ( e d s . ) : Software Engineering Techniques. Report on a Conference, Rome. B r ü s s e l : NATO S c i e n t i f i c A f f a i r s D i v i s i o n 1969 [Chang78] Chang,E., Kaden,N.F., El 1iott,W.D.: Abstract Data Types in EUCLID. SIGPLAN 13, 3 ( 7 8 ) , 34 - 42 [Chen78] Chen,T.E.: Program Complexity and Programmer P r o d u c t i v i t y . IEEE ToSE SE-4, 3 ( 7 8 ) , 187 - 194 [Chrysler78] C h r y s l e r , E . : Some B a s i c Determinants of Computer Programming P r o d u c t i v i t y . CACM 21, 6 ( 7 8 ) , 472 - 483 [ C l i n t 7 2 ] C l i n t , M . , Hoare,C.A.R.: Program Proving: Jumps and Functions. Acta Informatica 1, ( 7 2 ) , 214 - 224 [Dahl72] D a h l , 0 . - J . , D i j k s t r a , E.W., Hoare.C.A.R.: Programming. London: Academic Press 1972
Structured
[Daniels71] D a n i e l s , A . , Yeates.D., Erbach,K.F.: Grundlagen der Systemanalyse. Köln: V e r l a g s g e s e l l s c h a f t Rudolf M ü l l e r 1971 [David68] D a v i d , E . E . J r . : Some Thoughts about the Production of Large Software Systems. Auszüge [Naur68] , 83 u.a.
Literatur
293
[Dehottay76] Dehottay,J.-P., Feuerhahn,H., Koster,C.H.A., S t a h l , H . - M . : Syntaktische Beschreibung von CDL2. B e r l i n ( W e s t ) : Technische U n i v e r s i t ä t B e r l i n Forschungsprojekt CDL2 1976 [DeMicheli76] DeMichel i , F . : C h e c k l i s t Kostensenken: EDV. München: Verlag Moderne I n d u s t r i e 1976 [Denert77] Denert.E.: S p e c i f i c a t i o n and Design of Dialogue Systems with State Diagrams. M a r l e t , E . , Ribbens,D. ( e d s . ) : International Computing Symposium, Liege. Amsterdam: North-Holland P u b l i s h i n g Company 1977, 417 - 424 [Denning76] Denning,P.J.: S a c r i f i c i n g the C a l f of F l e x i b i l i t y on the A l t a r of R e l i a b i l i t y . [ICSE76], 384 - 386 [Dennis75] Dennis,J.B.: The Design and Construction of Software Systems. [Bauer73], 12 - 28 [DeRemer76] DeRemer,F.L., Kron,H.: Programming-in-the-Large Programming-in-the-Small. [Schneider76], 80 - 89
versus
[ D i j k s t r a 6 8 ] D i j k s t r a . E . W . : Goto Statement Considered Harmful. CACM lj., 3 ( 6 8 ) , 147 - 148 [ D i j k s t r a 6 9 ] D i j k s t r a , E . W . : Structured Programming. [Buxton69], 84 - 88 [ D i j k s t r a 7 2 ] D i j k s t r a . E . W . : Notes on Structured Programming. [Dahl72], 1 - 82 [ D i j k s t r a 7 5 ] D i j k s t r a . E . W . : Guarded Commands, Nondeterminacy and Formal Derivation of Programs. CACM 18, 8 ( 7 5 ) , 453 - 457 [ D i j k s t r a 7 6 ] D i j k s t r a , E . W . : A D i s c i p l i n e of Programming. Englewood C l i f f s : Prentice Hall 1976 [ D i j k s t r a 7 8 ] D i j k s t r a . E . W . : DoD-I: The Summing Up. SIGPLAN 13, 7 ( 7 8 ) , 21 - 26 [ D i j k s t r a 7 8 a ] D i j k s t r a . E . W . : On the BLUE Language submitted to the DoD. On the GREEN Language submitted to the DoD. On the Yellow Language submitted to the DoD. On the RED Language submitted to the DoD. SIGPLAN 13, 10 (78), 10 - 32 [D0D77] Department of Defense Requirements f o r High Order Computer Programming Languages. Revised "Ironman", July 77, The Technical Requirements. SIGPLAN 12, 12 (77), 39 - 54 [Dzida78] Dzida.W., Herda,S., Itzfeld,W.D.: User Perceived Quality of I n t e r a c t i v e Systems. [ICSE78], 188 - 195 auch: IEEE ToSE SE-4, 4 (78), 270 - 275 [Ehrig77] E h r i g , H . , Kreowski,H.-J., Padawitz.P.: Stepwise S p e c i f i c a t i o n and Implementation of Abstract Data Types. TU B e r l i n , FB Informatik, Nov. 77
294
Literatur
[Ehrig77a] E h r i g , H . , Kreowski,H.-J.: Some Remarks Concerning Correct S p e c i f i c a t i o n and Implementation of Abstract Data Types. TU B e r l i n , FB Informatik, Bericht 77-13, Aug. 77 [Endres75] Endres,A.: Planung und Durchführung von Programmierungsprojekten. S t u t t g a r t : Vorlesungsmanuskript U n i v e r s i t ä t S t u t t g a r t , 1975 [Endres75a] Endres,A.: An A n a l y s i s of Errors and t h e i r Causes i n System Programs. [ICRS75], 327 - 336 [Floyd67] Floyd,R.W.: A s s i g n i n g Meanings to Programs. Proceedings of Symposia in Applied Mathematics Vol. X I X , Mathematical Aspects of Computer Science: American Mathematical Society 1967, 19 - 31 [Geschke75] Geschke,C., M i t c h e l l , J . : On the Problem of Uniform References to Data S t r u c t u r e s . [ICRS75], 31 - 42 auch: IEEE ToSE SEXL, 2 (76), 207 - 219 [Goguen75] Goguen,J.A., Thatcher,J.W., Wagner,E.G., W r i g h t , J . B . : Abstract Data Types as I n i t i a l Algebras and Correctness of Data Representations. Proceedings Conference on Computer Graphics, Pattern Recognition and Data S t r u c t u r e , May 75, 89 - 93 [Goguen76] Goguen.J.A., Thatcher,J.W., Wagner,E.G.: An I n i t i a l Algebra Approach to the S p e c i f i c a t i o n , Correctness, and Implementation of Abstract Data Types. [Yeh76], 80 - 149 [Goguen77] Goguen,J.A., Tardo,J.: OBJ-O Preliminary Users Manual. Los Angeles: UCLA 1977 [Goguen78] Goguen.J.A.: Abstract Errors for Abstract Data Types. [Neuhold78], 491 - 525 [Goodenough75] Goodenough,J.D.: Structured Exception Handling. 2nd ACM Symposium on P r i n c i p l e s of Programming Languages. Palo A l t o : New York 1975, 204 - 224 [Goos73]
Goos,G.: Documentation. [Bauer73], 385 - 394
[Goos76] Goos,G.: Programmkonstruktion. Karlsruhe: Vorlesungsmanus k r i p t U n i v e r s i t ä t Karlsruhe 1976 [Goos76a] Goos,G.: E i n i g e Eigenschaften der Programmiersprache BALG. [Schneider76], 90 - 100 [Goos77] Goos,G., Kastens,U.: Programming Languages and the Design of Modular Programs. [Hibbard78], 153 - 186 [GREEN78] Reference Manual for the GREEN Programming Language. A Language Designed i n Accordance with the Ironman Requirements. Feb. 78
Literatur
295
[Gries76] Gries,D.: An Illustration of Current Ideas on the Derivation of Correctness Proofs and Correct Programs. IEEE ToSE SE-2, 4 (76), 238 - 243 [Gries78] Gries,D.: The Multiple Assignment Statement. IEEE ToSE SE-4, 2 (78), 89 - 93 [Guttag75] Guttag,J.V.: Dyadic Specification and Its Impact on Reliability. Technical Report CSRG 45, University of Toronto 1975, 48 - 87 [Guttag76] Guttag,J.V.: Abstract Data Types and the Development of Data Structures. Conference on Data, Salt Lake City 1976 CACM 20, 6 (77), 396 - 404 [Guttag77] Guttag,J.V., Horowitz,E., Musser.D.: Some Extensions to Algebraic Specifications. SIGPLAN 12, 3 (77), 63 - 67 [Hackl75] Hackl,K.E.: Programming Methodology. 4th Informatik Symposium, IBM Germany, Wildbad 1974. Berlin (West): Springer Verlag 1975, LNCS 23 [Halstead77] Halstead.M.H.: Elements of Software Science. New York: Elsevier North-Holland 1977 [Heuermann74] Heuermann,A., Myers,G.J., Winterton,J.H.: Automated Test and Verification. IBM Technical Disclosure Bulletin, U , 7 (74), 2030 - 2035 [Hibbard78] Hibbard.P.G., Schuman.S. (eds.): Constructing Quality Software. Proceedings of the IFIP-TC2 Conference, Novosibirsk 1977. Amsterdam: North-Holland Publishing Company 1978 [Hoare69] Hoare.C.A.R.: An Axiomatic Basis for Computer Programming. CACM 12, 10 (69), 576 - 580, 583 [Hoare72]
Hoare.C.A.R.: Notes on Data Structuring. [Dahl72], 83-174
[Hoare73] Hoare.C.A.R., Wirth.N.: An Axiomatic Definition of the Programming Language PASCAL. Acta Informatica 2, (73), 335 - 355 [Hoare75]
Hoare.C.A.R.: Data Reliability. [ICRS75], 528 - 533
[Hoener75] Hoener.M., Weber,R.: Die Anwendung von PSL, veranschaulicht anhand der Beschreibung eines Zielsystems unter Benutzung der Top-down-Analyse. ISD0S Working Paper No. 116, Ann Arbor: University of Michigan 1975 [Holt76] Holt.A.W.: Net Models of Organizational Systems in Theory and Practice. Wakefield, Mass.: Massachusetts Computer Associates, INC 1976 [Hommel78] Hommel,G., Jäckel,J., Jähnichen.S., Kleine,K., Koch,W.: Beschreibung der Programmiersprache ELAN. Version 2.3. TU Berlin, FB Informatik, Bericht 78-26, Aug. 78
296
Literatur
[Howden75] Howden.W.E.: Methodology for the Generation of Program Test Data. IEEE ToC C-24, 5 (75), 554 - 559 [Howden77] Howden.W.E.: Symbolic Testing and the DISSECT Symbolic Evaluation System. IEEE ToSE SE-3, 4 (77), 266 - 278 [Howden78] Howden.W.E.: Theoretical and Empirical Studies of Program Testing. [ICSE78], 305 - 311 [IBM74] HIPO - A Design Aid and Documentation Technique. IBM Order No. GC20-1851-1, 1974 [Ichbiah74] Ichbiah.J.D., Héliard.J.C., Rissen,J.P., Cousot.P.: The System Implementation Language LIS. Reference Manual. C I I Technical Report 4549E/EN: Louveciennes, Dec. 74 [ICRS75] Proceedings International Conference on Reliable Software, Los Angeles. New York: Institute of Electrical and Electronics Engineers 1975, IEEE Cat. No. 75 CHO 940-7CSR [ICSE76] Proceedings 2nd International Conference on Software Engineering, San Francisco. New York: Institute of Electrical and Electronics Engineers 1976, IEEE Cat. No. 76 CH1125-4C [ICSE78] Proceedings 3rd International Conference on Software Engineering, Atlanta, USA. New York: Institute of Electrical and Electronics Engineers 1978, IEEE Cat. No. 78CH1317-7C [IEEE77] Special Collection on Requirement Analysis. IEEE ToSE SE-3, 1 (77) [Infotech76] Structured Programming. Infotech State of the Art Report. Maidenhead/Berkshire: Infotech International 1976 [Infotech76a] Program Optimization. Infotech State of the Art Report. Maidenhead/Berkshire: Infotech International 1976 [Jackson75] Jackson,M.A.: Principles of Program Design. London: Academic Press 1975 [Jackson76] Jackson,M.A.: Data Structure as a Basis for Program Design. [Infotech76], 279 - 292 [Kerutt77] Kerutt,H.: Programmieren in CDL2 (Einführung für ALG0L60Kenner). Version 2.2. TU B e r l i n , FB Informatik, Bericht 77-24, Dez. 77 [King75] King,J.C.: A New Approach to Program Testing. [ICRS75], 228 - 233 [King76] King,J.C.: Symbolic Execution and Program Testing. CACM 19, 7 (76), 385 - 394 [Knuth71] Knuth.D.E.: An Empirical Study of FORTRAN Programs. Software P&E J., 2 (71), 105 - 133 [Knuth74] Knuth,D.E.: Structured Programming with Goto Statements. Computing Surveys 6, 4 (74), 261 - 301
Literatur
297
[Koster74] Koster,C.H.A.: Draft of CF Syntax of CDL2. TU Berlin, Internes Arbeitspapier, Nov. 74 [Koster76] Koster,C.H.A.: Visibility and Types. SIGPLAN 11, Special Issue Proceedings Conference on Data, Salt LäT