Skip to content

Document your code

B. K. Oxley (binkley) edited this page Sep 8, 2024 · 15 revisions
Document your code

Documentation

You do not just manage or work on code as an individual. It is important to share API and code details to others.

Consider code documentation as a tool for sharing with others, including:

  • Onboarding new contributors to your project
  • Sharing code internally for review or adoption
  • Sharing code to the public (open source or perhaps business partners)
  • Discussing code goals and APIs with manager roles

The goal is to document your code API for review, explanation, and sharing. Typically, internal "internal/business only" code is not documented but should be self-explanatory through private method and parameter naming. However, shared (public) entries to a project should be well-documented: saying too much in is better than saying too little, and lessens under-informed conversations.

When should you document code?

(And when should you not document code?)

This is a great question for your team and your environment! At extremes are two philosophies:

Document everything always
This comes from times when code was mostly read offline, good tools were scarce, specifications were cruc$ial, and every code change needed written detail to help the next person. We don't live on those times, but it still appears to lesser extents in certain situations such as teaching programming and government contracts. You may still encounter this in legacy code and standards.
Document nothing ever
This comes from more recent practices such as "self-explanatory" method names and variables, constant refactoring for comprehension and readability, assuming shared culture for handoffs among code creators and maintainers, and modern tools that lift restrictions on lengths for naming things.

These approaches are, well, extremes. There is nothing wrong with either approach! Both have circumstances where they shine, and circumstances where they are more, well, unshiny. In most circumstances, how do you pick among these? Your best approach to code documentation is "sailing between Scylla and Charbydis", meaning, it is not a OR question: it is an AND question. You want to document when it makes sense and to not document when it makes sense.

Here are some questions to think about on when to write or not write documentation:

  1. What is the agreement among those writing code?
    This is the top question.
    If those writing code do not agree amongst themselves on when to document or not, then the code base becomes a hodgepodge with well-understood areas, and "here be dragons" areas. This is similar to agreement on common code style, yet more important (see next question). A key demographic to consider: Future code maintainers (including your future self who forgot about details from 6 months ago).
  2. What is the agreement between those writing, and those reading or using code?
    If the users of your code do not understand it, or their seemingly random questions are unanswered, they will abandon for other code. When you think about source documentation, this question is best illustrated by:
    • Onboarding new developers
    • Users wanting to understand features and limitations at the code level
  3. What about external consumers of your code? A typical example is REST APIs on the web.
    Essentially no undocumented API is every adopted except under exceptional circumstances.

Some guidelines:

  1. Avoid boilerplate.
    If your tooling auto-generates documentation, let it! (An example of providing auto-generated javadoc is Lombok.) It will be boring, and not that informative, but is a start. You then review the documentation, and hand-edit key parts with useful information, or when far enough along with documentation, disable the auto-generation (so that undocumented code sticks out, and promises the reader no surprises).

    An example in Java are "getters/setters". These never need documentation unless they have further side effects, or do something surprising. An example of both side effect and surprising is in the JDK docs for System.setOut (and yet this documentation does not mention thread-safety for a mutable global static field!).

  2. Generally do not document internal code unless meaningful.
    When code is private or "default" scope, that means it is for use only by yourself, especially so for low-level code. If you have a special algorithm for sorting a list following an unusual order, can you capture that in the method's name? (But see next guideline.)

  3. In internal projects, most of your code is not documented per se: strive to have self-explanatory method and field names. However, this is also an opportunity to document whatever business rules, references to internal documentation, corner cases, good contacts, etc. are needed to maintain the code.

  4. For any public surface in shared code such as dependencies published internally or—expecially—publically (nearly all open-source projects fall in this category), always provide documentation. And that documentation is best both as javadoc jars to be downloaded, and as a web site to read (moreso on public projects than internal). Javadoc jars let the users' IDE provide immediate popup documentation on calls to your code.

Aside Documenting even hidden internal code can have later benefits when you call out concerns (and not write or generate boilerplate). This can be motivation for future developers to improve and clean up otherwise messy or questionable code. We have encountered this as we work on projects, and is an opportunity to follow the "Boy Scout principle" to leave code better than you found it. The previous authors' documentation and comments make this much easier for you.

