Last time, MojoTech Developer David Leal discussed some reasons that should help you to at least consider writing living documentation. (The Why.) In this article, he explores what makes good living documentation. (The How.) Update: Be sure to check out Part Three: How You Can Implement Living Documentation
The examples are written in Gherkin, Cucumber’s living documentation format, because Cucumber is the tool I use at work. It is not perfect — in fact, it has a few annoying shortcomings — but I believe it is a step in the right direction. No matter what tool you think is best, this article will benefit you anyway.
Who writes the documentation? Who is our audience?
This article will focus on documentation that is meant to be written by developers, for developers. In the last article, I showed that developers benefit from this kind of documentation in several ways:
- Writing the documentation before we implement a feature forces us to make sure we understand the domain. It forces us to consider what we want to happen, and what we don’t want to happen, but can happen anyway. It forces us to consider edge cases. It forces us to define (or discover) the concepts that are relevant to the business domain. This, in turn, should guide the way we structure our code.
- The documentation gives the team a shared domain language, making it easier for everyone to communicate.
- Because the documentation is written in a way that is closer to natural language, it makes it easier to onboard new developers. Before we understand how things are done, it’s useful to understand what is done, and why it’s done. But this is not only useful to new team members. A quick glance at the relevant documentation will also help seasoned developers keep in mind things they should consider, so that they don’t discover halfway through implementation that the approach they took is wrong due to an aspect that they failed to consider.
- Finally, since this documentation is backed by tests, it tends to stay up-to-date.
How not to write living documentation
We have come a long way since Cucumber first introduced its
web_steps.rb. I remember writing my first features the way many did at the time:
Scenario: viewing a photo shoot Given a client with the following attributes: | id | 1 | | name | Guybrush Threepwood | And the following photo shoot: | id | 2 | | client_id | 1 | | name | Guybrush & Elaine | | shot_at | 2 days ago | And the following photos exist: | photo_shoot_id | name | | 2 | Put that voodoo ring away | | 2 | Here comes LeChuck | | 2 | Kiss the bride | And I am signed in as "Guybrush Threepwood" When I visit "/photo_shoots" And I click "Guybrush & Elaine" Then I should see "The cursed ring" And I should see "Here comes LeChuck" Then I should see "Kiss the bride"
This scenario, as it is written, has several problems.
First, it exposes a few implementation details: database-specific field names (
shot_at) and a URL.
When we write living documentation, we should not be concerned with implementation details. As I said above, living documentation is supposed to help you think about the domain, not the implementation. It should be written before you start writing code.
If I had an hour to solve a problem I'd spend 55 minutes thinking about the problem and 5 minutes thinking about solutions.
— Albert Einstein
Second, it’s not clear why
shot_at is required. What happens if we remove it? And, whatever does happen, why does it happen?
Third, the scenario is written in an imperative manner, a bit like a user manual. “Visit a”, “click b” they describe how to do something, but they don’t describe what the user is doing — viewing a photo shoot. Imagine that there are more scenarios that use the photo shoot page. Are we going to keep repeating the same steps over and over again?
Plus, “URLs”, “visiting”, and “clicking” are also UI concepts — another kind of implementation detail. We don’t care about links, text fields or combo boxes (unless a business rule explicitly mentions these details.)
Fourth, do you think that scenario is readable at all? I certainly don’t. The combination of problems I described above, and the unpleasant duplication (see how the photo names are repeated twice?), make the scenario boring and artificial sounding. I can understand why a software developer would write it this way, but I can’t imagine why anyone (even other software developers) would want to read it. It will become another maintenance burden, instead of being regarded as an important piece of documentation.
How to write living documentation
So now it’s time to try to fix the problems mentioned above. Here’s the same scenario, written the way I currently believe it should be written:
Scenario: letting clients view photos When a client views a completed photo shoot Then they should see the list of all its photos
This scenario is much shorter than its previous iteration. Each bit of information is there for a reason.
A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.
— Antoine de Saint Exupéry
Not to say it’s perfect, but at least I don’t know what else I could take away.
But now we have a few problems. For example, nowhere is it mentioned that the client must be signed in. And what’s a “completed” photo shoot?
A tale of two definitions
I’ve come to believe that living documentation is mainly about 2 different areas:
Entities in the system, and the set of all relevant properties that comprise them. Relevant properties are those that are part of the business domain (i.e, they’re not simply an implementation detail.)
The behavior of the system. This is where someone, or something, under a certain role causes the system to move from a state to another. This state, in turn, can be divided into state of the UI, and state of the data.
I believe the scenario above is about a behavior of the system. In this case, only the UI changes. As will often be the case, we need to complement these behavioral scenarios with entity-based scenarios. So we’ll first need to define what it means for someone to be a client:
Feature: Client A client is someone with an account in the system who has been, or will be the subject of a photo shoot. Whenever a client is mentioned without any other qualification (e.g., a signed out client), it should be assumed that the client is signed in.
So we have just unambiguously defined an entity in the system and the context in which that entity is usually referenced. As a nice side effect, this eliminates the need to keep repeating steps like I am signed in as a client.
One down, one to go. What exactly is a “completed” photo shoot? Before I explain, let me ask you: where would you look?
If you answered
photo_shoot.feature, you’re absolutely right!
Before I show the definition of a completed photo shoot, remember how the first iteration we discussed contained an explicit reference to
shot_at? I’ll fill you in on why it’s used:
shot_at is used by the UI code to determine whether to show the photos listing or a message saying the photo shoot hasn’t happened yet, and some tips to make the most out the photo shoot (how to dress, what to expect, etc). That could look like this, if using Ruby on Rails:
<% if @photo_shoot.shot_at %> <!-- show list of photos --> <% else %> <!-- show photo shoot tips --> <% end %>
And that is why it’s being set in the scenario. The problem is, we are substituting an implementation detail for an actual business concept. It doesn’t matter which field we use — what we are really interested in knowing is whether a photo shoot took place already. To do that, we must come up with a term that will unambiguously express that state. In this article, after considering the problem for a few seconds, I decided to go with “completed”. In real life work, some terms will be easy to come up with, others will likely require some thought and discussion.
So, now that we have a fitting term, we can refactor our template code:
<% if @photo_shoot.completed? %> <!-- show list of photos --> <% else %> <!-- show photo shoot tips --> <% end %>
But in real life this refactoring would probably not have been necessary. Having written the scenario first, I would be aware of the appropriate term during implementation, and naturally, I would have used it the first time.
Feature: Photo Shoot [...] Scenario: ”completed” state A photo shoot is said to be “completed” if * it contains photos and has been assigned a shooting date
(As an aside, * is a valid Cucumber step marker, just like Given, When, and Then.)
This is acceptable, but there is a potential problem: if the photo shoot doesn’t have a shooting date, what state is the photo shoot in? If only the completed state matters, it’s possible that doesn’t make a difference. On the other hand, you’re left with uncertainty that may come back to bite you in the future. Let’s say we decided that there are no other relevant states. We can simply say that a photo shoot is “not completed.” We can make that decision explicit:
Scenario: “not completed” state A photo shoot is said to be “not completed” if * it contains photos but no shooting date has been assigned * it has an assigned shooting date but it doesn’t contain any photos * it contains no photos and no shooting date has been assigned
In this article, I’m not dealing with validation issues, like entering a shooting date that is in the future, or trying to set a shooting date without associating any photos. Are any of those possible? We don't know but, if this was actual documentation that we were writing, we would find out. The response to these questions would likely influence our understanding of what the completed state means, but it also illustrates how useful it is to explore the domain we’re working on. Once we’ve properly defined the problem (and recorded that understanding using living documentation), it should be easier for us to come up with better solutions.
These two examples illustrate how we can use living documentation to document the domain’s entities. This approach gives us the following benefits:
- We now have authoritative definitions for what it means to be a client, and what it means for a photo shoot to be completed. If we follow appropriate conventions (like defining what it means to be a client in client.feature) we make it really easy for people to infer where they can find relevant documentation.
- Confident that concepts are properly defined, we can do away with all unnecessary detail, write scenarios that are more concise, and more resilient.
Comparing the two scenarios
With the definitions out of the way, we can go back to compare the two scenarios:
First, all implementation details are gone. There are no more references to id columns or URLs.
Second, we replaced the reference to
shot_at with the more concise and understandable concept of photo shoot “completeness.”
Third, the imperative steps have been replaced with the concept of viewing a photo shoot. This is another implementation-to-concept kind of substitution, and — again — it helps us discuss the scenario (and the concept) with other people.
Fourth, the scenario is now much easier to read and understand. The first version of this scenario was written in the manner of “show, don’t tell” — it forces the user to infer the business rule. But, when writing living documentation, this technique is not helpful. We need to tell, concisely and unambiguously, what each feature is about. We don’t want to waste our readers’ time, and force them to do unnecessary work.
I’d like to conclude with a few clarifications about the process of writing living documentation:
First, this is not Big Design Up Front. I’m not arguing that you should document the entire system in one sitting and only then proceed to implementation.
Second, this is all about the domain. Consequently, I don’t believe it should be relegated to a unit test framework, just because it deals with a single entity in the system. Living documentation is not simply about end-to-end tests. It’s about documenting your system properly, and basing that documentation on tests that give you enough confidence to believe that the system is working.
I hope this article has helped clarify how good living documentation looks, and how it can help you understand the problem domain better without writing a single line of code. I hope to continue exploring this subject in future articles.
If you made it all the way to the bottom of this article, we probably want to hire you. Check out our jobs page.