Ziele der Software-Entwicklung
Das Hauptziel der Softwareentwicklung ist es, mit wenig Aufwand nützliche Software zu erstellen.
Der Aufwand an Arbeitszeit und Kosten für die Herstellung von Software soll also während der Herstellung und während der Zeit des Einsatzes möglichst klein sein (Primärziel: Sparsamkeit ). Nützlich ist Software, wenn sie korrekt arbeitet (Primärziel: Korrektheit ) und wenn sie während ihrer Verwendung die vorhandenen Ressourcen dazu effizient einsetzt (Primärziel: Effizienz ).
Kleine Programmierprojekte sind nicht das Problem. In Programmierkursen beginnt man aber vernünftigerweise mit Miniaturproblemen, da die Fähigkeiten der Lernenden ja anfangs für große Projekte noch nicht ausreichen und diese auch oft den zeitlichen Rahmen einer Lehrveranstaltung sprengen würden.
Dennoch ergeben sich die stilistischen Regeln für die Programmierung oft aus der Anforderung, bei großen Programmierprojekten Sparsamkeit, Korrektheit und Effizienz sicherzustellen. Da der Lernende die Probleme größerer Projekte aber noch nicht kennt, werden ihm manche diesbezügliche Hinweise gelegentlich merkwürdig vorkommen. Um das Verständnis dafür zu fördern, sollen im folgenden Primärziele (Hauptziele) der Softwareentwicklung großer Projekte zusammen mit den sich aus ihnen ergebenden Sekundärzielen (Unterzielen) genannt werden. Aus den Sekundärzielen können sich wiederum weitere Tertiärziele (Unterziele der Unterziele) ergeben.
Primärziel Korrektheit
Die Korrektheit ist sicher das Primärziel mit dem höchsten Rang, denn Software muß die gestellte Aufgabe korrekt lösen. Ein Programm, das irgendetwas anderes macht, ist im Allgemeinen wertlos oder im besten Fall noch eine mehr oder weniger gut brauchbare Annährung an das Ziel.
Sekundärziel Definition
Damit überhaupt geklärt werden kann, ob ein Produkt korrekt ist, muß zunächst einmal definiert werden, was Korrektheit in einem speziellen Fall überhaupt bedeutet. Um prüfen zu können, ob ein Programm eine gegebene Anforderung korrekt löst, muß die Anforderung zum einen in Form einer stabilen Beschreibung der Aufgabenstellung (nicht nur in Form eines flüchtigen Gespräches oder Gedankens) gegeben sein und zum anderen muß ein Verfahren bekannt sein, das es erlaubt die durch die Beschreibung spezifizierten Anforderung mit dem Produkt zu vergleichen. Nicht zuletzt ist das wichtig, um im Rahmen eines Vertrages zu klären, ob die vereinbarte Leistung der Herstellung von Software korrekt erbracht wurde und der Kunde das Produkt voll zu bezahlen hat.
Sekundärziel Technische Unterstützung
Technische Unterstützung bei der Erstellung eines korrekten Programmes bietet die Verwendung maschineller Hilfsmittel zur Sicherstellung der Korrektheit eines Programmes.
Tertiärziel Strenge
Ein maschineller Analysator (z.B. ein Übersetzer oder Interpretierer) von Programmen ist umso strenger, je weniger Formulierungen er als gültig erkennt. Ein gar nicht strenger Analysator von Programmen würde jede Eingabe als gültig anerkennen und irgendwie verarbeiten. Von Menschen geschriebene Programme enthalten aber oft Fehler, die dann auch als gültig anerkannt werden würden und später zu schädlichen Konsequenzen führen würden. Bei strengeren Sprachen führen Tippfehler öfter zu ungültigen Programmen, die dann sogleich als fehlerhaft zurückgewiesen werden können, bevor sie Schaden anrichten können.
Nach einigen Quellen wurde die amerikanische Viking Venus Sonde wurde durch eine fehlerhaft programmierte Schleife verloren. Anstelle des beabsichtigten Quelltextes
beabsichtigter Quelltext
DO 20 I = 1,100
wurde in dem Programm tatsächlich die folgende Zeile verwendet.
tatsächlicher Quelltext
DO 20 I = 1.100
Die dabei verwendete Programmiersprache FORTRAN ignoriert Leerzeichung und sah dies als Zuweisung des Wertes "1.100" an die Variable "DO20I" an. Das Akzeptieren dieser Anweisung soll die Ursache für den Verlust der Sonde gewesen sein. Es gibt zwar andere Quellen, nach denen diese Geschichte nur eine unzutreffende Legende ist, aber selbst, wenn das sich nicht so ereignet hat, hätte es so sein können und illustriert die Probleme mit zu toleranten Programmiersprachen.
Zur Sicherheit von Programmen trägt die Typstrenge (Typsicherheit ) einer Sprache ebenfalls wesentlich bei.
Speichern einer Kommazahl in einen Ganzahlenspeicher
I = 3.5
Das Schreiben einer Kommazahl in einen ganzzahligen Speicherbereich kann auf einen Fehler zurückgehen, da ganzzahlige Speicherbereich zur Aufnahme ganzzahliger Werte gedacht sind. Eine Programmiersprache, die hier nicht einfach automatisch eine Umwandlung erledigt oder die Anweisung irgendwie verarbeitet, sondern eine Fehlermeldung erzeugt, kann daher einem Programmierer helfen, Fehler zu finden.
Sekundärziel Sorgfalt
Durch gute Ausbildung der Software-Entwickler und sorgfältige Programmierung können viele Typen von Fehlern stark vermindert werden. Oft führen Zeit- und Kostengrenzen zu mangelhafter Software, etwa wenn ein Projekt zu einem Preis angeboten wurde, der die Kosten einer sorgfältigen Ausführung gar nicht decken kann. Bevor ein Programmierer richtig verstanden hat, was er programmieren soll oder bisher programmiert hat, eilt er oft schon zum „Ausprobieren“ und beginnt alsdann an den erkannten Fehlern herumzubasteln, bis alles einigermaßen läuft.
Im Rahmen der Methode cleanroom design nimmt man sich mehr Zeit, um Software sorgfältig zu konstruieren und schrittweise zu erstellen. Dabei geht man nach dem Vorbild der Reinräume der Produktionstechnik vor, deren Aufgabe es ist, Fehler von Anfang an zu verhindern, und nicht schon entstandene Fehler zu suchen und zu korrigieren. Man nimmt sich vor, eher 50 % zu 100 % korrekt zu implementieren als 100 % zu 50 % korrekt. Die Entwickler überzeugen sich durch Beweisverfahren von der Korrektheit des Programmes, bevor das Programm zum ersten Mal ausgeführt wird. Das Ziel ist die Erstellung fehlerarmer Software mit durch statistische Tests ermittelter zertifizierter MTTF (Durchschnittliche zu erwartende Dauer des fehlerfreien Betriebs).
Sekundärziel Testen
Selbst bei cleanroom design gibt es eine Zertifikation der Zuverlässigkeit eines Produktes. In den meisten anderen Produktionsmethoden verwendet man Tests, um sich von der Korrektheit von Software zu überzeugen.
Das Testen von Programmen durch Menschen ist aufwendig. Da Programme nach Änderungen wiederholt zu testen sind, werden oft automatische Testprogramme geschrieben, die andere Programme selbständig testen können. Sind solche Testprogramme so sorgfältig geschrieben worden, daß sie möglichst viele verschiedene Situationen prüfen, können sie hilfreich sein, um Fehler in Programmen zu finden.
Bei statistischen Testmethoden werden Eingaben für Programme durch Zufall so erzeugt, daß ihre statistische Verteilung mit der erwarteten Verteilung im Betrieb übereinstimmt.
Bei Weißkastentests schreibt ein Programmierer, der den internen Aufbau eines Programmes kennt, Testprogramme, die das zu testende Programm wiederholt so aufrufen, daß jeder Programmteil mindestens einmal durchlaufen und getestet wird.
Ein Programmierer kann aber blinde Flecke haben, die dafür sorgen, daß er die selben Fehler, die er bei der Erstellung eines Programmes machte, auch beim Schreiben von Tests für das Programm macht und dadurch Fehler nicht erkannt werden. Um auch solche Fehler zu erkennen, kann es Außenstehender einen Schwarzkastentest schreiben, der ein Programm testet, ohne den inneren Aufbau zu kennen.
In der Praxis sind während der Lebenszeit eines Software-Produktes oft umfangreiche Anpassungsarbeiten an dem Programm nötig, bei denen manchmal aber Fehler eingebaut werden. Es kommt auch manchmal vor, daß eine Programmänderung, die eigentlich einen Fehler beseitigen soll, tatsächlich weitere Fehler einbaut. Wenn ein Testprogramm einmal geschrieben wurde, dann kann es nach jeder Änderung verwendet werden, um sicherzustellen, daß das Programm wenigstens die vorprogrammierten Testfälle weiterhin korrekt behandelt.
Unter anderem im Extreme Programming (XP ) verwendet man die Vorgehensweise, solche Schwarzkastentests zu schreiben, bevor ein Programm erstellt wird. Dies erlaubt es, dann die Tests noch unvoreingenommen von Aspekten der Implementation zu schreiben und die Testprogramme von Anfang an während der Produktion und nach Änderung zur Verfikation der Software einzusetzen. Der Programmierer ist dann teilweise von der Aufgabe geleitet, ein Programm zu schreiben, das die vorgefertigten Tests besteht.
Primärziel Sparsamkeit
Die Sparsamkeit gebietet es, ein Entwicklungsvorhaben mit möglichst wenig Aufwand an Arbeitszeit, Geld und anderen Ressourcen zu vollenden. Die entscheidende Komponente ist hierbei meistens die Arbeitszeit der eingesetzten Entwickler. Damit verbunden ist auch das Ziel, ein Projekt bis zu einem bestimmten Zieltermin fertigzustellen.
Sekundärziel Wiederverwendbarkeit
Wenn es gelingt, Programmbausteine aus einem früheren Produkt in einem laufenden Projekt wiederzuverwenden, kann man sich die Arbeit, diese neu zu schreiben, ersparen. Daher ist es ein hochrangiges Sekundärziel, die Wiederverwendung und Wiederverwendbarkeit von Programmbausteinen schon während der Planung sicherzustellen. Hierzu kann es auch eine strategisch richtige Entscheidung sein, nicht alles selber zu schreiben, sondern vorgefertigte Programmteile von anderen Anbietern zu beziehen.
Tertiärziel Portabilität
Eine Form der Wiederverwendung von Programmbausteinen ist die Portierung, also die Übertragung auf eine andere Umgebung (auf eine andere Plattform, beispielsweise auf ein anderes Betriebssystem oder auf einen anderen Rechnertyp).
Ein Programm oder Programmbaustein ist auf eine Umgebung portabel, wenn er auf diese übertragen werden kann und auch unter dieser Umgebung sein Aufgabe erfüllen kann. Durch einen portablen Programmbaustein erspart man es sich, diesen für jede Umgebung immer wieder neu schreiben zu müssen.
Tertiärziel Allgemeinheit
Programmbausteine können auch so geschrieben werden, daß sie möglichst allgemein nutzbar sind, auch wenn die Allgemeinheit nicht sofort benötigt wird. Dadurch können sie aber später häufiger wiederverwendet werden.
Allgemeine und portable Programmbausteine helfen, Arbeit zu sparen, weil sie universeller wiederverwendet werden können. Doch da die Allgemeinheit und Portabilität selber unter Umständen auch wieder aufwendig herzustellen ist, sollte man nicht versuchen zu allgemein zu programmieren. Allgemeinheit ist immer dann nützlich wenn sie mit wenig Mehraufwand erreicht werden kann, andernfalls kann auch abgewartet werden, bis die Notwendigkeit zur Verallgemeinerung anfällt. Portabilität verlangt nicht, daß ein Programmbaustein von Anfang an auch dort, wo dies mühevoll ist, so geschrieben wird, daß er unter allen denkbaren Umgebungen läuft. Oft reicht es schon, bei der Programmierung die Fehler zu vermeiden, welche die Portabilität unnötigerweise einschränken würden.
Tertiärziel Modularität
Damit Programmbausteine eines Programmes in einem anderen Programm wiederverwendet werden können, müssen sie erkennbar und hinreichend von anderen Teilen des Programmes abgegrenzt sein.
Tertiärziel Hohe Kohäsion
Ein Programmbaustein hat eine hohe Kohäsion, wenn er eine einzige wohlbestimmte Aufgabe erfüllt. Meistens kann man dies daran erkennen, daß man seinen Sinn mit einem Wort (oder wenigen Worten) benennen kann, ohne daß man dabei eine Aufzählung verschiedener Aufgaben verwendet. Solche Programmbausteine können leicht einer bestimmten Aufgabe zugeordnet werden und dann immer wieder eingesetzt werden, wenn genau diese Aufgabe anfällt.
Eine geringe Kohäsion hat ein Programmbaustein, der verschiedene Aufgaben miteinander verbindet, die wenig miteinander zu tun haben. Ein Beispiel dafür wäre ein Programmbaustein, der von einem Betrag einen Rabatt abzieht und dann das Ergebnis auf einen Drucker ausgibt. Solch eine Programmbaustein ist nicht einsetzbar, wenn nur der Rabett berechnet werden, aber keine Ausgabe auf einen Drucker erfolgen soll.
Dies kann man mit einer Packung Zahnpasta vergleichen, die nur zusammen mit einer Flasche Wein verkauft wird. Die Brauchbarkeit dieses Produktes ist stark eingeschränkt, weil man nicht immer diese Kombination wünscht. Solch ein Produkt hätte eine geringe Kohäsion. Jemand, der Zahnpaste verwendet, trinkt nicht immer Wein. Die Kombination einer Packung Zahnpasta mit einer Zahnbürste hätte eine höhere Kohäsion, weil beide Produkte oft zusammen benötigt werden. Aber auch diese so naheliegende Kombination erscheint als unpassend, wenn man schon genug Zahnbürsten besitzt. Ein Produkt, das nur Zahnpasta enthält, hat die höchste Kohäsion. Obwohl Zahnpasta selber eine Mischung verschiedener Bestandteile ist, die a priori auch nichts miteinander zu tun haben, werden sie durch den gemeinsamen Sinn (der Zahnpflege) zu einem Produkt mit hoher Kohäsion vereinigt.
Dieser Text selber ist ein anderes Beispiel für hohe Kohäsion: Er hat genau ein Thema, nämlich eine hierarchische Beschreibung der wichtigsten Ziele der Software-Entwicklung.
Tertiärziel Kontrollierte Kopplung
Damit Programmbausteine in verschiedenen Projekten verwendet werden können, sollte ihre Kopplung untereinander möglichst kontrolliert sein: Ein Programmbaustein sollte wohldefinierte Anbindungen besitzen, über die er mit seiner Umgebung kommuniziert. Neben diesen Schnittstellen darf keine andere Form der Bindung an die Umgebung vorliegen.
Dem dient beispielsweise die Kapselung : Durch das Verstecken bestimmter interner Bestandteile von Programmbausteine wird es verhindert, daß von außen auf diese zugegriffen werden kann. Dadurch wird es von Anfang an verhindert, daß die Umgebung über diese Bestandteile mit dem Programmbaustein verbunden wird. Nur über bestimmte dafür vorgesehene Anbindungen ist eine Kommunikation mit dem Programmbaustein möglich. Zur Wiederverwendung ist dann nur noch die Herstellung der Kommunikation über die wenigen vorgesehenen Anbindungen notwendig.
Kapselung
^ ^ ^
.--- | --- | --- | ---. Schnittstelle
| v v v |
|---------------------|
| interner |
| Bereich |
| (Implementation) |
'---------------------'
Selbst die Natur bedient sich dieses Prinzips und macht damit beispielsweise Organtransplantation möglich: Das Herz erfüllt eine bestimmte Aufgabe (hohe Kohäsion) und verfügt über wenige Ankopplungen (Zu- und abgehende Blutgefäße), dadurch kann es relativ leicht als ganzes transplantiert werden. Auch für technische Geräte findet dieses Prinzip durchgehend Verwendung: Fernseher, Mikrowellengeräte, Telefone und Rechner bestehe aus einem unzugänglichen inneren Teil und einer zugänglichen Schnittstelle.
Dieser Text selber ist ein anderes Beispiel für die Vermeidung unnötiger Kopplungen. Er bezieht sich beispielsweise kaum auf eine bestimmte Programmiersprache und kann dadurch auf viele Programmiersprachen angewendet werden.
Tertiärziel Lesbarkeit
Damit ein Programmbaustein in einem Projekt wiederverwendet werden kann, muß seine Funktion und seine Schnittstelle leicht verständlich sein. Diesem Ziele dient es also, Schnittstellen mit Hinblick auf leichte Verständlichkeit (für Menschen) zu schreiben. Es geht nicht nur darum, daß das Programm richtig funktioniert. Der Wiederverwendbarkeit eines Programmbausteinen dient es damit, wenn für diese eine Anleitung geschrieben wird, welche die Schnittstelle dieses Programmbausteines beschreibt.
Tertiärziel Musterverwendung
Schließlich zeigt es sich, daß erfahrene Programmierer bei größeren Softwareprojekten auf bestimmte Verfahren immer wieder zurückgreifen. Diese Verfahren der Konstruktion von Programmen werden auch als Muster (engl. “patterns ”) bezeichnet, mit Namen versehen und veröffentlicht, damit andere Programmierer sie wiederverwenden können, ohne sie erst mühevoll neu erfinden zu müssen.
Tertiärziel Änderbarkeit
Eine wichtige Form der Wiederverwendung eines Programmes ist es auch, dieses zu einem späteren Zeitpunkt als dem der ersten Fertigstellung wiederverwenden oder weiterverwenden zu können. Ein Programm soll also sozusagen in die Zukunft übertragen werden können, dies bedeutet, daß die Wartung des Programmes und die Anpassung an sich ändernde Umstände leicht möglich sein sollen. Man möchte nicht ein neues Programm schreiben müssen, nur weil ein Steuersatz oder eine gesetzliche Regelung geändert wurde oder ein neues Jahrtausend begonnen hat.
Die Kapselung erleichtert auch die Änderbarkeit eines Programmbausteines, etwas die Verbesserung der Implementation. Denn solange die Schnittstelle dadurch nicht verändert wird, bedeutet dies, daß in Folge einer Änderung der Implementation keine Änderungen an anderen Programmbausteinen nötig werden.
Die Änderung der Implementationen eines Programmbausteines soll auch dadurch erleichter werden, daß dieser ausreichend erklärt und beschrieben ist. Während die Anleitung zur Benutzung eines Programmbausteines dessen Benutzung an seiner Schnittstelle beschreibt, erklärt die Dokumentation die interne Realisierung der Funktion der Schnittstelle, also die Implementation. Da ein Programmierer, der einen Programmteil ändern soll, diesen erst einmal verstehen muß, spart es Zeit, wenn die Dokumentation ihm dabei hilft. Andererseits gibt es auch den Standpunkt (etwa im Rahmen des extreme programming ), daß der Code so übersichtlich und lesbar geschrieben werden soll, daß er sich selbst dokumentiert, ohne daß weitere zugefügte Erklärungen notwendig sind.
Die Dokumentation muß nicht gleich in der „heißen Phase“ einer Programmentwicklung geschrieben werden, wenn sich das Programm noch oft verändert, da es sehr aufwendig wäre, die Dokumentation immer wieder anzupassen. Es reicht, sie dann zu erstellen, wenn ein Programm eine gewisse Stabilität erreicht hat.
Die Lesbarkeit und Wartbarkeit wird auch erleichtert durch einen konsistenten, also nicht ständig wechselnden, Stil und die Verwendung bekannten Programmierparadigmen, wie etwa der strukturierten Programmierung, der objekt- oder aspektorientierten Programmierung oder der Verwendung bekannter Muster. Unverständliche Programmiertricks sollten vermieden oder wenigstens in der Dokumentation erklärt werden.
Primärziel Effizienz
Ein Programm ist effizient, wenn es die beim Programmablauf verfügbaren Ressourcen zur Lösung einer Aufgabe möglichst wenig in Anspruch nimmt. Oft müssen dabei Abwägungen getroffen werden, welche Ressourcen am meisten geschont werden sollen. Die entsprechende Einstellung eines Programmes wird auch als „Optimierung“ bezeichnet: sprachlich besser wäre es hier von der „Verbesserung“ des Programmes hinsichtlich eines bestimmten Zieles zu sprechen.
Aspekt Laufzeiteffizienz
Die Ressource, deren Schonung besonders häufig verlangt wird, ist die Laufzeit. Anders gesagt: Ein Programm soll oft möglichst schnell laufen.
Viele Fehler entstehen aber nur dadurch, daß unnötig früh versucht wird, dieses Ziel zu erreichen und dadurch die Korrektheit gefährdet wird. Daher wird oft empfohlen, sich zunächst nicht mit der Optimierung (hinsichtlich der Laufzeit) zu beschäftigen: Ein Programm sollte zunächst vor allem in Hinblick auf korrekte Funktion geschrieben werden. Erst wenn dann festgestellt wird, daß es zu langsam läuf, kann eine gezielte Optimierung begonnen werden. Die dazu angemessenen Verfahren sprengen den Rahmen dieses Textes und sollen daher hier nicht weiter behandelt werden.
Allerdings sollte eine als ineffizient bekanntes Vorgehen von Anfang an vermieden werden, wenn durch ein effizienteres Verfahren die Korrektheit und Sparsamkeit nicht bedeutend bedroht wird. Es ist darauf zu achten, in ein Projekt, keine ineffizienten Verfahren zu verankern, wenn diese später nur noch schwer durch effizientere ersetzt werden können. Wo Effizienz also von Anfang an ohne zu großes Risiko berücksichtigt werden kann, sollte dies also auch geschehen.
Allerdings gibt es bestimmte Programmiersprachen, wie etwa C und C++, bei deren Spezifikation Fragen der Laufzeiteffizienz eine große Rolle gespielt haben. Solche Sprachen kann man nicht richtig verstehen, ohne diese Überlegungen nachzuvollziehen. Ohne Interesse an Effizienz, würde man diese Sprachen vielleicht nicht verwenden. Daher müssen beim Unterricht dieser Sprachen schon von Anfang an, Effizienzüberlegungen einbezogen werden, um zu erklären, warum sie bestimmte Sprachelemente in der gegebenen Form enthalten.
Zusammenfassung
In der folgenden Übersicht werden die verschiedenen Ziele noch einmal zusammenfassend dargestellt.
Primärziel Korrektheit
Sekundärziel Definition
Sekundärziel Technische Unterstützung
Tertiärziel Strenge
Sekundärziel Sorgfalt
Sekundärziel Tests
Primärziel Sparsamkeit
Sekundärziel Wiederverwendbarkeit
Tertiärziel Portabilität
Tertiärziel Allgemeinheit
Tertiärziel Modularität
Tertiärziel Hohe Kohäsion
Tertiärziel Kontrollierte Kopplung
Tertiärziel Lesbarkeit
Tertiärziel Musterverwendung
Tertiärziel Änderbarkeit
Primärziel Effizienz
Aspekt Laufzeiteffizienz