This is my preferred process for developing software. I call it ‘Documentation Driven Development’, or ‘Manual Page Driven Development’.

It is not an easy process to follow, but it generally results in high quality (i.e., well-designed, well-tested and well-documented) software. The reason it does so is because (IMHO):

  1. It offers a structured way to guide focus during design and implementation.
  2. It offers a structured way to incorporate insights from prior experience into the design, particularly when used alongside a suitable peer-review process.
  3. It emphasises fitness for use from the start.
  4. It scales reasonably well with additional participants.
  5. And finally, it is effective in building shared knowledge about the system within a team.
Process steps for documentation driven development.

Process steps for documentation driven development.

Please see the case study on the development of libelf below for a real-world example of the use of this process.

Documentation Driven Development Link to heading

This description below applies to library development. The process for developing standalone programs is similar and is described further down.

1. Write reference documentation Link to heading

I start by writing concise manual page(s) describing the new API.

If you are unsure about how to do this, you could look at *BSD’s canonical manual page structure for inspiration. A manual page written to this template would have the following sections:

NAME

SYNOPSIS

DESCRIPTION

RETURN VALUES (or EXIT STATUS for tools)

ERRORS

ENVIRONMENT

FILES (list related files)

EXAMPLES (cover the important use cases)

IMPLEMENTATION NOTES (if significant or useful)

DIAGNOSTICS (list possible error messages from tools)

SEE ALSO (mention related tools and APIs)

STANDARDS (if standards compliance is important)

HISTORY (if useful)

AUTHORS

CAVEATS (anything else users should know)

BUGS

SECURITY CONSIDERATIONS

The goal here is to examine the API being designed from multiple angles; the sections in the template above are possible starting points for such examination. I add additional sections to the list above as needed (e.g. a section titled CONCURRENCY for a multi-threaded API).

If I am designing a group of related APIs together I prepare draft manual pages for all of the APIs first. Once this is done, I check for consistency and coherence across these APIs.

Next, I write example code fragments to validate how the new APIs would be used. These code fragments would give my a sense of how the APIs would be used in practice. I look for gaps in the API set.

I keep notes about my assumptions about the environment in which the APIs will be implemented. These assumptions can then be checked by tests (see below).

I revise my draft manual pages till I am satisfied that I understand the system being designed well.

Step 1 should not be rushed IMHO — time spent here pays off in spades later.

2. Prepare assertions Link to heading

Next, I go through the manual pages that I have written and convert the descriptions of the APIs, the pre-conditions & post-conditions for API calls, and the assumptions about the environment into testable assertions.

3. Write tests Link to heading

I then write test cases that check these assertions.

At this step I often circle back to refine my draft manual pages as I gain additional insights into the system being designed.

4. Write target code Link to heading

The goal of this step is to get the test cases to pass 😎.

I often add more test assertions, or refine existing test assertions at this step.

As I know more about the implementation I update the ERRORS and IMPLEMENTATION NOTES sections of the manual pages prepared in step 1.

5. Prepare examples Link to heading

Useful example programs provide additional validation of the design.

I try to cover the expected usage of API in these example programs.

I also try to think of unusual ways that the APIs could be used. This step can lead to better error checking in code, and to clearer documentation.

6. Write user-facing documentation Link to heading

In this step I write user-facing documentation (e.g. tutorials) explaining how the system works.

User-facing documentation is a good place to explain the “conceptual model” underlying the API.

Once I can explain the “conceptual model” of the system in a user-centric way, I add a concise description of this model to the reference documentation created in step 1 above.

7. Prepare maintainer-facing documentation Link to heading

Once the structure of the implementation is known I then prepare an implementation guide for future maintainers (which could include myself). This can range from a simple README to more elaborate documentation.

Notes Link to heading

On Design Documents Link to heading

In my experience the traditional ‘design document’ (i.e., a document with a detailed prose description of the intended structure of the system) becomes stale and potentially misleading very quickly.

Instead, I prefer to focus on defining the boundaries of the design using testable assertions. These assertions then serve to guide future maintenance.

Using Documentation-Driven-Development For Programs Link to heading

For standalone programs, when preparing reference documentation (step 1) I try to write down how the tool would be used, where its inputs and outputs would reside, where its configuration information would be located, the data formats that it would accept, the assumptions the tool needs to make about its operating environment, and so on.

These descriptions would then be translated to testable assertions (step 2) and the remaining steps would be similar to my preferred process for developing APIs.

Open Source Case Study : libelf Development Link to heading

I used the process above for authoring BSD libelf.

  1. The ELF(3) API is a SysV Unix™ API that was already largely described in SysV Unix™ documentation. Nevertheless, manual pages still needed to be written since the documentation had to describe the behaviour of this specific libelf implementation (e.g. elf(3)).
  2. The tests were then written using the TET test toolkit.
  3. The implementation of libelf was then filled in.

Since there were many APIs to be implemented, I divided them into groups of related APIs and implemented each API group with its tests together.

  1. The initial ‘useful real-world programs’ were programs in my PmcTools toolkit (e.g. pmcstat).
  2. The user-facing tutorial was Libelf by Example.