Wie kann ich eine bessere Code-basierte Referenzdokumentation für Programmierschnittstellen schreiben?

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.)

Wenn jetzt nur alle Programmierer WritersSE beitreten und diese Frage/Antwort lesen würden!
@dmm "all" ist ein Stretch Goal, aber Sie können gerne einen Link an Leute weitergeben, die Sie kennen. :-)

Antworten (4)

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 findValuesagt 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 findValueanstelle 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 createEntryMethodendokumentation.) 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.

Zusammenfassung

Hier sind einige wichtige Punkte zum Schreiben einer guten API-Referenzdokumentation:

  • Dokumentieren Sie den Vertrag, nicht die Umsetzung. 1
  • Erkläre unscharfe Verben. Was bedeutet „finden“ im Vergleich zu „bekommen“? Legen Sie die Erwartungen der Benutzer fest.
  • Dokumentieren Sie Einschränkungen für Argumente oder Rückgabewerte, die nicht vollständig von der Signatur übermittelt werden (wie dass eine Ganzzahl positiv sein muss oder im Bereich 1-100 usw.).
  • Cover Misserfolg und nicht nur Erfolg. Können Argumente ungültig sein? Kann sich Ihr Code abnormal verhalten, selbst wenn die Eingaben gültig sind? Wie signalisieren Sie Fehler oder andere Probleme?
  • Seien Sie gründlich, aber nicht wortreich. Wiederholen Sie keine Informationen, die aus der Signatur hervorgehen.

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.

Den Vertrag erklären und dokumentieren („Angaben nicht wiederholen? ☺)? Übrigens kann die Entscheidung, welche Implementierungsdetails Teil des Vertrags sind, schwierig sein; Abstraktionen können auf unerwartete Weise durchsickern (z. B. können nicht zwischengespeicherte Zugriffe weniger kostspielig werden, wenn der Remote-Datenspeicher auf eine VM auf demselben Computer verschoben wird, dann kann das Deaktivieren des Cachings die durchschnittliche Leistung verbessern, und Hacks zur Bereitstellung zeitlicher Lokalität können zu Pessimierungen werden).
@PaulA.Clayton Hoppla, guter Fang – jetzt behoben. :-) Ich habe eine Anmerkung zu den Schwierigkeiten hinzugefügt, um festzustellen, was der Vertrag tatsächlich ist ; Es gibt dort eine größere Diskussion über das Softwaredesign, aber hier fühlt es sich an, als würde der Umfang kriechen. Aber es ist wichtig zu beachten, also danke für den Hinweis.
Definitiv Scope Creep. Leider vermute ich, dass das Thema zu breit und meinungsbasiert für jede SE ist (obwohl eine eingeschränktere Frage bei Programmierern funktionieren könnte und möglicherweise bereits gestellt wurde). Der Dokumentenumfang (und die Organisation [z. B. Q&A vs. Wiki]) ist ein schwieriges Problem für die Kommunikation im Allgemeinen. (Hör auf, geschwätzige Kommentare zu fördern! ☺)
Als Programmierer von Beruf würde ich es lieben, eine Dokumentation von der gleichen Qualität wie Ihr Beispiel zu sehen.

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.

Vielen Dank für das Aufrufen von Typen (wie List), die allein nicht alle erforderlichen Informationen übermitteln. Dies war früher auch ein Problem in Java (großartig, es gibt eine Collection zurück ... wovon?), bevor Generics in die Sprache kamen. Aber selbst die Angabe des Typs der Mitglieder eines Arrays/einer Liste/Sammlung/usw. ist nicht unbedingt ausreichend.

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) {...}
  1. Indem ich [message ID] für item ersetze, versuche ich darauf hinzuweisen, dass die Dokumentation so spezifisch wie möglich sein sollte. Wenn Sie feststellen, dass Sie den Namen des Parameters in der Beschreibung wiederholen, stimmt möglicherweise etwas nicht.
  2. Punkt 1 gilt auch für den Rückgabewert.
  3. Stellen Sie sicher, dass der Entwickler den Fall behandelt hat, in dem es keine entsprechende Zeichenfolge für [Nachrichten-ID] gibt.
  4. Schließlich sollten Sie als guter API-Tech-Autor nach Programmierproblemen Ausschau halten. Wenn eine Methode einen String zurückgibt, sollte sie nicht null zurückgeben, wenn nichts zurückzugeben ist. Stattdessen sollte es einen leeren String zurückgeben. Dies vereinfacht das Testen des Rückgabewerts der Methode.
  5. Wenn das Nichtfinden des Elements ein wirklich schwerwiegender Fehler ist, was bedeutet, dass die Anwendung nicht fortgesetzt werden sollte, sollte die Methode eine Ausnahme auslösen. Diese Ausnahme sollte ebenfalls dokumentiert werden.

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 .