Informationen zu Abschlussarbeiten der Datenbank-Gruppe

Empfehlungen zur Java-Programmierung

Zu relativ vielen Abschlussarbeiten gehört auch die Entwicklung eines Programms. Dies ermöglicht es z.B., die vorgestellten Ideen auch an Beispielen auszuprobieren. Die Existenz einer Implementierung garantiert auch, dass die Ideen bis ins Detail durchdacht worden sind, während eine rein textuelle Beschreibung möglicherweise Lücken aufweist. Natürlich befreit eine Implementierung nicht davon, die Ideen auch hinreichend präzise und formal im Text der Arbeit zu beschreiben, aber eine funktionierende Implementierung gibt erstmal einen Vertrauensvorschuss beim Gutachter. In einigen Fällen ist die Implementierung auch unbedingt nötig, wenn z.B. Laufzeiten gemessen werden sollen.

Natürlich sollen Sie auch zeigen, dass Sie gut programmieren können. Es ist ja eine wichtige handwerkliche Fähigkeit, die Sie in Ihrem Studium erworben haben sollten. Viele Lehrveranstaltungen im Studium haben direkt oder indirekt dazu beigetragen, dass Sie im Rahmen Ihrer Abschlussarbeit auch qualitativ hochwertigen Programmcode erstellen können. Demonstrieren Sie auch diese wichtige Kompetenz.

Diese Seite ist "under construction". Ich habe mir vorgenommen, zukünftig aufzuschreiben, was mir bei Entwürfen von Programmen auffällt, die ich zu sehen bekomme (und was von allgemeiner Bedeutung ist). Momentan ist es noch ziemlich unvollständig.

Die Java Code Conventions von Sun/Oracle sind sicher lesens- und bedenkenswert. Ich stimme nicht mit allem vollständig überein, aber da das eine offizielle Empfehlung ist, können Sie vermutlich nichts falsch machen, wenn Sie dem folgen. Es gibt aber natürlich leichte Abweichungen davon, die auch ok sind.

Es gibt auch den Google Java Style Guide.

Und Java Style Guidelines aus dem Umfeld der OpenJDK-Initiative.

Formatierung

  • Versuchen Sie bitte, die Zeilenbreite von 80 Zeichen bei einer Tabulatorgroesse von 8 Zeichen einzuhalten. Ich kann den Programmtext dann besser in meinem Editor lesen. Mein Editor (gvim) bricht die Zeilen bei 80 Zeichen um und setzt sie in der nächsten Zeile fort. Das sieht unübersichtlich aus. Aber die Alternative, dass man den Scrollbar verwenden muss, um den Rest der Zeile zu sehen, ist doch auch nicht besser.
  • Während bei vielen Entwicklungsumgebungen die Tabulatorgröße wählbar ist, und inzwischen häufig vier Zeichen voreingestellt sind, ist die klassische Tabulatorgröße bei UNIX 8 Zeichen. Ich glaube, dass das noch immer portabler ist (und wenn jemand eine für TABs mit 8 Zeichen formatierte Datei mit der TAB-Größe 4 anschaut, sollte es noch anständig aussehen, was umgekehrt nicht unbedingt der Fall ist). Ich stelle die Tabulatorgröße auf 8 Zeichen ein, wo ich kann.
  • Man kann Methodenköpfe mit vielen Parametern auch über mehrere Zeilen verteilen, mit passender Einrückung. Ebenso kann man natürlich lange Statements sinnvoll über mehrere Zeilen verteilen. Lange Stringkonstanten können Sie auch aus mehreren Stücken mit + zusammensetzen. Wenn alles nicht hilft, wäre es vielleicht an der Zeit, über eine Hilfsmethode nachzudenken, oder eine Klasse, in der Sie die Daten verpacken können.
  • Rücken Sie konsistent ein. Wenn man von einer geschweiften Klammer zu } nach oben peilt, sollte man das zugehörige if, for, while etc. sehen.
  • Wenn Sie die Einrückung sauber und konsistent machen, behalten Sie auch selbst besser den Überblick.

Dokumentation

  • Verwenden Sie JavaDoc zur Dokumentation von Klassen und Methoden und ggf. Paketen.
  • Verschieben Sie das nicht zu lange, am Ende macht es sicher keinen Spass mehr, noch alles nachzudokumentieren. Es hilft Ihnen auch, selbst die Übersicht zu behalten.

