Wie schreibt man ein gutes Programm?

Das Ziel dieses Artikels ist es zu zeigen, wie man bei einem Software-Projekt vorgehen soll, um das Projekt mit Zukunfts-Perspektiven möglichst schnell und Erfolgreich fertig zu stellen. Vor allem wird gezeigt, dass zum Programmieren sehr viel Planung gehört, so dass Schreiben von Quelltext nur eine Teil-Aufgabe des erfolgreichen Programmierers ist.

Einleitung

Computer nehmen eine immer wichtigere Rolle in unserem Alltag ein. Viele Menschen können sich mittlerweile Ihren Beruf, Studium oder Freizeit nur noch bedingt ohne Computer vorstellen. Buchhaltung, Bearbeiten von Fotos, sogar Einkäufe und vieles mehr wird mithilfe von Computern getätigt. Kleine Computer sind sogar in Kaffeemaschinen oder DVD-Player zu finden. Diese Geräte vereinfachen uns das Leben und nehmen uns sehr viel Arbeit ab.

Jedoch die mechanische oder elektronische Ausrüstung dieser Geräte allein genügt nicht um komplexe Funktionen ausführen zu können, sondern benötigt zusätzlich Software, die dem Gerät Anweisungen gibt. Während Hardware gebaut wird, muss Software programmiert werden. Unter „Programmieren“ bezeichnet man die Kommunikation zwischen Menschen und Computer. Das Ziel dieses Papers ist es, dass möglichst gute Vorgehen bei einem Software-Projekt zu beschreiben.

Viele Programmierer begehen den großen Fehler, sich direkt an den Computer zu setzen, um daraufhin los zu programmieren, nachdem sie zumindest denken, das Problem erkannt zu haben. Dies mag bei kleineren Programmen funktionieren, aber bei größeren Projekten wird es schwierig und zeitintensiv. Daher ist es wichtig ein Projekt richtig zu managen und zu planen. Dazu gehört zunächst eine klare Problem-Definition, die zusammen mit dem Auftraggeber präzise erstellt wird. In den nächsten Schritten wird die Lösung des Problems geplant, nach nützlichen Algorithmen gesucht und die Struktur des Programmaufbaus definiert. Erst in den letzten Schritten, wird das Ganze in eine computerorientierte Sprache übersetzt.

Problem definieren

Um ein Problem lösen zu können, muss dieses natürlich zuerst definiert werden. Wenn nun der Programmierer selbst „Auftraggeber“ ist, wird es sehr einfach, die Definition nieder zu schreiben. Viele Programmierer überspringen dieses und nach einiger Zeit haben sie schon ihre Ideen vergessen. Darum ist das Aufschreiben von einer Problemdefinition auch in dem Fall wichtig.

Wenn sich nun Auftraggeber und Programmierer unterscheiden, sollten sie sich zusammensetzen um über das Problem zu diskutieren. In dieser Konferenz sollte eine genaue Angabe des Problems und mögliche Lösungsansätze des Auftraggebers aufgeschrieben werden. In den meisten Fällen ist der Programmierer in der Lage wertvolle Beiträge zur Problemdefinition und die technische Umsetzung der Lösung einzubringen. Soweit es sich nicht um ein kleines Programm handelt, sollte auch besprochen werden ob sich das Programm in der Zukunft erweitern wird. Falls dies der Fall ist, muss das Programm so entworfen werden, dass Änderung einfach durchzuführen sind. Soll das Programm eine grafische Benutzeroberfläche haben, sollten auch Skizzen der Benutzeroberfläche angefertigt werden.

Nach der Konferenz muss eine detaillierte, schriftliche Spezifikation erstellt werden. In den meisten Fällen wird dies vom Programmierer übernommen. Darin wird eine möglichst genaue Feststellung des Problems, die Eingabe- und Ausgabespezifikationen, sinnvolle Programmiersprachen, benötigte Hard- und Software sowie andere Faktoren die das Programm beeinflussen, aufgeschrieben.

Nachdem eine schriftliche Spezifikation erstellt worden ist, müssen sich Auftraggeber und der Programmierer nochmal treffen, damit sich der Auftraggeber die schriftliche Zusammenfassung ansehen kann. Falls bisher Missverständnisse aufgekommen waren, da der Auftraggeber über spezielles Hintergrundwissen in seinem Gebiet verfügt, worauf der Programmierer nicht spezialisiert ist, können diese nun nochmal besprochen und Unklarheiten beseitigt werden.

Wählen von Algorithmen, Bibliotheken und Programmiersprachen

Wenn das Problem definiert ist, kann man die Methoden bestimmen, es zu lösen. In manchen Fällen sind diese Methoden vom Auftraggeber durch sein Hintergrundwissen gegeben. In anderen Fällen, kennt man schon nützliche Algorithmen und Datenstrukturen aus Erfahrung. Oft lassen sich auch ähnliche Algorithmen im Internet finden, die nur noch angepasst werden müssen. Frameworks wie Boost, .NET u.v.m. bieten auch häufig sehr gute Möglichkeiten. Zusätzlich kann man noch erfahrenere Programmierer um Rat fragen, denn es ist immer hilfreich, das Problem aus mehreren Perspektiven zu sehen, zudem erfahrenere Programmierer meist bessere Konzepte und Techniken kennen. Wenn man passende Algorithmen gefunden bzw. erstellt hat, sollten diese genau notiert werden.

Nun kann man anhand der Spezifikationen, benötigte Bibliotheken und Algorithmen eine sinnvolle Programmiersprache auswählen. Heutzutage werden die meisten Programme mit objektorientierten Sprachen wie C++, C#, Java und Python entwickelt. Jeder dieser Sprachen hat ihre Vor- und Nachteile, auf die geachtet werden muss.

Benötigt das Programm hohe Systemleistung, sollte C++ aus den oben genannten Sprachen die erste Wahl sein. Die Standard-Bibliothek von C++ ist zwar selbst sehr eingeschränkt, jedoch freie Bibliotheken wie z.B. Boost bringen sehr viele Funktionalitäten mit. Mit C++ lassen sich z.B. mit Qt auch grafische Benutzeroberflächen erstellen, die unter Windows, Linux und MacOS ausgeführt werden können. C++ wird meistens für 3D-Spiele, Suchverfahren, Kompressionsverfahren und allgemein zeitkritische Anwendungen verwendet.

Hat man sich jedoch entschlossen, das Programm nur unter Windows auszuführen und die Geschwindigkeit ist zweitrangig, so ist C# eine Alternative. Mit der von Microsoft entwickelten Sprache C# hat man vollen Zugriff auf die .NET Framework und kann die moderne grafische Benutzeroberfläche von .NET nutzen. Die .NET Framework von Microsoft ist sehr mächtig und bietet gute Möglichkeiten um schnell ein Programm zu schreiben. Jedoch im Vergleich zu C++ ist der Sprachumfang von C# kleiner. Zum Beispiel lassen sich in C# keine lokalen statischen Variablen definieren und eine Art Mehrfachvererbung wird nur durch Interfaces geboten. Const-Correctness wurde genauso wie in Python ausgelassen. Mit dem Projekt Mono ist es auch möglich C# Code unter Linux zu kompilieren, jedoch noch nicht mit dem kompletten Funktionsumfang des .NET-Frameworks.

Die interpretierende Sprache Python gewinnt in letzter Zeit immer mehr Entwickler für sich, denn sie bietet eine Multi-OS Unterstützung und ihre Syntax wird häufig als sehr aufgeräumt und sauber empfunden. Die Standard-Bibliothek ist auch sehr umfassend. Grafische Benutzeroberflächen können z.B. mittels Qt-Bibliothek gebildet werden und sind wiederum auf Windows, Linux und MacOS ausführbar. Aber auch Webanwendungen lassen sich mit Python sehr gut erstellen. Die Sprache ist jedoch eingeschränkt gegenüber zu C++. Zum Beispiel wurde für die Vereinfachung der Sprache, Const-Correctness für Funktionen ausgelassen. Bei Python-Programmen sollte die Geschwindigkeit auch eine zweitrangige Rolle spielen, da der Interpreter nicht sehr schnell ist.

Java ist sowohl eine interpretierende und eine kompilierende Sprache, die auf Windows, Linux und MacOS ausgeführt werden kann. Sie bietet auch eine umfassende Bibliothek, womit sogar grafische Benutzeroberflächen erstellt werden können. Jedoch ist Java als Interpreter in den meisten Fällen langsam und der Garbage Collector verbraucht viel Speicher. Die Bibliotheken weisen Lücken auf, die zum Teil mit Java-Code nicht gefüllt werden können. Zum Beispiel ist es nicht möglich, systemnahe Informationen, wie z.B. die Prozessorauslastung, aus dem System auszulesen. Hierfür müssen über JNI (Java Native Interface) Funktionen einer systemnahen Sprache wie C++ angesprochen werden. Daher und begründet durch die Konkurrenz von C# wird Java in letzter Zeit seltener für neue Projekte verwendet.

Dies ist keinesfalls als ein umfassender Vergleich der modernen Programmiersprachen zu verstehen, jedoch aus meiner Sicht sind dort die wichtigsten Eigenschaften der Sprachen aufgeführt, wonach man grob eine moderne Programmiersprache für sein Programm auswählen kann.

Man sollte jedoch auch in Betracht ziehen, dass das Erlernen einer Programmiersprache bzw. das effektive Einsetzen der Sprache sehr zeitintensiv sein kann, wodurch man bei manchen Programmen mit einer Sprache, die man sehr gut beherrscht schneller und effektiver sein kann.

Modellierung und Struktur

Nach der Wahl der Programmiersprache, muss man die Struktur des Programms planen. Dies ist ein sehr wichtiger Schritt, denn später, wenn das Programm geschrieben wird, ist es oft sehr schwer grundlegende Strukturen zu ändern. Somit sollte die Struktur gut durchdacht werden. Vor allem, wenn das Programm erweiterbar sein soll, müssen Schnittstellen geschaffen werden, wo neuer Code andocken kann.

Das Konzept der objektorientierten Programmierung ist bei der Strukturierung des Programms sehr hilfreich. Denn einzelne Teilaufgaben können in Objekte bzw. Klassen programmiert werden und diese können wiederum von in der Klassenhierarchie höher liegenden Klassen benutzt werden. Es ist sinnvoll das Programm „Top-Down“ zu planen. Das heißt, mit der höchsten Ebene anzufangen und immer weiter tiefer in die Klassen-Hierarchie zu steigen. Dabei ist es wichtig, detailliert aufzuschreiben, wo welche Klasse in der Hierarchie platziert ist und welche Funktion sie besitzt. Dazu kann man z.B. Klassen-Stammbäume skizzieren.

In der Praxis kommt sehr oft das MVC-Entwurfsmuster (Model View Controller) zum Einsatz, wobei das Programm in 3 große Teile gespalten wird: Modell (Model), Präsentation (View) und Steuerung (Controller). Somit entstehen 3 große Klassen mit vielen Unterklassen.

Das Steuerungs-Objekt nimmt Benutzeraktionen, wie z.B. Maus-Klicks entgegen. Die Benutzeraktion wird nun zum Präsentations-Objekt oder Modell-Objekt weitergeleitet um z.B. die Benutzeroberfläche zu verändern oder anhand der Daten eine Funktion auszuführen. Die Präsentation wie schon erwähnt, nimmt Befehle vom Modell-Objekt sowie Steuerungs-Objekt entgegen und ist in der Lage dem Modell-Objekt Werte zu übergeben. Das Modell-Objekt ist das Kern des Programms, es reagiert auf dem Steuerungs-Modell, berechnet Werte oder führt Funktionen aus und übergibt seine Ergebnisse zum Präsentations-Objekt weiter.

Software-Aufbau nach diesem Prinzip ermöglicht z.B. ein einfacheres Auswechseln der Präsentations-Schicht mit z.B. anderen GUI-Bibliotheken. Dabei müssen lediglich die Steuerungs- und Präsentations-Klassen ausgewechselt bzw. verändert werden.

Passende Projekt-Umgebung schaffen

In den meisten Fällen und vor allem, wenn mehr als ein Programmierer an diesem Projekt arbeiten soll, ist ein Versionsverwaltungs-Tool wie z.B. Subversion sehr nützlich. Dabei wird auf einem Server ein Projektarchiv angelegt, das einzelne Revisionen vom Projekt enthält. Wenn nun ein Entwickler seine Änderungen am Quelltext vorgenommen hat und diese mit den anderen Entwicklern teilen möchte, überträgt er seine Änderungen auf dem Server. Die anderen Entwickler können jetzt ihre Revision aktualisieren, um die sogenannte Head-Version zu laden. Dabei werden nur die Veränderungen in den Dateien registriert und übertragen. Damit ist es sogar möglich, dass zwei Entwickler in derselben Datei arbeiten können ohne sich in die Quere zu kommen. Durch die Versionsverwaltung, kann Subversion auch als Backup der einzelnen Versionen verstanden werden, wodurch es oft eine große Hilfe beim Debuggen bereits ausgelieferter Versionen bietet.

