A little documentation goes a long way

The most important change in my working practices in recent months, making your life a little better

Over the past couple of years, we've seen a dramatic shift in the way Plone software (and Zope software, and, really, Python software in general) is packaged. Our code is now broken into dozens of packages, pulled in via a complex set of dependencies, and managed via powerful tools like Buildout.

However, the part of this whole story that excites me the most is the humble README.txt, combined with the practice of turning it into the "long description" in setup.py that appears on PyPI.

I didn't always appreciate this quite so much, but that little bit of documentation is the minimum you must do if you release something. It is not a substitute for more narrative documentation, which helps people learn and understand how the pieces fit together. However, the package README is the first line of defence for reference documentation. Once people have an idea about how your software fits together, they will find the package and expect to be told enough about to use it without having to resort to code archeology.

Let's look at some examples. First, there's the narrative "how does it all fit together" type documentation. This needs to be kept separate, and must be discoverable independently of code structure. A good example of mine is the Dexterity manuals.

Next, a bad example: The README for plone.theme. Ironically, I was trying to remember how to hook a browser layer up to a CMF theme (the raison d'etre of plone.theme) and got rather annoyed by the total lack of information in that README. I then realised who wrote the package. D'oh.

A better example is collective.beaker, a package for using the Beaker session and caching management framework in Zope 2. Notice how you both find out what the package is for, and how it is used. There are installation instructions, too, and hints on things like testing, where this package has some unusual implications.

More recently, I've started doing this very seriously, for example in the as-yet unreleased update to plone.app.registry and the new-and-exciting plone.app.caching. My coding cycle is now to do the interfaces module first, then code and tests in tandem, and then documentation. I normally start writing the README while waiting for test runs to complete. This cycle is then repeated, so that I update and revise the README as I refactor code. Finally, I read the rendered README through once as a sanity check before doing a release to PyPI.

Importantly, the documentation for the packages above has helped make them substantially better. By trying to explain how they worked, I found troubling inconsistencies and omissions that I'd failed to spot in my unit tests or interfaces.

Put differently, if something cannot be documented properly, it's crap. Find out for yourself before you foist it on everyone else, as Chris McDonough would no doubt agree with.

I'll leave you with some practical advice:

Have a README.txt in your package and make sure it's being included as the long_description in setup.py. The standard ZopeSkel templates takes care of this for you.

Make sure your README makes sense to someone other than yourself. Start by explaining what the package is for and why it exists. Mention briefly how to install it. Then explain the main use cases and how they are achieved. Even a few bullet points will go a long way. If your package is mostly user-facing, explain where people should expect to find its functionality in the UI. You can reference interfaces or code as necessary, but give people a fighting chance to see the big picture first.

Your README is not a doctest. You can have doctests too, and if you genuinely believe they are useful to someone trying to understand your package, you can append them too to the long_description after the README. They must be separate files. Doctests are often good for low-level documentation (and sometimes more appropriate for really basic packages that have no end-user or integrator relevance). But they are not by themselves sufficient for documentation, and can be hugely off-putting by tricking the reader into thinking they can figure out how to use the package, when in fact it only shows contrived test cases based on unrealistic test setup. (Yes, I haven't always been of this persuasion, but I've seen it go wrong too many times).

Make sure your README is valid reStructuredText. I dislike reST, but I've grown to tolerate it, and it's what PyPI renders. In TextMate, the reStructuredText bundle helps a lot, as I can now preview the rendered text with Cmd+Alt+Ctrl+P.

Have a HISTORY.txt in the long_description too (the standard templates again set this up for you), and maintain a change log once you've released the package. I cannot overstate how important it is to have a clear picture of how a package has changed, and in which versions new features or bug fixes were introduced.

Some people are instinctively scared by documentation. Don't be. It doesn't take long. It will likely pay off later when you come back to the code, and it'll certainly help others, who are now less likely to hassle you for basic help. You went through the trouble of writing a package and releasing it. Why do that if no-one can figure out how it works?

The standard pattern these days is to put the docs in the top level of the package. The problem is that all this stuff disappears upon install. Sure, it's nice to have good docs in pypi but it would be better if we kept it in the installed package too.

Is there an issue with discouraging top-level docs and instead promoting bottom-level docs which stay with the installed package?

Mmm, that's a good point. I normally reach for PyPI for this information, but obviously having it in the egg is also useful. It will be in the egg-info directory in the PKG_INFO file, but this is somewhat more obtuse.

I'd prefer to leave the files at the top level (more accessible when browsing the code) but find some way for setuptools to include it in a more sensible place in the packaged up egg. Anyone know how to do that?