Stil

  • Vermeiden Sie Code-Duplizierung. Das ist schlechter Stil.
  • Bemühen Sie sich um größtmögliche Einheitlichkeit und leichte Änderbarkeit.
  • Wählen Sie gute, verständliche Namen für Klassen, Methoden und Variablen.
  • Öffentlich zugreifbare Attribute sind fast immer ein übler Stil-Fehler. Attribute sollten private sein.
  • Deklarieren Sie Konstanten als static final.
  • Nutzen Sie Konstruktoren. Wenn irgend möglich sollten Objekte bei der Erzeugung schon einen sinnvollen Zustand bekommen. Ideal ist natürlich, wenn ein Objekt nach der Erzeugung gar nicht mehr verändert werden kann. Das geht nicht immer, aber es spart Ihnen später Sorgen, ob das Objekt vielleicht unabsichtlich geändert wurde.
  • Nutzen Sie for-Schleifen, wo es sich anbietet. Eine einfache Zählschleife mit while zu schreiben, halte ich für ein Zeichen, dass Sie mit Ihrer Entwicklung in der Mitte des ersten Semester stehen geblieben sind.
  • if(flag == true) ist auch so eine Sache. Das schreibt man natürlich if(flag).
  • Wenn eine Fallunterscheidung mit if einen kurzen Fall (z.B. Fehler) und einen langen Fall (die eigentliche Arbeit) hat, sollten Sie den kurzen Fall zuerst schreiben. Dann braucht man den noch offenen Fall nicht lange in Erinnerung zu behalten. Wenn man Tests auf Fehler zuerst macht, kann man die Methode auch mit return verlassen, wenn man einen Fehler entdeckt hat. Dann braucht man im Rest nicht mehr so tief zu schachteln.

Import-Anweisungen

  • Verwenden Sie keine Wildcards "*" in import-Anweisungen, sondern listen Sie die verwendeten Klassen explizit auf. Es wird sonst unübersichtlich, woher eine Klasse kommt. Es wäre auch denkbar, dass vielleicht zu einem Tippfehler eine Klasse gefunden wird.

Typisierung: Klassen, Aufzählungstypen

  • Führen Sie eigene Typen für alle Konzepte in Ihren Programm ein (Klassen oder Aufzählungstypen). Die starke Typisierung hilft zum Verständnis und zur Vermeidung/Erkennung von Fehlern.
  • Wie soll ich z.B. Map<String, List<String>> in einem Programm als Rückgabe- oder Argumenttyp einer Methode verstehen? Natürlich verstehe ich, dass ich ein Objekt bekomme, in das ich eine Zeichenkette hineinstecken kann, und eine Liste von Zeichenketten herausbekomme. Aber was das für Zeichenketten sein sollen, und was die Abbildung als Ganzes bedeutet, müsste ich mir mühsam aus dem Kontext zurechtlegen. Sie können solche Typen natürlich zur Implementierung benutzen (als Typ eines privaten Attributs). Aber das ganze Ding sollte einen sinnvollen Klassennamen haben, und auch die Methode zum Nachschauen eines Eintrags sollte anwendungsspezifisch benannt sein. Wahrscheinlich sollte man auch eine eigene Klasse für die Liste von Strings einführen, die als Ergebnis geliefert wird.
  • Irgendwelche Codes wie 1, 2, 3 gehen natürlich nicht. Führen Sie einen Aufzählungstyp (enumeration) ein.
  • Zu der guten Typisierung gehört auch, dass Sie den Konstruktor unbrauchbar machen, wenn eine Klasse keine Instanzen haben soll (sondern nur statische Methoden). Der Compiler legt ja einen public Default-Konstruktor an, wenn man keinen deklariert hat. Deklarieren Sie einen Konstruktor private, und schreiben Sie vielleicht noch assert false; hinein oder werfen Sie eine Exception. Joshua Block empfiehlit in "Effective Java": throw new AssertionError(); Das kann man sicher nicht übersehen.
  • Selbstverständlich sollten Sie auch alle Methoden private deklarieren, die nur als interne Hilfsmethoden gedacht sind.

Fehler

  • Prüfen Sie alle Eingaben auf Fehler. Ihr Programm soll bei keiner möglichen Eingabe abstürzen.
  • Noch schlimmer als ein Absturz ist ein unbemerkter Fehler, wenn also Ihr Programm einen Teil seiner Funktion gar nicht ausgeführt hat (z.B. die eingegebenen Daten nicht geschrieben hat), oder falsche Ergebnisse berechnet hat, aber das Problem für den Nutzer nicht offensichtlich ist, weil er am Ende die übliche Ausgabe erhält. Das darf nicht passieren! Fangen Sie z.B. nicht Exceptions ab und ignorieren die dann einfach.
  • Wenn Sie eine Methode aufrufen, die Exceptions werfen kann, fangen Sie die Exception möglichst dicht darum ab, so dass Sie noch eine sinnvolle Fehlermeldung geben können.
  • Wenn denkbar ist, dass eine Methode einen Nullpointer zurückgibt, testen Sie das, und drucken Sie ggf. eine sinnvolle Fehlermeldung, statt es einfach später beim Zugriff mit einer NullPointerException "knallen" zu lassen.
  • Achten Sie auf verständliche und zutreffende Fehlermeldungen.
  • Es könnte günstig sein, sich eine Klasse zur Ausgabe von Fehlermeldungen zu machen. Dann können Sie zentral entscheiden, ob Sie die nach System.err oder System.out ausgeben wollen, oder was sonst damit geschieht (Logging). Wahrscheinlich sieht alles auch einheitlicher aus.
  • Es gibt natürlich auch offizielle Logger für Java. Siehe z.B. In unserem SQL Lernspiel wird SLF4J, die "Simple Logging Facade for Java" benutzt, um von den verschiedenen Logging-Bibliotheken zu abstrahieren. Das eigentliche Logging macht momentan LOGBack. Siehe auch die Anleitung von Base22.
  • Es könnte günstig sein, toString() für jede Klasse zu definieren, so dass Sie sich beim Debugging oder für Kontrollen leicht die wesentlichen Daten eines Objekts ausgeben können.
  • Ich persönlich verwende assert für Konsistenztests und zum Aufspüren von Fehlern. Manchmal können Sie dadurch Fehler früher entdecken (näher dran am tatsächlichen Fehler), und nicht erst, wenn ein Programm viel später abstürzt oder ein falsches Ergebnis liefert. Es ist auch eine gute Dokumentation der Voraussetzungen an einer bestimmten Stelle.

Konfigurierbarkeit

  • Denken Sie daran: "Die einzigen Programme, die nie geändert werden, sind die, die nicht verwendet werden."
  • Wenn Sie also schon vorhersehen können, das jemand in Zukunft vermutlich einmal etwas ändern möchte, erleichtern Sie ihm/ihr das Leben. Dann wird der Eindruck dieses Menschen von Ihrem Code besser.
  • Was soll ich z.B. von einem Programm halten, das sogar eine Konfigurationsdatei hat, in der der Name des Datenbank-Servers festgelegt wird, das dann aber an mehreren Stellen doch direkt einen bestimmten Servernamen im Quellcode stehen hat?
  • Eine übliche Regel ist, dass der Code keine Konstanten außer 0 und 1 direkt enthalten soll, sondern nur symbolische Konstanten. Vielleicht ist das ein bisschen extrem, aber es zeigt die richtige Richtung an.
  • Insbesondere ist es übel, wenn Sie wissen, dass an zwei Stellen im Programm der gleiche Wert stehen muss, und Sie haben ihn zwei Mal direkt hingeschrieben, statt eine symbolische Konstante (die an genau einer Stelle definiert ist). Solche Situationen schreien förmlich nach dem Fehler, dass jemand den Wert an einer Stelle ändert, aber nicht an der anderen.

Nutzung von Datenbanken

  • Passworte im Programmcode sind nicht optimal. Mindestens sollten alle Daten fuer die Datenbank-Verbindungen in eine Konfigurationsdatei ausgelagert werden. Wenn ich das Programm testen will, hätte ich ja wahrscheinlich andere Nutzer-Accounts und Passworte. Wenn wir das Programm später ins Internet stellen, würden wir eine Beispiel-Konfigurationsdatei beilegen, die gerade nicht die tatsächlichen Passworte enthält, die Sie auf Ihrem Rechner nutzen. Viele Datenbank-Server sind automatisch am Netz.
  • Denken Sie daran, dass Datenbank-Verbindungen eine relativ begrenzte Resource sind. Wenn sich Ihr Programm mehrfach mit der Datenbank verbindet, sollten Sie die vorige Verbindung unbedingt schließen.
  • Es dauert auch etwas, eine Datenbank-Verbindung aufzubauen. Wenn Sie die gleiche Verbindung für alle SQL-Befehle in Ihrem Programm verwenden können, sollten Sie das tun.
  • Lassen Sie die Datenbank ihren Job machen. Es wäre z.B. blöd, einen Join oder eine Aggregation auszuprogrammieren, wenn das auch in einem SQL-Statement geht. Sie sparen sich Arbeit (und damit Zeit), und es läuft auch schneller, weil die Datenbank dafür optimiert ist, und nur einmal Daten zwischen Client und Server hin und hergeschickt werden müssen.
  • Nutzen Sie Prepared Statements. Setzen Sie nicht SQL-Statements als Strings zusammen. Denken Sie an die Möglichkeit von SQL Injection Angriffen.

Ausgabe-Dateien

  • Wenn Sie HTML oder ein anderes Format ausgeben, achten Sie darauf, dass es auch im Texteditor lesbar ist. Z.B. sollten Sie nicht alles in einer langen Zeile ausgeben. Dem Browser ist es zwar egal, aber Sie müssen es ja vielleicht einmal debuggen, oder zumindest kontrollieren.

Vermeiden Sie gefährliche Operationen

  • Denken Sie daran, dass eine Datenbank bzw. ein Datenbank-Server nicht selten von verschiedenen Anwendungen genutzt wird. Der Prototyp des SQL Lernspiels hat gerade alle Datenbanken auf meinem PostgreSQL Server gelöscht! Ich halte das nicht einmal für ok, wenn es in der README Datei gestanden hätte.
  • Auch das Löschen oder Verändern von Dateien, die nicht ganz ausdrücklich exklusiv nur für Ihr Programm verwendet werden, ist nicht ok, oder fordert mindestens eine Sicherheits-Nachfrage.

Zusammenarbeit in Projekten

  • Wenn Ihre Abschlussarbeit zu einem größeren Projekt gehört, an dem auch andere Studierende mitarbeiten (oder sogar der Professor), müssen Sie damit rechnen, dass andere Team-Mitglieder ihren Beitrag eventuell nicht rechtzeitig fertig stellen.
  • Sie sollten also auf jeden Fall auch einen unabhägig zu testenden Prototyp erstellen, z.B. nur mit einer Konsolen-Schnittstelle. Dann haben Sie etwas, mit dem Sie Ihre Ideen demonstrieren können, selbst wenn sich das Gesamtprojekt verzögert oder es sogar scheitert. Letztendlich sind für die Abschlussarbeit ja die konzeptuellen Fortschritte wichtig, nicht so sehr ein schönes Programm. Eine Implementierung ist aber wichtig, weil Sie so demonstrieren können, dass Ihre Ideen funktionieren, und Sie an alle Details gedacht haben.
  • Im allgemeinen möchte man ja auch ein Modul getrennt testen, bevor es in ein großes Projekt eingebaut wird. Insofern ist die Empfehlung, eine eigene, kleine Testumgebung zu bauen, kein ungewöhnliche, zusätzliche Last, die Ihnen auferlegt wird. Wahrscheinlich wird die Fehlersuche auch leichter, wenn weniger fremde Software beteiligt ist, und Sie die Aufrufe Ihrer Methoden direkter kontrollieren können.
  • Wenn Ihr Teil des Projektes eventuell veränderte Schnittstellen benötigt, sollten Sie das ganz frühzeitig melden. Denken Sie über die Schnittstellen eher zu Beginn der Bearbeitungszeit nach, spätestens in der Mitte. Je später Sie Änderungswünsche melden, desto unwahrscheinlicher wird es, dass Ihre Wünsche noch rechtzeitig erfüllt werden.
Prof. Dr. Stefan Brass
Impressum