Should you document test code?

This is an interesting question. You will encounter a lot of views on this, and many lean towards the extreme of "never document". Let us offer a yes/no suggestion (not advice):

  • When a test is obvious, do not document. This is most unit tests.
  • When a test is not obvious, or expresses key information, do document. This also highlights an opportunity to improve the tests, or refactor the production code, and may capture a key business rule (in which case the production code documentation should example further).
  • For unit tests, eschew documentation and lean on good test names. If a new developer on the project has difficulty understanding a unit test, this is a signal that either the test could be written better, or the production code under test needs attention.
  • For integration tests, lean on documentation. These higher-level tests capture important data or state changes, and are some of the most informative means to communicate key details. A common example is a bank account:
    • Adding funds increases the account balance
    • Removing funds decreases the account balance
    • At no point should other accounts be changed Your documentation for these should be sensible by those outside the code developers.

An example that comes up is mixing unit and integration tests. This may result in heavy use of mocking (test double/fakes, etc) that leads to "fat" tests heavily reliant on test setup. This is a clear signal that tests need improving, and likely the production code as well.

Example code for your build

This project is JVM-based, so the tool of choice is Javadoc.

Note

Java 23+ may offer Markdown as an alternative for current Javadoc format. Some other JVM languages provide this at present such as Kotlin.

Gradle

The simplest approach with Gradle is to use the withJavadocJar() method inside the top-level java block of your build.gradle such as this project does. This example Gradle project generates Javadoc for only the "main" source root (but not for "test" or "integrationTest" source roots):

java {
    // Other configuration first ...

    withJavadocJar() // Javadoc for "main" source code
    // More configuration needed for "test" or "integrationTest" source code
}

The outputs for this project:

HTML
  • Main code — ./build/docs/javadoc/index.html
Jar
  • Main code — ./build/libs/modern-java-practices-0-SNAPSHOT-javadoc.jar

Maven

Use the maven-javadoc-plugin in your pom.xml such as this project does. This example Maven project generates Javadoc for both the "main" and "test" source roots (but not for "integrationTest" source root):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>${maven-javadoc-plugin.version}</version>
    <executions>
        <execution>
            <id>javadoc-jars</id>
            <phase>package</phase>
            <goals>
                <goal>jar</goal> <!-- Javadoc for "main" source code -->
                <goal>test-jar</goal> <!-- Javadoc for "test" source code -->
            </goals>
        </execution>
    </executions>
</plugin>

The outputs for this project:

HTML
  • Main code — ./target/reports/apidocs/index.html
  • Unit test code — ./target/reports/testapidocs/index.html
Jar
  • Main code — target/modern-java-practices-0-SNAPSHOT-javadoc.jar
  • Unit test code — target/modern-java-practices-0-SNAPSHOT-test-javadoc.jar

Example code for your CI

GitHub

Using GitHub as an example, you should upload your build artifacts so that they are visible to others, HTML or jars. An example for this project is from the build implementing this feature.

This example uses Maven for building documentation for "main" and "test" classes, and the standard GitHub upload-artifact action. Adjusting for Gradle is straight-forward:

      - name: Save Javadoc HTML for main
        uses: actions/upload-artifact@v4
        with:
          name: javadoc-html-main
          if-no-files-found: error
          path: target/reports/apidocs/

      - name: Save Javadoc HTML Jar for main
        uses: actions/upload-artifact@v4
        with:
          name: javadoc-unittests-main
          if-no-files-found: error
          path: target/modern-java-practices-0-SNAPSHOT-javadoc.jar

      - name: Save Javadoc HTML for unit tests
        uses: actions/upload-artifact@v4
        with:
          name: javadoc-html-unittests
          if-no-files-found: error
          path: target/reports/testapidocs/

      - name: Save Javadoc HTML Jar for unit tests
        uses: actions/upload-artifact@v4
        with:
          name: javadoc-jar-unittests
          if-no-files-found: error
          path: target/modern-java-practices-0-SNAPSHOT-test-javadoc.jar
Clone this wiki locally