Programmierer können Kommentare in Code schreiben, der automatisch in API-Dokumentation umgewandelt werden kann (wie Javadoc). Alles, was ich tun muss, ist, einige Kommentare hinzuzufügen, die erklären, was eine Klasse oder Methode tut und welche Argumente sie benötigt, und die Software verwandelt diese Kommentare und die Signaturen aus dem Code in eine Referenzdokumentation. Zum Beispiel kann ich so etwas in meinen Code schreiben:
/* Finds the value for the given ID.
* @param id the item to look up
* @return the value
*/
public String findValue(int id) {...}
Klingt toll, aber meine Leser sagen mir, dass meine Dokumentation nicht sehr hilfreich ist. Ich kann kein Buch in Codekommentaren schreiben, aber ich möchte, dass meine Dokumentation nützlich ist. Was sollte ich also tun, um bessere API-Referenzdokumente zu erstellen?
(Ich frage dies im Namen einer anderen Person. Da ich dieser Person bereits geantwortet habe, wollte ich sie auch hier beantworten.)
Beginnen Sie mit den Stilrichtlinien von Oracle für Javadoc . Während diese Richtlinien speziell für das Javadoc-Tool (und die Java-Sprache) geschrieben wurden, gelten die Prinzipien dort für die entsprechenden Tools für andere Sprachen. (Ich habe diese Art von Dokumentation für C++-, C#- und JavaScript-APIs gesehen.) Diese Antwort ergänzt diesen Styleguide.
Ich werde Ihr Beispiel kritisieren, um einige Prinzipien zu veranschaulichen. Beginnen wir mit der Beschreibung, die Sie geschrieben haben:
Findet den Wert für die angegebene ID.
Für eine Methode mit dem Namen findValue
sagt uns das nicht viel. Vor allem, wenn wir aus der Signatur ersehen können, dass es ein benanntes Argument braucht id
(das laut Ihrer Dokumentation "das nachzuschlagende Element" ist). Wir hätten das alles aus der Unterschrift selbst herausbekommen können; diese Dokumentation fügt nichts hinzu. Geben Sie sich keine Mühe, Dokumentation zu schreiben, die bereits offensichtlich ist.
Was soll man also zur Beschreibung schreiben? Beginnen Sie damit, zu klären, was "finden" bedeutet. Ist dies eine einfache Operation, die einen Wert aus einer In-Memory-Karte zurückgibt? Führt es einen Datenbankaufruf durch? Wird ein Dienst über eine langsame Verbindung aufgerufen? Geben Sie die Implementierung nicht preis, wenn sie nicht Teil des Vertrags ist, aber geben Sie den Lesern einige Hinweise darauf, was sie zu erwarten haben. Zum Beispiel:
Ruft den Wert für das Objekt der angegebenen ID entweder aus einem lokalen Cache (falls zuvor abgerufen) oder aus einem Remote-Datenspeicher ab.
Hier haben Sie Informationen hinzugefügt, die aus der Signatur nicht ersichtlich sind. Sie haben dem Leser gesagt, dass ein erster Abruf langsam sein kann, nachfolgende jedoch möglicherweise schneller – aber Sie haben keine konkreten Versprechungen gemacht, und Sie haben angedeutet, dass er immer noch langsamer sein könnte als eine einfache Suche im Speicher. (Vielleicht haben Sie dies deshalb findValue
anstelle von genannt getValue
.) Sie haben dem Anrufer gesagt: "Hey, vielleicht möchten Sie diesen Anruf nicht in einer engen Schleife tätigen, die sehr schnell sein muss".
Aber du bist noch nicht fertig. Sie sollten sich einige Fragen zu dieser Schnittstelle stellen. Was passiert, wenn der Ausweis nicht erkannt wird? Was passiert, wenn dieser ID kein Objekt zugeordnet ist? Was passiert, wenn Sie den Vorgang aus irgendeinem Grund nicht abschließen können (z. B. wenn das Netzwerk ausgefallen ist)? Ihr Code berücksichtigt wahrscheinlich diese Fälle, also teilen Sie dem Leser mit, was er zu erwarten hat .
Vergleichen Sie Ihre Dokumentation mit der folgenden:
/* Fetches the value for the object of the given ID, either from a local
* cache (if previously fetched) or from a remote data store.
*
* @param id the item to look up (a positive integer returned from
* createEntry)
* @return the value of id if set, or null if there is no value, or
* INVALID_ID if id is not recognized
* @throws AccessException if the data could not be accessed; this is an
* internal error that may require administrator attention
*/
public String findValue(int id) throws AccessException {...}
Erstens haben Sie vielleicht bemerkt, dass ich Ihren Code geändert habe, um die Ausnahme hinzuzufügen. Das liegt daran, dass es keine gute Antwort auf die Frage gab, was zu tun ist, wenn Sie die Datenbank nicht erreichen können – wir hatten an dieses Problem nicht gedacht und es nicht berücksichtigt. Idealerweise schreiben Sie Ihre Dokumentation , während Sie den Code schreiben , damit Sie solche Probleme erkennen und die erforderlichen Änderungen vornehmen können, bevor Sie Ihren Code freigeben. Wenn das hier nicht der Fall wäre, müssten Sie stattdessen etwas mehr Dokumentation schreiben, um zu erklären, was in diesem Fall passiert. (Und natürlich sollte später kein anderer Autor kommen und Ihren Code so ändern; ich gehe in dieser Antwort davon aus, dass Sie die Kontrolle darüber haben.) Der Prozess der Dokumentation einer Schnittstelle kann Probleme in dieser Schnittstelle aufdecken,
Ich habe Ihrer Parameterbeschreibung für zwei Dinge hinzugefügt id
. Erstens, dass es eine positive ganze Zahl ist; Es stellt sich heraus, dass IDs in Ihrem Code (den ich beim Schreiben dieser Antwort hypothetisch gelesen habe) nicht negativ sein können. Die Signatur vermittelt dies nicht (es sagt nur, dass es eine ganze Zahl ist), also dokumentieren Sie es. Zweitens habe ich gesagt, woher IDs kommen. (In der eigentlichen Dokumentation wäre dies ein Link zur createEntry
Methodendokumentation.) Dies ist nicht notwendig, kann aber hilfreich sein, insbesondere wenn ungültige IDs ein Problem für Ihre Benutzer darstellen.
Ich habe die Informationen zu ungültigen IDs und Nullwerten zur Dokumentation für den Rückgabewert hinzugefügt. Sie könnten es stattdessen der Beschreibung dessen hinzufügen, was die Methode tut. Es gibt vernünftige Argumente für beide Wege. Aber erklären Sie es irgendwo.
Beachten Sie, dass die Dokumentation der (neuen) Ausnahme sowohl sagt, was sie bedeutet (ohne Implementierungsdetails preiszugeben) als auch, was möglicherweise dagegen getan werden muss. In diesem Fall kann der Anrufer nichts tun, um den Fehler zu beheben, aber er möchte vielleicht seinen Benutzer benachrichtigen oder das Problem protokollieren. Diese Entscheidung überlassen wir ihm.
Sie hatten Bedenken, ein Buch schreiben zu müssen, um Ihre API-Dokumentation zu verbessern, aber alles, was Sie wirklich brauchten, waren ein paar gut durchdachte Sätze. Sie wollen kein Buch ; Eine gute API-Dokumentation sagt dem Leser alles und nur das, was er wissen muss. Dies kann kurz und bündig erfolgen, und Ihre Leser werden es Ihnen danken, dass Sie sie nicht durch eine Menge zusätzlichen Textes lesen lassen.
Hier sind einige wichtige Punkte zum Schreiben einer guten API-Referenzdokumentation:
1 Um dies zu tun, müssen Sie feststellen, was der Vertrag tatsächlich ist – was versprechen Sie Ihren Benutzern? Dies ist ein großes Softwaredesign-Thema, das über den Rahmen dieser Frage hinausgeht.
Als Softwareentwickler (C#, .NET, yada, yada) kam Monicas Antwort bei mir gut an. (Ich habe noch nicht genug Repräsentanten, um es zu kommentieren, also müssen meine Ergänzungen hier hin.)
Ich möchte hinzufügen, dass ich großen Wert auf eine API-Dokumentation lege, die so explizit wie möglich ist, aber nicht mit bedeutungslosen Details überladen ist. Außerdem ist es mir sehr wichtig, dass die Dokumentation mir sagt, was die API tun soll , nicht was sie tatsächlich tut. (Einige werden mir diesbezüglich nicht zustimmen; wenn die Software jedoch nicht das tut, was in der Referenz angegeben ist, handelt es sich um einen Defekt, der behoben werden muss.)
Wenn der Rückgabetyp einer Methode beispielsweise List ist, gibt die Methode null zurück, oder gibt sie garantiert immer eine Nicht-Null-Referenz von null oder mehr Elementen zurück?
Wenn eine Methode einen Referenztyp annimmt, wird sie ausgelöst, wenn das Argument null ist? Wenn das Argument als out oder ref definiert ist, wird es ausgelöst, wenn das Argument nicht initialisiert wurde? Wenn das Argument ein Array oder eine Liste ist, wird es ausgelöst, wenn die Sequenz leer ist? Wird die Methode ausgelöst, wenn eine erforderliche Konfigurationseinstellung fehlt oder ungültig ist? (Wenn ja, von welchen Konfigurationseinstellungen hängt es ab?)
Dies sind Dinge von immensem Wert für einen Softwareentwickler. Sie aufzuspüren, indem man sich durch möglicherweise Hunderttausende von Codezeilen wühlen muss, verschwendet Zeit und Geld und erhöht das Risiko und die Wahrscheinlichkeit, dass ich einen Fehler mache.
Bitte seien Sie so deutlich wie möglich und nicht weiter.
Bitten Sie Ihre Leser, Sie auf die API-Dokumentation hinzuweisen, die sie hilfreich finden .
Holen Sie sich Klarheit von Ihren Lesern. Meinen sie, dass das "Javadoc" zu vage ist? unvollständig? schlecht formuliert? Oder meinen sie, dass sie das Referenzdokument verstehen können, aber es hilft ihnen nicht, eine Anwendung mit der API zu schreiben?
Hier ist meine Bearbeitung des ursprünglichen Beispiels. Ich musste einige Annahmen hinzufügen:
/* Finds the [message string] for the given [message ID]. If the [message ID] doesn't
* exist, returns an empty String
*
* @param id the [message ID] to find
* @return the [message string], or if the [message ID] doesn't exist, returns an
* empty String
*/
public String findValue(int id) {...}
Als Benutzer verwenden Sie die API für bestimmte Zwecke. Sie haben bestimmte Ziele, die Sie erreichen möchten, und die API ist ein Tool, das Ihnen dabei helfen soll, diese zu erreichen.
Ihr Problem ist, wie Sie diese Ziele erreichen.
Denken Sie an die API „hardwareToolbox“, eine gängige Toolbox in Ihrer Garage. Es gibt einen Hammer, einen Satz Schraubenschlüssel, einen Schraubenzieher, eine Metallsäge und eine elektrische Bohrmaschine. Jede von ihnen sind Klassen, die ihre Methoden haben. Sie haben auch Nägel, Schrauben, Dübel usw. (alle von Right Factory-Objekten bereitgestellt.)
Sie möchten ein Bild an die Wand hängen. Du hast keine Ahnung, wie es geht.
Die richtige Vorgehensweise besteht darin, das Loch in die Wand zu bohren, einen Dübel mit einem Hammer einzusetzen und dann eine Schraube einzudrehen. Als Benutzer wissen Sie, dass Sie eine Schraube in der Wand benötigen, aber nicht viel mehr darüber hinaus.
Sehen wir uns nun Ihre Dokumentation an.
Class Electric_hammer_drill. Makes holes.
@methods: drill, hammer.
method drill: Makes holes in material that doesn't need hammering
@params: tool, depth, direction
@return: hole
method hammer: Makes holes in material that needs hammering
@params: tool, depth, direction
@return: hole
Class Hammer. Applies kinetic impulse to objects.
@methods: hit.
method hit: Applies kinetic impulse to object
@params: object, strength, location
Factory Screw_anchor: anchors screw to wall.
@params: inner_diameter, outer_diameter, length
Factory Screw: Binds things together
@params: diameter, length.
Class Screwdriver: Turns screws.
@methods turn
Method turn: Turns screw.
@params: direction, rotations, hole, screw.
...and about 40 other tools and items you don't need.
Siehst du, wohin ich gehe?
Der Benutzer wird ableiten: Ich brauche eine Schraube in der Wand. Um die Schraube zu bewegen, brauche ich einen Schraubendreher. Schraubendreher benötigt Loch. Um ein Loch zu machen, brauche ich den Bohrer. Durch viel Versuch und Irrtum entdecke ich, dass die Wand gehämmert werden muss und ein Mauerwerksstück als Werkzeug, um ein Loch zu erhalten. (Oder ich bohre einfach eine mit Holzbohrer und verfluche die Ineffizienz der API.) Dann versuche ich, die Schraube anzubringen, und sie hält nicht, und ich bin total frustriert.
Was Sie brauchen, sind Muster verwenden. Beispiele. ZWECKE.
Denken Sie daran.
method drill: Makes holes in material that doesn't need hammering, like wood, metal or plastic. Check table@... for right tool for the material.
@params: tool, depth, direction
@return: hole
method hammer: Makes holes in material that needs hammering, specifically concrete. Use masonry bit for a tool.
@params: tool, depth, direction
@return: hole
Factory Screw: Binds things together. Requires hole of matching diameter in flexible materials like wood, threaded hole in metal, or a screw anchor of matching inner diameter in concrete.
@params: diameter, length.
Factory Screw_anchor: anchors screw to wall. Requires a hole of outer_diameter in concrete type material. Provides hole of inner_diameter in flexible type material. Should be installed using a hammer. Typical use is allowing installing screws in walls.
@params: inner_diameter, outer_diameter, length
Anhand einer solchen Dokumentation sollte der Benutzer in der Lage sein, die korrekte Vorgehensweise zur Montage der Schraube für den Bilderrahmen abzuleiten. Es enthält Zwecke, tatsächliche praktische Erfordernisse (im Gegensatz zu formalen), Hinweise zur Verwendung bestimmter Werkzeuge, wann sie verwendet werden und ob sie in bestimmten Situationen spezifische Erfordernisse haben.
Noch besser ist es, reichhaltige Beispiele bereitzustellen, die jedoch außerhalb der direkten Funktionsdokumentation erscheinen sollen. Schreiben Sie jedoch nicht nur, was eine Funktion tut, sondern auch, wofür (Zweck - warum haben Sie dieses Ergebnis jemals benötigt), tatsächliche Anforderungen (nicht nur eine Liste von Parametern, die sie benötigen könnte , sondern Parameter, die sie unbedingt benötigt ), typische Verwendungsmuster (Hammer Beton, bohren Sie den Rest, ein Schraubanker muss mit einem Hammer eingetrieben werden) und Vorbehalte (Schraube hält nicht in Rohbeton, benötigt Schraubanker.)
Nur zu sagen, was eine Funktion tut, ist nicht hilfreich, wenn ich nie weiß, warum sie es tut und wie ich es dazu bringen kann, es richtig zu machen .
dmm
Monika Cellio