Code documentation: comments, docstrings, and tests


Documentation is split in three parts: code documentation, technical documentation, and organization documentation.

They differ in purpose and audience, so I tend to write them and publish them in different places.

In this article, let’s talk about code documentation.

Inline code comments

Many beginner coders are wrongly tempted to write code following a repeating pattern: first a comment, then a line or couple lines of code. Then another comment, and another line of code, and so on.

I think this stems from college courses recommending you write your programs out in pseudocode before you go into your editor and write actual code.

And if you’ve ever asked ChatGPT or other LLMs to write some code for you, you know that they also tend to follow a similar structure.

You should not write comments before every line of code.

Inline comments have a time and place in your codebase, and it’s when you need to explain why something is done a certain way.

Generally speaking, if you are using inline comments to explain what a certain piece of code does, you should probably re-write the code so it’s easier to understand. Of course, there are exceptions to this. Sometimes a piece of code is complex and writing a comment to explain it is the best approach.

Docstrings and type hinting

Module, class, or function docstrings are comments placed at the start of a module, class, or function, and they’re used to:

  • Say what the module, class, or function does.
  • For functions, explain the parameters and return values.
  • Give usage examples.

When writing a module or function docstring you want to think about when you’ll be reading the docstring itself.

So, where are docstrings shown?

  • In your IDE’s autocomplete. When you import and use a function, your IDE will most likely show you the function’s docstring. You can then read it to remind yourself what the function does, what its parameters are, and how you should use it.
  • In your built documentation. When you write your app or library documentation more on that in the next article on Technical documentation (coming soon), you’ll likely want to include a full reference. This reference is built from the docstrings you wrote in the code.

I write my docstrings using the Google standard for Python. Here’s an example from our website:

def verify_paddle_billing_signature(request_body: str, signature_header: str) -> bool:
    """
    Verifies the Paddle signature for the given data.

    Each webhook Paddle sends us (when a subscription is created, cancelled, etc.) is
    signed with a signature. This signature is generated using the private key
    of the Paddle account. We can verify the signature using the public key of the
    Paddle account, which should be given to our running application as
    an environment variable.

    Args:
        request_body: The data from the webhook
        signature_header: The signature header from Paddle
    Returns:
        True if the signature is valid, False otherwise
    """

This is then shown like this, in the built documentation:

Screenshot of the built documentation website showing the function docstring

When I want to use a third party library and I can’t find a complete reference built from these docstrings in their documentation, that’s a huge bummer. It’s almost a given that you’ll have to find what a specific class or function does at some point during your usage of the library, and you just won’t be able to find it without digging into the source code.

Open source projects such as Flask are excellent for having a comprehensive reference. Aspire to that level of quality in your own projects.

I also use type hinting for Python, which means defining the types of parameters and return values for functions.

Doing so helps prevent common issues like passing the wrong type of argument to a function. If all your code and libraries have types, the entire code is more resilient.

You may think that writing the type hints is a time sink, but it actually might be a time saver, as you end up having to write fewer tests.

Talking about tests…

Tests

You can’t really talk about code documentation without talking about tests.

Often as developers, we’ll look to our test suite to remind ourselves how some piece of functionality should work. The tests are, in essence, the source of truth—even more so than the docstrings and written documentation.

That’s because anything written in a comment can become outdated when the code changes, and we can forget to edit the comments. But we can’t forget to edit the tests or else they’ll start failing!

Different kinds of test serve different documentation purposes.

I find most usefulness in unit and acceptance tests. Unit tests document how the unit (usually a function, class, or module) should work. Acceptance tests, which I usually write using BDD (Behaviour-Driven Development), document entire flows for how the entire app should work.

Other types of test, like integration tests and system tests, can also be useful. I find myself checking them less often, however.

Other kinds of documentation

In my next article, I’ll talk about Technical Documentation and Organization Documentation.

Thanks for reading!

— Jose