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