By Najaf Ali
The prime directive when writing anything is to empathize with your reader. Writing documentation is exactly the same. Write documentation for the person reading it, think about their problems and help them achieve their objectives.
Who exactly is going to be reading this documentation?
- Your future self after a week, months or years.
- Developers of varying levels of experience.
They're going to be reading the documentation for one or more of the following reasons:
- They want to set up the application for development locally.
- They've just started work on the project and want to get an overview of how it works.
- They're planning a new feature or change in application behaviour.
- They're diagnosing a bug.
Comprehensive, well-maintained documentation can help achieve all of the above. It makes your development on your system more productive. The price for that productivity is making the effort to write it in the first place and care and attention in keeping long-standing documentation up to date.
Places for documentation
README.md and docs/
README.md is the starting place for all documentation. When someone wants to find out something about a codebase, it's the logical first place to look.
README.md should provide at the very least an overview of what the application does. This is especially important if you have an organisation with lots of repositories. Getting a ten-second overview in
README.md allows developers who might be moving between repos to quickly get up to speed.
Depending on how old your project is you might keep all of your codebase-specific documentation in
README.md. For most non-trivial projects, however, documentation will quickly outgrow
README.md. Once the file goes over a few screenfuls of information, it's probably a good idea to break it down into coherent pieces.
README.md should then act as a table of contents for the rest of your documentation. No matter where it's located, you should be able to find what you're looking for by scanning
README.md. It's the landing page for your documentation so a developer looking for something should find it there.
When you start breaking your
README.md into smaller pieces, the natural place to put them is your
docs/ directory. You might consider putting codebase-specific documentation on a wiki. Keeping technical documentation close-to-hand alongside your code will probably make it more likely that developers keep it up to date.
By "commit message" we mean the full commit message rather than the summary. The summary is the short description of a commit that you're used to typing at the command line or in your version control client of choice. The message, on the other hand, is a text field that has no limit on size. You can type in as much information as you need to in the commit message.
When looking back at historical commits, a key question that developers are trying to answer is "what was that developer thinking?". Whether the original developer is right or wrong, having some clear indication of what they were trying to attempt makes comprehending their changes a lot easier.
We can try to make our intentions self-evident in our code through our choice and naming of abstraction. However, it doesn't hurt to reinforce this by making our intentions explicit in commit messages. By discussing the reasoning behind a particular changeset, we contribute to future technical decisions rather than hold them back.
Commit messages have the added benefit of never going out-of-date. They apply only to a particular snapshot of the codebase. When you change the code, adding a new commit message explaining your modifications is enough.
What to document
Exactly what to document depends on the details of the project. There are however some common themes to all Rails applications, so we're able to make some specific recommendations below. You can pick and choose from this list and come up with your own ideas. The aim here is to get you thinking about what to document.
Local development environment setup
Getting a local development environment set up is going to be the first interaction a new developer has with your codebase. The quicker we can get this process complete, the sooner we can have that developer making meaningful contributions to the project.
Rails developers are going to be familiar with the basics of creating a
database.yml, perhaps a
.env and running
bundle install and various rake commands to get the application running. You should still document these commands because there will probably be small deviations from the norm that you've become used to. You should also make a note if, for example, you're using
foreman to run your application locally.
If your application relies on other infrastructures like Elastic Search or Redis, then document how you would set them up to work with your application. You don't know what sort of unix your developer is using, so it's probably not worth going into detail about how to install these programs. It is, however, useful to make a note of how the application interacts with them. A simple note saying that it looks for Elastic Search at a particular port or uses a non-default port for Redis will be enough direction to help most developers get set up.
As developers, we typically want to automate everything as much as humanly possible. For the setup process, you might want to reconsider this. You don't know what operating system (at what version) the developer running your setup script is going to be running. No matter how much effort you put into writing it, there are going to be parts of your setup script that are incorrect or out-of-date occasionally.
The developer stepping through the setup process is going to be debugging it one way or another. This is far easier to do when you have a document with a set of steps and goals rather than with a typically anemic bash or ruby script. With a set of instructions for humans, a developer can easily slot in steps specific to their environment with the end result of the application running locally.
The goal of this part of your documentation is to get a developer to the point where they can run your full test-suite locally. If you don't have a test-suite, start writing a test suite. In the meantime, a reasonable goal might be to get the application running locally with core functionality working.
At the bottom of the setup documentation, there should a section titled "Troubleshooting" that lists possible sources of confusion and how to resolve them. As more developer move onto your project, they should update the document (and this list in particular) with any problems they encounter.
Tests and testing strategy
In this document, you might want to start by describing how to run existing tests, different sections of the test suite, all the tests in a given file, and a particular test. If you have a large test-suite, write about what tools you use to make them run faster.
What tests to write in what style are usually decided in two parts. First we have the developer writing the tests and then the developer reviewing the tests in a pull-request. Between them, they get to decide the coverage and types of tests that make it into the codebase. Over time, this can result in lots of different testing styles in a codebase that no one developer is particularly happy with.
If instead you write down your overall testing strategy, it makes clear how this application should be tested. Whether or not everyone on the team agrees about the tools being used or the level of coverage, documenting a testing strategy sets clear expectations for everyone.
In the testing strategy, you could describe exactly how you test all features. Some teams test every last line of code. Others are happy with high-level acceptance tests around key functionality, "unit" tests that exercise database interactions and not much else. Yet others might prefer to mock out much of the functionality in your subclasses of
ActiveRecord. Your team may prefer bits and pieces or all of the above. Whatever the general approach is, writing it down in a designated place can help new developers quickly start contributing in the existing style of the codebase.
Most Rails applications integrate with at least one third-party API. It's worth documenting exactly which API's you integrate with, how the integration works and where to look when making changes around an integration.
Integrations with third-party API's are a source of change in your application that you have little control over. API's can be deprecated, updated or experience downtime and there's little you can do about. You can, however, prepare for these scenarios, and good documentation is one way to do that.
For each API integration you have, make a note of:
- An overview of what you use the integration for.
- What libraries you use to integrate with API's.
- The URLs for engineering blogs of the company that provides the API.
- The URLs for documentation.
- Where in your code you make requests to the API within the request-response cycle.
- Where in your code you make requests to the API in background jobs.
- A guess as to how your system will behave if the API stopped responding (i.e. HTTP requests to it timed out).
- If you're using a feature toggle, how the application will behave if the integration is toggled off.
- A procedure for dealing with downtime in the API.
- When the last time you rotated API keys was and who did it.
Major non-CRUD features
Rails applications are designed for create, read, update, and delete functionality. Any Rails developer looking at a Rails project will be able to see from routes, controllers and models how basic CRUD functionality works in that application.
Any other features or complexity (inherent or otherwise) will be novel to your application. It's worth setting aside a document for each non-trivial feature or idea in your application that's worth taking the time to explain.
If you have a complex single-page application, for example, you could go into detail as to how it's implemented, including what patterns you use to organise the code. If you have models that have named state (usually described with a state machine implementation), it's worth including the state diagram and adding context to each state the model can be in. This is especially useful in orders in e-commerce applications, but is generally applicable to any user interaction that keeps a lot of state between requests.
Aside from technical considerations, if you operate in a business domain that the average developer may not have exposure too, write down the context of the domain that will help the developer understand the code better.
A step-by-step guide to deployment, all the way from creating a branch for a feature to deploying the feature to production. Don't forget to mention important stages in the process like running database migrations, setting production to "maintenance" mode if required, running database seeds, and flipping on feature toggles after pushing code.
Be careful to also mention parts of the process that are completely automated. Some teams, for example, have setups where all pull-requests get automatically deployed to a review environment. Without mentioning this in the deploy steps, the process can be needlessly surprising when you're just starting with the process.
Also mention exactly who is responsible for getting a feature to production. Some teams push their work to a development branch, and a release manager pulls that work into master on a regular basis before deploying to production. Other teams delegate the responsibility of launching a feature to production to the developer who created it. However you decide to do it on your team, make it clear in the deployment documentation, so there's no confusion.
Making documentation happen
"Just write" is unhelpful advice. People that write for a living have process in place to stay productive and produce good material. Writing good documentation is roughly as hard as being good at writing, so any improvement to your writing skill will make the documentation you produce better.
Choose some documentation that your codebase is missing or could do with improvement. Good ones that are relevant to projects are setup instructions or deployment.
For whatever you picked, ask yourself what would be useful to include for any developer working on the codebase. Based on your answers, make a rough outline of a document that hits all of those points.
Once you have a basic outline, start writing. Tend towards over-communicating rather than under-communicating and don't edit yourself just yet. If you feel like you've provided too much information about a given topic, then you're on the right track.
Once you've completed the first draft, do a few rounds of editing. Start by editing for spelling, grammar and punctuation. Continue by looking to improve word-choice, eliminate repetition and creating a coherent composition where one paragraph follows logically to the next.
Ask yourself if there's anything you've explained here that's better explained elsewhere. If there's another document in
docs/ where it would be better to elaborate on something, reference it instead. If it's about a particular library or program, it might be worth linking to official documentation rather than trying to detail it yourself.
After editing, as with any change you make to your codebase, it's worth getting a second set of eyes on it. Open a pull-request or otherwise ask another member of your team to give you feedback on it.
Repeat for whatever other documentation you think will be useful.
The primary complaint about documentation is that it goes out of date and becomes useless or dangerously misleading. The actual problem is that no one values it, and so no one keeps it up to date.
Valuing documentation is the same as valuing the long-term productivity of everyone who will ever work on the project. If you only care about your own productivity on the codebase today, then you won't value documentation.
The same argument could be made about tests. There's nothing, technically, stopping you from shipping code to production that has no tests around it. But we do this because we know about the long-term benefits of tests. We pay a price now and get more productivity later.
Documentation works in the same way. By making documentation a first-class citizen in your codebase that gets the same time and attention as tests, all developers on the project from now until forever will reap the benefits. Often that developer is you, three years later, scratching your head and trying to figure what you were thinking when you wrote some feature into the application.
There are three times where it's useful to update documentation:
- When you're using it and discover that it's incorrect.
- As part of making changes to your application.
- As part of regular time set aside for maintenance.
When you're using documentation and notice something is incorrect, you should either update it straight away or open a ticket of some kind saying someone should update it later. The more detail you can put into writing straight away, the better. You'll likely forget the mistake very quickly, and the mistake will linger in the documentation, ready to trip someone else up.
Along with passing tests and adherence to code style guidelines, documentation should be checked for as part of your review process. How much/little to write, what to include and how it's phrased should be put under as much scrutiny as it would be if you were writing code.
You should set regular time aside to do maintenance. You should use part of that time to write new documentation and keep your existing documentation up to date. You should use this time to delete documentation that is no longer relevant, update the details of anything that's changed and add documentation for things that need to be documented.