LE 32

Werbung
LE 32
1
Software-Technik
4 Die
Implementierungsphase
Prof. Dr. Helmut Balzert
Lehrstuhl für Software-Technik
Ruhr-Universität Bochum
© Helmut Balzert 1998
LE 32
2
E ni füh rung und Ü be rb lci k
LE 1
V U n te rnehm en sm ode llei rung
1 G rund al gen
2 O b ej k to rei n tei r te
U n te rnehm en sm ode llei rung
LE 24
LE 25
2 LE
IIISW Q
- ua litä ts s ci he rung
ISW -E n w
t ci k ul ng
IISW M
- anagem en t
1 G rund al gen
1 D ei P al nung spha se
1 G rund al gen
LE 2 – 3
LE 1
2 P al nung
2 Q ua litä ts s ci he rung
2 D ei D e fni itoi n spha se
LE 2
3 O rgan si a toi n
LE 4 – 22
LE 10
LE 23 – 31
3 M anue lel
P rüm
f e thoden
LE 11
3 D ei E n w
t u r fspha se
LE 3 – 4
4 P e rsona l
4 D ei m
I p elm en tei rung spha se
LE 5
5 Le itung
6 K on tro lel
LE 12 – 13
LE 33
5 P rodu k tqua litä t
– K om ponen ten
LE 14 – 17
6 D ei W a r tung s -& P fel gepha se
LE 8
4 P ro zeß qua litä t
LE 32
5 D ei A bnahm e - und
E ni füh rung spha se
LE 6 – 7
LE 9
LE 33
8 LE
6 P rodu k tqua litä t
– S ys tem e
LE 18 – 19
32 LE
11 LE
V
I Q ue rschn itte und A u sb lci ke
1 P rni z pi ei n
& M e thoden
LE 20
2
LE 21
3 W ei de rve w
r e n d un g
LE 22
4 S an ei rung
LE 23
4 LE
LE 32
3
Lernziele
 Ein gegebenes Programm überprüfen
können, ob die vorgestellten
Implementierungsprinzipien eingehalten
wurden
 Die vorgestellten
Implementierungsprinzipien bei der
Erstellung eines Programms
berücksichtigen können
 Die Methode der schrittweisen
Verfeinerung bei der Erstellung eines
Programms anwenden können
 Die beschriebenen Regeln und Richtlinien
bei der Erstellung von Programmen
beachten können.
LE 32
4
Inhalt
4 Die Implementierungsphase
4.1 Einführung und Überblick
4.2 Prinzipien der Implementierung
4.2.1 Prinzip der Verbalisierung
4.2.2 Prinzip der problemadäquaten Datentypen
4.2.3 Prinzip der Verfeinerung
4.2.4 Prinzip der integrierten Dokumentation
4.3 Methode der schrittweisen Verfeinerung
4.4 Zur Psychologie des Programmierens
4.5 Selbstkontrolliertes Programmieren
4.6 Programmierrichtlinien am Beispiel von C++.
LE 32
5
4 Die Implementierungsphase
 Zur Historie
 Prof. Dr. Niklaus Wirth
*15.2.1934 in Winterthur, Schweiz
Professor an der ETH Zürich
 Erfinder der Programmiersprachen
Pascal (1970), Modula-2 (1982)
und Oberon (1989)
 Erfinder der Computersysteme Lilith (1980)
und Ceres (1986) zusammen mit ihren
Betriebssystemen
 Wegbereiter der strukturierten
Programmierung und der schrittweisen
Verfeinerung.
LE 32
6
4.1 Einführung und Überblick
 Aufgabe des Programmierens:
 Aus vorgegebenen Spezifikationen für eine
Systemkomponente die Systemkomponente zu
implementieren, d.h. die geforderten
Leistungen in Form eines oder mehrerer
Programme zu realisieren

Implementierungsphase:
 Durchführung der Programmiertätigkeiten
 Eingebettet zwischen


der Entwurfsphase und
der Abnahme- und Einführungsphase.
LE 32
7
4.1 Einführung und Überblick
 Voraussetzungen
 In der Entwurfsphase wurde eine Software-
architektur entworfen, die zu geeigneten
Systemkomponenten geführt hat
 Abhängig von der Entwurfsmethode kann eine
Systemkomponente folgendermaßen aussehen:
 Strukturierter Entwurf:
 funktionales Modul

Modularer Entwurf:
 funktionales Modul
 Datenobjekt-Modul
 Datentyp-Modul

Objektorientierter Entwurf:
 Klassen.
LE 32
8
4.1 Einführung und Überblick
 Voraussetzungen
 Für jede Systemkomponente existiert eine
Spezifikation
 Die Softwarearchitektur ist so ausgelegt, daß die
Implementierungen umfangsmäßig pro Funktion,
Zugriffsoperation bzw. Operation wenige Seiten
nicht überschreiten.
LE 32
9
4.1 Einführung und Überblick
LE 32
10
4.1 Einführung und Überblick
 Aktivitäten
 Konzeption von Datenstrukturen und Algorithmen
 Strukturierung des Programms durch geeignete





Verfeinerungsebenen
Dokumentation der Problemlösung und der
Implementierungsentscheidungen
Umsetzung der Konzepte in die Konstrukte der
verwendeten Programmiersprache
Angaben zur Zeit- und Speicherkomplexität
Test oder Verifikation des Programmes einschl.
Testplanung und Testfallerstellung
Auch »Programmieren im Kleinen« genannt.
LE 32
11
4.1 Einführung und Überblick
 Teilprodukte
 Quellprogramm einschl. integrierter Dokumentation
 Objektprogramm
 Testplanung und Testprotokoll bzw.
Verifikationsdokumentation

Alle Teilprodukte aller Systemkomponenten
müssen integriert und einem Systemtest
unterzogen werden.
LE 32
12
4.1 Einführung und Überblick
 Basiskonzepte zur Implementierung der
Systemkomponenten:
 Kontrollstrukturen
nur »lineare Kontrollstrukturen«
 »Strukturiertes Programmieren i. e. S.«
 Entscheidungstabellen.

LE 32
13
4.2 Prinzipien der Implementierung
 Bei der Implementierung sollten folgende
Prinzipien eingehalten werden:
 Prinzip der Verbalisierung
 Prinzip der problemadäquaten Datentypen
 Prinzip der Verfeinerung
 Prinzip der integrierten Dokumentation.
LE 32
14
4.2.1 Prinzip der Verbalisierung
 Verbalisierung
 Gedanken und Vorstellungen in Worten
ausdrücken und damit ins Bewußtsein bringen

Gute Verbalisierung
 Aussagekräftige, mnemonische
Namensgebung
 Geeignete Kommentare
 Selbstdokumentierende Programmiersprache.
LE 32
15
4.2.1 Prinzip der Verbalisierung
 Bezeichnerwahl
 Problemadäquate Charakterisierung
Konstanten, Variablen, Prozeduren usw.
 Soll die Funktion dieser Größe bzw. ihre
Aufgabe zum Ausdruck bringen
 Bezeichner, die problemfrei sind oder
technische Aspekte z.B. der Repräsentation
bezeichnen, sind zu vermeiden
 Die in der Mathematik übliche Verwendung
einzelner Buchstaben ist ebenfalls ungeeignet.
LE 32
16
4.2.1 Prinzip der Verbalisierung
 Beispiel 1

Feld1, Feld2, Zaehler
 problemfreie, technische Bezeichner


Besser:
Messreihe1, Messreihe2, Anzahlzeichen
 problembezogene Bezeichner
 Beispiel 2

P = G2 + Z1 * D
 Bezeichner ohne Aussagekraft, zu kurz


Besser:
Praemie = Grundpraemie2 + Zulage1 *
Dienstjahre;.
LE 32
17
4.2.1 Prinzip der Verbalisierung
 Beispiel 3

Praemie = 50.0 + 10.0 * Dienstjahre
 unverständliche Konstanten




Besser:
const float Grundpraemie2 = 50.0;
const float Zulage1 = 10.0;
Praemie = Grundpraemie2 + Zulage1 *
Dienstjahre;
 Durch die Verwendung benannter Konstanten wird
die Lesbarkeit eines Programmes deutlich
verbessert
 Zusätzlich wird das Programm dadurch auch
änderungsfreundlicher.
LE 32
18
4.2.1 Prinzip der Verbalisierung
 Kurze Bezeichner sind nicht aussagekräftig und
außerdem wegen der geringen Redundanz
anfälliger gegen Tippfehler und Verwechslungen
 Der erhöhte Schreibaufwand durch lange
Bezeichner wird durch die Vorteile mehr als
ausgeglichen.
LE 32
19
4.2.1 Prinzip der Verbalisierung
 Verbalisierung wird durch geeignete
Kommentare unterstützt
 Als vorteilhaft hat sich erwiesen, den else-Teil
einer Auswahl zu kommentieren
 Als Kommentar wird angegeben, welche
Bedingungen im else-Teil gelten
 Beispiel
if (A < 5)
{ ...}
else
//A >= 5
{ ...}.
LE 32
20
4.2.1 Prinzip der Verbalisierung
 Kurzkommentare sollten vermieden
werden
 Die in ihnen enthaltene Information ist meist
besser in Namen unterzubringen
 Beispiel
 I = I + 1;
//I wird um Eins erhöht


Besser:
Lagermenge = Lagermenge + 1;
Besonders wichtige Kommentare
 In einen Kommentarkasten.
LE 32
21
4.2.1 Prinzip der Verbalisierung
 Grad der Kommentierung
 Abhängig davon, ob Programmiersprache
