Problems with keeping the documentation up-to-date are one of the reasons why we – programmers – don’t like writing it. At the same time, we don’t like when it is missing. However, this doesn’t have to be the case. I have always strived to document my projects and avoid “tribal knowledge” problems. Here, I would like to share a couple of tips and tricks how to write a maintainable documentation.

tl;dr;

To write a maintainable documentation, pay attention to the target group and the length. Use the right documentation techniques, such as ADR-s, and finally: automate whenever possible. It will help you keeping it up-to-date.

Tip 1: understand your target group

Some internal documents are horrible to read, because we forget whom we are writing for. If you know your target group, you can easily identify their needs, and focus on them. Even a short note may be worth 100x more than a lengthy article, if it helps people finding the solution to their problem. Below, we can see some examples of typical company target groups:

  • newcomers: they want to learn the high-level picture of the project and how to get started,
  • developers from other teams: they will be likely interested in the integration details, contribution rules, etc.
  • your own team: check-lists, decisions, main conventions.

All those groups have one thing in common: they have specific needs and they want to find the information quickly.

Tip 2: keep it short

Does a microservice need a 50-page documentation? Probably not. With the right target group in mind, we can easily notice that writing one would be a waste of time. Nobody would read it! The key to make a maintainable and up-to-date documentation is the length. Over the last years, I was looking for a good balance between it and the level of details. Finally, I came up with a habit of writing project README-s which are 6-8 pages long. Their form is also very simple – they are mostly pictures with comments to them. The structure looks like this:

  1. short information about the business functionality and the motivation to have it,
  2. C4 diagrams with additional descriptions (usually doing first 2 or 3 levels is enough),
  3. sequence diagrams for the most important processes,
  4. notes about performance,
  5. additional links, if necessary.

This form addresses the needs of the first two groups mentioned earlier, and can be also useful for the rest of the team. It is very expressive, despite the short length, and also easy to update. If anything important changes, we just need to redraw one or two pictures and update a couple of lines of text.

Tip 3: write ADR-s

Architecture decision records (ADR) are a technique of documenting the main technical decisions. I wrote more about them in a separate article, therefore I would not go into much detail here of what they are. They focus on the team needs rather than people from the outside. Their biggest strength is their immutability: once written, we do not modify them anymore. If we change a decision, we write a new ADR, and just mark the previous one as obsolete.

Let’s notice that ADR-s are complimentary to the README. It is harder to get a full picture of the project solely from ADR-s, therefore README serves as a snapshot of the current state of the project, compiled from multiple records. On the other hand, we don’t need to put all details from ADR-s into the README which helps us keeping the documentation up-to-date.

Sustainable documentation: complimentary relationship between ADR-s and README.
Relationship between ADR-s and project README

Tip 4: clean code

The code itself can serve as an always up-to-date and maintainable documentation at the tactical level. All clean code guidelines apply here, so I will not go much into details about them. However, I’d like to point out two important things about naming and Javadocs. Many IDE-s assist the programmer with generating parameter and variable names. For example, if we want to create a parameter of type LazyIssueFilterRepository in IntelliJ IDEA, we will likely get the default name “lazyIssueFilterRepository“. Where’s the catch? Accepting defaults leads directly to introducing Captain Obvious to our code.

public class IssueFilterManager {
   private final LazyIssueFilterRepository lazyIssueFilterRepository;

   public IssueManager(LazyIssueFilterRepository lazyIssueFilterRepository) {
      this.lazyIssueFilterRepository = lazyIssueFilterRepository;
   }

   private doSomething() {
      var issueFilters = lazyIssueFilterRepository.fetchIssueFilters();
      // ...
   }
}

I heard once an explanation for doing so: but now we can see what the type of the parameter is. However, there are three counter-arguments:

  1. all modern IDE-s will show you the type name on demand (even code review tools, such as Github, are better and better in doing so)
  2. repeating the same obvious information over and over again doesn’t help in focusing – this is not how we, humans, process written language.
  3. it violates many good naming practices.

Long ago, there was a naming convention called “Hungarian notation” which required prefixing variable names with type information, for example “sName” for string or “iAge” for integers. The reasons why we no longer use it today are the same. So, if everything in this class is about managing issue filters, the name repository is enough. This is an important tip to make your code more human-friendly.

4A: Javadoc used wisely

Many programmers hate javadoc and other forms of inline documentation due to another attack of Captain Obvious:

/**
 * Gets the minimum age.
 *
 * @return minimum age
 */
public int getMinimumAge() {
   return minimumAge;
} // repeating 'minimum age' 5 times in 8 lines is not enough,
  // so once more: minimum age

Of course, we should never do that. The tips about shortness and target groups also apply to javadoc comments. We can skip writing anything that can be easily deduced from the code. What is worth documenting, are intents and high-level use cases to help people link the code with e.g. business needs. Here’s a good example:

/**
 * Optimized for issue details page. The resolver can also fetch the data in
 * a different way X, but the current one is faster for 95% of requests, and the
 * performance loss is not significant for the remaining 5%.
 */
public class BestMatchingIssueResolver {
   // ...
}

Such javadoc comments are valuable for one more reason, important from the maintenance perspective. They don’t get old too quickly, and their validity should never be affected by properly understood refactoring.

Tip 5: use automation

Clean source code is a valuable source of information. By using right automation tools, we can extract it and present in a better form. A good example is the REST API documentation in OpenAPI (Swagger) format. All the modern Java web frameworks have the tools that can generate this documentation from the existing code structure. In Micronaut, the support is built into the framework. In Spring, we can use springdoc-openapi library. All we need is to enable them to get the basic API documentation for free, which is often enough.

Summary

One might ask, how efficient those tips are. I’ve been learning how to write maintainable documentation for years. I regularly get praised for it by other developers. Recently, I was able to give a technical introduction to a newcomer, solely by using diagrams from the README. Much of the documentation gets updated automatically, and applying the changes to the project README takes me 15-20 minutes. Of course, there may be also other useful tips and tricks for that. Do you have one? Feel free to share in the comments!

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments