Kommentierung
Inhalt:
Projekt-Überblick-Datei
Package-Kommentare
Klassen-Kommentare
Methoden-Kommentare
JavaDoc erzeugen
Kommentare innerhalb Methoden
Allgemein
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
Real programmers don't comment their code - it was hard to write, it should be hard to understand
Und als Beweis letzterer Aussage: Mein persönlicher Favorit für Variablen-Kommentierung (in C#, Erklärung folgt weiter unten):
//regulärer Ausdruck für benutzerdefinierte Eingaben
private Regex neuerReg = new Regex("\\$(?<Tag>[^\\[]*)\\[(?<Typ>[^\\]]*)\\]\\$");
JavaDoc-Kommentare finden zwei Verwendungen:
a) Lesbarkeit des Quellcodes verbessern (z.B. wenn ein neuer Programmierer das Projekt übernimmt)
b) Dokumentation für Closed-Source-Bibliotheken, die ohne Quellcode vertrieben werden, oder auch Open-Source-Bibliotheken,
bei denen man den Quellcode nicht direkt zur Hand hat.
In letzterem Fall ist eine gute Dokumentation besonders wichtig.
Die Kommentierung erfolgt auf drei Ebenen:
- Klassen-Kommentare: beschreiben die Klassen im Überblick.
- Methoden-Kommentare: beschreiben die einzelnen Methoden im Detail
- Kommentare innerhalb des Code: diese sind für den Nutzer einer Library unwichtig, aber sinnvoll für den, der sich mit dem Quellcode befassen muss.
Gute Kommentare sollten alle Fragen beantworten, die sich bei einen Blick auf Klasse oder Methode stellen.
Projekt-Überblick-Datei
Siehe auch http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#overviewcomment.
Wenn man für die JavaDoc der gesamten Anwendung eine Startseite angeben will, kann man das über einen "overview comment" machen:
Man legt eine eigene HTML-Datei ins Projekt (oder an beliebige andere Stelle). Die empfohlene Stelle ist der Quellcodeordner, und der Dateiname
könnte "overview.html" sein.
Bei der JavaDoc-Erzeugung wird der "body"-Inhalt dieser Datei in die Datei "overview-summary.html" der generierten JavaDoc übertragen.
Zur Verwendung in Eclipse siehe unten.
Laut Oracle-Seite sollte der "title"-Inhalt ebenfalls für die JavaDoc übernommen werden. Aber scheinbar muss man in Eclipse den Titel
separat beim JavaDoc-Erzeugen als Option angeben.
Package-Kommentare
Die Package-Kommentare geben einen kurzen Überblick über das jeweilige Package (bzw. in unserem Fall wohl die gesamte Anwendung).
Siehe http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#packagecomment
Variante 1: "package-info.java"
In jedem package kann eine Datei "package-info.java" liegen. Diese enthält nichts außer einer "package"-Deklaration und JavaDoc-Kommentare dazu:
/**Erklärungen zum Package oder zur Anwendung...
*
* @author Ich war's!
*
*/
package de.hs_rm.cs.bla.fasel;
Variante 2: "package.html"
Alternativ kann man eine Datei "package.html" ins Package legen. Beim JavaDoc-Erzeugen wird nur der "body"-Inhalt in die JavaDoc übertragen.
Variante 1 (package-info.java) ist wohl die empfohlerene Variante, weil hier Tools (Eclipse) die Datei besser validieren können.
Beispiel für einen Package-Kommentar: http://docs.oracle.com/javase/7/docs/api/java/util/package-summary.html.
Klassen-Kommentare
Die Kommentierung der Klasse sollte einen Überblick über die Funktion der Klasse geben. Nach dem Lesen der Klassenkommentare sollte
man wissen, wofür sie überhaupt da ist, und sollte die grundlegende Funktionsweise kennen.
Dies alles gilt natürlich auch für Interfaces ! Falls eine Klasse ein Interface implementiert, sollten die Kommentare an beiden Stellen identisch sein.
Beispiel 1: org.apache.commons.lang.StringUtils
aus der Library "commons-lang" der Apache Foundation:
http://commons.apache.org/lang/api-3.1/org/apache/commons/lang3/StringUtils.html.
Es handelt sich um eine Sammlung von static Methoden, die keine Wechselwirkungen untereinander haben. Deshalb hat die Klasse keinen Status
(in Form von Membervariablen).
Was finden wir hier an Vorteilen ?
- Guter Grobüberblick: Der Kommentar gibt hauptsächlich ein Kurzüberblick über die möglichen Methoden, wodurch man alles schnell erfassen kann.
- Grundlegende Konzepte: Im Klassenkommentar wird erklärt, was in den folgenden Methoden z.B. unter einem "Whitespace" zu verstehen ist.
Beispiel 2: java.text.SimpleDateFormat
: http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html.
Hier handelt es sich eine Klasse, von der man Instanzen erzeugen muss. Die Klassen-Kommentare gehen leider wenig auf die Verwendung ein.
Was können wir hier lernen ?
- Erzeugung von Instanzen: Der Kommentar weißt darauf hin, dass man Instanzen idealerweise über static Methoden erzeugt.
- Funktionsweise: Die Datumsformate werden sehr detailliert beschrieben, einschließlich Beispielen.
Beispiel 3:java.util.Hashtable
: http://docs.oracle.com/javase/6/docs/api/java/util/Hashtable.html
Dies ist ebenfalls eine Klasse, von der Instanzen erzeugt werden. Der Klassenkommentar gibt einen groben Überblick über die Verwendung und geht
detailliert auf Performanz-Fragen ein.
Was bringt uns das ?
- Klassenüberblick: hier ist in Fließtext-Form die grundlegende Verwendung der Klasse beschrieben.
- Besonderheiten bei der grundlegenden Verwendung der Klasse: es wird einigermaßen detailliert auf "initial capacity" und "load factor" eingegangen.
- Besonderheiten bei einzelnen Aspekten der Klasse: es wird explizit darauf hingewiesen dass der Iterator-Aspekt des Hashtable
unter gewissen Umständen gefährlich sein kann.
Gegenbeispiel 4:
aus dem JBoss 4.0.5: http://docs.jboss.org/jbossas/javadoc/4.0.5/system/org/jboss/deployment/MainDeployer.html
Bei dieser schicken Klasse besteht der Kommentar aus "The main/primary component for deployment."
Es steht nichts dazu, was sie überhaupt macht oder wie man Instanzen erzeugt (das ist vor allem knifflig, wenn eine Client-Anwendung von außerhalb des
Servers auf sie zugreift).
Zusammenfassung:
Ein Klassenkommentar sollte:
- Einen Überblick über die Verwendung geben.
- Erklären, wie man an Instanzen der Klasse kommt (falls es nicht über einen Standard-Konstruktor geht)
- Grundkonzepte der Klasse erklären
- Sonstige wichtige Informationen über die Klasse geben (z.B. Performanz-Betrachtungen, Beziehung zu anderen Klassen)
Methoden-Kommentare
Die nächste Stufe der Kommentierung nach den Klassenkommentare sind die Methoden-Kommentare.
Die Kommentare müssen für die jeweilige Methode beschreiben, was genau sie tut, und welche Rahmenbedingungen zu beachten sind.
Beispiel 1: aus dem Stateless-Beispiel:
/**
* Berechnet ein Quader-Volumen
*
* @param a Seite a
* @param b Seite b
* @param c Seite c
* @return Volumen
* @exception InvalidParameterException
*/
public double computeCuboidVolume(double a, double b, double c) throws InvalidParameterException
Die grundlegende Funktionalität ist beschrieben.
Nicht so gelungen sind hier:
- Aus der Dokumentation geht nicht hervor, dass es Einschränkungen für die Parameter a, b und c gibt. Gemäß Doku wären hier +5, 0 und -5 zulässig.
- Es wird nirgends erklärt wie das Volumen überhaupt ermittelt wird.
- Wann und wie die Exception ausgelöst wird, wird nirgends erklärt.
Beispiel 2: das gleiche in besser:
/**
* Berechnet ein Quader-Volumen nach der Formel "a*b*c"
*
* @param a Seite a, mit Wert >= 0
* @param b Seite b, mit Wert >= 0
* @param c Seite c, mit Wert >= 0
* @return Volumen nach der Formel "V = a * b * c"
* @exception InvalidParameterException Wenn a oder b oder c < 0
*/
public double computeCuboidVolume(double a, double b, double c) throws InvalidParameterException
Hier sind die obigen Punkte behoben, die Doku läßt hoffentlich keine Fragen offen.
Beispiel 3: org.apache.commons.lang.exception.ExceptionUtils
aus dem Apache-Commons-Paket: http://commons.apache.org/lang/api-3.1/org/apache/commons/lang3/exception/ExceptionUtils.html#getMessage(java.lang.Throwable)
Der Kommentar ist nicht allzu ausführlich, aber ich denke man hat nach einem Blick darauf ein brauchbare Vorstellung vom Verhalten bekommen.
Auch hier:
- Besonderheiten bei Parameter (Verhalten bei Übergabe von "Null") sind beschrieben.
- Aussehen der Rückgabe ist beschrieben.
- Exceptions gibt es diesmal nicht.
Beispiel 4: Besonderheiten bei Verwendung:
Dieser Ausschnitt ist aus dem KuchenZutatNM-Beispiel. Ich gebe zu, dass er in dieser Form dort nicht so zu finden ist ;-).
/**Abrufen der Zutatenliste.
*
* Beim Entfernen einer Zutat aus dem Kuchen werden die verbundenen Zutaten NICHT gelöscht !
* Beim Laden des Kuchen werden sie NICHT mitgeladen. Ein Verwender kann die Zutatenliste eines aus der DB geladenen Kuchen also
* nur abrufen, solange die Bean unter Entity-Manager-Kontrolle ist.
*
* @return Liste der Zutaten.
*/
@ManyToMany(mappedBy="kuchen", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
public Collection<ZutatNMBean> getZutaten()
{
return this.collZutaten;
}
Da die Annotations nicht im JavaDoc erkennbar sind, muss hier einiges zur Verwendung erklärt werden, nämlich der Umgang mit den Zutaten beim
Löschen des Kuchens, sowie die Auswirkungen des FetchType.LAZY
.
Beispiel 5: Verweise:
Jetzt wollen wir Beispiel 4 noch verbessern: um auch im JavaDoc-HTML die beiden Seiten der Relationship zu "verbinden", können
wir unter Verwendung des @see
-Tags Verweise zu anderen Methoden oder Klassen erzeugen.
Das Javadoc-Tool erzeugt dabei HTML-Links, durch die man mit einem Klick zur anderen Methode gelangt.
/**Abrufen der Zutatenliste.
*
* Beim Entfernen einer Zutat aus dem Kuchen werden die verbundenen Zutaten NICHT gelöscht !
* Beim Laden des Kuchen werden sie NICHT mitgeladen. Ein Verwender kann die Zutatenliste eines aus der DB geladenen Kuchen also
* nur abrufen, solange die Bean unter Entity-Manager-Kontrolle ist.
*
* @return Liste der Zutaten.
* @see ZutatNMBean#getKuchen()
*/
@ManyToMany(mappedBy="kuchen", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY)
public Collection<ZutatNMBean> getZutaten()
{
return this.collZutaten;
}
Die Syntax ist: "Package.Klasse#Methode". Die Angabe des Packages ist optional, wenn wir das Ziel des Verweises innerhalb des gleichen Packages wie
die aktuelle Klasse liegt.
Gegenbeispiel 6: viele offene Fragen
Dieses Beispiel ist aus einer .NET-Library, mit der ich in der Firma arbeiten muss, und die wir angekauft haben. Die Doku einer Enumeration
für Excel-Linienstile sieht so aus:
Die Kommentare zu den einzelnen Werten geben keine Informationen, die über den Enum-Namen hinausgehen. Interessant wäre z.B. gewesen wieviele Pixel eine "Medium"-Linie breit ist.
Gegenbeispiel 7: großes Mysterium
Dieser Codeausschnitt stammt aus einem C#-Projekt, ich bin bei mir auf der Arbeit darüber gestolpert. Es ist eine Property in C#-Syntax, aber hoffentlich verständlich.
Man beachte die Kombination von nichtssagenden Namen (was ist ein Primär-/Sekundär-Intervall?) und belanglosen Kommentaren:
/// <summary>
/// Setzt und liefert den Primaer-Intervall
/// </summary>
public DateTimeNullableVonBis PrimaerIntervall
{
get
{
return this.intervallPrimaer;
}
set
{
this.intervallPrimaer = value;
}
}
/// <summary>
/// Setzt und liefert den Sekundär-Intervall
/// </summary>
public DateTimeNullableVonBis SekundaerIntervall
{
get
{
return this.intervallSekundaer;
}
set
{
this.intervallSekundaer = value;
}
}
Gegenbeispiel 8: ich weiß doch, was das macht!
Dieses Methode stammt aus der Realität und dient dazu, einen RGB-Wert in einen Grauton umzurechnen.
/// <summary>
/// Macht aus einer Farbe einen Grauton.
/// </summary>
/// <param name="farbe"></param>
/// <returns></returns>
private Color ProjeziereAufGrau(Color farbe)
{
int intGrau = (int) Math.Round( 255* Math.Sqrt(farbe.R* farbe.R + farbe.G*farbe.G + farbe.B * farbe.B) / (Math.Sqrt(3) * 255) ,0);
Color graustufe = Color.FromArgb(intGrau, intGrau, intGrau);
return graustufe;
}
Erfunden hat sie ein ehemaliger Mathematiklehrer, für den geometrische Operationen wohl ein Kinderspiel sind. Aber
ich hätte keine Chance, das Verfahren nachzuvollziehen.
Trivial-Operationen:
Man mag argumentieren, dass Kommentare für Trivial-Operationen wie Property-Zugriffe unnötige Tipparbeit sind.
Meiner Meinung nach bedeutet ein nicht vorhandener Kommentar allerdings nur, dass man absolut keine Aussage über die Methode
machen kann.
Dass die Methode "KuchenBean.getName()" nur die Property "name" zurückliefert, ist für den Programmierer, der nur die JavaDoc
zur Hand hat, nicht zu erkennen. Für ihn ist die Methode eine Blackbox, und hier kann alles passieren. Wer garantiert, dass "getName" nicht einen
aufwändigen Datenbankzugriff ausführt, um den Namen des aktuellen Kuchens aus einer völlig anderen Datenbank-Tabelle zu holen ?
Der Programmierer weiß allerdings, dass ihm kein Unheil droht, wenn im Kommentar etwas harmloses wie "Liefert den Namen des Kuchens" steht, und nichts davon steht
dass hier schlimmer Voodoo vonstatten geht.
Ein weiteres Beispiel: "ZutatBean.getMenge/setMenge": Was hat man sich unter "Menge" vorzustellen? Welchen Regeln genügt dies ? Dass hier ein
Freitext ohne weitere Regeln dahintersteckt, und Eingaben wie "100g Mehl", "5 Liter Milch" etc. erlaubt sind, sieht man von außen nicht.
Eigentlich hilft auch ein Blick in den Quellcode nicht weiter, denn dies ist eine Information, die man nur aus der Anforderungsdefinition erhält,
und die existiert nur in meinem Kopf.
Doppel-Kommentare in Interface und Klasse:
In der EJB-Welt sind Local-/Remote-Interface so dicht verdrahtet, dass die Kommentare für Interface und Bean-Klasse eigentlich absolut identisch sein können.
Rein theoretisch würde es also genügen, z.B. das Interface zu dokumentieren, und in der implementierenden Methode der Bean-Klasse nur mittels @see
-Tag
auf die Interface-Methode zu verweisen. Allerdings macht es grundsätzlich Sinn, bei der Klassen-Methode einige Details über die Implementierung zu beschreiben,
falls diese Relevanz für den Verwender haben.
Beispielsweise genügt für eine allgemeine Interface-Methode "getDaten" die Beschreibung "Liest die Daten ein, wobei dies abhängig von
der Implementierung ist". Die implementierenden Klassen könnten jetzt aber Beschreibungen wie "Liest die Daten aus der Datenbank" oder "Greift auf eine XML-Datei
zu um die Daten einzulesen. Diese muss folgendes Format haben: ...".
Für den Fall, dass Kommentare in Klassenmethode und Interfacemethode absolut identisch sind, kann man folgenden Kommentar deklarieren:
/**{@inheritDoc}
*/
@Override
public KuchenNMBean findKuchenById(Integer int_Id)
{
Das Tag {@inheritDoc}
sucht den Methodenkommentar in Basisklassen oder Interfaces und übernimmt ihn von dort. Allerdings muss man die Kommentare
trotzdem in Remote- und Local-Interface duplizieren.
Siehe: http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javadoc.html#inheritingcomments
Das Tag gilt per Default für den gesamten Kommentar. Es lässt sich aber auch für einzelne Parameter, den Rückgabewert oder Exceptions definieren, so dass nur
deren Kommentare übernommen werden.
Übrigens kann man den aus der Interface-Methode gezogenen Kommentar auch erweitern.
/**{@inheritDoc}
Zusätzlicher Kommentar.
@param int_Id {@inheritDoc} (und außerdem gilt für diesen Parameter auch: ...)
*/
@Override
public KuchenNMBean findKuchenById(Integer int_Id)
{
Jetzt wird der Methodenkommentar aus dem Interface gezogen und mit dem zusätzlichen Kommentartext verbunden. Das gleiche gilt für den Parameter-Kommentar.
Die Kommentare aus dem Interface werden jeweils an der Stelle des {@inheritDoc}
eingetragen.
Zusammenfassung:
Ein Methodenkommentar sollte:
- Einen Überblick über die Funktion geben.
- Besonderheiten bei der Verwendung erklären.
- Erlaubte und unzulässige Werte für Parameter erklären
- Die Rückgabewerte erklären
- Exceptions und deren Ursachen erklären
- Bussiness-Logik erklären (vor allem, falls die nicht mal aus dem Quellcode hervorgeht)
- Und vor allem: dem Verwender das Gefühl geben, dass nichts innerhalb der Methoden passiert, was nicht in den Kommentaren steckt.
JavaDoc erzeugen
Das Erzeugen von JavaDoc geschieht leider in Eclipse nicht manuell. Man wählt das Projekt aus, für das man JavaDocs erzeugen will, und ruft
im Menü "File" -> "Export..." auf. Im Export-Dialog wählt man "Java" -> "Javadoc" aus:
Alternativ wäre auch Menü "Project" => "Generate Javadoc..." möglich und würde zum selben nächsten Schritt führen.
Im nächsten Schritt wählt man die Projekt aus, für die man generieren will. Ich empfehle hier, alle Teilprojekte der Enterprise-Application zu wählen,
im Beispiel also EJB-, Web- und ApplicationClient-Projekt. Außerdem wird die Option "Create Javadoc for members with Visibility" auf "Protected" gesetzt.
Als Zielpfad empfehle ich ein Unterverzeichnis außerhalb des Projekts. Grund: ansonsten explodiert unsere Fehlerliste, weil das generierte
HTML ebenfalls validiert wird.
Im Feld "javadoc command" muss man eventuell erst den Pfad zu "javadoc.exe" eintragen, je nachdem ob man im Projekt eine JRE oder ein JDK verwendet.
In Schritt 3 können wir alles auf den Defaults lassen.
In Schritt 4 müssen wir die "JRE source compatibility" vom Default "1.3" auf "1.6" ändern, damit Annotations verarbeitet werden:
Falls wir eine Anwendungsüberblick-Datei erzeugt haben ("overview.html)", geben wir den Pfad zur HTML-Datei in Schritt 4 an
(Screenshot ist noch aus einer veralteten Version der Dokumentation - der Dateiname "package-template.html" stammt aus einem Mißverständnis meinerseits!):
Die Ausgabe des Javadoc-Laufs erscheint auf einer Konsole, und hier sollten wir ein Auge auf Fehler oder Warnungen haben.
Kommentare innerhalb Methoden
Diese Kommentare spielen für JavaDoc bzw. für Verwender der Library natürlich keine Rolle. Sie sind aber wichtig, wenn ein neuer Entwickler
ins Projekt kommt, oder wenn man selbst Workarounds einbaut und sich später noch an diese erinnern will.
Ich würde empfehlen, logisch zusammenhängende Codeblöcke per Kommentar zusammenzufassen. Also nicht vor jeder Codezeile ein Kommentar,
sondern immer vor Teilstücken.
Beispiel 1:
Dieses Beispiel stammt aus der "KuchenZutatNMWorkerBean" des KuchenZutatNM-Beispiel:
/**Die übergebene Zutat löschen.
*
* @param intZutatId ID der zu löschenden Zutat.
* @exception EntityNotFoundException Wenn Zutat nicht gefunden wurde.
*/
public void deleteZutat (Integer intZutatId)
{
logger.info ("deleteZutat " + intZutatId);
//Die Zutat im EntityManager laden.
//Hier mit "getReference" arbeiten, damit eine böse EntityNotFoundException fliegt wenn Zutat nicht gefunden wird.
ZutatNMBean zutat = this.entityManager.getReference(ZutatNMBean.class, intZutatId );
//Jetzt wird es knifflig: wir müssen die Kuchen der Zutat holen,
//und aus den Zutat-Collections der Kuchen die Zutat entfernen.
//Grund scheint zu sein dass der EntityManager ansonsten noch die
//Zutat im Kuchen hält und dabei in einen Fehlerzustand läuft.
Collection<KuchenNMBean> listeKuchen = zutat.getKuchen();
Iterator<KuchenNMBean> iteratorKuchen = listeKuchen.iterator();
while (iteratorKuchen.hasNext() == true)
{
//Zutat aus der Zutatenliste des Kuchens entfernen:
KuchenNMBean kuchenMitZutat = iteratorKuchen.next();
kuchenMitZutat.getZutaten().remove(zutat);
}
//Jetzt endlich dürfen wir die Zutat löschen.
this.entityManager.remove(zutat);
}
Hier sind einzelne Abschnitte kommentiert.
Es gibt zwei wichtige Fragen:
- a) Warum wird "entityManager.getReference" verwendet statt "find" ?
Erklärung: um bei ungültigen IDs eine Exception auszulösen.
- b) Was macht die Schleife ? Der Kommentar beschreibt zusammenfassend,
was die Schleife überhaupt macht. Er ist hier sehr detailliert und begründet, warum die folgende Aktion notwendig ist.
In diesem Detailgrad sind Kommentare nicht mehr nötig, wenn man genügend Erfahrung im Umgang mit Relationships gesammelt hat
und auf den ersten Blick sieht was zu tun ist.
Aber gerade für einen EJB3-Neuling ist der Sinn dieser Schleife ohne detaillierten Kommentar nicht erkennbar.
Beispiel 2:
Dieses Beispiel stammt aus der "KuchenZutatNMWorkerBean" des KuchenZutatNM-Beispiel:
/**Finden des Kuchens zur übergebenen ID.
*
* @param int_Id ID des gesuchten Kuchens
* @return Gefundener Kuchen oder null, wenn nicht gefunden (es gibt in diesem Fall keine Exception)!
*/
public KuchenNMBean findKuchenById(Integer int_Id)
{
logger.info ("findKuchenById " + int_Id);
//Den Kuchen im EntityManager laden.
//Hier mit "find" arbeiten, da bei ungültiger ID "null" zurückkommen soll.
KuchenNMBean kuchen = this.entityManager.find(KuchenNMBean.class, int_Id);
//Falls etwas gefunden wurde, dann die Relationship einlesen.
if (kuchen != null)
{
//Da der FetchType der ManyToMany-Relation auf LAZY gesetzt ist müssen die Zutaten
//hier explizit abgerufen werden solange die KuchenNMBean noch nicht
//detached ist. Später würde ansonsten ein Zugriff auf die Zutatenliste eine
//Exception auslösen.
Collection<ZutatNMBean> collZutaten = kuchen.getZutaten();
//Wir müssen einen Zugriff auf die Collection selbst ausführen, Abrufen der Property alleine reicht nicht !
logger.info ("Anzahl Zutaten: " + collZutaten.size());
}
else
{
logger.info ("Kuchen zur ID " + int_Id + " nicht gefunden !");
}
return kuchen;
}
Der Methoden-Kommentar macht präzise Aussagen, was bei einer ungültigen ID passiert.
Innerhalb der Methode ist der relevante Kommentar der Workaround für Probleme beim FetchType.LAZY
: Schaut ein Entwickler später in den Code, dann wird er wahrscheinlich
keinen Sinn mehr hinter dem doch scheinbar unnötigen collZutaten.size()
erkennen. Deshalb muss hier ein Kommentar hin.
Gegenbeispiel 3: Selbsterklärend!
Dieses Beispiel (C#) findet sich in diesem Kapitel, da es sich um eine private Membervariable handelt, die deshalb in generierter Javadoc nie auftaucht und
deshalb nicht für das Erstellen von Libraries relevant ist.
//regulärer Ausdruck für benutzerdefinierte Eingaben
private Regex neuerReg = new Regex("\\$(?<Tag>[^\\[]*)\\[(?<Typ>[^\\]]*)\\]\\$");
Zur Erklärung mein Wissensstand zu dem Zeitpunkt, als ich das entsprechende Feature nutzen wollte und irgendwo im Quellcode die Doku dazu suchte:
Es handelt sich um eine Klasse "Abfrage", über die im Programm SQL-Statements unter einem bestimmten Namen abgespeichert werden können. Später können User diese
Abfragen aus einer Liste auswählen und ausführen und erhalten das Resultset der Datenbankabfrage als Tabelle zurück. Ich wusste außerdem, dass man in diesen
Abfragen irgendwie Parameter angegeben kann (die vom User per Dialog abgefragt werden), also z.B. ein Datum. Für diese Parameter muss ein Anzeigename und
ein Datentyp angegeben werden. Und auf der Suche nach der genauen Syntax habe ich an der untersten Stelle angefangen, also bei der Deklaration der regulären Expression.
Preisfrage: wer kann die Syntax hierfür skizzieren bzw. ein Beispiel bauen (eine Auflösung gibt es nicht ;-) )?
Man beachte auch den geschickt und aussagekräftig gewählten Namen der Variablen ;-).
Ich habe später an einer völlig anderen Stelle im Code (nämlich in der Klasse für den Bearbeiten-Dialog der Abfragen) den gesuchten Kommentar zumindest in Ansätzen
gefunden, aber da war es schon zu spät.
Falls ihr später mal eurem Chef Rechenschaft ablegen müsst: ich brauchte ca. 30 Minuten, bis ich über Trial&Error die richtige Syntax herausgebracht hatte.
Wie lange hätte eine aussagekräftige Kommentierung gedauert?
Gegenbeispiel 4
Folgenden Kommentar eines Kollegen habe ich am 26.11.2007 in einem Codeschnipsel gefunden (die von ihm geänderte Zeile führte nebenbei zu einem Crash
an anderer Stelle). Es handelt sich übrigens um eine C#-Property:
public override DateTime Von
{
get
{
...
}
set
{
//Anpassen auf den NÄCHSTEN Montag:
DateTime datMontag = DatumZeitFunktionen.GetMontag(value);
// Korrektur JK 18.01.2007
if (datMontag < value) datMontag = datMontag.AddDays(7);
base.Von = datMontag;
}
Wir haben danach eine Viertelstunde lang gerätselt, was die eingefügte Zeile zu bedeuten habe, und mussten am Ende sogar beim Kunden anrufen,
um uns bestätigen zu lassen, was wir entschlüsselt hatten ;-). Ich muss meinem Kollegen allerdings zugute halten, dass er immerhin ein
"Schuldeingeständnis" in den Code gesetzt hat (in Form eines Änderungs-Kommentars). Ansonsten hätte ich die mir unsinnig erscheinende Zeile
wohl einfach auskommentiert (versehen mit einem "//Erscheint mir sinnlos und führt zu Bug in Situation xyz (WKnauf 26.11.2007)"), und
dadurch wäre die Situation wohl nicht besser geworden ;-).
Gegenbeispiel 5
Nochmal ein Beispiel aus der praktischen Arbeit: im folgenden Codeschnipsel waren zwei Zeilen auskommentiert.
Der originale Code erschien mir sinnvoll, aber es war nicht zu erkennen, a) wer der "Schuldige" war und b) warum es überhaupt auskommentiert war.
if (PeMKBBDataGlobal.HasBenutzerRecht(AppBaseGUIGlobal.ApplicationBase, AppBaseGUIGlobal.ApplicationBase.Benutzer, RechtPeMKBB.Objekt_Termin_Zeiten_Bearbeiten))
{
//this.dtpIstBeginn.Enabled = true;
//this.dtpIstEnde.Enabled = true;
this.dtpBeginn.Enabled = true;
this.dtpEnde.Enabled = true;
this.pnlBeginnEndeSynch.ReadOnly = false;
}
Zusammenfassung
- Kommentare sollten immer Code-Blöcke zusammenfassen und damit die Lesbarkeit des Codes erhöhen.
- Sie sollen Besonderheiten und Workarounds erklären, und zwar so, dass man auch nach 5 Jahren noch weiß was man sich damals
gedacht hat !
- Wenn man in bestehendem Code etwas ändert, dann macht es Sinn, einen Bekennerbrief und eine Begründung zu hinterlassen ;-).
Kommentare der Form "Schleife über xyz" oder "IF-Abfrage", nur um meinen Kommentar-Hunger zu stillen, sind natürlich total unnötig.
Wenn es wirklich nichts zu sagen gibt, dann ist auch kein Kommentar nötig. Aber sobald eine IF-Abfrage mehrere mit UND oder ODER verknüpfte
Bestandteile hat, muss man die Bedingung einfach in Prosa-Text übersetzen, sonst kapiert man sie selbst nicht.
Meine Trivial-Beispiele enthalten leider nicht allzuviele Stellen, an denen es wirklich knackig wird. Aber die Realität besteht oft
aus fünfzeiligen IF-Bedingungen mit vielen Klammern ;-).
Stand 03.01.2013
Historie:
16.12.2008: erstellt aus Vorjahresbeispiel, angepaßt an Eclipse 3.4, mehr schlechte Beispiel aus der Praxis.
31.12.2008: RegEx-Beispiel
03.06.2009: {@inheritDoc}
erweitert
12.07.2009: Negativ-Beispiel 8 für Methodenkommentare
21.12.2011: Div. Links repariert
03.01.2013: Overview-Datei, Beschreibung der Package-Kommentare korrekt gemacht und erweitert