selbstdokumentierend
 ADA gilt in dieser Beziehung als vorbildlich
 C++ ist ein schlechtes Beispiel

Vorteile der Verbaliserung
+
+
+
Leichte Einarbeitung in fremde Programme
bzw. Wiedereinarbeitung in eigene Programme
Erleichtert »code reviews«, Modifikationen und
Wartung
Verbesserte Lesbarkeit der Programme.
LE 32
22
4.2.2 Prinzip der problemadäquate Datentypen
 Problemadäquate Datentypen
 Daten- und Kontrollstrukturen eines Problems
sollen sich in der programmiersprachlichen
Lösung möglichst unverfälscht widerspiegeln
 Programmiersprache sollte folgende
Eigenschaften bezogen auf Datenstrukturen
besitzen:
 Umfangreiches Repertoire an Basistypen
 Geeignete Typkonstruktoren
 Benutzerdefinierbare Typen.
LE 32
23
4.2.2 Prinzip der problemadäquate Datentypen
 Aufgabe des Programmierers
 Angebot an Konzepten einer
Programmiersprache optimal zur
problemnahen Lösungsformulierung
verwenden

Regeln:
 Können die Daten durch Basistypen
beschrieben werden, dann ist der geeignete
Basistyp auszuwählen
 Der Wertebereich sollte so festgelegt
werden, daß er möglichst genau das
Problem widerspiegelt – unter Umständen
durch Einschränkungen des Basistyps.
LE 32
24
4.2.2 Prinzip der problemadäquate Datentypen
 Beispiele

enum FamilienstandT {ledig,
verheiratet, geschieden, verwitwet};
enum GeschlechtT {weiblich, maennlich};
enum AmpelT {rot, gruen, gelb};
enum SteuerschluesselT {ohne_Steuer,
Vorsteuer, halbe_MwSt,volle_MwSt};
enum WeinpraedikateT {Kabinett,
Spaetlese, Auslese, Beerenauslese,
Trockenbeerenauslese};
enum BauteiltypT {R, L, C, U, I};

n! ist nur für nichtnegative ganze Zahlen definiert





 Der Basistyp sollte entsprechend gewählt werden:
 unsigned long NFAK (unsigned long n);.
LE 32
25
4.2.2 Prinzip der problemadäquate Datentypen
 Typkonstruktor Feld verwenden, wenn:
a Zusammenfassung gleicher Datentypen
b Zugriff wird dynamisch berechnet (während der
Laufzeit)
c Hohe Komponentenanzahl möglich
d Feldgrenzen statisch, dynamisch oder
unspezifiziert
e Mittlere Zugriffszeit auf eine Komponente,
unabhängig vom Wert des Index
f Als zugehörige Kontrollstruktur wird die zählende
Wiederholung eingesetzt
 Beispiel
 In einem Textsystem wird eine Textseite
folgendermaßen beschrieben:
 typedef char TextzeileT[Zeilenlaenge];
 typedef TextzeileT TextseiteT[Zeilenanzahl];
LE 32
26
4.2.2 Prinzip der problemadäquate Datentypen
 Typkonstruktor Verbund verwenden, wenn:
a Zusammenfassung logischer Daten mit
unterschiedlichen Typen
b Zugriff wird statisch berechnet (zur
Übersetzungszeit)
c Anzahl der Komponenten ist immer fest
d Jede Komponente ist einzeln benannt, daher ist
der Umfang begrenzt
e Kurze Zugriffszeit auf Komponente erwünscht
f Bei varianten Verbunden ist die Mehrfachauswahl die geeignete Kontrollstruktur.
LE 32
27
4.2.2 Prinzip der problemadäquate Datentypen
 Typkonstruktor Verbund: Beispiele

Der Datentyp eines Adreßverwaltungsprogramms
sieht problemadäquat folgendermaßen aus:
struct AdresseT
const char * Strasse;
int PLZ;
const char * Wohnort;

Es sollen komplexe Zahlen verarbeitet werden;
der geeignete Datentyp ist:
struct ComplexT
{
float Re, Im;
};.
LE 32
28
4.2.2 Prinzip der problemadäquate Datentypen
 Vorteile problemadäquater Datentypen
+
+
+
Gut verständliche, leicht lesbare,
selbstdokumentierende und wartbare
Programme
Statische und dynamische Typprüfungen
verbessern die Qualität des jeweiligen
Programms
Die Daten des Problems werden 1:1 in
Datentypen des Programms abgebildet, d.h.
Wertebereiche werden weder über- noch
unterspezifiziert.
LE 32
29
4.2.2 Prinzip der problemadäquate Datentypen
 Voraussetzungen:
 Möglichst optimale Unterstützung durch
geeignete Konzepte in der verwendeten
Programmiersprache
 Anwendung des Prinzips der Verbalisierung,
insbesondere bei der Namenswahl
 Bei fehlenden modernen Sprachkonzepten
ausführliche Kommentierung
 Definition geeigneter Datenabstraktionen bzw.
Klassen mit problemangepaßten Operationen.
LE 32
30
4.2.3 Prinzip der Verfeinerung
 Verfeinerung
 Dient dazu, ein Programm durch
Abstraktionsebenen zu strukturieren

Verfeinerungsstruktur kann auf 2 Arten im
Quellprogramm sichtbar gemacht werden
 Die oberste Verfeinerungsebene – bestehend
aus abstrakten Daten und abstrakten
Anweisungen – ist kompakt beschrieben
 Die Realisierung jeder Verfeinerung wird an
anderer Stelle beschrieben
 Alle Verfeinerungsebenen sind substituiert
 Die übergeordneten Verfeinerungen werden
als Kommentare gekennzeichnet.
LE 32
31
4.2.3 Prinzip der Verfeinerung
 Ein Algorithmus soll aus zwei eingegebenen
Daten die Anzahl der Zinstage berechnen:
void berechneZinstage()
{
int Datum1, Datum2; // Eingabedaten, Form TTMM
int Tage; // Ausgabedaten
// Voraussetzung:Daten liegen innerhalb eines Jahres
// Algorithmus ------------------------------------// Eingabe
// Aufgliederung in Tag und Monat
// Beruecksichtigung der Sonderfaelle
// Berechnung der Tage
// Ausgabe
}.
LE 32
32
4.2.3 Prinzip der Verfeinerung
// refinements: 1. Verfeinerung:
// Eingabe
cout << "1. Zinsdatums:"; cin >> Datum1;
cout << "2. Zinsdatums:"; cin >> Datum2; cout << endl;
// Aufgliederung in Tag und Monat
// Aufgliederung in Tag
// Aufgliederung in Monat
// Beruecksichtigung der Sonderfaelle
if (Tag1 == 31) Tag1 = 30;
if (Tag2 == 31) Tag2 = 30;
// Berechnung der Tage
Tage = (Monat2 - Monat1) * 30 + Tag2 - Tag1;
// Ausgabe
if (Tage < 0) cout << "Fehler: 1. Datum liegt vor dem
2. Datum!“ << endl;
else cout << "Anzahl der Zinstage = " << Tage;.
LE 32
33
4.2.3 Prinzip der Verfeinerung
// refinements: 2. Verfeinerung:
// Aufgliederung in Tag und Monat
// Aufgliederung in Tag
int Tag1, Tag2; // Hilfsgroessen
Tag1 = Datum1 / 100;
Tag2 = Datum2 / 100;
// Aufgliederung in Monat
int Monat1, Monat2; // Hilfsgroessen
Monat1 = Datum1 % 100;
Monat2 = Datum2 % 100;
// end berechneZinstage()
+
+
Sowohl der Kern des Algorithmus als auch die
Verfeinerungsstufen sind deutlich sichtbar
Die Verfeinerungen muß man sich nur ansehen,
wenn man sich für die Details interessiert.
LE 32
34
4.2.3 Prinzip der Verfeinerung
 Da die meisten Programmiersprachen eine
