How can I write better code-based reference documentation for programming interfaces?
Programmers can write comments in code that can be automatically turned into API documentation (like Javadoc). All I have to do is add some comments explaining what a class or method does and what arguments it takes, and software turns those comments and the signatures from the code into reference documentation. For example, I can write something like this in my code:
/* Finds the value for the given ID.
* @param id the item to look up
* @return the value
*/
public String findValue(int id) {...}
Sounds great, but my readers tell me that my documentation isn't very helpful. I can't write a book in code comments but I want my documentation to be useful, so what should I be doing to make better API reference docs?
(I'm asking this on behalf of somebody else. Having already answered that person, I wanted to also answer it here.)
4 answers
One possibility on why the readers didn't find the documentation helpful may be that they are unfamiliar to the API, and the reference documentation is the only documentation available.
Reference manuals are there for the people who already are familiar with the API, and only have to look up the specifics of a function. It is almost always not suitable for people who are completely new to the API (an exception is e.g. a collection of math functions that are independent of each other).
This is a trap that especially projects using auto-generated reference manuals tend to fall into: They think they auto-generate reference documentation, therefore their API is adequately documented. But if your only documentation is the reference, then your API is not adequately documented.
As an example, take an imaginary graphics library.
If you look at the reference manual for the drawLine
function, then you might find something like this:
void drawLine(Canvas c, GraphicsContext gc, Point start, Point end)
Draws a line on a given canvas, using the settings from the given graphics context.
Arguments:
Canvas c
: The canvas to draw on. If Null, the function does not draw anything, but still updates the optional graphics context passed as second argument.
GraphicsContext gc
: The graphics context to use. If Null, the default graphics context of the supplied canvas is used. If no canvas is given either (the first argument is Null, too), the call returns immediately without doing anything.The graphics context is used to determine the line style and drawing mode, and optionally the start and/or end point. On return, the
initialPoint
andcurrentPoint
values of the graphics context are updated as described in the next two parameters.See the type documentation for
GraphicsContext
for details.
Point start
: The point where to start the line. If Null, thecurrentPoint
from the graphics context is used. If not Null, the value is stored in the graphics context asinitialPoint
on return.
Point end
: The point where to end the line. If Null, theinitialPoint
from the graphics context is used. On return, thecurrentPoint
of the graphics context is set to this value.
Now if you are familiar with the graphics library, this documentation contains everything you need. But if you are not familiar with it, it will raise more questions than answers: What is a canvas, and how do I get one? What is a graphics context? What's that thing about initialPoint
and currentPoint
?
Now for sure, you can find all this in the reference documentation; you'll probably start by looking up the Canvas and GraphicsContext types, which will then lead you to functions that can be used to create those types and manipulate the GraphicsContext, probably involving other concepts like devices, leading you to the documentation of the corresponding types and functions for those … basically you'll have to discover the structure of the API on your own.
And that's where a programmer's manual comes in: This does not tell you every detail of the API, but it tells you the high-level aspects. It presents you the key concepts of the API in a logical and easy to grasp manner, tells you which steps you have to do in which order to get to the point where you can issue drawing commands, tells you abut the most important differences between drawing on the screen and drawing in a file, etc.
Basically, the programmer's manual allows you to get familiar with the API, while the reference library tells you the details of every single function and type, assuming you are already familiar with the API.
The two manuals are different because they are for different audiences. Someone new to the API will mainly use the programmer's manual. Someone familiar with the API will exclusively use the reference manual.
And most importantly, a programmer's manual cannot be generated from comments on the specific functions. Sorry, you'll not get to avoid the work.
0 comment threads
As a user, you use API for certain purposes. You have certain goals you want to achieve, and the API is a tool that should help you achieve them.
Your problem is, how to achieve these goals.
Think of API "hardwareToolbox", which is a common toolbox in your garage. There's a hammer, there's a set of wrenches, a screwdriver, there's a hacksaw and an electric drill. Each of them are classes that have their methods. You also have nails, screws, screw anchors, etc. (all provided by right Factory objects.)
You want to hang a picture on the wall. You have no clue how to do it.
The right procedure is to drill the hole in the wall, put a screw anchor in, using a hammer, and then screw a screw in. As the user you know you need a screw in the wall, but not much more beyond this.
Now let's look at your documentation.
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.
Do you see where I'm going?
The user will deduce: I need a screw in the wall. To move screw I need screwdriver. Screwdriver requires hole. To make hole I need the drill. By a lot of trial and error I discover wall requires hammering, and a masonry bit for a tool to obtain a hole. (or I just drill one with wood bit, cursing the inefficiency of the API.) Then I try to fit the screw and it doesn't hold, and I'm all frustrated.
What you need is use patterns. Examples. PURPOSES.
Think of this.
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
Basing on such a documentation the user should be able to deduce the correct procedure for installing the screw for the picture frame. It contains purposes, actual practical requisites (as opposed to formal), hints on how to use given tools, when to use them, and if they have specific requisites in specific situations.
Even better is to provide rich examples, but these are to appear outside the direct function documentation. Nevertheless, besides writing what a function does, write its what for (purpose - why'd you ever need that result), actual requisites (not just a list of params it might take, but params it absolutely needs, typical usage patterns (hammer concrete, drill all the rest, a screw anchor needs to be driven by a hammer) and caveats (screw won't hold in raw concrete, needs screw anchor.)
Just telling what a function does is not helpful if I never know why it does it, and how to get it to do it right.
This post was sourced from https://writers.stackexchange.com/a/11012. It is licensed under CC BY-SA 3.0.
0 comment threads
Start with the style guidelines from Oracle for Javadoc. While those guidelines are written for the Javadoc tool (and the Java language) in particular, the principles there apply to the corresponding tools for other languages. (I've seen this kind of documentation for C++, C#, and JavaScript APIs.) This answer augments that style guide.
I'm going to critique your example as a way of illustrating some principles. Let's start with the description you wrote:
Finds the value for the given ID.
For a method named findValue
, this doesn't tell us very much. Especially when we can see from the signature that it takes an argument named id
(which your documentation says is "the item to look up"). We could have figured all that out from the signature itself; this documentation doesn't add anything. Don't expend effort writing documentation that's already obvious.
So what should you write for the description? Start by clarifying what "find" means. Is this a lightweight operation that returns a value from an in-memory map? Is it making a database call? Is it calling a service on a slow connection? Don't reveal implementation if it's not part of the contract, but do give readers some hints about what to expect. For example:
Fetches the value for the object of the given ID, either from a local cache (if previously fetched) or from a remote data store.
Here you've added information not obvious from the signature. You've told the reader that a first fetch may be slow but subsequent ones may be faster -- but you've made no specific promises, and you've implied that it still might be slower than a simple in-memory lookup. (Maybe that's why you named this findValue
instead of getValue
.) You've told the caller "hey, maybe you don't want to make this call inside a tight loop that has to be really fast".
But you're not done. You should be asking yourself some questions about this interface. What happens if the ID isn't recognized? What happens if there's no object mapped to that ID? What happens if you can't complete the operation for some reason (like the network is down)? Your code probably accounts for these cases, so tell the reader what to expect.
Compare your documentation to the following:
/* 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 {...}
First, you may have noticed that I modified your code to add the exception. That's because there wasn't a good answer for the question about what to do if you can't contact the database -- we hadn't thought of that problem and hadn't accounted for it. Ideally you're writing your documentation while you write the code, so you can catch issues like this and make the necessary changes before you release your code. If that wasn't the case here, then you'd instead need to write some more documentation to explain what happens in that case. (And obviously another writer shouldn't come along later and modify your code like this; I'm assuming in this answer that you have control over this.) The process of documenting an interface can reveal problems in that interface, so start early.
I added two things to your parameter description for id
. First, that it's a positive integer; it turns out that in your code (which I hypothetically read while writing this answer), IDs can't be negative. The signature doesn't convey this (it just says it's an integer), so document it. Second, I said where IDs come from. (In the actual documentation this would be a link to the createEntry
method documentation.) This isn't necessary but might be helpful, particularly if invalid IDs are a problem for your users.
I added the information about invalid IDs and null values to the documentation for the return value. You could instead add it to the description of what the method does; there are reasonable arguments to be made for both ways. But explain it somewhere.
Note that the documentation of the (new) exception says both what it means (without revealing implementation details) and what might need to be done about it. In this case the caller can't do anything to fix the error, but he might want to notify his user or log the problem. We leave that decision up to him.
You were concerned about having to write a book to improve your API documentation, but all you really needed were another couple well-thought-out phrases. You don't want a book; good API documentation tells the reader all and only what he needs to know. This can be done concisely, and your readers will thank you for not making them read through a bunch of extra text.
Summary
Here are some key points to writing good API reference documentation:
- Document the contract, not the implementation.1
- Explain fuzzy verbs. What does "find" mean, versus "get"? Set users' expectations.
- Document restrictions on arguments or return values that aren't fully conveyed by the signature (like that an integer has to be positive, or in the range 1-100, etc).
- Cover failure and not just success. Can arguments be invalid? Can your code behave in abnormal ways even if the inputs are valid? How do you signal errors or other problems?
- Be thorough but not verbose. Don't repeat information that's clear from the signature.
1 To do this you need to determine what the contract actually is -- what promises are you making to your users? This is a large software-design topic beyond the scope of this question.
0 comment threads
As a software developer (C#, .NET, yada, yada), Monica's answer resounded nicely with me. (I don't have enough rep yet to comment on it, so my additions have to go here.)
I would add that I find great value in API documentation that is as explicit as it can be, but not overburdened with meaningless details. Further, it's very important to me that the documentation tells me what the API is supposed to do, not what it actually does. (Some will disagree with me about this; however, if the software doesn't do what the reference says it should be doing, that's a defect, requiring resolution.)
For example, if the return type of a method is List, will the method return null, or is it guaranteed to always return a non-null reference of zero or more items?
If a method takes a reference type, will it throw if the argument is null? If the argument is defined as out or ref, will it throw if the argument has not been initialized? If the argument is an array or list, will it throw if the sequence is empty? Does the method throw if a required configuration setting is missing or invalid? (If so, what configuration setting(s) does it depend on?)
These are things of immense value to a software developer. Having to track them down by wading through potentially hundreds of thousands of lines of code wastes time and money, and increases risk and the chance that I'll make a mistake.
Please, be as explicit as you can be, and no further.
This post was sourced from https://writers.stackexchange.com/a/10997. It is licensed under CC BY-SA 3.0.
0 comment threads