Ein anderes sinnvolles Tool ist ein Bugtracker, worin sich Aufgabenlisten für Entwickler erstellen lassen. Bugzilla ist in dem Bereich ein sehr mächtiges Tool, jedoch bietet Trac, das ebenso eine webbasierte Oberfläche einsetzt, zusätzlich ein Wiki und ermöglicht das Betrachten von Subversion-Projektarchiven. Somit können z.B. im Wiki die Software-Spezifikationen und die Modellierung für Entwickler veröffentlicht werden und im Bugtracker die Aufgaben für die Entwickler verteilt werden. Zusätzlich kann man über die Web-Oberfläche schnell in die verschiedenen Revisionen schauen.

Ein sehr gutes Dokumentations-Tool ist Doxygen, das viele der gängigen Programmiersprachen unterstützt. Dabei werden einfach im Quelltext spezielle Kommentare gemacht, aus denen Doxygen beispielsweise eine HTML-Dokumentation generiert. Somit kann man z.B. direkt während man eine Klassen programmiert ihre Funktion mit Kommentaren und Variablen beschreiben. Dies erspart Zeit und ist einfacher als eine „externe“ Dokumentation. Sogar eine Klassen-Hierarchie wird von Doxygen ermittelt und aufgelistet.

Die Dokumentation spielt eine große Rolle, falls Änderungen an bestehenden Quelltext durchgeführt werden müssen oder sogar Funktionen von anderen Klassen benötigt werden. Sie ist somit ein sehr wichtiges Teil des Programms. Ob ein Programmierer allein entwickelt oder ein Team - niemand kann sich Einzelheiten Monate oder Jahre lang merken. Somit sollte die Dokumentation eine Pflicht neben dem Programmieren sein. Die Spezifikationen, die mit dem Auftraggeber erstellt worden sind, die Aufschriften der Modellierung und allgemein alle Daten, die in der Zukunft nützlich sein könnten, sollten in der Dokumentation vorhanden sein.

Programmieren und debuggen

In diesem Schritt wird die Logik und Struktur, die festgelegt worden ist, für den Computer mit der gewählten Programmiersprache übersetzt. Falls mehrere Entwickler an diesem Programm entwickeln sollte man sich darauf einigen, in welchem Stil programmiert wird, damit der Quelltext später möglichst einheitlich aussieht und leichter lesbar ist. Die Aufgaben sollten über das Bugtracking verteilt sein und dann kann es mit dem eigentlichen Programmieren los gehen.

Dabei geht man genauso wie beim Erstellen des Modells vor. Zuerst werden die oberen Ebenen programmiert und man arbeitet sich immer mehr nach unten zu den spezielleren Modulen. Es ist dabei zu achten, dass Funktionen, Klassen und Variable sinnvolle Namen erhalten und das gleichzeitige Dokumentieren nicht vergessen werden sollte. Sinnvoll ist es auch Dummy-Module zu programmieren, die in der Klassen-Hierarchie tiefer liegende Objekte ersetzen, um die oberen Klassen zu testen und zu debuggen.

Sobald man in die tieferen Schichten des Klassen-Baumes kommt, sollten Test-Klassen programmiert werden, die immer automatisch Klassen testen, indem sie vom Programmierer festgelegte Parameter übergeben und den zurückgegebenen Wert mit dem erwarteten Wert vergleichen.

Nachdem das Programm komplett ohne Fehler kompiliert, muss es gründlich getestet werden. Die Entwickler sollten zunächst alle Funktionen des Programms aus Sicht des Benutzers testen. Dabei werden die meisten Fehler festgestellt, die debuggt werden müssen. Man kann zwar nie alle Fehler herausfinden, jedoch sollte es versucht werden. Dazu kann man noch andere Test-Personen auffordern mit dem Programm umzugehen, um vielleicht mögliche Fehler zu finden.

Auf dem Wunsch des Auftraggebers kann nun natürlich auch eine Benutzer-Dokumentation erstellt werden und das Programm ist fertig für die Abgabe beim Auftraggeber.

Da das Programm sehr wahrscheinlich noch Bugs enthält die erst später gefunden werden, wird es anhand der guten Dokumentation auch Jahre später einfacher sein diese zu beheben.

Fazit

Wir konnten hier zeigen, dass Programmieren viel mehr bedeutet als „nur“ Quelltext zu schreiben. Schon in mittelgroßen Software-Firmen, gibt es Projektmanager, die ihre Programmierer streng nach solchen Schritten arbeiten lassen und kontrollieren. Dieses Paper soll nur grobe Übersicht über die Software-Management und Software-Technik geben, die einem Software-Entwickler in einer Firma erwartet und sollte nicht als eine Schritt für Schritt Anleitung für alle Programm-Sorten verstanden werden. Jedoch für Einsteiger dürfte diese Anleitung etwas Klarheit in die Software-Management bringen.