solche Darstellung nicht erlauben, müssen die
Verfeinerungen substituiert werden:
void berechneZinstage()
{
int Datum1, Datum2; // Eingabedaten, Form TTMM
int Tage; // Ausgabedaten
//Voraussetzung: Die Daten liegen innerhalb eines Jahres
int Tag1, Tag2; // Hilfsgroessen
int Monat1, Monat2; // Hilfsgroessen
// Eingabe
cout << "1. Zinsdatums:"; cin >> Datum1;
cout << “2. Zinsdatums:"; cin >> Datum2; cout << endl;.
LE 32
35
4.2.3 Prinzip der Verfeinerung
// Aufgliederung in Tag und Monat
// Aufgliederung in Tag
Tag1 = Datum1 / 100;
Tag2 = Datum2 / 100;
// Aufgliederung in Monat
Monat1 = Datum1 % 100;
Monat2 = Datum2 % 100;
// Beruecksichtigung der Sonderfaelle
if (Tag1 == 31) Tag1 = 30;
if (Tag2 == 31) Tag2 = 30;
// Berechnung der Tage
Tage = (Monat2 - Monat1) * 30 + Tag2 - Tag1;
// Ausgabe
if (Tage < 0) cout << "Fehler: 1. Datum liegt vor
dem 2. Datum!" << endl;
else cout << "Anzahl der Zinstage = " << Tage;.
LE 32
36
4.2.3 Prinzip der Verfeinerung
 In dieser Notation werden in Kommentarform die
Verfeinerungsschichten aufgeführt
 Durch Einrücken nach rechts kann man die Tiefe
der Verfeinerung hervorheben
 Bei diesen Kommentaren handelt es sich um
Verfeinerungen, die bei der Konzeption des
Programms entstanden sind und nur in
Kommentarform notiert werden.
LE 32
37
4.2.3 Prinzip der Verfeinerung
 Vorteile
+
+
+
+
+
Der Entwicklungsprozeß ist im Quellprogramm
dokumentiert
Leichtere und schnellere Einarbeitung in ein
Programm
+ Die Details können zunächst übergangen
werden
Die Entwicklungsentscheidungen können besser
nachvollzogen werden
Die oberen Verfeinerungsschichten können in
natürlicher Sprache formuliert werden
Ein Programm wird zweidimensional strukturiert:
sowohl durch Kontrollstrukturen als auch durch
Verfeinerungsschichten.
LE 32
38
4.2.4 Prinzip der integrierten Dokumentation
 Dokumentation
 Integraler Bestandteil jedes Programms

Ziele der Dokumentation:
 Angabe von Verwaltungsinformationen
 Erleichterung der Einarbeitung
 Beschreibung der Entwicklungs-
entscheidungen
 Warum wurde welche Alternative gewählt?.
LE 32
39
4.2.4 Prinzip der integrierten Dokumentation
 Richtlinie
 Folgende Verwaltungsinformationen sind am
1
2
3
4
5
Anfang eines Programms aufzuführen
(Vorspann des Programms):
Name des Programms
Name des bzw. der Programmautoren (Vorund Nachname)
Versionsnummer, Status und Datum
Aufgabe des Programms:
Kurzgefaßte Beschreibung
Zeit- und Speicherkomplexität in O-Notation.
LE 32
40
4.2.4 Prinzip der integrierten Dokumentation
 Versionsnummer besteht aus 2 Teilen:
 Release-Nummer
I. allg. einstellig
 Steht, getrennt durch einen Punkt, vor der
Level-Nummer (maximal zweistellig)
 Vor der Release-Nummer steht ein V
 Level-Nummer
 Beispiel: V1.2


Die Version kennzeichnet zusammen mit
dem Status den Bearbeitungszustand des
Programms.
LE 32
41
4.2.4 Prinzip der integrierten Dokumentation
 Bearbeitungszustände (V-Modell)
 geplant
Ein neues Programm enthält die
Versionsnummer 1.0 und den Status
»geplant«
 in Bearbeitung
 Das Programm befindet sich im privaten
Entwicklungsbereich des Implementierers
oder unter seiner Kontrolle in der
Produktbibliothek.

LE 32
42
4.2.4 Prinzip der integrierten Dokumentation
 vorgelegt
Das Programm ist aus Implementierersicht fertig
und wird in die Konfigurationsverwaltung
übernommen
 Vom Status »vorgelegt« ab führen Modifikationen
zu einer Fortschreibung der Versionsangabe
 akzeptiert
 Das Programm wurde von der Qualitätssicherung
überprüft und freigegeben
 Es darf nur innerhalb einer neuen Version
geändert werden.

LE 32
43
4.2.4 Prinzip der integrierten Dokumentation
 Beispiel: Programmvorspann
// Programmname: XYZ
//****************************************************
// Autor 1: Vorname Nachname
// Autor 2: Vorname Nachname
//****************************************************
// Version: V1.0 in Bearbeitung 4.4.96
//
vorgelegt 6.4.96
//
akzeptiert 7.4.96
//
V1.1 in Bearbeitung 15.4.96
//
vorgelegt 16.4.96
//****************************************************
// Aufgabe: Aufgabenbeschreibung
// Zeitkomplexitaet: O(n log 2n)
// Speicherkomplexitaet O(2n).
LE 32
44
4.2.4 Prinzip der integrierten Dokumentation
 Dokumentation muß integraler Bestandteil der
Software-Entwicklung sein:
 Bei einer Nachdokumentation am Ende der
Codeerstellung sind wichtige Informationen, die
während der Entwicklung angefallen sind, oft nicht
mehr vorhanden
 Entwicklungsentscheidungen (z.B.: Warum wurde
welche Alternative gewählt?) müssen dokumentiert
werden, um bei Modifikationen und
Neuentwicklungen bereits gemachte Erfahrungen
auswerten zu können
 Der Aufwand für die Dokumentation wird reduziert,
wenn zu dem Zeitpunkt, an dem die Information
anfällt von demjenigen, der sie erzeugt oder
verarbeitet, auch dokumentiert wird.
LE 32
45
4.2.4 Prinzip der integrierten Dokumentation
 Das Prinzips der integrierten Dokumentation...
+
+
+
+

reduziert den Aufwand zur Dokumentenerstellung,
stellt sicher, daß keine Informationen
verlorengehen,
garantiert die rechtzeitige Verfügbarkeit der
Dokumentation,
erfordert die entwicklungsbegleitende
Dokumentation
Eine gute Dokumentation bildet die
Voraussetzung für
 leichte Einarbeitung in ein Produkt bei
Personalwechsel oder durch neue Mitarbeiter
 gute Wartbarkeit des Produkts.
LE 32
46
4.3 Methode der schrittweisen Verfeinerung
 Schrittweise Verfeinerung
(stepwise refinement)
 Bietet einen methodischen Rahmen für die
Anwendung der allgemeinen top-downMethode auf das »Programmieren im Kleinen«
 Im Gegensatz zum Prinzip der Verfeinerung,
das das zu erreichende Ziel beschreibt, gibt
die schrittweise Verfeinerung einen
methodischen Ansatz an, wie man dieses Ziel
erreicht
 Ausgehend von einer gegebenen und
präzisen Problemstellung z.B. in Form einer
Systemkomponenten-Spezifikation wird eine
Problemlösung mit abstrakten Daten und
abstrakten Anweisungen formuliert.
LE 32
47
4.3 Methode der schrittweisen Verfeinerung
 Beispiel:
 Zur Konzentration der Einkaufs-, Vertriebs-
und Marketingstrategie wird in einer
Weinhandlung in vierteljährlichen Abständen
eine Statistik benötigt, die die
Umsatzverteilung der Weinanbaugebiete
angibt
 Die Statistik soll den Umsatz in DM sowie die
prozentuale Veränderung gegenüber der
letzten Statistikperiode pro Anbaugebiet
angeben.
LE 32
48
4.3 Methode der schrittweisen Verfeinerung
 Datentypen und Prozeduren:
// Typ fuer Artikelnummern
typedef unsigned short ArtikelNrT;
typedef float DMarkT; // Typ fuer Deutsche Mark
enum GebietT {BADEN, RHEINPFALZ, WUERTTEMBERG,
FRANKEN, RHEINHESSEN, HESSISCHEBERGSTR, RHEINGAU,
NAHE, MITTELRHEIN, MOSELSAARRUWER, AHR, AUSLAND};
const int ANZAHLGEBIETE = AUSLAND + 1
// Liste mit Umsatz in DM fuer jedes Anbaugebiet
typedef DMarkT UmsatzlisteT[ANZAHLGEBIETE];
// Prozeduren
void ErmittleWeinumsatz(int ArtikelNr, DMarkT
&ViertelJahresUmsatz, GebietT &Anbaugebiet,
bool &ArtikelNrBelegt);
void UmsatzAltLesen(UmsatzlisteT &UListe);
void UmsatzAltAktualisieren(UmsatzlisteT &UListe);.
LE 32
49
4.3 Methode der schrittweisen Verfeinerung
1. Schritt: abstrakte Problemlösung
 Es werden problemspezifische Typen, Daten und
Anweisungen eingeführt und in verbaler Form
beschrieben
 Die Typen und Daten sollen problemrelevante
Aspekte benennen
 Die Operationen sollen Teilaufgaben abgrenzen
 Beides soll losgelöst von programmiersprachlichen
Formulierungen erfolgen.
LE 32
50
4.3 Methode der schrittweisen Verfeinerung
1. Schritt: abstrakte Problemlösung
//
//
//
//
//
//
Programmname: UmsatzProAnbaugebiet
Aufgabe: ....
Importierte Typen:
ArtikelNrT, DMarkT, GebietT, UmsatzListeT
Importierte Prozeduren:
ErmittleWeinumsatz, UmsatzAltLesen,
UmsatzAltAktualisieren
// Datenstrukturen ---------------------------------// TabelleUmsatzProGebiet
// Algorithmus -------------------------------------// Vorbereitung
(1)
// Weinumsatz pro Anbaugebiet ermitteln
(2)
// Vergleich mit Umsatzstatistik der Vorperiode
(3)
// Umsatzstatistik Vorperiode durch neue ersetzen (4)
// Druckaufbereitung und Ausgabe
(5).
LE 32
51
4.3 Methode der schrittweisen Verfeinerung
2. Schritt: Verfeinerung der abstrakten
Problemlösung
 Typen, Daten und Anweisungen werden verfeinert
konkretisiert, präzisiert, detailliert, formalisiert
 Typen und Daten werden unmittelbar oder mittelbar
durch Konstruktionskonzepte auf Standardtypen
abgebildet
 Anweisungen werden auf primitivere Anweisungen
oder Standardfunktionen, -prozeduren oder
Datenabstraktionen zurückgeführt
 Reihenfolge der Verfeinerungen so wählen, daß
zunächst die abstrakten Anweisungen bearbeitet
werden, die auf Auswahl- oder
Wiederholungsanweisungen abgebildet werden.

LE 32
52
4.3 Methode der schrittweisen Verfeinerung
// Verfeinerung der Datenstrukturen -----------------// Datentyp fuer Tabelle mit
// - Umsatz in DM und
// - prozentuale Veraenderung pro Anbaugebiet
//
struct UmsatzT
{
DMarkT UmsatzNeu;
int ProzentAbweichung;
};
typedef struct UmsatzT UmsatzTabT[AnzahlGebiete];
// Variablendefinition:
UmsatzTabT TabelleUmsatzProGebiet;.
LE 32
53
4.3 Methode der schrittweisen Verfeinerung
//
//
//
//
//
//
//
//
//
//
//
//
Verfeinerung der abstrakten Anweisungen-----------Vorbereitung
(1)
Weinumsatz pro Anbaugebiet ermitteln:
(2)
for alle Weinartikel do
(2a)
Weinumsatz pro Artikel lesen;
(2b)
Umsatz in TabelleUmsatzProGebiet aufaddieren; (2c)
Vergleich mit Umsatzstatistik der Vorperiode: (3)
Alte Umsatzstatistik lesen
(3a)
for alle Anbaugebiete do
(3b)
Umsatzwerte vergleichen;
(3c)
Prozentuale Abweichung ausrechnen und
(3d)
in TabelleUmsatzProGebiet speichern;.
LE 32
54
4.3 Methode der schrittweisen Verfeinerung
// Umsatzstatistik Vorperiode durch neue ersetzen: (4)
// Neue Werte in alte Liste eintragen
(4a)
// UmsatzAltAktualisieren(UmsatzListeAlt);
(4b)
// Druckaufbereitung und Ausgabe:
(5)
// Drucke Ueberschrift;
(5a)
// for alle Anbaugebiete do
(5b)
// Drucke(Anbaugebiet, Umsatz, Prozentabweichung).
LE 32
55
4.3 Methode der schrittweisen Verfeinerung
3. Schritt: Weitere Verfeinerung
 Die sukzessive Zerlegung oder Verfeinerung
endet, wenn alle Typen, Daten und Anweisungen
in Termen der verwendeten Programmiersprache
beschrieben sind
 Nach jeder Verfeinerung können die verfeinerten
Teile in die Problemlösung der übergeordneten
Verfeinerungsschicht substituiert werden
 Die abstrakten Typen, Daten und Anweisungen
werden dann zu Kommentaren.
LE 32
56
4.3 Methode der schrittweisen Verfeinerung
3. Schritt: Weitere Verfeinerung
// benoetigte Variablen
ArtikelNrT ArtikelNr;
DMarkT Umsatz;
GebietT Gebiet;
UmsatzlisteT UmsatzListeAlt;
UmsatzTabT TabelleUmsatzProGebiet;
bool Ok;
char *GText[]={ „Baden“, „Rheinpfalz“,
„Württemberg“, „Franken“,
„Rheinhessen“, „Hessischebergstr”,
„Rheingau“, „Nahe“, „Mittelrhein“,
„Moselsaarruwer“, „Ahr“, „Ausland“ };.
LE 32
57
4.3 Methode der schrittweisen Verfeinerung
3. Schritt: Weitere Verfeinerung
// Umsetzung der verbal beschriebenen Anweisungen
// in die verwendete Zielsprache
// Vorbereitung:
(1)
for (Gebiet = BADEN; Gebiet <= AUSLAND; Gebiet++)
{
TabelleUmsatzProGebiet[Gebiet].UmsatzNeu=0.0;
TabelleUmsatzProGebiet[Gebiet].ProzentAbweichung=0;
}
// for alle Weinartikel do:
(2a)
for (ArtikelNr = 1; ArtikelNr <= 2000; ArtikelNr++)
// Weinumsatz pro Artikel lesen:
(2b)
ErmittleWeinumsatz(ArtikelNr, Umsatz, Gebiet, Ok);
// Umsatz in TabelleUmsatzProGebiet aufaddieren: (2c)
if (Ok)
LE 32
58
4.3 Methode der schrittweisen Verfeinerung
3. Schritt: Weitere Verfeinerung
// Alte Umsatzstatistik lesen:
(3a)
UmsatzAltLesen(UmsatzListeAlt);
// for alle Anbaugebiete do:
(3b)
for (Gebiet = BADEN; Gebiet <= AUSLAND; Gebiet++)
// Umsatzwerte vergleichen;
(3c)
// Prozentuale Abweichung ausrechnen
// und in TabelleUmsatzProGebiet speichern:
(3d)
TabelleUmsatzProGebiet[Gebiet].ProzentAbweichung =
TabelleUmsatzProGebiet[Gebiet].UmsatzNeu * 100 /
UmsatzListeAlt[Gebiet] - 100;.
LE 32
59
4.3 Methode der schrittweisen Verfeinerung
3. Schritt: Weitere Verfeinerung
// Neue Werte in alte Liste eintragen:
(4a)
for (Gebiet = BADEN; Gebiet <= AUSLAND; Gebiet++)
UmsatzListeAlt[Gebiet] =
TabelleUmsatzProGebiet[Gebiet].UmsatzNeu;
// Drucke Ueberschrift:
(5a)
cout << "Umsatzstatistik nach Anbaugebieten" << endl;
// Drucke(Anbaugebiet,Umsatz, Prozentabweichung): (5b)
cout << GText[Anbaugebiet] << " "<< Umsatz << " "
<< Prozentabweichung << endl;


Damit ist dieses Problem mit Hilfe der
schrittweisen Verfeinerung vollständig gelöst
Eine Substitution ergibt ein »klassisches
Programm«.
LE 32
60
4.3 Methode der schrittweisen Verfeinerung
 Merkmale der schrittweisen Verfeinerung:
 Daten und Anweisungen sollen parallel verfeinert




werden
Es soll solange wie möglich eine Notation benutzt
werden, die das Problem in natürlicher Form
beschreibt
Abstrakte Anweisungen, die durch Auswahl- oder
Wiederholungsstrukturen realisiert werden, sollten
zuerst verfeinert werden
Entscheidungen über die Datenrepräsentation
sollten solange wie möglich aufgeschoben werden
Während der Verfeinerung werden Hilfsobjekte
eingeführt.
LE 32
61
4.3 Methode der schrittweisen Verfeinerung
 Jede Verfeinerung impliziert
Implementierungsentscheidungen, die explizit
dargelegt werden sollen
 Im Gegensatz zum Softwareentwurf sind alle Daten
zwischen den verschiedenen Teilanweisungen
bekannt und global (kein Geheimnisprinzip)
 Ausgeprägte Verbalisierung ist erforderlich
 Der Implementierungsprozeß wird in kleine
inkrementelle Abstraktionsschritte unterteilt.
LE 32
62
4.3 Methode der schrittweisen Verfeinerung
 Vorteile
+
+
+
+
+
+
+
+
Realisiert das Prinzip der Verfeinerung
Fördert eine problemorientierte Betrachtungsweise
Ist für ein breites Anwendungsspektrum einsetzbar
Der Entscheidungsprozeß ist nachvollziehbar
Durch die systematische, dokumentierte
Problemstrukturierung werden Änderbarkeit und
Wartbarkeit erleichtert
top-down-orientiert
Führt zu einer gestuften algorithmischen
Abstraktion
Unterstützt systematisches Vorgehen.
LE 32
63
4.3 Methode der schrittweisen Verfeinerung
 Nachteile:
– Keine ausgeprägte Methode mit definierten,
–
–
–
problemunabhängigen Schritten
Anwendung erfordert Erfahrung und Intuition,
insbesondere um zur ersten abstrakten
Problemlösung zu gelangen
Bei mehreren Verfeinerungsstufen verliert man
leicht den Überblick
Automatische Substitutionsmechanismen für
verfeinerte Typen und Datenstrukturen stellt noch
keine Sprache zur Verfügung.
LE 32
64
4.4 Zur Psychologie des Programmierens
 Jeder Programmierer macht Fehler!
 Viele dieser Fehler sind auf Eigenheiten der




menschlichen Wahrnehmung und des
menschlichen Denkens zurückzuführen
Außerdem spielt die Denkpsychologie eine
Rolle
Assoziationen und Einstellungen des
Menschen beeinflussen sein Denken
Die meisten Programmierfehler lassen sich auf
bestimmte Denkstrukturen und -prinzipien
zurückführen
Durch die Beachtung einiger Regeln lassen
sich diese Fehler daher vermeiden.
LE 32
65
4.4 Zur Psychologie des Programmierens
 Hintergrundwissen
 Jeder Mensch benutzt bei seinen Handlungen
bewußt oder unbewußt angeborenes oder
erlerntes Hintergrundwissen
 Dieses Hintergrundwissen ist Teil des
Wissens, das einer Bevölkerungsgruppe, z.B.
den Programmierern, gemeinsam ist
 Programmierfehler lassen sich nun danach
klassifizieren, ob das Hintergrundwissen für
die Erledigung der Aufgabe angemessen ist
oder nicht.
LE 32
66
4.4 Zur Psychologie des Programmierens
 Klassifizierung von Programmierfehlern
LE 32
67
4.4 Zur Psychologie des Programmierens
 »Schnitzer«
 sind Tippfehler, Versprecher usw.
 Sie werden vor allem durch eine schlechte
Benutzungsoberfläche verursacht

Irrtümer
 sind der Kategorie »Wissen« zuzuordnen
 Diesen Fehlern ist gemeinsam, daß der
Programmierer den Fehler auch beim
wiederholten Durchlesen seines Programms
nicht sieht.
LE 32
68
4.4 Zur Psychologie des Programmierens
 Denkfallen
 Überindividuelle Irrtümer
Sie treten auf, wenn das Hintergrundwissen
der Aufgabenstellung nicht angemessen ist
 Individuelle Irrtümer
 Ergeben sich durch unzureichende
Begabung oder Ausbildung
 Denkfallen ergeben sich aus bestimmten
Denkstrukturen und -prinzipien
 Diese führen zu einem Verhaltens- und
Denkmodell, das das Verhalten eines
Programmierers beeinflußt.

LE 32
69
4.4 Zur Psychologie des Programmierens
 Prinzipien des Verhaltens- und Denkmodells
1 Übergeordnete Prinzipien
 Scheinwerferprinzip
 Sparsamkeits- bzw. Ökonomieprinzip
2 Die »angeborenen Lehrmeister«
 Strukturerwartung
(Prägnanzprinzip, kategorisches Denken)
 Kausalitätserwartung
(lineares Ursache-Wirkungs-Denken)
 Anlage zur Induktion (Überschätzung
bestätigender Informationen)
3 Bedingungen des Denkens
 Assoziationen
LE 32
70
4.4 Zur Psychologie des Programmierens
1 Übergeordnete Prinzipien
 Beschreiben, wie unser Wahrnehmungs- und
Denkapparat mit begrenzten Ressourcen fertig wird
 Scheinwerferprinzip
 Aus den Unmengen an Informationen, die der
Mensch ständig aus seiner Umwelt erhält, wählt
er aus und verarbeitet er bewußt stets nur relativ
kleine Portionen
 Leider sind es oft die wichtigen Dinge, die
unbeachtet bleiben
 Viele Programmierfehler zeigen dies:
 Ausnahme- und Grenzfälle werden übersehen
 Die Initialisierung von Variablen wird vergessen
 Funktionen besitzen unübersehbare Nebenwirkungen.
LE 32
71
4.4 Zur Psychologie des Programmierens
 Sparsamkeits- bzw. Ökonomieprinzip




Es kommt darauf an, ein Ziel mit möglichst
geringem Aufwand zu erreichen
Der Trend zum sparsamen Einsatz der
verfügbaren Mittel birgt allerdings auch Gefahren
in sich
Denk- und Verhaltensmechanismen, die unter
normalen Umständen vorteilhaft sind, können
dem Programmierer zum Verhängnis werden
Typische Fehler gehen auf falsche Hypothesen
über die Arbeitsweise des Computers zurück
 Insbesondere mit der Maschinenarithmetik kommt der
Programmierer oft aufgrund zu einfacher
Modellvorstellungen in Schwierigkeiten.
LE 32
72
4.4 Zur Psychologie des Programmierens
2 Die »angeborenen Lehrmeister«
 Stellen höheres Wissen dar, das den weiteren
Wissenserwerb steuert
 Dieses höhere Wissen spiegelt den »Normalfall«
wider
 Mit ungewöhnlichen Situationen wird es nicht so
gut fertig
 Strukturerwartung
 Erleichtert dem Menschen die Abstraktion
 Daraus folgt das Prägnanzprinzip, das besagt,
daß es sich lohnt, Ordnung aufzuspüren und
auszunutzen
 Bilden von Kategorien zum Schaffen von
Ordnung.
LE 32
73
4.4 Zur Psychologie des Programmierens
 Strukturerwartung



Prägnanzprinzip und kategorisches Denken
führen in außergewöhnlichen Situationen
gelegentlich zu Irrtümern, zu unpassenden
Vorurteilen oder zu Fehlverhalten
Eine Reihe von Programmierfehlern läßt sich
darauf zurückführen
Beispiel
 Für reelle Zahlen gilt das assoziative Gesetz
 Für die Maschinenarithmetik gilt dies aber nicht

Allgemein gilt:
 Fehler, die darauf beruhen, daß der Ordnungsgehalt der
Dinge überschätzt wird oder den Dingen zu viele
Gesetze auferlegt werden, lassen sich auf das
Prägnanzprinzip zurückführen.
LE 32
74
4.4 Zur Psychologie des Programmierens
 Kausalitätserwartung

Führt zu einem linearen Ursache-WirkungsDenken und zu der übermäßigen Vereinfachung
komplexer Sachverhalte
 Der Mensch neigt dazu, irgendwelche Erscheinungen
vorzugsweise nur einer einzigen Ursache zuzuschreiben
 Er macht oft die Erfahrung, daß die gleichen
Erscheinungen dieselbe Ursache haben, so daß dieses
Vorurteil zum festen Bestandteil des menschlichen
Denkens gehört und nur mit Anstrengung überwunden
werden kann


Das lineare Ursache-Wirkungs-Denken kann in
komplexen Entscheidungssituationen
»fürchterlich« versagen
Gerade die Umwelt eines Programmierers ist
aber ein vernetztes System.
LE 32
75
4.4 Zur Psychologie des Programmierens
 Die Anlage zur Induktion



Der Mensch neigt zu induktiven Hypothesen
Die Fähigkeit, im Besonderen das Allgemeine, im
Vergangenen das Zukünftige erkennen zu
können, verleitet zur Überschätzung
bestätigender Informationen
Beispiel
 Ein Test, der das erwartete Ergebnis liefert, wird in
seiner Aussagekraft überschätzt



Im Gegensatz zur Deduktion ist die Induktion
kein zwingendes Schließen
Gesetzmäßigkeiten werden vorschnell als
gesichert angesehen
Dadurch werden Dinge klargemacht, die
eigentlich verwickelt und undurchsichtig sind.
LE 32
76
4.4 Zur Psychologie des Programmierens
 Die Anlage zur Induktion




In der Programmierung stößt man oft auf die
Denkfalle, daß bestätigende Informationen
überschätzt werden
Der Programmierer gibt sich oft schon mit
einer schwachen Lösung zufrieden
Nach einer besseren wird nicht gesucht
Das Programm wird für optimal gehalten,
nur weil es offensichtlich funktioniert.
LE 32
77
4.4 Zur Psychologie des Programmierens
3 Bedingungen des Denkens
 Die Bedingungen des Denkens betreffen die
parallele Darstellung der Denkinhalte und den
sequentiellen Ablauf der bewußten Denk-vorgänge,
d.h. die raum-zeitliche Organisation des Denkens
 Assoziationen
 Neue Denkinhalte werden in ein Netz von
miteinander assoziierten Informationen
eingebettet
 Bei der Aktivierung eines solchen Denkinhalts
wird das assoziative Umfeld mit aktiviert
 Geht es beim Programmieren um die Zuordnung
von Bezeichnern und Bedeutungen, dann spielen
Assoziationen eine Rolle.
LE 32
78
4.4 Zur Psychologie des Programmierens
 Assoziationen
Mnemotechnische Namen können irreführend
sein
 Es wird eher an den Namen als an die Bedeutung
geglaubt
 Eine sorglose Bezeichnerwahl kann zur
Verwirrung führen
 Einstellungen
 Fehler im Problemlösungsprozeß führen dazu,
daß...

 entweder gar keine Lösung gefunden wird, obwohl sie
existiert
 oder daß nur eine mangelhafte Lösung erkannt wird, die
noch weit vom Optimum entfernt ist.
LE 32
79
4.4 Zur Psychologie des Programmierens
 Einstellungen


Einstellungen führen zu einer vorgeprägten
Ausrichtung des Denkens
Sie können zurückgehen auf...
 die Erfahrungen aus früheren Problemlöseversuchen in
ähnlichen Situationen
 die Gewöhnung und Mechanisierung durch wiederholte
Anwendung eines Denkschemas
 die Gebundenheit von Methoden und Werkzeugen an
bestimmte Verwendungszwecke
 die Vermutung von Vorschriften, wo es keine gibt
(Verbotsirrtum).
LE 32
80
4.4 Zur Psychologie des Programmierens
 Einstellungen


Um Fehler durch Einstellungen zu verhindern,
sollte das Aufzeigen von Lösungsalternativen
und eine Begründung der Methodenwahl zum
festen Bestandteil der Problembearbeitung
gemacht werden
Einstellungen führen zu determinierenden
Tendenzen beim Problemlösen:
 Die Anziehungskraft, die von einem nahen (Teil-)Ziel
ausgeht, verhindert das Auffinden eines
problemlösenden Umwegs
 Die vorhandenen Strukturen und Teillösungen lenken
den weiteren Lösungsprozeß in bestimmte Bahnen.
LE 32
81
4.5 Selbstkontroliertes Programmieren
 Der erfahrene Programmierer lernt aus
seinen Fehlern
 Er hält sich an Regeln und Vorschriften, die der
Vermeidung dieser Fehler dienen
 In diesem Sinne ist das Programmieren eine
Erfahrungswissenschaft
 Jeder Programmierer sollte für sich einen
Katalog von Programmierregeln aufstellen und
fortlaufend verbessern
 Es entsteht ein Regelkreis des
selbstkontrollierten Programmierens.
LE 32
82
4.5 Selbstkontroliertes Programmieren
 Der Regelkreis des selbstkontrollierten
Programmierens
LE 32
83
Typische Programmierfehler (1)
 Unnatürliche Zahlen
 Negative Zahlen werden oft falsch behandelt
In der täglichen Erfahrung tauchen negative
Zahlen nicht auf, weil sie »unnatürlich« sind
 Ursache: Prägnanzprinzip
 Beispiel:
 Oft werden für die Beendigung der Eingabe
die Werte 0 oder negative Werte verwendet
 Diese Verwendung »unnatürlicher Zahlen«
als Endezeichen vermischt zwei Funktionen,
nämlich das Beenden des
Eingabevorganges und die Werteeingabe.

LE 32
84
Typische Programmierfehler (2)
 Ausnahme- und Grenzfälle
 Vorzugsweise werden nur die Normalfälle




behandelt
Sonderfälle und Ausnahmen werden
übersehen
Es werden nur die Fälle erfaßt, die man für
repräsentativ hält
Ursachen: Kausalitätserwartung,
Sparsamkeitsprinzip
Beispiele:
 »Um eins daneben« Fehler
 Indexzählfehler.
LE 32
85
Typische Programmierfehler (3)
 Falsche Hypothesen
 Erfahrene Programmierer haben Faustregeln
entwickelt, sich einfache Hypothesen und
Modelle über die Arbeitsweise eines
Computers zurechtgelegt
 Aber: Dieses Wissen veraltet, mit einer
Änderung der Umwelt werden die Hypothesen
falsch
 Ursache: Prägnanzprinzip
 Beispiele:
 Multiplikationen dauern wesentlich länger
als Additionen
 Potenzieren ist aufwendiger als
Multiplizieren.
LE 32
86
Typische Programmierfehler (4)
 Tücken der Maschinenarithmetik
 Sonderfall der falschen Hypothesen
 Der Computer hält sich nicht an die Regeln der
Algebra und Analysis
 Reelle Zahlen werden nur mit begrenzter
Genauigkeit dargestellt
 Ursache: Prägnanzprinzip
 Beispiele:
 Abfrage auf Gleichheit reeller Zahlen, statt
abs(a – b ) < epsilon;

Aufsummierung unendlicher Reihen:
Summanden werden so klein, daß ihre
Beträge bei der Rundung verlorengehen.
LE 32
87
Typische Programmierfehler (5)
 Irreführende Namen
 Wahl eines Namens, der eine falsche Semantik
vortäuscht
 Daraus ergibt sich eine fehlerhafte Anwendung
 Ursache: Assoziationstäuschung

Unvollständige Bedingungen
 Das konsequente Aufstellen komplexer
logischer Bedingungen fällt schwer
 Häufig ist die Software nicht so komplex, wie
das zu lösende Problem
 Ursache: Kausalitätserwartung.
LE 32
88
Typische Programmierfehler (6)
 Unverhoffte Variablenwerte
 Die Komplexität von Zusammenhängen wird
nicht erfaßt
 Ursache: Scheinwerferprinzip,
Kausalitätserwartung
 Beispiele:
 Verwechslung global wirksamer Größen mit
Hilfsgrößen
 Falsche oder vergessene Initialisierung von
Variablen.
LE 32
89
Typische Programmierfehler (7)
 Wichtige Nebensachen
 Die Unterteilung von Programmen in
sicherheitskritische und sicherheitsunkritische
Teile, in eigentliches Programm und
Kontrollausdrucke führt oft zur
Vernachlässigung der »Nebensachen«
 Dadurch entstehen Programme mit
»abgestufter Qualität«, wobei Fehler in den
»Nebensachen« oft übersehen werden
 Ursache: Prägnanzprinzip
 Beispiel:
 Fehler bei der Plazierung von
Kontrollausdrucken täuschen Fehler im
Programm vor.
LE 32
90
Typische Programmierfehler (8)
 Trügerische Redundanz
 Durch achtloses Kopieren werden Strukturen
geschaffen, die den menschlichen
Denkapparat überfordern
 Übertriebene Kommentierung von
Programmen erhöht die Redundanz eines
Programms
 Ursache: Anlage zur Induktion
 Beispiele:
 Weiterentwicklumg eines Programms
geschieht nicht an der aktuellen Version,
sondern an einem Vorläufer
 Bei Programmänderungen wird vergessen,
den Kommentar ebenfalls zu ändern.
LE 32
91
Typische Programmierfehler (9)
 Gebundenheit
 Boolesche Ausdrücke treten oft in Verbindung
mit Entscheidungen auf
 Dies führt in anderen Situationen zu
komplizierten Ausdrücken
 Ursache: funktionale Gebundenheit
boolescher Ausdrücke
 Beispiel:
 if B then x:=true else x:= false
anstelle von
 x:=B (B=beliebiger boolescher Ausdruck).
LE 32
92
4.5 Selbstkontroliertes Programmieren
 Das Lernen aus Fehlern ist besonders gut
geeignet, um Denkfallen zu vermeiden
 Im Laufe des Anpassungsprozesses wird der
»Scheinwerfer der Aufmerksamkeit« auf die
kritischen Punkte gerichtet
 Jeder Programmierer sollte einen Regelkatalog
in einer Datei bereithalten und an seine
Bedürfnisse anpassen
 Das Lernen aus Fehlern wird außerdem
dadurch gefördert, daß man die Fehler
dokumentiert
 Es sollte ein Fehlerbuch angelegt werden,
das eine Sammlung typischer Fehler enthält.
LE 32
93
4.5 Selbstkontroliertes Programmieren
 Ein Fehler sollte in ein Fehlerbuch
eingetragen werden, wenn...
 die Fehlersuche lange gedauert hat
 die durch den Fehler verursachten Kosten
hoch waren oder
 der Fehler lange unentdeckt geblieben ist.
LE 32
94
4.5 Selbstkontroliertes Programmieren
 Folgende Daten sollten im Fehlerbuch
erfaßt werden:
 Laufende Fehlernummer
 Datum (wann entstanden, wann entdeckt)
 Programmname mit Versionsangabe
 Fehlerkurzbeschreibung (Titel)
 Ursache (Verhaltensmechanismus)
 Rückverfolgung
Gab es schon Fehler derselben Sorte?
 Warum war eine früher vorgeschlagene
Gegenmaßnahme nicht wirksam?
 Programmierregel, Gegenmaßnahme
 Ausführliche Fehlerbeschreibung.

LE 32
95
Regelkatalog zur Vermeidung von Fehlern (1)
 Grundsätze des Programmentwurfs
 Auf Lesbarkeit achten
Bedeutung der Prinzipien Verbalisierung,
problemadäquate Datentypen, integrierte
Dokumentation.
 Nach der Methode der schrittweisen
Verfeinerung vorgehen
 Sparsamkeit bei Schnittstellen- und
Variablendeklarationen
 Globale Variablen vermeiden
 kurze Parameterlisten
 Gültigkeitsbereiche von Variablen möglichst
stark beschränken.

LE 32
96
Regelkatalog zur Vermeidung von Fehlern (2)
 Das Programm und seine Teile gliedern in:
Initialisierung, Eingabe, Verarbeitung,
Ausgabe
 Verwendung linearer Kontrollstrukturen
 Sequenz, Auswahl, Wiederholung, Aufruf


Regeln gegen das Prägnanzprinzip
 Fehlerkontrolle durchführen


Sorgfalt besonders bei Diskretisierung
kontinuierlicher Größen
Bei numerischen Programmen
Maschinenarithmetik beachten.
LE 32
97
Regelkatalog zur Vermeidung von Fehlern (3)
 Nie reelle Zahlen auf Gleichheit oder
Ungleichheit abfragen
 Keine temporäre Ausgabebefehle verwenden
 Ausgabebefehle zur Unterstützung von
Tests in Auswahlanweisungen plazieren und
global ein- und ausschalten
 Faustregeln von Zeit zu Zeit überprüfen
 Effizienzsteigernde Tricks können durch
neue Hardware unnötig werden.
LE 32
98
Regelkatalog zur Vermeidung von Fehlern (4)
 Regeln gegen das lineare Kausaldenken
 Redundanz reduzieren
Prozeduren verwenden, anstatt mehrfach
verwendete Teile zu kopieren
 Kommentare sparsam verwenden, besser
gute Bezeichner und passende
Datenstrukturen wählen
 Zusicherungen ins Programm einbauen
 Als Kommentare ins Programm einfügen
 else-Teil einer Auswahl kommentieren
 Als Kommentar angeben, welche
Bedingungen für einen else-Teil gelten.

LE 32
99
Regelkatalog zur Vermeidung von Fehlern (5)
 Ist ein Fehler gefunden: weitersuchen
In der Umgebung von Fehlern sind meist weitere
 ETn und Entscheidungsbäume beim Aufstellen
komplexer logischer Bedingungen verwenden


Regeln gegen die Überschätzung
bestätigender Informationen
 Alternativen suchen



Stets davon ausgehen, daß es noch bessere
Lösungen gibt
Aktivierung von Heuristiken
Regeln gegen irreführende Assoziationen
 Bezeichner wählen, die Variablen und Operationen
möglichst exakt bezeichnen.
LE 32
100
4.5 Selbstkontroliertes Programmieren
 Heuristiken
 Hat man ein Problem und keine Lösungsidee,
dann kann die bewußte Aktivierung von
Heuristiken helfen, Denkblockaden
aufzubrechen und gewohnte Denkbahnen zu
verlassen
 Heuristiken sind Lösungsfindeverfahren, die
auf Hypothesen, Analogien oder Erfahrungen
aufgebaut sind.
LE 32
101
Heuristiken für die Programmierung (1)
 Basisheuristik
 Kann ich in der Liste der Heuristiken eine
finden, die mir weiterhilft?

Analogie
 Habe ich ein ähnliches Problem schon einmal
bearbeitet?
 Kenne ich ein verwandtes Problem?

Verallgemeinerung
 Hilft mir der Übergang von einem Objekt zu
einer Klasse von Objekten weiter?

Spezialisierung
 Bringt es mich weiter, wenn ich zunächst einen
leicht zugänglichen Spezialfall löse?
LE 32
102
Heuristiken für die Programmierung (2)
 Variation
 Komme ich durch eine Veränderung der
Problemstellung der Lösung näher?
 Kann ich die Problemstellung anders
ausdrücken?

Rückwärtssuche
 Ich betrachte das gewünschte Ergebnis


Welche Operationen können mich zu diesem
Ergebnis führen?
Teile und herrsche
 Läßt sich das Problem in leichter lösbare
Teilprobleme zerlegen?
LE 32
103
Heuristiken für die Programmierung (3)
 Vollständige Enumeration
 Ich lasse einen Teil der Bedingungen weg


Kann ich mir Lösungen verschaffen, die
wenigstens einen Teil der Zielbedingungen
erfüllen?
Kann ich mir alle Lösungen verschaffen, die
diese Bedingungen erfüllen?
LE 32
104
4.6 Programmierrichtlinien für C++
 Richtlinien
 Sind für den Programmierer verbindlich und
müssen unbedingt eingehalten werden
 Die Einhaltung muß überwacht werden
 Wenn möglich durch Werkzeuge
 Wenn nötig manuelle Überprüfung
 Die Einhaltung der Richtlinien muß eine echte
Qualitätsverbesserung bewirken

Empfehlungen
 Sollen nach Möglichkeit eingehalten werden
 Abweichungen sind in Ausnahmefällen erlaubt
 Sie müssen durch entsprechende Kommentare
im Quellprogramm begründet werden.
LE 32
105
4.6 Programmierrichtlinien für C++
 C++-Richtlinien als Beispiele:
 Richtlinien für Bezeichner
 Richtlinien für die Formatierung
 Richtlinien für den OO-Teil
 Richtlinien für den prozeduralen Teil
 Richtlinien für portables, d.h. plattform- und
compilerübergreifendes Programmieren
 Richtlinien zur Projektorganisation.
LE 32
106
C++-Richtlinien für Bezeichner (1)
 Bezeichner
 Natürlichsprachige oder problemnahe Namen
oder verständliche Abkürzungen solcher Namen
 Kein Bezeichner beginnt mit einem Unterstrich _
 Für Bibliotheksfunktionen oder
Präprozessornamen reserviert
 Generell Groß-/Kleinschreibung
 2 Bezeichner dürfen sich nicht nur bzgl. der
Groß-/Kleinschreibung unterscheiden
 Klassennamen beginnen immer mit einem
Großbuchstaben.
 Präprozessornamen immer groß.
LE 32
107
C++-Richtlinien für Bezeichner (2)
 Deutsche oder englische Namensgebung
 Ausnahme: Allgemein übliche englische Begriffe,
z.B. push

Länge des Bezeichners
 Allgemein zwischen 5 und 15 Zeichen

Für Laufvariablen in for-Schleifen und als
array-Indizes
 Kleinbuchstaben erlaubt, z.B. i, j, k

Für Zeiger-»Laufvariablen«
 Man kann p und q benutzen.
LE 32
108
C++-Richtlinien für Bezeichner (3)
 Bezeichner aus mehreren Worten
 Jedes Wort beginnt mit einem Großbuchstaben
Beispiel: AnzahlWorte
 Unterstriche werden nicht zur Trennung eingesetzt


Funktionen und Operationen
 Führen Aktionen aus, daher sollte der Namen ein
Verb enthalten
 Beispiele: void Sortiere(int Zahlen [ ]);
 Geht das relevante Objekt, mit dem die Aktion
ausgeführt wird, nicht aus der Parameterliste
hervor, dann ist es in den Namen aufzunehmen
 Beispiele:
 void DruckeRechnung(int Nr)
 int LesePosZahl();.
LE 32
109
C++-Richtlinien für Bezeichner (4)
 Wenn Operation nur Instanzvariable liest oder
schreibt
 Namen soll mit Get bzw. Set beginnen
 Ausnahme: Anderer Namen bringt bessere
Lesbarkeit
 Beispiel: int GetXKoord()

Variablen detailliert beschreiben
 Beispiele: ZeilenZaehler, WindGeschw, DateiStatus
 Folgende Präfixe besitzen eine spezielle
Bedeutung:
 a-/ein- irgendeine Instanz einer Klasse
 Beispiele: aStack, einKunde

global- globale Variable
 Beispiel: globalError.
LE 32
110
C++-Richtlinien für Bezeichner (5)
 Folgendes Postfix besitzt eine spezielle Bedeutung:

-Z/-Ptr Zeigervariable
 Beispiele: anObjectPtr, AnkerZ

Bei Strukturen unnötige Redundanz vermeiden
 Variablenbezeichner und Member-Bezeichner
müssen einen aussagefähigen Namen ergeben
 Beispiel:
struct PersonT
{ char Name[ ]
int Alter;
};
PersonT Person;
Person.Name = "Hans";
Person.Alter = 24;

Der Strukturbezeichner erhält das Postfix T.
LE 32
111
C++-Richtlinien für Bezeichner (6)
 Alle Typbezeichner
 Erhalten das Postfix T


Beispiel: enum FarbeT {blau, rot, weiss};
Mit typedef aussagefähige Typbezeichnungen
einführen
 Beispiel:


typedef unsigned int ArtikelnrT;
ArtikelnrT Artikelnr;
Variablen vom Typ bool
 Jeder Bezeichner wie folgt:

Richtig, EingabeRichtig, Gefunden,
ZahlGefunden, Offen, TuerOffen.
LE 32
112
C++-Richtlinien für die Formatierung (1)
 Leerzeichen
 Bei binären Operatoren

Operanden und Operator durch jeweils ein
Leerzeichen trennen, z.B. Zahl1 + Zahl2 * 3
 Keine Leerzeichen innerhalb von Referenzen

Beispiele: Adresse.Name, AdressPtr–>Name,
Angestellter[10], Matrix[10] [2]
 Zwischen Funktionsname und Klammer
Kein Leerzeichen, z.B. ZinsBerechnen()
 Nach der öffnenden und vor der schließenden
Klammer
 Kein Leerzeichen: GGT(123,124);clrscr();

 Nach Schlüsselwörtern 1 Leerzeichen, z. B. if (....).
LE 32
113
C++-Richtlinien für die Formatierung (2)
 Leerzeilen
 Leerzeilen sollen Blöcke von logisch




zusammenhängenden Anweisungen trennen
Der Deklarationsteil soll stets durch eine Leerzeile
von den Anweisungen getrennt werden
Jeder Funktionsdeklaration soll eine Leerzeile
vorausgehen
Umfangreiche Kontrollstrukturen sind durch eine
Leerzeile zu trennen
Umfangreiche Deklarationen sind durch eine
Leerzeile zu trennen.
LE 32
114
C++-Richtlinien für die Formatierung (3)
 Einrücken und Klammern von Strukturen
 Bei allen Kontrollstrukturen gilt eine Einrücktiefe
von 4 Leerzeichen
 Läßt Platz für aussagefähige Bezeichner, z.B.
if (Bedingung1)
{
{
Anweisung4;
Anweisung1;
Anweisung5;
if(Bedingung2)
}
Anweisung2;
do
else
{
Anweisung3;
Anweisung;
}
}while(Bedingung);
else.
LE 32
115
C++-Richtlinien für die Formatierung (4)
 Funktionen/Operationen
 Jede Funktion/Operation ist wie folgt zu
deklarieren:
//--------- Point::MoveTo --------------void Point::MoveTo(int newX, int NewY)
{
Hide();
X = NewX;
Y = NewY;
Show();
}.
LE 32
116
C++-Richtlinien für die Formatierung (5)
 Einheitlicher Aufbau einer Klasse
 Folgende Reihenfolge ist einzuhalten:

public-Member-Funktionen
 aus dem objektorientierten Entwurf

protected-Daten und -Funktionen
 alle verkapselten Daten sind protected zu vereinbaren
private-Deklarationen
 Alle Member-Funktionen sind in folgender
Gruppierung aufzuführen:
 Konstruktoren
 Destruktoren
 Operatoren, z.B. Zuweisung, Vergleich
 Operationen mit schreibendem
ZugriffOperationen mit lesendem Zugriff.

LE 32
117
C++-Richtlinien für den OO-Teil (1)
 Zugriffsrechte
 Verkapselte Daten (Member-Daten) sind generell als
protected zu definieren
 Member-Funktionen sind als public zu definieren
 Friend-Funktionen sind nur in begründeten
Ausnahmefällen zu verwenden
 Friends werden weder vererbt noch sind sie
transitiv

Operationen mit lesendem Zugriff
 Damit die Operation keine Veränderung auf den
verkapselten Daten durchführen kann, ist sie mit
const zu vereinbaren
 Beispiel: const char* get_name() const;.
LE 32
118
C++-Richtlinien für den OO-Teil (2)
 Inline-Funktionen
 Verletzen das Geheimnisprinzip, daher nur bei
erheblichen Leistungsverbesserungen verwenden
 extern inline-Funktionen bieten die gleichen
Vorteile, bewahren aber das Geheimnisprinzip

Virtuelle Funktionen
 Operationen einer Klasse, die in einer Unterklasse
detailliert werden, sind grundsätzlich als virtuell zu
vereinbaren
 Dann ist sichergestellt, daß immer die Operation
der Unterklasse aufgerufen wird, auch wenn der
Zugriff über einen Zeiger der Oberklasse erfolgt ist
 Beispiel: .
LE 32
119
C++-Richtlinien für den OO-Teil (3)
class B
class D: public B
{public:
{public:
virtual void f1(); void f1();
void f2()
void f2();
...
...
};
};
D d; B*bptr = &d;
bptr–>f1();//ruft D::f1
bptr–>f2();//ruft B::f2
LE 32
120
C++-Richtlinien für den OO-Teil (4)
 Konstruktoren
 Ein Default-Konstruktor ist ein Konstruktor, der
ohne Argumente aufgerufen werden kann, z.B.
Complex ().
 Ist für eine Klasse ein Konstruktor definiert, dann
muß ein Default-Konstruktor definiert sein, oder
sein Fehlen begründet werden

Destruktoren
 Wird kein Destruktor angegeben, dann erzeugt der
Compiler automatisch einen Default-Destruktor, der
nicht virtuell ist
 Um den Polymorphismus auszunutzen, ist der
Destruktor stets virtuell zu deklarieren
 Beispiel: .
LE 32
121
C++-Richtlinien für den OO-Teil (5)
class B
class D: public B
{public:
{public:
virtual ~B();
virtual ~D();
...
...
};
};
D d; B*bptr = &d;
delete (bptr);
//ruft Destruktor von D,
// wenn Destruktor
// virtuell,
//sonst Destruktur von B.
LE 32
122
C++-Richtlinien für den OO-Teil (6)
 Abstrakte Klassen
 Jede abstrakte Klasse ist durch einen Kommentar
zu kennzeichnen, z.B.
class Abstract //abstrakte Klasse
 Eine abstrakte Klasse muß mindestens eine pure
virtual member function besitzen
 Dadurch wird sichergestellt, daß von dieser Klasse
keine Objekte gebildet werden können, z.B.
virtual void do_something() = 0;
//pure virtual member function.
LE 32
123
C++-Richtlinien für den OO-Teil (7)
 Generische Klassen
 Von generischen Klassen sind nicht direkt Objekte
zu bilden, sondern es sind zuvor Typen oder
Klassen zu bilden:
 Nicht: Node<char> aNode;



Sondern:
typedef Node<char> Char_Node;
Char_Node aNode;
class Char_Node: public Node <char> {};
Char_Node aNode.
LE 32
124
C++-Richtlinien für den prozeduralen Teil (1)
 Ein-/Ausgabe in C++
 Bei Ein-/Ausgabefunktionen sind grundsätzlich die
Routinen der iostream-Bibliothek zu verwenden
 Die entsprechenden C-Routinen sind weder
typsicher noch erlauben sie die Integration
benutzerdefinierter Klassen in das E/A-System

Verwendung von Feldern (arrays)
 Zugriff über Feldname & Index Liste[Index] ist
besser lesbar, als Zeigerzugriff *(Liste + Index)
 Zeigerzugriff ist bei mehrdimensionalen Feldern
effizienter, jedoch nur marginal
 Wichtiger ist die bessere Lesbarkeit und die
einfachere Indexüberprüfung beim Indexzugriff.
LE 32
125
C++-Richtlinien für den prozeduralen Teil (2)
 Felder immer vollständig initialisieren


Beispiel:
int Matrix[2][2] = { {1,0}, {1,0} };
Verwendung von Zeigern
 Zeiger sind ein mächtiges, aber auch ein
gefährliches Sprachkonzept
 Ein Zeiger wird häufig auf den Feldanfang gesetzt:
int *pWert;
int Werte [Max];
...
pWert = &Werte[0]; oder pWert = Werte;

Während die 1. Form lesbarer ist, ist die 2. Form
verbreiteter und kann daher auch gewählt
werden.
LE 32
126
C++-Richtlinien für den prozeduralen Teil (3)
 Variablendeklaration
 Variablen sind zu Beginn eines Blocks zu
vereinbaren
 Laufvariablen sind immer im Initialsierungsteil zu
deklarieren
for int (i=0; i < 10; i++) {};

Verwendung von Konstanten
 Offenkundige Konstanten sind als Konstanten zu
definieren, z.B.
const int MaxZeilen = 24,
MaxSpalten = 80;
 Für Aufzählungen Aufzählungstypen verwenden
enum StatusT = {ok, SchreibFehler,
LeseFehler, DisketteVoll};.
LE 32
127
C++-Richtlinien für den prozeduralen Teil (4)
 Voneinander abhängige Konstanten durch Formeln
wiedergeben, z.B.
const int Hoehe = 10,
Breite = 25,
Flaeche = Hoehe * Breite;

Call-by-reference Parameter
 Immer wählen, wenn eine Funktion Werte
zurückgibt
 Bei umfangreichen Datenstrukturen call-byreference auch bei Eingabeparametern
 Unbeabsichtigte Veränderung der
Eingabeparameter mit const verhindern, z.B.
void DruckeAdresse(const AdresseT&
Adresse);.
LE 32
128
C++-Richtlinien für portables Programmieren (1)
 Ganzzahlige Datentypen
 Verfügen in C/C++ über keinen garantierten
Wertebereich
 Daher selbstdefinierte Datentypen verwenden, die
auf jeder Plattform exakt denselben Wertebereich
abdecken
 In einer Datei folgende Datentypen vereinbaren:





int32
uint32
short16
ushort16
char8
 uchar8
 String
vorzeichenbehaftete 32-Bit Integer-Zahl
vorzeichenlose 32-Bit Integer-Zahl
vorzeichenbehaftete 16-Bit Integer-Zahl
vorzeichenlose 16-Bit Integer-Zahl
ein Zeichen
(vorzeichenbehaftete 8-Bit Integer-Zahl)
vorzeichenlose 8-Bit Zahl
Objekt der Klasse String.
LE 32
129
C++-Richtlinien für portables Programmieren (2)
 Die Klasse String
 Verschiedene Compiler besitzen verschiedene
Implementierungen der Klasse String, die teilweise
nicht zueinander kompatibel sind
 Daher sollte eine selbstentwickelte String-Klasse
oder die dem ANSI-Standard entsprechende
benutzt werden

Definieren von Aufzählungstypen
 Viele Compiler erlauben folgende Definition:
typedef enum x {...};
 typedef ist an dieser Stelle überflüssig und wird
von einigen Compilern nicht akzeptiert.
LE 32
130
C++-Richtlinien für portables Programmieren (3)
 Zeichensätze
 Auf verschiedenen Rechnern sind Zeichen mit
einem Code >127 und damit alle Umlaute
unterschiedlich kodiert
 Umlaute auf keinen Fall in Kommentaren
benutzen!
 Sind Zeichenketten mit Umlauten notwendig
 Den Programmtext mit einem Editor bearbeiten, der im
ISO-Latin_1-Zeichensatz speichern kann
 Beispiele:
 der einfache Windows-Editor (notepad.exe)
 der vi von Linux (elvis).
LE 32
131
C++-Richtlinien für portables Programmieren (4)
 Initialisierung von Variablen
 Die meisten Compiler weisen Variablen, die bei der
Deklaration nicht explizit initialisiert werden, keinen
Wert zu
 Dies gilt besonders für lokale Variablen, die auf
dem Keller angelegt werden und damit einen
zufälligen Wert erhalten
 Codeteile, die definierte Werte in definierten
Variablen benötigen, erfordern eine
ordnungsgemäße Initialisierung der Variablen.
LE 32
132
C++-Richtlinien für portables Programmieren (5)
 Dateiverwaltung
 DOS- und Unix-Dateisysteme sind ähnlich
aufgebaut
 Der Aufbau der Dateinamen ist jedoch
unterschiedlich
 Unix ist ein Mehrbenutzer-System
 Daher ist beim Anlegen bzw. Öffnen von Dateien
immer zu prüfen, ob der Benutzer die
Berechtigung für die Operation hat
 Objekte bzw. Operationen, die mit Dateien arbeiten,
sollten immer einen aussagefähigen Fehlercode
liefern.
LE 32
133
C++-Richtlinien für portables Programmieren (6)
 Standardeingabe- und -ausgabekanäle
 Statt in die Kanäle cout/cerr bzw. cin zu schreiben


Referenzen auf Objekte der Klassen ostream
oder istream erzeugen
Diese je nach Betriebssystem auf
Standardkanäle bzw. in eine Datei schreiben
lassen.
LE 32
134
C++-Richtlinien zur Projektorganisation (1)
 Projektverwaltung
 Spezifikation und Implementierung auf 2 Dateien
aufteilen, um das Geheimnisprinzip zu realisieren
 Beispiel:
 Spezifikation in die Datei counter.h (Header-Datei)
 Implementierung in die Datei counter.cpp
// counter.h
//counter.cpp
class Counter
#include "counter.h"
{
{ //––– Konstruktor––––
public:
Counter::Counter()
Counter();
{...}
void up();
protected:
//––– Counter::up ––––
int count;
void up()
};
{...}.
LE 32
135
C++-Richtlinien zur Projektorganisation (2)
 Bei Klassenhierarchien können (bei kleinem
Umfang) mehrere Spezifikationen und
Implementierungen jeweils in einer Datei enthalten
sein , z.B. Location –> Point –> Circle in den
Dateien figures.h und figures.cpp

Präprozessor-Anweisungen
 Wird eine Header-Datei mehrfach inkludiert, dann
treten Fehlermeldungen auf
 Daher wird bei jeder Header-Datei (z.B. queue.h)
folgender Mechanismus verwendet:
 Sie beginnt stets mit
#ifndef QUEUE_H
#define QUEUE_H
 und endet mit
#endif //QUEUE_H.
LE 32
136



Danke!
Aufgaben
Diese Präsentation bzw. Teile
dieser Präsentation enthalten
Inhalte und Grafiken des
Lehrbuchs der SoftwareTechnik (Band 1)
von Helmut Balzert,
Spektrum Akademischer
Verlag, Heidelberg 1996
Herunterladen