Will ChenWill Chen
← Writingsystem design

How I rebuilt a blockchain's documentation

I rewrote a blockchain's documentation twice. The second time I read the whole thing I was replacing before writing a word.

Will ChenWill Chen6 min

Motivation

I joined Terra in January 2020 as the person responsible for developer experience, which in practice meant that if an outside programmer wanted to build something on the chain, everything they read on the way was mine. The chain already existed and had users. What it did not have was a way for a stranger to find out how any of it worked.

Documentation for a blockchain covers three kinds of reader who want different things. There are people running the machines that keep the network alive, called validators, who need operational instructions and will lose money if the instructions are wrong. There are people writing programs that talk to the chain, who need a reference for every message the chain accepts. And there are people who have heard of the project and want to know what it is before they commit an afternoon. One site has to serve all three without any of them feeling like they are reading somebody else's mail.

What a developer needs

In my first week I read an API design book and wrote down what I took from it. The list ranks four properties of a developer's experience, and the ranking is the part I still use:

  • Clarity, meaning intuitive visuals, which I put last.
  • Ease of use, meaning quick access and speed.
  • Stability, meaning reliability and consistency.
  • Function, meaning expected behaviour, which I put first.

The line I wrote under it was that this means you focus on function first and form second. Putting visual clarity at the bottom of a list about developer experience looks wrong until you have watched somebody try to use a beautiful reference that is missing three endpoints. A programmer reading documentation is not browsing. They arrived with a specific thing they are trying to make the machine do, and the site either contains that thing or it does not. Everything else is a tiebreaker.

I also wrote down a funnel for the whole job, four stages a developer passes through: hearing that the platform exists, learning to use it, actually building with it, and getting far enough to be glad they did. Documentation is the intervention for the first two, which is a narrower claim than "documentation is important" and it told me what to work on.

Choosing a model to follow

On May 15 I started the rewrite by cloning the docs of our own Python SDK. That is the obvious move. The files are right there, the styling matches, and the tone is already ours.

The next day I threw it out and started modelling the site on Stellar's documentation instead. Cloning your own adjacent docs feels like reuse and is actually inheritance. You get the previous author's idea of who the reader is, their idea of what needs explaining, and their idea of what can be assumed, and you get all of it silently, because none of those assumptions are written down anywhere in the files you copied. Going to a project outside your own organisation costs a few days of reading and buys you a second opinion about what a stranger needs.

Reading before reorganizing

On May 22 I set out to make a mind map and an outline, and I spent the first two hours of the day reorganizing content. Then I wrote this down:

Feels like I should read the validator documentation first before trying to reorganize it.

It reads as obvious written out like that, and I had already spent a morning proving it is not, because reading the thing you are about to replace feels like wasted motion when the plan is to throw it away. It is not wasted. A reorganisation is a claim about what the material is made of, and you cannot make that claim about material you have not read. What you do instead is move the shapes around until the table of contents looks tidy, which is a different activity that produces a document with a good table of contents and the same problems.

The order that came out of it was specification, then mind map, then outline, then content. Three structural passes before a sentence gets written for the page.

Outlines instead of cranking

In January I had given one of my own daily entries the header "Crank out a rough Client SDK Spec". In May I wrote this:

you're not supposed to crank them out .. you're supposed to write an outline and ask what do they want to know..

Same verb, four months apart, once as the plan and once as the diagnosis. Cranking treats documentation as a queue of articles with a length target, so the measure of a good day is how many you finished. That measure is available immediately and it is uncorrelated with whether anybody can now do the thing. Asking what they want to know produces a different artifact, because the unit stops being the article and becomes the question, and questions can be ordered by how early a reader hits them. Around the same time I decided the tutorial should come before the page explaining what Terra is, which only makes sense once the reader's question is the organising unit rather than the subject matter.

The upstream documentation gap

In July I got told the docs were not good, at a point where I had been working on them for six months. Sitting with it afterwards, I could separate four different problems that had been arriving as one bad feeling. The surface area was larger than one person's week. Everything had to be technically correct in a domain I was still learning. There was one person who could check the hard sections, and they were not available. And underneath those, the structural one: several sections could only be written by first working out how Tendermint and CosmWasm behaved, and at that point neither had written the documentation I needed. Both were young and moving fast, and documentation is the thing that lags when a project is still deciding what it is, so this is a description of a moment in 2020 rather than a complaint about either team.

Three of those four get better if I work harder or longer. The fourth does not, and that asymmetry is what made it worth separating them. You cannot write a good third-layer document when the two layers underneath it are undocumented. What you are actually doing in that situation is reverse-engineering somebody else's system and then explaining your own on top of the reconstruction, at your own risk, and no amount of better writing at my layer would have changed it.

What I wrote next, the same evening, was that instead of complaining I would focus on what was important, then fix the docs, then polish the English. Three phases in an order, which is the same separation I had just found for structure, applied to a bad evening.

Generating what could be generated

By October I was doing this a second time, for a different protocol, and the method had settled into a sequence. Understand the whole protocol as process flows before touching the site. Then structure. Then write. Then stop, and the stopping condition was explicit: I noted the point at which I was only revising for diminishing returns, and shipped.

The other thing that came out of October was a tool. Contracts on this chain publish machine readable descriptions of the messages they accept, in JSON Schema, so on October 21 I wrote something that reads those descriptions and rebuilds the original Rust types from them. The core is one recursive function that asks what kind of schema node it is looking at and returns the type it corresponds to:

def parse_schema(schema: dict, env: dict = {}):
    if "anyOf" in schema:
        variants = [parse_schema(branch, env) for branch in schema["anyOf"]]
        return Enum(name=schema.get("title"), variants=variants)

    if schema.get("type") == "object":
        members = {n: parse_schema(m, env) for (n, m) in schema["properties"].items()}
        return Struct(name=schema.get("title"), members=members)

    if schema.get("type") == "array":
        return Vec(parse_schema(schema["items"], env))

    if schema.get("type") == "string":
        return String()

A choice between alternatives becomes an enum, a set of named properties becomes a struct, a list becomes a vector, and the recursion handles the nesting. Once you have the type back you can print it as a reference page, and the page cannot drift from the contract, because it was derived from what the contract publishes about itself. It is a small tool and it points at a larger idea I had already written down in May, when I described wanting a documentation generator generator, meaning a kit for writing documentation generators rather than one more generator. Anything a machine can read about your system is documentation you do not have to keep in sync by hand, and the parts you do keep by hand are then the parts that actually needed a person.